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

@@ -1,11 +1,15 @@
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using AxCopilot.Services;
using AxCopilot.Services.Agent;
namespace AxCopilot.Views;
public partial class ChatWindow
{
private Storyboard? _statusSpinStoryboard;
private AppStateService.OperationalStatusPresentationState BuildOperationalStatusPresentation()
{
var hasLiveRuntimeActivity = !string.Equals(_activeTab, "Chat", StringComparison.OrdinalIgnoreCase)
@@ -169,4 +173,112 @@ public partial class ChatWindow
StatusTokens.Visibility = Visibility.Visible;
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;
}
}