OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

QuickRepliesTest.php

4 KB
<?php

declare(strict_types=1);

use OpenNIT\AI\QuickReplies;

/**
 * Schnell-Antworten des KI-Assistenten.
 *
 * Sie sollen als Schaltflächen erscheinen. Wird der Block nicht erkannt, steht
 * er stattdessen als Quelltext in der Unterhaltung – sichtbar unbrauchbar. Die
 * Modelle halten die vereinbarte Form unterschiedlich genau ein, deshalb zählt
 * hier die Nachsicht beim Lesen.
 */
return static function (): void {

    $wurzel = dirname(__DIR__);
    $quelle = static fn (string $pfad): string => (string)file_get_contents($wurzel . '/' . $pfad);

    test('Die vereinbarte Form wird gelesen', function () {
        $gelesen = QuickReplies::lesen("Wobei darf ich helfen?\n\n```suggestions\nTickets\nBestellungen\n```");

        assertSame(['Tickets', 'Bestellungen'], $gelesen['vorschlaege']);
        assertSame('Wobei darf ich helfen?', $gelesen['text']);
    });

    test('Das Kennwort in der ersten Zeile zählt ebenso', function () {
        // So schreiben es kleinere Modelle – bisher blieb der Block als
        // Quelltext stehen und war nicht anklickbar.
        $gelesen = QuickReplies::lesen("Hallo!\n\n```\nsuggestions\nTickets\nBestellungen\nInventar\nVerträge\nSonstiges\n```");

        assertSame(['Tickets', 'Bestellungen', 'Inventar', 'Verträge', 'Sonstiges'], $gelesen['vorschlaege']);
        assertSame('Hallo!', $gelesen['text']);
    });

    test('Aufzählungszeichen und Auszeichnung fallen weg', function () {
        $gelesen = QuickReplies::lesen("```suggestions\n- Tickets\n* Bestellungen\n1. Inventar\n`Verträge`\n```");

        assertSame(['Tickets', 'Bestellungen', 'Inventar', 'Verträge'], $gelesen['vorschlaege']);
    });

    test('Ein gewöhnlicher Code-Block bleibt stehen', function () {
        // Sonst verschwände ein Beispiel aus der Antwort.
        $antwort = "So geht es:\n\n```php\n\$x = 1;\n```";
        $gelesen = QuickReplies::lesen($antwort);

        assertSame([], $gelesen['vorschlaege']);
        assertSame($antwort, $gelesen['text']);
    });

    test('Ohne Block bleibt der Text unberührt', function () {
        foreach (['', 'Nur Text.', "```\n\n```", "```suggestions\n\n```"] as $antwort) {
            $gelesen = QuickReplies::lesen($antwort);
            assertSame([], $gelesen['vorschlaege'], 'Angenommen: ' . var_export($antwort, true));
            assertSame(trim($antwort), $gelesen['text']);
        }
    });

    test('Mehr als eine Handvoll Vorschläge wird gekürzt', function () {
        $viele   = implode("\n", array_map(static fn (int $i): string => 'Option ' . $i, range(1, 12)));
        $gelesen = QuickReplies::lesen("```suggestions\n" . $viele . "\n```");

        assertSame(6, count($gelesen['vorschlaege']));
        assertSame('Option 1', $gelesen['vorschlaege'][0]);
    });

    test('Beide Antwortwege nutzen denselben Leser', function () use ($quelle) {
        // Der Chat antwortet gestreamt wie am Stück; wird nur einer gepflegt,
        // hängt es vom Weg ab, ob die Schaltflächen erscheinen.
        $controller = $quelle('src/Controllers/AIChatController.php');

        assertSame(2, substr_count($controller, 'QuickReplies::lesen'), 'Nicht beide Wege lesen die Vorschläge gleich');
        assertFalse(str_contains($controller, "'/```suggestions"), 'Es wird noch mit einem eigenen Muster gesucht');
    });

    test('Die Oberfläche entfernt den Block in beiden Formen', function () use ($quelle) {
        $js = $quelle('public/assets/js/ai-chat.js');

        $von       = (int)strpos($js, 'function formatMessage(');
        assertTrue($von > 0, 'Die Formatierung fehlt');
        $abschnitt = substr($js, $von, 1200);

        assertTrue(str_contains($abschnitt, "=== 'suggestions'"), 'Die erste Zeile des Blocks wird nicht geprüft');
        assertFalse(str_contains($abschnitt, '/```suggestions[\s\S]*?```/g'), 'Es bleibt beim starren Muster');
    });
};