refactor(agent-config): single AgentConfigEditor for list + task scopes
This commit is contained in:
259
src/ClaudeDo.Ui/ViewModels/Agent/AgentConfigEditorViewModel.cs
Normal file
259
src/ClaudeDo.Ui/ViewModels/Agent/AgentConfigEditorViewModel.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Agent;
|
||||
|
||||
public enum AgentConfigScope { List, Task }
|
||||
|
||||
/// <summary>
|
||||
/// One agent-config editor (Model / MaxTurns / SystemPrompt / AgentFile with inherited
|
||||
/// badges + reset) shared by the List Settings modal and the per-task gear flyout.
|
||||
/// Scope picks the inheritance depth (List: list→global; Task: task→list→global) and the
|
||||
/// persistence (List: explicit <see cref="SaveAsync"/>; Task: debounced auto-save).
|
||||
/// </summary>
|
||||
public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
private readonly IWorkerClient _worker;
|
||||
private readonly AgentConfigScope _scope;
|
||||
private readonly EventHandler _langChangedHandler;
|
||||
|
||||
/// scope==List ⇒ the list id; scope==Task ⇒ the task id. Null ⇒ no save target.
|
||||
internal string? TargetId { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsEnabled))]
|
||||
private bool _isRunning;
|
||||
|
||||
// Task scope gates the editor while the run is live; List scope is always enabled.
|
||||
public bool IsEnabled => !IsRunning;
|
||||
|
||||
[ObservableProperty] private string? _model;
|
||||
[ObservableProperty] private decimal? _maxTurns;
|
||||
[ObservableProperty] private string _systemPrompt = "";
|
||||
[ObservableProperty] private AgentInfo? _selectedAgent;
|
||||
|
||||
[ObservableProperty] private string _modelBadge = "";
|
||||
[ObservableProperty] private string _modelInheritedHint = "";
|
||||
[ObservableProperty] private string _turnsBadge = "";
|
||||
[ObservableProperty] private string _turnsInheritedHint = "";
|
||||
[ObservableProperty] private string _agentBadge = "";
|
||||
[ObservableProperty] private string _effectiveSystemPromptHint = "";
|
||||
|
||||
private string _globalModel = ModelRegistry.DefaultAlias;
|
||||
private int _globalMaxTurns = 100;
|
||||
private string? _listModel; // Task scope only
|
||||
private int? _listMaxTurns; // Task scope only
|
||||
private string? _listAgentName; // Task scope only
|
||||
|
||||
private bool _suppressSave;
|
||||
private CancellationTokenSource? _saveCts;
|
||||
|
||||
public int EffectiveMaxTurns =>
|
||||
MaxTurns is decimal t ? (int)t : (_listMaxTurns ?? _globalMaxTurns);
|
||||
|
||||
public ObservableCollection<string> ModelOptions { get; } = new(ModelRegistry.Aliases);
|
||||
public ObservableCollection<AgentInfo> Agents { get; } = new();
|
||||
|
||||
public AgentConfigEditorViewModel(IWorkerClient worker, AgentConfigScope scope)
|
||||
{
|
||||
_worker = worker;
|
||||
_scope = scope;
|
||||
_langChangedHandler = (_, _) => RecomputeBadges();
|
||||
// Only the long-lived Task editor needs live re-badging; the List editor is a
|
||||
// short-lived modal recreated with the current language on each open.
|
||||
if (scope == AgentConfigScope.Task)
|
||||
Loc.LanguageChanged += _langChangedHandler;
|
||||
}
|
||||
|
||||
public void Dispose() => Loc.LanguageChanged -= _langChangedHandler;
|
||||
|
||||
partial void OnModelChanged(string? value) { RecomputeModelBadge(); QueueSave(); }
|
||||
|
||||
partial void OnMaxTurnsChanged(decimal? value)
|
||||
{
|
||||
RecomputeTurnsBadge();
|
||||
OnPropertyChanged(nameof(EffectiveMaxTurns));
|
||||
QueueSave();
|
||||
}
|
||||
|
||||
partial void OnSystemPromptChanged(string value) => QueueSave();
|
||||
partial void OnSelectedAgentChanged(AgentInfo? value) { RecomputeAgentBadge(); QueueSave(); }
|
||||
|
||||
private void RecomputeBadges()
|
||||
{
|
||||
RecomputeModelBadge();
|
||||
RecomputeTurnsBadge();
|
||||
RecomputeAgentBadge();
|
||||
}
|
||||
|
||||
private void RecomputeModelBadge()
|
||||
{
|
||||
var own = string.IsNullOrWhiteSpace(Model) ? null : Model;
|
||||
var (value, source) = _scope == AgentConfigScope.Task
|
||||
? InheritanceResolver.Resolve(own, _listModel, _globalModel)
|
||||
: InheritanceResolver.ResolveList(own, _globalModel);
|
||||
ModelInheritedHint = value;
|
||||
ModelBadge = BadgeFor(source, own is not null);
|
||||
}
|
||||
|
||||
private void RecomputeTurnsBadge()
|
||||
{
|
||||
var own = MaxTurns?.ToString();
|
||||
var (value, source) = _scope == AgentConfigScope.Task
|
||||
? InheritanceResolver.Resolve(own, _listMaxTurns?.ToString(), _globalMaxTurns.ToString())
|
||||
: InheritanceResolver.ResolveList(own, _globalMaxTurns.ToString());
|
||||
TurnsInheritedHint = value;
|
||||
TurnsBadge = BadgeFor(source, MaxTurns is not null);
|
||||
}
|
||||
|
||||
private void RecomputeAgentBadge()
|
||||
{
|
||||
var agentSet = SelectedAgent is not null && !string.IsNullOrWhiteSpace(SelectedAgent.Path);
|
||||
var own = agentSet ? SelectedAgent!.Path : null;
|
||||
var (_, source) = _scope == AgentConfigScope.Task
|
||||
? InheritanceResolver.Resolve(own, _listAgentName, null)
|
||||
: InheritanceResolver.ResolveList(own, null);
|
||||
AgentBadge = BadgeFor(source, agentSet);
|
||||
}
|
||||
|
||||
private static string BadgeFor(InheritSource source, bool isSet) => isSet
|
||||
? Loc.T("settings.inherit.overrideBadge")
|
||||
: source == InheritSource.List
|
||||
? Loc.T("settings.inherit.inheritedFromList")
|
||||
: Loc.T("settings.inherit.inheritedFromGlobal");
|
||||
|
||||
private void QueueSave()
|
||||
{
|
||||
// List scope persists on the modal Save button; only Task auto-saves.
|
||||
if (_suppressSave || _scope != AgentConfigScope.Task || TargetId is null) return;
|
||||
_saveCts?.Cancel();
|
||||
_saveCts = new CancellationTokenSource();
|
||||
_ = DebouncedSaveAsync(_saveCts.Token);
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task DebouncedSaveAsync(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await System.Threading.Tasks.Task.Delay(300, ct);
|
||||
if (TargetId is null) return;
|
||||
await SaveAsync();
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch { }
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task SaveAsync()
|
||||
{
|
||||
if (TargetId is null) return;
|
||||
var model = string.IsNullOrWhiteSpace(Model) ? null : Model;
|
||||
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;
|
||||
|
||||
if (_scope == AgentConfigScope.Task)
|
||||
await _worker.UpdateTaskAgentSettingsAsync(new UpdateTaskAgentSettingsDto(TargetId, model, sp, ap, turns));
|
||||
else
|
||||
await _worker.UpdateListConfigAsync(new UpdateListConfigDto(TargetId, model, sp, ap, turns));
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task LoadForListAsync(string listId, CancellationToken ct = default)
|
||||
{
|
||||
_suppressSave = true;
|
||||
try
|
||||
{
|
||||
TargetId = listId;
|
||||
await ReloadAgentsAsync("(none)");
|
||||
await LoadGlobalDefaultsAsync();
|
||||
|
||||
var cfg = await _worker.GetListConfigAsync(listId);
|
||||
ApplyConfig(cfg?.Model, cfg?.MaxTurns, cfg?.SystemPrompt, cfg?.AgentPath);
|
||||
|
||||
_listModel = null; _listMaxTurns = null; _listAgentName = null;
|
||||
EffectiveSystemPromptHint = "";
|
||||
RecomputeBadges();
|
||||
OnPropertyChanged(nameof(EffectiveMaxTurns));
|
||||
}
|
||||
finally { _suppressSave = false; }
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task LoadForTaskAsync(TaskEntity entity, CancellationToken ct = default)
|
||||
{
|
||||
_suppressSave = true;
|
||||
try
|
||||
{
|
||||
TargetId = entity.Id;
|
||||
await ReloadAgentsAsync("(inherited)");
|
||||
ApplyConfig(entity.Model, entity.MaxTurns, entity.SystemPrompt, entity.AgentPath);
|
||||
|
||||
var listCfg = await _worker.GetListConfigAsync(entity.ListId);
|
||||
await LoadGlobalDefaultsAsync();
|
||||
_listModel = listCfg?.Model;
|
||||
_listMaxTurns = listCfg?.MaxTurns;
|
||||
_listAgentName = string.IsNullOrWhiteSpace(listCfg?.AgentPath)
|
||||
? null : System.IO.Path.GetFileName(listCfg!.AgentPath!);
|
||||
EffectiveSystemPromptHint = string.IsNullOrWhiteSpace(listCfg?.SystemPrompt)
|
||||
? "" : listCfg!.SystemPrompt!;
|
||||
|
||||
RecomputeBadges();
|
||||
OnPropertyChanged(nameof(EffectiveMaxTurns));
|
||||
}
|
||||
finally { _suppressSave = false; }
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_suppressSave = true;
|
||||
try
|
||||
{
|
||||
Model = null;
|
||||
MaxTurns = null;
|
||||
SystemPrompt = "";
|
||||
SelectedAgent = null;
|
||||
}
|
||||
finally { _suppressSave = false; }
|
||||
EffectiveSystemPromptHint = "";
|
||||
TargetId = null;
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task ReloadAgentsAsync(string placeholderName)
|
||||
{
|
||||
Agents.Clear();
|
||||
Agents.Add(new AgentInfo(placeholderName, "", ""));
|
||||
foreach (var a in await _worker.GetAgentsAsync()) Agents.Add(a);
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task LoadGlobalDefaultsAsync()
|
||||
{
|
||||
var app = await _worker.GetAppSettingsAsync();
|
||||
_globalModel = app?.DefaultModel ?? ModelRegistry.DefaultAlias;
|
||||
_globalMaxTurns = app?.DefaultMaxTurns ?? 100;
|
||||
}
|
||||
|
||||
private void ApplyConfig(string? model, int? maxTurns, string? systemPrompt, string? agentPath)
|
||||
{
|
||||
Model = string.IsNullOrWhiteSpace(model) ? null : model!;
|
||||
MaxTurns = maxTurns is int mt ? mt : (decimal?)null;
|
||||
SystemPrompt = systemPrompt ?? "";
|
||||
SelectedAgent = string.IsNullOrWhiteSpace(agentPath)
|
||||
? Agents[0]
|
||||
: (Agents.FirstOrDefault(a => a.Path == agentPath) ?? Agents[0]);
|
||||
}
|
||||
|
||||
[RelayCommand] private void ResetModel() => Model = null;
|
||||
[RelayCommand] private void ResetTurns() => MaxTurns = null;
|
||||
[RelayCommand] private void ResetAgent() => SelectedAgent = Agents.Count > 0 ? Agents[0] : null;
|
||||
|
||||
[RelayCommand]
|
||||
private void ResetAll()
|
||||
{
|
||||
Model = null;
|
||||
MaxTurns = null;
|
||||
SystemPrompt = "";
|
||||
SelectedAgent = Agents.Count > 0 ? Agents[0] : null;
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Ui.Helpers;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Islands;
|
||||
|
||||
public sealed partial class AgentSettingsSectionViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
private readonly IWorkerClient _worker;
|
||||
private readonly EventHandler _langChangedHandler;
|
||||
|
||||
internal string? TaskId { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsAgentSectionEnabled))]
|
||||
private bool _isRunning;
|
||||
|
||||
public bool IsAgentSectionEnabled => !IsRunning;
|
||||
|
||||
[ObservableProperty] private string? _taskModelSelection;
|
||||
[ObservableProperty] private string _taskSystemPrompt = "";
|
||||
[ObservableProperty] private AgentInfo? _taskSelectedAgent;
|
||||
[ObservableProperty] private decimal? _taskMaxTurns;
|
||||
[ObservableProperty] private string _modelBadge = "";
|
||||
[ObservableProperty] private string _modelInheritedHint = "";
|
||||
[ObservableProperty] private string _turnsBadge = "";
|
||||
[ObservableProperty] private string _turnsInheritedHint = "";
|
||||
[ObservableProperty] private string _agentBadge = "";
|
||||
[ObservableProperty] private string _effectiveSystemPromptHint = "";
|
||||
|
||||
private string _globalModel = ModelRegistry.DefaultAlias;
|
||||
private int _globalMaxTurns = 100;
|
||||
private string? _listModel;
|
||||
private int? _listMaxTurns;
|
||||
private string? _listAgentName;
|
||||
|
||||
private bool _suppressAgentSave;
|
||||
private CancellationTokenSource? _agentSaveCts;
|
||||
|
||||
public int EffectiveMaxTurns =>
|
||||
TaskMaxTurns is decimal t ? (int)t : (_listMaxTurns ?? _globalMaxTurns);
|
||||
|
||||
public ObservableCollection<string> TaskModelOptions { get; } = new(ModelRegistry.Aliases);
|
||||
public ObservableCollection<AgentInfo> TaskAgentOptions { get; } = new();
|
||||
|
||||
public AgentSettingsSectionViewModel(IWorkerClient worker)
|
||||
{
|
||||
_worker = worker;
|
||||
_langChangedHandler = (_, _) =>
|
||||
{
|
||||
RecomputeModelBadge();
|
||||
RecomputeTurnsBadge();
|
||||
RecomputeAgentBadge();
|
||||
};
|
||||
Loc.LanguageChanged += _langChangedHandler;
|
||||
}
|
||||
|
||||
public void Dispose() => Loc.LanguageChanged -= _langChangedHandler;
|
||||
|
||||
partial void OnTaskModelSelectionChanged(string? value) { RecomputeModelBadge(); QueueAgentSave(); }
|
||||
|
||||
partial void OnTaskMaxTurnsChanged(decimal? value)
|
||||
{
|
||||
RecomputeTurnsBadge();
|
||||
OnPropertyChanged(nameof(EffectiveMaxTurns));
|
||||
QueueAgentSave();
|
||||
}
|
||||
|
||||
partial void OnTaskSystemPromptChanged(string value) => QueueAgentSave();
|
||||
partial void OnTaskSelectedAgentChanged(AgentInfo? value) { RecomputeAgentBadge(); QueueAgentSave(); }
|
||||
|
||||
private void RecomputeModelBadge()
|
||||
{
|
||||
var (value, source) = InheritanceResolver.Resolve(TaskModelSelection, _listModel, _globalModel);
|
||||
ModelInheritedHint = value;
|
||||
ModelBadge = BadgeFor(source, !string.IsNullOrWhiteSpace(TaskModelSelection));
|
||||
}
|
||||
|
||||
private void RecomputeTurnsBadge()
|
||||
{
|
||||
var (value, source) = InheritanceResolver.Resolve(
|
||||
TaskMaxTurns?.ToString(), _listMaxTurns?.ToString(), _globalMaxTurns.ToString());
|
||||
TurnsInheritedHint = value;
|
||||
TurnsBadge = BadgeFor(source, TaskMaxTurns is not null);
|
||||
}
|
||||
|
||||
private void RecomputeAgentBadge()
|
||||
{
|
||||
var taskSet = TaskSelectedAgent is not null && !string.IsNullOrWhiteSpace(TaskSelectedAgent.Path);
|
||||
var (_, source) = InheritanceResolver.Resolve(
|
||||
taskSet ? TaskSelectedAgent!.Path : null, _listAgentName, null);
|
||||
AgentBadge = BadgeFor(source, taskSet);
|
||||
}
|
||||
|
||||
private static string BadgeFor(InheritSource source, bool taskSet) => taskSet
|
||||
? Loc.T("settings.inherit.overrideBadge")
|
||||
: source == InheritSource.List
|
||||
? Loc.T("settings.inherit.inheritedFromList")
|
||||
: Loc.T("settings.inherit.inheritedFromGlobal");
|
||||
|
||||
private void QueueAgentSave()
|
||||
{
|
||||
if (_suppressAgentSave || TaskId is null) return;
|
||||
_agentSaveCts?.Cancel();
|
||||
_agentSaveCts = new CancellationTokenSource();
|
||||
_ = SaveAgentSettingsAsync(_agentSaveCts.Token);
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task SaveAgentSettingsAsync(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await System.Threading.Tasks.Task.Delay(300, ct);
|
||||
if (TaskId is null) return;
|
||||
|
||||
var model = string.IsNullOrWhiteSpace(TaskModelSelection) ? null : TaskModelSelection;
|
||||
var sp = string.IsNullOrWhiteSpace(TaskSystemPrompt) ? null : TaskSystemPrompt;
|
||||
var ap = TaskSelectedAgent is null || string.IsNullOrWhiteSpace(TaskSelectedAgent.Path)
|
||||
? null : TaskSelectedAgent.Path;
|
||||
var turns = TaskMaxTurns is decimal d ? (int?)d : null;
|
||||
|
||||
await _worker.UpdateTaskAgentSettingsAsync(
|
||||
new UpdateTaskAgentSettingsDto(TaskId, model, sp, ap, turns));
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch { }
|
||||
}
|
||||
|
||||
internal async System.Threading.Tasks.Task LoadAsync(
|
||||
ClaudeDo.Data.Models.TaskEntity entity, CancellationToken ct)
|
||||
{
|
||||
_suppressAgentSave = true;
|
||||
try
|
||||
{
|
||||
TaskAgentOptions.Clear();
|
||||
TaskAgentOptions.Add(new AgentInfo("(inherited)", "", ""));
|
||||
var agents = await _worker.GetAgentsAsync();
|
||||
foreach (var a in agents) TaskAgentOptions.Add(a);
|
||||
|
||||
TaskModelSelection = string.IsNullOrWhiteSpace(entity.Model) ? null : entity.Model!;
|
||||
TaskMaxTurns = entity.MaxTurns is int tmt ? tmt : (decimal?)null;
|
||||
TaskSystemPrompt = entity.SystemPrompt ?? "";
|
||||
TaskSelectedAgent = string.IsNullOrWhiteSpace(entity.AgentPath)
|
||||
? TaskAgentOptions[0]
|
||||
: (TaskAgentOptions.FirstOrDefault(a => a.Path == entity.AgentPath) ?? TaskAgentOptions[0]);
|
||||
|
||||
var listCfg = await _worker.GetListConfigAsync(entity.ListId);
|
||||
var app = await _worker.GetAppSettingsAsync();
|
||||
_globalModel = app?.DefaultModel ?? ModelRegistry.DefaultAlias;
|
||||
_globalMaxTurns = app?.DefaultMaxTurns ?? 100;
|
||||
_listModel = listCfg?.Model;
|
||||
_listMaxTurns = listCfg?.MaxTurns;
|
||||
_listAgentName = string.IsNullOrWhiteSpace(listCfg?.AgentPath)
|
||||
? null : System.IO.Path.GetFileName(listCfg!.AgentPath!);
|
||||
|
||||
EffectiveSystemPromptHint = string.IsNullOrWhiteSpace(listCfg?.SystemPrompt)
|
||||
? "" : listCfg!.SystemPrompt!;
|
||||
|
||||
RecomputeModelBadge();
|
||||
RecomputeTurnsBadge();
|
||||
RecomputeAgentBadge();
|
||||
OnPropertyChanged(nameof(EffectiveMaxTurns));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressAgentSave = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
_suppressAgentSave = true;
|
||||
try
|
||||
{
|
||||
TaskModelSelection = null;
|
||||
TaskMaxTurns = null;
|
||||
TaskSystemPrompt = "";
|
||||
TaskSelectedAgent = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressAgentSave = false;
|
||||
}
|
||||
EffectiveSystemPromptHint = "";
|
||||
TaskId = null;
|
||||
}
|
||||
|
||||
[RelayCommand] private void ResetTaskModel() => TaskModelSelection = null;
|
||||
[RelayCommand] private void ResetTaskTurns() => TaskMaxTurns = null;
|
||||
[RelayCommand] private void ResetTaskAgent() =>
|
||||
TaskSelectedAgent = TaskAgentOptions.Count > 0 ? TaskAgentOptions[0] : null;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using ClaudeDo.Ui.Helpers;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.Services.Interfaces;
|
||||
using ClaudeDo.Ui.ViewModels.Agent;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -56,7 +57,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
private readonly IMergeCoordinator _merge;
|
||||
|
||||
// ── Section view models ───────────────────────────────────────────────────
|
||||
public AgentSettingsSectionViewModel AgentSettings { get; }
|
||||
public AgentConfigEditorViewModel AgentSettings { get; }
|
||||
public MergeSectionViewModel Merge { get; }
|
||||
public PrepPanelViewModel Prep { get; }
|
||||
|
||||
@@ -425,7 +426,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
_notesApi = notesApi;
|
||||
_merge = merge;
|
||||
|
||||
AgentSettings = new AgentSettingsSectionViewModel(worker);
|
||||
AgentSettings = new AgentConfigEditorViewModel(worker, AgentConfigScope.Task);
|
||||
Merge = new MergeSectionViewModel(worker, services);
|
||||
Prep = new PrepPanelViewModel(worker);
|
||||
|
||||
@@ -436,7 +437,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
|
||||
AgentSettings.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(AgentSettingsSectionViewModel.EffectiveMaxTurns))
|
||||
if (e.PropertyName == nameof(AgentConfigEditorViewModel.EffectiveMaxTurns))
|
||||
OnPropertyChanged(nameof(TurnsText));
|
||||
};
|
||||
|
||||
@@ -685,8 +686,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
Merge.SyncWorktree(WorktreePath, WorktreeBaseCommit, WorktreeHeadCommit,
|
||||
WorktreeStateLabel, _listWorkingDir);
|
||||
|
||||
AgentSettings.TaskId = row.Id;
|
||||
await AgentSettings.LoadAsync(entity, ct);
|
||||
await AgentSettings.LoadForTaskAsync(entity, ct);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var runRepo = new TaskRunRepository(ctx);
|
||||
|
||||
@@ -4,6 +4,7 @@ using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels.Agent;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -28,25 +29,11 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
[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();
|
||||
// The shared agent-config editor (Model / MaxTurns / SystemPrompt / AgentFile),
|
||||
// scoped to this list (list → global inheritance).
|
||||
public AgentConfigEditorViewModel Agent { get; }
|
||||
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
@@ -54,34 +41,7 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
{
|
||||
_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");
|
||||
Agent = new AgentConfigEditorViewModel(worker, AgentConfigScope.List);
|
||||
}
|
||||
|
||||
public async Task LoadAsync(
|
||||
@@ -96,44 +56,19 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
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();
|
||||
await Agent.LoadForListAsync(listId, ct);
|
||||
}
|
||||
|
||||
[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));
|
||||
await Agent.SaveAsync();
|
||||
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
@@ -171,17 +106,4 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user