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); }); 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; } } }