모델 프로파일 기반 Cowork/Code 루프와 진행 UX 고도화 반영

- 등록 모델 실행 프로파일을 검증 게이트, 문서 fallback, post-tool verification까지 확장 적용

- Cowork/Code 진행 카드에 계획/도구/검증/압축/폴백/재시도 단계 메타를 추가해 대기 상태 가시성 강화

- OpenAI/vLLM tool 요청에 병렬 도구 호출 힌트를 추가하고 회귀 프롬프트 문서를 프로파일 기준으로 전면 정리

- 검증: 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:
2026-04-08 13:41:57 +09:00
parent b391dfdfb3
commit a2c952879d
552 changed files with 8094 additions and 13595 deletions

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
@@ -32,9 +32,7 @@ public partial class ChatWindow
if (conversation?.ShowExecutionHistory ?? true)
return events;
return events
.Where(ShouldShowCollapsedProgressEvent)
.ToList();
return events.Where(ShouldShowCollapsedProgressEvent).ToList();
}
private static bool ShouldShowCollapsedProgressEvent(ChatExecutionEvent executionEvent)
@@ -53,9 +51,19 @@ public partial class ChatWindow
return true;
}
// 문서 생성 ToolResult 성공 시 항상 표시 (미리보기 카드용)
if (restoredEvent.Type == AgentEventType.ToolResult && restoredEvent.Success
&& IsDocumentCreationTool(restoredEvent.ToolName))
return true;
return IsProcessFeedEvent(restoredEvent);
}
private static bool IsDocumentCreationTool(string? toolName) =>
toolName is "html_create" or "docx_create" or "excel_create" or "xlsx_create"
or "csv_create" or "markdown_create" or "md_create" or "script_create"
or "pptx_create";
private List<(DateTime Timestamp, int Order, Action Render)> BuildTimelineRenderActions(
IReadOnlyCollection<ChatMessage> visibleMessages,
IReadOnlyCollection<ChatExecutionEvent> visibleEvents)
@@ -63,22 +71,52 @@ public partial class ChatWindow
var timeline = new List<(DateTime Timestamp, int Order, Action Render)>(visibleMessages.Count + visibleEvents.Count);
foreach (var msg in visibleMessages)
timeline.Add((msg.Timestamp, 0, () => AddMessageBubble(msg.Role, msg.Content, animate: false, message: msg)));
{
var capturedMsg = msg;
var cacheKey = $"m_{msg.MsgId}";
timeline.Add((msg.Timestamp, 0, () =>
{
// 캐시된 버블이 있으면 재생성 없이 재사용 (O(1) Add vs 전체 재생성)
if (_elementCache.TryGetValue(cacheKey, out var cached))
MessagePanel.Children.Add(cached);
else
AddMessageBubble(capturedMsg.Role, capturedMsg.Content, animate: false, message: capturedMsg);
}));
}
// 현재 실행 중인 run의 process feed 이벤트를 통합 카드로 대체 (히스토리 접힘 모드)
var showFullHistory = _currentConversation?.ShowExecutionHistory ?? true;
var activeRunId = _isStreaming ? (_appState.AgentRun.RunId ?? "") : "";
foreach (var executionEvent in visibleEvents)
{
// 스트리밍 중이고 히스토리 접힘 상태일 때, 현재 run의 process feed 이벤트는 통합 카드에서 표시
if (!showFullHistory && _isStreaming
&& !string.IsNullOrEmpty(activeRunId)
&& string.Equals(executionEvent.RunId, activeRunId, StringComparison.Ordinal))
{
var restoredCheck = ToAgentEvent(executionEvent);
if (IsProcessFeedEvent(restoredCheck))
continue; // 통합 카드로 대체 — 개별 pill 스킵
}
var restoredEvent = ToAgentEvent(executionEvent);
timeline.Add((executionEvent.Timestamp, 1, () => AddAgentEventBanner(restoredEvent)));
}
// 스트리밍 중 + 히스토리 접힘: 통합 진행 카드 삽입 (개별 pill 대체)
if (!showFullHistory && _isStreaming && _currentRunProgressSteps.Count > 0)
{
var capturedSteps = _currentRunProgressSteps.ToList();
var cardTimestamp = capturedSteps[^1].Timestamp;
timeline.Add((cardTimestamp, 1, () => AddLiveRunProgressCard(capturedSteps)));
}
var liveProgressHint = GetLiveAgentProgressHint();
if (liveProgressHint != null)
timeline.Add((liveProgressHint.Timestamp, 2, () => AddAgentEventBanner(liveProgressHint)));
return timeline
.OrderBy(x => x.Timestamp)
.ThenBy(x => x.Order)
.ToList();
return timeline.OrderBy(x => x.Timestamp).ThenBy(x => x.Order).ToList();
}
private Border CreateTimelineLoadMoreCard(int hiddenCount)
@@ -198,11 +236,7 @@ public partial class ChatWindow
};
var stack = new StackPanel();
var header = new StackPanel
{
Orientation = Orientation.Horizontal,
Margin = new Thickness(0, 0, 0, 6),
};
var header = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 6) };
header.Children.Add(new TextBlock
{
Text = icon,
@@ -222,12 +256,8 @@ public partial class ChatWindow
});
stack.Children.Add(header);
var lines = (message.Content ?? "")
.Replace("\r\n", "\n")
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Trim())
.Where(line => !string.IsNullOrWhiteSpace(line))
.ToList();
var lines = (message.Content ?? "").Replace("\r\n", "\n").Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Trim()).Where(line => !string.IsNullOrWhiteSpace(line)).ToList();
foreach (var line in lines)
{