From e2278eec2400d58f5af899aa2f7dde41c88de414 Mon Sep 17 00:00:00 2001 From: lacvet Date: Thu, 16 Apr 2026 01:02:13 +0900 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFAX=20Agent=20=EC=A7=84=ED=96=89=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=ED=8F=AD=C2=B7=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=20=EB=B0=8F=20=EC=9D=B8=EC=BD=94=EB=94=A9=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 앱 생성 진행/도구/완료 카드에 전용 최대폭을 도입하고 좌측 정렬로 통일 - 라이브 진행 카드와 검증 게이트 문구에서 깨져 보이던 문자열을 정상화 - build_run/process 도구가 Windows 기본 출력 인코딩을 우선 사용하도록 조정 - README와 DEVELOPMENT 문서에 2026-04-16 00:57 (KST) 기준 이력 반영 검증: - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_agent_ui_layout_encoding\ -p:IntermediateOutputPath=obj\verify_agent_ui_layout_encoding\ (경고 0 / 오류 0) - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatWindowSlashPolicyTests|AgentLoopCodeQualityTests" -p:OutputPath=bin\verify_agent_ui_layout_encoding_tests\ -p:IntermediateOutputPath=obj\verify_agent_ui_layout_encoding_tests\ (통과 194) --- README.md | 8 +++++ docs/DEVELOPMENT.md | 7 ++++ .../AgentLoopTransitions.Verification.cs | 30 ++++++++--------- src/AxCopilot/Services/Agent/BuildRunTool.cs | 7 ++-- src/AxCopilot/Services/Agent/ProcessTool.cs | 7 ++-- .../Views/ChatWindow.AgentEventRendering.cs | 14 ++++---- .../Views/ChatWindow.ResponsePresentation.cs | 19 +++++++++-- .../ChatWindow.V2AgentEventPresentation.cs | 32 +++++++++++++------ .../ChatWindow.V2LiveProgressPresentation.cs | 19 +++++++---- 9 files changed, 98 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 8321f38..65518fd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # AX Commander +- 업데이트: 2026-04-16 00:57 (KST) +- AX Agent 앱 생성 메시지의 가로 폭과 정렬을 다시 다듬었습니다. `src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs`에 `GetAgentEventMaxWidth()`를 추가해 앱이 그리는 진행/도구/완료 카드 폭만 별도로 줄였고, `src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs`, `src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs`, `src/AxCopilot/Views/ChatWindow.V2AgentEventPresentation.cs`는 해당 폭을 사용하면서 중앙 정렬 대신 좌측 기준으로 붙도록 맞췄습니다. +- 라이브 진행 카드와 검증 게이트 문구에서 깨져 보이던 문자열도 함께 정리했습니다. `src/AxCopilot/Services/Agent/AgentLoopTransitions.Verification.cs`의 검증/재시도 안내 문구를 정상 한국어로 교체했고, `src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs`, `src/AxCopilot/Views/ChatWindow.V2AgentEventPresentation.cs`에는 런타임에 보이는 상태 문구를 안정적인 문자열로 다시 연결했습니다. +- 프로세스 출력 인코딩은 `src/AxCopilot/Services/Agent/BuildRunTool.cs`, `src/AxCopilot/Services/Agent/ProcessTool.cs`에서 Windows 기본 출력 인코딩을 우선 사용하도록 바꿔 build/test나 셸 실행 결과가 UTF-8 고정 디코딩 때문에 깨질 가능성을 줄였습니다. +- 검증: + - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_agent_ui_layout_encoding\\ -p:IntermediateOutputPath=obj\\verify_agent_ui_layout_encoding\\` 경고 0 / 오류 0 + - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatWindowSlashPolicyTests|AgentLoopCodeQualityTests" -p:OutputPath=bin\\verify_agent_ui_layout_encoding_tests\\ -p:IntermediateOutputPath=obj\\verify_agent_ui_layout_encoding_tests\\` 통과 194 + - 업데이트: 2026-04-16 00:01 (KST) - AX Agent 스크롤 맨아래 이동 FAB가 입력창 아래로 잘려 보이던 위치 문제를 조정했습니다. `src/AxCopilot/Views/ChatWindow.xaml`에서 버튼을 `Grid.RowSpan="2"`로 옮겨 메시지 영역과 입력 바를 함께 기준으로 잡고, 기본 하단 여백도 더 넉넉하게 올렸습니다. - `src/AxCopilot/Views/ChatWindow.xaml.cs`에는 `UpdateScrollToBottomFabPosition()`을 추가해 `ComposerShell` 높이, 입력창 크기 변화, 창 리사이즈에 맞춰 버튼 하단 여백을 자동 계산하도록 연결했습니다. 이제 입력창이 커지거나 상태 행 높이가 변해도 FAB가 그 위에 안전하게 떠 있습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 514099f..04f6414 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1739,3 +1739,10 @@ UI ?遺우쁽????域뱀뮆???귐뗫솯?醫딆춦 ???袁る퓮 ?臾믩씜 ??疫 - 검증: - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_ppt_manifest_quality\\ -p:IntermediateOutputPath=obj\\verify_ppt_manifest_quality\\` 경고 0 / 오류 0 - `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "PptQualityGatePolicyTests|PptxTemplateManifestCatalogTests|PptxSkillTemplatePackTests|PptxSkillTemplateDiagnosticsTests|PptxSkillAutoRepairTests|PptxSkillGoldenDeckTests" -p:OutputPath=bin\\verify_ppt_manifest_quality_tests\\ -p:IntermediateOutputPath=obj\\verify_ppt_manifest_quality_tests\\` 통과 15 +업데이트: 2026-04-16 00:57 (KST) +- AX Agent 앱 생성 메시지 전용 폭을 분리했습니다. `src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs`에 `GetAgentEventMaxWidth()`를 추가하고, `src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs`, `src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs`, `src/AxCopilot/Views/ChatWindow.V2AgentEventPresentation.cs`가 같은 값을 사용하도록 맞춰 진행 카드/도구 카드/완료 카드가 본문 전체 폭을 과하게 점유하지 않게 했습니다. +- 라이브 진행 카드와 하단 상태 카드의 정렬도 함께 다듬었습니다. V2 라이브 컨테이너와 완료 카드 정렬을 좌측 기준으로 통일하고, 상태 텍스트의 `TextAlignment`를 왼쪽으로 고정해 요약/상세 줄이 카드 중앙이 아니라 본문 축에서 바로 읽히도록 정리했습니다. +- 깨진 안내 문구는 두 경로에서 정리했습니다. `src/AxCopilot/Services/Agent/AgentLoopTransitions.Verification.cs`의 검증 게이트/재시도 이벤트 메시지를 정상 한국어로 교체했고, `src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs`, `src/AxCopilot/Views/ChatWindow.V2AgentEventPresentation.cs`에는 런타임에 다시 덮어쓰는 안전한 상태 문구를 추가해 기존 깨진 문자열이 그대로 노출되지 않게 했습니다. +- 프로세스 출력 인코딩은 `src/AxCopilot/Services/Agent/BuildRunTool.cs`, `src/AxCopilot/Services/Agent/ProcessTool.cs`에서 UTF-8 고정 대신 Windows 기본 출력 인코딩을 우선 사용하도록 조정했습니다. 한국어 콘솔 출력이 UTF-8로 강제 디코딩되며 깨질 수 있던 경로를 줄이기 위한 수정입니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_agent_ui_layout_encoding\\ -p:IntermediateOutputPath=obj\\verify_agent_ui_layout_encoding\\` 경고 0 / 오류 0 +- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ChatWindowSlashPolicyTests|AgentLoopCodeQualityTests" -p:OutputPath=bin\\verify_agent_ui_layout_encoding_tests\\ -p:IntermediateOutputPath=obj\\verify_agent_ui_layout_encoding_tests\\` 통과 194 diff --git a/src/AxCopilot/Services/Agent/AgentLoopTransitions.Verification.cs b/src/AxCopilot/Services/Agent/AgentLoopTransitions.Verification.cs index 146f78d..98febd8 100644 --- a/src/AxCopilot/Services/Agent/AgentLoopTransitions.Verification.cs +++ b/src/AxCopilot/Services/Agent/AgentLoopTransitions.Verification.cs @@ -28,8 +28,8 @@ public partial class AgentLoopService taskPolicy) }); EmitEvent(AgentEventType.Thinking, "", highImpactCodeChange - ? "怨좎쁺??肄붾뱶 蹂€寃쎌쑝濡?遺꾨쪟??李몄“ 寃€利앷낵 build/test 寃€利앹쓣 ???꾧꺽?섍쾶 ?댁뼱媛묐땲??" - : "肄붾뱶 蹂€寃???build/test/diff 寃€利앹쓣 ?댁뼱媛묐땲??"); + ? "고영향 코드 변경으로 분류되어 참조 검증과 build/test 검증을 더 엄격하게 이어갑니다." + : "코드 변경 이후 build/test/diff 검증을 이어갑니다."); } else if (HasCodeVerificationEvidenceAfterLastModification(messages, requireHighImpactCodeVerification)) { @@ -81,12 +81,12 @@ public partial class AgentLoopService { Role = "user", Content = requireHighImpactCodeVerification - ? "[System:CodeQualityGate] 怨듭슜/?듭떖 肄붾뱶 蹂€寃??댄썑 寃€利?洹쇨굅媛€ 遺€議깊빀?덈떎. 醫낅즺?섏? 留먭퀬 file_read, grep/glob, git diff, build/test源뚯? ?뺤씤???ㅼ뿉留?留덈Т由ы븯?몄슂." - : "[System:CodeQualityGate] 留덉?留?肄붾뱶 ?섏젙 ?댄썑 build/test/file_read/diff 洹쇨굅媛€ 遺€議깊빀?덈떎. 醫낅즺?섏? 留먭퀬 寃€利?洹쇨굅瑜?蹂닿컯???ㅼ뿉留?留덈Т由ы븯?몄슂." + ? "[System:CodeQualityGate] 공용/고영향 코드 변경 이후 검증 근거가 부족합니다. 종료하지 말고 file_read, grep/glob, git diff, build/test까지 확인한 뒤에만 마무리하세요." + : "[System:CodeQualityGate] 마지막 코드 수정 이후 build/test/file_read/diff 근거가 부족합니다. 종료하지 말고 검증 근거를 보강한 뒤에만 마무리하세요." }); EmitEvent(AgentEventType.Thinking, "", requireHighImpactCodeVerification - ? "?듭떖 肄붾뱶 蹂€寃쎌쓽 寃€利?洹쇨굅媛€ 遺€議깊빐 異붽? 寃€利앹쓣 吏꾪뻾?⑸땲??.." - : "肄붾뱶 寃곌낵 寃€利?洹쇨굅媛€ 遺€議깊빐 異붽? 寃€利앹쓣 吏꾪뻾?⑸땲??.."); + ? "고영향 코드 변경의 검증 근거가 부족해 추가 검증을 진행합니다..." + : "코드 결과 검증 근거가 부족해 추가 검증을 진행합니다..."); return true; } @@ -101,9 +101,9 @@ public partial class AgentLoopService messages.Add(new ChatMessage { Role = "user", - Content = "[System:HighImpactBuildTestGate] ?듭떖 肄붾뱶 蹂€寃쎌엯?덈떎. 醫낅즺?섏? 留먭퀬 build_run怨?test_loop瑜?紐⑤몢 ?ㅽ뻾???깃났 洹쇨굅瑜??뺣낫???ㅼ뿉留?留덈Т由ы븯?몄슂." + Content = "[System:HighImpactBuildTestGate] 고영향 코드 변경입니다. 종료하지 말고 build_run과 test_loop를 모두 실행해 성공 근거를 확보한 뒤에만 마무리하세요." }); - EmitEvent(AgentEventType.Thinking, "", "?듭떖 蹂€寃쎌씠??build+test ?깃났 洹쇨굅瑜?紐⑤몢 ?뺣낫???뚭퉴吏€ 吏꾪뻾?⑸땲??.."); + EmitEvent(AgentEventType.Thinking, "", "고영향 변경이라 build와 test 성공 근거를 모두 확보할 때까지 진행합니다..."); return true; } @@ -127,7 +127,7 @@ public partial class AgentLoopService Role = "user", Content = BuildFinalReportQualityPrompt(taskPolicy, requireHighImpactCodeVerification) }); - EmitEvent(AgentEventType.Thinking, "", "理쒖쥌 蹂닿퀬??蹂€寃승룰?利씲룸━?ㅽ겕 ?붿빟??遺€議깊빐 ??踰????뺣━?⑸땲??.."); + EmitEvent(AgentEventType.Thinking, "", "최종 보고에 변경·검증·리스크 요약이 부족해 한 번 더 정리합니다..."); return true; } @@ -171,7 +171,7 @@ public partial class AgentLoopService EmitEvent( AgentEventType.Thinking, "", - $"?꾨줈?앺듃 援ъ“媛€ ?됰㈃?곸쑝濡??앹꽦???대뜑 ?덉씠?꾩썐??癒쇱? ?뺣━?⑸땲??({runState.ProjectLayoutGateRetry}/1)"); + $"프로젝트 구조가 평면적으로 생성되어 폴더 레이아웃을 먼저 정리합니다. ({runState.ProjectLayoutGateRetry}/1)"); return true; } @@ -238,9 +238,9 @@ public partial class AgentLoopService messages.Add(new ChatMessage { Role = "user", - Content = "[System:CodeDiffGate] 肄붾뱶 蹂€寃??댄썑 diff 洹쇨굅媛€ 遺€議깊빀?덈떎. git_tool ?꾧뎄濡?蹂€寃??뚯씪怨??듭떖 diff瑜?癒쇱? ?뺤씤?섍퀬 ?붿빟?섏꽭?? 吏€湲?利됱떆 git_tool ?꾧뎄瑜??몄텧?섏꽭??" + Content = "[System:CodeDiffGate] 코드 변경 이후 diff 근거가 부족합니다. git_tool로 변경 파일과 핵심 diff를 먼저 확인하고 요약하세요. 지금 즉시 git_tool을 호출하세요." }); - EmitEvent(AgentEventType.Thinking, "", "肄붾뱶 diff 洹쇨굅媛€ 遺€議깊빐 git diff 寃€利앹쓣 異붽??⑸땲??.."); + EmitEvent(AgentEventType.Thinking, "", "코드 diff 근거가 부족해 git diff 검증을 추가합니다..."); return true; } @@ -277,7 +277,7 @@ public partial class AgentLoopService Role = "user", Content = BuildRecentExecutionEvidencePrompt(taskPolicy) }); - EmitEvent(AgentEventType.Thinking, "", "理쒓렐 ?섏젙 ?댄썑 ?ㅽ뻾 洹쇨굅媛€ 遺€議깊빐 build/test ?ш?利앹쓣 ?섑뻾?⑸땲??.."); + EmitEvent(AgentEventType.Thinking, "", "최근 수정 이후 실행 근거가 부족해 build/test 재검증을 진행합니다..."); return true; } @@ -314,7 +314,7 @@ public partial class AgentLoopService Role = "user", Content = BuildExecutionSuccessGatePrompt(taskPolicy) }); - EmitEvent(AgentEventType.Thinking, "", "?ㅽ뙣???ㅽ뻾 洹쇨굅留??덉뼱 build/test ?깃났 寃곌낵瑜??ㅼ떆 寃€利앺빀?덈떎..."); + EmitEvent(AgentEventType.Thinking, "", "실패한 실행 근거만 있어 build/test 성공 결과를 다시 검증합니다..."); return true; } @@ -354,7 +354,7 @@ public partial class AgentLoopService Role = "user", Content = BuildTerminalEvidenceGatePrompt(taskPolicy, lastArtifactFilePath) }); - EmitEvent(AgentEventType.Thinking, "", $"醫낅즺 ???ㅽ뻾 利앷굅媛€ 遺€議깊빐 蹂닿컯 ?④퀎瑜?吏꾪뻾?⑸땲??({runState.TerminalEvidenceGateRetry}/{retryMax})"); + EmitEvent(AgentEventType.Thinking, "", $"종료 전 실행 근거가 부족해 보강 단계를 진행합니다. ({runState.TerminalEvidenceGateRetry}/{retryMax})"); return true; } } diff --git a/src/AxCopilot/Services/Agent/BuildRunTool.cs b/src/AxCopilot/Services/Agent/BuildRunTool.cs index f83fcef..116258e 100644 --- a/src/AxCopilot/Services/Agent/BuildRunTool.cs +++ b/src/AxCopilot/Services/Agent/BuildRunTool.cs @@ -143,8 +143,8 @@ public class BuildRunTool : IAgentTool RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, - StandardOutputEncoding = Encoding.UTF8, - StandardErrorEncoding = Encoding.UTF8, + StandardOutputEncoding = ResolveProcessOutputEncoding(), + StandardErrorEncoding = ResolveProcessOutputEncoding(), }; using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); @@ -186,6 +186,9 @@ public class BuildRunTool : IAgentTool private record ProjectInfo(string Type, string Marker, string BuildCommand, string TestCommand, string RunCommand, string LintCommand, string FormatCommand); + private static Encoding ResolveProcessOutputEncoding() + => OperatingSystem.IsWindows() ? Encoding.Default : Encoding.UTF8; + private static ProjectInfo? DetectProjectType(string dir) { if (Directory.GetFiles(dir, "*.sln").Length > 0) diff --git a/src/AxCopilot/Services/Agent/ProcessTool.cs b/src/AxCopilot/Services/Agent/ProcessTool.cs index 6cb9f59..89ad79d 100644 --- a/src/AxCopilot/Services/Agent/ProcessTool.cs +++ b/src/AxCopilot/Services/Agent/ProcessTool.cs @@ -75,8 +75,8 @@ public class ProcessTool : IAgentTool RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, - StandardOutputEncoding = Encoding.UTF8, - StandardErrorEncoding = Encoding.UTF8, + StandardOutputEncoding = ResolveProcessOutputEncoding(), + StandardErrorEncoding = ResolveProcessOutputEncoding(), }; // 작업 폴더 설정 @@ -130,4 +130,7 @@ public class ProcessTool : IAgentTool return ToolResult.Fail($"명령 실행 실패: {ex.Message}"); } } + + private static Encoding ResolveProcessOutputEncoding() + => OperatingSystem.IsWindows() ? Encoding.Default : Encoding.UTF8; } diff --git a/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs b/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs index 83983ba..9fc92bb 100644 --- a/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs +++ b/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs @@ -59,7 +59,7 @@ public partial class ChatWindow bool liveWaitingStyle = false) { var liveAccentColor = ResolveLiveProgressAccentColor(accentBrush); - var pillMaxWidth = GetMessageMaxWidth(); + var pillMaxWidth = GetAgentEventMaxWidth(); return new Border { Background = liveWaitingStyle @@ -72,7 +72,7 @@ public partial class ChatWindow CornerRadius = new CornerRadius(8), Padding = new Thickness(10, 6, 10, 6), Margin = new Thickness(0, 4, 0, 4), - HorizontalAlignment = HorizontalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, MaxWidth = pillMaxWidth, Child = new Grid { @@ -280,7 +280,7 @@ public partial class ChatWindow && !string.Equals(body, toolLabel, StringComparison.OrdinalIgnoreCase) && !string.Equals(body, headerText, StringComparison.OrdinalIgnoreCase); - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); var stack = new StackPanel { HorizontalAlignment = HorizontalAlignment.Left, @@ -514,7 +514,7 @@ public partial class ChatWindow var hintBg = TryFindResource("HintBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); var container = new StackPanel { HorizontalAlignment = HorizontalAlignment.Left, @@ -1685,7 +1685,7 @@ public partial class ChatWindow } // ── Claude Code 스타일: 접히는 한 줄 행 (배너 이벤트도 동일 형식) ── - var msgMaxWidth2 = GetMessageMaxWidth(); + var msgMaxWidth2 = GetAgentEventMaxWidth(); // 헤더 텍스트 구성: label + fileName (접기 화살표가 별도 표시되므로 statusPrefix 불필요) var bannerFileName = !string.IsNullOrWhiteSpace(evt.FilePath) @@ -1953,7 +1953,7 @@ public partial class ChatWindow // ── 요약 카드 ── if (!string.IsNullOrWhiteSpace(evt.Summary) && evt.Summary != "에이전트 작업 완료") { - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); var hintBg = TryFindResource("HintBackground") as Brush ?? BrushFromHex("#F8FAFC"); var borderColor = TryFindResource("BorderColor") as Brush ?? BrushFromHex("#E2E8F0"); var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.Black; @@ -1967,7 +1967,7 @@ public partial class ChatWindow Padding = new Thickness(16, 12, 16, 12), Margin = new Thickness(0, 6, 0, 2), MaxWidth = msgMaxWidth, - HorizontalAlignment = HorizontalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, }; var cardStack = new StackPanel(); diff --git a/src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs b/src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs index 3632457..568ee07 100644 --- a/src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs @@ -227,6 +227,21 @@ public partial class ChatWindow return Math.Clamp(maxW, 320, 1040); } + private double GetAgentEventMaxWidth() + { + var baseWidth = GetMessageMaxWidth(); + var reservedRightGap = baseWidth switch + { + >= 980 => 190, + >= 860 => 160, + >= 720 => 132, + _ => 92, + }; + + var reducedWidth = baseWidth - reservedRightGap; + return Math.Clamp(reducedWidth, 280, Math.Min(900, baseWidth)); + } + private bool UpdateResponsiveChatLayout() { var viewportWidth = MessageList?.ActualWidth ?? 0; @@ -269,10 +284,10 @@ public partial class ChatWindow private StackPanel CreateStreamingContainer(out TextBlock streamText) { - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); var container = new StackPanel { - HorizontalAlignment = HorizontalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, Width = msgMaxWidth, MaxWidth = msgMaxWidth, Margin = new Thickness(0, 3, 0, 3), diff --git a/src/AxCopilot/Views/ChatWindow.V2AgentEventPresentation.cs b/src/AxCopilot/Views/ChatWindow.V2AgentEventPresentation.cs index 9c78549..0e89a60 100644 --- a/src/AxCopilot/Views/ChatWindow.V2AgentEventPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.V2AgentEventPresentation.cs @@ -36,7 +36,7 @@ public partial class ChatWindow ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue; - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); var (icon, iconColor) = GetV2ToolIcon(toolCall.ToolName); var elapsed = NormalizeProgressElapsedMs(toolResult.ElapsedMs); var elapsedText = elapsed > 0 ? $"{elapsed / 1000.0:F1}s" : ""; @@ -45,7 +45,7 @@ public partial class ChatWindow // 외부 컨테이너 — 왼쪽 세로선 + 카드 var outerGrid = new Grid { - HorizontalAlignment = HorizontalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, Width = msgMaxWidth, MaxWidth = msgMaxWidth, Margin = new Thickness(0, 2, 0, 2), @@ -300,7 +300,7 @@ public partial class ChatWindow private UIElement CreateV2ThinkingBlock(AgentEvent agentEvent) { var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); var summary = AgentProgressSummarySanitizer.NormalizeThinkingSummary( agentEvent.Summary, @@ -318,7 +318,7 @@ public partial class ChatWindow var outerGrid = new Grid { - HorizontalAlignment = HorizontalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, Width = msgMaxWidth, MaxWidth = msgMaxWidth, Margin = new Thickness(0, 2, 0, 2), @@ -382,12 +382,12 @@ public partial class ChatWindow var hintBg = TryFindResource("HintBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); var (icon, iconColor) = GetV2ToolIcon(agentEvent.ToolName); var outerGrid = new Grid { - HorizontalAlignment = HorizontalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, Width = msgMaxWidth, MaxWidth = msgMaxWidth, Margin = new Thickness(0, 2, 0, 2), @@ -460,7 +460,7 @@ public partial class ChatWindow /// V2: 작업 완료 배너 private UIElement CreateV2CompleteBanner(AgentEvent agentEvent) { - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); var elapsed = NormalizeProgressElapsedMs(agentEvent.ElapsedMs); var elapsedText = elapsed > 0 ? $" · {elapsed / 1000.0:F1}s" : ""; @@ -473,6 +473,14 @@ public partial class ChatWindow tokenInfo = $" · {string.Join("/", parts)} 토큰"; } + if (agentEvent.InputTokens > 0 || agentEvent.OutputTokens > 0) + { + var localizedParts = new System.Collections.Generic.List(); + if (agentEvent.InputTokens > 0) localizedParts.Add($"\uC785\uB825 {agentEvent.InputTokens:N0}"); + if (agentEvent.OutputTokens > 0) localizedParts.Add($"\uCD9C\uB825 {agentEvent.OutputTokens:N0}"); + tokenInfo = $" \u00B7 {string.Join("/", localizedParts)} \uD1A0\uD070"; + } + var banner = new Border { Background = new SolidColorBrush(Color.FromArgb(0x24, 0x66, 0xBB, 0x6A)), @@ -481,7 +489,7 @@ public partial class ChatWindow CornerRadius = new CornerRadius(8), Padding = new Thickness(14, 8, 14, 8), Margin = new Thickness(0, 6, 0, 4), - HorizontalAlignment = HorizontalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, MaxWidth = msgMaxWidth, }; @@ -502,6 +510,8 @@ public partial class ChatWindow Foreground = new SolidColorBrush(Color.FromRgb(0x66, 0xBB, 0x6A)), VerticalAlignment = VerticalAlignment.Center, }); + if (sp.Children.Count > 1 && sp.Children[1] is TextBlock completeText) + completeText.Text = $"\uC791\uC5C5 \uC644\uB8CC{elapsedText}{tokenInfo}"; banner.Child = sp; return banner; } @@ -509,7 +519,7 @@ public partial class ChatWindow /// V2: 에러 배너 private UIElement CreateV2ErrorBanner(AgentEvent agentEvent) { - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); var banner = new Border { Background = new SolidColorBrush(Color.FromArgb(0x24, 0xEF, 0x53, 0x50)), @@ -518,7 +528,7 @@ public partial class ChatWindow CornerRadius = new CornerRadius(8), Padding = new Thickness(14, 8, 14, 8), Margin = new Thickness(0, 6, 0, 4), - HorizontalAlignment = HorizontalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, MaxWidth = msgMaxWidth, }; @@ -540,6 +550,8 @@ public partial class ChatWindow TextWrapping = TextWrapping.Wrap, VerticalAlignment = VerticalAlignment.Center, }); + if (sp.Children.Count > 1 && sp.Children[1] is TextBlock errorText) + errorText.Text = $"\uC624\uB958 \uBC1C\uC0DD: {agentEvent.Summary ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}"; banner.Child = sp; return banner; } diff --git a/src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs b/src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs index 68bc7c8..79757ee 100644 --- a/src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.V2LiveProgressPresentation.cs @@ -32,7 +32,7 @@ public partial class ChatWindow _v2LiveToolCards.Clear(); _v2LastLiveToolCallId = null; - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue; var accentColor = ResolveLiveProgressAccentColor(accentBrush); var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; @@ -40,7 +40,7 @@ public partial class ChatWindow _v2LiveContainer = new StackPanel { - HorizontalAlignment = HorizontalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Left, Width = msgMaxWidth, MaxWidth = msgMaxWidth, Margin = new Thickness(0, 4, 0, 6), @@ -99,6 +99,7 @@ public partial class ChatWindow FontSize = 14, FontWeight = FontWeights.SemiBold, Foreground = primaryText, + TextAlignment = TextAlignment.Left, TextWrapping = TextWrapping.Wrap, LineHeight = 20, }; @@ -107,6 +108,7 @@ public partial class ChatWindow FontSize = 11, Foreground = secondaryText, Opacity = 0.88, + TextAlignment = TextAlignment.Left, TextWrapping = TextWrapping.Wrap, LineHeight = 18, Margin = new Thickness(0, 4, 0, 0), @@ -116,6 +118,7 @@ public partial class ChatWindow FontSize = 10, Foreground = secondaryText, Opacity = 0.72, + TextAlignment = TextAlignment.Left, Margin = new Thickness(0, 8, 0, 0), }; @@ -127,6 +130,7 @@ public partial class ChatWindow CornerRadius = new CornerRadius(12), Padding = new Thickness(14, 12, 14, 12), Margin = new Thickness(0, 4, 0, 6), + HorizontalAlignment = HorizontalAlignment.Left, Child = new StackPanel { Children = @@ -148,7 +152,7 @@ public partial class ChatWindow { if (_v2LiveElapsedText == null) return; var sec = (int)(DateTime.UtcNow - _v2LiveStartTime).TotalSeconds; - _v2LiveElapsedText.Text = sec > 0 ? $"{sec}초 경과" : ""; + _v2LiveElapsedText.Text = sec > 0 ? $"{sec}\uCD08 \uACBD\uACFC" : string.Empty; }; _v2LiveElapsedTimer.Start(); } @@ -160,7 +164,7 @@ public partial class ChatWindow var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; - var msgMaxWidth = GetMessageMaxWidth(); + var msgMaxWidth = GetAgentEventMaxWidth(); UpdateV2LiveStatusCardFromEvent(agentEvent); switch (agentEvent.Type) @@ -441,7 +445,7 @@ public partial class ChatWindow return; var narrative = AgentStatusNarrativeCatalog.BuildFromEvent(agentEvent, _activeTab); - var statusSummaryMaxLength = GetMessageMaxWidth() > 900 ? 260 : 180; + var statusSummaryMaxLength = GetAgentEventMaxWidth() > 780 ? 260 : 180; var normalizedThinking = agentEvent.Type == AgentEventType.Thinking ? AgentProgressSummarySanitizer.NormalizeThinkingSummary( agentEvent.Summary, @@ -459,9 +463,10 @@ public partial class ChatWindow if (_v2LiveStatusText == null || _v2LiveStatusDetailText == null || _v2LiveStatusMetaText == null) return; - _v2LiveStatusText.Text = string.IsNullOrWhiteSpace(message) - ? "작업을 이어가고 있습니다..." + var normalizedMessage = string.IsNullOrWhiteSpace(message) + ? "\uC791\uC5C5\uC744 \uC774\uC5B4\uAC00\uACE0 \uC788\uC2B5\uB2C8\uB2E4..." : message; + _v2LiveStatusText.Text = normalizedMessage; _v2LiveStatusDetailText.Text = detail ?? string.Empty; _v2LiveStatusDetailText.Visibility = string.IsNullOrWhiteSpace(detail)