using System.Collections.ObjectModel; 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; public OperationStatus RestoreOp { get; } = new(); public OperationStatus ClaudeBinOp { get; } = new(); // worker.config.json's claude_bin: a command name or a full path to the CLI. Saved on its own // (not with the modal's Save button) because the worker owns the file, not app_settings. [ObservableProperty] private string _claudeBin = ""; [ObservableProperty] private string _claudeBinResolvedHint = ""; [ObservableProperty] private string _statusMessage = ""; [ObservableProperty] private bool _hasCustomizedPrompts; [ObservableProperty] private string? _viewedKindName; [ObservableProperty] private string _viewedContent = ""; 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 string PowerUsePromptPath { get; } = PromptFiles.PathFor(PromptKind.PowerUseAddendum); public ObservableCollection CustomizedPrompts { get; } = new(); public FilesSettingsTabViewModel(IWorkerClient worker) { _worker = worker; RefreshCustomizedPrompts(); RestoreOp.PropertyChanged += (_, e) => { if (e.PropertyName == nameof(OperationStatus.IsRunning)) RestoreDefaultAgentsCommand.NotifyCanExecuteChanged(); }; } public async Task LoadClaudeBinAsync() { var dto = await _worker.GetClaudeBinAsync(); if (dto is null) return; ClaudeBin = dto.Value; ApplyResolvedHint(dto); } [RelayCommand] private async Task SaveClaudeBin() { StatusMessage = ""; using var op = ClaudeBinOp.Begin(Loc.T("ops.claudeBin.saving")); var dto = await _worker.SetClaudeBinAsync(ClaudeBin); if (dto is null) { StatusMessage = Loc.T("vm.filesTab.workerOffline"); return; } ClaudeBin = dto.Value; ApplyResolvedHint(dto); } private void ApplyResolvedHint(ClaudeBinDto dto) => ClaudeBinResolvedHint = dto.ResolvedPath is null ? Loc.T("settings.files.claudeBinUnresolved") : Loc.T("settings.files.claudeBinResolved", dto.ResolvedPath); private bool CanRestoreDefaultAgents() => !RestoreOp.IsRunning; [RelayCommand(CanExecute = nameof(CanRestoreDefaultAgents))] private async Task RestoreDefaultAgents() { StatusMessage = ""; using var op = RestoreOp.Begin(Loc.T("ops.skills.restoringDefaults")); 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); } } [RelayCommand] private void ViewPrompt(string? kindName) { if (!Enum.TryParse(kindName, ignoreCase: true, out var kind)) return; ViewedKindName = kindName; ViewedContent = PromptFiles.ReadOrDefault(kind); } 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); }