- ThemeResourceHelper에 CascadiaCode/ConsolasCode/ConsolasCourierNew 정적 필드 추가 - 25개 파일, 89개 new FontFamily(...) 호출을 정적 캐시 참조로 교체 - LauncherWindow.xaml.cs (1,563줄) → 5개 파셜 파일로 분할 (63% 감소) - LauncherWindow.Theme.cs (116줄): ApplyTheme, 커스텀 딕셔너리 빌드 - LauncherWindow.Animations.cs (153줄): 무지개 글로우, 애니메이션 헬퍼 - LauncherWindow.Keyboard.cs (593줄): 단축키 20종, ShowToast, IME 검색 - LauncherWindow.Shell.cs (177줄): Shell32, SendToRecycleBin, 클릭 핸들러 - 빌드: 경고 0, 오류 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
415 lines
17 KiB
C#
415 lines
17 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Shapes;
|
|
|
|
namespace AxCopilot.Views;
|
|
|
|
/// <summary>모델 등록/편집 다이얼로그. 별칭 + 실제 모델명 입력.</summary>
|
|
internal sealed class ModelRegistrationDialog : Window
|
|
{
|
|
private readonly TextBox _aliasBox;
|
|
private readonly TextBox _modelBox;
|
|
private readonly TextBox _endpointBox;
|
|
private readonly TextBox _apiKeyBox;
|
|
|
|
// CP4D 인증 필드
|
|
private readonly ComboBox _authTypeBox;
|
|
private readonly StackPanel _cp4dPanel;
|
|
private readonly TextBox _cp4dUrlBox;
|
|
private readonly TextBox _cp4dUsernameBox;
|
|
private readonly PasswordBox _cp4dPasswordBox;
|
|
|
|
public string ModelAlias => _aliasBox.Text.Trim();
|
|
public string ModelName => _modelBox.Text.Trim();
|
|
public string Endpoint => _endpointBox.Text.Trim();
|
|
public string ApiKey => _apiKeyBox.Text.Trim();
|
|
public string AuthType => (_authTypeBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "bearer";
|
|
public string Cp4dUrl => _cp4dUrlBox.Text.Trim();
|
|
public string Cp4dUsername => _cp4dUsernameBox.Text.Trim();
|
|
public string Cp4dPassword => _cp4dPasswordBox.Password.Trim();
|
|
|
|
public ModelRegistrationDialog(string service, string existingAlias = "", string existingModel = "",
|
|
string existingEndpoint = "", string existingApiKey = "",
|
|
string existingAuthType = "bearer", string existingCp4dUrl = "",
|
|
string existingCp4dUsername = "", string existingCp4dPassword = "")
|
|
{
|
|
bool isEdit = !string.IsNullOrEmpty(existingAlias);
|
|
Title = isEdit ? "모델 편집" : "모델 추가";
|
|
Width = 420;
|
|
SizeToContent = SizeToContent.Height;
|
|
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
|
ResizeMode = ResizeMode.NoResize;
|
|
WindowStyle = WindowStyle.None;
|
|
AllowsTransparency = true;
|
|
Background = Brushes.Transparent;
|
|
|
|
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 = 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 = "\uEA86",
|
|
FontFamily = ThemeResourceHelper.SegoeMdl2,
|
|
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);
|
|
|
|
// 서비스 표시
|
|
var serviceLabel = service == "vllm" ? "vLLM" : "Ollama";
|
|
var serviceBadge = new Border
|
|
{
|
|
Background = accentBrush,
|
|
CornerRadius = new CornerRadius(6),
|
|
Padding = new Thickness(10, 3, 10, 3),
|
|
Margin = new Thickness(0, 0, 0, 16),
|
|
HorizontalAlignment = HorizontalAlignment.Left,
|
|
Opacity = 0.85,
|
|
};
|
|
serviceBadge.Child = new TextBlock
|
|
{
|
|
Text = $"{serviceLabel} 서비스",
|
|
FontSize = 11,
|
|
FontWeight = FontWeights.SemiBold,
|
|
Foreground = Brushes.White,
|
|
};
|
|
stack.Children.Add(serviceBadge);
|
|
|
|
// 별칭 입력
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "별칭 (표시 이름)",
|
|
FontSize = 12,
|
|
FontWeight = FontWeights.SemiBold,
|
|
Foreground = primaryText,
|
|
Margin = new Thickness(0, 0, 0, 6),
|
|
});
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "채팅 화면에서 표시될 이름입니다. 예: 코드 리뷰 전용, 일반 대화",
|
|
FontSize = 11,
|
|
Foreground = secondaryText,
|
|
Margin = new Thickness(0, 0, 0, 8),
|
|
});
|
|
_aliasBox = new TextBox
|
|
{
|
|
Text = existingAlias,
|
|
FontSize = 13,
|
|
Padding = new Thickness(12, 8, 12, 8),
|
|
Foreground = primaryText,
|
|
Background = itemBg,
|
|
CaretBrush = accentBrush,
|
|
BorderBrush = borderBrush,
|
|
BorderThickness = new Thickness(1),
|
|
};
|
|
var aliasBorder = new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _aliasBox };
|
|
stack.Children.Add(aliasBorder);
|
|
|
|
// 구분선
|
|
stack.Children.Add(new Rectangle
|
|
{
|
|
Height = 1,
|
|
Fill = borderBrush,
|
|
Margin = new Thickness(0, 16, 0, 16),
|
|
Opacity = 0.5,
|
|
});
|
|
|
|
// 모델명 입력
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "실제 모델명",
|
|
FontSize = 12,
|
|
FontWeight = FontWeights.SemiBold,
|
|
Foreground = primaryText,
|
|
Margin = new Thickness(0, 0, 0, 6),
|
|
});
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "서버에 배포된 실제 모델 ID (예: llama3:8b, qwen2:7b)",
|
|
FontSize = 11,
|
|
Foreground = secondaryText,
|
|
Margin = new Thickness(0, 0, 0, 8),
|
|
});
|
|
_modelBox = new TextBox
|
|
{
|
|
Text = existingModel,
|
|
FontSize = 13,
|
|
Padding = new Thickness(12, 8, 12, 8),
|
|
Foreground = primaryText,
|
|
Background = itemBg,
|
|
CaretBrush = accentBrush,
|
|
BorderBrush = borderBrush,
|
|
BorderThickness = new Thickness(1),
|
|
};
|
|
var modelBorder = new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _modelBox };
|
|
stack.Children.Add(modelBorder);
|
|
|
|
// 구분선
|
|
stack.Children.Add(new Rectangle
|
|
{
|
|
Height = 1, Fill = borderBrush,
|
|
Margin = new Thickness(0, 16, 0, 16), Opacity = 0.5,
|
|
});
|
|
|
|
// 서버 엔드포인트 입력
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "서버 엔드포인트 (선택)",
|
|
FontSize = 12, FontWeight = FontWeights.SemiBold,
|
|
Foreground = primaryText, Margin = new Thickness(0, 0, 0, 6),
|
|
});
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "이 모델 전용 서버 주소. 비워두면 기본 엔드포인트를 사용합니다.",
|
|
FontSize = 11, Foreground = secondaryText, Margin = new Thickness(0, 0, 0, 8),
|
|
});
|
|
_endpointBox = new TextBox
|
|
{
|
|
Text = existingEndpoint,
|
|
FontSize = 13, Padding = new Thickness(12, 8, 12, 8),
|
|
Foreground = primaryText, Background = itemBg,
|
|
CaretBrush = accentBrush, BorderBrush = borderBrush, BorderThickness = new Thickness(1),
|
|
};
|
|
stack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _endpointBox });
|
|
|
|
// ── 인증 방식 선택 ──────────────────────────────────────────────────
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "인증 방식",
|
|
FontSize = 12, FontWeight = FontWeights.SemiBold,
|
|
Foreground = primaryText, Margin = new Thickness(0, 10, 0, 6),
|
|
});
|
|
_authTypeBox = new ComboBox
|
|
{
|
|
FontSize = 13, Padding = new Thickness(8, 6, 8, 6),
|
|
Foreground = primaryText, Background = itemBg,
|
|
BorderBrush = borderBrush, BorderThickness = new Thickness(1),
|
|
};
|
|
var bearerItem = new ComboBoxItem { Content = "Bearer 토큰 (API 키)", Tag = "bearer" };
|
|
var cp4dItem = new ComboBoxItem { Content = "CP4D (IBM Cloud Pak for Data)", Tag = "cp4d" };
|
|
_authTypeBox.Items.Add(bearerItem);
|
|
_authTypeBox.Items.Add(cp4dItem);
|
|
_authTypeBox.SelectedItem = existingAuthType == "cp4d" ? cp4dItem : bearerItem;
|
|
stack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _authTypeBox });
|
|
|
|
// ── Bearer 인증: API 키 입력 ────────────────────────────────────────
|
|
var apiKeyPanel = new StackPanel();
|
|
apiKeyPanel.Children.Add(new TextBlock
|
|
{
|
|
Text = "API 키 (선택)",
|
|
FontSize = 12, FontWeight = FontWeights.SemiBold,
|
|
Foreground = primaryText, Margin = new Thickness(0, 10, 0, 6),
|
|
});
|
|
_apiKeyBox = new TextBox
|
|
{
|
|
Text = existingApiKey,
|
|
FontSize = 13, Padding = new Thickness(12, 8, 12, 8),
|
|
Foreground = primaryText, Background = itemBg,
|
|
CaretBrush = accentBrush, BorderBrush = borderBrush, BorderThickness = new Thickness(1),
|
|
};
|
|
apiKeyPanel.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _apiKeyBox });
|
|
stack.Children.Add(apiKeyPanel);
|
|
|
|
// ── CP4D 인증: URL + 사용자명 + 비밀번호 ────────────────────────────
|
|
_cp4dPanel = new StackPanel { Visibility = existingAuthType == "cp4d" ? Visibility.Visible : Visibility.Collapsed };
|
|
|
|
_cp4dPanel.Children.Add(new TextBlock
|
|
{
|
|
Text = "CP4D 서버 URL",
|
|
FontSize = 12, FontWeight = FontWeights.SemiBold,
|
|
Foreground = primaryText, Margin = new Thickness(0, 10, 0, 6),
|
|
});
|
|
_cp4dPanel.Children.Add(new TextBlock
|
|
{
|
|
Text = "예: https://cpd-host.example.com",
|
|
FontSize = 11, Foreground = secondaryText, Margin = new Thickness(0, 0, 0, 6),
|
|
});
|
|
_cp4dUrlBox = new TextBox
|
|
{
|
|
Text = existingCp4dUrl,
|
|
FontSize = 13, Padding = new Thickness(12, 8, 12, 8),
|
|
Foreground = primaryText, Background = itemBg,
|
|
CaretBrush = accentBrush, BorderBrush = borderBrush, BorderThickness = new Thickness(1),
|
|
};
|
|
_cp4dPanel.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _cp4dUrlBox });
|
|
|
|
_cp4dPanel.Children.Add(new TextBlock
|
|
{
|
|
Text = "사용자 이름",
|
|
FontSize = 12, FontWeight = FontWeights.SemiBold,
|
|
Foreground = primaryText, Margin = new Thickness(0, 10, 0, 6),
|
|
});
|
|
_cp4dUsernameBox = new TextBox
|
|
{
|
|
Text = existingCp4dUsername,
|
|
FontSize = 13, Padding = new Thickness(12, 8, 12, 8),
|
|
Foreground = primaryText, Background = itemBg,
|
|
CaretBrush = accentBrush, BorderBrush = borderBrush, BorderThickness = new Thickness(1),
|
|
};
|
|
_cp4dPanel.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _cp4dUsernameBox });
|
|
|
|
_cp4dPanel.Children.Add(new TextBlock
|
|
{
|
|
Text = "비밀번호 / API 키",
|
|
FontSize = 12, FontWeight = FontWeights.SemiBold,
|
|
Foreground = primaryText, Margin = new Thickness(0, 10, 0, 6),
|
|
});
|
|
_cp4dPasswordBox = new PasswordBox
|
|
{
|
|
Password = existingCp4dPassword,
|
|
FontSize = 13, Padding = new Thickness(12, 8, 12, 8),
|
|
Foreground = primaryText, Background = itemBg,
|
|
CaretBrush = accentBrush, BorderBrush = borderBrush, BorderThickness = new Thickness(1),
|
|
};
|
|
_cp4dPanel.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _cp4dPasswordBox });
|
|
|
|
stack.Children.Add(_cp4dPanel);
|
|
|
|
// 인증 방식 전환 시 패널 표시/숨김
|
|
_authTypeBox.SelectionChanged += (_, _) =>
|
|
{
|
|
var isCp4d = AuthType == "cp4d";
|
|
_cp4dPanel.Visibility = isCp4d ? Visibility.Visible : Visibility.Collapsed;
|
|
apiKeyPanel.Visibility = isCp4d ? Visibility.Collapsed : Visibility.Visible;
|
|
};
|
|
// 초기 상태 설정
|
|
apiKeyPanel.Visibility = existingAuthType == "cp4d" ? Visibility.Collapsed : Visibility.Visible;
|
|
|
|
// 보안 안내
|
|
var securityNote = new StackPanel
|
|
{
|
|
Orientation = Orientation.Horizontal,
|
|
Margin = new Thickness(0, 12, 0, 0),
|
|
};
|
|
securityNote.Children.Add(new TextBlock
|
|
{
|
|
Text = "\uE72E",
|
|
FontFamily = ThemeResourceHelper.SegoeMdl2,
|
|
FontSize = 11,
|
|
Foreground = new SolidColorBrush(Color.FromRgb(0x38, 0xA1, 0x69)),
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
Margin = new Thickness(0, 0, 6, 0),
|
|
});
|
|
securityNote.Children.Add(new TextBlock
|
|
{
|
|
Text = "모델명은 AES-256으로 암호화되어 설정 파일에 저장됩니다",
|
|
FontSize = 10.5,
|
|
Foreground = secondaryText,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
});
|
|
stack.Children.Add(securityNote);
|
|
|
|
// 버튼 바
|
|
var btnBar = new StackPanel
|
|
{
|
|
Orientation = Orientation.Horizontal,
|
|
HorizontalAlignment = HorizontalAlignment.Right,
|
|
Margin = new Thickness(0, 24, 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(_aliasBox.Text))
|
|
{
|
|
CustomMessageBox.Show("별칭을 입력하세요.", "입력 오류", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
_aliasBox.Focus();
|
|
return;
|
|
}
|
|
if (string.IsNullOrWhiteSpace(_modelBox.Text))
|
|
{
|
|
CustomMessageBox.Show("모델명을 입력하세요.", "입력 오류", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
_modelBox.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 += (_, _) => { _aliasBox.Focus(); _aliasBox.SelectAll(); };
|
|
|
|
// 드래그 이동
|
|
root.MouseLeftButtonDown += (_, me) =>
|
|
{
|
|
if (me.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
|
|
try { DragMove(); } catch (Exception) { }
|
|
};
|
|
}
|
|
}
|