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, Task> CreatePlanDecisionCallback() { return async (planSummary, options) => { var tcs = new TaskCompletionSource(); var steps = TaskDecomposer.ExtractSteps(planSummary); await Dispatcher.InvokeAsync(() => { _pendingPlanSummary = planSummary; _pendingPlanSteps = steps.ToList(); EnsurePlanViewerWindow(); _planViewerWindow?.LoadPlan(planSummary, steps, tcs); ShowPlanButton(true); AddDecisionButtons(tcs, options); // 플랜 창 자동 표시 (사용자가 직접 BtnPlanViewer 클릭 안 해도 됨) if (_planViewerWindow != null && IsWindowAlive(_planViewerWindow)) { _planViewerWindow.Show(); _planViewerWindow.Activate(); } }); 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.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) { // 레거시: 이전 세션에서 동적 주입된 MoodIconPanel 칩 정리 try { for (int i = MoodIconPanel.Children.Count - 1; i >= 0; i--) { if (MoodIconPanel.Children[i] is Border b && b.Tag?.ToString() == "PlanBtn") { // separator 먼저 제거 (인덱스 시프트 전) int sepIdx = i - 1; MoodIconPanel.Children.RemoveAt(i); // PlanBtn 제거 if (sepIdx >= 0 && sepIdx < MoodIconPanel.Children.Count && MoodIconPanel.Children[sepIdx] is Border sep && sep.Tag?.ToString() == "PlanSep") MoodIconPanel.Children.RemoveAt(sepIdx); break; } } } catch { // 레거시 정리 실패 시에도 버튼 토글은 반드시 실행 } // StatusBar의 XAML 선언 계획 버튼 토글 BtnPlanViewer.Visibility = show ? Visibility.Visible : Visibility.Collapsed; } private void BtnPlanViewer_Click(object sender, MouseButtonEventArgs 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(); } } /// 계획 버튼 호버 효과 초기화 (Loaded에서 호출). private void InitPlanButtonHover() { var hoverBg = TryFindResource("ItemHoverBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); var normalBg = TryFindResource("HintBackground") as Brush ?? Brushes.Transparent; BtnPlanViewer.MouseEnter += (_, _) => BtnPlanViewer.Background = hoverBg; BtnPlanViewer.MouseLeave += (_, _) => BtnPlanViewer.Background = normalBg; } 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; } } }