diff --git a/README.md b/README.md index 261fa98..7700c46 100644 --- a/README.md +++ b/README.md @@ -1497,3 +1497,9 @@ MIT License - 스트리밍 종료/취소 시 `_streamStartTime`을 즉시 초기화하도록 정리해, 이전 실행의 시간이 다음 실행 카드나 assistant 메타에 새어 들어가지 않게 했습니다. - 채팅 입력창 글로우는 런처와 같은 방식으로 `표시/숨김 + 얇은 외곽선 + 부드러운 투명도` 중심으로 다듬었습니다. 과한 블러와 두꺼운 외곽선 때문에 지저분하게 보이던 인상을 줄였습니다. - 런처 글로우 토글은 일반 설정에 그대로 유지하고, AX Agent 내부 설정은 채팅 입력창 글로우만 담당하도록 역할을 분리했습니다. +- 업데이트: 2026-04-08 12:18 (KST) + - `claw-code`의 `Messages.tsx`, `VirtualMessageList.tsx`, `StatusLine.tsx`, `StreamingToolExecutor.ts` 흐름을 다시 대조해 AX의 코워크/코드가 실행 중 유독 무거운 원인을 점검했습니다. + - 구조 비교 결과, AX는 코워크/코드 스트리밍 중에도 process feed 이벤트가 transcript 전체 재렌더를 자주 유발하는 경로가 남아 있어 `claw-code`보다 UI 스레드 부담이 큰 상태였습니다. + - [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 에 `IsLightweightLiveProgressMode()`를 추가해, 코워크/코드 + 실행 히스토리 접힘 상태에서는 라이브 진행 카드를 우선 사용하고 transcript 재렌더 빈도를 더 강하게 낮추도록 조정했습니다. + - 같은 조건에서 `_executionHistoryRenderTimer`, `_agentUiEventTimer` 간격도 더 느슨하게 조정해, 스트리밍 중 작은 진행 이벤트가 여러 타이머를 통해 UI 전체를 자주 흔들던 문제를 줄였습니다. + - 코워크/코드 실행 중 접힌 히스토리 상태에서는 process feed 계열 이벤트가 더 이상 매번 transcript 렌더를 요청하지 않고, 완료/오류/문서 생성 결과처럼 실제로 기록 가치가 큰 이벤트만 강하게 렌더 요청을 남기도록 정리했습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 36a240d..a9818b4 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -5427,3 +5427,19 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - 글로우 설정 정책 정리 - 런처 글로우(`런처 무지개 글로우`, `런처 선택 글로우`)는 일반 설정에 그대로 유지한다. - AX Agent 내부 설정은 채팅 입력창 글로우만 조정하도록 역할을 분리했다. + +## 2026-04-08 12:18 (KST) + +- `claw-code` 구조 비교 + - [Messages.tsx](/E:/AX%20Copilot%20-%20Codex/claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/components/Messages.tsx), [VirtualMessageList.tsx](/E:/AX%20Copilot%20-%20Codex/claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/components/VirtualMessageList.tsx), [StatusLine.tsx](/E:/AX%20Copilot%20-%20Codex/claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/components/StatusLine.tsx), [StreamingToolExecutor.ts](/E:/AX%20Copilot%20-%20Codex/claw-code/claw-code-f5a40b86dede580f6543bf8926c9af017eea9409/src/services/tools/StreamingToolExecutor.ts)를 다시 대조했다. + - `claw-code`는 가상화된 메시지 리스트, memoized header/status line, 실행 중 라이브 영역과 최종 transcript 분리를 통해 CPU 쓰기를 크게 줄인다. + - AX는 코워크/코드 스트리밍 중에도 process feed 이벤트가 transcript 재렌더를 자주 유발해 `claw-code`보다 UI 스레드 부담이 큰 상태였다. +- [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) + - `IsLightweightLiveProgressMode()`를 추가해 `Cowork/Code + 실행 히스토리 접힘` 조합을 별도 경량 모드로 판단하도록 했다. + - 경량 모드에서는 `_executionHistoryRenderTimer` 간격을 2200ms, `_agentUiEventTimer` 간격을 420ms로 더 늦춰 진행 이벤트 폭주 시 UI 스레드 부하를 줄였다. + - 경량 모드에서는 process feed/대기/압축 계열 이벤트가 매번 transcript 렌더를 요청하지 않도록 조정하고, 완료/오류/문서 생성 결과처럼 실제 기록 가치가 큰 이벤트만 transcript 렌더 요청을 남기도록 정리했다. + - 라이브 진행 힌트(`UpdateLiveAgentProgressHint`)도 경량 모드에서는 transcript 렌더를 직접 깨우지 않고, 기존 라이브 카드/펄스 상태만 갱신하도록 바꿨다. +- 기대 효과 + - 코워크/코드 실행 중 잦은 `RenderMessages()` 호출 감소 + - 진행 이벤트가 많은 세션에서 입력/스크롤/탭 전환 버벅임 완화 + - 실행 중 UI는 라이브 카드 중심, 결과 기록은 완료 시점 중심으로 분리 diff --git a/src/AxCopilot/Views/ChatWindow.xaml.cs b/src/AxCopilot/Views/ChatWindow.xaml.cs index a2cd198..afce823 100644 --- a/src/AxCopilot/Views/ChatWindow.xaml.cs +++ b/src/AxCopilot/Views/ChatWindow.xaml.cs @@ -165,6 +165,20 @@ public partial class ChatWindow : Window private DateTime _lastAgentProgressEventAt = DateTime.UtcNow; private double _lastResponsiveComposerWidth; private double _lastResponsiveMessageWidth; + private bool IsLightweightLiveProgressMode(string? runTab = null) + { + var tab = string.IsNullOrWhiteSpace(runTab) ? (_streamRunTab ?? _activeTab) : runTab!; + if (!string.Equals(tab, "Cowork", StringComparison.OrdinalIgnoreCase) + && !string.Equals(tab, "Code", StringComparison.OrdinalIgnoreCase)) + return false; + + ChatConversation? conversation; + lock (_convLock) + conversation = _currentConversation; + + return !(conversation?.ShowExecutionHistory ?? true); + } + private void ApplyQuickActionVisual(Button button, bool active, string activeBg, string activeFg) { if (button?.Content is not string text) @@ -255,7 +269,9 @@ public partial class ChatWindow : Window _executionHistoryRenderTimer.Stop(); // 스트리밍 중에는 전체 재렌더링 빈도를 줄여 UI 부하 감소 _executionHistoryRenderTimer.Interval = _isStreaming - ? TimeSpan.FromMilliseconds(1500) + ? (IsLightweightLiveProgressMode() + ? TimeSpan.FromMilliseconds(2200) + : TimeSpan.FromMilliseconds(1500)) : TimeSpan.FromMilliseconds(350); RenderMessages(preserveViewport: true); if (_pendingExecutionHistoryAutoScroll) @@ -282,7 +298,9 @@ public partial class ChatWindow : Window { _agentUiEventTimer.Stop(); _agentUiEventTimer.Interval = _isStreaming - ? TimeSpan.FromMilliseconds(300) + ? (IsLightweightLiveProgressMode() + ? TimeSpan.FromMilliseconds(420) + : TimeSpan.FromMilliseconds(300)) : TimeSpan.FromMilliseconds(140); FlushPendingAgentUiEvent(); }; @@ -6819,8 +6837,16 @@ public partial class ChatWindow : Window // AppendConversationExecutionEvent, AppendConversationAgentRun, 디스크 저장은 // 백그라운드 스레드에서 배치 처리됩니다. UI 렌더 갱신도 배치 완료 후 1회만 호출됩니다. var shouldShowExecutionHistory = _currentConversation?.ShowExecutionHistory ?? false; - var shouldRender = (shouldShowExecutionHistory || ShouldRenderProgressEventWhenHistoryCollapsed(evt)) - && string.Equals(eventTab, _activeTab, StringComparison.OrdinalIgnoreCase); + var isActiveRunTab = string.Equals(eventTab, _activeTab, StringComparison.OrdinalIgnoreCase); + var lightweightLiveMode = isActiveRunTab + && !shouldShowExecutionHistory + && IsLightweightLiveProgressMode(eventTab); + var shouldRender = isActiveRunTab && ( + shouldShowExecutionHistory + || (!lightweightLiveMode && ShouldRenderProgressEventWhenHistoryCollapsed(evt)) + || evt.Type == AgentEventType.Complete + || evt.Type == AgentEventType.Error + || (evt.Type == AgentEventType.ToolResult && evt.Success && IsDocumentCreationTool(evt.ToolName))); EnqueueAgentEventWork(evt, eventTab, shouldRender); // ── 3단계: 경량 상태 추적 (UI 스레드) ─────────────────────────────── @@ -7430,7 +7456,9 @@ public partial class ChatWindow : Window }; var runTab = string.IsNullOrWhiteSpace(_streamRunTab) ? _activeTab : _streamRunTab!; - if (MessagePanel != null && string.Equals(runTab, _activeTab, StringComparison.OrdinalIgnoreCase)) + if (MessagePanel != null + && string.Equals(runTab, _activeTab, StringComparison.OrdinalIgnoreCase) + && !IsLightweightLiveProgressMode(runTab)) ScheduleExecutionHistoryRender(autoScroll: false); }