Files
AX-Copilot-Codex/src/AxCopilot/Views/ChatWindow.TranscriptRendering.cs
lacvet 227f5ab0d3
Some checks are pending
Release Gate / gate (push) Waiting to run
에이전트 진행 표시 구조를 claude-code식 row 기반으로 재정리
- AgentTranscriptDisplayCatalog를 row presentation 중심으로 재구성해 thinking/waiting/compact/tool activity/permission/tool result/status를 타입별로 분리함
- PermissionRequestPresentationCatalog와 ToolResultPresentationCatalog를 정리해 권한 요청과 결과 상태를 행위/상태 기준으로 더 명확하게 표현함
- ChatWindow.AgentEventRendering에서 process feed 계열 이벤트를 GroupKey 기준으로 병합해 append 수를 줄이고 진행 흐름이 기본 transcript에 남도록 조정함
- FooterPresentation에서 Cowork/Chat 프리셋 안내 카드가 execution event 이후 자동으로 숨겨지도록 하고 입력 워터마크와 footer 기본 문구를 정리함
- render_messages 성능 로그에 processFeed append/merge 수치와 rowKindCounts를 추가해 %APPDATA%\\AxCopilot\\perf 기준 실검증이 가능하도록 함
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 기준 경고 0 / 오류 0 확인
2026-04-09 14:49:53 +09:00

125 lines
4.9 KiB
C#

using System.Diagnostics;
using System.Linq;
using System.Windows.Threading;
using AxCopilot.Models;
using AxCopilot.Services;
namespace AxCopilot.Views;
public partial class ChatWindow
{
private int GetActiveTimelineRenderLimit()
{
if (!_isStreaming)
return _timelineRenderLimit;
var streamingLimit = IsLightweightLiveProgressMode()
? TimelineLightweightStreamingRenderLimit
: TimelineStreamingRenderLimit;
return Math.Min(_timelineRenderLimit, streamingLimit);
}
private void RenderMessages(bool preserveViewport = false)
{
// B-4: 비가시 상태일 때 렌더링 차단 — 최소화/숨김 시 불필요한 UI 재구축 방지
if (this.WindowState == System.Windows.WindowState.Minimized || !IsVisible)
return;
var renderStopwatch = Stopwatch.StartNew();
var previousScrollableHeight = GetTranscriptScrollableHeight();
var previousVerticalOffset = GetTranscriptVerticalOffset();
ChatConversation? conv;
lock (_convLock) conv = _currentConversation;
_appState.RestoreAgentRunHistory(conv?.AgentRunHistory);
var visibleMessages = GetVisibleTimelineMessages(conv);
var visibleEvents = GetVisibleTimelineEvents(conv);
if (_isStreaming && preserveViewport
&& visibleMessages.Count == _lastRenderedMessageCount
&& visibleEvents.Count == _lastRenderedEventCount
&& (conv?.ShowExecutionHistory ?? true) == _lastRenderedShowHistory
&& string.Equals(_lastRenderedConversationId, conv?.Id, StringComparison.OrdinalIgnoreCase))
return;
if (conv == null || (visibleMessages.Count == 0 && visibleEvents.Count == 0))
{
ClearTranscriptElements();
_runBannerAnchors.Clear();
_lastRenderedTimelineKeys.Clear();
_lastRenderedMessageCount = 0;
_lastRenderedEventCount = 0;
EmptyState.Visibility = System.Windows.Visibility.Visible;
return;
}
if (!string.Equals(_lastRenderedConversationId, conv.Id, StringComparison.OrdinalIgnoreCase))
{
_lastRenderedConversationId = conv.Id;
_timelineRenderLimit = TimelineRenderPageSize;
_elementCache.Clear();
_lastRenderedTimelineKeys.Clear();
_lastRenderedMessageCount = 0;
_lastRenderedEventCount = 0;
InvalidateTimelineCache();
}
EmptyState.Visibility = System.Windows.Visibility.Collapsed;
var renderPlan = BuildTranscriptRenderPlan(conv, visibleMessages, visibleEvents);
// B-3: 스트리밍 전용 빠른 경로 → 일반 인크리멘탈 → 전체 재빌드
if (!TryApplyStreamingAppendRender(renderPlan)
&& !TryApplyIncrementalTranscriptRender(renderPlan))
ApplyFullTranscriptRender(renderPlan);
PruneTranscriptElementCache(renderPlan.NewKeys);
_lastRenderedMessageCount = visibleMessages.Count;
_lastRenderedEventCount = visibleEvents.Count;
_lastRenderedShowHistory = renderPlan.ShowHistory;
renderStopwatch.Stop();
if (renderStopwatch.ElapsedMilliseconds >= 24 || _isStreaming)
{
AgentPerformanceLogService.LogMetric(
"transcript",
"render_messages",
conv.Id,
_activeTab ?? "",
renderStopwatch.ElapsedMilliseconds,
new
{
preserveViewport,
streaming = _isStreaming,
lightweight = IsLightweightLiveProgressMode(),
visibleMessages = visibleMessages.Count,
visibleEvents = visibleEvents.Count,
renderedItems = renderPlan.NewKeys.Count,
hiddenCount = renderPlan.HiddenCount,
transcriptElements = GetTranscriptElementCount(),
processFeedAppends = _processFeedAppendCount,
processFeedMerges = _processFeedMergeCount,
rowKindCounts = _transcriptRowKindCounts.ToDictionary(
pair => pair.Key.ToString(),
pair => pair.Value),
});
}
if (!preserveViewport)
{
_ = Dispatcher.InvokeAsync(ScrollTranscriptToEnd, DispatcherPriority.Background);
return;
}
_ = Dispatcher.InvokeAsync(() =>
{
if (_transcriptScrollViewer == null)
return;
var newScrollableHeight = GetTranscriptScrollableHeight();
var delta = newScrollableHeight - previousScrollableHeight;
var targetOffset = Math.Max(0, previousVerticalOffset + Math.Max(0, delta));
ScrollTranscriptToVerticalOffset(targetOffset);
}, DispatcherPriority.Background);
}
}