using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace AxCopilot.Views; /// /// Ctrl+Shift+P로 호출되는 커맨드 팔레트. /// 모든 기능에 빠르게 접근할 수 있는 통합 명령 검색 창입니다. /// public partial class CommandPaletteWindow : Window { private readonly Action? _onExecute; private readonly List _commands = new(); // 향후 키보드 탐색용 // private int _selectedIndex = -1; public string? SelectedCommand { get; private set; } public CommandPaletteWindow(Action? onExecute = null) { InitializeComponent(); _onExecute = onExecute; RegisterCommands(); RenderItems(""); Loaded += (_, _) => { SearchBox.Focus(); }; } private void RegisterCommands() { // 탭 전환 _commands.Add(new("Chat 탭으로 전환", "탭 전환", "\uE8BD", "tab:chat")); _commands.Add(new("Cowork 탭으로 전환", "탭 전환", "\uE8BD", "tab:cowork")); _commands.Add(new("Code 탭으로 전환", "탭 전환", "\uE8BD", "tab:code")); // 대화 관리 _commands.Add(new("새 대화 시작", "대화", "\uE710", "new_conversation")); _commands.Add(new("대화 검색", "대화", "\uE721", "search_conversation")); // 모델 변경 _commands.Add(new("모델 변경", "설정", "\uEA86", "change_model")); // 프리셋 _commands.Add(new("프리셋 선택", "프리셋", "\uE71D", "select_preset")); // 설정 _commands.Add(new("설정 열기", "앱", "\uE713", "open_settings")); _commands.Add(new("테마 변경", "앱", "\uE790", "change_theme")); _commands.Add(new("통계 보기", "앱", "\uE9F9", "open_statistics")); // 파일 _commands.Add(new("작업 폴더 변경", "파일", "\uED25", "change_folder")); _commands.Add(new("파일 탐색기 열기", "파일", "\uE8B7", "open_file_explorer")); // 에이전트 _commands.Add(new("개발자 모드 토글", "에이전트", "\uE71C", "toggle_devmode")); _commands.Add(new("감사 로그 보기", "에이전트", "\uE9D9", "open_audit_log")); // 도구 _commands.Add(new("클립보드에서 붙여넣기", "도구", "\uE77F", "paste_clipboard")); _commands.Add(new("대화 내보내기", "도구", "\uE78C", "export_conversation")); } private void RenderItems(string query) { ResultPanel.Children.Clear(); var filtered = string.IsNullOrWhiteSpace(query) ? _commands : _commands.Where(c => c.Label.Contains(query, StringComparison.OrdinalIgnoreCase) || c.Category.Contains(query, StringComparison.OrdinalIgnoreCase) || (Core.FuzzyEngine.IsChosung(query) && Core.FuzzyEngine.ContainsChosung(c.Label, query))).ToList(); for (int i = 0; i < filtered.Count; i++) { var cmd = filtered[i]; var idx = i; var sp = new StackPanel { Orientation = Orientation.Horizontal }; sp.Children.Add(new TextBlock { Text = cmd.Icon, FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 14, Foreground = FindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 10, 0), }); var textSp = new StackPanel(); textSp.Children.Add(new TextBlock { Text = cmd.Label, FontSize = 13, Foreground = FindResource("PrimaryText") as Brush ?? Brushes.White, }); textSp.Children.Add(new TextBlock { Text = cmd.Category, FontSize = 10.5, Foreground = FindResource("SecondaryText") as Brush ?? Brushes.Gray, }); sp.Children.Add(textSp); var item = new Border { Child = sp, Background = Brushes.Transparent, CornerRadius = new CornerRadius(8), Cursor = Cursors.Hand, Padding = new Thickness(10, 8, 14, 8), Margin = new Thickness(0, 1, 0, 1), }; var hoverBg = FindResource("HintBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); item.MouseEnter += (s, _) => { if (s is Border b) b.Background = hoverBg; }; item.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; }; item.MouseLeftButtonUp += (_, _) => ExecuteCommand(cmd.CommandId); ResultPanel.Children.Add(item); } } private void ExecuteCommand(string commandId) { SelectedCommand = commandId; _onExecute?.Invoke(commandId); Close(); } private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) { RenderItems(SearchBox.Text); } private void Window_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Escape) { Close(); e.Handled = true; } else if (e.Key == Key.Enter && ResultPanel.Children.Count > 0) { // 첫 번째 항목 실행 var filtered = string.IsNullOrWhiteSpace(SearchBox.Text) ? _commands : _commands.Where(c => c.Label.Contains(SearchBox.Text, StringComparison.OrdinalIgnoreCase) || c.Category.Contains(SearchBox.Text, StringComparison.OrdinalIgnoreCase)).ToList(); if (filtered.Count > 0) ExecuteCommand(filtered[0].CommandId); e.Handled = true; } } private void Window_Deactivated(object sender, EventArgs e) => Close(); private record CommandEntry(string Label, string Category, string Icon, string CommandId); }