484 lines
20 KiB
C#
484 lines
20 KiB
C#
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;
|
|
|
|
/// <summary>커스텀 프리셋 추가/편집 다이얼로그.</summary>
|
|
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; }
|
|
}
|
|
}
|