AX Agent 코워크·코드 흐름과 컨텍스트 관리를 claude-code 기준으로 대폭 정리
- 코워크·코드 프롬프트, 도구 선택, 문서 생성/검증 흐름을 claude-code 동등 품질 기준으로 재정렬함 - OpenAI/vLLM 경로의 오래된 tool history를 평탄화하고 최근 이력만 구조화해 컨텍스트 직렬화를 경량화함 - AX Agent UI를 테마 기준으로 재구성하고 플랜 승인/오버레이/이벤트 렌더링/명령 입력 상호작용을 개선함 - 파일 후보 제안, 반복 경로 정체 복구, LSP 보강, 문서·PPT 처리 개선, 설정/서비스 인터페이스 정리를 함께 반영함 - README.md 및 docs/DEVELOPMENT.md를 작업 시점별로 갱신함 - 검증: 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:
@@ -123,8 +123,93 @@ public partial class ChatWindow
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// React Virtual DOM reconciliation 방식의 diff 렌더.
|
||||
/// Incremental(prefix-match)이 실패해도, 키 기반 diff로 삭제/추가만 처리하여
|
||||
/// 전체 재빌드(Full Render)를 회피합니다.
|
||||
/// StreamingAppend → Incremental → DiffRender → FullRender 순으로 호출됩니다.
|
||||
/// </summary>
|
||||
private bool TryApplyDiffRender(TranscriptRenderPlan renderPlan)
|
||||
{
|
||||
// 이전 렌더 기록이 없으면 diff 불가
|
||||
if (_lastRenderedTimelineKeys.Count == 0 || renderPlan.NewKeys.Count == 0)
|
||||
return false;
|
||||
|
||||
// hiddenCount가 다르면 visible 범위 자체가 달라진 것 — diff 신뢰 불가
|
||||
if (renderPlan.HiddenCount != _lastRenderedHiddenCount)
|
||||
return false;
|
||||
|
||||
var oldKeys = _lastRenderedTimelineKeys;
|
||||
var newKeys = renderPlan.NewKeys;
|
||||
|
||||
// 변화가 없으면 빠른 경로
|
||||
if (oldKeys.Count == newKeys.Count && oldKeys.SequenceEqual(newKeys, StringComparer.Ordinal))
|
||||
{
|
||||
_lastRenderedTimelineKeys = renderPlan.NewKeys;
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 기존 키 → 인덱스 매핑
|
||||
var oldKeyIndex = new Dictionary<string, int>(oldKeys.Count, StringComparer.Ordinal);
|
||||
for (var i = 0; i < oldKeys.Count; i++)
|
||||
oldKeyIndex[oldKeys[i]] = i;
|
||||
|
||||
var newKeySet = new HashSet<string>(newKeys, StringComparer.Ordinal);
|
||||
|
||||
// 2. 라이브 컨테이너 임시 분리
|
||||
var hadLiveContainer = _agentLiveContainer != null && ContainsTranscriptElement(_agentLiveContainer);
|
||||
if (hadLiveContainer)
|
||||
RemoveTranscriptElement(_agentLiveContainer!);
|
||||
|
||||
// "더보기" 카드가 있으면 오프셋 1
|
||||
var loadMoreOffset = renderPlan.HiddenCount > 0 ? 1 : 0;
|
||||
|
||||
// 3. 삭제할 항목 제거 (뒤에서부터 — 인덱스 안정성 유지)
|
||||
for (var i = oldKeys.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!newKeySet.Contains(oldKeys[i]))
|
||||
{
|
||||
var elementIndex = i + loadMoreOffset;
|
||||
if (elementIndex < GetTranscriptElementCount())
|
||||
RemoveTranscriptElementAt(elementIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 새 항목만 생성·삽입 — 이미 존재하는 키는 건너뜀
|
||||
foreach (var item in renderPlan.VisibleTimeline)
|
||||
{
|
||||
if (!oldKeyIndex.ContainsKey(item.Key))
|
||||
item.Render();
|
||||
}
|
||||
|
||||
// 5. 라이브 컨테이너 재삽입
|
||||
if (hadLiveContainer && _agentLiveContainer != null)
|
||||
AddTranscriptElement(_agentLiveContainer);
|
||||
|
||||
_lastRenderedTimelineKeys = renderPlan.NewKeys;
|
||||
_lastRenderedHiddenCount = renderPlan.HiddenCount;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Services.LogService.Warn($"Diff 렌더 실패, 전체 렌더로 전환: {ex.Message}");
|
||||
_lastRenderedTimelineKeys.Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFullTranscriptRender(TranscriptRenderPlan renderPlan)
|
||||
{
|
||||
// 스트리밍 중에는 ItemsSource 분리/재연결을 하지 않음
|
||||
// — 전체 시각적 트리 파괴 + VirtualizingStackPanel 컨테이너 재생성이 UI 렉의 핵심 원인
|
||||
// 비스트리밍 시에만 분리/재연결 (대량 초기 로드 시 레이아웃 패스 1회 축소 효과)
|
||||
var disconnectItemsSource = !_isStreaming;
|
||||
|
||||
if (disconnectItemsSource)
|
||||
MessageList.ItemsSource = null;
|
||||
|
||||
ClearTranscriptElements();
|
||||
_runBannerAnchors.Clear();
|
||||
|
||||
@@ -137,6 +222,14 @@ public partial class ChatWindow
|
||||
if (_agentLiveContainer != null && !ContainsTranscriptElement(_agentLiveContainer))
|
||||
AddTranscriptElement(_agentLiveContainer);
|
||||
|
||||
if (disconnectItemsSource)
|
||||
{
|
||||
// ItemsSource 재연결 — 단일 레이아웃 패스
|
||||
MessageList.ItemsSource = _transcriptElements;
|
||||
// ItemsSource 변경 시 ScrollViewer가 재생성될 수 있으므로 훅 재연결
|
||||
AttachTranscriptScrollChanged();
|
||||
}
|
||||
|
||||
_lastRenderedTimelineKeys = renderPlan.NewKeys;
|
||||
_lastRenderedHiddenCount = renderPlan.HiddenCount;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user