using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; using AxCopilot.Models; namespace AxCopilot.Views; /// 커스텀 프리셋 추가/편집 다이얼로그. internal sealed class CustomPresetDialog : Window { private readonly TextBox _nameBox; private readonly TextBox _descBox; private readonly TextBox _promptBox; private readonly ComboBox _tabCombo; private string _selectedColor; private string _selectedSymbol; private Border? _iconPreview; private TextBlock? _iconPreviewText; public string PresetName => _nameBox.Text.Trim(); public string PresetDescription => _descBox.Text.Trim(); public string PresetSystemPrompt => _promptBox.Text.Trim(); public string PresetTab => (_tabCombo.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "Chat"; public string PresetColor => _selectedColor; public string PresetSymbol => _selectedSymbol; // 사전 정의 색상 팔레트 private static readonly (string Label, string Hex)[] Colors = { ("인디고", "#6366F1"), ("블루", "#3B82F6"), ("티일", "#14B8A6"), ("그린", "#22C55E"), ("옐로우", "#EAB308"), ("오렌지", "#F97316"), ("레드", "#EF4444"), ("핑크", "#EC4899"), ("퍼플", "#A855F7"), ("슬레이트", "#64748B"), }; // 아이콘 셋트 — (카테고리, 아이콘 목록) private static readonly (string Category, (string Label, string Symbol)[] Icons)[] IconSets = { ("업무", new[] { ("일반", "\uE771"), ("문서", "\uE8A5"), ("메일", "\uE715"), ("캘린더", "\uE787"), ("차트", "\uE9D9"), ("발표", "\uE7F4"), ("계산기", "\uE8EF"), ("메모", "\uE70B"), }), ("기술", new[] { ("코드", "\uE943"), ("설정", "\uE713"), ("데이터", "\uEA86"), ("검색", "\uE721"), ("보안", "\uE72E"), ("서버", "\uE839"), ("버그", "\uEBE8"), ("배포", "\uE7F8"), }), ("커뮤니케이션", new[] { ("대화", "\uE8BD"), ("사람", "\uE77B"), ("팀", "\uE716"), ("전화", "\uE717"), ("알림", "\uEA8F"), ("공유", "\uE72D"), ("피드백", "\uE939"), ("질문", "\uE897"), }), ("콘텐츠", new[] { ("사진", "\uE722"), ("동영상", "\uE714"), ("음악", "\uE8D6"), ("글쓰기", "\uE70F"), ("책", "\uE82D"), ("웹", "\uE774"), ("디자인", "\uE790"), ("다운로드", "\uE896"), }), ("분석", new[] { ("인사이트", "\uE9D9"), ("대시보드", "\uE809"), ("리포트", "\uE9F9"),("트렌드", "\uE8A1"), ("필터", "\uE71C"), ("목표", "\uE7C1"), ("비교", "\uE8FD"), ("측정", "\uE7C8"), }), }; public CustomPresetDialog( string existingName = "", string existingDesc = "", string existingPrompt = "", string existingColor = "#6366F1", string existingSymbol = "\uE713", string existingTab = "Chat") { bool isEdit = !string.IsNullOrEmpty(existingName); Title = isEdit ? "프리셋 편집" : "프리셋 추가"; Width = 520; SizeToContent = SizeToContent.Height; WindowStartupLocation = WindowStartupLocation.CenterOwner; ResizeMode = ResizeMode.NoResize; WindowStyle = WindowStyle.None; AllowsTransparency = true; Background = Brushes.Transparent; _selectedColor = existingColor; _selectedSymbol = existingSymbol; var bgBrush = Application.Current.TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E)); var primaryText = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White; var secondaryText = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush ?? Brushes.Blue; var itemBg = Application.Current.TryFindResource("ItemBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40)); var borderBrush = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var root = new Border { Background = bgBrush, CornerRadius = new CornerRadius(16), BorderBrush = borderBrush, BorderThickness = new Thickness(1), Padding = new Thickness(28, 24, 28, 24), Effect = new System.Windows.Media.Effects.DropShadowEffect { BlurRadius = 24, ShadowDepth = 4, Opacity = 0.3, Color = System.Windows.Media.Colors.Black, }, }; var stack = new StackPanel(); // 헤더 var header = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 20) }; header.Children.Add(new TextBlock { Text = "\uE710", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 18, Foreground = accentBrush, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 10, 0), }); header.Children.Add(new TextBlock { Text = isEdit ? "프리셋 편집" : "새 커스텀 프리셋", FontSize = 17, FontWeight = FontWeights.Bold, Foreground = primaryText, VerticalAlignment = VerticalAlignment.Center, }); stack.Children.Add(header); // ── 프리셋 이름 ── AddLabel(stack, "프리셋 이름", primaryText); AddHint(stack, "대화 주제 버튼에 표시될 이름입니다", secondaryText); _nameBox = CreateTextBox(existingName, primaryText, itemBg, accentBrush, borderBrush); stack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _nameBox }); // ── 설명 ── AddSeparator(stack, borderBrush); AddLabel(stack, "설명", primaryText); AddHint(stack, "프리셋 카드 하단에 표시될 짧은 설명입니다", secondaryText); _descBox = CreateTextBox(existingDesc, primaryText, itemBg, accentBrush, borderBrush); stack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _descBox }); // ── 아이콘 + 색상 ── AddSeparator(stack, borderBrush); AddLabel(stack, "아이콘 & 배경색", primaryText); var iconColorRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 8) }; // 아이콘 미리보기 (클릭하면 아이콘 선택 팝업) _iconPreview = new Border { Width = 48, Height = 48, CornerRadius = new CornerRadius(24), Background = new SolidColorBrush(ParseColor(_selectedColor)) { Opacity = 0.2 }, Cursor = Cursors.Hand, Margin = new Thickness(0, 0, 16, 0), ToolTip = "아이콘 선택", }; _iconPreviewText = new TextBlock { Text = _selectedSymbol, FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 20, Foreground = BrushFromHex(_selectedColor), HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, }; _iconPreview.Child = _iconPreviewText; _iconPreview.MouseLeftButtonDown += (_, _) => ShowIconPickerPopup(); iconColorRow.Children.Add(_iconPreview); // 탭 선택 var tabStack = new StackPanel { Margin = new Thickness(0, 0, 16, 0) }; tabStack.Children.Add(new TextBlock { Text = "탭", FontSize = 11, Foreground = secondaryText, Margin = new Thickness(0, 0, 0, 4) }); _tabCombo = new ComboBox { Width = 100, FontSize = 12, Foreground = primaryText, Background = itemBg, BorderBrush = borderBrush, BorderThickness = new Thickness(1), Padding = new Thickness(6, 4, 6, 4), }; _tabCombo.Items.Add(new ComboBoxItem { Content = "Chat", Tag = "Chat", IsSelected = existingTab == "Chat" }); _tabCombo.Items.Add(new ComboBoxItem { Content = "Cowork", Tag = "Cowork", IsSelected = existingTab == "Cowork" }); tabStack.Children.Add(_tabCombo); iconColorRow.Children.Add(tabStack); // 색상 팔레트 var colorStack = new StackPanel(); colorStack.Children.Add(new TextBlock { Text = "배경색", FontSize = 11, Foreground = secondaryText, Margin = new Thickness(0, 0, 0, 4) }); var colorPanel = new WrapPanel(); foreach (var (label, hex) in Colors) { var colorHex = hex; var swatch = new Border { Width = 22, Height = 22, CornerRadius = new CornerRadius(11), Background = BrushFromHex(hex), Margin = new Thickness(0, 0, 5, 4), Cursor = Cursors.Hand, BorderThickness = new Thickness(2), BorderBrush = hex == _selectedColor ? primaryText : Brushes.Transparent, ToolTip = label, }; swatch.MouseLeftButtonDown += (s, _) => { _selectedColor = colorHex; foreach (var child in colorPanel.Children) if (child is Border b) b.BorderBrush = Brushes.Transparent; if (s is Border clicked) clicked.BorderBrush = primaryText; UpdateIconPreview(); }; colorPanel.Children.Add(swatch); } colorStack.Children.Add(colorPanel); iconColorRow.Children.Add(colorStack); stack.Children.Add(iconColorRow); // ── 시스템 프롬프트 ── AddSeparator(stack, borderBrush); AddLabel(stack, "시스템 프롬프트", primaryText); AddHint(stack, "AI가 이 프리셋에서 따를 지침입니다. 비워두면 기본 프롬프트가 사용됩니다.", secondaryText); _promptBox = CreateTextBox(existingPrompt, primaryText, itemBg, accentBrush, borderBrush, multiline: true); stack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _promptBox }); // 글자 수 var charCount = new TextBlock { FontSize = 10.5, Foreground = secondaryText, Margin = new Thickness(0, 6, 0, 0), HorizontalAlignment = HorizontalAlignment.Right, }; UpdateCharCount(charCount); _promptBox.TextChanged += (_, _) => UpdateCharCount(charCount); stack.Children.Add(charCount); // ── 버튼 바 ── var btnBar = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(0, 20, 0, 0), }; var cancelBtn = new Button { Content = "취소", Width = 80, Padding = new Thickness(0, 8, 0, 8), Margin = new Thickness(0, 0, 10, 0), Background = Brushes.Transparent, Foreground = secondaryText, BorderBrush = borderBrush, BorderThickness = new Thickness(1), Cursor = Cursors.Hand, FontSize = 13, }; cancelBtn.Click += (_, _) => { DialogResult = false; Close(); }; btnBar.Children.Add(cancelBtn); var okBtn = new Button { Content = isEdit ? "저장" : "추가", Width = 80, Padding = new Thickness(0, 8, 0, 8), Background = accentBrush, Foreground = Brushes.White, BorderThickness = new Thickness(0), Cursor = Cursors.Hand, FontSize = 13, FontWeight = FontWeights.SemiBold, }; okBtn.Click += (_, _) => { if (string.IsNullOrWhiteSpace(_nameBox.Text)) { CustomMessageBox.Show("프리셋 이름을 입력하세요.", "입력 오류", MessageBoxButton.OK, MessageBoxImage.Warning); _nameBox.Focus(); return; } DialogResult = true; Close(); }; btnBar.Children.Add(okBtn); stack.Children.Add(btnBar); root.Child = stack; Content = root; KeyDown += (_, ke) => { if (ke.Key == Key.Escape) { DialogResult = false; Close(); } }; Loaded += (_, _) => { _nameBox.Focus(); _nameBox.SelectAll(); }; root.MouseLeftButtonDown += (_, me) => { if (me.LeftButton == MouseButtonState.Pressed) try { DragMove(); } catch { } }; } private void UpdateIconPreview() { if (_iconPreview == null || _iconPreviewText == null) return; _iconPreview.Background = new SolidColorBrush(ParseColor(_selectedColor)) { Opacity = 0.2 }; _iconPreviewText.Text = _selectedSymbol; _iconPreviewText.Foreground = BrushFromHex(_selectedColor); } // ── 아이콘 피커 팝업 ──────────────────────────────────────────── private void ShowIconPickerPopup() { var bgBrush = Application.Current.TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E)); var primaryText = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White; var secondaryText = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var borderBrush = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var hoverBg = Application.Current.TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Transparent; var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush ?? Brushes.Blue; // 별도 Window로 표시 (모달 다이얼로그 위에서 Popup 충돌 방지) var pickerWin = new Window { Title = "아이콘 선택", Width = 340, SizeToContent = SizeToContent.Height, WindowStartupLocation = WindowStartupLocation.CenterOwner, Owner = this, ResizeMode = ResizeMode.NoResize, WindowStyle = WindowStyle.None, AllowsTransparency = true, Background = Brushes.Transparent, }; var popupBorder = new Border { Background = bgBrush, CornerRadius = new CornerRadius(12), BorderBrush = borderBrush, BorderThickness = new Thickness(1), Padding = new Thickness(12), Effect = new System.Windows.Media.Effects.DropShadowEffect { BlurRadius = 16, ShadowDepth = 3, Opacity = 0.3, Color = System.Windows.Media.Colors.Black, }, }; var mainStack = new StackPanel(); // 타이틀 + 닫기 var titleRow = new Grid(); titleRow.Children.Add(new TextBlock { Text = "아이콘 선택", FontSize = 14, FontWeight = FontWeights.SemiBold, Foreground = primaryText, Margin = new Thickness(0, 0, 0, 10), HorizontalAlignment = HorizontalAlignment.Left, }); var closeBtn = new TextBlock { Text = "\uE711", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 12, Foreground = secondaryText, Cursor = Cursors.Hand, HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Top, }; closeBtn.MouseLeftButtonDown += (_, _) => pickerWin.Close(); titleRow.Children.Add(closeBtn); mainStack.Children.Add(titleRow); // 드래그로 창 이동 popupBorder.MouseLeftButtonDown += (_, e) => { try { pickerWin.DragMove(); } catch { } }; // 카테고리별 아이콘 그리드 foreach (var (category, icons) in IconSets) { mainStack.Children.Add(new TextBlock { Text = category, FontSize = 11, FontWeight = FontWeights.SemiBold, Foreground = secondaryText, Margin = new Thickness(0, 6, 0, 4), }); var wrapPanel = new WrapPanel(); foreach (var (label, symbol) in icons) { var capturedSymbol = symbol; var isSelected = _selectedSymbol == symbol; var iconBtn = new Border { Width = 36, Height = 36, CornerRadius = new CornerRadius(8), Background = isSelected ? new SolidColorBrush(ParseColor(_selectedColor)) { Opacity = 0.2 } : Brushes.Transparent, BorderBrush = isSelected ? accentBrush : Brushes.Transparent, BorderThickness = new Thickness(isSelected ? 1.5 : 0), Cursor = Cursors.Hand, Margin = new Thickness(0, 0, 4, 4), ToolTip = label, }; iconBtn.Child = new TextBlock { Text = symbol, FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 16, Foreground = isSelected ? BrushFromHex(_selectedColor) : primaryText, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, }; iconBtn.MouseEnter += (s, _) => { if (s is Border b && !(_selectedSymbol == capturedSymbol)) b.Background = hoverBg; }; iconBtn.MouseLeave += (s, _) => { if (s is Border b && !(_selectedSymbol == capturedSymbol)) b.Background = Brushes.Transparent; }; iconBtn.MouseLeftButtonDown += (_, e) => { e.Handled = true; _selectedSymbol = capturedSymbol; UpdateIconPreview(); pickerWin.Close(); }; wrapPanel.Children.Add(iconBtn); } mainStack.Children.Add(wrapPanel); } popupBorder.Child = mainStack; pickerWin.Content = popupBorder; pickerWin.ShowDialog(); } private static void AddLabel(StackPanel parent, string text, Brush fg) => parent.Children.Add(new TextBlock { Text = text, FontSize = 12, FontWeight = FontWeights.SemiBold, Foreground = fg, Margin = new Thickness(0, 0, 0, 6), }); private static void AddHint(StackPanel parent, string text, Brush fg) => parent.Children.Add(new TextBlock { Text = text, FontSize = 11, Foreground = fg, Margin = new Thickness(0, 0, 0, 8), }); private static void AddSeparator(StackPanel parent, Brush color) => parent.Children.Add(new Rectangle { Height = 1, Fill = color, Margin = new Thickness(0, 12, 0, 12), Opacity = 0.5, }); private static TextBox CreateTextBox(string text, Brush fg, Brush bg, Brush caret, Brush border, bool multiline = false) { var tb = new TextBox { Text = text, FontSize = 13, Padding = new Thickness(12, 8, 12, 8), Foreground = fg, Background = bg, CaretBrush = caret, BorderBrush = border, BorderThickness = new Thickness(1), }; if (multiline) { tb.AcceptsReturn = true; tb.TextWrapping = TextWrapping.Wrap; tb.MinHeight = 100; tb.MaxHeight = 200; tb.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; } return tb; } private void UpdateCharCount(TextBlock tb) => tb.Text = $"{_promptBox.Text.Length}자"; private static Brush BrushFromHex(string hex) { try { return new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex)); } catch { return Brushes.Gray; } } private static Color ParseColor(string hex) { try { return (Color)ColorConverter.ConvertFromString(hex); } catch { return System.Windows.Media.Colors.Gray; } } }