Quelltext des aktuellen Standes
<?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');
});
};