Kontext: src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs ist mit 1431 Zeilen ein God-VM mit ~12 Concerns (Log-Streaming, Titel/Description-Editing, Subtasks, Child-Outcomes, Merge-Preview/-Targets, Diff, Agent-Settings-Overrides, Notes-Mode, Prep-Mode, Tabs, Session-Outcome/Roadblocks, Worktree-Info). Jedes neue Feature landet dort. Änderungen — drei klar abgrenzbare Sektionen als ei ClaudeDo-Task: 483e419f-1ec8-46ba-986b-8b90d6596b49
197 lines
7.4 KiB
C#
197 lines
7.4 KiB
C#
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;
|
|
}
|