using System.Collections.ObjectModel; using System.Diagnostics; using ClaudeDo.Data; using ClaudeDo.Ui.Localization; using ClaudeDo.Ui.Services; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; namespace ClaudeDo.Ui.ViewModels.Modals.Settings; public sealed partial class FilesSettingsTabViewModel : ViewModelBase { private readonly IWorkerClient _worker; [ObservableProperty] private string _statusMessage = ""; [ObservableProperty] private bool _isBusy; [ObservableProperty] private bool _hasCustomizedPrompts; public string SystemPromptPath { get; } = PromptFiles.PathFor(PromptKind.System); public string PlanningPromptPath { get; } = PromptFiles.PathFor(PromptKind.Planning); public string PlanningInitialPromptPath { get; } = PromptFiles.PathFor(PromptKind.PlanningInitial); public string RetryPromptPath { get; } = PromptFiles.PathFor(PromptKind.Retry); public string DailyPrepPromptPath { get; } = PromptFiles.PathFor(PromptKind.DailyPrep); public string WeeklyReportPromptPath { get; } = PromptFiles.PathFor(PromptKind.WeeklyReport); public ObservableCollection CustomizedPrompts { get; } = new(); public FilesSettingsTabViewModel(IWorkerClient worker) { _worker = worker; RefreshCustomizedPrompts(); } [RelayCommand] private async Task RestoreDefaultAgents() { IsBusy = true; StatusMessage = ""; try { var r = await _worker.RestoreDefaultAgentsAsync(); if (r is null) StatusMessage = Loc.T("vm.filesTab.workerOffline"); else if (r.Copied == 0 && r.Skipped == 0) StatusMessage = Loc.T("vm.filesTab.noneBundled"); else if (r.Copied == 0) StatusMessage = Loc.T("vm.filesTab.allPresent"); else StatusMessage = Loc.T("vm.filesTab.restored", r.Copied); await _worker.RefreshAgentsAsync(); } catch (Exception ex) { StatusMessage = Loc.T("vm.filesTab.restoreFailed", ex.Message); } finally { IsBusy = false; } } [RelayCommand] private void OpenPrompt(string? kindName) { if (!Enum.TryParse(kindName, ignoreCase: true, out var kind)) return; try { // No override file yet: seed it with today's bundled default (hash-tracked, so a later // default change reconciles this file away automatically unless the user actually edits it) // rather than leaving a plain file that would freeze the prompt forever. if (!File.Exists(PromptFiles.PathFor(kind))) PromptFiles.Save(kind, PromptFiles.DefaultFor(kind)); Process.Start(new ProcessStartInfo(PromptFiles.PathFor(kind)) { UseShellExecute = true }); } catch (Exception ex) { StatusMessage = Loc.T("vm.filesTab.openFailed", ex.Message); } finally { RefreshCustomizedPrompts(); } } private void RefreshCustomizedPrompts() { CustomizedPrompts.Clear(); foreach (var kind in Enum.GetValues()) { if (PromptFiles.Classify(kind) != PromptFileState.Edited) continue; CustomizedPrompts.Add(new CustomizedPromptRowViewModel(kind, PromptFiles.DiffAgainstDefault(kind), OnResetPromptToDefault)); } HasCustomizedPrompts = CustomizedPrompts.Count > 0; } private void OnResetPromptToDefault(PromptKind kind) { PromptFiles.ResetToDefault(kind); RefreshCustomizedPrompts(); StatusMessage = Loc.T("vm.filesTab.resetToDefault"); } } public sealed partial class CustomizedPromptRowViewModel : ObservableObject { private readonly Action _onReset; public PromptKind Kind { get; } public string KindName => Kind.ToString(); public string DiffPreview { get; } public CustomizedPromptRowViewModel(PromptKind kind, string diffPreview, Action onReset) { Kind = kind; DiffPreview = diffPreview; _onReset = onReset; } [RelayCommand] private void Reset() => _onReset(Kind); }