feat(ui): session skills registry tab + per-level selectors

This commit is contained in:
Mika Kuns
2026-07-23 16:47:14 +02:00
committed by mika kuns
parent b4c58087d2
commit 7c3c061428
12 changed files with 562 additions and 10 deletions
@@ -1,5 +1,8 @@
using System.Collections.ObjectModel;
using ClaudeDo.Data.Models;
using ClaudeDo.Localization;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Agent;
using CommunityToolkit.Mvvm.ComponentModel;
namespace ClaudeDo.Ui.ViewModels.Modals.Settings;
@@ -22,6 +25,8 @@ public sealed partial class GeneralSettingsTabViewModel : ViewModelBase
public IReadOnlyList<string> Models { get; } = ModelRegistry.Aliases;
public IReadOnlyList<string> PermissionModes { get; } = PermissionModeRegistry.Modes;
public ObservableCollection<SelectableSkillViewModel> SessionSkills { get; } = new();
public GeneralSettingsTabViewModel() { }
public GeneralSettingsTabViewModel(ILocalizer localizer, Action<string> persist)
@@ -51,4 +56,20 @@ public sealed partial class GeneralSettingsTabViewModel : ViewModelBase
return "Max parallel executions must be between 1 and 20.";
return null;
}
/// <summary>Loads the installed-skills checkbox list, reflecting the given selection.</summary>
public async Task LoadSessionSkillsAsync(IWorkerClient worker, IReadOnlyCollection<string>? selected)
{
var installed = await worker.GetSessionSkillsAsync();
var selectedSet = selected is null ? new HashSet<string>() : new HashSet<string>(selected);
SessionSkills.Clear();
foreach (var s in installed)
SessionSkills.Add(new SelectableSkillViewModel(s.Name, s.Description, selectedSet.Contains(s.Name)));
}
public List<string>? SelectedSessionSkillNames()
{
var names = SessionSkills.Where(s => s.IsSelected).Select(s => s.Name).ToList();
return names.Count == 0 ? null : names;
}
}
@@ -0,0 +1,78 @@
using System.Collections.ObjectModel;
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 SessionSkillsSettingsTabViewModel : ViewModelBase
{
private readonly IWorkerClient _worker;
[ObservableProperty] private string _installUrl = "";
[ObservableProperty] private string _statusMessage = "";
[ObservableProperty] private bool _isBusy;
public ObservableCollection<SessionSkillDto> Skills { get; } = new();
public SessionSkillsSettingsTabViewModel(IWorkerClient worker) => _worker = worker;
public async Task LoadAsync()
{
IsBusy = true;
try
{
var skills = await _worker.GetSessionSkillsAsync();
Skills.Clear();
foreach (var s in skills) Skills.Add(s);
}
finally { IsBusy = false; }
}
[RelayCommand]
private async Task InstallAsync()
{
if (string.IsNullOrWhiteSpace(InstallUrl)) return;
IsBusy = true; StatusMessage = "";
try
{
var installed = await _worker.InstallSessionSkillAsync(InstallUrl.Trim());
StatusMessage = Loc.T("vm.sessionSkillsTab.installed", string.Join(", ", installed));
InstallUrl = "";
await LoadAsync();
}
catch (Exception ex) { StatusMessage = Loc.T("vm.sessionSkillsTab.installFailed", ex.Message); }
finally { IsBusy = false; }
}
[RelayCommand]
private async Task UpdateAsync(string? sourceUrl)
{
if (string.IsNullOrWhiteSpace(sourceUrl)) return;
IsBusy = true; StatusMessage = "";
try
{
await _worker.UpdateSessionSkillAsync(sourceUrl);
StatusMessage = Loc.T("vm.sessionSkillsTab.updated");
await LoadAsync();
}
catch (Exception ex) { StatusMessage = Loc.T("vm.sessionSkillsTab.updateFailed", ex.Message); }
finally { IsBusy = false; }
}
[RelayCommand]
private async Task RemoveAsync(string? sourceUrl)
{
if (string.IsNullOrWhiteSpace(sourceUrl)) return;
IsBusy = true; StatusMessage = "";
try
{
await _worker.RemoveSessionSkillAsync(sourceUrl);
StatusMessage = Loc.T("vm.sessionSkillsTab.removed");
await LoadAsync();
}
catch (Exception ex) { StatusMessage = Loc.T("vm.sessionSkillsTab.removeFailed", ex.Message); }
finally { IsBusy = false; }
}
}