diff --git a/src/PolyTrader.App.Avalonia/App.axaml b/src/PolyTrader.App.Avalonia/App.axaml
index 24b49b8..86d9486 100644
--- a/src/PolyTrader.App.Avalonia/App.axaml
+++ b/src/PolyTrader.App.Avalonia/App.axaml
@@ -1,7 +1,78 @@
+ x:Class="PolyTrader.App.Avalonia.App">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18,11 +89,11 @@
-
-
+
@@ -50,13 +121,18 @@
-
+
+
+
+
diff --git a/src/PolyTrader.App.Avalonia/App.axaml.cs b/src/PolyTrader.App.Avalonia/App.axaml.cs
index 3274a6d..750ed81 100644
--- a/src/PolyTrader.App.Avalonia/App.axaml.cs
+++ b/src/PolyTrader.App.Avalonia/App.axaml.cs
@@ -28,6 +28,12 @@ namespace PolyTrader.App.Avalonia
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && Services != null)
{
+ // Farbschema aus den Server-Settings übernehmen, BEVOR das erste Fenster entsteht –
+ // sonst blitzt kurz das helle Standardschema auf.
+ var settings = Services.GetRequiredService();
+ Shell.ThemeManager.Initialize(settings, "server_settings.xml",
+ Services.GetRequiredService());
+
var uiHost = Services.GetRequiredService();
CoreViews.Register(uiHost, Services);
diff --git a/src/PolyTrader.App.Avalonia/Controls/SettingsEditor.axaml b/src/PolyTrader.App.Avalonia/Controls/SettingsEditor.axaml
index 6acc58a..919a02d 100644
--- a/src/PolyTrader.App.Avalonia/Controls/SettingsEditor.axaml
+++ b/src/PolyTrader.App.Avalonia/Controls/SettingsEditor.axaml
@@ -29,7 +29,7 @@
@@ -106,7 +106,7 @@
+ Foreground="{DynamicResource AppReadOnlyTextBrush}" />
diff --git a/src/PolyTrader.App.Avalonia/Controls/WindowMenuBar.axaml b/src/PolyTrader.App.Avalonia/Controls/WindowMenuBar.axaml
index b920ffb..65c3148 100644
--- a/src/PolyTrader.App.Avalonia/Controls/WindowMenuBar.axaml
+++ b/src/PolyTrader.App.Avalonia/Controls/WindowMenuBar.axaml
@@ -14,7 +14,7 @@
(Avalonia kennt kein ToolStripItemAlignment.Right — die rechte Ausrichtung entsteht
dadurch, dass der letzte Eintrag im DockPanel rechts angedockt wird.)
-->
-
+
@@ -25,6 +25,7 @@
+ Foreground="{DynamicResource AppPositiveBrush}" />
diff --git a/src/PolyTrader.App.Avalonia/Program.cs b/src/PolyTrader.App.Avalonia/Program.cs
index a865fde..1dfa84b 100644
--- a/src/PolyTrader.App.Avalonia/Program.cs
+++ b/src/PolyTrader.App.Avalonia/Program.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Avalonia;
+using Avalonia.Controls;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -91,6 +92,14 @@ namespace PolyTrader.App.Avalonia
var logger = new TerminalLogger();
bootLog = logger;
+ // Anzeige-Kultur festnageln: Auf Linux richtet sich die Kultur sonst nach LANG/LC_ALL,
+ // das unter systemd oft gar nicht gesetzt ist - dann faellt .NET auf Invariant zurueck
+ // und aus "1.234,56 USDC" wird "1,234.56 USDC". Maschinen-I/O (API, Logs, CSV) laeuft
+ // unabhaengig davon ohnehin invariant.
+ var uiCulture = new System.Globalization.CultureInfo("de-DE");
+ System.Globalization.CultureInfo.DefaultThreadCurrentCulture = uiCulture;
+ System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = uiCulture;
+
ConfigureSecretProtection(logger);
// Anzeige-Zeitzone einmalig setzen (Logs, Auswertungen). Ein unbekannter Wert ist nicht
@@ -170,6 +179,7 @@ namespace PolyTrader.App.Avalonia
// Avalonia muss initialisiert sein, damit Fenster konstruiert werden koennen.
BuildAvaloniaApp().SetupWithoutStarting();
App.Services = host.Services;
+ ThemeManager.Initialize(host.Services.GetRequiredService(), ServerSettingsPath, logger);
var uiHost = host.Services.GetRequiredService();
CoreViews.Register(uiHost, host.Services);
@@ -241,6 +251,34 @@ namespace PolyTrader.App.Avalonia
Console.WriteLine($"[FEHLER] Einstellungs-Editor: {ex.GetType().Name}: {ex.Message}");
}
+ // Farbschema: jeder Token muss in BEIDEN Varianten aufloesen. Ein Tippfehler im
+ // Schluessel faellt sonst nicht auf - ThemeManager.Brush liefert dann still Grau.
+ string[] themeKeys =
+ {
+ "AppSurfaceBrush", "AppSurfaceAltBrush", "AppCardBrush", "AppBorderBrush",
+ "AppMutedTextBrush", "AppCaptionTextBrush", "AppReadOnlyTextBrush",
+ "AppPositiveBrush", "AppNegativeBrush", "AppWarningBrush",
+ "AppTradeLossBrush", "AppTradeSmallWinBrush", "AppTradeBigWinBrush",
+ "AppToggleActiveBrush", "AppToggleSellOnlyBrush", "AppToggleInactiveBrush",
+ "AppChatUserBrush", "AppChatAgentBrush"
+ };
+ foreach (var variant in new[] { global::Avalonia.Styling.ThemeVariant.Light,
+ global::Avalonia.Styling.ThemeVariant.Dark })
+ {
+ var missing = themeKeys.Where(k =>
+ !(global::Avalonia.Application.Current!.TryFindResource(k, variant, out object? v) && v is global::Avalonia.Media.IBrush))
+ .ToList();
+ if (missing.Count > 0)
+ {
+ failures++;
+ Console.WriteLine($"[FEHLER] Farbschema „{variant}“: fehlende Farben: {string.Join(", ", missing)}");
+ }
+ else
+ {
+ Console.WriteLine($"[OK] Farbschema „{variant}“: alle {themeKeys.Length} Farben vorhanden");
+ }
+ }
+
Console.WriteLine(failures == 0 ? "=== Smoke-UI OK ===" : $"=== Smoke-UI: {failures} Fehler ===");
return failures == 0 ? 0 : 1;
}
diff --git a/src/PolyTrader.App.Avalonia/Shell/ThemeManager.cs b/src/PolyTrader.App.Avalonia/Shell/ThemeManager.cs
new file mode 100644
index 0000000..b7f509f
--- /dev/null
+++ b/src/PolyTrader.App.Avalonia/Shell/ThemeManager.cs
@@ -0,0 +1,102 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Styling;
+using PolyTraderSharp.Models;
+using PolyTraderSharp.Services;
+
+namespace PolyTrader.App.Avalonia.Shell
+{
+ ///
+ /// Verwaltet das Farbschema (hell/dunkel/System) und macht es persistent.
+ ///
+ /// Die Standard-Steuerelemente stellt Avalonia selbst um, sobald
+ /// wechselt – für alles, was per
+ /// DynamicResource gebunden ist, gilt dasselbe. Farben, die im Code gesetzt werden
+ /// (Zeilenhintergründe, Umschalter-Flächen, KPI-Vorzeichen), aktualisieren sich dagegen NICHT von
+ /// selbst. Fenster mit solchen Stellen hängen sich deshalb an und
+ /// zeichnen sie dort neu.
+ ///
+ public static class ThemeManager
+ {
+ private static ServerSettings? _settings;
+ private static string? _settingsPath;
+ private static TerminalLogger? _logger;
+
+ /// Feuert nach jedem Wechsel – Fenster zeichnen daraufhin ihre Code-Farben neu.
+ public static event Action? ThemeChanged;
+
+ /// Ist gerade das dunkle Schema aktiv (auch wenn es über „System" kam)?
+ public static bool IsDark =>
+ global::Avalonia.Application.Current?.ActualThemeVariant == ThemeVariant.Dark;
+
+ ///
+ /// Einmalig beim Start aufrufen: übernimmt das gespeicherte Schema und merkt sich, wohin
+ /// spätere Änderungen geschrieben werden.
+ ///
+ public static void Initialize(ServerSettings settings, string settingsPath, TerminalLogger? logger = null)
+ {
+ _settings = settings;
+ _settingsPath = settingsPath;
+ _logger = logger;
+ ApplyVariant(settings.Theme);
+ }
+
+ /// Wechselt hell ↔ dunkel und speichert die Wahl. „System" wird dabei zu einer festen Wahl.
+ public static void Toggle()
+ {
+ Set(IsDark ? AppThemeVariant.Hell : AppThemeVariant.Dunkel);
+ }
+
+ /// Setzt das Schema, speichert es und benachrichtigt die Fenster.
+ public static void Set(AppThemeVariant variant)
+ {
+ ApplyVariant(variant);
+
+ if (_settings != null && _settingsPath != null)
+ {
+ _settings.Theme = variant;
+ try
+ {
+ _settings.Save(_settingsPath);
+ }
+ catch (Exception ex)
+ {
+ // Ein fehlgeschlagenes Speichern darf die Umschaltung nicht verhindern –
+ // sie gilt dann nur für diese Sitzung.
+ _logger?.Warning($"Farbschema konnte nicht gespeichert werden: {ex.Message}");
+ }
+ }
+
+ ThemeChanged?.Invoke();
+ }
+
+ private static void ApplyVariant(AppThemeVariant variant)
+ {
+ var app = global::Avalonia.Application.Current;
+ if (app == null) return;
+
+ app.RequestedThemeVariant = variant switch
+ {
+ AppThemeVariant.Hell => ThemeVariant.Light,
+ AppThemeVariant.Dunkel => ThemeVariant.Dark,
+ _ => ThemeVariant.Default // folgt dem Betriebssystem
+ };
+ }
+
+ ///
+ /// Löst eine Farbe aus den Themen-Ressourcen auf. Für Stellen, die ihre Farbe im Code setzen
+ /// müssen (Grid-Zeilen, Umschalter) – im XAML nimmt man stattdessen DynamicResource.
+ ///
+ public static IBrush Brush(string key)
+ {
+ var app = global::Avalonia.Application.Current;
+ if (app != null && app.TryFindResource(key, app.ActualThemeVariant, out object? value) && value is IBrush b)
+ return b;
+
+ // Unbekannter Schlüssel ist ein Programmierfehler, darf die Oberfläche aber nicht kippen.
+ return Brushes.Gray;
+ }
+ }
+}
diff --git a/src/PolyTrader.App.Avalonia/Shell/WindowMenuModel.cs b/src/PolyTrader.App.Avalonia/Shell/WindowMenuModel.cs
index 39411da..64dea76 100644
--- a/src/PolyTrader.App.Avalonia/Shell/WindowMenuModel.cs
+++ b/src/PolyTrader.App.Avalonia/Shell/WindowMenuModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
@@ -24,6 +24,7 @@ namespace PolyTrader.App.Avalonia.Shell
public sealed class WindowMenuEntry
{
public string Title { get; init; } = "";
+ public string? ToolTip { get; init; }
public Bitmap? Icon { get; init; }
/// Aktuelles Fenster – wird fett dargestellt.
@@ -67,10 +68,15 @@ namespace PolyTrader.App.Avalonia.Shell
Rebuild();
_host.OpenStateChanged += OnOpenStateChanged;
+ ThemeManager.ThemeChanged += OnOpenStateChanged; // Symbol des Umschalters dreht sich
}
/// Registrierung lösen, wenn das Fenster geht – kein Event-Leak über die Host-Lebensdauer.
- public void Detach() => _host.OpenStateChanged -= OnOpenStateChanged;
+ public void Detach()
+ {
+ _host.OpenStateChanged -= OnOpenStateChanged;
+ ThemeManager.ThemeChanged -= OnOpenStateChanged;
+ }
private void OnOpenStateChanged()
{
@@ -104,6 +110,15 @@ namespace PolyTrader.App.Avalonia.Shell
});
}
+ // Farbschema-Umschalter: sitzt auf JEDEM Fenster, damit er ueberall erreichbar ist.
+ // Sonne = ins Helle wechseln, Mond = ins Dunkle.
+ Entries.Add(new WindowMenuEntry
+ {
+ Title = ThemeManager.IsDark ? "☀" : "🌙",
+ ToolTip = ThemeManager.IsDark ? "Zum hellen Farbschema wechseln" : "Zum dunklen Farbschema wechseln",
+ Command = new RelayCommand(ThemeManager.Toggle)
+ });
+
// Kontextabhaengige rechte Aktion – wie in der WinForms-Fassung: nur der Launcher darf die
// App beenden (mit Sicherheitsabfrage), jedes andere Fenster schliesst nur sich selbst.
Entries.Add(_currentViewId == null
diff --git a/src/PolyTrader.App.Avalonia/ViewModels/CopyTradingRows.cs b/src/PolyTrader.App.Avalonia/ViewModels/CopyTradingRows.cs
index 304ff22..80d625c 100644
--- a/src/PolyTrader.App.Avalonia/ViewModels/CopyTradingRows.cs
+++ b/src/PolyTrader.App.Avalonia/ViewModels/CopyTradingRows.cs
@@ -1,19 +1,24 @@
using System;
using Avalonia.Media;
+using PolyTrader.App.Avalonia.Shell;
using PolyTrader.Modules.CopyTrading.Logic;
namespace PolyTrader.App.Avalonia.ViewModels
{
///
- /// Avalonia-Farbpalette zu den -Kategorien. Die Schwellenlogik liegt
+ /// Farbpalette zu den -Kategorien. Die Schwellenlogik liegt
/// (getestet) in ; hier steht nur, wie die Kategorien aussehen.
- /// Werte unverändert aus der WinForms-Oberfläche übernommen.
+ ///
+ /// Die Farben kommen aus den Themen-Ressourcen und werden bei JEDEM Zugriff aufgelöst –
+ /// deshalb Eigenschaften statt static readonly. Nach einem Themenwechsel liefern sie
+ /// sofort die passende Variante; im hellen Schema die Pastelltöne der bisherigen Oberfläche,
+ /// im dunklen gedämpfte Entsprechungen (helle Pastelltöne würden dort blenden).
///
public static class TradeRowPalette
{
- public static readonly IBrush Loss = new SolidColorBrush(Color.FromRgb(245, 200, 200)); // rot
- public static readonly IBrush SmallWin = new SolidColorBrush(Color.FromRgb(212, 240, 212)); // hellgrün
- public static readonly IBrush BigWin = new SolidColorBrush(Color.FromRgb(140, 214, 140)); // grün
+ public static IBrush Loss => ThemeManager.Brush("AppTradeLossBrush");
+ public static IBrush SmallWin => ThemeManager.Brush("AppTradeSmallWinBrush");
+ public static IBrush BigWin => ThemeManager.Brush("AppTradeBigWinBrush");
public static IBrush ForPnlPercent(decimal pnlPercent) => TradeRowColoring.ForPnlPercent(pnlPercent) switch
{
diff --git a/src/PolyTrader.App.Avalonia/ViewModels/LogRows.cs b/src/PolyTrader.App.Avalonia/ViewModels/LogRows.cs
index 078d3e5..27d42b9 100644
--- a/src/PolyTrader.App.Avalonia/ViewModels/LogRows.cs
+++ b/src/PolyTrader.App.Avalonia/ViewModels/LogRows.cs
@@ -1,4 +1,5 @@
using Avalonia.Media;
+using PolyTrader.App.Avalonia.Shell;
namespace PolyTrader.App.Avalonia.ViewModels
{
@@ -21,11 +22,24 @@ namespace PolyTrader.App.Avalonia.ViewModels
public string Message { get; init; } = string.Empty;
}
- /// Ein Eintrag im Supervisor-Analyseverlauf (Frage oder Antwort).
+ /// Wer im Supervisor-Verlauf spricht – bestimmt die Farbe der Kopfzeile.
+ public enum ChatRole { User, Agent, Error }
+
+ ///
+ /// Ein Eintrag im Supervisor-Analyseverlauf. Speichert die ROLLE, nicht die Farbe – dadurch
+ /// stimmt die Darstellung nach einem Themenwechsel ohne Neuaufbau der Liste.
+ ///
public sealed class ChatEntry
{
public string Header { get; init; } = string.Empty;
public string Text { get; init; } = string.Empty;
- public IBrush HeaderBrush { get; init; } = Brushes.SteelBlue;
+ public ChatRole Role { get; init; } = ChatRole.User;
+
+ public IBrush HeaderBrush => Role switch
+ {
+ ChatRole.Agent => ThemeManager.Brush("AppChatAgentBrush"),
+ ChatRole.Error => ThemeManager.Brush("AppNegativeBrush"),
+ _ => ThemeManager.Brush("AppChatUserBrush")
+ };
}
}
diff --git a/src/PolyTrader.App.Avalonia/Views/DashboardWindow.axaml b/src/PolyTrader.App.Avalonia/Views/DashboardWindow.axaml
index 6ee5ab4..f12c815 100644
--- a/src/PolyTrader.App.Avalonia/Views/DashboardWindow.axaml
+++ b/src/PolyTrader.App.Avalonia/Views/DashboardWindow.axaml
@@ -75,7 +75,7 @@
+ BorderBrush="{DynamicResource AppBorderBrush}" BorderThickness="1" Margin="0,0,0,8">
@@ -87,7 +87,7 @@
+ BorderBrush="{DynamicResource AppBorderBrush}" BorderThickness="1" Margin="0,0,4,0">
@@ -98,7 +98,7 @@
+ BorderBrush="{DynamicResource AppBorderBrush}" BorderThickness="1" Margin="4,0,0,0">
diff --git a/src/PolyTrader.App.Avalonia/Views/DashboardWindow.axaml.cs b/src/PolyTrader.App.Avalonia/Views/DashboardWindow.axaml.cs
index 6f23a81..254d41f 100644
--- a/src/PolyTrader.App.Avalonia/Views/DashboardWindow.axaml.cs
+++ b/src/PolyTrader.App.Avalonia/Views/DashboardWindow.axaml.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -73,6 +73,11 @@ namespace PolyTrader.App.Avalonia.Views
RefreshData();
InitModulesTab();
+
+ // Die KPI-Farbe wird im Code gesetzt und wechselt daher nicht von selbst mit dem Thema.
+ void OnTheme() => ApplyScope();
+ Shell.ThemeManager.ThemeChanged += OnTheme;
+ Closed += (_, _) => Shell.ThemeManager.ThemeChanged -= OnTheme;
}
// ===== Daten laden / Scope =====
@@ -150,9 +155,7 @@ namespace PolyTrader.App.Avalonia.Views
var pnl = this.FindControl("kpiPnl")!;
pnl.Text = $"{k.NetPnl:N2} USDC";
- pnl.Foreground = new global::Avalonia.Media.SolidColorBrush(
- k.NetPnl >= 0 ? global::Avalonia.Media.Color.FromRgb(0x2E, 0x7D, 0x32)
- : global::Avalonia.Media.Color.FromRgb(0xC6, 0x28, 0x28));
+ pnl.Foreground = Shell.ThemeManager.Brush(k.NetPnl >= 0 ? "AppPositiveBrush" : "AppNegativeBrush");
this.FindControl("kpiWin")!.Text = $"{k.WinRatePct:N1} %";
this.FindControl("kpiTrades")!.Text = k.TradeCount.ToString();
diff --git a/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml b/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml
index afe35c1..afd6ef3 100644
--- a/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml
+++ b/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml
@@ -57,7 +57,7 @@
IsVisible="{Binding Icon, Converter={x:Static ObjectConverters.IsNotNull}}" />
-
+
@@ -66,7 +66,7 @@
diff --git a/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml.cs b/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml.cs
index 450ba5e..1f4fd74 100644
--- a/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml.cs
+++ b/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Controls;
@@ -62,10 +62,14 @@ namespace PolyTrader.App.Avalonia.Views
e.Cancel = true;
Dispatcher.UIThread.Post(_uiHost.RequestShutdown);
};
+ // Die Umschalterflaechen werden im Code gesetzt und folgen dem Thema nicht von selbst.
+ ThemeManager.ThemeChanged += UpdateTradingToggles;
+
Closed += (_, _) =>
{
_statusTimer.Stop();
_uiHost.OpenStateChanged -= OnOpenStateChanged;
+ ThemeManager.ThemeChanged -= UpdateTradingToggles;
};
}
@@ -106,17 +110,18 @@ namespace PolyTrader.App.Avalonia.Views
Apply(this.FindControl
@@ -46,7 +46,7 @@
-
+
-
+
diff --git a/src/PolyTrader.App.Avalonia/Views/Modules/CopyTradingWindow.axaml.cs b/src/PolyTrader.App.Avalonia/Views/Modules/CopyTradingWindow.axaml.cs
index 02ecdd5..8eb14c9 100644
--- a/src/PolyTrader.App.Avalonia/Views/Modules/CopyTradingWindow.axaml.cs
+++ b/src/PolyTrader.App.Avalonia/Views/Modules/CopyTradingWindow.axaml.cs
@@ -93,6 +93,16 @@ namespace PolyTrader.App.Avalonia.Views.Modules
LoadOpenTrades();
LoadClosedTrades();
LoadAccountList();
+
+ // Zeilenhintergruende werden in LoadingRow gesetzt und wechseln daher nicht von selbst
+ // mit dem Thema. Ein Neuzuweisen der Quelle laesst die Zeilen neu erzeugen.
+ void OnTheme()
+ {
+ gridOpen.ItemsSource = null; gridOpen.ItemsSource = _openTrades;
+ gridClosed.ItemsSource = null; gridClosed.ItemsSource = _closedTrades;
+ }
+ Shell.ThemeManager.ThemeChanged += OnTheme;
+ Closed += (_, _) => Shell.ThemeManager.ThemeChanged -= OnTheme;
}
// ===== Master-Trader =====
diff --git a/src/PolyTrader.App.Avalonia/Views/Modules/ResolutionFarmingWindow.axaml b/src/PolyTrader.App.Avalonia/Views/Modules/ResolutionFarmingWindow.axaml
index d06e934..0eb79a8 100644
--- a/src/PolyTrader.App.Avalonia/Views/Modules/ResolutionFarmingWindow.axaml
+++ b/src/PolyTrader.App.Avalonia/Views/Modules/ResolutionFarmingWindow.axaml
@@ -16,7 +16,7 @@
-
+
@@ -84,7 +84,7 @@
-
+
diff --git a/src/PolyTrader.App.Avalonia/Views/Modules/SupervisorWindow.axaml b/src/PolyTrader.App.Avalonia/Views/Modules/SupervisorWindow.axaml
index 64f791e..edb4c3d 100644
--- a/src/PolyTrader.App.Avalonia/Views/Modules/SupervisorWindow.axaml
+++ b/src/PolyTrader.App.Avalonia/Views/Modules/SupervisorWindow.axaml
@@ -26,7 +26,7 @@
-
+
@@ -76,7 +76,7 @@
-
+
("btnSend")!;
var status = this.FindControl("lblChatStatus")!;
- AddChat("Du", question, Brushes.SteelBlue);
+ AddChat("Du", question, ChatRole.User);
tbQuestion.Text = string.Empty;
btn.IsEnabled = false;
status.Text = "Der Supervisor denkt nach …";
@@ -102,13 +101,13 @@ namespace PolyTrader.App.Avalonia.Views.Modules
var result = await _agent.AskAsync(question, model, progress, profile);
- AddChat($"Supervisor ({profile?.Name ?? "Allgemein"})", result.Answer, Brushes.SeaGreen);
+ 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, Brushes.Firebrick);
+ AddChat("Fehler", ex.Message, ChatRole.Error);
status.Text = "Anfrage fehlgeschlagen.";
_logger.Error($"Supervisor-Anfrage fehlgeschlagen: {ex.Message}");
}
@@ -119,9 +118,9 @@ namespace PolyTrader.App.Avalonia.Views.Modules
}
}
- private void AddChat(string who, string text, IBrush brush)
+ private void AddChat(string who, string text, ChatRole role)
{
- _chat.Add(new ChatEntry { Header = $"[{DateTime.Now:HH:mm:ss}] {who}", Text = text, HeaderBrush = brush });
+ _chat.Add(new ChatEntry { Header = $"[{DateTime.Now:HH:mm:ss}] {who}", Text = text, Role = role });
this.FindControl("chatScroll")!.ScrollToEnd();
}
diff --git a/src/PolyTrader.App.Avalonia/Views/SettingsWindow.axaml b/src/PolyTrader.App.Avalonia/Views/SettingsWindow.axaml
index 8ccfcd5..58b0c47 100644
--- a/src/PolyTrader.App.Avalonia/Views/SettingsWindow.axaml
+++ b/src/PolyTrader.App.Avalonia/Views/SettingsWindow.axaml
@@ -62,7 +62,7 @@
-
+
diff --git a/src/PolyTrader.App.Avalonia/Views/ShutdownConfirmWindow.axaml b/src/PolyTrader.App.Avalonia/Views/ShutdownConfirmWindow.axaml
index 5fbabf3..b689b2d 100644
--- a/src/PolyTrader.App.Avalonia/Views/ShutdownConfirmWindow.axaml
+++ b/src/PolyTrader.App.Avalonia/Views/ShutdownConfirmWindow.axaml
@@ -17,7 +17,7 @@
Text="Offene Orders und laufende Jobs werden geordnet heruntergefahren. Das kann einen Moment dauern. Die Schaltfläche ist kurz gesperrt, damit das Beenden nicht versehentlich ausgelöst wird." />
diff --git a/src/PolyTrader.App.Avalonia/Views/TerminalWindow.axaml b/src/PolyTrader.App.Avalonia/Views/TerminalWindow.axaml
index 243c6c0..68588bc 100644
--- a/src/PolyTrader.App.Avalonia/Views/TerminalWindow.axaml
+++ b/src/PolyTrader.App.Avalonia/Views/TerminalWindow.axaml
@@ -30,7 +30,7 @@
-
+
@@ -70,7 +70,7 @@
-
+
diff --git a/src/PolyTrader.Core/Models/ServerSettings.cs b/src/PolyTrader.Core/Models/ServerSettings.cs
index a5c7ddf..25dfbac 100644
--- a/src/PolyTrader.Core/Models/ServerSettings.cs
+++ b/src/PolyTrader.Core/Models/ServerSettings.cs
@@ -5,6 +5,15 @@ using System.Xml.Serialization;
namespace PolyTraderSharp.Models
{
+ /// Farbschema der Oberfläche.
+ public enum AppThemeVariant
+ {
+ /// Folgt der Einstellung des Betriebssystems.
+ System,
+ Hell,
+ Dunkel
+ }
+
public class ServerSettings
{
[Category("Module")]
@@ -16,6 +25,13 @@ namespace PolyTraderSharp.Models
[XmlArrayItem("Module")]
public List DisabledModules { get; set; } = new();
+ [Category("Anzeige")]
+ [DisplayName("Farbschema")]
+ [Description("Hell, Dunkel oder der Einstellung des Betriebssystems folgen. " +
+ "Lässt sich jederzeit über den Umschalter in der Fensterleiste wechseln; " +
+ "der zuletzt gewählte Stand wird hier gespeichert.")]
+ public AppThemeVariant Theme { get; set; } = AppThemeVariant.System;
+
[Category("Anzeige")]
[DisplayName("Zeitzone")]
[Description("Zeitzone für Anzeige und Logdatei-Tagesgrenzen, IANA-Schreibweise (z.B. \"Europe/Berlin\", " +