feat(ui): session skills registry tab + per-level selectors
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ClaudeDo.Data.Models;
|
||||
@@ -57,6 +60,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
|
||||
public ObservableCollection<string> ModelOptions { get; } = new(ModelRegistry.Aliases);
|
||||
public ObservableCollection<AgentInfo> Agents { get; } = new();
|
||||
public ObservableCollection<SelectableSkillViewModel> SessionSkills { get; } = new();
|
||||
|
||||
public AgentConfigEditorViewModel(IWorkerClient worker, AgentConfigScope scope)
|
||||
{
|
||||
@@ -67,9 +71,31 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
// short-lived modal recreated with the current language on each open.
|
||||
if (scope == AgentConfigScope.Task)
|
||||
Loc.LanguageChanged += _langChangedHandler;
|
||||
|
||||
SessionSkills.CollectionChanged += OnSessionSkillsCollectionChanged;
|
||||
}
|
||||
|
||||
public void Dispose() => Loc.LanguageChanged -= _langChangedHandler;
|
||||
private void OnSessionSkillsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.NewItems is not null)
|
||||
foreach (SelectableSkillViewModel item in e.NewItems)
|
||||
item.PropertyChanged += OnSkillItemPropertyChanged;
|
||||
if (e.OldItems is not null)
|
||||
foreach (SelectableSkillViewModel item in e.OldItems)
|
||||
item.PropertyChanged -= OnSkillItemPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnSkillItemPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(SelectableSkillViewModel.IsSelected)) QueueSave();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Loc.LanguageChanged -= _langChangedHandler;
|
||||
SessionSkills.CollectionChanged -= OnSessionSkillsCollectionChanged;
|
||||
foreach (var s in SessionSkills) s.PropertyChanged -= OnSkillItemPropertyChanged;
|
||||
}
|
||||
|
||||
partial void OnModelChanged(string? value) { RecomputeModelBadge(); QueueSave(); }
|
||||
|
||||
@@ -154,11 +180,18 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
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;
|
||||
var skills = SelectedSessionSkillNames();
|
||||
|
||||
if (_scope == AgentConfigScope.Task)
|
||||
await _worker.UpdateTaskAgentSettingsAsync(new UpdateTaskAgentSettingsDto(TargetId, model, sp, ap, turns));
|
||||
await _worker.UpdateTaskAgentSettingsAsync(new UpdateTaskAgentSettingsDto(TargetId, model, sp, ap, turns, skills));
|
||||
else
|
||||
await _worker.UpdateListConfigAsync(new UpdateListConfigDto(TargetId, model, sp, ap, turns));
|
||||
await _worker.UpdateListConfigAsync(new UpdateListConfigDto(TargetId, model, sp, ap, turns, skills));
|
||||
}
|
||||
|
||||
private List<string>? SelectedSessionSkillNames()
|
||||
{
|
||||
var names = SessionSkills.Where(s => s.IsSelected).Select(s => s.Name).ToList();
|
||||
return names.Count == 0 ? null : names;
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task LoadForListAsync(string listId, CancellationToken ct = default)
|
||||
@@ -172,6 +205,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
|
||||
var cfg = await _worker.GetListConfigAsync(listId);
|
||||
ApplyConfig(cfg?.Model, cfg?.MaxTurns, cfg?.SystemPrompt, cfg?.AgentPath);
|
||||
await ReloadSessionSkillsAsync(cfg?.SessionSkills);
|
||||
|
||||
_listModel = null; _listMaxTurns = null; _listAgentName = null;
|
||||
EffectiveSystemPromptHint = "";
|
||||
@@ -189,6 +223,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
TargetId = entity.Id;
|
||||
await ReloadAgentsAsync("(inherited)");
|
||||
ApplyConfig(entity.Model, entity.MaxTurns, entity.SystemPrompt, entity.AgentPath);
|
||||
await ReloadSessionSkillsAsync(ParseSessionSkills(entity.SessionSkills));
|
||||
|
||||
var listCfg = await _worker.GetListConfigAsync(entity.ListId);
|
||||
await LoadGlobalDefaultsAsync();
|
||||
@@ -214,12 +249,30 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
MaxTurns = null;
|
||||
SystemPrompt = "";
|
||||
SelectedAgent = null;
|
||||
foreach (var s in SessionSkills) s.IsSelected = false;
|
||||
}
|
||||
finally { _suppressSave = false; }
|
||||
EffectiveSystemPromptHint = "";
|
||||
TargetId = null;
|
||||
}
|
||||
|
||||
private static List<string>? ParseSessionSkills(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json)) return null;
|
||||
try { return JsonSerializer.Deserialize<List<string>>(json); }
|
||||
catch (JsonException) { return null; }
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task ReloadSessionSkillsAsync(IReadOnlyCollection<string>? selected)
|
||||
{
|
||||
var installed = await _worker.GetSessionSkillsAsync();
|
||||
var selectedSet = selected is null ? new HashSet<string>() : new HashSet<string>(selected);
|
||||
foreach (var s in SessionSkills) s.PropertyChanged -= OnSkillItemPropertyChanged;
|
||||
SessionSkills.Clear();
|
||||
foreach (var s in installed)
|
||||
SessionSkills.Add(new SelectableSkillViewModel(s.Name, s.Description, selectedSet.Contains(s.Name)));
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task ReloadAgentsAsync(string placeholderName)
|
||||
{
|
||||
Agents.Clear();
|
||||
@@ -255,5 +308,6 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
||||
MaxTurns = null;
|
||||
SystemPrompt = "";
|
||||
SelectedAgent = Agents.Count > 0 ? Agents[0] : null;
|
||||
foreach (var s in SessionSkills) s.IsSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user