188 lines
7.2 KiB
C#
188 lines
7.2 KiB
C#
using System.Collections.ObjectModel;
|
|
using ClaudeDo.Data;
|
|
using ClaudeDo.Data.Models;
|
|
using ClaudeDo.Data.Repositories;
|
|
using ClaudeDo.Ui.Localization;
|
|
using ClaudeDo.Ui.Services;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace ClaudeDo.Ui.ViewModels.Modals;
|
|
|
|
public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
|
{
|
|
private readonly WorkerClient _worker;
|
|
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
|
|
|
public string ListId { get; set; } = "";
|
|
|
|
// True after the list was deleted, so the caller reloads the list nav instead of refreshing the row.
|
|
public bool Deleted { get; private set; }
|
|
|
|
// Wired by the view to prompt yes/no before deleting and to surface a blocking-FK error.
|
|
public Func<string, Task<bool>>? ConfirmAsync { get; set; }
|
|
public Func<string, Task>? ShowErrorAsync { get; set; }
|
|
|
|
[ObservableProperty] private string _name = "";
|
|
[ObservableProperty] private string _workingDir = "";
|
|
[ObservableProperty] private string _defaultCommitType = CommitTypeRegistry.DefaultType;
|
|
|
|
[ObservableProperty] private string? _selectedModel; // null = inherit from global
|
|
[ObservableProperty] private decimal? _maxTurns; // null = inherit from global
|
|
[ObservableProperty] private string _modelInheritedHint = ""; // resolved value placeholder, e.g. "sonnet"
|
|
[ObservableProperty] private string _modelBadge = "";
|
|
[ObservableProperty] private string _turnsInheritedHint = "";
|
|
[ObservableProperty] private string _turnsBadge = "";
|
|
[ObservableProperty] private string _agentBadge = "";
|
|
|
|
[ObservableProperty] private string _systemPrompt = "";
|
|
[ObservableProperty] private AgentInfo? _selectedAgent;
|
|
|
|
private string _globalModel = ModelRegistry.DefaultAlias;
|
|
private int _globalMaxTurns = 100;
|
|
|
|
public ObservableCollection<string> ModelOptions { get; } = new(ModelRegistry.Aliases);
|
|
|
|
public ObservableCollection<string> CommitTypeOptions { get; } = new(CommitTypeRegistry.Types);
|
|
|
|
public ObservableCollection<AgentInfo> Agents { get; } = new();
|
|
|
|
public Action? CloseAction { get; set; }
|
|
|
|
public ListSettingsModalViewModel(WorkerClient worker, IDbContextFactory<ClaudeDoDbContext> dbFactory)
|
|
{
|
|
_worker = worker;
|
|
_dbFactory = dbFactory;
|
|
}
|
|
|
|
partial void OnSelectedModelChanged(string? value) => RecomputeModelBadge();
|
|
partial void OnMaxTurnsChanged(decimal? value) => RecomputeTurnsBadge();
|
|
partial void OnSelectedAgentChanged(AgentInfo? value) => RecomputeAgentBadge();
|
|
|
|
private void RecomputeModelBadge()
|
|
{
|
|
ModelInheritedHint = _globalModel;
|
|
ModelBadge = !string.IsNullOrWhiteSpace(SelectedModel)
|
|
? Loc.T("settings.inherit.overrideBadge")
|
|
: Loc.T("settings.inherit.inheritedFromGlobal");
|
|
}
|
|
|
|
private void RecomputeTurnsBadge()
|
|
{
|
|
TurnsInheritedHint = _globalMaxTurns.ToString();
|
|
TurnsBadge = MaxTurns is not null
|
|
? Loc.T("settings.inherit.overrideBadge")
|
|
: Loc.T("settings.inherit.inheritedFromGlobal");
|
|
}
|
|
|
|
private void RecomputeAgentBadge()
|
|
{
|
|
var overridden = SelectedAgent is not null && !string.IsNullOrWhiteSpace(SelectedAgent.Path);
|
|
AgentBadge = overridden
|
|
? Loc.T("settings.inherit.overrideBadge")
|
|
: Loc.T("settings.inherit.inheritedFromGlobal");
|
|
}
|
|
|
|
public async Task LoadAsync(
|
|
string listId,
|
|
string name,
|
|
string? workingDir,
|
|
string defaultCommitType,
|
|
CancellationToken ct = default)
|
|
{
|
|
ListId = listId;
|
|
Name = name;
|
|
WorkingDir = workingDir ?? "";
|
|
DefaultCommitType = string.IsNullOrWhiteSpace(defaultCommitType) ? CommitTypeRegistry.DefaultType : defaultCommitType;
|
|
|
|
Agents.Clear();
|
|
Agents.Add(new AgentInfo("(none)", "", ""));
|
|
var agents = await _worker.GetAgentsAsync();
|
|
foreach (var a in agents) Agents.Add(a);
|
|
|
|
var config = await _worker.GetListConfigAsync(listId);
|
|
|
|
var app = await _worker.GetAppSettingsAsync();
|
|
_globalModel = app?.DefaultModel ?? ModelRegistry.DefaultAlias;
|
|
_globalMaxTurns = app?.DefaultMaxTurns ?? 100;
|
|
|
|
SelectedModel = string.IsNullOrWhiteSpace(config?.Model) ? null : config!.Model!;
|
|
MaxTurns = config?.MaxTurns is int mt ? mt : (decimal?)null;
|
|
SystemPrompt = config?.SystemPrompt ?? "";
|
|
SelectedAgent = string.IsNullOrWhiteSpace(config?.AgentPath)
|
|
? Agents[0]
|
|
: (Agents.FirstOrDefault(a => a.Path == config!.AgentPath) ?? Agents[0]);
|
|
|
|
RecomputeModelBadge();
|
|
RecomputeTurnsBadge();
|
|
RecomputeAgentBadge();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task SaveAsync()
|
|
{
|
|
var model = string.IsNullOrWhiteSpace(SelectedModel) ? null : SelectedModel;
|
|
var sp = string.IsNullOrWhiteSpace(SystemPrompt) ? null : SystemPrompt;
|
|
var ap = SelectedAgent is null || string.IsNullOrWhiteSpace(SelectedAgent.Path) ? null : SelectedAgent.Path;
|
|
var turns = MaxTurns is decimal d ? (int?)d : null;
|
|
|
|
await _worker.UpdateListAsync(new UpdateListDto(
|
|
ListId,
|
|
string.IsNullOrWhiteSpace(Name) ? Loc.T("vm.listSettings.untitled") : Name,
|
|
string.IsNullOrWhiteSpace(WorkingDir) ? null : WorkingDir,
|
|
DefaultCommitType));
|
|
|
|
await _worker.UpdateListConfigAsync(new UpdateListConfigDto(ListId, model, sp, ap, turns));
|
|
|
|
CloseAction?.Invoke();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task DeleteAsync()
|
|
{
|
|
var displayName = string.IsNullOrWhiteSpace(Name) ? Loc.T("vm.listSettings.untitled") : Name;
|
|
if (ConfirmAsync is not null)
|
|
{
|
|
var ok = await ConfirmAsync($"Delete list \"{displayName}\" and all its tasks? This cannot be undone.");
|
|
if (!ok) return;
|
|
}
|
|
|
|
try
|
|
{
|
|
await using var ctx = await _dbFactory.CreateDbContextAsync();
|
|
var lists = new ListRepository(ctx);
|
|
await lists.DeleteAsync(ListId);
|
|
}
|
|
catch (Exception ex) when (
|
|
(ex is Microsoft.Data.Sqlite.SqliteException
|
|
|| ex.InnerException is Microsoft.Data.Sqlite.SqliteException)
|
|
&& (ex.Message.Contains("FOREIGN KEY", StringComparison.OrdinalIgnoreCase)
|
|
|| ex.InnerException?.Message.Contains("FOREIGN KEY", StringComparison.OrdinalIgnoreCase) == true))
|
|
{
|
|
if (ShowErrorAsync is not null)
|
|
await ShowErrorAsync("This list has planning sessions with child tasks. Discard those first, then delete the list.");
|
|
return;
|
|
}
|
|
|
|
Deleted = true;
|
|
CloseAction?.Invoke();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void Cancel() => CloseAction?.Invoke();
|
|
|
|
[RelayCommand] private void ResetModel() => SelectedModel = null;
|
|
[RelayCommand] private void ResetTurns() => MaxTurns = null;
|
|
[RelayCommand] private void ResetAgent() => SelectedAgent = Agents.Count > 0 ? Agents[0] : null;
|
|
|
|
[RelayCommand]
|
|
private void ResetAgentSettings()
|
|
{
|
|
SelectedModel = null;
|
|
MaxTurns = null;
|
|
SystemPrompt = "";
|
|
SelectedAgent = Agents.Count > 0 ? Agents[0] : null;
|
|
}
|
|
}
|