코워크/코드 실행 중 UI 렌더 부담 완화 및 claude-code 구조 비교 반영
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
- claude-code의 Messages, VirtualMessageList, StatusLine, StreamingToolExecutor 흐름을 다시 비교해 AX의 구조적 병목을 점검함 - Cowork/Code에서 실행 히스토리를 접어 둔 상태일 때 process feed 이벤트가 transcript 전체 재렌더를 자주 유발하던 경로를 줄임 - 경량 라이브 진행 모드를 도입해 라이브 카드와 상태 표시를 우선 사용하고 execution history render / agent UI event timer 간격을 더 느슨하게 조정함 - 완료/오류/문서 결과처럼 기록 가치가 큰 이벤트만 적극적으로 transcript 렌더를 요청하도록 정리함 - README와 DEVELOPMENT 문서를 2026-04-08 12:18 (KST) 기준으로 갱신함 - 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0, 오류 0)
This commit is contained in:
@@ -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 렌더를 요청하지 않고, 완료/오류/문서 생성 결과처럼 실제로 기록 가치가 큰 이벤트만 강하게 렌더 요청을 남기도록 정리했습니다.
|
||||
|
||||
@@ -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는 라이브 카드 중심, 결과 기록은 완료 시점 중심으로 분리
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user