AX Agent 코워크·코드 흐름과 컨텍스트 관리를 claude-code 기준으로 대폭 정리
- 코워크·코드 프롬프트, 도구 선택, 문서 생성/검증 흐름을 claude-code 동등 품질 기준으로 재정렬함 - OpenAI/vLLM 경로의 오래된 tool history를 평탄화하고 최근 이력만 구조화해 컨텍스트 직렬화를 경량화함 - AX Agent UI를 테마 기준으로 재구성하고 플랜 승인/오버레이/이벤트 렌더링/명령 입력 상호작용을 개선함 - 파일 후보 제안, 반복 경로 정체 복구, LSP 보강, 문서·PPT 처리 개선, 설정/서비스 인터페이스 정리를 함께 반영함 - README.md 및 docs/DEVELOPMENT.md를 작업 시점별로 갱신함 - 검증: 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:
257
src/AxCopilot/Views/ToolApprovalWindow.cs
Normal file
257
src/AxCopilot/Views/ToolApprovalWindow.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Effects;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
/// <summary>
|
||||
/// 도구 실행 승인을 위한 간결한 별도 다이얼로그 창.
|
||||
/// PlanViewerV2(전체 계획 뷰어)와 분리하여 도구 단위 승인에 사용합니다.
|
||||
/// </summary>
|
||||
internal sealed class ToolApprovalWindow : Window
|
||||
{
|
||||
private string? _result;
|
||||
|
||||
private ToolApprovalWindow(string message, List<string> options)
|
||||
{
|
||||
Width = 480;
|
||||
MinWidth = 400;
|
||||
MaxWidth = 600;
|
||||
SizeToContent = SizeToContent.Height;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
ResizeMode = ResizeMode.NoResize;
|
||||
WindowStyle = WindowStyle.None;
|
||||
AllowsTransparency = true;
|
||||
Background = Brushes.Transparent;
|
||||
ShowInTaskbar = false;
|
||||
Topmost = true;
|
||||
|
||||
var bg = Application.Current.TryFindResource("LauncherBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E));
|
||||
var primary = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var secondary = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var accent = Application.Current.TryFindResource("AccentColor") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
|
||||
var border = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
var itemBg = Application.Current.TryFindResource("ItemBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40));
|
||||
|
||||
var root = new Border
|
||||
{
|
||||
Background = bg,
|
||||
BorderBrush = border,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(14),
|
||||
Padding = new Thickness(20, 16, 20, 16),
|
||||
Effect = new DropShadowEffect
|
||||
{
|
||||
BlurRadius = 20,
|
||||
ShadowDepth = 4,
|
||||
Opacity = 0.35,
|
||||
Color = Colors.Black,
|
||||
},
|
||||
};
|
||||
|
||||
var stack = new StackPanel();
|
||||
|
||||
// Header
|
||||
var header = new Grid { Margin = new Thickness(0, 0, 0, 12) };
|
||||
header.MouseLeftButtonDown += (_, _) => { try { DragMove(); } catch { } };
|
||||
header.Children.Add(new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new TextBlock
|
||||
{
|
||||
Text = "\uE946", // Shield icon
|
||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 15,
|
||||
Foreground = accent,
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
},
|
||||
new TextBlock
|
||||
{
|
||||
Text = "실행 확인",
|
||||
FontSize = 13.5,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primary,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close button
|
||||
var close = new Border
|
||||
{
|
||||
Width = 26,
|
||||
Height = 26,
|
||||
CornerRadius = new CornerRadius(7),
|
||||
Background = Brushes.Transparent,
|
||||
Cursor = Cursors.Hand,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = "\uE8BB",
|
||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 10,
|
||||
Foreground = secondary,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
},
|
||||
};
|
||||
close.MouseLeftButtonUp += (_, _) => { _result = "취소"; Close(); };
|
||||
close.MouseEnter += (_, _) => close.Background = new SolidColorBrush(Color.FromArgb(0x20, 0xFF, 0xFF, 0xFF));
|
||||
close.MouseLeave += (_, _) => close.Background = Brushes.Transparent;
|
||||
header.Children.Add(close);
|
||||
stack.Children.Add(header);
|
||||
|
||||
// Message content
|
||||
var msgBorder = new Border
|
||||
{
|
||||
Background = itemBg,
|
||||
CornerRadius = new CornerRadius(10),
|
||||
Padding = new Thickness(14, 11, 14, 11),
|
||||
Margin = new Thickness(0, 0, 0, 14),
|
||||
};
|
||||
|
||||
var msgText = new TextBlock
|
||||
{
|
||||
Text = message,
|
||||
FontSize = 12.5,
|
||||
FontFamily = new FontFamily("Segoe UI"),
|
||||
Foreground = primary,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
LineHeight = 19,
|
||||
};
|
||||
msgBorder.Child = msgText;
|
||||
stack.Children.Add(msgBorder);
|
||||
|
||||
// Buttons
|
||||
var btnPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
};
|
||||
|
||||
foreach (var option in options)
|
||||
{
|
||||
var btn = CreateOptionButton(option, primary, secondary, accent, bg);
|
||||
btn.MouseLeftButtonUp += (_, _) => { _result = option; Close(); };
|
||||
btnPanel.Children.Add(btn);
|
||||
}
|
||||
stack.Children.Add(btnPanel);
|
||||
|
||||
root.Child = stack;
|
||||
Content = root;
|
||||
|
||||
// Entrance animation
|
||||
root.Opacity = 0;
|
||||
root.RenderTransformOrigin = new Point(0.5, 0.5);
|
||||
root.RenderTransform = new ScaleTransform(0.96, 0.96);
|
||||
Loaded += (_, _) =>
|
||||
{
|
||||
root.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(140)));
|
||||
var sx = new DoubleAnimation(0.96, 1, TimeSpan.FromMilliseconds(180))
|
||||
{
|
||||
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut },
|
||||
};
|
||||
var sy = new DoubleAnimation(0.96, 1, TimeSpan.FromMilliseconds(180))
|
||||
{
|
||||
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut },
|
||||
};
|
||||
((ScaleTransform)root.RenderTransform).BeginAnimation(ScaleTransform.ScaleXProperty, sx);
|
||||
((ScaleTransform)root.RenderTransform).BeginAnimation(ScaleTransform.ScaleYProperty, sy);
|
||||
};
|
||||
|
||||
// ESC to cancel
|
||||
KeyDown += (_, e) =>
|
||||
{
|
||||
if (e.Key == Key.Escape)
|
||||
{
|
||||
_result = "취소";
|
||||
Close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Border CreateOptionButton(string label, Brush primary, Brush secondary, Brush accent, Brush bg)
|
||||
{
|
||||
Brush foreground, background, borderBrush;
|
||||
switch (label)
|
||||
{
|
||||
case "확인":
|
||||
case "승인":
|
||||
foreground = Brushes.White;
|
||||
background = accent;
|
||||
borderBrush = accent;
|
||||
break;
|
||||
case "취소":
|
||||
case "중단":
|
||||
foreground = new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
|
||||
background = Brushes.Transparent;
|
||||
borderBrush = new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
|
||||
break;
|
||||
default:
|
||||
foreground = primary;
|
||||
background = Brushes.Transparent;
|
||||
borderBrush = secondary;
|
||||
break;
|
||||
}
|
||||
|
||||
var btn = new Border
|
||||
{
|
||||
MinWidth = 70,
|
||||
Height = 32,
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Background = background,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(14, 0, 14, 0),
|
||||
Margin = new Thickness(6, 0, 0, 0),
|
||||
Cursor = Cursors.Hand,
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = foreground,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
},
|
||||
};
|
||||
|
||||
btn.MouseEnter += (_, _) => btn.Opacity = 0.85;
|
||||
btn.MouseLeave += (_, _) => btn.Opacity = 1.0;
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
/// <summary>도구 승인 다이얼로그를 표시하고 결과를 반환합니다.</summary>
|
||||
internal static string? Show(Window? owner, string message, List<string> options)
|
||||
{
|
||||
var dialog = new ToolApprovalWindow(message, options);
|
||||
if (owner != null && IsWindowAlive(owner))
|
||||
{
|
||||
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
dialog.Owner = owner;
|
||||
}
|
||||
|
||||
dialog.ShowDialog();
|
||||
return dialog._result;
|
||||
}
|
||||
|
||||
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