diff --git a/README.md b/README.md index 42d05a1..759e417 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저 개발 참고: Claw Code 동등성 작업 추적 문서 `docs/claw-code-parity-plan.md` +- 업데이트: 2026-04-06 01:37 (KST) +- AX Agent의 계획 승인 흐름을 더 transcript 우선 구조로 정리했습니다. 인라인 승인 카드가 기본 경로를 맡고, `계획` 버튼은 저장된 계획 요약/단계를 여는 상세 보기 역할만 하도록 분리했습니다. +- 상태선과 quick strip 계산도 presentation state로 더 모았습니다. runtime badge, compact strip, quick strip의 텍스트/강조색/노출 여부를 한 번에 계산해 창 코드의 직접 분기를 줄였습니다. + - 업데이트: 2026-04-06 00:50 (KST) - 채팅/코워크 프리셋 카드의 hover 설명 레이어를 카드 내부 오버레이 방식에서 안정적인 tooltip형 설명으로 바꿨습니다. 카드 배경/테두리만 반응하게 정리해 hover 시 반복 깜빡임을 줄였습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 689818a..e9c595c 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,5 +1,8 @@ # AX Copilot - 媛쒕컻 臾몄꽌 +- Document update: 2026-04-06 01:37 (KST) - Reworked AX Agent plan approval toward a more transcript-native flow. The inline decision card remains the primary approval path, while the `계획` affordance now opens the stored plan as a detail-only surface instead of acting like a required popup step. +- Document update: 2026-04-06 01:37 (KST) - Expanded `OperationalStatusPresentationState` so runtime badge, compact strip, and quick-strip labels/colors/visibility are calculated together. `ChatWindow` now consumes a richer presentation model instead of branching on strip kinds and quick-strip counters independently. + - Document update: 2026-04-05 19:04 (KST) - Added transcript-facing tool/skill display normalization in `ChatWindow.xaml.cs`. Tool and skill events now use role-first badges (`도구`, `도구 결과`, `스킬`) with human-readable item labels such as `파일 읽기`, `빌드/실행`, `Git`, or `/skill-name`, instead of exposing raw snake_case names as the primary visual label. - Document update: 2026-04-05 19:04 (KST) - Reduced task-summary observability noise by limiting permission/background observability sections to debug-level sessions. Default AX Agent runtime UX now stays closer to `claw-code`, where transcript reading flow remains primary and diagnostics stay secondary. diff --git a/src/AxCopilot/Services/AppStateService.cs b/src/AxCopilot/Services/AppStateService.cs index b28d74b..d20028b 100644 --- a/src/AxCopilot/Services/AppStateService.cs +++ b/src/AxCopilot/Services/AppStateService.cs @@ -167,6 +167,20 @@ public sealed class AppStateService public bool ShowCompactStrip { get; init; } public string StripKind { get; init; } = "none"; public string StripText { get; init; } = ""; + public string StripBackgroundHex { get; init; } = ""; + public string StripBorderHex { get; init; } = ""; + public string StripForegroundHex { get; init; } = ""; + public bool ShowQuickStrip { get; init; } + public string QuickRunningText { get; init; } = ""; + public string QuickHotText { get; init; } = ""; + public bool QuickRunningActive { get; init; } + public bool QuickHotActive { get; init; } + public string QuickRunningBackgroundHex { get; init; } = "#F8FAFC"; + public string QuickRunningBorderHex { get; init; } = "#E5E7EB"; + public string QuickRunningForegroundHex { get; init; } = "#6B7280"; + public string QuickHotBackgroundHex { get; init; } = "#F8FAFC"; + public string QuickHotBorderHex { get; init; } = "#E5E7EB"; + public string QuickHotForegroundHex { get; init; } = "#6B7280"; } public ChatSessionStateService? ChatSession { get; private set; } @@ -631,7 +645,13 @@ public sealed class AppStateService }; } - public OperationalStatusPresentationState GetOperationalStatusPresentation(string tab, bool hasLiveRuntimeActivity) + public OperationalStatusPresentationState GetOperationalStatusPresentation( + string tab, + bool hasLiveRuntimeActivity, + int runningConversationCount, + int spotlightConversationCount, + bool runningOnlyFilter, + bool sortConversationsByRecent) { var status = GetOperationalStatus(tab); var showCompactStrip = !string.Equals(tab, "Chat", StringComparison.OrdinalIgnoreCase) @@ -639,6 +659,31 @@ public sealed class AppStateService || string.Equals(status.StripKind, "failed_run", StringComparison.OrdinalIgnoreCase) || string.Equals(status.StripKind, "permission_denied", StringComparison.OrdinalIgnoreCase)); + var stripBackgroundHex = ""; + var stripBorderHex = ""; + var stripForegroundHex = ""; + if (showCompactStrip) + { + if (string.Equals(status.StripKind, "permission_waiting", StringComparison.OrdinalIgnoreCase)) + { + stripBackgroundHex = "#FFF7ED"; + stripBorderHex = "#FDBA74"; + stripForegroundHex = "#C2410C"; + } + else if (string.Equals(status.StripKind, "failed_run", StringComparison.OrdinalIgnoreCase) + || string.Equals(status.StripKind, "permission_denied", StringComparison.OrdinalIgnoreCase)) + { + stripBackgroundHex = "#FEF2F2"; + stripBorderHex = "#FECACA"; + stripForegroundHex = "#991B1B"; + } + } + + var allowQuickStrip = !string.Equals(tab, "Chat", StringComparison.OrdinalIgnoreCase); + var quickRunningActive = runningOnlyFilter && runningConversationCount > 0; + var quickHotActive = !sortConversationsByRecent && spotlightConversationCount > 0; + var showQuickStrip = allowQuickStrip && (quickRunningActive || quickHotActive); + return new OperationalStatusPresentationState { ShowRuntimeBadge = status.ShowRuntimeBadge && hasLiveRuntimeActivity, @@ -648,6 +693,20 @@ public sealed class AppStateService ShowCompactStrip = showCompactStrip, StripKind = showCompactStrip ? status.StripKind : "none", StripText = showCompactStrip ? status.StripText : "", + StripBackgroundHex = stripBackgroundHex, + StripBorderHex = stripBorderHex, + StripForegroundHex = stripForegroundHex, + ShowQuickStrip = showQuickStrip, + QuickRunningText = runningConversationCount > 0 ? $"진행 {runningConversationCount}" : "진행", + QuickHotText = spotlightConversationCount > 0 ? $"활동 {spotlightConversationCount}" : "활동", + QuickRunningActive = quickRunningActive, + QuickHotActive = quickHotActive, + QuickRunningBackgroundHex = quickRunningActive ? "#DBEAFE" : "#F8FAFC", + QuickRunningBorderHex = quickRunningActive ? "#93C5FD" : "#E5E7EB", + QuickRunningForegroundHex = quickRunningActive ? "#1D4ED8" : "#6B7280", + QuickHotBackgroundHex = quickHotActive ? "#F5F3FF" : "#F8FAFC", + QuickHotBorderHex = quickHotActive ? "#C4B5FD" : "#E5E7EB", + QuickHotForegroundHex = quickHotActive ? "#6D28D9" : "#6B7280", }; } diff --git a/src/AxCopilot/Views/ChatWindow.InlineInteractions.cs b/src/AxCopilot/Views/ChatWindow.InlineInteractions.cs index 8a436e0..a868298 100644 --- a/src/AxCopilot/Views/ChatWindow.InlineInteractions.cs +++ b/src/AxCopilot/Views/ChatWindow.InlineInteractions.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -259,6 +260,8 @@ public partial class ChatWindow await Dispatcher.InvokeAsync(() => { + _pendingPlanSummary = planSummary; + _pendingPlanSteps = steps.ToList(); EnsurePlanViewerWindow(); _planViewerWindow?.LoadPlan(planSummary, steps, tcs); ShowPlanButton(true); @@ -268,7 +271,11 @@ public partial class ChatWindow var completed = await Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromMinutes(5))); if (completed != tcs.Task) { - await Dispatcher.InvokeAsync(() => _planViewerWindow?.Hide()); + await Dispatcher.InvokeAsync(() => + { + _planViewerWindow?.Hide(); + ResetPendingPlanPresentation(); + }); return "취소"; } @@ -290,12 +297,15 @@ public partial class ChatWindow await Dispatcher.InvokeAsync(() => { _planViewerWindow?.SwitchToExecutionMode(); - _planViewerWindow?.Hide(); }); } else { - await Dispatcher.InvokeAsync(() => _planViewerWindow?.Hide()); + await Dispatcher.InvokeAsync(() => + { + _planViewerWindow?.Hide(); + ResetPendingPlanPresentation(); + }); } return agentDecision; @@ -355,8 +365,18 @@ public partial class ChatWindow planBtn.MouseLeftButtonUp += (_, e) => { e.Handled = true; + if (string.IsNullOrWhiteSpace(_pendingPlanSummary) && _pendingPlanSteps.Count == 0) + return; + + EnsurePlanViewerWindow(); if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow)) { + if (string.IsNullOrWhiteSpace(_planViewerWindow.PlanText) + || _planViewerWindow.PlanText != (_pendingPlanSummary ?? string.Empty) + || !_planViewerWindow.Steps.SequenceEqual(_pendingPlanSteps)) + { + _planViewerWindow.LoadPlanPreview(_pendingPlanSummary ?? "", _pendingPlanSteps); + } _planViewerWindow.Show(); _planViewerWindow.Activate(); } @@ -377,6 +397,13 @@ public partial class ChatWindow { if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow)) _planViewerWindow.MarkComplete(); + ResetPendingPlanPresentation(); + } + + private void ResetPendingPlanPresentation() + { + _pendingPlanSummary = null; + _pendingPlanSteps.Clear(); ShowPlanButton(false); } diff --git a/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs b/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs new file mode 100644 index 0000000..075b7bc --- /dev/null +++ b/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs @@ -0,0 +1,172 @@ +using System.Windows; +using System.Windows.Media; +using AxCopilot.Services; + +namespace AxCopilot.Views; + +public partial class ChatWindow +{ + private AppStateService.OperationalStatusPresentationState BuildOperationalStatusPresentation() + { + var hasLiveRuntimeActivity = !string.Equals(_activeTab, "Chat", StringComparison.OrdinalIgnoreCase) + && (_runningConversationCount > 0 || _appState.ActiveTasks.Count > 0); + return _appState.GetOperationalStatusPresentation( + _activeTab, + hasLiveRuntimeActivity, + _runningConversationCount, + _spotlightConversationCount, + _runningOnlyFilter, + _sortConversationsByRecent); + } + + private void UpdateTaskSummaryIndicators() + { + var status = BuildOperationalStatusPresentation(); + + if (RuntimeActivityBadge != null) + RuntimeActivityBadge.Visibility = status.ShowRuntimeBadge + ? Visibility.Visible + : Visibility.Collapsed; + + if (RuntimeActivityLabel != null) + RuntimeActivityLabel.Text = status.RuntimeLabel; + + if (LastCompletedLabel != null) + { + LastCompletedLabel.Text = status.LastCompletedText; + LastCompletedLabel.Visibility = status.ShowLastCompleted ? Visibility.Visible : Visibility.Collapsed; + } + + if (ConversationStatusStrip != null && ConversationStatusStripLabel != null) + { + ConversationStatusStrip.Visibility = status.ShowCompactStrip + ? Visibility.Visible + : Visibility.Collapsed; + ConversationStatusStripLabel.Text = status.ShowCompactStrip ? status.StripText : ""; + + if (status.ShowCompactStrip) + { + ConversationStatusStrip.Background = BrushFromHex(status.StripBackgroundHex); + ConversationStatusStrip.BorderBrush = BrushFromHex(status.StripBorderHex); + ConversationStatusStripLabel.Foreground = BrushFromHex(status.StripForegroundHex); + } + } + + UpdateConversationQuickStripUi(status); + } + + private void UpdateConversationQuickStripUi() + { + UpdateConversationQuickStripUi(BuildOperationalStatusPresentation()); + } + + private void UpdateConversationQuickStripUi(AppStateService.OperationalStatusPresentationState status) + { + if (ConversationQuickStrip == null || QuickRunningLabel == null || QuickHotLabel == null + || BtnQuickRunningFilter == null || BtnQuickHotSort == null) + return; + + ConversationQuickStrip.Visibility = status.ShowQuickStrip + ? Visibility.Visible + : Visibility.Collapsed; + + QuickRunningLabel.Text = status.QuickRunningText; + QuickHotLabel.Text = status.QuickHotText; + + BtnQuickRunningFilter.Background = BrushFromHex(status.QuickRunningBackgroundHex); + BtnQuickRunningFilter.BorderBrush = BrushFromHex(status.QuickRunningBorderHex); + BtnQuickRunningFilter.BorderThickness = new Thickness(1); + QuickRunningLabel.Foreground = BrushFromHex(status.QuickRunningForegroundHex); + + BtnQuickHotSort.Background = BrushFromHex(status.QuickHotBackgroundHex); + BtnQuickHotSort.BorderBrush = BrushFromHex(status.QuickHotBorderHex); + BtnQuickHotSort.BorderThickness = new Thickness(1); + QuickHotLabel.Foreground = BrushFromHex(status.QuickHotForegroundHex); + } + + private void SetStatus(string text, bool spinning) + { + if (StatusLabel != null) + StatusLabel.Text = text; + + if (spinning) + StartStatusAnimation(); + else + StopStatusAnimation(); + } + + private static bool IsDecisionPending(string? summary) + { + var text = summary?.Trim() ?? ""; + if (string.IsNullOrWhiteSpace(text)) + return true; + + return text.Contains("확인 대기", StringComparison.OrdinalIgnoreCase) + || text.Contains("승인 대기", StringComparison.OrdinalIgnoreCase); + } + + private static bool IsDecisionApproved(string? summary) + { + var text = summary?.Trim() ?? ""; + if (string.IsNullOrWhiteSpace(text)) + return false; + + return text.Contains("계획 승인", StringComparison.OrdinalIgnoreCase); + } + + private static bool IsDecisionRejected(string? summary) + { + var text = summary?.Trim() ?? ""; + if (string.IsNullOrWhiteSpace(text)) + return false; + + return text.Contains("계획 반려", StringComparison.OrdinalIgnoreCase) + || text.Contains("수정 요청", StringComparison.OrdinalIgnoreCase) + || text.Contains("취소", StringComparison.OrdinalIgnoreCase); + } + + private static string GetDecisionStatusText(string? summary) + { + if (IsDecisionPending(summary)) + return "계획 승인 대기 중"; + if (IsDecisionApproved(summary)) + return "계획 승인됨 · 실행 시작"; + if (IsDecisionRejected(summary)) + return "계획 반려됨 · 계획 재작성"; + return string.IsNullOrWhiteSpace(summary) ? "사용자 의사결정 대기 중" : TruncateForStatus(summary); + } + + private void SetStatusIdle() + { + StopStatusAnimation(); + if (StatusLabel != null) + StatusLabel.Text = "대기 중"; + if (StatusElapsed != null) + { + StatusElapsed.Text = ""; + StatusElapsed.Visibility = Visibility.Collapsed; + } + if (StatusTokens != null) + { + StatusTokens.Text = ""; + StatusTokens.Visibility = Visibility.Collapsed; + } + RefreshContextUsageVisual(); + ScheduleGitBranchRefresh(250); + } + + private void UpdateStatusTokens(int inputTokens, int outputTokens) + { + if (StatusTokens == null) + return; + + var llm = _settings.Settings.Llm; + var (inCost, outCost) = Services.TokenEstimator.EstimateCost( + inputTokens, outputTokens, llm.Service, llm.Model); + var totalCost = inCost + outCost; + var costText = totalCost > 0 ? $" · {Services.TokenEstimator.FormatCost(totalCost)}" : ""; + StatusTokens.Text = $"↑{Services.TokenEstimator.Format(inputTokens)} ↓{Services.TokenEstimator.Format(outputTokens)}{costText}"; + StatusTokens.Visibility = Visibility.Visible; + RefreshContextUsageVisual(); + } +} diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index c4834b5..dc43f2e 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -69,6 +69,8 @@ public partial class ChatWindow : Window private WorkflowAnalyzerWindow? _analyzerWindow; // 워크플로우 분석기 private PlanViewerWindow? _planViewerWindow; // 실행 계획 뷰어 private Border? _userAskCard; // transcript 내 질문 카드 + private string? _pendingPlanSummary; + private List _pendingPlanSteps = new(); private bool _userScrolled; // 사용자가 위로 스크롤했는지 private readonly HashSet _sessionPermissionRules = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _sessionMcpEnabledOverrides = new(StringComparer.OrdinalIgnoreCase); @@ -8965,89 +8967,6 @@ public partial class ChatWindow : Window } } - private void UpdateTaskSummaryIndicators() - { - var hasLiveRuntimeActivity = !string.Equals(_activeTab, "Chat", StringComparison.OrdinalIgnoreCase) - && (_runningConversationCount > 0 || _appState.ActiveTasks.Count > 0); - var status = _appState.GetOperationalStatusPresentation(_activeTab, hasLiveRuntimeActivity); - - if (RuntimeActivityBadge != null) - RuntimeActivityBadge.Visibility = status.ShowRuntimeBadge - ? Visibility.Visible - : Visibility.Collapsed; - - if (RuntimeActivityLabel != null) - RuntimeActivityLabel.Text = status.RuntimeLabel; - - if (LastCompletedLabel != null) - { - LastCompletedLabel.Text = status.LastCompletedText; - LastCompletedLabel.Visibility = status.ShowLastCompleted ? Visibility.Visible : Visibility.Collapsed; - } - - if (ConversationStatusStrip != null && ConversationStatusStripLabel != null) - { - if (!status.ShowCompactStrip) - { - ConversationStatusStrip.Visibility = Visibility.Collapsed; - ConversationStatusStripLabel.Text = ""; - } - else if (string.Equals(status.StripKind, "permission_waiting", StringComparison.OrdinalIgnoreCase)) - { - ConversationStatusStrip.Visibility = Visibility.Visible; - ConversationStatusStrip.Background = BrushFromHex("#FFF7ED"); - ConversationStatusStrip.BorderBrush = BrushFromHex("#FDBA74"); - ConversationStatusStripLabel.Foreground = BrushFromHex("#C2410C"); - ConversationStatusStripLabel.Text = status.StripText; - } - else if (string.Equals(status.StripKind, "failed_run", StringComparison.OrdinalIgnoreCase) - || string.Equals(status.StripKind, "permission_denied", StringComparison.OrdinalIgnoreCase)) - { - ConversationStatusStrip.Visibility = Visibility.Visible; - ConversationStatusStrip.Background = BrushFromHex("#FEF2F2"); - ConversationStatusStrip.BorderBrush = BrushFromHex("#FECACA"); - ConversationStatusStripLabel.Foreground = BrushFromHex("#991B1B"); - ConversationStatusStripLabel.Text = status.StripText; - } - else - { - ConversationStatusStrip.Visibility = Visibility.Collapsed; - ConversationStatusStripLabel.Text = ""; - } - } - - UpdateConversationQuickStripUi(); - } - - private void UpdateConversationQuickStripUi() - { - if (ConversationQuickStrip == null || QuickRunningLabel == null || QuickHotLabel == null - || BtnQuickRunningFilter == null || BtnQuickHotSort == null) - return; - - var allowQuickStrip = !string.Equals(_activeTab, "Chat", StringComparison.OrdinalIgnoreCase); - var hasQuickSignal = allowQuickStrip - && ((_runningOnlyFilter && _runningConversationCount > 0) - || (!_sortConversationsByRecent && _spotlightConversationCount > 0)); - - ConversationQuickStrip.Visibility = hasQuickSignal - ? Visibility.Visible - : Visibility.Collapsed; - - QuickRunningLabel.Text = _runningConversationCount > 0 ? $"진행 {_runningConversationCount}" : "진행"; - QuickHotLabel.Text = _spotlightConversationCount > 0 ? $"활동 {_spotlightConversationCount}" : "활동"; - - BtnQuickRunningFilter.Background = _runningOnlyFilter ? BrushFromHex("#DBEAFE") : BrushFromHex("#F8FAFC"); - BtnQuickRunningFilter.BorderBrush = _runningOnlyFilter ? BrushFromHex("#93C5FD") : BrushFromHex("#E5E7EB"); - BtnQuickRunningFilter.BorderThickness = new Thickness(1); - QuickRunningLabel.Foreground = _runningOnlyFilter ? BrushFromHex("#1D4ED8") : (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray); - - BtnQuickHotSort.Background = !_sortConversationsByRecent ? BrushFromHex("#F5F3FF") : BrushFromHex("#F8FAFC"); - BtnQuickHotSort.BorderBrush = !_sortConversationsByRecent ? BrushFromHex("#C4B5FD") : BrushFromHex("#E5E7EB"); - BtnQuickHotSort.BorderThickness = new Thickness(1); - QuickHotLabel.Foreground = !_sortConversationsByRecent ? BrushFromHex("#6D28D9") : (TryFindResource("SecondaryText") as Brush ?? Brushes.Gray); - } - private static string GetRunStatusLabel(string? status) => status switch { @@ -17812,53 +17731,6 @@ public partial class ChatWindow : Window } } - private void SetStatus(string text, bool spinning) - { - if (StatusLabel != null) StatusLabel.Text = text; - if (spinning) StartStatusAnimation(); - } - - private static bool IsDecisionPending(string? summary) - { - var text = summary?.Trim() ?? ""; - if (string.IsNullOrWhiteSpace(text)) - return true; - - return text.Contains("확인 대기", StringComparison.OrdinalIgnoreCase) - || text.Contains("승인 대기", StringComparison.OrdinalIgnoreCase); - } - - private static bool IsDecisionApproved(string? summary) - { - var text = summary?.Trim() ?? ""; - if (string.IsNullOrWhiteSpace(text)) - return false; - - return text.Contains("계획 승인", StringComparison.OrdinalIgnoreCase); - } - - private static bool IsDecisionRejected(string? summary) - { - var text = summary?.Trim() ?? ""; - if (string.IsNullOrWhiteSpace(text)) - return false; - - return text.Contains("계획 반려", StringComparison.OrdinalIgnoreCase) - || text.Contains("수정 요청", StringComparison.OrdinalIgnoreCase) - || text.Contains("취소", StringComparison.OrdinalIgnoreCase); - } - - private static string GetDecisionStatusText(string? summary) - { - if (IsDecisionPending(summary)) - return "계획 승인 대기 중"; - if (IsDecisionApproved(summary)) - return "계획 승인됨 — 실행 시작"; - if (IsDecisionRejected(summary)) - return "계획 반려됨 — 계획 재작성"; - return string.IsNullOrWhiteSpace(summary) ? "사용자 의사결정 대기 중" : TruncateForStatus(summary); - } - private void StartStatusAnimation() { if (_statusSpinStoryboard != null) return; @@ -17884,37 +17756,6 @@ public partial class ChatWindow : Window _statusSpinStoryboard = null; } - private void SetStatusIdle() - { - StopStatusAnimation(); - if (StatusLabel != null) StatusLabel.Text = "대기 중"; - if (StatusElapsed != null) - { - StatusElapsed.Text = ""; - StatusElapsed.Visibility = Visibility.Collapsed; - } - if (StatusTokens != null) - { - StatusTokens.Text = ""; - StatusTokens.Visibility = Visibility.Collapsed; - } - RefreshContextUsageVisual(); - ScheduleGitBranchRefresh(250); - } - - private void UpdateStatusTokens(int inputTokens, int outputTokens) - { - if (StatusTokens == null) return; - var llm = _settings.Settings.Llm; - var (inCost, outCost) = Services.TokenEstimator.EstimateCost( - inputTokens, outputTokens, llm.Service, llm.Model); - var totalCost = inCost + outCost; - var costText = totalCost > 0 ? $" · {Services.TokenEstimator.FormatCost(totalCost)}" : ""; - StatusTokens.Text = $"↑{Services.TokenEstimator.Format(inputTokens)} ↓{Services.TokenEstimator.Format(outputTokens)}{costText}"; - StatusTokens.Visibility = Visibility.Visible; - RefreshContextUsageVisual(); - } - private void BtnCompactNow_Click(object sender, RoutedEventArgs e) { if (_isStreaming) diff --git a/src/AxCopilot/Views/PlanViewerWindow.cs b/src/AxCopilot/Views/PlanViewerWindow.cs index 7f4f454..8df7ca7 100644 --- a/src/AxCopilot/Views/PlanViewerWindow.cs +++ b/src/AxCopilot/Views/PlanViewerWindow.cs @@ -306,6 +306,25 @@ internal sealed class PlanViewerWindow : Window _statusBar.Visibility = Visibility.Collapsed; } + public void LoadPlanPreview(string planText, List steps) + { + _planText = planText; + _steps = steps; + _tcs = null; + _currentStep = -1; + _isExecuting = false; + _expandedSteps.Clear(); + if (_uiExpressionLevel == "rich") + { + for (int i = 0; i < _steps.Count; i++) + _expandedSteps.Add(i); + } + + RenderSteps(); + BuildCloseButton(); + _statusBar.Visibility = Visibility.Collapsed; + } + public Task ShowPlanAsync(string planText, List steps, TaskCompletionSource tcs) { LoadPlan(planText, steps, tcs);