KI-Backend: Umfangs-Umschalter + Prompt Caching (4.5.15/4.5.16)

Umfangs-Umschalter: Checkbox "Auch bestehende Stundeninhalte anpassen" im
AiAssistDialog steuert, ob die KI bestehende Stunden inhaltlich ändern darf oder
die Einheit nur um neue Stunden erweitern soll. Zweifach durchgesetzt (Systemprompt
+ hartes client-seitiges Verwerfen in ApplyResponse), nicht nur der KI-Antwort
vertraut. Dabei auch einen Bug gefixt: eine bereits "Durchgeführt" markierte Stunde
wurde durch eine übernommene KI-Änderung stillschweigend auf "Geplant" zurückgesetzt.

Prompt Caching: der Systemprompt ist jetzt vollständig statisch (Voraussetzung für
Caching) und wird von AnthropicProvider.php als "cache_control: ephemeral" markiert
— wiederholte Anfragen innerhalb der 5-Minuten-TTL zahlen nur den reduzierten
Cache-Read-Preis. transactions-Tabelle und Preistabelle um Cache-Token-Spalten
erweitert, Migration für bereits deployte Installationen beigelegt.
This commit is contained in:
2026-08-16 16:06:52 +02:00
parent 4437f951d9
commit 1928916eac
14 changed files with 328 additions and 38 deletions
@@ -1113,6 +1113,7 @@ public partial class AiAssistDialogViewModel : ObservableObject
private readonly Unit _unit;
[ObservableProperty] private string _instruction = "";
[ObservableProperty] private bool _allowModifyingExisting = true;
[ObservableProperty] private bool _isBusy;
[ObservableProperty] private string _errorMessage = "";
[ObservableProperty] private bool _hasResults;
@@ -1143,12 +1144,19 @@ public partial class AiAssistDialogViewModel : ObservableObject
ErrorMessage = ""; IsBusy = true;
try
{
var response = await _aiPlanning.RequestPlanAsync(_unit, Instruction, token);
var response = await _aiPlanning.RequestPlanAsync(_unit, Instruction, token, AllowModifyingExisting);
var existingIds = _lessons.GetByUnit(_unit.Id).Select(l => l.Id).ToHashSet();
ReviewItems.Clear();
foreach (var l in response.Lessons)
ReviewItems.Add(new AiLessonReviewItem(l, isNew: l.Id is not { } id || !existingIds.Contains(id)));
{
var isExisting = l.Id is { } id && existingIds.Contains(id);
// Falls der Modus Änderungen an bestehenden Stunden verbietet, aber die KI die
// Anweisung trotzdem ignoriert hat: gar nicht erst zur Übernahme anbieten, statt
// dem Nutzer eine Auswahl zu zeigen, die ApplyResponse ohnehin verwerfen würde.
if (isExisting && !AllowModifyingExisting) continue;
ReviewItems.Add(new AiLessonReviewItem(l, isNew: !isExisting));
}
Summary = response.Summary;
HasResults = true;
}
@@ -1160,7 +1168,7 @@ public partial class AiAssistDialogViewModel : ObservableObject
private void Apply()
{
var accepted = ReviewItems.Where(i => i.Accepted).Select(i => i.Source).ToList();
foreach (var lesson in _aiPlanning.ApplyResponse(_unit, accepted))
foreach (var lesson in _aiPlanning.ApplyResponse(_unit, accepted, AllowModifyingExisting))
_lessons.Save(lesson);
Result = true;
}