구조 개선: transcript 지연 가상화와 tool executor 분리 적용
이번 변경은 claude-code 기준 구조 강건성을 높이기 위한 리팩터링입니다. 핵심 수정 사항: - AX Agent transcript를 TranscriptVisualItem/TranscriptVisualHost 기반 지연 materialization 구조로 전환해 MessageList 가상화 기반을 강화했습니다. - StreamingToolExecutionCoordinator를 IToolExecutionCoordinator 뒤로 분리해 AgentLoopService가 구체 구현에 직접 묶이지 않도록 정리했습니다. - 라이브 진행 카드 렌더를 ChatWindow.LiveProgressPresentation partial로 이동해 ChatWindow.xaml.cs의 책임을 더 줄였습니다. - 기존 메시지 bubble 조립 로직을 유지하면서 transcript host가 필요 시점에 bubble을 만들 수 있도록 helper 경로를 추가했습니다. 검증 결과: - 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:
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -7,7 +9,8 @@ namespace AxCopilot.Views;
|
||||
|
||||
public partial class ChatWindow
|
||||
{
|
||||
private readonly ObservableCollection<UIElement> _transcriptElements = [];
|
||||
private readonly ObservableCollection<TranscriptVisualItem> _transcriptElements = [];
|
||||
private readonly Dictionary<UIElement, TranscriptVisualItem> _transcriptElementMap = new();
|
||||
private ScrollViewer? _transcriptScrollViewer;
|
||||
private bool _transcriptScrollHooked;
|
||||
|
||||
@@ -55,6 +58,8 @@ public partial class ChatWindow
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string CreateTranscriptElementKey() => $"ui_{Guid.NewGuid():N}";
|
||||
|
||||
private int GetTranscriptElementCount() => _transcriptElements.Count;
|
||||
|
||||
private UIElement? GetTranscriptElementAt(int index)
|
||||
@@ -62,24 +67,73 @@ public partial class ChatWindow
|
||||
if (index < 0 || index >= _transcriptElements.Count)
|
||||
return null;
|
||||
|
||||
return _transcriptElements[index];
|
||||
return _transcriptElements[index].GetOrCreateElement();
|
||||
}
|
||||
|
||||
private int GetTranscriptElementIndex(UIElement element) => _transcriptElements.IndexOf(element);
|
||||
private int GetTranscriptElementIndex(UIElement element)
|
||||
{
|
||||
if (_transcriptElementMap.TryGetValue(element, out var item))
|
||||
return _transcriptElements.IndexOf(item);
|
||||
|
||||
private bool ContainsTranscriptElement(UIElement element) => _transcriptElements.Contains(element);
|
||||
for (var i = 0; i < _transcriptElements.Count; i++)
|
||||
{
|
||||
var candidate = _transcriptElements[i];
|
||||
if (!candidate.IsMaterialized)
|
||||
continue;
|
||||
|
||||
private void AddTranscriptElement(UIElement element) => _transcriptElements.Add(element);
|
||||
if (ReferenceEquals(candidate.Element, element))
|
||||
{
|
||||
_transcriptElementMap[element] = candidate;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearTranscriptElements() => _transcriptElements.Clear();
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void RemoveTranscriptElement(UIElement element) => _transcriptElements.Remove(element);
|
||||
private bool ContainsTranscriptElement(UIElement element) => GetTranscriptElementIndex(element) >= 0;
|
||||
|
||||
private TranscriptVisualItem WrapTranscriptElement(string key, UIElement element)
|
||||
{
|
||||
var item = new TranscriptVisualItem(key, element);
|
||||
_transcriptElementMap[element] = item;
|
||||
return item;
|
||||
}
|
||||
|
||||
private void AddTranscriptElement(UIElement element)
|
||||
=> _transcriptElements.Add(WrapTranscriptElement(CreateTranscriptElementKey(), element));
|
||||
|
||||
private void AddDeferredTranscriptElement(string key, Func<UIElement> elementFactory)
|
||||
{
|
||||
TranscriptVisualItem? item = null;
|
||||
item = new TranscriptVisualItem(
|
||||
key,
|
||||
elementFactory,
|
||||
element => _transcriptElementMap[element] = item!);
|
||||
_transcriptElements.Add(item);
|
||||
}
|
||||
|
||||
private void ClearTranscriptElements()
|
||||
{
|
||||
_transcriptElements.Clear();
|
||||
_transcriptElementMap.Clear();
|
||||
}
|
||||
|
||||
private void RemoveTranscriptElement(UIElement element)
|
||||
{
|
||||
var index = GetTranscriptElementIndex(element);
|
||||
if (index >= 0)
|
||||
RemoveTranscriptElementAt(index);
|
||||
}
|
||||
|
||||
private void RemoveTranscriptElementAt(int index)
|
||||
{
|
||||
if (index < 0 || index >= _transcriptElements.Count)
|
||||
return;
|
||||
|
||||
var item = _transcriptElements[index];
|
||||
if (item.Element != null)
|
||||
_transcriptElementMap.Remove(item.Element);
|
||||
_transcriptElements.RemoveAt(index);
|
||||
}
|
||||
|
||||
@@ -88,7 +142,11 @@ public partial class ChatWindow
|
||||
if (index < 0 || index >= _transcriptElements.Count)
|
||||
return;
|
||||
|
||||
_transcriptElements[index] = element;
|
||||
var previous = _transcriptElements[index];
|
||||
if (previous.Element != null)
|
||||
_transcriptElementMap.Remove(previous.Element);
|
||||
|
||||
_transcriptElements[index] = WrapTranscriptElement(CreateTranscriptElementKey(), element);
|
||||
}
|
||||
|
||||
private double GetTranscriptScrollableHeight() => _transcriptScrollViewer?.ScrollableHeight ?? 0;
|
||||
|
||||
Reference in New Issue
Block a user