using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; namespace AxCopilot.Views; /// 모델 등록/편집 다이얼로그. 별칭 + 실제 모델명 입력. 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) { } }; } }