Neue Write-Tools create_time_entry, create_grade_entry und update_student_group_assignment schreiben nie direkt: jeder Aufruf zeigt zuerst einen menschenlesbaren Bestätigungsdialog (bestehender ConfirmDialog, über Dispatcher.UIThread aus dem Pipe-Session-Thread angezeigt) und schreibt erst nach Bestätigung, mit 2-Minuten-Timeout gegen eine hängende Session. Zusätzliches Read-Tool get_lesson_plans. create_note bewusst nicht umgesetzt (kollidiert mit dem bestehenden Dokumentations-Ausschluss aus Phase 1), create_lesson_plan/ update_lesson_plan wegen der Modellkomplexität von Lesson zurückgestellt (siehe TODO.md 4.5.26). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
50 lines
2.2 KiB
C#
50 lines
2.2 KiB
C#
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.ApplicationLifetimes;
|
|
using Avalonia.Threading;
|
|
using LehrerApp.Desktop.Views.Shared;
|
|
|
|
namespace LehrerApp.Desktop.Services.Mcp;
|
|
|
|
/// <summary>
|
|
/// Produktive <see cref="IMcpConfirmationService"/>-Implementierung: zeigt den bestehenden
|
|
/// <see cref="ConfirmDialog"/> (Views/Shared) über dem Hauptfenster an. Der aufrufende Tool-Handler
|
|
/// läuft auf einem Hintergrund-Thread (MCP-Pipe-Session in <see cref="McpServerHostedService"/>),
|
|
/// deshalb Marshalling über <see cref="Dispatcher.UIThread"/>.
|
|
///
|
|
/// Ohne Reaktion des Nutzers würde die Pipe-Session (und damit der wartende KI-Client) unbegrenzt
|
|
/// hängen bleiben — nach <see cref="Timeout"/> wird der Dialog automatisch geschlossen und die
|
|
/// Änderung als abgelehnt gewertet.
|
|
/// </summary>
|
|
public sealed class AvaloniaMcpConfirmationService : IMcpConfirmationService
|
|
{
|
|
private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(2);
|
|
|
|
public async Task<bool> ConfirmAsync(string title, string message, CancellationToken ct)
|
|
{
|
|
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
|
return false;
|
|
|
|
// Die gesamte Warte-/Timeout-Logik läuft als ein Stück innerhalb des UI-Thread-Callbacks:
|
|
// Avalonias Dispatcher-Synchronisationskontext sorgt dafür, dass die Fortsetzung nach
|
|
// "await Task.WhenAny(...)" wieder auf dem UI-Thread läuft, sodass dialog.Close() dort
|
|
// sicher aufgerufen werden kann.
|
|
return await Dispatcher.UIThread.InvokeAsync(async () =>
|
|
{
|
|
var dialog = new ConfirmDialog
|
|
{
|
|
DataContext = new ConfirmDialogInfo { Title = title, Message = message, ConfirmText = "Übernehmen" },
|
|
};
|
|
var dialogTask = dialog.ShowDialog<bool>(owner);
|
|
var timeoutTask = Task.Delay(Timeout, ct);
|
|
var completed = await Task.WhenAny(dialogTask, timeoutTask);
|
|
if (completed != dialogTask)
|
|
{
|
|
dialog.Close(false);
|
|
return false;
|
|
}
|
|
return await dialogTask;
|
|
});
|
|
}
|
|
}
|