feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using ClawdDotNet.App;
|
||||
using ClawdDotNet.Core.Config;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ClawdDotNet.Desktop.ViewModels;
|
||||
|
||||
public sealed partial class ToolItemViewModel : ObservableObject
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEnabled;
|
||||
|
||||
public ToolItemViewModel(string name, bool isEnabled)
|
||||
{
|
||||
Name = name;
|
||||
_isEnabled = isEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class AgentItemViewModel : ObservableObject
|
||||
{
|
||||
public AgentConfig Config { get; }
|
||||
|
||||
public string AgentId => Config.AgentId;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _displayName;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _model;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _systemPrompt;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _identity;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _soul;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _description;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _promptCaching;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _maxToolResultChars;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _maxSteps;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _maxContextTokens;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _timeoutSeconds;
|
||||
|
||||
[ObservableProperty]
|
||||
private decimal _dailyCostUsd;
|
||||
|
||||
[ObservableProperty]
|
||||
private long _dailyTokens;
|
||||
|
||||
public ObservableCollection<ToolItemViewModel> AvailableTools { get; } = [];
|
||||
|
||||
public static readonly List<string> KnownTools =
|
||||
[
|
||||
"FileRW", "Telegram", "Mail", "Database", "FTP",
|
||||
"DirectAPI", "WebFetch", "WebMonitor", "AgentComm",
|
||||
"SocialMediaManager", "AgentSpawn", "AgentEditor", "Memory", "Taskboard"
|
||||
];
|
||||
|
||||
public AgentItemViewModel(AgentConfig config)
|
||||
{
|
||||
Config = config;
|
||||
_displayName = config.DisplayName;
|
||||
_model = config.Model;
|
||||
_systemPrompt = config.SystemPrompt;
|
||||
_identity = config.Identity;
|
||||
_soul = config.Soul;
|
||||
_description = config.Description;
|
||||
_promptCaching = config.PromptCaching;
|
||||
_maxToolResultChars = config.MaxToolResultChars;
|
||||
_maxSteps = config.LoopGuard.MaxSteps;
|
||||
_maxContextTokens = config.LoopGuard.MaxContextTokens;
|
||||
_timeoutSeconds = config.LoopGuard.TimeoutSeconds;
|
||||
_dailyCostUsd = config.Budget.DailyCostUsd;
|
||||
_dailyTokens = config.Budget.DailyTokens;
|
||||
|
||||
foreach (var tool in KnownTools)
|
||||
{
|
||||
var isEnabled = config.Tools.ContainsKey(tool);
|
||||
var item = new ToolItemViewModel(tool, isEnabled);
|
||||
item.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(ToolItemViewModel.IsEnabled))
|
||||
{
|
||||
if (item.IsEnabled && !Config.Tools.ContainsKey(tool))
|
||||
Config.Tools[tool] = new Dictionary<string, object?>();
|
||||
else if (!item.IsEnabled)
|
||||
Config.Tools.Remove(tool);
|
||||
}
|
||||
};
|
||||
AvailableTools.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyChanges()
|
||||
{
|
||||
Config.DisplayName = DisplayName;
|
||||
Config.Model = Model;
|
||||
Config.SystemPrompt = SystemPrompt;
|
||||
Config.Identity = Identity;
|
||||
Config.Soul = Soul;
|
||||
Config.Description = Description;
|
||||
Config.PromptCaching = PromptCaching;
|
||||
Config.MaxToolResultChars = MaxToolResultChars;
|
||||
Config.LoopGuard.MaxSteps = MaxSteps;
|
||||
Config.LoopGuard.MaxContextTokens = MaxContextTokens;
|
||||
Config.LoopGuard.TimeoutSeconds = TimeoutSeconds;
|
||||
Config.Budget.DailyCostUsd = DailyCostUsd;
|
||||
Config.Budget.DailyTokens = DailyTokens;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ansichtsmodell für die Agenten-Verwaltungsseite.
|
||||
/// </summary>
|
||||
public sealed partial class AgentsPageViewModel : PageViewModel
|
||||
{
|
||||
private readonly AppHost? _host;
|
||||
private readonly ILogger? _logger;
|
||||
|
||||
public ObservableCollection<AgentItemViewModel> Agents { get; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private AgentItemViewModel? _selectedAgent;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _statusText = "Bereit";
|
||||
|
||||
public List<string> KnownModels { get; } =
|
||||
[
|
||||
"anthropic/claude-3-5-sonnet",
|
||||
"anthropic/claude-3-opus",
|
||||
"openai/gpt-4o",
|
||||
"openai/gpt-4o-mini",
|
||||
"google/gemini-2.5-flash",
|
||||
"google/gemini-2.5-pro",
|
||||
"deepseek/deepseek-r1"
|
||||
];
|
||||
|
||||
public List<string> CachingOptions { get; } = ["auto", "on", "off"];
|
||||
|
||||
public event Func<AddAgentViewModel, Task<AddAgentResultData?>>? RequestAddAgentDialog;
|
||||
public event Func<ToolSettingsViewModel, Task<Dictionary<string, object?>?>>? RequestToolSettingsDialog;
|
||||
|
||||
public AgentsPageViewModel() : this(null) { }
|
||||
|
||||
public AgentsPageViewModel(AppHost? host) : base("Agenten")
|
||||
{
|
||||
_host = host;
|
||||
|
||||
if (host is not null)
|
||||
{
|
||||
_logger = host.LoggerFactory.CreateLogger("ClawdDotNet.Desktop.Agents");
|
||||
ReloadAgents();
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusText = "Entwurfsmodus";
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ReloadAgents()
|
||||
{
|
||||
Agents.Clear();
|
||||
if (_host is null) return;
|
||||
|
||||
foreach (var agent in _host.Instance.Agents)
|
||||
{
|
||||
Agents.Add(new AgentItemViewModel(agent));
|
||||
}
|
||||
|
||||
SelectedAgent = Agents.FirstOrDefault();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddAgentAsync()
|
||||
{
|
||||
if (_host is null || RequestAddAgentDialog is null) return;
|
||||
|
||||
var vm = new AddAgentViewModel();
|
||||
var result = await RequestAddAgentDialog(vm);
|
||||
if (result is null) return;
|
||||
|
||||
if (_host.Instance.Agents.Any(a => a.AgentId.Equals(result.AgentId, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
StatusText = $"Ein Agent mit der ID '{result.AgentId}' existiert bereits.";
|
||||
return;
|
||||
}
|
||||
|
||||
var newConfig = new AgentConfig
|
||||
{
|
||||
AgentId = result.AgentId,
|
||||
DisplayName = result.DisplayName,
|
||||
Model = result.Model,
|
||||
Description = result.Description
|
||||
};
|
||||
|
||||
_host.Instance.Agents.Add(newConfig);
|
||||
SaveAgents();
|
||||
|
||||
var item = new AgentItemViewModel(newConfig);
|
||||
Agents.Add(item);
|
||||
SelectedAgent = item;
|
||||
|
||||
StatusText = $"Agent '{result.DisplayName}' angelegt.";
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RemoveAgent()
|
||||
{
|
||||
if (_host is null || SelectedAgent is null) return;
|
||||
|
||||
var agentId = SelectedAgent.AgentId;
|
||||
_host.Instance.Agents.RemoveAll(a => a.AgentId == agentId);
|
||||
|
||||
SaveAgents();
|
||||
Agents.Remove(SelectedAgent);
|
||||
SelectedAgent = Agents.FirstOrDefault();
|
||||
|
||||
StatusText = $"Agent '{agentId}' entfernt.";
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ConfigureToolAsync(ToolItemViewModel? toolItem)
|
||||
{
|
||||
if (SelectedAgent is null || toolItem is null || RequestToolSettingsDialog is null) return;
|
||||
|
||||
var toolName = toolItem.Name;
|
||||
if (!SelectedAgent.Config.Tools.TryGetValue(toolName, out var config))
|
||||
{
|
||||
config = new Dictionary<string, object?>();
|
||||
SelectedAgent.Config.Tools[toolName] = config;
|
||||
toolItem.IsEnabled = true;
|
||||
}
|
||||
|
||||
var vm = new ToolSettingsViewModel(toolName, config);
|
||||
var newConfig = await RequestToolSettingsDialog(vm);
|
||||
|
||||
if (newConfig is not null)
|
||||
{
|
||||
SelectedAgent.Config.Tools[toolName] = newConfig;
|
||||
SaveAgents();
|
||||
StatusText = $"Einstellungen für Werkzeug '{toolName}' gespeichert.";
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SaveAgents()
|
||||
{
|
||||
if (_host is null) return;
|
||||
|
||||
foreach (var agentVm in Agents)
|
||||
{
|
||||
agentVm.ApplyChanges();
|
||||
}
|
||||
|
||||
_host.Directories.SaveInstanceConfig(_host.InstancePath, _host.Instance);
|
||||
_logger?.LogInformation("Agenten-Konfiguration gespeichert.");
|
||||
StatusText = "Agenten-Konfiguration gespeichert.";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user