148 lines
5.8 KiB
C#
148 lines
5.8 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
|
|
namespace AxCopilot.Views;
|
|
|
|
/// <summary>
|
|
/// Ctrl+Shift+P로 호출되는 커맨드 팔레트.
|
|
/// 모든 기능에 빠르게 접근할 수 있는 통합 명령 검색 창입니다.
|
|
/// </summary>
|
|
public partial class CommandPaletteWindow : Window
|
|
{
|
|
private readonly Action<string>? _onExecute;
|
|
private readonly List<CommandEntry> _commands = new();
|
|
// 향후 키보드 탐색용
|
|
// private int _selectedIndex = -1;
|
|
|
|
public string? SelectedCommand { get; private set; }
|
|
|
|
public CommandPaletteWindow(Action<string>? 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);
|
|
}
|