- 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:
@@ -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 구조화를 계속 진행하기 쉬운 기반을 만들었습니다.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user