의견 요청과 계획 승인 렌더를 분리해 메시지 타입 구조를 정리한다
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
- ChatWindow.InlineInteractions를 UserAskPresentation과 PlanApprovalPresentation으로 분리해 사용자 질문 카드와 계획 승인 흐름의 책임을 나눔 - 메시지 타입 renderer 분리 계획의 다음 단계로 ChatWindow.xaml.cs와 mixed inline interaction partial의 결합도를 낮춤 - README, DEVELOPMENT, claw-code parity plan 문서를 2026-04-06 09:44 (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:
@@ -1172,3 +1172,6 @@ MIT License
|
|||||||
- [OperationalStatusPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/OperationalStatusPresentationCatalog.cs)를 추가해 compact strip/quick strip의 색상, 노출 조건, 빠른 상태 배지 문구 계산을 전용 카탈로그로 분리했다. [AppStateService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AppStateService.cs)의 `GetOperationalStatusPresentation(...)`은 이제 상태 집계 후 카탈로그 결과만 반환한다.
|
- [OperationalStatusPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/OperationalStatusPresentationCatalog.cs)를 추가해 compact strip/quick strip의 색상, 노출 조건, 빠른 상태 배지 문구 계산을 전용 카탈로그로 분리했다. [AppStateService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AppStateService.cs)의 `GetOperationalStatusPresentation(...)`은 이제 상태 집계 후 카탈로그 결과만 반환한다.
|
||||||
- [PermissionRequestPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs), [ToolResultPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs)에 `Kind`, `Description` 메타를 추가했다. [ChatWindow.AgentEventRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs)는 이제 이벤트 요약이 비어 있을 때 이 설명을 transcript fallback으로 사용한다.
|
- [PermissionRequestPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PermissionRequestPresentationCatalog.cs), [ToolResultPresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ToolResultPresentationCatalog.cs)에 `Kind`, `Description` 메타를 추가했다. [ChatWindow.AgentEventRendering.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.AgentEventRendering.cs)는 이제 이벤트 요약이 비어 있을 때 이 설명을 transcript fallback으로 사용한다.
|
||||||
- [PermissionModePresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PermissionModePresentationCatalog.cs) 에서 제거된 계획 모드 잔재를 걷어내고, [ChatWindow.PermissionPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.PermissionPresentation.cs)의 권한 선택 UI와 상단 배너도 `권한 요청 / 편집 자동 승인 / 권한 건너뛰기 / 읽기 전용`만 다루도록 정리했다.
|
- [PermissionModePresentationCatalog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/PermissionModePresentationCatalog.cs) 에서 제거된 계획 모드 잔재를 걷어내고, [ChatWindow.PermissionPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.PermissionPresentation.cs)의 권한 선택 UI와 상단 배너도 `권한 요청 / 편집 자동 승인 / 권한 건너뛰기 / 읽기 전용`만 다루도록 정리했다.
|
||||||
|
- 업데이트: 2026-04-06 09:44 (KST)
|
||||||
|
- inline interaction renderer를 `의견 요청`과 `계획 승인`으로 다시 분리했다. [ChatWindow.UserAskPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.UserAskPresentation.cs)에 사용자 질문 카드 렌더를, [ChatWindow.PlanApprovalPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.PlanApprovalPresentation.cs)에 계획 승인/상세창 연동 흐름을 옮겨 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs)의 메시지 타입 책임을 더 줄였다.
|
||||||
|
- 이번 단계까지 완료된 계획 항목은 `상태선 카탈로그화`, `권한/도구 결과 카탈로그 정교화`, `권한 UI 정리`, `의견 요청/계획 승인 renderer 분리`다. 남은 큰 축은 `footer/composer를 더 작업 바 중심으로 정리`와 `회귀 프롬프트 세트의 개발 루틴 고정`이다.
|
||||||
|
|||||||
@@ -4915,3 +4915,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
|
|||||||
- Document update: 2026-04-06 09:36 (KST) - Added `OperationalStatusPresentationCatalog.cs` and moved compact-strip / quick-strip styling decisions out of `AppStateService.GetOperationalStatusPresentation(...)`. The service now delegates runtime/status presentation shaping to a dedicated catalog instead of mixing state aggregation with UI color logic.
|
- Document update: 2026-04-06 09:36 (KST) - Added `OperationalStatusPresentationCatalog.cs` and moved compact-strip / quick-strip styling decisions out of `AppStateService.GetOperationalStatusPresentation(...)`. The service now delegates runtime/status presentation shaping to a dedicated catalog instead of mixing state aggregation with UI color logic.
|
||||||
- Document update: 2026-04-06 09:36 (KST) - Expanded `PermissionRequestPresentationCatalog.cs` and `ToolResultPresentationCatalog.cs` with `Kind` and `Description` metadata so permission/tool-result transcript entries carry typed explanatory text rather than badge-only labels. `ChatWindow.AgentEventRendering.cs` now uses that description as a fallback summary when the event summary is empty.
|
- Document update: 2026-04-06 09:36 (KST) - Expanded `PermissionRequestPresentationCatalog.cs` and `ToolResultPresentationCatalog.cs` with `Kind` and `Description` metadata so permission/tool-result transcript entries carry typed explanatory text rather than badge-only labels. `ChatWindow.AgentEventRendering.cs` now uses that description as a fallback summary when the event summary is empty.
|
||||||
- Document update: 2026-04-06 09:36 (KST) - Removed the stale `Plan` option from `PermissionModePresentationCatalog.cs` and simplified `ChatWindow.PermissionPresentation.cs` so the permission popup and top-banner presentation only expose the live modes (`권한 요청`, `편집 자동 승인`, `권한 건너뛰기`, `읽기 전용`).
|
- Document update: 2026-04-06 09:36 (KST) - Removed the stale `Plan` option from `PermissionModePresentationCatalog.cs` and simplified `ChatWindow.PermissionPresentation.cs` so the permission popup and top-banner presentation only expose the live modes (`권한 요청`, `편집 자동 승인`, `권한 건너뛰기`, `읽기 전용`).
|
||||||
|
- Document update: 2026-04-06 09:44 (KST) - Split inline interaction rendering further by replacing `ChatWindow.InlineInteractions.cs` with `ChatWindow.UserAskPresentation.cs` and `ChatWindow.PlanApprovalPresentation.cs`. User-question cards and plan approval/detail flows now live in dedicated partials instead of sharing one mixed interaction file.
|
||||||
|
- Document update: 2026-04-06 09:44 (KST) - At this point the completed structure-improvement items are: status presentation cataloging, permission/tool-result catalog enrichment, permission UI cleanup, and ask/plan renderer separation. The remaining larger tracks are footer/composer work-bar refinement and enforcing the regression prompt ritual in day-to-day development.
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
- Engine-affecting settings should be handled conservatively during parity work. If a setting changes the main execution route, approval flow, or recovery behavior without representing a stable real-world user choice, it should be moved to developer-only UI or removed from user-facing surfaces.
|
- Engine-affecting settings should be handled conservatively during parity work. If a setting changes the main execution route, approval flow, or recovery behavior without representing a stable real-world user choice, it should be moved to developer-only UI or removed from user-facing surfaces.
|
||||||
- Updated: 2026-04-06 09:36 (KST)
|
- Updated: 2026-04-06 09:36 (KST)
|
||||||
- Progressed the maintainability track by moving runtime strip styling into `OperationalStatusPresentationCatalog.cs`, expanding permission/tool-result transcript catalogs with typed descriptions, and removing the stale plan-mode presentation branch from permission UI surfaces. The next structural focus remains footer/status/composer presentation slimming and regression ritual enforcement.
|
- Progressed the maintainability track by moving runtime strip styling into `OperationalStatusPresentationCatalog.cs`, expanding permission/tool-result transcript catalogs with typed descriptions, and removing the stale plan-mode presentation branch from permission UI surfaces. The next structural focus remains footer/status/composer presentation slimming and regression ritual enforcement.
|
||||||
|
- Updated: 2026-04-06 09:44 (KST)
|
||||||
|
- Continued the maintainability track by splitting mixed inline interaction rendering into `ChatWindow.UserAskPresentation.cs` and `ChatWindow.PlanApprovalPresentation.cs`. This reduces message-type coupling inside the main window and keeps the next focus on footer/composer presentation and regression-routine formalization.
|
||||||
|
|
||||||
## Preserved History (Summary)
|
## Preserved History (Summary)
|
||||||
- Core loop guards and post-tool verification gates are already partially implemented.
|
- Core loop guards and post-tool verification gates are already partially implemented.
|
||||||
|
|||||||
183
src/AxCopilot/Views/ChatWindow.PlanApprovalPresentation.cs
Normal file
183
src/AxCopilot/Views/ChatWindow.PlanApprovalPresentation.cs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
|
||||||
|
namespace AxCopilot.Views;
|
||||||
|
|
||||||
|
public partial class ChatWindow
|
||||||
|
{
|
||||||
|
private Func<string, List<string>, Task<string?>> CreatePlanDecisionCallback()
|
||||||
|
{
|
||||||
|
return async (planSummary, options) =>
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<string?>();
|
||||||
|
var steps = TaskDecomposer.ExtractSteps(planSummary);
|
||||||
|
|
||||||
|
await Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
_pendingPlanSummary = planSummary;
|
||||||
|
_pendingPlanSteps = steps.ToList();
|
||||||
|
EnsurePlanViewerWindow();
|
||||||
|
_planViewerWindow?.LoadPlan(planSummary, steps, tcs);
|
||||||
|
ShowPlanButton(true);
|
||||||
|
AddDecisionButtons(tcs, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
var completed = await Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromMinutes(5)));
|
||||||
|
if (completed != tcs.Task)
|
||||||
|
{
|
||||||
|
await Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
_planViewerWindow?.Hide();
|
||||||
|
ResetPendingPlanPresentation();
|
||||||
|
});
|
||||||
|
return "취소";
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await tcs.Task;
|
||||||
|
var agentDecision = result;
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
agentDecision = _planViewerWindow?.BuildApprovedDecisionPayload(AgentLoopService.ApprovedPlanDecisionPrefix);
|
||||||
|
}
|
||||||
|
else if (!string.Equals(result, "취소", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !string.Equals(result, "확인", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !string.IsNullOrWhiteSpace(result))
|
||||||
|
{
|
||||||
|
agentDecision = $"수정 요청: {result.Trim()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
await Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
_planViewerWindow?.SwitchToExecutionMode();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
_planViewerWindow?.Hide();
|
||||||
|
ResetPendingPlanPresentation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return agentDecision;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsurePlanViewerWindow()
|
||||||
|
{
|
||||||
|
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_planViewerWindow = new PlanViewerWindow(this);
|
||||||
|
_planViewerWindow.Closing += (_, e) =>
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
_planViewerWindow.Hide();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowPlanButton(bool show)
|
||||||
|
{
|
||||||
|
if (!show)
|
||||||
|
{
|
||||||
|
for (int i = MoodIconPanel.Children.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (MoodIconPanel.Children[i] is Border b && b.Tag?.ToString() == "PlanBtn")
|
||||||
|
{
|
||||||
|
if (i > 0 && MoodIconPanel.Children[i - 1] is Border sep && sep.Tag?.ToString() == "PlanSep")
|
||||||
|
MoodIconPanel.Children.RemoveAt(i - 1);
|
||||||
|
if (i < MoodIconPanel.Children.Count)
|
||||||
|
MoodIconPanel.Children.RemoveAt(Math.Min(i, MoodIconPanel.Children.Count - 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in MoodIconPanel.Children)
|
||||||
|
{
|
||||||
|
if (child is Border b && b.Tag?.ToString() == "PlanBtn")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var separator = new Border
|
||||||
|
{
|
||||||
|
Width = 1,
|
||||||
|
Height = 18,
|
||||||
|
Background = TryFindResource("SeparatorColor") as Brush ?? Brushes.Gray,
|
||||||
|
Margin = new Thickness(4, 0, 4, 0),
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
Tag = "PlanSep",
|
||||||
|
};
|
||||||
|
MoodIconPanel.Children.Add(separator);
|
||||||
|
|
||||||
|
var planBtn = CreateFolderBarButton("\uE9D2", "계획", "실행 계획 보기", "#10B981");
|
||||||
|
planBtn.Tag = "PlanBtn";
|
||||||
|
planBtn.MouseLeftButtonUp += (_, e) =>
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
if (string.IsNullOrWhiteSpace(_pendingPlanSummary) && _pendingPlanSteps.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EnsurePlanViewerWindow();
|
||||||
|
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_planViewerWindow.PlanText)
|
||||||
|
|| _planViewerWindow.PlanText != (_pendingPlanSummary ?? string.Empty)
|
||||||
|
|| !_planViewerWindow.Steps.SequenceEqual(_pendingPlanSteps))
|
||||||
|
{
|
||||||
|
_planViewerWindow.LoadPlanPreview(_pendingPlanSummary ?? "", _pendingPlanSteps);
|
||||||
|
}
|
||||||
|
_planViewerWindow.Show();
|
||||||
|
_planViewerWindow.Activate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
MoodIconPanel.Children.Add(planBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePlanViewerStep(AgentEvent evt)
|
||||||
|
{
|
||||||
|
if (_planViewerWindow == null || !IsWindowAlive(_planViewerWindow))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (evt.StepCurrent > 0)
|
||||||
|
_planViewerWindow.UpdateCurrentStep(evt.StepCurrent - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CompletePlanViewer()
|
||||||
|
{
|
||||||
|
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
||||||
|
_planViewerWindow.MarkComplete();
|
||||||
|
ResetPendingPlanPresentation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetPendingPlanPresentation()
|
||||||
|
{
|
||||||
|
_pendingPlanSummary = null;
|
||||||
|
_pendingPlanSteps.Clear();
|
||||||
|
ShowPlanButton(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsWindowAlive(Window? w)
|
||||||
|
{
|
||||||
|
if (w == null)
|
||||||
|
return false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var _ = w.IsVisible;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ using System.Windows.Controls;
|
|||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
using AxCopilot.Services.Agent;
|
|
||||||
|
|
||||||
namespace AxCopilot.Views;
|
namespace AxCopilot.Views;
|
||||||
|
|
||||||
@@ -250,175 +249,4 @@ public partial class ChatWindow
|
|||||||
inputBox.Focus();
|
inputBox.Focus();
|
||||||
inputBox.CaretIndex = inputBox.Text.Length;
|
inputBox.CaretIndex = inputBox.Text.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Func<string, List<string>, Task<string?>> CreatePlanDecisionCallback()
|
|
||||||
{
|
|
||||||
return async (planSummary, options) =>
|
|
||||||
{
|
|
||||||
var tcs = new TaskCompletionSource<string?>();
|
|
||||||
var steps = TaskDecomposer.ExtractSteps(planSummary);
|
|
||||||
|
|
||||||
await Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
_pendingPlanSummary = planSummary;
|
|
||||||
_pendingPlanSteps = steps.ToList();
|
|
||||||
EnsurePlanViewerWindow();
|
|
||||||
_planViewerWindow?.LoadPlan(planSummary, steps, tcs);
|
|
||||||
ShowPlanButton(true);
|
|
||||||
AddDecisionButtons(tcs, options);
|
|
||||||
});
|
|
||||||
|
|
||||||
var completed = await Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromMinutes(5)));
|
|
||||||
if (completed != tcs.Task)
|
|
||||||
{
|
|
||||||
await Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
_planViewerWindow?.Hide();
|
|
||||||
ResetPendingPlanPresentation();
|
|
||||||
});
|
|
||||||
return "취소";
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await tcs.Task;
|
|
||||||
var agentDecision = result;
|
|
||||||
if (result == null)
|
|
||||||
{
|
|
||||||
agentDecision = _planViewerWindow?.BuildApprovedDecisionPayload(AgentLoopService.ApprovedPlanDecisionPrefix);
|
|
||||||
}
|
|
||||||
else if (!string.Equals(result, "취소", StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& !string.Equals(result, "승인", StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& !string.IsNullOrWhiteSpace(result))
|
|
||||||
{
|
|
||||||
agentDecision = $"수정 요청: {result.Trim()}";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == null)
|
|
||||||
{
|
|
||||||
await Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
_planViewerWindow?.SwitchToExecutionMode();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
_planViewerWindow?.Hide();
|
|
||||||
ResetPendingPlanPresentation();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return agentDecision;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsurePlanViewerWindow()
|
|
||||||
{
|
|
||||||
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_planViewerWindow = new PlanViewerWindow(this);
|
|
||||||
_planViewerWindow.Closing += (_, e) =>
|
|
||||||
{
|
|
||||||
e.Cancel = true;
|
|
||||||
_planViewerWindow.Hide();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowPlanButton(bool show)
|
|
||||||
{
|
|
||||||
if (!show)
|
|
||||||
{
|
|
||||||
for (int i = MoodIconPanel.Children.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
if (MoodIconPanel.Children[i] is Border b && b.Tag?.ToString() == "PlanBtn")
|
|
||||||
{
|
|
||||||
if (i > 0 && MoodIconPanel.Children[i - 1] is Border sep && sep.Tag?.ToString() == "PlanSep")
|
|
||||||
MoodIconPanel.Children.RemoveAt(i - 1);
|
|
||||||
if (i < MoodIconPanel.Children.Count)
|
|
||||||
MoodIconPanel.Children.RemoveAt(Math.Min(i, MoodIconPanel.Children.Count - 1));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var child in MoodIconPanel.Children)
|
|
||||||
{
|
|
||||||
if (child is Border b && b.Tag?.ToString() == "PlanBtn")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var separator = new Border
|
|
||||||
{
|
|
||||||
Width = 1,
|
|
||||||
Height = 18,
|
|
||||||
Background = TryFindResource("SeparatorColor") as Brush ?? Brushes.Gray,
|
|
||||||
Margin = new Thickness(4, 0, 4, 0),
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
Tag = "PlanSep",
|
|
||||||
};
|
|
||||||
MoodIconPanel.Children.Add(separator);
|
|
||||||
|
|
||||||
var planBtn = CreateFolderBarButton("\uE9D2", "계획", "실행 계획 보기", "#10B981");
|
|
||||||
planBtn.Tag = "PlanBtn";
|
|
||||||
planBtn.MouseLeftButtonUp += (_, e) =>
|
|
||||||
{
|
|
||||||
e.Handled = true;
|
|
||||||
if (string.IsNullOrWhiteSpace(_pendingPlanSummary) && _pendingPlanSteps.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
EnsurePlanViewerWindow();
|
|
||||||
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(_planViewerWindow.PlanText)
|
|
||||||
|| _planViewerWindow.PlanText != (_pendingPlanSummary ?? string.Empty)
|
|
||||||
|| !_planViewerWindow.Steps.SequenceEqual(_pendingPlanSteps))
|
|
||||||
{
|
|
||||||
_planViewerWindow.LoadPlanPreview(_pendingPlanSummary ?? "", _pendingPlanSteps);
|
|
||||||
}
|
|
||||||
_planViewerWindow.Show();
|
|
||||||
_planViewerWindow.Activate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
MoodIconPanel.Children.Add(planBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdatePlanViewerStep(AgentEvent evt)
|
|
||||||
{
|
|
||||||
if (_planViewerWindow == null || !IsWindowAlive(_planViewerWindow))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (evt.StepCurrent > 0)
|
|
||||||
_planViewerWindow.UpdateCurrentStep(evt.StepCurrent - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CompletePlanViewer()
|
|
||||||
{
|
|
||||||
if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow))
|
|
||||||
_planViewerWindow.MarkComplete();
|
|
||||||
ResetPendingPlanPresentation();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetPendingPlanPresentation()
|
|
||||||
{
|
|
||||||
_pendingPlanSummary = null;
|
|
||||||
_pendingPlanSteps.Clear();
|
|
||||||
ShowPlanButton(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsWindowAlive(Window? w)
|
|
||||||
{
|
|
||||||
if (w == null)
|
|
||||||
return false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var _ = w.IsVisible;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user