using AxCopilot.Models; namespace AxCopilot.Views; public partial class ChatWindow { private sealed class TranscriptRenderPlan { public required List<(string Key, DateTime Timestamp, int Order, Action Render)> VisibleTimeline { get; init; } public required List NewKeys { get; init; } public required bool ShowHistory { get; init; } public required int HiddenCount { get; init; } public required bool CanIncremental { get; init; } public required int PreviousLiveCount { get; init; } public required int PreviousStableCount { get; init; } } private TranscriptRenderPlan BuildTranscriptRenderPlan( ChatConversation conv, IReadOnlyList visibleMessages, IReadOnlyList visibleEvents) { var orderedTimeline = BuildTimelineRenderActions(visibleMessages, visibleEvents); var effectiveRenderLimit = GetActiveTimelineRenderLimit(); var hiddenCount = Math.Max(0, orderedTimeline.Count - effectiveRenderLimit); // B-1: 스트리밍 중 hiddenCount 안정화 — 감소만 차단하여 prefix 키 불일치 방지 // 스트리밍 시 GetActiveTimelineRenderLimit()가 더 작은 값을 반환하면 // hiddenCount가 변동하여 visible 범위가 밀리고 인크리멘탈 렌더가 항상 실패함 if (_isStreaming && _lastRenderedHiddenCount >= 0 && hiddenCount < _lastRenderedHiddenCount) hiddenCount = _lastRenderedHiddenCount; var visibleTimeline = hiddenCount > 0 ? orderedTimeline.GetRange(hiddenCount, orderedTimeline.Count - hiddenCount) : orderedTimeline; var newKeys = new List(visibleTimeline.Count); foreach (var item in visibleTimeline) newKeys.Add(item.Key); // B-2: _agentLiveContainer가 transcript에 있어도 인크리멘탈 허용 // 라이브 컨테이너는 인크리멘탈 렌더 시 임시 분리 후 재삽입하므로 // expectedChildCount에 포함하고 canIncremental 조건에서 제외 var liveContainerInTranscript = _agentLiveContainer != null && ContainsTranscriptElement(_agentLiveContainer); var expectedChildCount = _lastRenderedTimelineKeys.Count + (_lastRenderedHiddenCount > 0 ? 1 : 0) + (liveContainerInTranscript ? 1 : 0); var canIncremental = _lastRenderedTimelineKeys.Count > 0 && newKeys.Count >= _lastRenderedTimelineKeys.Count && _lastRenderedHiddenCount == hiddenCount && GetTranscriptElementCount() == expectedChildCount; var previousLiveCount = 0; if (canIncremental) { for (var i = _lastRenderedTimelineKeys.Count - 1; i >= 0; i--) { if (_lastRenderedTimelineKeys[i].StartsWith("_live_", StringComparison.Ordinal)) previousLiveCount++; else break; } } return new TranscriptRenderPlan { VisibleTimeline = visibleTimeline, NewKeys = newKeys, ShowHistory = conv.ShowExecutionHistory, HiddenCount = hiddenCount, CanIncremental = canIncremental, PreviousLiveCount = previousLiveCount, PreviousStableCount = _lastRenderedTimelineKeys.Count - previousLiveCount, }; } }