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); } }