AX Agent 상태선 렌더 구조 분리 및 문서 갱신
Some checks failed
Release Gate / gate (push) Has been cancelled

- ChatWindow.StatusPresentation.cs로 UpdateStatusBar, StartStatusAnimation, StopStatusAnimation을 이동해 runtime 상태 이벤트와 상태선 표현 책임을 메인 창 코드에서 분리함

- ChatWindow.xaml.cs는 transcript 오케스트레이션 중심으로 더 정리했고, claw-code 기준 status/footer 품질 개선을 이어가기 쉬운 구조로 개선함

- README.md와 docs/DEVELOPMENT.md에 2026-04-06 08:39 (KST) 기준 변경 이력을 반영함

- 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-06 08:40:56 +09:00
parent 35ec073eb9
commit 2b21e8cdfb
4 changed files with 118 additions and 109 deletions

View File

@@ -7,6 +7,10 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
개발 참고: Claw Code 동등성 작업 추적 문서 개발 참고: Claw Code 동등성 작업 추적 문서
`docs/claw-code-parity-plan.md` `docs/claw-code-parity-plan.md`
- 업데이트: 2026-04-06 08:39 (KST)
- AX Agent 하단 상태바 이벤트 처리와 회전 애니메이션을 `ChatWindow.StatusPresentation.cs`로 옮겼습니다. `UpdateStatusBar`, `StartStatusAnimation`, `StopStatusAnimation`이 상태 표현 파일로 이동해 메인 창 코드의 runtime/status 분기가 더 줄었습니다.
- `ChatWindow.xaml.cs`는 대화 실행 orchestration 중심으로 더 정리됐고, claw-code 기준 status line 정교화와 footer presentation 개선을 계속 이어가기 쉬운 구조로 맞췄습니다.
- 업데이트: 2026-04-06 08:27 (KST) - 업데이트: 2026-04-06 08:27 (KST)
- AX Agent 메시지 액션/메타/편집 렌더를 `ChatWindow.MessageInteractions.cs`로 분리했습니다. 좋아요·싫어요 피드백 버튼, 응답 메타 텍스트, 메시지 등장 애니메이션, 사용자 메시지 편집·재생성 흐름이 메인 창 코드 밖으로 이동했습니다. - AX Agent 메시지 액션/메타/편집 렌더를 `ChatWindow.MessageInteractions.cs`로 분리했습니다. 좋아요·싫어요 피드백 버튼, 응답 메타 텍스트, 메시지 등장 애니메이션, 사용자 메시지 편집·재생성 흐름이 메인 창 코드 밖으로 이동했습니다.
- `ChatWindow.xaml.cs`는 transcript 오케스트레이션과 상태 흐름에 더 집중하도록 정리했고, claw-code 기준 메시지 타입 분리와 renderer 구조화를 계속 진행하기 쉬운 기반을 만들었습니다. - `ChatWindow.xaml.cs`는 transcript 오케스트레이션과 상태 흐름에 더 집중하도록 정리했고, claw-code 기준 메시지 타입 분리와 renderer 구조화를 계속 진행하기 쉬운 기반을 만들었습니다.

View File

@@ -4897,3 +4897,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- Document update: 2026-04-06 08:28 (KST) - This keeps `ChatWindow.xaml.cs` more orchestration-focused while preserving the same runtime behavior, and it aligns AX Agent more closely with the `claw-code` model of separating prompt/footer presentation from session execution logic. - Document update: 2026-04-06 08:28 (KST) - This keeps `ChatWindow.xaml.cs` more orchestration-focused while preserving the same runtime behavior, and it aligns AX Agent more closely with the `claw-code` model of separating prompt/footer presentation from session execution logic.
- Document update: 2026-04-06 08:27 (KST) - Split message interaction presentation out of `ChatWindow.xaml.cs` into `ChatWindow.MessageInteractions.cs`. Feedback button creation, assistant response meta text, transcript entry animation, and inline user-message edit/regenerate flow are now grouped in a dedicated partial. - Document update: 2026-04-06 08:27 (KST) - Split message interaction presentation out of `ChatWindow.xaml.cs` into `ChatWindow.MessageInteractions.cs`. Feedback button creation, assistant response meta text, transcript entry animation, and inline user-message edit/regenerate flow are now grouped in a dedicated partial.
- Document update: 2026-04-06 08:27 (KST) - This pass further reduces renderer responsibility in the main chat window file and moves AX Agent closer to the `claw-code` structure where transcript orchestration and message interaction presentation are separated. - Document update: 2026-04-06 08:27 (KST) - This pass further reduces renderer responsibility in the main chat window file and moves AX Agent closer to the `claw-code` structure where transcript orchestration and message interaction presentation are separated.
- Document update: 2026-04-06 08:39 (KST) - Moved runtime status bar event handling and spinner animation out of `ChatWindow.xaml.cs` into `ChatWindow.StatusPresentation.cs`. `UpdateStatusBar`, `StartStatusAnimation`, and `StopStatusAnimation` now live alongside the existing operational status presentation helpers.
- Document update: 2026-04-06 08:39 (KST) - This pass reduces direct status-line branching in the main chat window file and keeps AX Agent closer to the `claw-code` model where runtime/footer presentation is separated from session orchestration.

View File

@@ -1,11 +1,15 @@
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation;
using AxCopilot.Services; using AxCopilot.Services;
using AxCopilot.Services.Agent;
namespace AxCopilot.Views; namespace AxCopilot.Views;
public partial class ChatWindow public partial class ChatWindow
{ {
private Storyboard? _statusSpinStoryboard;
private AppStateService.OperationalStatusPresentationState BuildOperationalStatusPresentation() private AppStateService.OperationalStatusPresentationState BuildOperationalStatusPresentation()
{ {
var hasLiveRuntimeActivity = !string.Equals(_activeTab, "Chat", StringComparison.OrdinalIgnoreCase) var hasLiveRuntimeActivity = !string.Equals(_activeTab, "Chat", StringComparison.OrdinalIgnoreCase)
@@ -169,4 +173,112 @@ public partial class ChatWindow
StatusTokens.Visibility = Visibility.Visible; StatusTokens.Visibility = Visibility.Visible;
RefreshContextUsageVisual(); RefreshContextUsageVisual();
} }
private void UpdateStatusBar(AgentEvent evt)
{
var toolLabel = evt.ToolName switch
{
"file_read" or "document_read" => "파일 읽기",
"file_write" => "파일 쓰기",
"file_edit" => "파일 수정",
"html_create" => "HTML 생성",
"xlsx_create" => "Excel 생성",
"docx_create" => "Word 생성",
"csv_create" => "CSV 생성",
"md_create" => "Markdown 생성",
"folder_map" => "폴더 탐색",
"glob" => "파일 검색",
"grep" => "내용 검색",
"process" => "명령 실행",
_ => evt.ToolName,
};
var isDebugLogLevel = string.Equals(_settings.Settings.Llm.AgentLogLevel, "debug", StringComparison.OrdinalIgnoreCase);
switch (evt.Type)
{
case AgentEventType.Thinking:
SetStatus("생각 중...", spinning: true);
break;
case AgentEventType.Planning:
SetStatus($"계획 수립 중 — {evt.StepTotal}단계", spinning: true);
break;
case AgentEventType.PermissionRequest:
SetStatus($"권한 확인 중: {toolLabel}", spinning: false);
break;
case AgentEventType.PermissionGranted:
SetStatus($"권한 승인됨: {toolLabel}", spinning: false);
break;
case AgentEventType.PermissionDenied:
SetStatus($"권한 거부됨: {toolLabel}", spinning: false);
StopStatusAnimation();
break;
case AgentEventType.Decision:
SetStatus(GetDecisionStatusText(evt.Summary), spinning: IsDecisionPending(evt.Summary));
break;
case AgentEventType.ToolCall:
if (!isDebugLogLevel)
break;
SetStatus($"{toolLabel} 실행 중...", spinning: true);
break;
case AgentEventType.ToolResult:
SetStatus(evt.Success ? $"{toolLabel} 완료" : $"{toolLabel} 실패", spinning: false);
break;
case AgentEventType.StepStart:
SetStatus($"[{evt.StepCurrent}/{evt.StepTotal}] {TruncateForStatus(evt.Summary)}", spinning: true);
break;
case AgentEventType.StepDone:
SetStatus($"[{evt.StepCurrent}/{evt.StepTotal}] 단계 완료", spinning: true);
break;
case AgentEventType.SkillCall:
if (!isDebugLogLevel)
break;
SetStatus($"스킬 실행 중: {TruncateForStatus(evt.Summary)}", spinning: true);
break;
case AgentEventType.Complete:
SetStatus("작업 완료", spinning: false);
StopStatusAnimation();
break;
case AgentEventType.Error:
SetStatus("오류 발생", spinning: false);
StopStatusAnimation();
break;
case AgentEventType.Paused:
if (!isDebugLogLevel)
break;
SetStatus("⏸ 일시정지", spinning: false);
break;
case AgentEventType.Resumed:
if (!isDebugLogLevel)
break;
SetStatus("▶ 재개됨", spinning: true);
break;
}
}
private void StartStatusAnimation()
{
if (_statusSpinStoryboard != null)
return;
var anim = new DoubleAnimation
{
From = 0,
To = 360,
Duration = TimeSpan.FromSeconds(2),
RepeatBehavior = RepeatBehavior.Forever,
};
_statusSpinStoryboard = new Storyboard();
Storyboard.SetTarget(anim, StatusDiamond);
Storyboard.SetTargetProperty(anim,
new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
_statusSpinStoryboard.Children.Add(anim);
_statusSpinStoryboard.Begin();
}
private void StopStatusAnimation()
{
_statusSpinStoryboard?.Stop();
_statusSpinStoryboard = null;
}
} }

View File

@@ -16892,115 +16892,6 @@ public partial class ChatWindow : Window
// ─── 하단 상태바 ────────────────────────────────────────────────────── // ─── 하단 상태바 ──────────────────────────────────────────────────────
private System.Windows.Media.Animation.Storyboard? _statusSpinStoryboard;
private void UpdateStatusBar(AgentEvent evt)
{
var toolLabel = evt.ToolName switch
{
"file_read" or "document_read" => "파일 읽기",
"file_write" => "파일 쓰기",
"file_edit" => "파일 수정",
"html_create" => "HTML 생성",
"xlsx_create" => "Excel 생성",
"docx_create" => "Word 생성",
"csv_create" => "CSV 생성",
"md_create" => "Markdown 생성",
"folder_map" => "폴더 탐색",
"glob" => "파일 검색",
"grep" => "내용 검색",
"process" => "명령 실행",
_ => evt.ToolName,
};
var isDebugLogLevel = string.Equals(_settings.Settings.Llm.AgentLogLevel, "debug", StringComparison.OrdinalIgnoreCase);
switch (evt.Type)
{
case AgentEventType.Thinking:
SetStatus("생각 중...", spinning: true);
break;
case AgentEventType.Planning:
SetStatus($"계획 수립 중 — {evt.StepTotal}단계", spinning: true);
break;
case AgentEventType.PermissionRequest:
SetStatus($"권한 확인 중: {toolLabel}", spinning: false);
break;
case AgentEventType.PermissionGranted:
SetStatus($"권한 승인됨: {toolLabel}", spinning: false);
break;
case AgentEventType.PermissionDenied:
SetStatus($"권한 거부됨: {toolLabel}", spinning: false);
StopStatusAnimation();
break;
case AgentEventType.Decision:
SetStatus(GetDecisionStatusText(evt.Summary), spinning: IsDecisionPending(evt.Summary));
break;
case AgentEventType.ToolCall:
if (!isDebugLogLevel)
break;
SetStatus($"{toolLabel} 실행 중...", spinning: true);
break;
case AgentEventType.ToolResult:
SetStatus(evt.Success ? $"{toolLabel} 완료" : $"{toolLabel} 실패", spinning: false);
break;
case AgentEventType.StepStart:
SetStatus($"[{evt.StepCurrent}/{evt.StepTotal}] {TruncateForStatus(evt.Summary)}", spinning: true);
break;
case AgentEventType.StepDone:
SetStatus($"[{evt.StepCurrent}/{evt.StepTotal}] 단계 완료", spinning: true);
break;
case AgentEventType.SkillCall:
if (!isDebugLogLevel)
break;
SetStatus($"스킬 실행 중: {TruncateForStatus(evt.Summary)}", spinning: true);
break;
case AgentEventType.Complete:
SetStatus("작업 완료", spinning: false);
StopStatusAnimation();
break;
case AgentEventType.Error:
SetStatus("오류 발생", spinning: false);
StopStatusAnimation();
break;
case AgentEventType.Paused:
if (!isDebugLogLevel)
break;
SetStatus("⏸ 일시정지", spinning: false);
break;
case AgentEventType.Resumed:
if (!isDebugLogLevel)
break;
SetStatus("▶ 재개됨", spinning: true);
break;
}
}
private void StartStatusAnimation()
{
if (_statusSpinStoryboard != null) return;
var anim = new System.Windows.Media.Animation.DoubleAnimation
{
From = 0, To = 360,
Duration = TimeSpan.FromSeconds(2),
RepeatBehavior = System.Windows.Media.Animation.RepeatBehavior.Forever,
};
_statusSpinStoryboard = new System.Windows.Media.Animation.Storyboard();
System.Windows.Media.Animation.Storyboard.SetTarget(anim, StatusDiamond);
System.Windows.Media.Animation.Storyboard.SetTargetProperty(anim,
new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
_statusSpinStoryboard.Children.Add(anim);
_statusSpinStoryboard.Begin();
}
private void StopStatusAnimation()
{
_statusSpinStoryboard?.Stop();
_statusSpinStoryboard = null;
}
private void BtnCompactNow_Click(object sender, RoutedEventArgs e) private void BtnCompactNow_Click(object sender, RoutedEventArgs e)
{ {
if (_isStreaming) if (_isStreaming)