using ClawdDotNet.App; using ClawdDotNet.App.Services; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.Logging; using ClawdDotNet.Desktop.Services; namespace ClawdDotNet.Desktop.ViewModels; /// /// Ansichtsmodell für die Einstellungen-Seite (Anwendungs- und Instanz-Einstellungen). /// public sealed partial class SettingsPageViewModel : PageViewModel { private readonly AppHost? _host; private readonly ILogger? _logger; /// Womit die Seite geladen wurde — nur, um eine Änderung zu erkennen. private string _logDirectoryAtLoad = ""; // ─── Allgemein & Pfade ─── [ObservableProperty] private string _instancesDirectory = ""; [ObservableProperty] private string _minimumLogLevel = "Info"; [ObservableProperty] private int _maxLogLinesInUi = 2000; [ObservableProperty] private int _logRefreshIntervalMs = 500; // ─── API & OpenRouter ─── [ObservableProperty] private string _openRouterBaseUrl = "https://openrouter.ai/api/v1/"; [ObservableProperty] private int _statusCheckIntervalSeconds = 60; // Die Sicherungs-Einstellungen (Zielordner, Uhrzeit, Anzahl) stehen bewusst // NICHT hier, sondern allein auf der Sicherungs-Seite. Sie lagen frueher // doppelt vor: Die Felder waren aus der Ansicht bereits verschwunden, wurden // aber weiter beim Start gelesen und beim Speichern zurueckgeschrieben - wer // die Uhrzeit auf der Sicherungs-Seite aenderte und danach hier speicherte, // bekam den alten Wert zurueck. // ─── Deploymentcenter ─── [ObservableProperty] private string _deploymentcenterUrl = "https://dc.mhdf.de"; [ObservableProperty] private string _deploymentcenterToken = ""; [ObservableProperty] private string _deploymentcenterEnvironment = "production"; [ObservableProperty] private bool _errorReportingEnabled = true; [ObservableProperty] private bool _updateCheckEnabled = true; [ObservableProperty] private string _updateChannel = "prod"; // ─── Lizenz ─── [ObservableProperty] private string _licenseKey = ""; // ─── Instanz-Einstellungen ─── [ObservableProperty] private string _instanceName = ""; /// /// Wohin diese Instanz protokolliert. Steht seit #528 (Stufe 1) hier und nicht mehr /// bei den Anwendungseinstellungen: Ein Verzeichnis für alle hiess, dass in /// Startup.log abwechselnd zwei Instanzen schrieben, ohne Kennzeichnung, welche /// gerade spricht. /// /// Leer angezeigt heisst leer gemeint — die Vorgabe steht als Hinweis unter /// dem Feld. Anders als bei der Sicherungsseite wird hier bewusst nicht aufgeloest: /// Der geltende Ort steht auf der Info-Seite, und ein aufgeloester Pfad im Feld /// wuerde beim naechsten Speichern festgeschrieben. /// [ObservableProperty] private string _logDirectory = ""; /// /// Ob der Instanzmanager diese Instanz am Laufen hält (#528). /// /// Steht hier und nicht beim Manager, weil es eine Eigenschaft dieser Instanz /// ist — und weil die Instanzdatei dem Instanzprozess gehört: Sie vom Manager /// schreiben zu lassen, während die Instanz läuft, waeren zwei Schreiber auf einer /// Datei. Der Manager liest den Schalter nur. /// [ObservableProperty] private bool _autostartByManager; [ObservableProperty] private string _openRouterApiKey = ""; [ObservableProperty] private decimal _instanceDailyCostUsd; [ObservableProperty] private long _instanceDailyTokens; [ObservableProperty] private string _statusText = "Bereit"; [ObservableProperty] private string _selectedLanguage = LocalizationManager.DefaultLanguage; public IReadOnlyList AvailableLanguages => LocalizationManager.Instance.AvailableLanguages; public LanguageOption? SelectedLanguageOption { get => AvailableLanguages.FirstOrDefault(l => l.Code.Equals(SelectedLanguage, StringComparison.OrdinalIgnoreCase)) ?? AvailableLanguages.FirstOrDefault(); set { if (value is not null && value.Code != SelectedLanguage) { SelectedLanguage = value.Code; OnPropertyChanged(); LocalizationManager.Instance.SetLanguage(value.Code); } } } public List LogLevels { get; } = ["Debug", "Info", "Warn", "Error"]; public List UpdateChannels { get; } = ["prod", "beta", "dev"]; public List Environments { get; } = ["production", "development", "staging"]; public SettingsPageViewModel() : this(null) { } public SettingsPageViewModel(AppHost? host) : base("nav.settings") { _host = host; if (host is not null) { _logger = host.LoggerFactory.CreateLogger("ClawdDotNet.Desktop.Settings"); LoadFromHost(); } else { StatusText = LocalizationManager.Instance["desktop.status.design"]; } } [RelayCommand] private void LoadFromHost() { if (_host is null) return; var s = _host.Settings.AppSettings; InstancesDirectory = s.InstancesDirectory; MinimumLogLevel = s.MinimumLogLevel; MaxLogLinesInUi = s.MaxLogLinesInUi; LogRefreshIntervalMs = s.LogRefreshIntervalMs; OpenRouterBaseUrl = s.OpenRouterBaseUrl; StatusCheckIntervalSeconds = s.StatusCheckIntervalSeconds; DeploymentcenterUrl = s.DeploymentcenterUrl; DeploymentcenterToken = s.DeploymentcenterToken; DeploymentcenterEnvironment = s.DeploymentcenterEnvironment; ErrorReportingEnabled = s.ErrorReportingEnabled; UpdateCheckEnabled = s.UpdateCheckEnabled; UpdateChannel = s.UpdateChannel; LicenseKey = s.LicenseKey; SelectedLanguage = s.Language ?? LocalizationManager.DefaultLanguage; OnPropertyChanged(nameof(SelectedLanguageOption)); var inst = _host.Instance; InstanceName = inst.InstanceName; LogDirectory = inst.LogDirectory; AutostartByManager = inst.AutostartByManager; _logDirectoryAtLoad = inst.LogDirectory; OpenRouterApiKey = inst.OpenRouterApiKey; InstanceDailyCostUsd = inst.Budget.DailyCostUsd; InstanceDailyTokens = inst.Budget.DailyTokens; } [RelayCommand] private void SaveSettings() { if (_host is null) return; var s = _host.Settings.AppSettings; s.InstancesDirectory = InstancesDirectory.Trim(); s.MinimumLogLevel = MinimumLogLevel; s.MaxLogLinesInUi = MaxLogLinesInUi; s.LogRefreshIntervalMs = LogRefreshIntervalMs; s.OpenRouterBaseUrl = OpenRouterBaseUrl.Trim(); s.StatusCheckIntervalSeconds = StatusCheckIntervalSeconds; s.DeploymentcenterUrl = DeploymentcenterUrl.Trim(); s.DeploymentcenterToken = DeploymentcenterToken.Trim(); s.DeploymentcenterEnvironment = DeploymentcenterEnvironment.Trim(); s.ErrorReportingEnabled = ErrorReportingEnabled; s.UpdateCheckEnabled = UpdateCheckEnabled; s.UpdateChannel = UpdateChannel; s.LicenseKey = LicenseKey.Trim(); s.Language = SelectedLanguage; _host.Settings.Save(); LocalizationManager.Instance.SetLanguage(SelectedLanguage); var inst = _host.Instance; inst.InstanceName = InstanceName.Trim(); inst.LogDirectory = LogDirectory.Trim(); inst.AutostartByManager = AutostartByManager; inst.OpenRouterApiKey = OpenRouterApiKey.Trim(); inst.Budget.DailyCostUsd = InstanceDailyCostUsd; inst.Budget.DailyTokens = InstanceDailyTokens; _host.SaveInstance(new InstanceChange(InstanceChangeKind.Settings)); _logger?.LogInformation("Settings and instance configuration saved."); // Das Protokoll-Verzeichnis greift erst beim naechsten Start: Die // Protokollierung steht seit dem Aufbau des AppHost und schreibt bereits. StatusText = string.Equals(inst.LogDirectory, _logDirectoryAtLoad, StringComparison.Ordinal) ? LocalizationManager.Instance["desktop.settings.saved"] : LocalizationManager.Instance["desktop.settings.savedLogRestart"]; _logDirectoryAtLoad = inst.LogDirectory; } }