Files
PolyTraderSharp/src/PolyTrader.App.Avalonia/Views/Modules/SupervisorWindow.axaml.cs
RichardandClaude Opus 5 dd8da3fd3f Arbeitsstand gesichert: Deploymentcenter-Integration (D-0 bis D-5) und Einfenster-Shell
Sicherungscommit vor dem Aufraeumen des Repos, damit nachvollziehbar bleibt,
welcher Stand vor der Bereinigung galt. Build gruen, 476 Tests gruen.

Zwei Straenge, die sich ueber .csproj, Program.cs und appsettings.json
ueberschneiden und darum gemeinsam abgelegt werden:

Deploymentcenter-Integration (P3c, Plan D-0 bis D-5 code-seitig fertig):
- Deploymentcenter.Client 2.5.0 als lokales Paket, Source-Mapping erweitert
- DeploymentcenterOptions, DeploymentcenterErrorReporter, LicenseGate/LicenseCli
- WatchdogHeartbeatService auf die Deploymentcenter-API umgestellt
  (version, os, checks, metrics, status stopped)
- Security: MasterKeyResolver, SecretRedactor, FilePermissions
- Directory.Build.props mit zentraler Version 0.1.0 (Packager-Versionsdisziplin)
- deploy/: Packager-Vorlage und systemd-Unit; echte Zugangsdaten bleiben
  ueber .gitignore aussen vor
- setup.json fuer die Erstinstallation
- UMSETZUNGSPLAN-Deploymentcenter-Integration.md; ANALYSE-Linux-Portierung.md
  verweist auf den neuen Plan

Einfenster-Shell (UI-Redesign):
- ShellWindow + ShellNavModel als Seitenleisten-Shell
- WindowMenuBar und WindowMenuModel entfallen
- Modul- und Kernfenster auf die Shell-Einbettung angepasst

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 10:42:22 +02:00

190 lines
7.4 KiB
C#

using System;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using PolyTrader.App.Avalonia.ViewModels;
using PolyTrader.Core.Analytics;
using PolyTrader.Core.Modularity;
using PolyTrader.Modules.Supervisor.Agent;
using PolyTrader.Modules.Supervisor.Counterfactual;
using PolyTrader.Modules.Supervisor.Persistence;
using PolyTrader.Modules.Supervisor.Services;
using PolyTraderSharp.Services;
namespace PolyTrader.App.Avalonia.Views.Modules
{
/// <summary>
/// Supervisor: Analyse-Chat gegen den Agenten, Dossiers je Signal, gespeicherte Berichte und
/// Counterfactuals. Layout vollständig in SupervisorWindow.axaml.
/// </summary>
public partial class SupervisorWindow : UserControl
{
private readonly ObservableCollection<ChatEntry> _chat = new();
private readonly ObservableCollection<SignalSummary> _signals = new();
private readonly ObservableCollection<SupervisorReport> _reports = new();
private readonly ObservableCollection<CounterfactualRecord> _counterfactuals = new();
private readonly SupervisorAgent _agent = null!;
private readonly DossierService _dossiers = null!;
private readonly ISupervisorReportRepository _reportRepo = null!;
private readonly ISupervisorCounterfactualRepository _cfRepo = null!;
private readonly TerminalLogger _logger = null!;
public SupervisorWindow() => AvaloniaXamlLoader.Load(this);
public SupervisorWindow(IModuleUiHost host, SupervisorAgent agent, DossierService dossiers,
ISupervisorReportRepository reportRepo, ISupervisorCounterfactualRepository cfRepo,
TerminalLogger logger) : this()
{
_agent = agent;
_dossiers = dossiers;
_reportRepo = reportRepo;
_cfRepo = cfRepo;
_logger = logger;
this.FindControl<ItemsControl>("chatLines")!.ItemsSource = _chat;
this.FindControl<DataGrid>("gridSignals")!.ItemsSource = _signals;
this.FindControl<DataGrid>("gridReports")!.ItemsSource = _reports;
this.FindControl<DataGrid>("gridCounterfactual")!.ItemsSource = _counterfactuals;
var cbProfile = this.FindControl<ComboBox>("cbProfile")!;
cbProfile.ItemsSource = SupervisorProfiles.All.ToList();
cbProfile.SelectedIndex = 0;
this.FindControl<Button>("btnSend")!.Click += async (_, _) => await AskAsync();
this.FindControl<Button>("btnClearChat")!.Click += (_, _) => _chat.Clear();
this.FindControl<Button>("btnDossierRefresh")!.Click += (_, _) => LoadSignals();
this.FindControl<Button>("btnDossierOpen")!.Click += (_, _) => OpenDossier();
this.FindControl<Button>("btnReportsRefresh")!.Click += (_, _) => LoadReports();
this.FindControl<Button>("btnCfRefresh")!.Click += (_, _) => LoadCounterfactuals();
// Auswahl im Signal-Grid übernimmt die SignalId ins Eingabefeld.
this.FindControl<DataGrid>("gridSignals")!.SelectionChanged += (_, _) =>
{
if (this.FindControl<DataGrid>("gridSignals")!.SelectedItem is SignalSummary s)
this.FindControl<TextBox>("tbSignalId")!.Text = s.SignalId;
};
LoadSignals();
LoadReports();
LoadCounterfactuals();
}
// ===== Analyse =====
private async System.Threading.Tasks.Task AskAsync()
{
var tbQuestion = this.FindControl<TextBox>("tbQuestion")!;
string question = tbQuestion.Text?.Trim() ?? string.Empty;
if (question.Length == 0) return;
var btn = this.FindControl<Button>("btnSend")!;
var status = this.FindControl<TextBlock>("lblChatStatus")!;
AddChat("Du", question, ChatRole.User);
tbQuestion.Text = string.Empty;
btn.IsEnabled = false;
status.Text = "Der Supervisor denkt nach …";
try
{
var profile = this.FindControl<ComboBox>("cbProfile")!.SelectedItem as SupervisorProfile;
string? model = this.FindControl<TextBox>("tbModel")!.Text?.Trim();
if (string.IsNullOrWhiteSpace(model)) model = null;
// Zwischenmeldungen des Agenten (Tool-Aufrufe) laufen über die Statuszeile.
var progress = new Progress<string>(m => Dispatcher.UIThread.Post(() => status.Text = m));
var result = await _agent.AskAsync(question, model, progress, profile);
AddChat($"Supervisor ({profile?.Name ?? "Allgemein"})", result.Answer, ChatRole.Agent);
status.Text = $"{result.ToolInvocations.Count} Tool-Aufruf(e) · " +
$"{result.PromptTokens + result.CompletionTokens} Token.";
}
catch (Exception ex)
{
AddChat("Fehler", ex.Message, ChatRole.Error);
status.Text = "Anfrage fehlgeschlagen.";
_logger.Error($"Supervisor-Anfrage fehlgeschlagen: {ex.Message}");
}
finally
{
btn.IsEnabled = true;
LoadReports(); // die Anfrage wurde als Bericht abgelegt
}
}
private void AddChat(string who, string text, ChatRole role)
{
_chat.Add(new ChatEntry { Header = $"[{DateTime.Now:HH:mm:ss}] {who}", Text = text, Role = role });
this.FindControl<ScrollViewer>("chatScroll")!.ScrollToEnd();
}
// ===== Dossiers =====
private void LoadSignals()
{
try
{
_signals.Clear();
foreach (var s in _dossiers.RecentSignals(200)) _signals.Add(s);
}
catch (Exception ex)
{
_logger.Error($"Signale konnten nicht geladen werden: {ex.Message}");
}
}
private void OpenDossier()
{
string signalId = this.FindControl<TextBox>("tbSignalId")!.Text?.Trim() ?? string.Empty;
var target = this.FindControl<SelectableTextBlock>("txtDossier")!;
if (signalId.Length == 0)
{
target.Text = "Bitte eine SignalId auswählen oder eingeben.";
return;
}
try
{
var dossier = _dossiers.BuildForSignal(signalId);
target.Text = DossierBuilder.ToMarkdown(dossier);
}
catch (Exception ex)
{
target.Text = $"Dossier konnte nicht erstellt werden: {ex.Message}";
}
}
// ===== Berichte / Counterfactual =====
private void LoadReports()
{
try
{
_reports.Clear();
foreach (var r in _reportRepo.GetRecent(200)) _reports.Add(r);
}
catch (Exception ex)
{
_logger.Error($"Berichte konnten nicht geladen werden: {ex.Message}");
}
}
private void LoadCounterfactuals()
{
try
{
_counterfactuals.Clear();
foreach (var c in _cfRepo.GetRecent(500)) _counterfactuals.Add(c);
}
catch (Exception ex)
{
_logger.Error($"Counterfactuals konnten nicht geladen werden: {ex.Message}");
}
}
}
}