diff --git a/README.md b/README.md index 96f306a..7b20319 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저 개발 참고: Claw Code 동등성 작업 추적 문서 `docs/claw-code-parity-plan.md` +- 업데이트: 2026-04-06 08:39 (KST) +- AX Agent 하단 상태바 이벤트 처리와 회전 애니메이션을 `ChatWindow.StatusPresentation.cs`로 옮겼습니다. `UpdateStatusBar`, `StartStatusAnimation`, `StopStatusAnimation`이 상태 표현 파일로 이동해 메인 창 코드의 runtime/status 분기가 더 줄었습니다. +- `ChatWindow.xaml.cs`는 대화 실행 orchestration 중심으로 더 정리됐고, claw-code 기준 status line 정교화와 footer presentation 개선을 계속 이어가기 쉬운 구조로 맞췄습니다. + - 업데이트: 2026-04-06 08:27 (KST) - AX Agent 메시지 액션/메타/편집 렌더를 `ChatWindow.MessageInteractions.cs`로 분리했습니다. 좋아요·싫어요 피드백 버튼, 응답 메타 텍스트, 메시지 등장 애니메이션, 사용자 메시지 편집·재생성 흐름이 메인 창 코드 밖으로 이동했습니다. - `ChatWindow.xaml.cs`는 transcript 오케스트레이션과 상태 흐름에 더 집중하도록 정리했고, claw-code 기준 메시지 타입 분리와 renderer 구조화를 계속 진행하기 쉬운 기반을 만들었습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 59c7fe3..4bd4c8d 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -4897,3 +4897,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - Document update: 2026-04-06 08:28 (KST) - This keeps `ChatWindow.xaml.cs` more orchestration-focused while preserving the same runtime behavior, and it aligns AX Agent more closely with the `claw-code` model of separating prompt/footer presentation from session execution logic. - Document update: 2026-04-06 08:27 (KST) - Split message interaction presentation out of `ChatWindow.xaml.cs` into `ChatWindow.MessageInteractions.cs`. Feedback button creation, assistant response meta text, transcript entry animation, and inline user-message edit/regenerate flow are now grouped in a dedicated partial. - Document update: 2026-04-06 08:27 (KST) - This pass further reduces renderer responsibility in the main chat window file and moves AX Agent closer to the `claw-code` structure where transcript orchestration and message interaction presentation are separated. +- Document update: 2026-04-06 08:39 (KST) - Moved runtime status bar event handling and spinner animation out of `ChatWindow.xaml.cs` into `ChatWindow.StatusPresentation.cs`. `UpdateStatusBar`, `StartStatusAnimation`, and `StopStatusAnimation` now live alongside the existing operational status presentation helpers. +- Document update: 2026-04-06 08:39 (KST) - This pass reduces direct status-line branching in the main chat window file and keeps AX Agent closer to the `claw-code` model where runtime/footer presentation is separated from session orchestration. diff --git a/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs b/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs index 075b7bc..6bb3d32 100644 --- a/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.StatusPresentation.cs @@ -1,11 +1,15 @@ using System.Windows; using System.Windows.Media; +using System.Windows.Media.Animation; using AxCopilot.Services; +using AxCopilot.Services.Agent; namespace AxCopilot.Views; public partial class ChatWindow { + private Storyboard? _statusSpinStoryboard; + private AppStateService.OperationalStatusPresentationState BuildOperationalStatusPresentation() { var hasLiveRuntimeActivity = !string.Equals(_activeTab, "Chat", StringComparison.OrdinalIgnoreCase) @@ -169,4 +173,112 @@ public partial class ChatWindow StatusTokens.Visibility = Visibility.Visible; RefreshContextUsageVisual(); } + private void UpdateStatusBar(AgentEvent evt) + { + var toolLabel = evt.ToolName switch + { + "file_read" or "document_read" => "파일 읽기", + "file_write" => "파일 쓰기", + "file_edit" => "파일 수정", + "html_create" => "HTML 생성", + "xlsx_create" => "Excel 생성", + "docx_create" => "Word 생성", + "csv_create" => "CSV 생성", + "md_create" => "Markdown 생성", + "folder_map" => "폴더 탐색", + "glob" => "파일 검색", + "grep" => "내용 검색", + "process" => "명령 실행", + _ => evt.ToolName, + }; + + var isDebugLogLevel = string.Equals(_settings.Settings.Llm.AgentLogLevel, "debug", StringComparison.OrdinalIgnoreCase); + + switch (evt.Type) + { + case AgentEventType.Thinking: + SetStatus("생각 중...", spinning: true); + break; + case AgentEventType.Planning: + SetStatus($"계획 수립 중 — {evt.StepTotal}단계", spinning: true); + break; + case AgentEventType.PermissionRequest: + SetStatus($"권한 확인 중: {toolLabel}", spinning: false); + break; + case AgentEventType.PermissionGranted: + SetStatus($"권한 승인됨: {toolLabel}", spinning: false); + break; + case AgentEventType.PermissionDenied: + SetStatus($"권한 거부됨: {toolLabel}", spinning: false); + StopStatusAnimation(); + break; + case AgentEventType.Decision: + SetStatus(GetDecisionStatusText(evt.Summary), spinning: IsDecisionPending(evt.Summary)); + break; + case AgentEventType.ToolCall: + if (!isDebugLogLevel) + break; + SetStatus($"{toolLabel} 실행 중...", spinning: true); + break; + case AgentEventType.ToolResult: + SetStatus(evt.Success ? $"{toolLabel} 완료" : $"{toolLabel} 실패", spinning: false); + break; + case AgentEventType.StepStart: + SetStatus($"[{evt.StepCurrent}/{evt.StepTotal}] {TruncateForStatus(evt.Summary)}", spinning: true); + break; + case AgentEventType.StepDone: + SetStatus($"[{evt.StepCurrent}/{evt.StepTotal}] 단계 완료", spinning: true); + break; + case AgentEventType.SkillCall: + if (!isDebugLogLevel) + break; + SetStatus($"스킬 실행 중: {TruncateForStatus(evt.Summary)}", spinning: true); + break; + case AgentEventType.Complete: + SetStatus("작업 완료", spinning: false); + StopStatusAnimation(); + break; + case AgentEventType.Error: + SetStatus("오류 발생", spinning: false); + StopStatusAnimation(); + break; + case AgentEventType.Paused: + if (!isDebugLogLevel) + break; + SetStatus("⏸ 일시정지", spinning: false); + break; + case AgentEventType.Resumed: + if (!isDebugLogLevel) + break; + SetStatus("▶ 재개됨", spinning: true); + break; + } + } + + private void StartStatusAnimation() + { + if (_statusSpinStoryboard != null) + return; + + var anim = new DoubleAnimation + { + From = 0, + To = 360, + Duration = TimeSpan.FromSeconds(2), + RepeatBehavior = RepeatBehavior.Forever, + }; + + _statusSpinStoryboard = new Storyboard(); + Storyboard.SetTarget(anim, StatusDiamond); + Storyboard.SetTargetProperty(anim, + new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)")); + _statusSpinStoryboard.Children.Add(anim); + _statusSpinStoryboard.Begin(); + } + + private void StopStatusAnimation() + { + _statusSpinStoryboard?.Stop(); + _statusSpinStoryboard = null; + } } diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index 88dc462..8a8d6ff 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -16892,115 +16892,6 @@ public partial class ChatWindow : Window // ─── 하단 상태바 ────────────────────────────────────────────────────── - private System.Windows.Media.Animation.Storyboard? _statusSpinStoryboard; - - private void UpdateStatusBar(AgentEvent evt) - { - var toolLabel = evt.ToolName switch - { - "file_read" or "document_read" => "파일 읽기", - "file_write" => "파일 쓰기", - "file_edit" => "파일 수정", - "html_create" => "HTML 생성", - "xlsx_create" => "Excel 생성", - "docx_create" => "Word 생성", - "csv_create" => "CSV 생성", - "md_create" => "Markdown 생성", - "folder_map" => "폴더 탐색", - "glob" => "파일 검색", - "grep" => "내용 검색", - "process" => "명령 실행", - _ => evt.ToolName, - }; - - var isDebugLogLevel = string.Equals(_settings.Settings.Llm.AgentLogLevel, "debug", StringComparison.OrdinalIgnoreCase); - - switch (evt.Type) - { - case AgentEventType.Thinking: - SetStatus("생각 중...", spinning: true); - break; - case AgentEventType.Planning: - SetStatus($"계획 수립 중 — {evt.StepTotal}단계", spinning: true); - break; - case AgentEventType.PermissionRequest: - SetStatus($"권한 확인 중: {toolLabel}", spinning: false); - break; - case AgentEventType.PermissionGranted: - SetStatus($"권한 승인됨: {toolLabel}", spinning: false); - break; - case AgentEventType.PermissionDenied: - SetStatus($"권한 거부됨: {toolLabel}", spinning: false); - StopStatusAnimation(); - break; - case AgentEventType.Decision: - SetStatus(GetDecisionStatusText(evt.Summary), spinning: IsDecisionPending(evt.Summary)); - break; - case AgentEventType.ToolCall: - if (!isDebugLogLevel) - break; - SetStatus($"{toolLabel} 실행 중...", spinning: true); - break; - case AgentEventType.ToolResult: - SetStatus(evt.Success ? $"{toolLabel} 완료" : $"{toolLabel} 실패", spinning: false); - break; - case AgentEventType.StepStart: - SetStatus($"[{evt.StepCurrent}/{evt.StepTotal}] {TruncateForStatus(evt.Summary)}", spinning: true); - break; - case AgentEventType.StepDone: - SetStatus($"[{evt.StepCurrent}/{evt.StepTotal}] 단계 완료", spinning: true); - break; - case AgentEventType.SkillCall: - if (!isDebugLogLevel) - break; - SetStatus($"스킬 실행 중: {TruncateForStatus(evt.Summary)}", spinning: true); - break; - case AgentEventType.Complete: - SetStatus("작업 완료", spinning: false); - StopStatusAnimation(); - break; - case AgentEventType.Error: - SetStatus("오류 발생", spinning: false); - StopStatusAnimation(); - break; - case AgentEventType.Paused: - if (!isDebugLogLevel) - break; - SetStatus("⏸ 일시정지", spinning: false); - break; - case AgentEventType.Resumed: - if (!isDebugLogLevel) - break; - SetStatus("▶ 재개됨", spinning: true); - break; - } - } - - private void StartStatusAnimation() - { - if (_statusSpinStoryboard != null) return; - - var anim = new System.Windows.Media.Animation.DoubleAnimation - { - From = 0, To = 360, - Duration = TimeSpan.FromSeconds(2), - RepeatBehavior = System.Windows.Media.Animation.RepeatBehavior.Forever, - }; - - _statusSpinStoryboard = new System.Windows.Media.Animation.Storyboard(); - System.Windows.Media.Animation.Storyboard.SetTarget(anim, StatusDiamond); - System.Windows.Media.Animation.Storyboard.SetTargetProperty(anim, - new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)")); - _statusSpinStoryboard.Children.Add(anim); - _statusSpinStoryboard.Begin(); - } - - private void StopStatusAnimation() - { - _statusSpinStoryboard?.Stop(); - _statusSpinStoryboard = null; - } - private void BtnCompactNow_Click(object sender, RoutedEventArgs e) { if (_isStreaming)