feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar

This commit is contained in:
Richard
2026-08-10 10:48:34 +02:00
parent a0e18d2a57
commit b5bf97ae74
187 changed files with 20054 additions and 882 deletions
@@ -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.";
}
}