모델 프로파일 기반 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:
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user