feat(ui): add MonitorPaneView

This commit is contained in:
Mika Kuns
2026-06-25 14:54:37 +02:00
parent 5a21d673c1
commit 15a3e65508
6 changed files with 84 additions and 0 deletions

View File

@@ -26,6 +26,13 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IDisposable
[ObservableProperty] private string _agentState = "idle";
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(DisplayTitle))]
private string? _title;
public string DisplayTitle =>
string.IsNullOrWhiteSpace(Title) ? (SubscribedTaskId ?? "task") : Title!;
public string AgentStatusLabel => Loc.T($"vm.agentStatus.{AgentState}");
public bool IsIdle => AgentState == "idle";
public bool IsQueued => AgentState == "queued";
@@ -142,6 +149,13 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IDisposable
OpenInAppRequested?.Invoke(_subscribedTaskId);
}
[RelayCommand]
private async System.Threading.Tasks.Task CancelTask()
{
if (!string.IsNullOrEmpty(_subscribedTaskId) && (IsRunning || IsQueued))
await _worker.CancelTaskAsync(_subscribedTaskId);
}
public void SetTaskId(string id) => _subscribedTaskId = id;
public void ApplyState(ClaudeDo.Data.Models.TaskStatus status) =>

View File

@@ -77,6 +77,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
var entity = await ctx.Tasks.AsNoTracking().FirstOrDefaultAsync(t => t.Id == taskId);
if (entity is null || monitor.SubscribedTaskId != taskId) return;
monitor.ApplyState(entity.Status);
monitor.Title = entity.Title;
var latestRun = await new TaskRunRepository(ctx).GetLatestByTaskIdAsync(taskId);
monitor.ApplyOutcome(entity.Result, latestRun?.ErrorMarkdown);
await monitor.ReplayLogFileAsync(entity.LogPath, CancellationToken.None);

View File

@@ -0,0 +1,53 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
xmlns:islands="using:ClaudeDo.Ui.Views.Islands"
xmlns:loc="using:ClaudeDo.Ui.Localization"
x:DataType="vm:TaskMonitorViewModel"
x:Class="ClaudeDo.Ui.Views.MissionControl.MonitorPaneView">
<Border Background="{DynamicResource Surface1Brush}"
BorderBrush="{DynamicResource LineBrush}"
BorderThickness="1" CornerRadius="10" ClipToBounds="True">
<DockPanel LastChildFill="True">
<!-- Footer: pane actions -->
<Border DockPanel.Dock="Bottom"
Background="{DynamicResource Surface2Brush}"
BorderBrush="{DynamicResource LineBrush}"
BorderThickness="0,1,0,0" Padding="8,6">
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Classes="btn" Content="{loc:Tr missionControl.openInApp}"
Command="{Binding OpenInAppCommand}" />
<Button Classes="btn" Content="{loc:Tr missionControl.cancel}"
Command="{Binding CancelTaskCommand}"
IsVisible="{Binding IsRunning}" />
</StackPanel>
</Border>
<!-- Blocking / roadblock banner -->
<Border DockPanel.Dock="Top"
IsVisible="{Binding ShowRoadblock}"
Background="{DynamicResource ErrorTintBrush}"
BorderBrush="{DynamicResource BloodBrush}"
BorderThickness="0,0,0,1" Padding="12,6">
<StackPanel Orientation="Horizontal" Spacing="8">
<PathIcon Data="{StaticResource Icon.Warning}"
Foreground="{DynamicResource BloodBrush}"
Width="13" Height="13" VerticalAlignment="Center" />
<TextBlock Classes="meta" Text="{Binding RoadblockMessage}"
Foreground="{DynamicResource BloodBrush}"
TextWrapping="Wrap" VerticalAlignment="Center" />
</StackPanel>
</Border>
<!-- Console body: reuse SessionTerminalView -->
<islands:SessionTerminalView
Entries="{Binding Log}"
Label="{Binding DisplayTitle}"
IsRunning="{Binding IsRunning}"
IsDone="{Binding IsDone}"
IsFailed="{Binding IsFailed}" />
</DockPanel>
</Border>
</UserControl>

View File

@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace ClaudeDo.Ui.Views.MissionControl;
public partial class MonitorPaneView : UserControl
{
public MonitorPaneView() => InitializeComponent();
}