From 76afb9d5c8990c8ac7e35e1b8e0c695f7aeb7d51 Mon Sep 17 00:00:00 2001 From: lacvet Date: Wed, 15 Apr 2026 22:02:55 +0900 Subject: [PATCH] =?UTF-8?q?AX=20Agent=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=20=EB=AC=B8=EA=B5=AC=20=EC=A7=80=EC=86=8D=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=ED=9A=8C=EA=B7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - V2 라이브 진행 카드에 고정 상태 본문/상세/메타 영역을 추가해 실행 로그만 남고 상단 메시지가 비어 보이던 문제를 줄임 - 스트리밍 시작 시 초기 내러티브를 채우고 각 AgentEvent 처리마다 상태 카드를 갱신하도록 정리함 - README.md와 docs/DEVELOPMENT.md에 2026-04-15 22:18 (KST) 기준 변경 이력과 검증 결과를 반영함 검증 결과 - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_live_message_persistence2\ -p:IntermediateOutputPath=obj\verify_live_message_persistence2\ : 경고 0 / 오류 0 - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ChatStreamingUiPolicyTests|ChatWindowSlashPolicyTests -p:OutputPath=bin\verify_live_message_persistence_tests\ -p:IntermediateOutputPath=obj\verify_live_message_persistence_tests\ : 통과 69 - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_live_message_persistence3\ -p:IntermediateOutputPath=obj\verify_live_message_persistence3\ : 경고 0 / 오류 0 --- README.md | 6 + docs/DEVELOPMENT.md | 6 + .../ChatWindow.V2LiveProgressPresentation.cs | 116 ++++++++++++++++++ 3 files changed, 128 insertions(+) diff --git a/README.md b/README.md index 9a3c43c..6282496 100644 --- a/README.md +++ b/README.md @@ -2279,3 +2279,9 @@ MIT License - 검증: - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_ppt_quality_gate\\ -p:IntermediateOutputPath=obj\\verify_ppt_quality_gate\\` 경고 0 / 오류 0 - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "PptxSkillTemplatePackTests|PptxSkillAutoRepairTests|PptxSkillGoldenDeckTests|PptQualityGatePolicyTests|PptxTemplateManifestCatalogTests" -p:OutputPath=bin\\verify_ppt_quality_gate_tests\\ -p:IntermediateOutputPath=obj\\verify_ppt_quality_gate_tests\\` 통과 12 +업데이트: 2026-04-15 22:18 (KST) +- AX Agent V2 라이브 진행 카드에 항상 남아 있는 상태 본문을 추가했습니다. 실행 로그 카드만 쌓이고 상단 안내 문구가 비어 보이던 문제를 줄이기 위해 `src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs`에서 고정 상태 카드와 상세/메타 텍스트를 함께 렌더링하도록 바꿨습니다. +- 라이브 카드 시작 시에는 초기 내러티브를, 이벤트 수신 중에는 최신 상태 요약을 같은 카드에 계속 갱신해 툴 호출 사이 공백 구간에도 “현재 무엇을 하고 있는지” 문구가 남도록 조정했습니다. +- 검증: + - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_live_message_persistence2\\ -p:IntermediateOutputPath=obj\\verify_live_message_persistence2\\` 경고 0 / 오류 0 + - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatStreamingUiPolicyTests|ChatWindowSlashPolicyTests" -p:OutputPath=bin\\verify_live_message_persistence_tests\\ -p:IntermediateOutputPath=obj\\verify_live_message_persistence_tests\\` 통과 69 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 84a5cfa..a70d648 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1602,3 +1602,9 @@ UI ?遺우쁽????域뱀뮆???귐뗫솯?醫딆춦 ???袁る퓮 ?臾믩씜 ??疫 - 검증: - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_ppt_quality_gate\\ -p:IntermediateOutputPath=obj\\verify_ppt_quality_gate\\` 경고 0 / 오류 0 - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "PptxSkillTemplatePackTests|PptxSkillAutoRepairTests|PptxSkillGoldenDeckTests|PptQualityGatePolicyTests|PptxTemplateManifestCatalogTests" -p:OutputPath=bin\\verify_ppt_quality_gate_tests\\ -p:IntermediateOutputPath=obj\\verify_ppt_quality_gate_tests\\` 통과 12 +업데이트: 2026-04-15 22:18 (KST) +- AX Agent V2 라이브 진행 카드의 상태 본문 지속성을 보강했습니다. `src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs`에 고정 상태 카드(`_v2LiveStatusCard`)와 본문/상세/메타 텍스트를 추가해, 실행 로그 항목만 남고 상단 안내 문구가 사라진 것처럼 보이던 회귀를 줄였습니다. +- 스트리밍 시작 시 `RefreshV2LiveStatusCard(runTab)`로 초기 상태를 먼저 채우고, 각 `AgentEvent` 처리 시 `UpdateV2LiveStatusCardFromEvent(...)`로 카드 내용을 갱신하도록 정리했습니다. 이제 툴 호출 카드가 접히거나 thinking 요약이 비어도 라이브 카드 상단 메시지는 유지됩니다. +- 검증: + - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_live_message_persistence2\\ -p:IntermediateOutputPath=obj\\verify_live_message_persistence2\\` 경고 0 / 오류 0 + - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatStreamingUiPolicyTests|ChatWindowSlashPolicyTests" -p:OutputPath=bin\\verify_live_message_persistence_tests\\ -p:IntermediateOutputPath=obj\\verify_live_message_persistence_tests\\` 통과 69 diff --git a/src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs b/src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs index 4a28e96..aa3e2e6 100644 --- a/src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs @@ -15,6 +15,10 @@ public partial class ChatWindow private DispatcherTimer? _v2LiveElapsedTimer; private DateTime _v2LiveStartTime; private TextBlock? _v2LiveElapsedText; + private Border? _v2LiveStatusCard; + private TextBlock? _v2LiveStatusText; + private TextBlock? _v2LiveStatusDetailText; + private TextBlock? _v2LiveStatusMetaText; /// V2: 스트리밍 시작 시 라이브 진행 컨테이너 생성 private void ShowAgentLiveCardV2(string runTab) @@ -29,7 +33,10 @@ public partial class ChatWindow _v2LastLiveToolCallId = null; var msgMaxWidth = GetMessageMaxWidth(); + var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue; + var accentColor = ResolveLiveProgressAccentColor(accentBrush); var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; + var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; _v2LiveContainer = new StackPanel { @@ -87,6 +94,52 @@ public partial class ChatWindow _v2LiveContainer.Children.Add(headerGrid); + _v2LiveStatusText = new TextBlock + { + FontSize = 14, + FontWeight = FontWeights.SemiBold, + Foreground = primaryText, + TextWrapping = TextWrapping.Wrap, + LineHeight = 20, + }; + _v2LiveStatusDetailText = new TextBlock + { + FontSize = 11, + Foreground = secondaryText, + Opacity = 0.88, + TextWrapping = TextWrapping.Wrap, + LineHeight = 18, + Margin = new Thickness(0, 4, 0, 0), + }; + _v2LiveStatusMetaText = new TextBlock + { + FontSize = 10, + Foreground = secondaryText, + Opacity = 0.72, + Margin = new Thickness(0, 8, 0, 0), + }; + + _v2LiveStatusCard = new Border + { + Background = new SolidColorBrush(Color.FromArgb(0x12, accentColor.R, accentColor.G, accentColor.B)), + BorderBrush = new SolidColorBrush(Color.FromArgb(0x36, accentColor.R, accentColor.G, accentColor.B)), + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(12), + Padding = new Thickness(14, 12, 14, 12), + Margin = new Thickness(0, 4, 0, 6), + Child = new StackPanel + { + Children = + { + _v2LiveStatusText, + _v2LiveStatusDetailText, + _v2LiveStatusMetaText, + } + } + }; + _v2LiveContainer.Children.Add(_v2LiveStatusCard); + RefreshV2LiveStatusCard(runTab); + AddTranscriptElement(_v2LiveContainer); ForceScrollToEnd(); @@ -108,6 +161,7 @@ public partial class ChatWindow var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; var msgMaxWidth = GetMessageMaxWidth(); + UpdateV2LiveStatusCardFromEvent(agentEvent); switch (agentEvent.Type) { @@ -327,6 +381,10 @@ public partial class ChatWindow _v2LiveElapsedTimer?.Stop(); _v2LiveElapsedTimer = null; _v2LiveElapsedText = null; + _v2LiveStatusCard = null; + _v2LiveStatusText = null; + _v2LiveStatusDetailText = null; + _v2LiveStatusMetaText = null; if (_v2LiveContainer == null) return; @@ -347,4 +405,62 @@ public partial class ChatWindow RemoveTranscriptElement(toRemove); } + + private void RefreshV2LiveStatusCard(string runTab) + { + if (_v2LiveStatusText == null) + return; + + var hint = GetLiveAgentProgressHint(); + if (hint != null) + { + var narrative = AgentStatusNarrativeCatalog.BuildFromEvent(hint, runTab); + var hintSummary = string.IsNullOrWhiteSpace(hint.Summary) + ? narrative.Message + : hint.Summary; + UpdateV2LiveStatusCard(hintSummary, narrative.Detail, BuildReadableProgressMetaText(hint)); + return; + } + + var initial = AgentStatusNarrativeCatalog.BuildInitial(runTab); + UpdateV2LiveStatusCard(initial.Message, initial.Detail, null); + } + + private void UpdateV2LiveStatusCardFromEvent(AgentEvent agentEvent) + { + if (_v2LiveStatusText == null) + return; + + var narrative = AgentStatusNarrativeCatalog.BuildFromEvent(agentEvent, _activeTab); + var normalizedThinking = agentEvent.Type == AgentEventType.Thinking + ? AgentProgressSummarySanitizer.NormalizeThinkingSummary( + agentEvent.Summary, + agentEvent.ToolName, + maxLength: 160) + : string.Empty; + var message = string.IsNullOrWhiteSpace(normalizedThinking) + ? narrative.Message + : normalizedThinking; + UpdateV2LiveStatusCard(message, narrative.Detail, BuildReadableProgressMetaText(agentEvent)); + } + + private void UpdateV2LiveStatusCard(string message, string? detail, string? meta) + { + if (_v2LiveStatusText == null || _v2LiveStatusDetailText == null || _v2LiveStatusMetaText == null) + return; + + _v2LiveStatusText.Text = string.IsNullOrWhiteSpace(message) + ? "작업을 이어가고 있습니다..." + : message; + + _v2LiveStatusDetailText.Text = detail ?? string.Empty; + _v2LiveStatusDetailText.Visibility = string.IsNullOrWhiteSpace(detail) + ? Visibility.Collapsed + : Visibility.Visible; + + _v2LiveStatusMetaText.Text = meta ?? string.Empty; + _v2LiveStatusMetaText.Visibility = string.IsNullOrWhiteSpace(meta) + ? Visibility.Collapsed + : Visibility.Visible; + } }