UI-Entwurf umgesetzt, Rocket.Chat-Tool, Deploymentcenter 2.4

Sicherungspunkt vor dem Aufraeumen. Buendelt die Arbeit, die seit dem
Abschluss der Avalonia-Portierung im Arbeitsverzeichnis lag.

Oberflaeche
- Entwurf aus Mockup/ umgesetzt: Theme.axaml (Farben je Thema, Barlow als
  mitgelieferte Schrift), Icons.axaml (Symbolgeometrien), Shell.axaml
  (eigene ControlThemes statt Fluent umzufaerben).
- Neue Steuerelemente StrokeIcon und BlueprintFrame, Seiten fuer
  Token-Verbrauch und Agenten-Chats, Werkzeug-Einstellungen als Seite
  statt eigenem Fenster, Texteditor-Fenster.
- ThemeManager mit hellem und dunklem Thema; die beiden Pinsel-Konverter
  entfallen, weil ein fester Farbwert den Themenwechsel nicht ueberlebt.

Rocket.Chat
- Neues Tool-Projekt (Client, Konfiguration, Workspace-Dateien) nach der
  Bauform des Telegram-Tools: rocketchat_poll als Tool-Job, geweckt wird
  nur, wenn wirklich etwas anliegt.
- send_file ist freigabepflichtig, send_message bewusst nicht: Der Raum
  ist Arbeitsraum, der Schutz sitzt an der Raum-Allowlist.
- Konzept-Doc um die Messung gegen die echte Instanz 8.7 ergaenzt; drei
  Annahmen waren falsch und sind korrigiert.

Deploymentcenter
- DC6 (Update anwenden) und DC7 (Erstinstallation ueber setup.json)
  erledigt, DC3 fuer win-x64/dev; deploy/publish.py als Release-Strecke.
- AppHost.DisposeAsync gegen doppeltes Herunterfahren gesperrt - sonst
  ueberschreibt eine zweite Abmeldung den Wartungszustand am Watchdog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-23 12:16:05 +02:00
co-authored by Claude Opus 5
parent b5bf97ae74
commit 33d95a6c3f
88 changed files with 11665 additions and 1029 deletions
@@ -0,0 +1,61 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="700"
x:Class="ClawdDotNet.Desktop.Views.AgentChatsPageView"
x:DataType="vm:AgentChatsPageViewModel">
<DockPanel MaxWidth="820" HorizontalAlignment="Left">
<!-- Wer mit wem -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="10"
Margin="0,0,0,16" VerticalAlignment="Center">
<TextBlock Text="Agent A:" FontWeight="SemiBold" VerticalAlignment="Center" />
<ComboBox Width="220" ItemsSource="{Binding Agents}" SelectedItem="{Binding AgentA}" />
<TextBlock Text="Agent B:" FontWeight="SemiBold" VerticalAlignment="Center"
Margin="8,0,0,0" />
<ComboBox Width="220" ItemsSource="{Binding Agents}" SelectedItem="{Binding AgentB}" />
</StackPanel>
<!-- Leerzustand: erklärt, warum hier noch nichts steht -->
<Border Classes="card" DockPanel.Dock="Top" VerticalAlignment="Top"
IsVisible="{Binding IsEmpty}">
<TextBlock Classes="muted" Text="{Binding EmptyText}" TextWrapping="Wrap" />
</Border>
<!-- Verlauf -->
<ScrollViewer VerticalScrollBarVisibility="Auto" IsVisible="{Binding !IsEmpty}">
<ItemsControl ItemsSource="{Binding Messages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="10" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:AgentMessageItem">
<Border Classes="card" MaxWidth="560"
Classes.accent="{Binding !IsFromAgentA}"
HorizontalAlignment="{Binding !IsFromAgentA,
Converter={x:Static conv:ChatAlignmentConverter.Instance}}">
<StackPanel Spacing="4">
<DockPanel>
<TextBlock FontSize="12" FontWeight="SemiBold" Text="{Binding SenderName}" />
<TextBlock Classes="caption" DockPanel.Dock="Right" HorizontalAlignment="Right"
Text="{Binding Time, StringFormat='{}{0:dd.MM. HH:mm}'}" />
</DockPanel>
<SelectableTextBlock FontSize="13.5" TextWrapping="Wrap" Text="{Binding Text}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</UserControl>
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace ClawdDotNet.Desktop.Views;
public partial class AgentChatsPageView : UserControl
{
public AgentChatsPageView() => InitializeComponent();
}
+248 -118
View File
@@ -3,153 +3,283 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
Padding="16"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="820"
x:Class="ClawdDotNet.Desktop.Views.AgentsPageView"
x:DataType="vm:AgentsPageViewModel">
<!--
Vier Felder wie in der WinForms-Vorlage: oben Agentenliste und Agenteneinstellungen,
unten Werkzeugliste und Werkzeugeinstellungen — aber mit ziehbaren Teilern statt
fester SplitterDistance und mit flachen Feldern statt PropertyGrid.
Die Teilerstellungen werden im Code-Behind gelesen und gespeichert: ColumnDefinition
liegt nicht im Sichtbarkeitsbaum und erbt deshalb keinen DataContext — eine Bindung
darauf wuerde stillschweigend nichts tun.
-->
<DockPanel>
<!-- Statusbar am unteren Rand -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Text="{Binding StatusText}" VerticalAlignment="Center" />
</Border>
<TextBlock DockPanel.Dock="Bottom" Classes="caption" Margin="0,10,0,0"
Text="{Binding StatusText}" TextWrapping="Wrap" />
<!-- Master-Detail Split -->
<Grid ColumnDefinitions="260,*">
<Grid x:Name="OuterGrid" RowDefinitions="*,Auto,*">
<!-- Linke Spalte: Agenten-Liste -->
<DockPanel Grid.Column="0" Margin="0,0,12,0">
<!-- Toolbar -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<Button Content="Neu" Command="{Binding AddAgentCommand}" Classes="accent" />
<Button Content="Entfernen" Command="{Binding RemoveAgentCommand}" IsEnabled="{Binding SelectedAgent, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="Speichern" Command="{Binding SaveAgentsCommand}" />
</StackPanel>
<!-- ══════════════════ Oben: Agenten ══════════════════ -->
<Grid x:Name="TopGrid" Grid.Row="0" ColumnDefinitions="0.44*,Auto,0.56*">
<!-- Liste -->
<ListBox ItemsSource="{Binding Agents}" SelectedItem="{Binding SelectedAgent}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:AgentItemViewModel">
<StackPanel Margin="4">
<TextBlock Text="{Binding DisplayName}" FontWeight="Bold" />
<TextBlock Text="{Binding Model}" Classes="caption" />
<DockPanel Grid.Column="0" Margin="0,0,14,0">
<WrapPanel DockPanel.Dock="Top" Classes="toolbar">
<Button Classes="toolbar" Command="{Binding AddAgentCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlus}" Width="14" Height="14" />
<TextBlock Text="Agent" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Button>
</DockPanel>
<Button Classes="toolbar" Command="{Binding RemoveAgentCommand}"
IsEnabled="{Binding SelectedAgent, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconMinus}" Width="14" Height="14" />
<TextBlock Text="Agent" />
</StackPanel>
</Button>
<!-- Rechte Spalte: Agenten-Details -->
<ContentControl Grid.Column="1" Content="{Binding SelectedAgent}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="vm:AgentItemViewModel">
<TabControl>
<Border Classes="sep" />
<Button Classes="toolbar" Command="{Binding RunAgentNowCommand}"
IsEnabled="{Binding !IsRunning}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlay}" Width="12" Height="12" IsFilled="True" />
<TextBlock Text="Jetzt ausf." />
</StackPanel>
</Button>
<Border Classes="sep" />
<Button Classes="toolbar" Command="{Binding EditIdentityCommand}"
IsEnabled="{Binding SelectedAgent, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPencil}" Width="14" Height="14" />
<TextBlock Text="Identity" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding EditSoulCommand}"
IsEnabled="{Binding SelectedAgent, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPencil}" Width="14" Height="14" />
<TextBlock Text="Soul" />
</StackPanel>
</Button>
<Border Classes="sep" />
<Button Classes="toolbar" Command="{Binding OpenAgentFolderCommand}"
IsEnabled="{Binding SelectedAgent, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconFolder}" Width="14" Height="14" />
<TextBlock Text="Ordner" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding SaveAgentsCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconSave}" Width="14" Height="14" />
<TextBlock Text="Speichern" />
</StackPanel>
</Button>
</WrapPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.4*,1.6*,2*">
<TextBlock Grid.Column="0" Classes="th" Text="ANZEIGENAME" />
<TextBlock Grid.Column="1" Classes="th" Text="MODELL" />
<TextBlock Grid.Column="2" Classes="th" Text="KURZINFO" />
</Grid>
</Border>
<ListBox Classes="table" ItemsSource="{Binding Agents}"
SelectedItem="{Binding SelectedAgent}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:AgentItemViewModel">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="1.4*,1.6*,2*">
<TextBlock Grid.Column="0" Classes="td" FontWeight="SemiBold"
Text="{Binding DisplayName}" />
<TextBlock Grid.Column="1" Classes="td dim" Text="{Binding Model}" />
<TextBlock Grid.Column="2" Classes="td dim" Text="{Binding Description}" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<GridSplitter x:Name="TopSplitter" Grid.Column="1" Classes="vertical"
ResizeDirection="Columns" />
<!-- Einstellungen des gewählten Agenten -->
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto" Padding="4,0,0,0">
<ContentControl Content="{Binding SelectedAgent}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="vm:AgentItemViewModel">
<StackPanel Spacing="20" MaxWidth="640" HorizontalAlignment="Left">
<!-- TAB 1: Allgemein & Modell -->
<TabItem Header="Allgemein &amp; Modell">
<ScrollViewer Padding="8">
<StackPanel Spacing="12">
<TextBlock Classes="kicker" Text="ALLGEMEIN &amp; MODELL" />
<TextBlock Classes="h2" Text="{Binding DisplayName}" />
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Agent ID:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding AgentId}" IsReadOnly="True" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Anzeigename:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding DisplayName}" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Modell:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding $parent[UserControl].((vm:AgentsPageViewModel)DataContext).KnownModels}" SelectedItem="{Binding Model}" HorizontalAlignment="Stretch" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Rolle / Beschreibung:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Description}" AcceptsReturn="True" Height="60" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Prompt Caching:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="4" Grid.Column="1" ItemsSource="{Binding $parent[UserControl].((vm:AgentsPageViewModel)DataContext).CachingOptions}" SelectedItem="{Binding PromptCaching}" Width="150" HorizontalAlignment="Left" Margin="0,8,0,0" />
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Agent-ID" />
<TextBox Text="{Binding AgentId}" IsReadOnly="True" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Anzeigename" />
<TextBox Text="{Binding DisplayName}" />
</StackPanel>
</Grid>
<!-- Limits Card -->
<Border Classes="card" Margin="0,8,0,0">
<StackPanel Spacing="8">
<TextBlock Text="Schutzgrenzen (LoopGuard &amp; Budget)" Classes="heading" />
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto,Auto">
<StackPanel Grid.Row="0" Grid.Column="0" Spacing="4">
<TextBlock Text="Max. Schritte pro Run:" />
<NumericUpDown Value="{Binding MaxSteps}" Minimum="1" Maximum="100" />
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" Spacing="4" Margin="8,0,0,0">
<TextBlock Text="Timeout (Sekunden):" />
<NumericUpDown Value="{Binding TimeoutSeconds}" Minimum="10" Maximum="3600" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="0" Spacing="4" Margin="0,8,0,0">
<TextBlock Text="Max. Kontext-Tokens:" />
<NumericUpDown Value="{Binding MaxContextTokens}" Minimum="1000" Maximum="2000000" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" Spacing="4" Margin="8,8,0,0">
<TextBlock Text="Max. Tool-Ergebnis (Zeichen):" />
<NumericUpDown Value="{Binding MaxToolResultChars}" Minimum="1000" Maximum="500000" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Modell" />
<ComboBox HorizontalAlignment="Stretch"
ItemsSource="{Binding $parent[UserControl].((vm:AgentsPageViewModel)DataContext).KnownModels}"
SelectedItem="{Binding Model}" />
</StackPanel>
</Border>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Prompt Caching" />
<ComboBox HorizontalAlignment="Stretch"
ItemsSource="{Binding $parent[UserControl].((vm:AgentsPageViewModel)DataContext).CachingOptions}"
SelectedItem="{Binding PromptCaching}" />
</StackPanel>
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Rolle / Beschreibung" />
<TextBox Text="{Binding Description}" AcceptsReturn="True" Height="54"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="System-Prompt (zusätzliche Anweisungen)" />
<TextBox Text="{Binding SystemPrompt}" AcceptsReturn="True" Height="70"
TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</TabItem>
<!-- TAB 2: System Prompt & Seele -->
<TabItem Header="System-Prompt &amp; Soul">
<ScrollViewer Padding="8">
<Border Classes="hr" />
<StackPanel Spacing="12">
<StackPanel Spacing="4">
<TextBlock Text="System-Prompt (Zusätzliche Anweisungen):" FontWeight="Bold" />
<TextBox Text="{Binding SystemPrompt}" AcceptsReturn="True" Height="120" TextWrapping="Wrap" />
</StackPanel>
<TextBlock Classes="kicker" Text="GRENZEN" />
<TextBlock Classes="h2" Text="Schutzgrenzen (LoopGuard &amp; Budget)" />
<StackPanel Spacing="4">
<TextBlock Text="Identität (Identity):" FontWeight="Bold" />
<TextBox Text="{Binding Identity}" AcceptsReturn="True" Height="100" TextWrapping="Wrap" />
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Max. Schritte pro Run" />
<TextBox Text="{Binding MaxSteps}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Timeout (Sekunden)" />
<TextBox Text="{Binding TimeoutSeconds}" />
</StackPanel>
</Grid>
<StackPanel Spacing="4">
<TextBlock Text="Seele &amp; Werte (Soul):" FontWeight="Bold" />
<TextBox Text="{Binding Soul}" AcceptsReturn="True" Height="100" TextWrapping="Wrap" />
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Max. Kontext-Tokens" />
<TextBox Text="{Binding MaxContextTokens}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Max. Tool-Ergebnis (Zeichen)" />
<TextBox Text="{Binding MaxToolResultChars}" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Tagesbudget ($ USD)" />
<TextBox Text="{Binding DailyCostUsd}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Tagesbudget (Tokens)" />
<TextBox Text="{Binding DailyTokens}" />
</StackPanel>
</Grid>
</StackPanel>
</ScrollViewer>
</TabItem>
<!-- TAB 3: Werkzeuge (Tools) -->
<TabItem Header="Werkzeuge (Tools)">
<ScrollViewer Padding="8">
<StackPanel Spacing="8">
<TextBlock Text="Zugelassene Werkzeuge für diesen Agenten:" Classes="heading" />
<ItemsControl ItemsSource="{Binding AvailableTools}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:ToolItemViewModel">
<Grid ColumnDefinitions="200,Auto" Margin="0,4">
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsEnabled}" Grid.Column="0" VerticalAlignment="Center" />
<Button Content="⚙ Konfigurieren" Command="{Binding $parent[UserControl].((vm:AgentsPageViewModel)DataContext).ConfigureToolCommand}" CommandParameter="{Binding}" IsEnabled="{Binding IsEnabled}" Grid.Column="1" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</TabItem>
</StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</ScrollViewer>
</Grid>
</TabControl>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
<GridSplitter x:Name="VerticalSplitter" Grid.Row="1" Classes="horizontal"
ResizeDirection="Rows" Margin="0,8" />
<!-- ══════════════════ Unten: Werkzeuge ══════════════════ -->
<Grid x:Name="BottomGrid" Grid.Row="2" ColumnDefinitions="0.44*,Auto,0.56*">
<DockPanel Grid.Column="0" Margin="0,0,14,0">
<TextBlock DockPanel.Dock="Top" Classes="kicker" Margin="0,4,0,8"
Text="{Binding SelectedAgent.DisplayName,
StringFormat='ZUGELASSENE WERKZEUGE FÜR {0}'}" />
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="36,1.4*,2.4*">
<TextBlock Grid.Column="1" Classes="th" Text="WERKZEUG" />
<TextBlock Grid.Column="2" Classes="th" Text="BESCHREIBUNG" />
</Grid>
</Border>
<ListBox Classes="table" ItemsSource="{Binding SelectedAgent.AvailableTools}"
SelectedItem="{Binding SelectedTool}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:ToolItemViewModel">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="36,1.4*,2.4*">
<CheckBox Grid.Column="0" IsChecked="{Binding IsEnabled}"
VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Classes="td" FontWeight="SemiBold"
Text="{Binding Name}" />
<TextBlock Grid.Column="2" Classes="td dim" Text="{Binding Description}" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<GridSplitter x:Name="BottomSplitter" Grid.Column="1" Classes="vertical"
ResizeDirection="Columns" />
<DockPanel Grid.Column="2" Margin="4,0,0,0">
<StackPanel DockPanel.Dock="Top" Spacing="2" Margin="0,4,0,10">
<TextBlock Classes="kicker" Text="WERKZEUG-EINSTELLUNGEN" />
<DockPanel>
<TextBlock Classes="h2" VerticalAlignment="Center"
Text="{Binding SelectedTool.Name}" />
<Button DockPanel.Dock="Right" HorizontalAlignment="Right" Classes="toolbar primary"
Command="{Binding SaveToolSettingsCommand}"
IsEnabled="{Binding ToolSettings, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconSave}" Width="14" Height="14" />
<TextBlock Text="Übernehmen" />
</StackPanel>
</Button>
</DockPanel>
</StackPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ContentControl Content="{Binding ToolSettings}" />
</ScrollViewer>
</DockPanel>
</Grid>
</Grid>
</DockPanel>
</UserControl>
@@ -1,46 +1,101 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class AgentsPageView : UserControl
{
private AgentsPageViewModel? _viewModel;
public AgentsPageView()
{
InitializeComponent();
// Ein Zug am Teiler ist erst zu Ende, wenn die Maus losgelassen wird. Waehrend
// des Ziehens zu speichern hiesse, fuer eine Bewegung ein paar Dutzend Mal die
// Einstellungsdatei zu schreiben.
TopSplitter.DragCompleted += (_, _) => SaveSplits();
BottomSplitter.DragCompleted += (_, _) => SaveSplits();
VerticalSplitter.DragCompleted += (_, _) => SaveSplits();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is AgentsPageViewModel vm)
{
vm.RequestAddAgentDialog += OnRequestAddAgentDialog;
vm.RequestToolSettingsDialog += OnRequestToolSettingsDialog;
}
if (DataContext is not AgentsPageViewModel vm) return;
_viewModel = vm;
vm.RequestAddAgentDialog += OnRequestAddAgentDialog;
vm.RequestTextEditorDialog += OnRequestTextEditorDialog;
ApplySavedSplits(vm);
}
/// <summary>
/// Setzt die gespeicherten Teilerstellungen.
///
/// <para>Von Hand und nicht per Bindung: <see cref="ColumnDefinition"/> und
/// <see cref="RowDefinition"/> liegen nicht im Sichtbarkeitsbaum und erben deshalb
/// keinen DataContext. Eine Bindung auf ihre Breite bliebe ohne Wirkung — und zwar
/// ohne Fehlermeldung.</para>
/// </summary>
private void ApplySavedSplits(AgentsPageViewModel vm)
{
SetRatio(TopGrid.ColumnDefinitions[0], TopGrid.ColumnDefinitions[2], vm.SplitTopList);
SetRatio(BottomGrid.ColumnDefinitions[0], BottomGrid.ColumnDefinitions[2], vm.SplitBottomList);
OuterGrid.RowDefinitions[0].Height = new GridLength(vm.SplitVertical, GridUnitType.Star);
OuterGrid.RowDefinitions[2].Height = new GridLength(1 - vm.SplitVertical, GridUnitType.Star);
}
private static void SetRatio(ColumnDefinition first, ColumnDefinition second, double ratio)
{
first.Width = new GridLength(ratio, GridUnitType.Star);
second.Width = new GridLength(1 - ratio, GridUnitType.Star);
}
private void SaveSplits()
{
if (_viewModel is null) return;
_viewModel.SaveSplits(
RowRatio(OuterGrid.RowDefinitions[0], OuterGrid.RowDefinitions[2]),
ColumnRatio(TopGrid.ColumnDefinitions[0], TopGrid.ColumnDefinitions[2]),
ColumnRatio(BottomGrid.ColumnDefinitions[0], BottomGrid.ColumnDefinitions[2]));
}
/// <summary>
/// Das Verhaeltnis wird aus den tatsaechlichen Pixelbreiten gebildet, nicht aus den
/// Sternwerten: Der GridSplitter setzt beim Ziehen mitunter absolute Breiten, und
/// dann stuenden in den Sternwerten noch die Zahlen von vor dem Zug.
/// </summary>
private static double ColumnRatio(ColumnDefinition first, ColumnDefinition second)
=> Ratio(first.ActualWidth, second.ActualWidth);
private static double RowRatio(RowDefinition first, RowDefinition second)
=> Ratio(first.ActualHeight, second.ActualHeight);
private static double Ratio(double first, double second)
{
var total = first + second;
return total > 0 ? first / total : 0.5;
}
private async Task<AddAgentResultData?> OnRequestAddAgentDialog(AddAgentViewModel vm)
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is Window window)
{
var dialog = new AddAgentWindow { DataContext = vm };
await dialog.ShowDialog(window);
return vm.Result;
}
return null;
if (TopLevel.GetTopLevel(this) is not Window window) return null;
var dialog = new AddAgentWindow { DataContext = vm };
await dialog.ShowDialog(window);
return vm.Result;
}
private async Task<Dictionary<string, object?>?> OnRequestToolSettingsDialog(ToolSettingsViewModel vm)
private async Task<string?> OnRequestTextEditorDialog(string title, string text)
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is Window window)
{
var dialog = new ToolSettingsWindow { DataContext = vm };
await dialog.ShowDialog(window);
return vm.Result;
}
return null;
if (TopLevel.GetTopLevel(this) is not Window window) return null;
return await TextEditorWindow.ShowAsync(window, title, text);
}
}
@@ -3,95 +3,147 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
Padding="16"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="740"
x:Class="ClawdDotNet.Desktop.Views.BackupPageView"
x:DataType="vm:BackupPageViewModel">
<DockPanel>
<!-- Statusbar am unteren Rand -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Text="{Binding StatusText}" VerticalAlignment="Center" />
</Border>
<TextBlock DockPanel.Dock="Bottom" Classes="caption" Margin="0,10,0,0"
Text="{Binding StatusText}" TextWrapping="Wrap" />
<ScrollViewer>
<StackPanel Spacing="16">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="24" MaxWidth="820" HorizontalAlignment="Left">
<TextBlock Text="Sicherung &amp; Wiederherstellung" Classes="heading" FontSize="20" />
<!-- ══════════════════ Manuell ══════════════════ -->
<StackPanel Spacing="12">
<TextBlock Classes="kicker" Text="MANUELL" />
<TextBlock Classes="h2" Text="Sicherung erstellen" />
<!-- Card: Neue Sicherung erstellen -->
<Border Classes="card">
<StackPanel Spacing="12">
<TextBlock Text="Manuelle Sicherung erstellen" Classes="heading" />
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto">
<TextBox Text="{Binding TargetFolder}" Grid.Column="0" PlaceholderText="Zielordner für Sicherungen..." />
<Button Content="Ordner wählen" Click="OnBrowseTargetFolder" Grid.Column="1" Margin="8,0,0,0" />
</Grid>
<!-- Zugangsdaten Optionen -->
<TextBlock Text="Zugangsdaten (API-Schlüssel, Passwörter):" FontWeight="Bold" Margin="0,4,0,0" />
<RadioButton Content="Zugangsdaten NICHT mit sichern (ohne Passphrase)" IsChecked="{Binding SecretsExclude}" />
<RadioButton Content="Mit Passphrase verschlüsseln" IsChecked="{Binding SecretsPassphrase}" />
<StackPanel Spacing="8" IsVisible="{Binding SecretsPassphrase}" Margin="20,0,0,0">
<TextBox Text="{Binding Passphrase}" PasswordChar="*" PlaceholderText="Passphrase eingeben" Width="300" HorizontalAlignment="Left" />
<TextBox Text="{Binding PassphraseRepeat}" PasswordChar="*" PlaceholderText="Passphrase wiederholen" Width="300" HorizontalAlignment="Left" />
<Grid ColumnDefinitions="*,Auto" VerticalAlignment="Bottom">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Zielordner" />
<TextBox Text="{Binding TargetFolder}"
PlaceholderText="Zielordner für Sicherungen…" />
</StackPanel>
<Button Grid.Column="1" Content="Durchsuchen…" Click="OnBrowseTargetFolder"
Margin="8,0,0,0" VerticalAlignment="Bottom" />
</Grid>
<!-- Inhalt Optionen -->
<CheckBox Content="Protokolle (Logs) mit einschließen" IsChecked="{Binding IncludeLogs}" />
<CheckBox Content="Chatverläufe mit einschließen" IsChecked="{Binding IncludeChatHistory}" />
<Button Content="▶ Sicherung jetzt erstellen" Command="{Binding CreateBackupCommand}"
IsEnabled="{Binding !IsBusy}" Classes="accent" HorizontalAlignment="Left" Margin="0,8,0,0" />
</StackPanel>
</Border>
<!-- Card: Automatische Sicherung -->
<Border Classes="card">
<StackPanel Spacing="12">
<TextBlock Text="Automatische Sicherung" Classes="heading" />
<CheckBox Content="Automatische tägliche Sicherung aktivieren" IsChecked="{Binding AutoBackupEnabled}" />
<StackPanel Orientation="Horizontal" Spacing="16" IsEnabled="{Binding AutoBackupEnabled}">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="Uhrzeit:" VerticalAlignment="Center" />
<TextBox Text="{Binding AutoBackupTime}" Width="80" PlaceholderText="03:00" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="Max. Anzahl behalten:" VerticalAlignment="Center" />
<NumericUpDown Value="{Binding KeepCount}" Minimum="1" Maximum="999" Width="120" />
</StackPanel>
<StackPanel Spacing="6">
<TextBlock Classes="label" Text="Zugangsdaten" />
<StackPanel Orientation="Horizontal" Spacing="18">
<RadioButton GroupName="secrets" Content="nicht mitsichern"
IsChecked="{Binding SecretsExclude}" />
<RadioButton GroupName="secrets" Content="mit Passphrase schützen"
IsChecked="{Binding SecretsPassphrase}" />
</StackPanel>
</StackPanel>
</Border>
<!-- Vorhandene Sicherungen List -->
<Border Classes="card">
<DockPanel Height="260">
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<TextBlock Text="Vorhandene Sicherungen" Classes="heading" VerticalAlignment="Center" />
<Button Content="Aktualisieren" Command="{Binding RefreshBackupListCommand}" Margin="12,0,0,0" />
<Button Content="Ordner öffnen" Command="{Binding ShowSelectedInFolderCommand}" IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="Löschen" Command="{Binding DeleteSelectedCommand}" IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="Wiederherstellen..." Command="{Binding RestoreSelectedCommand}" IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" Classes="accent" />
<StackPanel Spacing="8" Margin="24,0,0,0" IsVisible="{Binding SecretsPassphrase}">
<TextBox Text="{Binding Passphrase}" PasswordChar="•" Width="320"
HorizontalAlignment="Left" PlaceholderText="Passphrase eingeben" />
<TextBox Text="{Binding PassphraseRepeat}" PasswordChar="•" Width="320"
HorizontalAlignment="Left" PlaceholderText="Passphrase wiederholen" />
</StackPanel>
<StackPanel Spacing="6">
<TextBlock Classes="label" Text="Umfang" />
<StackPanel Orientation="Horizontal" Spacing="18">
<CheckBox Content="Chatverläufe" IsChecked="{Binding IncludeChatHistory}" />
<CheckBox Content="Protokolle" IsChecked="{Binding IncludeLogs}" />
</StackPanel>
</StackPanel>
<Button Classes="primary" HorizontalAlignment="Left" Margin="0,4,0,0"
Command="{Binding CreateBackupCommand}" IsEnabled="{Binding !IsBusy}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlay}" Width="12" Height="12" IsFilled="True" />
<TextBlock Text="Sicherung jetzt erstellen" />
</StackPanel>
</Button>
</StackPanel>
<Border Classes="hr" />
<!-- ══════════════════ Automatik ══════════════════ -->
<StackPanel Spacing="10">
<TextBlock Classes="kicker" Text="AUTOMATIK" />
<TextBlock Classes="h2" Text="Automatische Sicherung" />
<CheckBox Content="täglich sichern um" IsChecked="{Binding AutoBackupEnabled}" />
<StackPanel Orientation="Horizontal" Spacing="24" Margin="24,0,0,0"
IsEnabled="{Binding AutoBackupEnabled}">
<StackPanel Width="100">
<TextBlock Classes="label" Text="Uhrzeit" />
<TextBox Text="{Binding AutoBackupTime}" PlaceholderText="03:00" />
</StackPanel>
<DataGrid ItemsSource="{Binding Backups}" SelectedItem="{Binding SelectedBackup}" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Dateiname" Binding="{Binding FileName}" Width="220" />
<DataGridTextColumn Header="Erstellt am" Binding="{Binding LastWriteTime, StringFormat='{}{0:g}'}" Width="150" />
<DataGridTextColumn Header="Instanz" Binding="{Binding InstanceName}" Width="130" />
<DataGridTextColumn Header="Größe" Binding="{Binding SizeFormatted}" Width="100" />
<DataGridTextColumn Header="Zugangsdaten" Binding="{Binding SecretsInfo}" Width="*" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Width="160">
<TextBlock Classes="label" Text="Aufbewahren" />
<TextBox Text="{Binding KeepCount}" />
</StackPanel>
</StackPanel>
<TextBlock Classes="caption" Margin="24,0,0,0" TextWrapping="Wrap"
Text="Die Automatik sichert ohne Zugangsdaten — eine gespeicherte Passphrase wäre wirkungslos." />
</StackPanel>
<Border Classes="hr" />
<!-- ══════════════════ Bestand ══════════════════ -->
<StackPanel Spacing="10">
<TextBlock Classes="kicker" Text="BESTAND" />
<DockPanel>
<TextBlock Classes="h2" Text="Vorhandene Sicherungen" VerticalAlignment="Center" />
<WrapPanel DockPanel.Dock="Right" HorizontalAlignment="Right" Classes="toolbar"
Margin="0">
<Button Classes="toolbar" Content="Aktualisieren"
Command="{Binding RefreshBackupListCommand}" />
<Button Classes="toolbar" Content="Ordner öffnen"
Command="{Binding ShowSelectedInFolderCommand}"
IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Classes="toolbar" Content="Löschen"
Command="{Binding DeleteSelectedCommand}"
IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Classes="toolbar primary" Content="Wiederherstellen…"
Command="{Binding RestoreSelectedCommand}"
IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" />
</WrapPanel>
</DockPanel>
</Border>
<Border Classes="thead">
<Grid ColumnDefinitions="2.6*,1.5*,1.3*,0.9*,1.3*">
<TextBlock Grid.Column="0" Classes="th" Text="DATEINAME" />
<TextBlock Grid.Column="1" Classes="th" Text="ERSTELLT AM" />
<TextBlock Grid.Column="2" Classes="th" Text="INSTANZ" />
<TextBlock Grid.Column="3" Classes="th" Text="GRÖSSE" />
<TextBlock Grid.Column="4" Classes="th" Text="ZUGANGSDATEN" />
</Grid>
</Border>
<ListBox Classes="table" MaxHeight="300" Margin="0,-10,0,0"
ItemsSource="{Binding Backups}" SelectedItem="{Binding SelectedBackup}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:BackupItemViewModel">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="2.6*,1.5*,1.3*,0.9*,1.3*">
<TextBlock Grid.Column="0" Classes="td" Text="{Binding FileName}" />
<TextBlock Grid.Column="1" Classes="td"
Text="{Binding LastWriteTime, StringFormat='{}{0:dd.MM.yyyy HH:mm}'}" />
<TextBlock Grid.Column="2" Classes="td" Text="{Binding InstanceName}" />
<TextBlock Grid.Column="3" Classes="td" Text="{Binding SizeFormatted}" />
<TextBlock Grid.Column="4" Classes="td dim" Text="{Binding SecretsInfo}" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
</ScrollViewer>
@@ -3,23 +3,19 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
Padding="16"
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="700"
x:Class="ClawdDotNet.Desktop.Views.ChatPageView"
x:DataType="vm:ChatPageViewModel">
<DockPanel>
<DockPanel MaxWidth="960" HorizontalAlignment="Left">
<!-- Statusleiste am unteren Rand -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Text="{Binding StatusText}" VerticalAlignment="Center" />
</Border>
<!-- Top Toolbar -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<TextBlock Text="Agent:" VerticalAlignment="Center" FontWeight="Bold" />
<ComboBox ItemsSource="{Binding AvailableAgents}" SelectedItem="{Binding SelectedAgent}" MinWidth="200">
<!-- Agentenauswahl und Verlaufsbefehle -->
<StackPanel DockPanel.Dock="Top" Classes="toolbar" Margin="0,0,0,16">
<TextBlock Text="Agent:" FontWeight="SemiBold" VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding AvailableAgents}" SelectedItem="{Binding SelectedAgent}"
Width="220">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
@@ -27,43 +23,67 @@
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Verlauf leeren" Command="{Binding ClearHistoryCommand}" Margin="12,0,0,0" />
<Button Content="Abbrechen" Command="{Binding AbortCommand}" IsEnabled="{Binding IsSending}" />
<Border Classes="sep" />
<Button Classes="toolbar" Command="{Binding ClearHistoryCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconTrash}" Width="14" Height="14" />
<TextBlock Text="Verlauf leeren" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding AbortCommand}" IsEnabled="{Binding IsSending}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconStop}" Width="12" Height="12" IsFilled="True" />
<TextBlock Text="Abbrechen" />
</StackPanel>
</Button>
</StackPanel>
<!-- Eingabezeile unten -->
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="*,Auto" Margin="0,8,0,0">
<TextBox Text="{Binding InputPrompt}" PlaceholderText="Nachricht eingeben... (Strg+Eingabe zum Senden)"
AcceptsReturn="True" Height="60" TextWrapping="Wrap" Grid.Column="0" KeyDown="OnInputKeyDown" />
<Button Content="Senden" Command="{Binding SendMessageCommand}" IsEnabled="{Binding !IsSending}"
Classes="accent" VerticalAlignment="Stretch" Margin="8,0,0,0" Grid.Column="1" Width="90" />
<!-- Statuszeile des Bereichs -->
<TextBlock DockPanel.Dock="Bottom" Classes="caption" Margin="0,8,0,0"
Text="{Binding StatusText}" />
<!-- Eingabe -->
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="*,Auto" Margin="0,12,0,0">
<TextBox Grid.Column="0" Text="{Binding InputPrompt}"
PlaceholderText="Nachricht eingeben… (Strg+Eingabe zum Senden)"
AcceptsReturn="True" Height="60" TextWrapping="Wrap"
KeyDown="OnInputKeyDown" />
<Button Grid.Column="1" Classes="primary" Content="Senden" Width="100"
Command="{Binding SendMessageCommand}" IsEnabled="{Binding !IsSending}"
VerticalAlignment="Stretch" Margin="8,0,0,0" />
</Grid>
<!-- Nachrichtenverlauf (Mitte) -->
<ScrollViewer x:Name="ChatScroller">
<!-- Verlauf -->
<ScrollViewer x:Name="ChatScroller" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding ChatEntries}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="10" />
<StackPanel Spacing="12" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:ChatEntryItemViewModel">
<!-- Distinct styling: User right-aligned bubble vs Assistant left-aligned card -->
<DockPanel HorizontalAlignment="{Binding IsUser, Converter={x:Static conv:ChatAlignmentConverter.Instance}}">
<Border Classes="card" CornerRadius="8" MaxWidth="680"
Background="{Binding IsUser, Converter={x:Static conv:ChatBubbleBrushConverter.Instance}, TargetNullValue={x:Null}}">
<StackPanel Spacing="4">
<DockPanel>
<TextBlock Text="{Binding Sender}" FontWeight="Bold" FontSize="12" />
<TextBlock Text="{Binding Timestamp, StringFormat='{}{0:HH:mm}'}" Classes="caption" HorizontalAlignment="Right" Margin="8,0,0,0" />
</DockPanel>
<!-- Eigene Nachrichten rechts und in Akzent hinterlegt, Antworten links
als schlichter Kasten — wie im Entwurf. -->
<Border Classes="card" MaxWidth="660"
Classes.accent="{Binding IsUser}"
HorizontalAlignment="{Binding IsUser,
Converter={x:Static conv:ChatAlignmentConverter.Instance}}">
<StackPanel Spacing="4">
<DockPanel>
<TextBlock Text="{Binding Sender}" FontWeight="SemiBold" FontSize="12" />
<TextBlock Classes="caption" DockPanel.Dock="Right" HorizontalAlignment="Right"
Margin="8,0,0,0"
Text="{Binding Timestamp, StringFormat='{}{0:HH:mm}'}" />
</DockPanel>
<SelectableTextBlock Text="{Binding Text}" TextWrapping="Wrap" FontSize="13" />
</StackPanel>
</Border>
</DockPanel>
<SelectableTextBlock Text="{Binding Text}" TextWrapping="Wrap" FontSize="13.5" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
@@ -3,67 +3,111 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="600"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="700"
x:Class="ClawdDotNet.Desktop.Views.InfoPageView"
x:DataType="vm:InfoPageViewModel">
<ScrollViewer Padding="16">
<StackPanel Spacing="16">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<!-- Etwas Luft nach aussen: Die Eckmarken des BlueprintFrame ragen ueber den
Rahmen hinaus und wuerden sonst am Rand des Bereichs abgeschnitten. -->
<StackPanel Spacing="26" Margin="8" MaxWidth="820" HorizontalAlignment="Left">
<!-- Titel -->
<TextBlock Text="Software- &amp; Instanz-Informationen" Classes="heading" FontSize="20" />
<!-- Die einzige Karte des Entwurfs mit Eckmarken -->
<ctrl:BlueprintFrame MaxWidth="420" HorizontalAlignment="Left">
<StackPanel Spacing="6">
<TextBlock Classes="h2" Text="ClawdDotNet" />
<TextBlock Classes="muted" FontSize="13">
<Run Text="Version" /><Run Text=" " /><Run Text="{Binding AppVersion}" /><Run Text=" · Avalonia UI · net10.0" />
</TextBlock>
</StackPanel>
</ctrl:BlueprintFrame>
<!-- Anwendungs-Details Card -->
<Border Classes="card">
<StackPanel Spacing="8">
<TextBlock Text="Anwendung" Classes="heading" />
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="140,*">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Version:" FontWeight="Bold" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding AppVersion}" />
<!-- Anwendung -->
<StackPanel Spacing="10">
<TextBlock Classes="kicker" Text="ANWENDUNG" />
<TextBlock Classes="h2" Text="Build" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Build-Summary:" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding BuildSummary}" Margin="0,4,0,0" TextWrapping="Wrap" />
<Grid ColumnDefinitions="170,*" RowDefinitions="Auto,Auto" >
<TextBlock Grid.Row="0" Grid.Column="0" Classes="label" Text="Version" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding AppVersion}" />
<TextBlock Grid.Row="1" Grid.Column="0" Classes="label" Text="Build-Zusammenfassung"
Margin="0,6,0,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding BuildSummary}"
TextWrapping="Wrap" Margin="0,6,0,0" />
</Grid>
</StackPanel>
<Border Classes="hr" />
<!-- Instanz -->
<StackPanel Spacing="10">
<TextBlock Classes="kicker" Text="INSTANZ" />
<DockPanel>
<TextBlock Classes="h2" Text="Aktuelle Instanz" VerticalAlignment="Center" />
<Button DockPanel.Dock="Right" HorizontalAlignment="Right" Classes="toolbar"
Command="{Binding OpenInstanceFolderCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconFolder}" Width="14" Height="14" />
<TextBlock Text="Instanz-Ordner öffnen" />
</StackPanel>
</Button>
</DockPanel>
<Grid ColumnDefinitions="170,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Classes="label" Text="Name" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding InstanceName}" />
<TextBlock Grid.Row="1" Grid.Column="0" Classes="label" Text="ID" Margin="0,6,0,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Classes="mono" Text="{Binding InstanceId}"
Margin="0,6,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Classes="label" Text="Pfad" Margin="0,6,0,0" />
<SelectableTextBlock Grid.Row="2" Grid.Column="1" Text="{Binding InstancePath}"
TextWrapping="Wrap" Margin="0,6,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Classes="label" Text="Log-Ordner" Margin="0,6,0,0" />
<SelectableTextBlock Grid.Row="3" Grid.Column="1" Text="{Binding LogDirectory}"
TextWrapping="Wrap" Margin="0,6,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Classes="label" Text="OpenRouter" Margin="0,6,0,0" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding OpenRouterStatus}" Margin="0,6,0,0" />
</Grid>
</StackPanel>
<Border Classes="hr" />
<!-- Baugruppen -->
<StackPanel Spacing="10">
<TextBlock Classes="kicker" Text="MODULE" />
<TextBlock Classes="h2" Text="Baugruppen" />
<Border Classes="thead">
<Grid ColumnDefinitions="1.6*,0.6*,1.4*,3*">
<TextBlock Grid.Column="0" Classes="th" Text="MODUL" />
<TextBlock Grid.Column="1" Classes="th" Text="BUILD" />
<TextBlock Grid.Column="2" Classes="th" Text="BUILD-DATUM" />
<TextBlock Grid.Column="3" Classes="th" Text="ÄNDERUNGEN" />
</Grid>
</StackPanel>
</Border>
</Border>
<!-- Instanz-Details Card -->
<Border Classes="card">
<StackPanel Spacing="8">
<TextBlock Text="Aktuelle Instanz" Classes="heading" />
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="140,*">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name:" FontWeight="Bold" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding InstanceName}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="ID:" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding InstanceId}" Margin="0,4,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Pfad:" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding InstancePath}" Margin="0,4,0,0" TextWrapping="Wrap" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Log-Ordner:" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding LogDirectory}" Margin="0,4,0,0" TextWrapping="Wrap" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="OpenRouter Status:" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding OpenRouterStatus}" Margin="0,4,0,0" />
</Grid>
</StackPanel>
</Border>
<!-- Assembly Build Info Card -->
<Border Classes="card">
<StackPanel Spacing="8">
<TextBlock Text="Module &amp; Baugruppen" Classes="heading" />
<DataGrid ItemsSource="{Binding Assemblies}" AutoGenerateColumns="False" IsReadOnly="True" GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="Modul" Binding="{Binding ShortName}" Width="160" />
<DataGridTextColumn Header="Build #" Binding="{Binding BuildNumber}" Width="80" />
<DataGridTextColumn Header="Build-Datum" Binding="{Binding BuildDate}" Width="160" />
<DataGridTextColumn Header="Änderungen" Binding="{Binding Changes}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Border>
<ItemsControl ItemsSource="{Binding Assemblies}" Margin="0,-10,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:AssemblyInfoItem">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="1.6*,0.6*,1.4*,3*">
<TextBlock Grid.Column="0" Classes="td" FontWeight="SemiBold" Text="{Binding ShortName}" />
<TextBlock Grid.Column="1" Classes="td" Text="{Binding BuildNumber}" />
<TextBlock Grid.Column="2" Classes="td dim" Text="{Binding BuildDate}" />
<TextBlock Grid.Column="3" Classes="td dim" Text="{Binding Changes}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</ScrollViewer>
@@ -3,71 +3,121 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:models="using:ClawdDotNet.App.Models"
mc:Ignorable="d" d:DesignWidth="720" d:DesignHeight="460"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="560"
x:Class="ClawdDotNet.Desktop.Views.InstancePickerWindow"
x:DataType="vm:InstancePickerViewModel"
Icon="/Assets/app.ico"
Title="ClawdDotNet – Instanz wählen"
Width="720" Height="460"
Title="ClawdDotNet – Instanz"
Width="640" Height="560"
MinWidth="520" MinHeight="420"
WindowStartupLocation="CenterScreen">
<DockPanel Margin="16">
<!-- Rahmen mit Eckmarken wie im Entwurf; 12 Punkte Abstand, weil die Marken nach
aussen ragen. -->
<ctrl:BlueprintFrame Margin="12" Padding="16">
<DockPanel>
<TextBlock DockPanel.Dock="Top" Classes="heading" Text="Instanz wählen" />
<TextBlock DockPanel.Dock="Top" Classes="caption" Margin="0,0,0,12"
TextWrapping="Wrap"
Text="{Binding InstancesDirectory, StringFormat='Verzeichnis: {0}'}" />
<!-- Fehlerzeile: nimmt keinen Platz weg, solange nichts anliegt -->
<TextBlock DockPanel.Dock="Bottom"
Margin="0,8,0,0"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
TextWrapping="Wrap"
Text="{Binding ErrorText}"
IsVisible="{Binding ErrorText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<!-- Aktionen -->
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="*,Auto,Auto" Margin="0,12,0,0">
<StackPanel Orientation="Horizontal" Spacing="6" Grid.Column="0">
<TextBox Width="200"
Watermark="Name der neuen Instanz"
Text="{Binding NewInstanceName}" />
<Button Content="Anlegen" Command="{Binding CreateCommand}" />
<StackPanel DockPanel.Dock="Top" Spacing="6" Margin="0,0,0,14">
<TextBlock Classes="h1" Text="{Binding Title}" />
<TextBlock Classes="muted" FontSize="13" Text="{Binding Hint}" TextWrapping="Wrap" />
<TextBlock Classes="caption" TextWrapping="Wrap"
Text="{Binding InstancesDirectory, StringFormat='Verzeichnis: {0}'}" />
</StackPanel>
<Button Grid.Column="1" Content="Abbrechen" Margin="0,0,6,0"
Command="{Binding CancelCommand}" />
<!-- Fehlerzeile: nimmt keinen Platz weg, solange nichts anliegt -->
<TextBlock DockPanel.Dock="Bottom" Margin="0,10,0,0" TextWrapping="Wrap"
Foreground="{DynamicResource DangerBrush}"
Text="{Binding ErrorText}"
IsVisible="{Binding ErrorText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<Button Grid.Column="2" Content="Starten"
Classes="accent"
IsDefault="True"
Command="{Binding StartCommand}" />
</Grid>
<!-- Neue Instanz anlegen -->
<StackPanel DockPanel.Dock="Bottom" Spacing="10" Margin="0,14,0,0">
<Border Classes="hr" />
<Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0" Text="{Binding NewInstanceName}"
PlaceholderText="Name der neuen Instanz" />
<Button Grid.Column="1" Classes="primary" Margin="8,0,0,0"
Command="{Binding CreateCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlus}" Width="14" Height="14" />
<TextBlock Text="Anlegen" />
</StackPanel>
</Button>
</Grid>
</StackPanel>
<!-- Liste -->
<DataGrid ItemsSource="{Binding Instances}"
SelectedItem="{Binding Selected}"
AutoGenerateColumns="False"
IsReadOnly="True"
SelectionMode="Single"
GridLinesVisibility="Horizontal">
<!-- Abschluss -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="8"
HorizontalAlignment="Right" Margin="0,14,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" IsCancel="True" />
<Button Classes="primary" Content="{Binding ConfirmLabel}" IsDefault="True"
Command="{Binding StartCommand}" />
</StackPanel>
<DataGrid.Columns>
<DataGridTextColumn Header="Instanzname" Width="2*"
Binding="{Binding InstanceName, x:DataType=models:InstanceInfo}" />
<DataGridTextColumn Header="Ordner" Width="2*"
Binding="{Binding FolderName, x:DataType=models:InstanceInfo}" />
<DataGridTextColumn Header="Agenten" Width="Auto"
Binding="{Binding AgentCount, x:DataType=models:InstanceInfo}" />
<DataGridTextColumn Header="API-Key" Width="Auto"
Binding="{Binding ApiKeyStatus, x:DataType=models:InstanceInfo}" />
</DataGrid.Columns>
<!-- Liste -->
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="InstanceList" ItemsSource="{Binding Instances}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="6" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</DataGrid>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:InstanceChoiceViewModel">
<Border Classes="card" Padding="8,8">
<DockPanel>
</DockPanel>
<!-- Monogramm mit Statuspunkt -->
<Panel DockPanel.Dock="Left" Width="30" Height="30" Margin="0,0,10,0"
VerticalAlignment="Center">
<Border Width="26" Height="26" Background="{DynamicResource Accent600Brush}"
HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBlock Classes="h2" FontSize="10" Text="{Binding Monogram}"
Foreground="{DynamicResource Neutral100Brush}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<Ellipse Width="10" Height="10" HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Stroke="{DynamicResource SurfaceBrush}" StrokeThickness="2"
Fill="{DynamicResource SuccessBrush}"
IsVisible="{Binding IsReady}"
ToolTip.Tip="API-Key hinterlegt" />
<Ellipse Width="10" Height="10" HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Stroke="{DynamicResource SurfaceBrush}" StrokeThickness="2"
Fill="{DynamicResource Neutral500Brush}"
IsVisible="{Binding !IsReady}"
ToolTip.Tip="Kein API-Key — Agenten bleiben deaktiviert" />
</Panel>
<Button DockPanel.Dock="Right" Classes="toolbar" VerticalAlignment="Center"
Content="{Binding $parent[ItemsControl].((vm:InstancePickerViewModel)DataContext).ConfirmLabel}"
Command="{Binding $parent[ItemsControl].((vm:InstancePickerViewModel)DataContext).ActivateCommand}"
CommandParameter="{Binding}" />
<Border DockPanel.Dock="Right" Classes="tag ok" Margin="0,0,8,0"
IsVisible="{Binding IsCurrent}">
<TextBlock Text="Aktiv" />
</Border>
<StackPanel VerticalAlignment="Center">
<TextBlock Classes="h2" FontSize="13.5" Text="{Binding InstanceName}"
TextTrimming="CharacterEllipsis" />
<TextBlock Classes="caption" Text="{Binding Summary}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
</DockPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</ctrl:BlueprintFrame>
</Window>
@@ -10,15 +10,21 @@ public partial class InstancePickerWindow : Window
{
InitializeComponent();
// Doppelklick auf eine Zeile startet — wie in der WinForms-Fassung. Das laesst
// sich nicht sinnvoll als Befehl binden, weil DataGrid kein DoubleTapped je
// Zeile anbietet; deshalb hier und nicht im Ansichtsmodell.
AddHandler(DoubleTappedEvent, OnDoubleTapped, handledEventsToo: false);
// Doppelklick auf eine Zeile bestaetigt — wie in der WinForms-Fassung.
//
// Der Behandler haengt an der Liste und nicht mehr am Fenster: Am Fenster loeste
// auch ein Doppelklick ins Namensfeld die Auswahl aus, was seit dem Umbau
// deutlich leichter passiert, weil das Feld gross und mittig steht.
InstanceList.AddHandler(DoubleTappedEvent, OnRowDoubleTapped, handledEventsToo: false);
}
private void OnDoubleTapped(object? sender, TappedEventArgs e)
private void OnRowDoubleTapped(object? sender, TappedEventArgs e)
{
if (DataContext is InstancePickerViewModel vm && vm.StartCommand.CanExecute(null))
vm.StartCommand.Execute(null);
if (DataContext is not InstancePickerViewModel vm) return;
// Getroffen wird irgendein Element in der Zeile; der Datenkontext ist derselbe.
if ((e.Source as Control)?.DataContext is not InstanceChoiceViewModel choice) return;
vm.ActivateCommand.Execute(choice);
}
}
@@ -23,7 +23,7 @@
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
</Border>
<TextBox Watermark="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
<TextBox PlaceholderText="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
Text="{Binding Key}"
FontFamily="{DynamicResource ContentControlThemeFontFamily}" />
+64 -35
View File
@@ -4,33 +4,50 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:svc="using:ClawdDotNet.App.Services"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="600"
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="700"
x:Class="ClawdDotNet.Desktop.Views.LogPageView"
x:DataType="vm:LogPageViewModel">
<DockPanel>
<!-- Werkzeugleiste -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<StackPanel DockPanel.Dock="Top" Classes="toolbar">
<TextBlock Text="Modul:" VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding Modules}"
SelectedItem="{Binding SelectedModule}"
MinWidth="180" />
<TextBlock Text="Modul:" FontSize="12.5" VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding Modules}" SelectedItem="{Binding SelectedModule}"
Width="180" MinHeight="30" Padding="8,3" />
<TextBlock Text="Stufe:" VerticalAlignment="Center" Margin="12,0,0,0" />
<ComboBox ItemsSource="{Binding Levels}"
SelectedItem="{Binding SelectedLevel}"
MinWidth="100" />
<TextBlock Text="Stufe:" FontSize="12.5" VerticalAlignment="Center" Margin="6,0,0,0" />
<ComboBox ItemsSource="{Binding Levels}" SelectedItem="{Binding SelectedLevel}"
Width="120" MinHeight="30" Padding="8,3" />
<Button Content="Aktualisieren" Command="{Binding RefreshModulesCommand}" Margin="12,0,0,0" />
<Button Content="Leeren" Command="{Binding ClearCommand}" />
<Button Content="Ordner öffnen" Command="{Binding OpenFolderCommand}" />
<Border Classes="sep" />
<Button Classes="toolbar" Command="{Binding RefreshModulesCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="Aktualisieren" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding ClearCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconTrash}" Width="14" Height="14" />
<TextBlock Text="Leeren" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding OpenFolderCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconFolder}" Width="14" Height="14" />
<TextBlock Text="Ordner öffnen" />
</StackPanel>
</Button>
<CheckBox Content="Mitlaufen" IsChecked="{Binding FollowTail}"
VerticalAlignment="Center" Margin="12,0,0,0" />
VerticalAlignment="Center" Margin="8,0,0,0" />
</StackPanel>
<!--
@@ -38,29 +55,41 @@
einzelne Zeile herauskopieren, und die Virtualisierung des ItemsControl haelt auch
bei 2000 Zeilen die Darstellung fluessig.
-->
<ScrollViewer Name="Scroller" Padding="8">
<ItemsControl ItemsSource="{Binding Lines}">
<Border Classes="console">
<ScrollViewer Name="Scroller" VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="svc:LogLine">
<SelectableTextBlock Text="{Binding Text}"
FontFamily="Consolas, Menlo, DejaVu Sans Mono, monospace"
FontSize="12"
TextWrapping="NoWrap"
Foreground="{Binding Level,
Converter={x:Static conv:LogLevelBrushConverter.Instance},
TargetNullValue={x:Null}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="svc:LogLine">
<!-- Nur Warnung und Fehler werden hervorgehoben. Alles einzufärben macht
die Ansicht unruhig und lenkt von genau den Zeilen ab, die auffallen
sollen; der Rest behält die Vordergrundfarbe des Themas. -->
<SelectableTextBlock Classes="mono"
Classes.warn="{Binding Level,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=WRN}"
Classes.err="{Binding Level,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=ERR}"
Classes.ftl="{Binding Level,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=FTL}"
Text="{Binding Text}"
LineHeight="21"
TextWrapping="NoWrap" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</ItemsControl>
</ScrollViewer>
</Border>
</DockPanel>
+202 -48
View File
@@ -3,70 +3,224 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="700"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="1440" d:DesignHeight="860"
x:Class="ClawdDotNet.Desktop.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/app.ico"
Title="ClawdDotNet"
Width="1100" Height="700"
MinWidth="800" MinHeight="500">
Width="1440" Height="860"
MinWidth="960" MinHeight="600">
<Design.DataContext>
<vm:MainWindowViewModel />
</Design.DataContext>
<DockPanel>
<Grid ColumnDefinitions="Auto,*">
<!-- Haupt-Menüleiste -->
<Menu DockPanel.Dock="Top">
<MenuItem Header="_Datei">
<MenuItem Header="_Beenden" Command="{Binding ExitCommand}" InputGesture="Alt+F4" />
</MenuItem>
<MenuItem Header="_Bearbeiten">
<MenuItem Header="_Instanz-Ordner öffnen" Command="{Binding OpenInstanceFolderCommand}" />
<MenuItem Header="_Log-Ordner öffnen" Command="{Binding OpenLogFolderCommand}" />
</MenuItem>
<MenuItem Header="_Instanz">
<MenuItem Header="_Instanz-Manager..." Command="{Binding OpenInstanceManagerCommand}" />
</MenuItem>
<MenuItem Header="_Ansicht">
<MenuItem Header="_Chat" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Chat" />
<MenuItem Header="_Logs" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Logs" />
<MenuItem Header="_Agenten" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Agenten" />
<MenuItem Header="_Einstellungen" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Einstellungen" />
<MenuItem Header="_Aufgaben" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Aufgaben" />
<MenuItem Header="_Sicherung" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Sicherung" />
<MenuItem Header="_Info" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Info" />
</MenuItem>
<MenuItem Header="_Hilfe">
<MenuItem Header="_Info" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Info" />
</MenuItem>
</Menu>
<!-- ══════════════════════════ Seitenleiste ══════════════════════════ -->
<Border Grid.Column="0" Classes="sidebar" Width="{Binding SidebarWidth}">
<DockPanel>
<!-- Statusleiste -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Classes="caption" Text="{Binding StatusText}" />
<!-- Marke und Einklapp-Schalter -->
<Border DockPanel.Dock="Top" BorderBrush="{DynamicResource DividerBrush}"
BorderThickness="0,0,0,1" Padding="14">
<DockPanel>
<ctrl:StrokeIcon Data="{StaticResource IconLogo}" Width="20" Height="20"
Foreground="{DynamicResource AccentBrush}"
DockPanel.Dock="Left" VerticalAlignment="Center" />
<Button DockPanel.Dock="Right" Classes="flat"
Command="{Binding ToggleSidebarCommand}"
ToolTip.Tip="Seitenleiste ein-/ausklappen"
Padding="4" VerticalAlignment="Center">
<ctrl:StrokeIcon Width="14" Height="14"
Data="{Binding IsSidebarCollapsed,
Converter={x:Static conv:SidebarChevronConverter.Instance}}" />
</Button>
<TextBlock Classes="h2" Text="ClawdDotNet" Margin="8,0,0,0"
VerticalAlignment="Center"
IsVisible="{Binding IsSidebarExpanded}" />
</DockPanel>
</Border>
<!-- Fußzeile: Version -->
<TextBlock DockPanel.Dock="Bottom" Classes="caption" Margin="16,10"
Text="{Binding VersionText}"
IsVisible="{Binding IsSidebarExpanded}" />
<!-- Instanz-Schalter: der einzige Knopf mit Akzentrahmen, weil er als
einziger die ganze Anwendung umschaltet. -->
<Button DockPanel.Dock="Top" Classes="instance" Margin="12,12,12,0"
Command="{Binding OpenInstanceManagerCommand}"
ToolTip.Tip="Instanz wechseln">
<DockPanel>
<ctrl:StrokeIcon Data="{StaticResource IconLogo}" Width="15" Height="15"
DockPanel.Dock="Left" VerticalAlignment="Center" />
<ctrl:StrokeIcon Data="{StaticResource IconChevronDown}" Width="14" Height="14"
DockPanel.Dock="Right" VerticalAlignment="Center"
IsVisible="{Binding IsSidebarExpanded}" />
<StackPanel Margin="8,0,0,0" IsVisible="{Binding IsSidebarExpanded}">
<TextBlock Text="INSTANZ" FontSize="9" LetterSpacing="1"
Foreground="{DynamicResource AccentBrush}" />
<TextBlock Classes="h2" FontSize="13" Text="{Binding InstanceName}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
</DockPanel>
</Button>
<!-- Navigation -->
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="10,14">
<ItemsControl ItemsSource="{Binding NavGroups}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="18" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:NavGroupViewModel">
<StackPanel Spacing="2">
<TextBlock Classes="navgroup" Text="{Binding Title}"
IsVisible="{Binding IsTitleVisible}" />
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:NavItemViewModel">
<Button Classes="nav"
Classes.selected="{Binding IsSelected}"
Classes.collapsed="{Binding IsCollapsed}"
Command="{Binding SelectCommand}"
ToolTip.Tip="{Binding Title}">
<StackPanel Orientation="Horizontal" Spacing="10">
<ctrl:StrokeIcon Width="17" Height="17"
VerticalAlignment="Center"
Data="{Binding IconKey,
Converter={x:Static conv:IconConverter.Instance}}" />
<TextBlock Text="{Binding Title}" VerticalAlignment="Center"
IsVisible="{Binding !IsCollapsed}" />
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Border>
<!-- Bereiche (Seitenleiste links) -->
<TabControl ItemsSource="{Binding Pages}"
SelectedItem="{Binding SelectedPage}"
TabStripPlacement="Left">
<!-- ══════════════════════════ Hauptbereich ══════════════════════════ -->
<DockPanel Grid.Column="1">
<TabControl.ItemTemplate>
<DataTemplate DataType="vm:PageViewModel">
<TextBlock Text="{Binding Title}" Margin="4,8" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Kopfzeile -->
<Border DockPanel.Dock="Top" Classes="topbar">
<DockPanel>
<TextBlock Classes="h1" Text="{Binding PageTitle}" VerticalAlignment="Center" />
<TabControl.ContentTemplate>
<DataTemplate DataType="vm:PageViewModel">
<ContentControl Content="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Spacing="10"
HorizontalAlignment="Right" VerticalAlignment="Center">
<TextBlock Classes="caption" Text="{Binding ThemeLabel}" VerticalAlignment="Center" />
<Button Classes="icon" Command="{Binding ToggleThemeCommand}"
ToolTip.Tip="Dunkelmodus umschalten">
<ctrl:StrokeIcon Width="16" Height="16"
Data="{Binding ThemeIconKey,
Converter={x:Static conv:IconConverter.Instance}}" />
</Button>
</StackPanel>
</DockPanel>
</Border>
</TabControl>
<!-- Benachrichtigungsleiste: nimmt keinen Platz, solange nichts anliegt -->
<ItemsControl DockPanel.Dock="Top" ItemsSource="{Binding Notifications}"
IsVisible="{Binding HasNotifications}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:NotificationViewModel">
<Panel>
<!-- Zwei Grundflächen statt eines umgerechneten Pinsels: So bleiben die
Farben an DynamicResource gebunden und wechseln mit dem Thema mit. -->
<Border Background="{DynamicResource DangerBarBrush}"
BorderBrush="{DynamicResource DangerBrush}" BorderThickness="3,0,0,0"
IsVisible="{Binding IsError}" />
<Border Background="{DynamicResource WarningSoftBrush}"
BorderBrush="{DynamicResource WarningBrush}" BorderThickness="3,0,0,0"
IsVisible="{Binding IsWarning}" />
</DockPanel>
<DockPanel Margin="24,8">
<ctrl:StrokeIcon Data="{StaticResource IconAlert}" Width="15" Height="15"
DockPanel.Dock="Left" VerticalAlignment="Center"
Margin="0,0,10,0" />
<Button DockPanel.Dock="Right" Classes="flat" Padding="2"
Command="{Binding DismissCommand}" ToolTip.Tip="Ausblenden"
VerticalAlignment="Center">
<ctrl:StrokeIcon Data="{StaticResource IconClose}" Width="13" Height="13" />
</Button>
<TextBlock FontSize="12.5" VerticalAlignment="Center" TextWrapping="Wrap">
<Run Text="{Binding InstanceName}" FontWeight="SemiBold" /><Run Text=": " /><Run Text="{Binding Text}" />
</TextBlock>
</DockPanel>
</Panel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Statusleiste -->
<Border DockPanel.Dock="Bottom" Classes="statusbar">
<DockPanel>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Classes="caption" Text="{Binding InstanceName, StringFormat='Instanz: {0}'}" />
<TextBlock Classes="caption" Text="·" />
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
<Ellipse Width="7" Height="7" VerticalAlignment="Center"
Fill="{DynamicResource SuccessBrush}"
IsVisible="{Binding IsApiReachable}" />
<Ellipse Width="7" Height="7" VerticalAlignment="Center"
Fill="{DynamicResource DangerBrush}"
IsVisible="{Binding !IsApiReachable}" />
<TextBlock Classes="caption" Text="{Binding ApiStatusText, StringFormat='API {0}'}" />
</StackPanel>
<TextBlock Classes="caption" Text="·"
IsVisible="{Binding UsageText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<TextBlock Classes="caption" Text="{Binding UsageText}"
ToolTip.Tip="{Binding UsageTooltip}"
IsVisible="{Binding UsageText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<!-- Der Trenner gehoert zum Abschnitt dahinter: Ohne Guthabenangabe stuende
sonst ein Punkt am Ende der Zeile, der nichts mehr trennt. -->
<TextBlock Classes="caption" Text="·"
IsVisible="{Binding CreditsText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<TextBlock Classes="caption" Text="{Binding CreditsText}"
IsVisible="{Binding CreditsText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel>
<TextBlock DockPanel.Dock="Right" Classes="caption" HorizontalAlignment="Right"
Text="{Binding ThemeLabel}" />
</DockPanel>
</Border>
<!-- Inhalt -->
<ContentControl Content="{Binding SelectedPage}" Margin="20" />
</DockPanel>
</Grid>
</Window>
@@ -13,16 +13,12 @@ public partial class MainWindow : Window
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is MainWindowViewModel vm)
{
vm.RequestExit += Close;
vm.RequestInstanceManagerDialog += OnOpenInstanceManager;
}
}
private async void OnOpenInstanceManager()
{
var dialog = new InstancePickerWindow();
await dialog.ShowDialog(this);
// Der Instanzwechsel haengt nicht mehr hier, sondern in App: Er braucht den
// Aufbau und die Lebensdauer der Anwendung, und beides kennt das Fenster nicht.
// Vorher wurde hier ein InstancePickerWindow ganz ohne DataContext geoeffnet —
// ein leerer Dialog, dessen Auswahl niemand ausgewertet hat.
if (DataContext is MainWindowViewModel vm)
vm.RequestExit += Close;
}
}
@@ -3,94 +3,128 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
Padding="16"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="740"
x:Class="ClawdDotNet.Desktop.Views.SettingsPageView"
x:DataType="vm:SettingsPageViewModel">
<DockPanel>
<!-- Statusbar am unteren Rand -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
<Button Content="Verwerfen / Zurücksetzen" Command="{Binding LoadFromHostCommand}" />
<Button Content="Speichern" Command="{Binding SaveSettingsCommand}" Classes="accent" />
<!-- Aktionen unten: Der Entwurf setzt flache Felder ohne Karten, damit bleibt am
unteren Rand nur die Zeile mit Verwerfen und Speichern. -->
<Border DockPanel.Dock="Bottom" BorderBrush="{DynamicResource DividerBrush}"
BorderThickness="0,1,0,0" Padding="0,12,0,0" Margin="0,16,0,0">
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right">
<Button Content="Verwerfen" Command="{Binding LoadFromHostCommand}" />
<Button Classes="primary" Command="{Binding SaveSettingsCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconSave}" Width="14" Height="14" />
<TextBlock Text="Speichern" />
</StackPanel>
</Button>
</StackPanel>
</Border>
<ScrollViewer>
<StackPanel Spacing="16">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="26" MaxWidth="680" HorizontalAlignment="Left">
<TextBlock Text="Anwendungs- &amp; Instanz-Einstellungen" Classes="heading" FontSize="20" />
<!-- ══════════════════ Instanz ══════════════════ -->
<StackPanel Spacing="14">
<TextBlock Classes="kicker" Text="INSTANZ" />
<!-- Card: Instanz-Einstellungen -->
<Border Classes="card">
<StackPanel Spacing="12">
<TextBlock Text="Aktuelle Instanz" Classes="heading" />
<Grid ColumnDefinitions="180,*" RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Instanz-Name:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding InstanceName}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="OpenRouter API-Key:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding OpenRouterApiKey}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Tagesbudget ($ USD):" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding InstanceDailyCostUsd}" Minimum="0" Maximum="10000" FormatString="F2" Width="140" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Tagesbudget (Tokens):" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding InstanceDailyTokens}" Minimum="0" Maximum="100000000" FormatString="0" Width="180" HorizontalAlignment="Left" Margin="0,8,0,0" />
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Instanz-Name" />
<TextBox Text="{Binding InstanceName}" />
</StackPanel>
</Border>
<!-- Card: Allgemein & Pfade -->
<Border Classes="card">
<StackPanel Spacing="12">
<TextBlock Text="Allgemein &amp; Pfade" Classes="heading" />
<Grid ColumnDefinitions="180,*" RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Log-Verzeichnis:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding LogDirectory}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Instanzen-Verzeichnis:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding InstancesDirectory}" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Minimaler Log-Level:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding LogLevels}" SelectedItem="{Binding MinimumLogLevel}" Width="140" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="OpenRouter Base-URL:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding OpenRouterBaseUrl}" Margin="0,8,0,0" />
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="OpenRouter API-Key" />
<TextBox Text="{Binding OpenRouterApiKey}" PasswordChar="•" />
</StackPanel>
</Border>
<!-- Card: Deploymentcenter & Lizenz -->
<Border Classes="card">
<StackPanel Spacing="12">
<TextBlock Text="Deploymentcenter &amp; Lizenz" Classes="heading" />
<Grid ColumnDefinitions="*,16,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Tagesbudget ($ USD)" />
<TextBox Text="{Binding InstanceDailyCostUsd}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Tagesbudget (Tokens)" />
<TextBox Text="{Binding InstanceDailyTokens}" />
</StackPanel>
</Grid>
</StackPanel>
<Grid ColumnDefinitions="180,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Server-URL:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding DeploymentcenterUrl}" />
<Border Classes="hr" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Master-Token:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding DeploymentcenterToken}" PasswordChar="*" Margin="0,8,0,0" />
<!-- ══════════════════ Allgemein ══════════════════ -->
<StackPanel Spacing="14">
<TextBlock Classes="kicker" Text="ALLGEMEIN" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Update-Kanal:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding UpdateChannels}" SelectedItem="{Binding UpdateChannel}" Width="140" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Umgebung:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="3" Grid.Column="1" ItemsSource="{Binding Environments}" SelectedItem="{Binding DeploymentcenterEnvironment}" Width="160" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Lizenzschlüssel:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding LicenseKey}" PasswordChar="*" Margin="0,8,0,0" />
</Grid>
<CheckBox Content="Fehler automatisch an Deploymentcenter melden" IsChecked="{Binding ErrorReportingEnabled}" Margin="0,4,0,0" />
<CheckBox Content="Beim Start automatisch auf Updates prüfen" IsChecked="{Binding UpdateCheckEnabled}" />
<StackPanel>
<TextBlock Classes="label" Text="Log-Verzeichnis" />
<TextBox Text="{Binding LogDirectory}" />
</StackPanel>
</Border>
<StackPanel>
<TextBlock Classes="label" Text="Instanzen-Verzeichnis" />
<TextBox Text="{Binding InstancesDirectory}" />
</StackPanel>
<Grid ColumnDefinitions="*,16,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Minimaler Log-Level" />
<ComboBox ItemsSource="{Binding LogLevels}" SelectedItem="{Binding MinimumLogLevel}"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="OpenRouter Base-URL" />
<TextBox Text="{Binding OpenRouterBaseUrl}" />
</StackPanel>
</Grid>
</StackPanel>
<Border Classes="hr" />
<!-- ══════════════════ Deploymentcenter ══════════════════ -->
<StackPanel Spacing="14">
<TextBlock Classes="kicker" Text="DEPLOYMENTCENTER &amp; LIZENZ" />
<StackPanel>
<TextBlock Classes="label" Text="Server-URL" />
<TextBox Text="{Binding DeploymentcenterUrl}" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Master-Token" />
<TextBox Text="{Binding DeploymentcenterToken}" PasswordChar="•" />
</StackPanel>
<Grid ColumnDefinitions="*,16,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Update-Kanal" />
<ComboBox ItemsSource="{Binding UpdateChannels}" SelectedItem="{Binding UpdateChannel}"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Umgebung" />
<ComboBox ItemsSource="{Binding Environments}"
SelectedItem="{Binding DeploymentcenterEnvironment}"
HorizontalAlignment="Stretch" />
</StackPanel>
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Lizenzschlüssel" />
<TextBox Text="{Binding LicenseKey}" PasswordChar="•" />
</StackPanel>
<StackPanel Spacing="10" Margin="0,4,0,0">
<CheckBox Content="Fehler automatisch an Deploymentcenter melden"
IsChecked="{Binding ErrorReportingEnabled}" />
<CheckBox Content="Beim Start automatisch auf Updates prüfen"
IsChecked="{Binding UpdateCheckEnabled}" />
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
+229 -74
View File
@@ -3,100 +3,255 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
Padding="16"
xmlns:models="using:ClawdDotNet.App.Models"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="740"
x:Class="ClawdDotNet.Desktop.Views.TasksPageView"
x:DataType="vm:TasksPageViewModel">
<DockPanel>
<!-- Statusbar unten -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Text="{Binding StatusText}" VerticalAlignment="Center" />
</Border>
<!-- Segmentleiste statt Registerkarten. Die Knöpfe überlappen sich um einen
Bildpunkt, damit zwischen ihnen nur eine Linie steht und nicht zwei. -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"
HorizontalAlignment="Left" Margin="0,0,0,14">
<ToggleButton Content="Geplante Jobs" Margin="0,0,-1,0"
IsChecked="{Binding IsJobsTab, Mode=OneWay}"
Command="{Binding SelectTabCommand}" CommandParameter="Jobs" />
<ToggleButton Content="Dienste" Margin="0,0,-1,0"
IsChecked="{Binding IsServicesTab, Mode=OneWay}"
Command="{Binding SelectTabCommand}" CommandParameter="Services" />
<ToggleButton Content="Verlauf"
IsChecked="{Binding IsHistoryTab, Mode=OneWay}"
Command="{Binding SelectTabCommand}" CommandParameter="History" />
</StackPanel>
<TabControl>
<!-- Statuszeile des Bereichs -->
<TextBlock DockPanel.Dock="Bottom" Classes="caption" Margin="0,10,0,0"
Text="{Binding StatusText}" TextWrapping="Wrap" />
<!-- TAB 1: Jobs (Scheduled / Worker) -->
<TabItem Header="Geplante Jobs">
<DockPanel Margin="0,8,0,0">
<Panel>
<!-- Toolbar -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<Button Content="Job hinzufügen" Command="{Binding AddJobCommand}" Classes="accent" />
<Button Content="Bearbeiten" Command="{Binding EditJobCommand}" IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="Entfernen" Command="{Binding RemoveJobCommand}" IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="▶ Jetzt ausführen" Command="{Binding RunJobNowCommand}" IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}" Margin="12,0,0,0" />
<Button Content="Aktualisieren" Command="{Binding RefreshJobListCommand}" />
</StackPanel>
<!-- ══════════════════ Geplante Jobs ══════════════════ -->
<DockPanel IsVisible="{Binding IsJobsTab}">
<!-- Jobs DataGrid -->
<DataGrid ItemsSource="{Binding JobEntries}" SelectedItem="{Binding SelectedJob}" AutoGenerateColumns="False" IsReadOnly="True" GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="Typ" Binding="{Binding JobType}" Width="120" />
<DataGridTextColumn Header="Agent" Binding="{Binding AgentName}" Width="150" />
<DataGridTextColumn Header="Tool / Nachricht" Binding="{Binding TaskMessage}" Width="*" />
<DataGridTextColumn Header="Cron-Ausdruck" Binding="{Binding CronExpression}" Width="120" />
<DataGridTextColumn Header="Nächster Lauf" Binding="{Binding NextRun}" Width="140" />
<DataGridTextColumn Header="Letzter Lauf" Binding="{Binding LastRun}" Width="140" />
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="100" />
</DataGrid.Columns>
</DataGrid>
<WrapPanel DockPanel.Dock="Top" Classes="toolbar">
<Button Classes="toolbar primary" Command="{Binding AddJobCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlus}" Width="14" Height="14" />
<TextBlock Text="Job hinzufügen" />
</StackPanel>
</Button>
</DockPanel>
</TabItem>
<Button Classes="toolbar" Command="{Binding EditJobCommand}"
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPencil}" Width="14" Height="14" />
<TextBlock Text="Bearbeiten" />
</StackPanel>
</Button>
<!-- TAB 2: Dienste (Services) -->
<TabItem Header="Dienste (Services)">
<DockPanel Margin="0,8,0,0">
<Button Classes="toolbar" Command="{Binding RemoveJobCommand}"
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconTrash}" Width="14" Height="14" />
<TextBlock Text="Entfernen" />
</StackPanel>
</Button>
<!-- Toolbar -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<Button Content="Dienst hinzufügen" Command="{Binding AddServiceCommand}" Classes="accent" />
<Button Content="Entfernen" Command="{Binding RemoveServiceCommand}" IsEnabled="{Binding SelectedService, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="Aktualisieren" Command="{Binding RefreshServiceListCommand}" />
</StackPanel>
<Button Classes="toolbar" Command="{Binding ToggleJobEnabledCommand}"
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPower}" Width="14" Height="14" />
<TextBlock Text="Aktivieren/Deaktivieren" />
</StackPanel>
</Button>
<!-- Services DataGrid -->
<DataGrid ItemsSource="{Binding ServiceEntries}" SelectedItem="{Binding SelectedService}" AutoGenerateColumns="False" IsReadOnly="True" GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="Dienstname" Binding="{Binding Name}" Width="180" />
<DataGridTextColumn Header="Typ" Binding="{Binding Type}" Width="100" />
<DataGridTextColumn Header="Port" Binding="{Binding Port}" Width="80" />
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="110" />
<DataGridTextColumn Header="Beschreibung" Binding="{Binding Description}" Width="*" />
</DataGrid.Columns>
</DataGrid>
<Border Classes="sep" />
</DockPanel>
</TabItem>
<Button Classes="toolbar" Command="{Binding RunJobNowCommand}"
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlay}" Width="12" Height="12" IsFilled="True" />
<TextBlock Text="Jetzt ausführen" />
</StackPanel>
</Button>
<!-- TAB 3: Verlauf (Job History) -->
<TabItem Header="Verlauf (History)">
<DockPanel Margin="0,8,0,0">
<Button Classes="toolbar" Command="{Binding RefreshJobListCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="Aktualisieren" />
</StackPanel>
</Button>
</WrapPanel>
<!-- Toolbar -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<Button Content="Verlauf aktualisieren" Command="{Binding RefreshJobHistoryCommand}" />
</StackPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.1*,1.3*,2.2*,1.1*,1.3*,1.3*,0.9*">
<TextBlock Grid.Column="0" Classes="th" Text="TYP" />
<TextBlock Grid.Column="1" Classes="th" Text="AGENT" />
<TextBlock Grid.Column="2" Classes="th" Text="TOOL / NACHRICHT" />
<TextBlock Grid.Column="3" Classes="th" Text="CRON" />
<TextBlock Grid.Column="4" Classes="th" Text="NÄCHSTER LAUF" />
<TextBlock Grid.Column="5" Classes="th" Text="LETZTER LAUF" />
<TextBlock Grid.Column="6" Classes="th" Text="STATUS" />
</Grid>
</Border>
<!-- History DataGrid -->
<DataGrid ItemsSource="{Binding JobHistoryEntries}" AutoGenerateColumns="False" IsReadOnly="True" GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="Zeitpunkt" Binding="{Binding Time, StringFormat='{}{0:g}'}" Width="140" />
<DataGridTextColumn Header="Job-Name" Binding="{Binding JobName}" Width="140" />
<DataGridTextColumn Header="Agent" Binding="{Binding Agent}" Width="140" />
<DataGridTextColumn Header="Beschreibung" Binding="{Binding JobDescription}" Width="220" />
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="100" />
<DataGridTextColumn Header="Details / Info" Binding="{Binding Info}" Width="*" />
</DataGrid.Columns>
</DataGrid>
<ListBox Classes="table" ItemsSource="{Binding JobEntries}"
SelectedItem="{Binding SelectedJob}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:JobDisplayItemViewModel">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="1.1*,1.3*,2.2*,1.1*,1.3*,1.3*,0.9*">
<TextBlock Grid.Column="0" Classes="td" Text="{Binding JobType}" />
<TextBlock Grid.Column="1" Classes="td" Text="{Binding AgentName}" />
<TextBlock Grid.Column="2" Classes="td" Text="{Binding TaskMessage}" />
<TextBlock Grid.Column="3" Classes="td mono" Text="{Binding CronExpression}" />
<TextBlock Grid.Column="4" Classes="td" Text="{Binding NextRun}" />
<TextBlock Grid.Column="5" Classes="td" Text="{Binding LastRun}" />
</DockPanel>
</TabItem>
<Border Grid.Column="6" Classes="tag"
Classes.ok="{Binding Status,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=Aktiv}">
<TextBlock Text="{Binding Status}" />
</Border>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</TabControl>
<!-- ══════════════════ Dienste ══════════════════ -->
<DockPanel IsVisible="{Binding IsServicesTab}">
<WrapPanel DockPanel.Dock="Top" Classes="toolbar">
<Button Classes="toolbar primary" Command="{Binding AddServiceCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlus}" Width="14" Height="14" />
<TextBlock Text="Dienst hinzufügen" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding RemoveServiceCommand}"
IsEnabled="{Binding SelectedService, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconTrash}" Width="14" Height="14" />
<TextBlock Text="Entfernen" />
</StackPanel>
</Button>
<Border Classes="sep" />
<!-- Der Entwurf zeigt hier „Start"/„Stop". Umgelegt wird aber das Kennzeichen
in der Konfiguration, nicht ein laufender Prozess — deshalb die ehrlichere
Beschriftung. -->
<Button Classes="toolbar" Command="{Binding ToggleServiceEnabledCommand}"
IsEnabled="{Binding SelectedService, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPower}" Width="14" Height="14" />
<TextBlock Text="Aktivieren/Deaktivieren" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding RefreshServiceListCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="Aktualisieren" />
</StackPanel>
</Button>
</WrapPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.8*,1*,0.7*,1*,3*">
<TextBlock Grid.Column="0" Classes="th" Text="DIENSTNAME" />
<TextBlock Grid.Column="1" Classes="th" Text="TYP" />
<TextBlock Grid.Column="2" Classes="th" Text="PORT" />
<TextBlock Grid.Column="3" Classes="th" Text="STATUS" />
<TextBlock Grid.Column="4" Classes="th" Text="BESCHREIBUNG" />
</Grid>
</Border>
<ListBox Classes="table" ItemsSource="{Binding ServiceEntries}"
SelectedItem="{Binding SelectedService}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:ServiceDisplayItemViewModel">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="1.8*,1*,0.7*,1*,3*">
<TextBlock Grid.Column="0" Classes="td" FontWeight="SemiBold" Text="{Binding Name}" />
<TextBlock Grid.Column="1" Classes="td" Text="{Binding Type}" />
<TextBlock Grid.Column="2" Classes="td" Text="{Binding Port}" />
<Border Grid.Column="3" Classes="tag"
Classes.ok="{Binding Status,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=Bereit}">
<TextBlock Text="{Binding Status}" />
</Border>
<TextBlock Grid.Column="4" Classes="td dim" Text="{Binding Description}" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<!-- ══════════════════ Verlauf ══════════════════ -->
<DockPanel IsVisible="{Binding IsHistoryTab}">
<StackPanel DockPanel.Dock="Top" Classes="toolbar">
<Button Classes="toolbar" Command="{Binding RefreshJobHistoryCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="Verlauf aktualisieren" />
</StackPanel>
</Button>
</StackPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.3*,1.6*,1.4*,2*,0.9*,2.4*">
<TextBlock Grid.Column="0" Classes="th" Text="ZEITPUNKT" />
<TextBlock Grid.Column="1" Classes="th" Text="JOB" />
<TextBlock Grid.Column="2" Classes="th" Text="AGENT" />
<TextBlock Grid.Column="3" Classes="th" Text="BESCHREIBUNG" />
<TextBlock Grid.Column="4" Classes="th" Text="STATUS" />
<TextBlock Grid.Column="5" Classes="th" Text="DETAILS" />
</Grid>
</Border>
<ItemsControl ItemsSource="{Binding JobHistoryEntries}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="models:JobHistoryEntry">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="1.3*,1.6*,1.4*,2*,0.9*,2.4*">
<TextBlock Grid.Column="0" Classes="td"
Text="{Binding Time, StringFormat='{}{0:dd.MM. HH:mm}'}" />
<TextBlock Grid.Column="1" Classes="td" Text="{Binding JobName}" />
<TextBlock Grid.Column="2" Classes="td" Text="{Binding Agent}" />
<TextBlock Grid.Column="3" Classes="td dim" Text="{Binding JobDescription}" />
<Border Grid.Column="4" Classes="tag"
Classes.ok="{Binding Status,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=Success}"
Classes.bad="{Binding Status,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=Error}">
<TextBlock Text="{Binding Status}" />
</Border>
<TextBlock Grid.Column="5" Classes="td dim" Text="{Binding Info}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</Panel>
</DockPanel>
</UserControl>
@@ -0,0 +1,35 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="420"
x:Class="ClawdDotNet.Desktop.Views.TextEditorWindow"
Icon="/Assets/app.ico"
Title="Text bearbeiten"
Width="600" Height="420"
MinWidth="420" MinHeight="300"
WindowStartupLocation="CenterOwner">
<!-- Der Rahmen mit Eckmarken sitzt hier wie im Entwurf um den ganzen Dialog. Die
Marken ragen nach aussen, deshalb 12 Punkte Abstand zum Fensterrand. -->
<ctrl:BlueprintFrame Margin="12" Padding="16">
<DockPanel>
<TextBlock x:Name="TitleText" DockPanel.Dock="Top" Classes="h1"
Text="Text bearbeiten" Margin="0,0,0,12" TextWrapping="Wrap" />
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="8"
HorizontalAlignment="Right" Margin="0,14,0,0">
<Button Content="Abbrechen" IsCancel="True" Click="OnCancel" />
<Button Classes="primary" Content="Speichern" IsDefault="True" Click="OnSave" />
</StackPanel>
<TextBox x:Name="Editor" AcceptsReturn="True" TextWrapping="Wrap"
VerticalContentAlignment="Top"
ScrollViewer.VerticalScrollBarVisibility="Auto" />
</DockPanel>
</ctrl:BlueprintFrame>
</Window>
@@ -0,0 +1,48 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace ClawdDotNet.Desktop.Views;
/// <summary>
/// Kleiner Editor für die langen Textfelder eines Agenten (Identity, Soul).
///
/// <para>Ohne eigenes Ansichtsmodell: Der Dialog hat einen Titel, ein Textfeld und zwei
/// Schaltflächen. Ein Ansichtsmodell dafür wäre eine Datei mehr, ohne dass irgendetwas
/// prüfbar würde.</para>
/// </summary>
public partial class TextEditorWindow : Window
{
private string? _result;
public TextEditorWindow()
{
InitializeComponent();
}
/// <summary>
/// Zeigt den Editor und liefert den bearbeiteten Text — oder <c>null</c>, wenn
/// abgebrochen wurde. Das Schließen über das Fensterkreuz gilt als Abbruch: Sonst
/// wäre ein weggeklickter Dialog dasselbe wie ein bestätigter.
/// </summary>
public static async Task<string?> ShowAsync(Window owner, string title, string text)
{
var window = new TextEditorWindow { Title = title };
window.TitleText.Text = title;
window.Editor.Text = text;
await window.ShowDialog(owner);
return window._result;
}
private void OnSave(object? sender, RoutedEventArgs e)
{
_result = Editor.Text ?? "";
Close();
}
private void OnCancel(object? sender, RoutedEventArgs e)
{
_result = null;
Close();
}
}
@@ -0,0 +1,114 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="700"
x:Class="ClawdDotNet.Desktop.Views.TokenUsagePageView"
x:DataType="vm:TokenUsagePageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="20">
<!-- Kennzahlen -->
<StackPanel Orientation="Horizontal" Spacing="12">
<Border Classes="card" MinWidth="160">
<StackPanel Spacing="6">
<TextBlock Classes="kicker" Text="TOKENS (7 TAGE)" />
<TextBlock Classes="h2" Text="{Binding TotalTokens}" />
</StackPanel>
</Border>
<Border Classes="card" MinWidth="160">
<StackPanel Spacing="6">
<TextBlock Classes="kicker" Text="KOSTEN (7 TAGE)" />
<TextBlock Classes="h2" Text="{Binding TotalCost}" />
</StackPanel>
</Border>
<Border Classes="card" MinWidth="160">
<StackPanel Spacing="6">
<TextBlock Classes="kicker" Text="RUNS (7 TAGE)" />
<TextBlock Classes="h2" Text="{Binding TotalRuns}" />
</StackPanel>
</Border>
<Button Classes="toolbar" Command="{Binding RefreshCommand}" VerticalAlignment="Bottom">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="Aktualisieren" />
</StackPanel>
</Button>
</StackPanel>
<!-- Hinweis, solange nichts verzeichnet ist -->
<TextBlock Classes="caption" Text="{Binding StatusText}" TextWrapping="Wrap"
IsVisible="{Binding StatusText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<!-- Tagesbalken -->
<StackPanel Spacing="12" IsVisible="{Binding !IsEmpty}">
<TextBlock Classes="h2" Text="Tokens pro Tag" />
<Border BorderBrush="{DynamicResource DividerBrush}" BorderThickness="0,0,0,1"
Padding="0,0,0,2">
<ItemsControl ItemsSource="{Binding Days7}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:TokenDayItem">
<StackPanel Spacing="6" VerticalAlignment="Bottom"
HorizontalAlignment="Center" Margin="8,0">
<TextBlock Classes="caption" Text="{Binding TokensLabel}"
HorizontalAlignment="Center" />
<Rectangle Width="28" Height="{Binding BarHeight}"
Fill="{DynamicResource AccentBrush}"
VerticalAlignment="Bottom" />
<TextBlock FontSize="11.5" Text="{Binding DayLabel}"
HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</StackPanel>
<!-- Aufschlüsselung -->
<StackPanel Spacing="10" MaxWidth="860" HorizontalAlignment="Left"
IsVisible="{Binding !IsEmpty}">
<TextBlock Classes="h2" Text="Aufschlüsselung nach Agent &amp; Modell" />
<Border Classes="thead">
<Grid ColumnDefinitions="2*,2*,1.2*,1.2*,0.8*">
<TextBlock Grid.Column="0" Classes="th" Text="AGENT" />
<TextBlock Grid.Column="1" Classes="th" Text="MODELL" />
<TextBlock Grid.Column="2" Classes="th" Text="TOKENS" />
<TextBlock Grid.Column="3" Classes="th" Text="KOSTEN" />
<TextBlock Grid.Column="4" Classes="th" Text="RUNS" />
</Grid>
</Border>
<ItemsControl ItemsSource="{Binding ByAgent}" Margin="0,-10,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:TokenAgentItem">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="2*,2*,1.2*,1.2*,0.8*">
<TextBlock Grid.Column="0" Classes="td" FontWeight="SemiBold"
Text="{Binding AgentName}" />
<TextBlock Grid.Column="1" Classes="td dim" Text="{Binding Model}" />
<TextBlock Grid.Column="2" Classes="td" Text="{Binding TokensLabel}" />
<TextBlock Grid.Column="3" Classes="td" Text="{Binding CostLabel}" />
<TextBlock Grid.Column="4" Classes="td" Text="{Binding Runs}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace ClawdDotNet.Desktop.Views;
public partial class TokenUsagePageView : UserControl
{
public TokenUsagePageView() => InitializeComponent();
}
@@ -0,0 +1,239 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="520" d:DesignHeight="420"
x:Class="ClawdDotNet.Desktop.Views.ToolSettingsView"
x:DataType="vm:ToolSettingsViewModel">
<!--
Die Felder eines Werkzeugs — im Entwurf unten rechts auf der Agenten-Seite, nicht
mehr in einem eigenen Fenster. Deshalb ein UserControl und kein Window: Der Inhalt
ist derselbe, der Rahmen drumherum entscheidet der Aufrufer.
Flach statt in Karten und ohne verschachtelte Registerkarten (siehe
Implementierungsleitfaden im Mockup-Verzeichnis): Beschriftung ueber dem Feld,
hoechstens zwei Spalten.
-->
<StackPanel Spacing="12" MaxWidth="520" HorizontalAlignment="Left">
<!-- 1. FTP -->
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=FTP}">
<StackPanel>
<TextBlock Classes="label" Text="Host" />
<TextBox Text="{Binding FtpHost}" PlaceholderText="ftp.example.com" />
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Port" />
<TextBox Text="{Binding FtpPort}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Benutzername" />
<TextBox Text="{Binding FtpUsername}" />
</StackPanel>
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Passwort" />
<TextBox Text="{Binding FtpPassword}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Root-Pfad" />
<TextBox Text="{Binding FtpRootPath}" />
</StackPanel>
</StackPanel>
<!-- 2. Mail -->
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Mail}">
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Benutzername" />
<TextBox Text="{Binding MailUsername}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Passwort" />
<TextBox Text="{Binding MailPassword}" PasswordChar="•" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="IMAP-Host" />
<TextBox Text="{Binding MailImapHost}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="IMAP-Port" />
<TextBox Text="{Binding MailImapPort}" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="SMTP-Host" />
<TextBox Text="{Binding MailSmtpHost}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="SMTP-Port" />
<TextBox Text="{Binding MailSmtpPort}" />
</StackPanel>
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Empfänger" />
<TextBox Text="{Binding MailAllowedRecipients}"
PlaceholderText="adresse1@domain.de, adresse2@domain.de" />
</StackPanel>
</StackPanel>
<!-- 3. Database -->
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Database}">
<StackPanel>
<TextBlock Classes="label" Text="Typ" />
<ComboBox ItemsSource="{Binding DbTypes}" SelectedItem="{Binding DbType}"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Connection-String" />
<TextBox Text="{Binding DbConnectionString}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Zugriffsebene" />
<ComboBox ItemsSource="{Binding DbAccessLevels}" SelectedItem="{Binding DbAccessLevel}"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Tabellen" />
<TextBox Text="{Binding DbAllowedTables}" PlaceholderText="tabelle1, tabelle2" />
</StackPanel>
</StackPanel>
<!-- 4. Telegram -->
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Telegram}">
<StackPanel>
<TextBlock Classes="label" Text="Bot-Token" />
<TextBox Text="{Binding TelegramBotToken}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Standard Chat-ID" />
<TextBox Text="{Binding TelegramDefaultChatId}" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Chat-IDs" />
<TextBox Text="{Binding TelegramAllowedChatIds}" PlaceholderText="id1, id2" />
</StackPanel>
</StackPanel>
<!-- 5. DirectAPI -->
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=DirectAPI}">
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Standard-Provider" />
<TextBox Text="{Binding DirectApiDefaultProvider}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Cache TTL (Sekunden)" />
<TextBox Text="{Binding DirectApiCacheTtlSeconds}" />
</StackPanel>
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Twelve Data Key" />
<TextBox Text="{Binding DirectApiTwelveDataKey}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Alpha Vantage Key" />
<TextBox Text="{Binding DirectApiAlphaVantageKey}" PasswordChar="•" />
</StackPanel>
</StackPanel>
<!-- 6. WebFetch -->
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=WebFetch}">
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Domains" />
<TextBox Text="{Binding WebFetchAllowedDomains}"
PlaceholderText="reuters.com, coindesk.com" />
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Max. Antwortgröße (KB)" />
<TextBox Text="{Binding WebFetchMaxResponseKb}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="User Agent" />
<TextBox Text="{Binding WebFetchUserAgent}" />
</StackPanel>
</Grid>
</StackPanel>
<!-- 7. SocialMediaManager -->
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=SocialMediaManager}">
<StackPanel>
<TextBlock Classes="label" Text="X (Twitter) API-Key" />
<TextBox Text="{Binding SmmXApiKey}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="X Accounts" />
<TextBox Text="{Binding SmmXWatchAccounts}" PlaceholderText="account1, account2" />
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Subreddits" />
<TextBox Text="{Binding SmmRedditWatchSubreddits}" PlaceholderText="stocks, investing" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Reddit Post Limit" />
<TextBox Text="{Binding SmmRedditPostLimit}" />
</StackPanel>
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="OpenRouter Key" />
<TextBox Text="{Binding SmmOpenRouterApiKey}" PasswordChar="•" />
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="STT-Modell" />
<TextBox Text="{Binding SmmSttModel}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="YouTube-Kanäle" />
<TextBox Text="{Binding SmmYoutubeChannels}" PlaceholderText="URL1, URL2" />
</StackPanel>
</Grid>
</StackPanel>
<!-- 8. FileRW -->
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=FileRW}">
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Dateiendungen (persönlich)" />
<TextBox Text="{Binding FileRwPersonalAllowedExtensions}" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Zugriffsebene (geteilt)" />
<ComboBox ItemsSource="{Binding FileRwAccessLevels}"
SelectedItem="{Binding FileRwSharedAccessLevel}"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Dateiendungen (geteilt)" />
<TextBox Text="{Binding FileRwSharedAllowedExtensions}" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Geschützte Pfade" />
<TextBox Text="{Binding FileRwProtectedPaths}" />
</StackPanel>
</StackPanel>
<!-- Werkzeuge ohne weitere Einstellungen -->
<TextBlock Classes="muted" FontStyle="Italic" TextWrapping="Wrap"
Text="Dieses Werkzeug benötigt keine weiteren Einstellungen."
IsVisible="{Binding SettingsObject, Converter={x:Static ObjectConverters.IsNull}}" />
</StackPanel>
</UserControl>
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace ClawdDotNet.Desktop.Views;
public partial class ToolSettingsView : UserControl
{
public ToolSettingsView() => InitializeComponent();
}
@@ -1,180 +0,0 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:models="using:ClawdDotNet.Models"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="560" d:DesignHeight="480"
Width="580" Height="500"
Padding="16"
WindowStartupLocation="CenterOwner"
CanResize="False"
Title="Werkzeug-Einstellungen"
x:Class="ClawdDotNet.Desktop.Views.ToolSettingsWindow"
x:DataType="vm:ToolSettingsViewModel">
<DockPanel>
<!-- Bottom Buttons -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,16,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" />
<Button Content="Speichern" Command="{Binding ConfirmCommand}" Classes="accent" />
</StackPanel>
<ScrollViewer>
<StackPanel Spacing="12">
<TextBlock Text="{Binding ToolName, StringFormat='Einstellungen für {0}'}" Classes="heading" FontSize="18" />
<!-- Forms for each Tool -->
<StackPanel Spacing="8">
<!-- 1. FTP -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=FTP}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Host:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding FtpHost}" PlaceholderText="ftp.example.com" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Port:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding FtpPort}" Minimum="1" Maximum="65535" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Benutzername:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding FtpUsername}" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Passwort:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding FtpPassword}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Root-Pfad:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding FtpRootPath}" Margin="0,8,0,0" />
</Grid>
<!-- 2. Mail -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Mail}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Benutzername:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding MailUsername}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Passwort:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding MailPassword}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="IMAP-Host:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding MailImapHost}" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="IMAP-Port:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding MailImapPort}" Minimum="1" Maximum="65535" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="SMTP-Host:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding MailSmtpHost}" Margin="0,8,0,0" />
<TextBlock Grid.Row="5" Grid.Column="0" Text="SMTP-Port:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="5" Grid.Column="1" Value="{Binding MailSmtpPort}" Minimum="1" Maximum="65535" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="Erlaubte Empfänger:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding MailAllowedRecipients}" PlaceholderText="email1@domain.de, email2@domain.de" Margin="0,8,0,0" />
</Grid>
<!-- 3. Database -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Database}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Typ:" FontWeight="Bold" VerticalAlignment="Center" />
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding DbTypes}" SelectedItem="{Binding DbType}" HorizontalAlignment="Stretch" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Connection-String:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding DbConnectionString}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Zugriffsebene:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding DbAccessLevels}" SelectedItem="{Binding DbAccessLevel}" HorizontalAlignment="Stretch" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Erlaubte Tabellen:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding DbAllowedTables}" PlaceholderText="tabelle1, tabelle2" Margin="0,8,0,0" />
</Grid>
<!-- 4. Telegram -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Telegram}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Bot-Token:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding TelegramBotToken}" PasswordChar="*" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Standard Chat-ID:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding TelegramDefaultChatId}" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Erlaubte Chat-IDs:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding TelegramAllowedChatIds}" PlaceholderText="id1, id2" Margin="0,8,0,0" />
</Grid>
<!-- 5. DirectAPI -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=DirectAPI}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Standard-Provider:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding DirectApiDefaultProvider}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Cache TTL (Sek.):" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding DirectApiCacheTtlSeconds}" Minimum="0" Maximum="86400" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Twelve Data Key:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding DirectApiTwelveDataKey}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Alpha Vantage Key:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding DirectApiAlphaVantageKey}" PasswordChar="*" Margin="0,8,0,0" />
</Grid>
<!-- 6. WebFetch -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=WebFetch}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Erlaubte Domains:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding WebFetchAllowedDomains}" PlaceholderText="reuters.com, bloomberg.com" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Max Response KB:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding WebFetchMaxResponseKb}" Minimum="1" Maximum="10000" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="User Agent:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding WebFetchUserAgent}" Margin="0,8,0,0" />
</Grid>
<!-- 7. SocialMediaManager -->
<Grid ColumnDefinitions="160,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=SocialMediaManager}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="X (Twitter) API-Key:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding SmmXApiKey}" PasswordChar="*" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="X Accounts:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding SmmXWatchAccounts}" PlaceholderText="elonmusk, unusual_whales" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Subreddits:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding SmmRedditWatchSubreddits}" PlaceholderText="wallstreetbets, stocks" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Reddit Post Limit:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding SmmRedditPostLimit}" Minimum="1" Maximum="100" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="OpenRouter Key:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding SmmOpenRouterApiKey}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="5" Grid.Column="0" Text="STT Modell:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding SmmSttModel}" Margin="0,8,0,0" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="YouTube Kanäle:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding SmmYoutubeChannels}" PlaceholderText="URL1, URL2" Margin="0,8,0,0" />
</Grid>
<!-- 8. FileRW -->
<Grid ColumnDefinitions="180,*" RowDefinitions="Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=FileRW}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Personal Allowed Ext:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding FileRwPersonalAllowedExtensions}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Shared Access Level:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding FileRwAccessLevels}" SelectedItem="{Binding FileRwSharedAccessLevel}" HorizontalAlignment="Stretch" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Shared Allowed Ext:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding FileRwSharedAllowedExtensions}" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Geschützte Pfade:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding FileRwProtectedPaths}" Margin="0,8,0,0" />
</Grid>
<!-- Fallback info for tools without extra config -->
<TextBlock Text="Dieses Werkzeug benötigt keine weiteren Einstellungen."
IsVisible="{Binding SettingsObject, Converter={x:Static ObjectConverters.IsNull}}"
FontStyle="Italic" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</DockPanel>
</Window>
@@ -1,21 +0,0 @@
using Avalonia.Controls;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class ToolSettingsWindow : Window
{
public ToolSettingsWindow()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is ToolSettingsViewModel vm)
{
vm.CloseRequested += () => Close();
}
}
}