에이전트 진행 표시 구조를 claude-code식 row 기반으로 재정리
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
- AgentTranscriptDisplayCatalog를 row presentation 중심으로 재구성해 thinking/waiting/compact/tool activity/permission/tool result/status를 타입별로 분리함 - PermissionRequestPresentationCatalog와 ToolResultPresentationCatalog를 정리해 권한 요청과 결과 상태를 행위/상태 기준으로 더 명확하게 표현함 - ChatWindow.AgentEventRendering에서 process feed 계열 이벤트를 GroupKey 기준으로 병합해 append 수를 줄이고 진행 흐름이 기본 transcript에 남도록 조정함 - FooterPresentation에서 Cowork/Chat 프리셋 안내 카드가 execution event 이후 자동으로 숨겨지도록 하고 입력 워터마크와 footer 기본 문구를 정리함 - render_messages 성능 로그에 processFeed append/merge 수치와 rowKindCounts를 추가해 %APPDATA%\\AxCopilot\\perf 기준 실검증이 가능하도록 함 - 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:
@@ -2,6 +2,28 @@ using System;
|
||||
|
||||
namespace AxCopilot.Services.Agent;
|
||||
|
||||
internal enum TranscriptRowKind
|
||||
{
|
||||
AssistantText,
|
||||
Thinking,
|
||||
ToolActivity,
|
||||
Permission,
|
||||
ToolResult,
|
||||
CompactBoundary,
|
||||
Waiting,
|
||||
PlanApproval,
|
||||
Status,
|
||||
}
|
||||
|
||||
internal sealed record AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind Kind,
|
||||
string BadgeLabel,
|
||||
string Title,
|
||||
string Description,
|
||||
string GroupKey,
|
||||
bool CanGroup,
|
||||
bool Emphasize);
|
||||
|
||||
internal static class AgentTranscriptDisplayCatalog
|
||||
{
|
||||
public static string GetDisplayName(string? rawName, bool slashPrefix = false)
|
||||
@@ -15,20 +37,27 @@ internal static class AgentTranscriptDisplayCatalog
|
||||
{
|
||||
"file_read" => "파일 읽기",
|
||||
"file_write" => "파일 쓰기",
|
||||
"file_edit" => "파일 편집",
|
||||
"file_watch" => "파일 변경 감시",
|
||||
"file_edit" => "파일 수정",
|
||||
"file_watch" => "파일 감시",
|
||||
"file_info" => "파일 정보",
|
||||
"file_manage" => "파일 관리",
|
||||
"glob" => "파일 찾기",
|
||||
"grep" => "내용 검색",
|
||||
"folder_map" => "폴더 구조",
|
||||
"multi_read" => "다중 파일 읽기",
|
||||
|
||||
"document_reader" => "문서 읽기",
|
||||
"document_planner" => "문서 계획",
|
||||
"document_assembler" => "문서 조합",
|
||||
"document_read" or "document_reader" => "문서 읽기",
|
||||
"document_plan" or "document_planner" => "문서 계획",
|
||||
"document_assemble" or "document_assembler" => "문서 조합",
|
||||
"document_review" => "문서 검토",
|
||||
"format_convert" => "형식 변환",
|
||||
"template_render" => "템플릿 렌더",
|
||||
"html_create" => "HTML 생성",
|
||||
"docx_create" => "Word 생성",
|
||||
"markdown_create" or "md_create" => "Markdown 생성",
|
||||
"excel_create" or "xlsx_create" => "Excel 생성",
|
||||
"csv_create" => "CSV 생성",
|
||||
"pptx_create" => "PowerPoint 생성",
|
||||
|
||||
"build_run" => "빌드/실행",
|
||||
"test_loop" => "테스트 루프",
|
||||
@@ -42,11 +71,11 @@ internal static class AgentTranscriptDisplayCatalog
|
||||
"powershell" => "PowerShell",
|
||||
"web_fetch" => "웹 요청",
|
||||
"http" => "HTTP 요청",
|
||||
"user_ask" => "의견 요청",
|
||||
"user_ask" => "질문 요청",
|
||||
"suggest_actions" => "다음 작업 제안",
|
||||
|
||||
"task_create" => "작업 생성",
|
||||
"task_update" => "작업 업데이트",
|
||||
"task_update" => "작업 갱신",
|
||||
"task_list" => "작업 목록",
|
||||
"task_get" => "작업 조회",
|
||||
"task_stop" => "작업 중지",
|
||||
@@ -104,7 +133,7 @@ internal static class AgentTranscriptDisplayCatalog
|
||||
AgentEventType.ToolResult => evt.Success ? $"{displayName} 실행 완료" : $"{displayName} 실행 실패",
|
||||
AgentEventType.SkillCall => $"{displayName} 실행",
|
||||
AgentEventType.PermissionRequest => $"{displayName} 실행 전에 권한 확인이 필요합니다.",
|
||||
AgentEventType.PermissionGranted => $"{displayName} 실행 권한이 승인되었습니다.",
|
||||
AgentEventType.PermissionGranted => $"{displayName} 실행 권한을 확인했습니다.",
|
||||
AgentEventType.PermissionDenied => $"{displayName} 실행 권한이 거부되었습니다.",
|
||||
AgentEventType.Complete => "에이전트 작업이 완료되었습니다.",
|
||||
AgentEventType.Error => "에이전트 실행 중 오류가 발생했습니다.",
|
||||
@@ -112,6 +141,257 @@ internal static class AgentTranscriptDisplayCatalog
|
||||
};
|
||||
}
|
||||
|
||||
public static AgentTranscriptRowPresentation ResolveRowPresentation(AgentEvent evt, string itemDisplayName, string transcriptBadgeLabel)
|
||||
{
|
||||
var summary = (evt.Summary ?? string.Empty).Trim();
|
||||
var toolName = (evt.ToolName ?? string.Empty).Trim().ToLowerInvariant();
|
||||
var resultPresentation = evt.Type == AgentEventType.ToolResult
|
||||
? ToolResultPresentationCatalog.Resolve(evt, transcriptBadgeLabel)
|
||||
: null;
|
||||
var permissionPresentation = evt.Type switch
|
||||
{
|
||||
AgentEventType.PermissionRequest => PermissionRequestPresentationCatalog.Resolve(evt.ToolName, pending: true),
|
||||
AgentEventType.PermissionGranted => PermissionRequestPresentationCatalog.Resolve(evt.ToolName, pending: false),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (string.Equals(toolName, "agent_wait", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.Waiting,
|
||||
"대기",
|
||||
"처리 중...",
|
||||
string.IsNullOrWhiteSpace(summary) ? "작업을 계속 진행하기 위한 응답을 기다리는 중입니다." : summary,
|
||||
"waiting:agent",
|
||||
true,
|
||||
true);
|
||||
}
|
||||
|
||||
if (string.Equals(toolName, "context_compaction", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.CompactBoundary,
|
||||
"압축",
|
||||
"컨텍스트 압축 중...",
|
||||
string.IsNullOrWhiteSpace(summary) ? "긴 대화를 계속 진행하기 위해 컨텍스트를 정리하고 있습니다." : summary,
|
||||
"compact:context",
|
||||
true,
|
||||
true);
|
||||
}
|
||||
|
||||
if (evt.Type == AgentEventType.Planning)
|
||||
{
|
||||
var title = evt.Steps is { Count: > 0 }
|
||||
? $"계획 {evt.Steps.Count}단계 정리"
|
||||
: "작업 계획 정리 중";
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.Thinking,
|
||||
"계획",
|
||||
title,
|
||||
string.IsNullOrWhiteSpace(summary) ? "실행 순서와 필요한 도구를 정리하고 있습니다." : summary,
|
||||
"planning",
|
||||
true,
|
||||
false);
|
||||
}
|
||||
|
||||
if (evt.Type == AgentEventType.Decision)
|
||||
{
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.PlanApproval,
|
||||
"확인",
|
||||
"계획 확인 필요",
|
||||
string.IsNullOrWhiteSpace(summary) ? "사용자 확인이 필요한 계획 단계입니다." : summary,
|
||||
"plan:approval",
|
||||
false,
|
||||
true);
|
||||
}
|
||||
|
||||
if (evt.Type is AgentEventType.StepStart or AgentEventType.StepDone)
|
||||
{
|
||||
var title = evt.StepTotal > 0
|
||||
? evt.Type == AgentEventType.StepStart
|
||||
? $"{evt.StepCurrent}/{evt.StepTotal} 단계 진행"
|
||||
: $"{evt.StepCurrent}/{evt.StepTotal} 단계 완료"
|
||||
: evt.Type == AgentEventType.StepStart ? "단계 진행" : "단계 완료";
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.ToolActivity,
|
||||
"단계",
|
||||
title,
|
||||
string.IsNullOrWhiteSpace(summary) ? title : summary,
|
||||
$"step:{evt.StepCurrent}:{evt.StepTotal}:{evt.Type}",
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
if (evt.Type == AgentEventType.Thinking)
|
||||
{
|
||||
var title = ResolveThinkingTitle(summary, toolName);
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.Thinking,
|
||||
"생각",
|
||||
title,
|
||||
string.IsNullOrWhiteSpace(summary) ? title : summary,
|
||||
$"thinking:{ResolveActivityGroup(toolName, summary)}",
|
||||
true,
|
||||
false);
|
||||
}
|
||||
|
||||
if (evt.Type == AgentEventType.ToolCall || evt.Type == AgentEventType.SkillCall)
|
||||
{
|
||||
var group = ResolveActivityGroup(toolName, summary);
|
||||
var title = BuildActivityTitle(toolName, itemDisplayName, summary);
|
||||
var badge = evt.Type == AgentEventType.SkillCall ? "스킬" : "도구";
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.ToolActivity,
|
||||
badge,
|
||||
title,
|
||||
BuildActivityDescription(group, summary),
|
||||
$"activity:{group}",
|
||||
true,
|
||||
false);
|
||||
}
|
||||
|
||||
if (permissionPresentation != null)
|
||||
{
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.Permission,
|
||||
"권한",
|
||||
permissionPresentation.Label,
|
||||
permissionPresentation.Description,
|
||||
$"permission:{permissionPresentation.Kind}",
|
||||
false,
|
||||
true);
|
||||
}
|
||||
|
||||
if (resultPresentation != null)
|
||||
{
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.ToolResult,
|
||||
"결과",
|
||||
resultPresentation.Label,
|
||||
resultPresentation.Description,
|
||||
$"result:{resultPresentation.Kind}:{resultPresentation.StatusKind}",
|
||||
false,
|
||||
resultPresentation.NeedsAttention);
|
||||
}
|
||||
|
||||
if (evt.Type == AgentEventType.Error)
|
||||
{
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.Status,
|
||||
"오류",
|
||||
"실행 중 오류 발생",
|
||||
string.IsNullOrWhiteSpace(summary) ? "에이전트 실행 중 오류가 발생했습니다." : summary,
|
||||
"status:error",
|
||||
false,
|
||||
true);
|
||||
}
|
||||
|
||||
if (evt.Type == AgentEventType.Complete)
|
||||
{
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.Status,
|
||||
"완료",
|
||||
"작업 완료",
|
||||
string.IsNullOrWhiteSpace(summary) ? "에이전트 작업이 완료되었습니다." : summary,
|
||||
"status:complete",
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
return new AgentTranscriptRowPresentation(
|
||||
TranscriptRowKind.Status,
|
||||
transcriptBadgeLabel,
|
||||
string.IsNullOrWhiteSpace(summary) ? transcriptBadgeLabel : summary,
|
||||
string.IsNullOrWhiteSpace(summary) ? transcriptBadgeLabel : summary,
|
||||
$"status:{evt.Type}",
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
private static string ResolveThinkingTitle(string summary, string toolName)
|
||||
{
|
||||
if (summary.Contains("검증", StringComparison.OrdinalIgnoreCase))
|
||||
return "결과 검증 중...";
|
||||
if (summary.Contains("diff", StringComparison.OrdinalIgnoreCase))
|
||||
return "변경 내용 확인 중...";
|
||||
if (summary.Contains("retry", StringComparison.OrdinalIgnoreCase) || summary.Contains("재시도", StringComparison.OrdinalIgnoreCase))
|
||||
return "재시도 준비 중...";
|
||||
if (summary.Contains("fallback", StringComparison.OrdinalIgnoreCase) || summary.Contains("자동 생성", StringComparison.OrdinalIgnoreCase))
|
||||
return "대체 경로 준비 중...";
|
||||
if (toolName.Contains("document"))
|
||||
return "문서 흐름 정리 중...";
|
||||
return string.IsNullOrWhiteSpace(summary) ? "분석 중..." : summary;
|
||||
}
|
||||
|
||||
private static string ResolveActivityGroup(string toolName, string summary)
|
||||
{
|
||||
if (toolName is "file_read" or "document_read" or "glob" or "grep" or "folder_map" or "multi_read")
|
||||
return "read";
|
||||
if (toolName is "file_edit" or "file_write")
|
||||
return "edit";
|
||||
if (toolName.Contains("build") || toolName.Contains("test") || toolName == "process")
|
||||
return "execute";
|
||||
if (toolName.Contains("git") || toolName.Contains("diff"))
|
||||
return "git";
|
||||
if (toolName.Contains("document") || toolName.Contains("html_create") || toolName.Contains("docx_create") || toolName.Contains("markdown_create"))
|
||||
return "document";
|
||||
if (toolName.Contains("web") || toolName.Contains("http"))
|
||||
return "web";
|
||||
if (summary.Contains("권한", StringComparison.OrdinalIgnoreCase))
|
||||
return "permission";
|
||||
return "general";
|
||||
}
|
||||
|
||||
private static string BuildActivityTitle(string toolName, string itemDisplayName, string summary)
|
||||
{
|
||||
if (toolName is "multi_read")
|
||||
return "여러 파일 읽는 중";
|
||||
if (toolName is "file_read" or "document_read")
|
||||
return "파일 읽는 중";
|
||||
if (toolName is "glob")
|
||||
return "관련 파일 찾는 중";
|
||||
if (toolName is "grep")
|
||||
return "코드 검색 중";
|
||||
if (toolName is "folder_map")
|
||||
return "구조 확인 중";
|
||||
if (toolName is "file_edit")
|
||||
return "파일 수정 중";
|
||||
if (toolName is "file_write")
|
||||
return "파일 작성 중";
|
||||
if (toolName.Contains("build") || toolName.Contains("test"))
|
||||
return "빌드/테스트 실행 중";
|
||||
if (toolName == "process" || toolName == "bash" || toolName == "powershell")
|
||||
return "명령 실행 중";
|
||||
if (toolName.Contains("git") || toolName.Contains("diff"))
|
||||
return "Git 작업 중";
|
||||
if (toolName.Contains("document") || toolName.Contains("html_create") || toolName.Contains("docx_create"))
|
||||
return "문서 결과 만드는 중";
|
||||
if (toolName.Contains("web") || toolName.Contains("http"))
|
||||
return "웹 정보 확인 중";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(itemDisplayName))
|
||||
return $"{itemDisplayName} 실행 중";
|
||||
return string.IsNullOrWhiteSpace(summary) ? "도구 실행 중" : summary;
|
||||
}
|
||||
|
||||
private static string BuildActivityDescription(string group, string summary)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(summary))
|
||||
return summary;
|
||||
|
||||
return group switch
|
||||
{
|
||||
"read" => "질문과 관련된 파일과 내용만 추려서 확인하고 있습니다.",
|
||||
"edit" => "필요한 변경만 적용하고 결과를 다시 확인하고 있습니다.",
|
||||
"execute" => "실행 결과와 로그를 확인해 다음 단계를 판단하고 있습니다.",
|
||||
"git" => "변경 범위와 저장소 상태를 확인하고 있습니다.",
|
||||
"document" => "문서 산출물을 준비하고 있습니다.",
|
||||
"web" => "필요한 외부 정보를 확인하고 있습니다.",
|
||||
_ => "다음 단계를 진행하기 위한 작업을 실행하고 있습니다."
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetToolCategoryLabel(string? rawName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawName))
|
||||
@@ -119,13 +399,13 @@ internal static class AgentTranscriptDisplayCatalog
|
||||
|
||||
return rawName.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"file_read" or "file_write" or "file_edit" or "glob" or "grep" or "folder_map" or "file_watch" or "file_info" or "file_manage"
|
||||
"file_read" or "file_write" or "file_edit" or "glob" or "grep" or "folder_map" or "file_watch" or "file_info" or "file_manage" or "multi_read"
|
||||
=> "파일",
|
||||
"build_run" or "test_loop" or "dev_env_detect"
|
||||
=> "빌드",
|
||||
"git_tool" or "diff_tool" or "diff_preview"
|
||||
=> "Git",
|
||||
"document_reader" or "document_planner" or "document_assembler" or "document_review" or "format_convert" or "template_render"
|
||||
"document_read" or "document_reader" or "document_plan" or "document_planner" or "document_assemble" or "document_assembler" or "document_review" or "format_convert" or "template_render" or "html_create" or "docx_create"
|
||||
=> "문서",
|
||||
"user_ask"
|
||||
=> "질문",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace AxCopilot.Services.Agent;
|
||||
|
||||
internal sealed record PermissionRequestPresentation(
|
||||
@@ -20,104 +22,104 @@ internal static class PermissionRequestPresentationCatalog
|
||||
if (tool.Contains("bash"))
|
||||
return Build("bash", pending, "\uE756",
|
||||
"Bash 실행 권한 요청", "Bash 실행 확인",
|
||||
"셸 명령을 실행하기 전에 확인이 필요합니다.",
|
||||
"Bash 실행이 승인되어 계속 진행합니다.",
|
||||
"명령과 작업 위치를 확인하세요.",
|
||||
"Bash 명령을 실행하기 전에 사용자 확인이 필요합니다.",
|
||||
"Bash 실행을 확인해 주시면 같은 흐름으로 이어집니다.",
|
||||
"명령과 작업 위치를 먼저 확인해 주세요.",
|
||||
"#FEF2F2", "#DC2626", "high", false);
|
||||
|
||||
if (tool.Contains("powershell"))
|
||||
return Build("powershell", pending, "\uE756",
|
||||
"PowerShell 권한 요청", "PowerShell 실행 확인",
|
||||
"PowerShell 명령을 실행하기 전에 확인이 필요합니다.",
|
||||
"PowerShell 실행이 승인되어 계속 진행합니다.",
|
||||
"스크립트와 실행 범위를 확인하세요.",
|
||||
"PowerShell 명령을 실행하기 전에 사용자 확인이 필요합니다.",
|
||||
"PowerShell 실행을 승인하면 같은 작업을 이어서 진행합니다.",
|
||||
"스크립트와 실행 범위를 확인해 주세요.",
|
||||
"#FEF2F2", "#DC2626", "high", false);
|
||||
|
||||
if (tool.Contains("process") || tool.Contains("build") || tool.Contains("test"))
|
||||
return Build("command", pending, "\uE756",
|
||||
"명령 실행 권한 요청", "명령 실행 확인",
|
||||
"명령 또는 빌드 작업 실행 전에 확인이 필요합니다.",
|
||||
"명령 실행이 승인되어 계속 진행합니다.",
|
||||
"실행 명령과 영향 범위를 확인하세요.",
|
||||
"명령이나 빌드 작업을 실행하기 전에 확인이 필요합니다.",
|
||||
"명령 실행을 승인하면 현재 흐름을 이어서 진행합니다.",
|
||||
"실행 명령과 영향 범위를 먼저 확인해 주세요.",
|
||||
"#FEF2F2", "#DC2626", "high", false);
|
||||
|
||||
if (tool.Contains("web") || tool.Contains("fetch") || tool.Contains("http"))
|
||||
return Build("web_fetch", pending, "\uE774",
|
||||
"웹 요청 권한 요청", "웹 요청 확인",
|
||||
"외부 요청을 보내기 전에 확인이 필요합니다.",
|
||||
"웹 요청이 승인되어 계속 진행합니다.",
|
||||
"조회 URL과 전송 범위를 확인하세요.",
|
||||
"외부 웹 요청을 보내기 전에 확인이 필요합니다.",
|
||||
"요청 대상을 확인해 주시면 이어서 처리합니다.",
|
||||
"조회 URL과 전송 범위를 확인해 주세요.",
|
||||
"#FFF7ED", "#C2410C", "medium", false);
|
||||
|
||||
if (tool.Contains("mcp"))
|
||||
return Build("mcp", pending, "\uE943",
|
||||
"MCP 도구 권한 요청", "MCP 도구 확인",
|
||||
"연결된 MCP 도구 사용 전에 확인이 필요합니다.",
|
||||
"MCP 도구 사용이 승인되어 계속 진행합니다.",
|
||||
"서버와 호출 의도를 확인하세요.",
|
||||
"연결된 MCP 도구를 사용하기 전에 확인이 필요합니다.",
|
||||
"MCP 도구 사용을 승인하면 흐름을 이어서 진행합니다.",
|
||||
"서버와 호출 도구를 확인해 주세요.",
|
||||
"#F5F3FF", "#7C3AED", "medium", false);
|
||||
|
||||
if (tool.Contains("skill"))
|
||||
return Build("skill", pending, "\uE8A5",
|
||||
"스킬 실행 권한 요청", "스킬 실행 확인",
|
||||
"연결된 스킬을 실행하기 전에 확인이 필요합니다.",
|
||||
"스킬 실행이 승인되어 계속 진행합니다.",
|
||||
"허용 도구와 실행 컨텍스트를 확인하세요.",
|
||||
"스킬 실행을 승인하면 같은 작업 흐름을 이어갑니다.",
|
||||
"사용 도구와 실행 컨텍스트를 확인해 주세요.",
|
||||
"#F5F3FF", "#7C3AED", "medium", false);
|
||||
|
||||
if (tool.Contains("ask"))
|
||||
return Build("question", pending, "\uE897",
|
||||
"의견 요청 확인", "의견 요청 완료",
|
||||
"사용자에게 선택이나 답변을 요청합니다.",
|
||||
"사용자 답변을 받아 다음 단계로 진행합니다.",
|
||||
"질문 의도와 선택지를 확인하세요.",
|
||||
"질문 요청 확인", "질문 요청 완료",
|
||||
"사용자에게 추가 질문이나 선택을 요청합니다.",
|
||||
"응답을 받으면 다음 단계로 자동으로 이어집니다.",
|
||||
"질문 의도와 선택지를 확인해 주세요.",
|
||||
"#EFF6FF", "#2563EB", "low", false);
|
||||
|
||||
if (tool.Contains("file_edit") || tool.Contains("edit"))
|
||||
return Build("file_edit", pending, "\uE70F",
|
||||
"파일 수정 권한 요청", "파일 수정 확인",
|
||||
"파일을 변경하기 전에 확인이 필요합니다.",
|
||||
"파일 수정이 승인되어 계속 진행합니다.",
|
||||
"변경 diff와 대상 파일을 확인하세요.",
|
||||
"파일을 변경하기 전에 사용자 확인이 필요합니다.",
|
||||
"변경 내용을 확인하면 같은 흐름으로 이어집니다.",
|
||||
"변경 diff와 대상 파일을 먼저 확인해 주세요.",
|
||||
"#FFF7ED", "#C2410C", "high", true);
|
||||
|
||||
if (tool.Contains("file_write") || tool.Contains("write"))
|
||||
return Build("file_write", pending, "\uE70F",
|
||||
"파일 쓰기 권한 요청", "파일 쓰기 확인",
|
||||
"새 파일 작성 또는 덮어쓰기 전에 확인이 필요합니다.",
|
||||
"파일 쓰기가 승인되어 계속 진행합니다.",
|
||||
"작성 위치와 새 내용 미리보기를 확인하세요.",
|
||||
"새 파일 생성이나 덮어쓰기 전에 사용자 확인이 필요합니다.",
|
||||
"파일 쓰기를 승인하면 같은 흐름을 이어갑니다.",
|
||||
"생성 위치와 내용 미리보기를 확인해 주세요.",
|
||||
"#FFF7ED", "#C2410C", "high", true);
|
||||
|
||||
if (tool.Contains("git"))
|
||||
return Build("git", pending, "\uE8A7",
|
||||
"Git 작업 권한 요청", "Git 작업 확인",
|
||||
"브랜치나 커밋 상태를 바꾸기 전에 확인이 필요합니다.",
|
||||
"Git 작업이 승인되어 계속 진행합니다.",
|
||||
"브랜치와 변경 범위를 확인하세요.",
|
||||
"브랜치나 작업트리 상태를 바꾸기 전에 확인이 필요합니다.",
|
||||
"Git 작업을 승인하면 같은 흐름을 이어갑니다.",
|
||||
"브랜치와 변경 범위를 확인해 주세요.",
|
||||
"#EFF6FF", "#2563EB", "medium", false);
|
||||
|
||||
if (tool.Contains("document") || tool.Contains("template") || tool.Contains("format"))
|
||||
return Build("document", pending, "\uE8A5",
|
||||
"문서 작업 권한 요청", "문서 작업 확인",
|
||||
"문서 생성 또는 변환 작업 전에 확인이 필요합니다.",
|
||||
"문서 작업이 승인되어 계속 진행합니다.",
|
||||
"출력 형식과 저장 위치를 확인하세요.",
|
||||
"문서 생성이나 변환 작업 전에 확인이 필요합니다.",
|
||||
"문서 작업을 승인하면 같은 흐름을 이어갑니다.",
|
||||
"출력 형식과 대상 위치를 확인해 주세요.",
|
||||
"#FFF7ED", "#C2410C", "medium", false);
|
||||
|
||||
if (tool.Contains("file") || tool.Contains("glob") || tool.Contains("grep") || tool.Contains("folder"))
|
||||
return Build("filesystem", pending, "\uE8A5",
|
||||
"파일 접근 권한 요청", "파일 접근 확인",
|
||||
"폴더나 파일 내용을 읽기 전에 확인이 필요합니다.",
|
||||
"파일 접근이 승인되어 계속 진행합니다.",
|
||||
"읽기 범위와 접근 경로를 확인하세요.",
|
||||
"파일이나 폴더를 읽기 전에 확인이 필요합니다.",
|
||||
"파일 접근을 승인하면 같은 흐름으로 이어집니다.",
|
||||
"읽기 범위와 경로를 확인해 주세요.",
|
||||
"#FFF7ED", "#C2410C", "medium", false);
|
||||
|
||||
return Build("generic", pending, "\uE897",
|
||||
"권한 요청", "권한 확인",
|
||||
"계속 진행하기 전에 사용자의 확인이 필요합니다.",
|
||||
"요청이 승인되어 계속 진행합니다.",
|
||||
"실행 의도와 대상 범위를 확인하세요.",
|
||||
"계속 진행하기 전에 사용자 확인이 필요합니다.",
|
||||
"요청을 승인하면 같은 흐름으로 이어집니다.",
|
||||
"실행 의도와 대상 범위를 확인해 주세요.",
|
||||
"#FFF7ED", "#C2410C", "medium", false);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace AxCopilot.Services.Agent;
|
||||
|
||||
internal sealed record ToolResultPresentation(
|
||||
@@ -29,7 +31,7 @@ internal static class ToolResultPresentationCatalog
|
||||
"\uE711",
|
||||
$"{baseLabel} 취소",
|
||||
"요청이 중단되어 결과가 취소되었습니다.",
|
||||
"필요하면 같은 요청을 다시 실행하세요.",
|
||||
"필요하면 같은 요청을 다시 실행할 수 있습니다.",
|
||||
"#F8FAFC",
|
||||
"#475569",
|
||||
"cancel",
|
||||
@@ -45,7 +47,7 @@ internal static class ToolResultPresentationCatalog
|
||||
"\uE783",
|
||||
$"{baseLabel} 거부",
|
||||
"권한이 거부되어 작업이 중단되었습니다.",
|
||||
"권한 모드를 바꾸거나 다시 승인하면 이어서 진행할 수 있습니다.",
|
||||
"권한 모드를 바꾸거나 다시 확인하면 이어서 진행할 수 있습니다.",
|
||||
"#FEF2F2",
|
||||
"#DC2626",
|
||||
"reject",
|
||||
@@ -58,8 +60,8 @@ internal static class ToolResultPresentationCatalog
|
||||
return new ToolResultPresentation(
|
||||
kind,
|
||||
"\uE8D7",
|
||||
$"{baseLabel} 승인 대기",
|
||||
"다음 단계로 진행하려면 사용자 승인이 필요합니다.",
|
||||
$"{baseLabel} 승인 필요",
|
||||
"다음 단계로 진행하려면 사용자 승인이나 확인이 필요합니다.",
|
||||
"승인 후 같은 작업 흐름이 이어집니다.",
|
||||
"#FFF7ED",
|
||||
"#C2410C",
|
||||
@@ -74,8 +76,8 @@ internal static class ToolResultPresentationCatalog
|
||||
kind,
|
||||
"\uE7BA",
|
||||
$"{baseLabel} 부분 완료",
|
||||
"일부 단계만 완료되어 후속 확인이나 재실행이 필요할 수 있습니다.",
|
||||
"남은 단계나 누락된 결과를 확인하세요.",
|
||||
"일부 단계만 완료되어 후속 확인이나 재시도가 필요할 수 있습니다.",
|
||||
"후속 단계와 파일 결과를 확인해 주세요.",
|
||||
"#FFFBEA",
|
||||
"#A16207",
|
||||
"partial",
|
||||
@@ -114,7 +116,7 @@ internal static class ToolResultPresentationCatalog
|
||||
return "file_edit";
|
||||
if (tool.Contains("file_write"))
|
||||
return "file_write";
|
||||
if (tool.Contains("file_read") || tool.Contains("glob") || tool.Contains("grep"))
|
||||
if (tool.Contains("file_read") || tool.Contains("glob") || tool.Contains("grep") || tool.Contains("folder_map") || tool.Contains("multi_read"))
|
||||
return "filesystem";
|
||||
if (tool.Contains("file"))
|
||||
return "file";
|
||||
@@ -141,111 +143,93 @@ internal static class ToolResultPresentationCatalog
|
||||
return "generic";
|
||||
}
|
||||
|
||||
private static string BuildSuccessLabel(string kind, string baseLabel)
|
||||
private static string BuildSuccessLabel(string kind, string baseLabel) => kind switch
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
"file_edit" => "파일 수정 완료",
|
||||
"file_write" => "파일 쓰기 완료",
|
||||
"filesystem" => "파일 탐색 완료",
|
||||
"file" => "파일 작업 완료",
|
||||
"build_test" => "빌드/테스트 완료",
|
||||
"git" => "Git 작업 완료",
|
||||
"document" => "문서 작업 완료",
|
||||
"skill" => "스킬 실행 완료",
|
||||
"mcp" => "MCP 도구 완료",
|
||||
"question" => "의견 요청 완료",
|
||||
"web" => "웹 요청 완료",
|
||||
"command" => "명령 실행 완료",
|
||||
_ => baseLabel,
|
||||
};
|
||||
}
|
||||
"file_edit" => "파일 수정 완료",
|
||||
"file_write" => "파일 쓰기 완료",
|
||||
"filesystem" => "파일 탐색 완료",
|
||||
"file" => "파일 작업 완료",
|
||||
"build_test" => "빌드/테스트 완료",
|
||||
"git" => "Git 작업 완료",
|
||||
"document" => "문서 작업 완료",
|
||||
"skill" => "스킬 실행 완료",
|
||||
"mcp" => "MCP 도구 완료",
|
||||
"question" => "질문 요청 완료",
|
||||
"web" => "웹 요청 완료",
|
||||
"command" => "명령 실행 완료",
|
||||
_ => baseLabel,
|
||||
};
|
||||
|
||||
private static string BuildFailureLabel(string kind, string baseLabel)
|
||||
private static string BuildFailureLabel(string kind, string baseLabel) => kind switch
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
"file_edit" => "파일 수정 실패",
|
||||
"file_write" => "파일 쓰기 실패",
|
||||
"filesystem" => "파일 탐색 실패",
|
||||
"file" => "파일 작업 실패",
|
||||
"build_test" => "빌드/테스트 실패",
|
||||
"git" => "Git 작업 실패",
|
||||
"document" => "문서 작업 실패",
|
||||
"skill" => "스킬 실행 실패",
|
||||
"mcp" => "MCP 도구 실패",
|
||||
"question" => "의견 요청 실패",
|
||||
"web" => "웹 요청 실패",
|
||||
"command" => "명령 실행 실패",
|
||||
_ => $"{baseLabel} 실패",
|
||||
};
|
||||
}
|
||||
"file_edit" => "파일 수정 실패",
|
||||
"file_write" => "파일 쓰기 실패",
|
||||
"filesystem" => "파일 탐색 실패",
|
||||
"file" => "파일 작업 실패",
|
||||
"build_test" => "빌드/테스트 실패",
|
||||
"git" => "Git 작업 실패",
|
||||
"document" => "문서 작업 실패",
|
||||
"skill" => "스킬 실행 실패",
|
||||
"mcp" => "MCP 도구 실패",
|
||||
"question" => "질문 요청 실패",
|
||||
"web" => "웹 요청 실패",
|
||||
"command" => "명령 실행 실패",
|
||||
_ => $"{baseLabel} 실패",
|
||||
};
|
||||
|
||||
private static string BuildSuccessDescription(string kind)
|
||||
private static string BuildSuccessDescription(string kind) => kind switch
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
"file_edit" => "파일 수정 결과가 저장되었습니다.",
|
||||
"file_write" => "새 파일 작성 결과가 저장되었습니다.",
|
||||
"filesystem" => "파일과 폴더 정보를 성공적으로 읽었습니다.",
|
||||
"file" => "파일 관련 작업이 정상적으로 끝났습니다.",
|
||||
"build_test" => "빌드 또는 테스트 단계가 성공적으로 끝났습니다.",
|
||||
"git" => "Git 관련 작업이 정상적으로 끝났습니다.",
|
||||
"document" => "문서 생성 또는 변환 작업이 완료되었습니다.",
|
||||
"skill" => "선택한 스킬이 정상적으로 실행되었습니다.",
|
||||
"mcp" => "등록된 MCP 도구 호출이 성공적으로 끝났습니다.",
|
||||
"question" => "사용자 응답을 받아 다음 단계로 넘어갈 수 있습니다.",
|
||||
"web" => "웹 요청이 정상적으로 끝났습니다.",
|
||||
"command" => "명령 실행이 정상적으로 끝났습니다.",
|
||||
_ => "요청한 작업이 정상적으로 완료되었습니다.",
|
||||
};
|
||||
}
|
||||
"file_edit" => "파일 수정 결과가 정상적으로 반영되었습니다.",
|
||||
"file_write" => "새 파일 생성이나 쓰기 작업이 완료되었습니다.",
|
||||
"filesystem" => "질문과 관련된 파일과 구조 정보를 찾았습니다.",
|
||||
"file" => "파일 관련 작업이 정상적으로 끝났습니다.",
|
||||
"build_test" => "빌드나 테스트 단계가 정상적으로 끝났습니다.",
|
||||
"git" => "저장소 상태 확인 또는 변경 작업이 완료되었습니다.",
|
||||
"document" => "문서 산출물 생성 또는 조립이 완료되었습니다.",
|
||||
"skill" => "선택한 스킬이 정상적으로 실행되었습니다.",
|
||||
"mcp" => "등록된 MCP 도구 호출이 완료되었습니다.",
|
||||
"question" => "사용자 응답을 받아 다음 단계로 이어갈 수 있습니다.",
|
||||
"web" => "필요한 웹 정보 조회가 완료되었습니다.",
|
||||
"command" => "명령 실행이 완료되어 결과를 확인할 수 있습니다.",
|
||||
_ => "요청한 작업이 정상적으로 완료되었습니다.",
|
||||
};
|
||||
|
||||
private static string BuildFailureDescription(string kind)
|
||||
private static string BuildFailureDescription(string kind) => kind switch
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
"file_edit" => "파일 변경 과정에서 문제가 발생했습니다.",
|
||||
"file_write" => "파일 작성 또는 저장 과정에서 문제가 발생했습니다.",
|
||||
"filesystem" => "파일/폴더 접근 중 문제가 발생했습니다.",
|
||||
"file" => "파일 처리 중 문제가 발생했습니다.",
|
||||
"build_test" => "빌드 또는 테스트 단계에서 실패가 발생했습니다.",
|
||||
"git" => "Git 관련 작업이 실패했습니다.",
|
||||
"document" => "문서 생성 또는 변환 작업이 실패했습니다.",
|
||||
"skill" => "스킬 실행 중 문제가 발생했습니다.",
|
||||
"mcp" => "MCP 도구 호출 중 문제가 발생했습니다.",
|
||||
"question" => "사용자 의견 요청 과정에서 문제가 발생했습니다.",
|
||||
"web" => "웹 요청 처리에 실패했습니다.",
|
||||
"command" => "명령 실행 중 오류가 발생했습니다.",
|
||||
_ => "작업 처리 중 오류가 발생했습니다.",
|
||||
};
|
||||
}
|
||||
"file_edit" => "파일 변경 과정에서 문제가 발생했습니다.",
|
||||
"file_write" => "파일 생성 또는 쓰기 과정에서 문제가 발생했습니다.",
|
||||
"filesystem" => "파일이나 폴더를 확인하는 과정에서 문제가 발생했습니다.",
|
||||
"file" => "파일 처리 중 문제가 발생했습니다.",
|
||||
"build_test" => "빌드 또는 테스트 단계에서 실패가 발생했습니다.",
|
||||
"git" => "Git 작업 중 오류가 발생했습니다.",
|
||||
"document" => "문서 생성 또는 조합 과정이 실패했습니다.",
|
||||
"skill" => "스킬 실행 중 문제가 발생했습니다.",
|
||||
"mcp" => "MCP 도구 호출 중 문제가 발생했습니다.",
|
||||
"question" => "사용자 질문/응답 처리 단계에서 문제가 발생했습니다.",
|
||||
"web" => "웹 요청 처리에 실패했습니다.",
|
||||
"command" => "명령 실행 중 오류가 발생했습니다.",
|
||||
_ => "작업 처리 중 오류가 발생했습니다.",
|
||||
};
|
||||
|
||||
private static string BuildSuccessFollowUp(string kind)
|
||||
private static string BuildSuccessFollowUp(string kind) => kind switch
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
"file_edit" or "file_write" => "변경 내용을 preview나 diff에서 다시 확인할 수 있습니다.",
|
||||
"build_test" => "출력 로그와 후속 수정 필요 여부를 확인하세요.",
|
||||
"git" => "브랜치 상태나 변경 요약을 이어서 확인하세요.",
|
||||
"document" => "생성된 산출물 경로를 열어 결과를 확인하세요.",
|
||||
"skill" => "같은 스킬을 다른 입력으로 이어서 실행할 수 있습니다.",
|
||||
_ => "필요하면 후속 요청을 이어서 실행할 수 있습니다.",
|
||||
};
|
||||
}
|
||||
"file_edit" or "file_write" => "변경 내용은 preview나 diff에서 다시 확인할 수 있습니다.",
|
||||
"build_test" => "출력 로그와 후속 수정 필요 여부를 확인해 주세요.",
|
||||
"git" => "브랜치와 변경 수치를 이어서 확인해 주세요.",
|
||||
"document" => "생성된 산출물 경로를 열어 결과를 확인해 주세요.",
|
||||
"skill" => "같은 스킬을 다른 입력으로 이어서 실행할 수 있습니다.",
|
||||
_ => "필요하면 다음 요청으로 이어서 작업할 수 있습니다.",
|
||||
};
|
||||
|
||||
private static string BuildFailureFollowUp(string kind)
|
||||
private static string BuildFailureFollowUp(string kind) => kind switch
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
"file_edit" or "file_write" => "대상 파일 경로와 권한, diff를 다시 확인하세요.",
|
||||
"build_test" => "실패 로그와 컴파일 오류 메시지를 먼저 확인하세요.",
|
||||
"git" => "현재 브랜치, 잠금 상태, 충돌 여부를 확인하세요.",
|
||||
"document" => "입력 데이터와 출력 형식, 저장 위치를 다시 확인하세요.",
|
||||
"skill" => "허용 도구와 런타임 요구사항을 다시 확인하세요.",
|
||||
"web" => "연결 상태와 요청 대상 URL을 다시 확인하세요.",
|
||||
"mcp" => "MCP 서버 연결 상태와 도구 등록 상태를 다시 확인하세요.",
|
||||
_ => "같은 요청을 재시도하기 전에 원인 메시지를 먼저 확인하세요.",
|
||||
};
|
||||
}
|
||||
"file_edit" or "file_write" => "대상 파일 경로와 권한, diff 결과를 다시 확인해 주세요.",
|
||||
"build_test" => "실패 로그와 컴파일/테스트 오류 메시지를 먼저 확인해 주세요.",
|
||||
"git" => "브랜치 상태와 충돌 여부를 다시 확인해 주세요.",
|
||||
"document" => "입력 데이터, 출력 형식, 대상 위치를 다시 확인해 주세요.",
|
||||
"skill" => "사용 도구와 스킬 요구사항을 다시 확인해 주세요.",
|
||||
"web" => "연결 상태와 요청 URL을 다시 확인해 주세요.",
|
||||
"mcp" => "MCP 서버 연결 상태와 도구 등록 상태를 다시 확인해 주세요.",
|
||||
_ => "같은 요청을 다시 시도하기 전에 원인 메시지를 먼저 확인해 주세요.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ namespace AxCopilot.Views;
|
||||
|
||||
public partial class ChatWindow
|
||||
{
|
||||
private string? _lastGroupedProcessFeedKey;
|
||||
private int _lastGroupedProcessFeedIndex = -1;
|
||||
private int _processFeedAppendCount;
|
||||
private int _processFeedMergeCount;
|
||||
private readonly Dictionary<TranscriptRowKind, int> _transcriptRowKindCounts = new();
|
||||
|
||||
private static Color ResolveLiveProgressAccentColor(Brush accentBrush)
|
||||
{
|
||||
return accentBrush is SolidColorBrush solid
|
||||
@@ -164,8 +170,24 @@ public partial class ChatWindow
|
||||
};
|
||||
}
|
||||
|
||||
private void AddProcessFeedMessage(AgentEvent evt, string transcriptBadgeLabel, string itemDisplayName, string? eventSummaryText)
|
||||
private void ResetProcessFeedGrouping()
|
||||
{
|
||||
_lastGroupedProcessFeedKey = null;
|
||||
_lastGroupedProcessFeedIndex = -1;
|
||||
}
|
||||
|
||||
private void TrackTranscriptRowKind(TranscriptRowKind kind)
|
||||
{
|
||||
if (_transcriptRowKindCounts.TryGetValue(kind, out var count))
|
||||
_transcriptRowKindCounts[kind] = count + 1;
|
||||
else
|
||||
_transcriptRowKindCounts[kind] = 1;
|
||||
}
|
||||
|
||||
private void AddProcessFeedMessage(AgentEvent evt, AgentTranscriptRowPresentation rowPresentation, string? eventSummaryText)
|
||||
{
|
||||
TrackTranscriptRowKind(rowPresentation.Kind);
|
||||
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.DimGray;
|
||||
var hintBg = TryFindResource("HintBackground") as Brush
|
||||
@@ -174,9 +196,9 @@ public partial class ChatWindow
|
||||
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
|
||||
var processMeta = BuildReadableProgressMetaText(evt);
|
||||
var summary = BuildReadableProcessFeedSummary(evt, transcriptBadgeLabel, itemDisplayName).Trim();
|
||||
var summary = rowPresentation.Title.Trim();
|
||||
if (string.IsNullOrWhiteSpace(summary))
|
||||
summary = transcriptBadgeLabel;
|
||||
summary = rowPresentation.BadgeLabel;
|
||||
|
||||
var msgMaxWidth = GetMessageMaxWidth();
|
||||
var stack = new StackPanel
|
||||
@@ -203,7 +225,7 @@ public partial class ChatWindow
|
||||
ApplyLiveWaitingPulseToMarker(pulseMarker);
|
||||
stack.Children.Add(summaryRow);
|
||||
|
||||
var body = (eventSummaryText ?? string.Empty).Trim();
|
||||
var body = (string.IsNullOrWhiteSpace(eventSummaryText) ? rowPresentation.Description : eventSummaryText ?? string.Empty).Trim();
|
||||
if (!string.IsNullOrWhiteSpace(body)
|
||||
&& !string.Equals(body, summary, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -241,7 +263,21 @@ public partial class ChatWindow
|
||||
stack.Children.Add(compactPathRow);
|
||||
}
|
||||
|
||||
AddTranscriptElement(stack);
|
||||
if (rowPresentation.CanGroup &&
|
||||
!string.IsNullOrWhiteSpace(rowPresentation.GroupKey) &&
|
||||
string.Equals(_lastGroupedProcessFeedKey, rowPresentation.GroupKey, StringComparison.Ordinal) &&
|
||||
_lastGroupedProcessFeedIndex >= 0)
|
||||
{
|
||||
ReplaceTranscriptElement(_lastGroupedProcessFeedIndex, stack);
|
||||
_processFeedMergeCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
AddTranscriptElement(stack);
|
||||
_lastGroupedProcessFeedIndex = Math.Max(0, GetTranscriptElementCount() - 1);
|
||||
_lastGroupedProcessFeedKey = rowPresentation.CanGroup ? rowPresentation.GroupKey : null;
|
||||
_processFeedAppendCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyLiveWaitingPulse(Border summaryRow)
|
||||
@@ -1151,6 +1187,9 @@ public partial class ChatWindow
|
||||
var toolResultPresentation = evt.Type == AgentEventType.ToolResult
|
||||
? ToolResultPresentationCatalog.Resolve(evt, transcriptBadgeLabel)
|
||||
: null;
|
||||
var rowPresentation = AgentTranscriptDisplayCatalog.ResolveRowPresentation(evt, itemDisplayName: evt.Type == AgentEventType.SkillCall
|
||||
? GetAgentItemDisplayName(evt.ToolName, slashPrefix: true)
|
||||
: GetAgentItemDisplayName(evt.ToolName), transcriptBadgeLabel);
|
||||
|
||||
var (icon, label, bgHex, fgHex) = isTotalStats
|
||||
? ("\uE9D2", "전체 통계", "#F3EEFF", "#7C3AED")
|
||||
@@ -1179,11 +1218,39 @@ public partial class ChatWindow
|
||||
{
|
||||
eventSummaryText = evt.Type switch
|
||||
{
|
||||
AgentEventType.PermissionRequest or AgentEventType.PermissionGranted => permissionPresentation?.Description ?? "",
|
||||
AgentEventType.ToolResult => toolResultPresentation?.Description ?? "",
|
||||
_ => ""
|
||||
AgentEventType.PermissionRequest or AgentEventType.PermissionGranted => permissionPresentation?.Description ?? rowPresentation.Description,
|
||||
AgentEventType.ToolResult => toolResultPresentation?.Description ?? rowPresentation.Description,
|
||||
_ => rowPresentation.Description
|
||||
};
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(rowPresentation.Description) == false &&
|
||||
string.Equals(eventSummaryText, evt.Summary, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
eventSummaryText = rowPresentation.Description;
|
||||
}
|
||||
|
||||
if (IsProcessFeedEvent(evt))
|
||||
{
|
||||
AddProcessFeedMessage(evt, rowPresentation, eventSummaryText);
|
||||
return;
|
||||
}
|
||||
|
||||
ResetProcessFeedGrouping();
|
||||
TrackTranscriptRowKind(rowPresentation.Kind);
|
||||
|
||||
if (rowPresentation.Kind == TranscriptRowKind.ToolResult && toolResultPresentation != null)
|
||||
{
|
||||
eventSummaryText = rowPresentation.Description;
|
||||
}
|
||||
else if (rowPresentation.Kind == TranscriptRowKind.Permission && permissionPresentation != null)
|
||||
{
|
||||
eventSummaryText = rowPresentation.Description;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(label) && !string.IsNullOrWhiteSpace(rowPresentation.BadgeLabel))
|
||||
label = rowPresentation.BadgeLabel;
|
||||
if (string.IsNullOrWhiteSpace(eventSummaryText))
|
||||
eventSummaryText = rowPresentation.Description;
|
||||
|
||||
// HTML/대용량 파일 내용이 이벤트 요약에 포함된 경우 인라인 표시 대신 1줄 요약으로 축소
|
||||
if (!string.IsNullOrWhiteSpace(eventSummaryText) && evt.Type == AgentEventType.ToolResult
|
||||
@@ -1212,12 +1279,6 @@ public partial class ChatWindow
|
||||
if (evt.Type == AgentEventType.StepStart && evt.StepTotal > 0)
|
||||
UpdateProgressBar(evt);
|
||||
|
||||
if (IsProcessFeedEvent(evt))
|
||||
{
|
||||
AddProcessFeedMessage(evt, transcriptBadgeLabel, itemDisplayName, eventSummaryText);
|
||||
return;
|
||||
}
|
||||
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.DimGray;
|
||||
var hintBg = TryFindResource("HintBackground") as Brush ?? BrushFromHex("#F8FAFC");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -47,9 +47,9 @@ public partial class ChatWindow
|
||||
return preset.Description.Trim();
|
||||
|
||||
if (string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase))
|
||||
return "선택한 작업 유형에 맞춰 문서·데이터·파일 작업 흐름으로 이어집니다.";
|
||||
return "선택된 작업 유형에 맞춰 문서, 데이터, 파일 작업 흐름으로 이어집니다.";
|
||||
|
||||
return "선택한 대화 주제에 맞춰 응답 방향과 초안 흐름을 정리합니다.";
|
||||
return "선택된 대화 주제에 맞춰 응답 방향과 초안 흐름을 정리합니다.";
|
||||
}
|
||||
|
||||
private void UpdateFolderBar()
|
||||
@@ -74,7 +74,7 @@ public partial class ChatWindow
|
||||
}
|
||||
else
|
||||
{
|
||||
FolderPathLabel.Text = "폴더를 선택하세요";
|
||||
FolderPathLabel.Text = "폴더를 선택하세요.";
|
||||
FolderPathLabel.ToolTip = null;
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ public partial class ChatWindow
|
||||
memory.Load(workFolder);
|
||||
var docs = memory.InstructionDocuments;
|
||||
var learned = memory.All.Count;
|
||||
var includePolicy = _settings.Settings.Llm.AllowExternalMemoryIncludes ? "외부 include 허용" : "외부 include 차단";
|
||||
var includePolicy = _settings.Settings.Llm.AllowExternalMemoryIncludes ? "?몃? include ?덉슜" : "?몃? include 李⑤떒";
|
||||
var auditEnabled = _settings.Settings.Llm.EnableAuditLog;
|
||||
var recentIncludeEntries = AuditLogService.LoadRecent("MemoryInclude", maxCount: 5, daysBack: 3);
|
||||
|
||||
@@ -143,7 +143,7 @@ public partial class ChatWindow
|
||||
var panel = new StackPanel { Margin = new Thickness(2) };
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "메모리 상태",
|
||||
Text = "硫붾え由??곹깭",
|
||||
FontSize = 13,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
@@ -151,7 +151,7 @@ public partial class ChatWindow
|
||||
});
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"계층형 규칙 {docs.Count}개 · 학습 메모리 {learned}개 · {includePolicy}",
|
||||
Text = $"怨꾩링??洹쒖튃 {docs.Count}媛?쨌 ?숈뒿 硫붾え由?{learned}媛?쨌 {includePolicy}",
|
||||
FontSize = 11.5,
|
||||
Foreground = secondaryText,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
@@ -163,7 +163,7 @@ public partial class ChatWindow
|
||||
panel.Children.Add(CreateSurfacePopupSeparator());
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "적용 중 규칙",
|
||||
Text = "?곸슜 以?洹쒖튃",
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
@@ -177,7 +177,7 @@ public partial class ChatWindow
|
||||
{
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"외 {docs.Count - 6}개 규칙",
|
||||
Text = $"??{docs.Count - 6}媛?洹쒖튃",
|
||||
FontSize = 11,
|
||||
Foreground = secondaryText,
|
||||
Margin = new Thickness(8, 2, 8, 4),
|
||||
@@ -188,7 +188,7 @@ public partial class ChatWindow
|
||||
panel.Children.Add(CreateSurfacePopupSeparator());
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "최근 include 감사",
|
||||
Text = "理쒓렐 include 媛먯궗",
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
@@ -199,7 +199,7 @@ public partial class ChatWindow
|
||||
{
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "감사 로그가 꺼져 있어 include 이력은 기록되지 않습니다.",
|
||||
Text = "媛먯궗 濡쒓렇媛 爰쇱졇 ?덉뼱 include ?대젰? 湲곕줉?섏? ?딆뒿?덈떎.",
|
||||
FontSize = 11,
|
||||
Foreground = secondaryText,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
@@ -210,7 +210,7 @@ public partial class ChatWindow
|
||||
{
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "최근 3일간 include 감사 기록이 없습니다.",
|
||||
Text = "理쒓렐 3?쇨컙 include 媛먯궗 湲곕줉???놁뒿?덈떎.",
|
||||
FontSize = 11,
|
||||
Foreground = secondaryText,
|
||||
Margin = new Thickness(8, 0, 8, 6),
|
||||
@@ -245,7 +245,7 @@ public partial class ChatWindow
|
||||
return path;
|
||||
|
||||
var directory = Path.GetDirectoryName(path);
|
||||
return string.IsNullOrWhiteSpace(directory) ? fileName : $"{fileName} · {directory}";
|
||||
return string.IsNullOrWhiteSpace(directory) ? fileName : $"{fileName} 쨌 {directory}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -266,14 +266,14 @@ public partial class ChatWindow
|
||||
var stack = new StackPanel { Margin = new Thickness(8, 2, 8, 4) };
|
||||
stack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"[{doc.Label}] 우선순위 {doc.Priority}",
|
||||
Text = $"[{doc.Label}] ?곗꽑?쒖쐞 {doc.Priority}",
|
||||
FontSize = 11.5,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
});
|
||||
stack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = meta.Count == 0 ? doc.Layer : $"{doc.Layer} · {string.Join(" · ", meta)}",
|
||||
Text = meta.Count == 0 ? doc.Layer : $"{doc.Layer} 쨌 {string.Join(" 쨌 ", meta)}",
|
||||
FontSize = 10.5,
|
||||
Foreground = secondaryText,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
@@ -297,13 +297,13 @@ public partial class ChatWindow
|
||||
private Border BuildMemoryPopupAuditRow(AuditEntry entry, Brush primaryText, Brush secondaryText, Brush okBrush, Brush warnBrush, Brush dangerBrush)
|
||||
{
|
||||
var statusBrush = entry.Success ? okBrush : dangerBrush;
|
||||
var statusText = entry.Success ? "허용" : "차단";
|
||||
var statusText = entry.Success ? "?덉슜" : "李⑤떒";
|
||||
var resultBrush = entry.Success ? secondaryText : warnBrush;
|
||||
|
||||
var stack = new StackPanel { Margin = new Thickness(8, 2, 8, 4) };
|
||||
stack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"{statusText} · {entry.Timestamp:HH:mm:ss}",
|
||||
Text = $"{statusText} 쨌 {entry.Timestamp:HH:mm:ss}",
|
||||
FontSize = 11.5,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = statusBrush,
|
||||
@@ -359,8 +359,9 @@ public partial class ChatWindow
|
||||
!string.IsNullOrWhiteSpace(m.Content) &&
|
||||
(string.Equals(m.Role, "user", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(m.Role, "assistant", StringComparison.OrdinalIgnoreCase))) == true;
|
||||
var hasVisibleExecution = conversation?.ExecutionEvents?.Count > 0;
|
||||
|
||||
if (string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase) || hasVisibleMessages || _isStreaming)
|
||||
if (string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase) || hasVisibleMessages || hasVisibleExecution || _isStreaming)
|
||||
{
|
||||
SelectedPresetGuide.Visibility = Visibility.Collapsed;
|
||||
SelectedPresetGuideTitle.Text = "";
|
||||
@@ -394,3 +395,4 @@ public partial class ChatWindow
|
||||
SelectedPresetGuide.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,11 @@ public partial class ChatWindow
|
||||
{
|
||||
_transcriptElements.Clear();
|
||||
_transcriptElementMap.Clear();
|
||||
_lastGroupedProcessFeedKey = null;
|
||||
_lastGroupedProcessFeedIndex = -1;
|
||||
_processFeedAppendCount = 0;
|
||||
_processFeedMergeCount = 0;
|
||||
_transcriptRowKindCounts.Clear();
|
||||
}
|
||||
|
||||
private void RemoveTranscriptElement(UIElement element)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.Services;
|
||||
@@ -95,6 +96,11 @@ public partial class ChatWindow
|
||||
renderedItems = renderPlan.NewKeys.Count,
|
||||
hiddenCount = renderPlan.HiddenCount,
|
||||
transcriptElements = GetTranscriptElementCount(),
|
||||
processFeedAppends = _processFeedAppendCount,
|
||||
processFeedMerges = _processFeedMergeCount,
|
||||
rowKindCounts = _transcriptRowKindCounts.ToDictionary(
|
||||
pair => pair.Key.ToString(),
|
||||
pair => pair.Value),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user