Initial commit to new repository

This commit is contained in:
2026-04-03 18:22:19 +09:00
commit 4458bb0f52
7672 changed files with 452440 additions and 0 deletions

View File

@@ -0,0 +1,483 @@
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; }
}
}