using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Windows.Forms; using System.Windows.Media; using AxCopilot.Models; using AxCopilot.Services; using AxCopilot.Services.Agent; using AxCopilot.Themes; using AxCopilot.Views; namespace AxCopilot.ViewModels; /// /// 설정 카드에 표시할 테마 미리보기 데이터 /// public class ThemeCardModel : INotifyPropertyChanged { private bool _isSelected; public string Key { get; init; } = ""; // settings.json 값 (dark, light, ...) public string Name { get; init; } = ""; // 표시 이름 // 미리보기용 색상 public string PreviewBackground { get; init; } = "#1A1B2E"; public string PreviewText { get; init; } = "#F0F0FF"; public string PreviewSubText { get; init; } = "#7A7D9C"; public string PreviewAccent { get; init; } = "#4B5EFC"; public string PreviewItem { get; init; } = "#252637"; public string PreviewBorder { get; init; } = "#2E2F4A"; public bool IsSelected { get => _isSelected; set { _isSelected = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); } /// /// 커스텀 테마 단일 색상 항목 (색상 편집 탭 1행 1색) /// public class ColorRowModel : INotifyPropertyChanged { private string _hex; public string Label { get; init; } = ""; public string Property { get; init; } = ""; // CustomThemeColors의 속성명 public string Hex { get => _hex; set { _hex = value; OnPropertyChanged(); OnPropertyChanged(nameof(Preview)); } } public SolidColorBrush Preview { get { try { return new SolidColorBrush((Color)ColorConverter.ConvertFromString(Hex)); } catch { return new SolidColorBrush(Colors.Transparent); } } } public ColorRowModel(string label, string property, string hex) { Label = label; Property = property; _hex = hex; } public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); } /// /// 빠른 실행 단축키 항목 (키워드 → 앱/URL/폴더) /// public class AppShortcutModel : INotifyPropertyChanged { private string _key = ""; private string _description = ""; private string _target = ""; private string _type = "app"; public string Key { get => _key; set { _key = value; OnPropertyChanged(); } } public string Description { get => _description; set { _description = value; OnPropertyChanged(); } } public string Target { get => _target; set { _target = value; OnPropertyChanged(); } } public string Type { get => _type; set { _type = value; OnPropertyChanged(); OnPropertyChanged(nameof(TypeSymbol)); OnPropertyChanged(nameof(TypeLabel)); } } public string TypeSymbol => Type switch { "url" => Symbols.Globe, "folder" => Symbols.Folder, _ => Symbols.App }; public string TypeLabel => Type switch { "url" => "URL", "folder" => "폴더", _ => "앱" }; public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); } /// /// 배치 명령 항목 (> 프리픽스) /// public class BatchCommandModel : INotifyPropertyChanged { private string _key = ""; private string _command = ""; private bool _showWindow; public string Key { get => _key; set { _key = value; OnPropertyChanged(); } } public string Command { get => _command; set { _command = value; OnPropertyChanged(); } } public bool ShowWindow { get => _showWindow; set { _showWindow = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); } public class SettingsViewModel : INotifyPropertyChanged { private readonly SettingsService _service; internal SettingsService Service => _service; /// CodeSettings 바인딩용 프로퍼티. XAML에서 {Binding Code.EnableLsp} 등으로 접근. public Models.CodeSettings Code => _service.Settings.Llm.Code; // ─── 작업 복사본 ─────────────────────────────────────────────────────── private string _hotkey; private int _maxResults; private double _opacity; private string _selectedThemeKey; private string _launcherPosition; private string _webSearchEngine; private bool _snippetAutoExpand; private string _language; private string _indexSpeed; private bool _aiEnabled = true; private string _operationMode = "internal"; private string _agentTheme = "system"; private string _agentThemePreset = "claw"; // 기능 토글 private bool _showNumberBadges; private bool _enableFavorites; private bool _enableRecent; private bool _enableActionMode; private bool _closeOnFocusLost; private bool _rememberPosition; private bool _showPrefixBadge; private bool _enableIconAnimation; private bool _enableRainbowGlow; private bool _enableSelectionGlow; private bool _enableRandomPlaceholder; private bool _showLauncherBorder; private bool _showWidgetPerf; private bool _showWidgetPomo; private bool _showWidgetNote; private bool _showWidgetWeather; private bool _showWidgetCalendar; private bool _showWidgetBattery; private bool _shortcutHelpUseThemeColor; // LLM 공통 설정 private string _llmService; private bool _llmStreaming; private int _llmMaxContextTokens; private int _llmRetentionDays; private double _llmTemperature; // 서비스별 독립 설정 private string _ollamaEndpoint = "http://localhost:11434"; private string _ollamaApiKey = ""; private string _ollamaModel = ""; private string _vllmEndpoint = ""; private string _vllmApiKey = ""; private string _vllmModel = ""; private bool _vllmAllowInsecureTls; private string _geminiApiKey = ""; private string _geminiModel = "gemini-2.5-flash"; private string _sigmoidApiKey = ""; private string _sigmoidModel = "cl" + "aude-sonnet-4-6"; // 등록 모델 목록 public ObservableCollection RegisteredModels { get; } = new(); private static string NormalizeServiceKey(string? service) => (service ?? "ollama").Trim().ToLowerInvariant() switch { "sigmoid" => "claude", _ => (service ?? "ollama").Trim().ToLowerInvariant(), }; public string LlmService { get => _llmService; set { _llmService = NormalizeServiceKey(value); OnPropertyChanged(); OnPropertyChanged(nameof(IsInternalService)); OnPropertyChanged(nameof(IsExternalService)); OnPropertyChanged(nameof(NeedsEndpoint)); OnPropertyChanged(nameof(NeedsApiKey)); OnPropertyChanged(nameof(IsGeminiSelected)); OnPropertyChanged(nameof(IsClaudeSelected)); OnPropertyChanged(nameof(IsSigmoidSelected)); } } public bool IsInternalService => _llmService is "ollama" or "vllm"; public bool IsExternalService => _llmService is "gemini" or "claude"; public bool NeedsEndpoint => _llmService is "ollama" or "vllm"; public bool NeedsApiKey => _llmService is not "ollama"; public bool IsGeminiSelected => _llmService == "gemini"; public bool IsClaudeSelected => _llmService == "claude"; public bool IsSigmoidSelected => IsClaudeSelected; // ── Ollama 설정 ── public string OllamaEndpoint { get => _ollamaEndpoint; set { _ollamaEndpoint = value; OnPropertyChanged(); } } public string OllamaApiKey { get => _ollamaApiKey; set { _ollamaApiKey = value; OnPropertyChanged(); } } public string OllamaModel { get => _ollamaModel; set { _ollamaModel = value; OnPropertyChanged(); } } // ── vLLM 설정 ── public string VllmEndpoint { get => _vllmEndpoint; set { _vllmEndpoint = value; OnPropertyChanged(); } } public string VllmApiKey { get => _vllmApiKey; set { _vllmApiKey = value; OnPropertyChanged(); } } public string VllmModel { get => _vllmModel; set { _vllmModel = value; OnPropertyChanged(); } } public bool VllmAllowInsecureTls { get => _vllmAllowInsecureTls; set { _vllmAllowInsecureTls = value; OnPropertyChanged(); } } // ── Gemini 설정 ── public string GeminiApiKey { get => _geminiApiKey; set { _geminiApiKey = value; OnPropertyChanged(); } } public string GeminiModel { get => _geminiModel; set { _geminiModel = value; OnPropertyChanged(); } } // ── Claude 설정 ── public string ClaudeApiKey { get => _sigmoidApiKey; set { _sigmoidApiKey = value; OnPropertyChanged(); OnPropertyChanged(nameof(SigmoidApiKey)); } } public string ClaudeModel { get => _sigmoidModel; set { _sigmoidModel = value; OnPropertyChanged(); OnPropertyChanged(nameof(SigmoidModel)); } } public string SigmoidApiKey { get => ClaudeApiKey; set => ClaudeApiKey = value; } // legacy binding public string SigmoidModel { get => ClaudeModel; set => ClaudeModel = value; } // legacy binding // ── 공통 응답 설정 ── public bool LlmStreaming { get => _llmStreaming; set { _llmStreaming = value; OnPropertyChanged(); } } public int LlmMaxContextTokens { get => _llmMaxContextTokens; set { _llmMaxContextTokens = Math.Clamp(value, 1024, 1_000_000); OnPropertyChanged(); } } public int LlmRetentionDays { get => _llmRetentionDays; set { _llmRetentionDays = value; OnPropertyChanged(); } } public double LlmTemperature { get => _llmTemperature; set { _llmTemperature = Math.Round(Math.Clamp(value, 0.0, 2.0), 1); OnPropertyChanged(); } } // 에이전트 기본 파일 접근 권한 private string _defaultAgentPermission; public string DefaultAgentPermission { get => _defaultAgentPermission; set { _defaultAgentPermission = PermissionModeCatalog.NormalizeGlobalMode(value); OnPropertyChanged(); } } // ── 코워크/에이전트 고급 설정 ── private string _defaultOutputFormat; public string DefaultOutputFormat { get => _defaultOutputFormat; set { _defaultOutputFormat = value; OnPropertyChanged(); } } private string _autoPreview; public string AutoPreview { get => _autoPreview; set { _autoPreview = value; OnPropertyChanged(); } } private int _maxAgentIterations; public int MaxAgentIterations { get => _maxAgentIterations; set { _maxAgentIterations = Math.Clamp(value, 1, 100); OnPropertyChanged(); } } private int _maxRetryOnError; public int MaxRetryOnError { get => _maxRetryOnError; set { _maxRetryOnError = Math.Clamp(value, 0, 10); OnPropertyChanged(); } } private bool _enableProactiveContextCompact; public bool EnableProactiveContextCompact { get => _enableProactiveContextCompact; set { _enableProactiveContextCompact = value; OnPropertyChanged(); } } private int _contextCompactTriggerPercent; public int ContextCompactTriggerPercent { get => _contextCompactTriggerPercent; set { _contextCompactTriggerPercent = Math.Clamp(value, 50, 95); OnPropertyChanged(); } } private string _agentLogLevel; public string AgentLogLevel { get => _agentLogLevel; set { _agentLogLevel = value; OnPropertyChanged(); } } private string _agentUiExpressionLevel = "balanced"; public string AgentUiExpressionLevel { get => _agentUiExpressionLevel; set { _agentUiExpressionLevel = (value ?? "balanced").Trim().ToLowerInvariant() switch { "rich" => "rich", "simple" => "simple", _ => "balanced", }; OnPropertyChanged(); } } private int _planDiffSeverityMediumCount = 2; public int PlanDiffSeverityMediumCount { get => _planDiffSeverityMediumCount; set { _planDiffSeverityMediumCount = Math.Clamp(value, 1, 20); OnPropertyChanged(); } } private int _planDiffSeverityHighCount = 5; public int PlanDiffSeverityHighCount { get => _planDiffSeverityHighCount; set { _planDiffSeverityHighCount = Math.Clamp(value, 1, 30); OnPropertyChanged(); } } private int _planDiffSeverityMediumRatioPercent = 25; public int PlanDiffSeverityMediumRatioPercent { get => _planDiffSeverityMediumRatioPercent; set { _planDiffSeverityMediumRatioPercent = Math.Clamp(value, 1, 100); OnPropertyChanged(); } } private int _planDiffSeverityHighRatioPercent = 60; public int PlanDiffSeverityHighRatioPercent { get => _planDiffSeverityHighRatioPercent; set { _planDiffSeverityHighRatioPercent = Math.Clamp(value, 1, 100); OnPropertyChanged(); } } private string _agentDecisionLevel = "detailed"; public string AgentDecisionLevel { get => _agentDecisionLevel; set { _agentDecisionLevel = value; OnPropertyChanged(); } } private bool _enableMultiPassDocument; public bool EnableMultiPassDocument { get => _enableMultiPassDocument; set { _enableMultiPassDocument = value; OnPropertyChanged(); } } private bool _enableCoworkVerification; public bool EnableCoworkVerification { get => _enableCoworkVerification; set { _enableCoworkVerification = value; OnPropertyChanged(); } } private bool _enableFilePathHighlight = true; public bool EnableFilePathHighlight { get => _enableFilePathHighlight; set { _enableFilePathHighlight = value; OnPropertyChanged(); } } private string _folderDataUsage; public string FolderDataUsage { get => _folderDataUsage; set { _folderDataUsage = value; OnPropertyChanged(); } } // ── 모델 폴백 + 보안 + MCP ── private bool _enableAuditLog; public bool EnableAuditLog { get => _enableAuditLog; set { _enableAuditLog = value; OnPropertyChanged(); } } private bool _enableAgentMemory; public bool EnableAgentMemory { get => _enableAgentMemory; set { _enableAgentMemory = value; OnPropertyChanged(); } } private bool _enableProjectRules = true; public bool EnableProjectRules { get => _enableProjectRules; set { _enableProjectRules = value; OnPropertyChanged(); } } private int _maxMemoryEntries; public int MaxMemoryEntries { get => _maxMemoryEntries; set { _maxMemoryEntries = value; OnPropertyChanged(); } } // ── 이미지 입력 (멀티모달) ── private bool _enableImageInput = true; public bool EnableImageInput { get => _enableImageInput; set { _enableImageInput = value; OnPropertyChanged(); } } private int _maxImageSizeKb = 5120; public int MaxImageSizeKb { get => _maxImageSizeKb; set { _maxImageSizeKb = value; OnPropertyChanged(); } } // ── 자동 모델 라우팅 ── private bool _enableAutoRouter; public bool EnableAutoRouter { get => _enableAutoRouter; set { _enableAutoRouter = value; OnPropertyChanged(); } } private double _autoRouterConfidence = 0.7; public double AutoRouterConfidence { get => _autoRouterConfidence; set { _autoRouterConfidence = value; OnPropertyChanged(); } } // ── 에이전트 훅 시스템 ── private bool _enableToolHooks = true; public bool EnableToolHooks { get => _enableToolHooks; set { _enableToolHooks = value; OnPropertyChanged(); } } private int _toolHookTimeoutMs = 10000; public int ToolHookTimeoutMs { get => _toolHookTimeoutMs; set { _toolHookTimeoutMs = value; OnPropertyChanged(); } } private bool _enableHookInputMutation; public bool EnableHookInputMutation { get => _enableHookInputMutation; set { _enableHookInputMutation = value; OnPropertyChanged(); } } private bool _enableHookPermissionUpdate; public bool EnableHookPermissionUpdate { get => _enableHookPermissionUpdate; set { _enableHookPermissionUpdate = value; OnPropertyChanged(); } } // ── 스킬 시스템 ── private bool _enableSkillSystem = true; public bool EnableSkillSystem { get => _enableSkillSystem; set { _enableSkillSystem = value; OnPropertyChanged(); } } private bool _enableForkSkillDelegationEnforcement = true; public bool EnableForkSkillDelegationEnforcement { get => _enableForkSkillDelegationEnforcement; set { _enableForkSkillDelegationEnforcement = value; OnPropertyChanged(); } } private string _skillsFolderPath = ""; public string SkillsFolderPath { get => _skillsFolderPath; set { _skillsFolderPath = value; OnPropertyChanged(); } } private int _slashPopupPageSize = 6; public int SlashPopupPageSize { get => _slashPopupPageSize; set { _slashPopupPageSize = Math.Clamp(value, 3, 10); OnPropertyChanged(); } } private int _maxFavoriteSlashCommands = 10; public int MaxFavoriteSlashCommands { get => _maxFavoriteSlashCommands; set { _maxFavoriteSlashCommands = Math.Clamp(value, 1, 30); OnPropertyChanged(); } } private int _maxRecentSlashCommands = 20; public int MaxRecentSlashCommands { get => _maxRecentSlashCommands; set { _maxRecentSlashCommands = Math.Clamp(value, 5, 50); OnPropertyChanged(); } } // ── 드래그&드롭 AI ── private bool _enableDragDropAiActions = true; public bool EnableDragDropAiActions { get => _enableDragDropAiActions; set { _enableDragDropAiActions = value; OnPropertyChanged(); } } private bool _dragDropAutoSend; public bool DragDropAutoSend { get => _dragDropAutoSend; set { _dragDropAutoSend = value; OnPropertyChanged(); } } // ── 코드 리뷰 ── private bool _enableCodeReview = true; public bool EnableCodeReview { get => _enableCodeReview; set { _enableCodeReview = value; OnPropertyChanged(); } } // ── 시각 효과 + 알림 + 개발자 모드 (공통) ── private bool _enableChatRainbowGlow; public bool EnableChatRainbowGlow { get => _enableChatRainbowGlow; set { _enableChatRainbowGlow = value; OnPropertyChanged(); } } private bool _notifyOnComplete; public bool NotifyOnComplete { get => _notifyOnComplete; set { _notifyOnComplete = value; OnPropertyChanged(); } } private bool _showTips; public bool ShowTips { get => _showTips; set { _showTips = value; OnPropertyChanged(); } } private bool _devMode; public bool DevMode { get => _devMode; set { _devMode = value; OnPropertyChanged(); } } private bool _devModeStepApproval; public bool DevModeStepApproval { get => _devModeStepApproval; set { _devModeStepApproval = value; OnPropertyChanged(); } } private bool _workflowVisualizer; public bool WorkflowVisualizer { get => _workflowVisualizer; set { _workflowVisualizer = value; OnPropertyChanged(); } } private bool _freeTierMode; public bool FreeTierMode { get => _freeTierMode; set { _freeTierMode = value; OnPropertyChanged(); } } private int _freeTierDelaySeconds = 4; public int FreeTierDelaySeconds { get => _freeTierDelaySeconds; set { _freeTierDelaySeconds = value; OnPropertyChanged(); } } private bool _showTotalCallStats; public bool ShowTotalCallStats { get => _showTotalCallStats; set { _showTotalCallStats = value; OnPropertyChanged(); } } private string _defaultMood = "modern"; public string DefaultMood { get => _defaultMood; set { _defaultMood = value; OnPropertyChanged(); } } // 차단 경로/확장자 (읽기 전용 UI) public ObservableCollection BlockedPaths { get; } = new(); public ObservableCollection BlockedExtensions { get; } = new(); public string Hotkey { get => _hotkey; set { _hotkey = value; OnPropertyChanged(); } } public int MaxResults { get => _maxResults; set { _maxResults = value; OnPropertyChanged(); } } public double Opacity { get => _opacity; set { _opacity = value; OnPropertyChanged(); OnPropertyChanged(nameof(OpacityPercent)); } } public int OpacityPercent => (int)Math.Round(_opacity * 100); public string SelectedThemeKey { get => _selectedThemeKey; set { _selectedThemeKey = value; OnPropertyChanged(); OnPropertyChanged(nameof(IsCustomTheme)); foreach (var card in ThemeCards) card.IsSelected = card.Key == value; } } public bool IsCustomTheme => _selectedThemeKey == "custom"; public string LauncherPosition { get => _launcherPosition; set { _launcherPosition = value; OnPropertyChanged(); } } public string WebSearchEngine { get => _webSearchEngine; set { _webSearchEngine = value; OnPropertyChanged(); } } public bool SnippetAutoExpand { get => _snippetAutoExpand; set { _snippetAutoExpand = value; OnPropertyChanged(); } } public string Language { get => _language; set { _language = value; OnPropertyChanged(); } } public string IndexSpeed { get => _indexSpeed; set { _indexSpeed = value; OnPropertyChanged(); OnPropertyChanged(nameof(IndexSpeedHint)); } } public bool AiEnabled { get => _aiEnabled; set { _aiEnabled = value; OnPropertyChanged(); } } public string OperationMode { get => _operationMode; set { _operationMode = string.IsNullOrWhiteSpace(value) ? "internal" : value.Trim().ToLowerInvariant(); OnPropertyChanged(); } } public string AgentTheme { get => _agentTheme; set { _agentTheme = string.IsNullOrWhiteSpace(value) ? "system" : value.Trim().ToLowerInvariant(); OnPropertyChanged(); } } public string AgentThemePreset { get => _agentThemePreset; set { _agentThemePreset = string.IsNullOrWhiteSpace(value) ? "claw" : value.Trim().ToLowerInvariant(); OnPropertyChanged(); } } public string IndexSpeedHint => _indexSpeed switch { "fast" => "CPU 사용률이 높아질 수 있습니다. 고성능 PC에 권장합니다.", "slow" => "인덱싱이 오래 걸리지만 PC 성능에 영향을 주지 않습니다.", _ => "일반적인 PC에 적합한 균형 설정입니다.", }; // ─── 기능 토글 속성 ─────────────────────────────────────────────────── public bool ShowNumberBadges { get => _showNumberBadges; set { _showNumberBadges = value; OnPropertyChanged(); } } public bool EnableFavorites { get => _enableFavorites; set { _enableFavorites = value; OnPropertyChanged(); } } public bool EnableRecent { get => _enableRecent; set { _enableRecent = value; OnPropertyChanged(); } } public bool EnableActionMode { get => _enableActionMode; set { _enableActionMode = value; OnPropertyChanged(); } } public bool CloseOnFocusLost { get => _closeOnFocusLost; set { _closeOnFocusLost = value; OnPropertyChanged(); } } public bool RememberPosition { get => _rememberPosition; set { _rememberPosition = value; OnPropertyChanged(); } } public bool ShowPrefixBadge { get => _showPrefixBadge; set { _showPrefixBadge = value; OnPropertyChanged(); } } public bool ShowWidgetPerf { get => _showWidgetPerf; set { _showWidgetPerf = value; OnPropertyChanged(); } } public bool ShowWidgetPomo { get => _showWidgetPomo; set { _showWidgetPomo = value; OnPropertyChanged(); } } public bool ShowWidgetNote { get => _showWidgetNote; set { _showWidgetNote = value; OnPropertyChanged(); } } public bool ShowWidgetWeather { get => _showWidgetWeather; set { _showWidgetWeather = value; OnPropertyChanged(); } } public bool ShowWidgetCalendar { get => _showWidgetCalendar; set { _showWidgetCalendar = value; OnPropertyChanged(); } } public bool ShowWidgetBattery { get => _showWidgetBattery; set { _showWidgetBattery = value; OnPropertyChanged(); } } public bool EnableIconAnimation { get => _enableIconAnimation; set { _enableIconAnimation = value; OnPropertyChanged(); } } public bool EnableRainbowGlow { get => _enableRainbowGlow; set { _enableRainbowGlow = value; OnPropertyChanged(); } } public bool EnableSelectionGlow { get => _enableSelectionGlow; set { _enableSelectionGlow = value; OnPropertyChanged(); } } public bool EnableRandomPlaceholder { get => _enableRandomPlaceholder; set { _enableRandomPlaceholder = value; OnPropertyChanged(); } } public bool ShowLauncherBorder { get => _showLauncherBorder; set { _showLauncherBorder = value; OnPropertyChanged(); } } // ─── v1.4.0 신기능 설정 ────────────────────────────────────────────────── private bool _enableTextAction = true; public bool EnableTextAction { get => _enableTextAction; set { _enableTextAction = value; OnPropertyChanged(); } } private bool _enableFileDialogIntegration = false; public bool EnableFileDialogIntegration { get => _enableFileDialogIntegration; set { _enableFileDialogIntegration = value; OnPropertyChanged(); } } private bool _enableClipboardAutoCategory = true; public bool EnableClipboardAutoCategory { get => _enableClipboardAutoCategory; set { _enableClipboardAutoCategory = value; OnPropertyChanged(); } } private int _maxPinnedClipboardItems = 20; public int MaxPinnedClipboardItems { get => _maxPinnedClipboardItems; set { _maxPinnedClipboardItems = value; OnPropertyChanged(); } } private string _textActionTranslateLanguage = "auto"; public string TextActionTranslateLanguage { get => _textActionTranslateLanguage; set { _textActionTranslateLanguage = value; OnPropertyChanged(); } } private int _maxSubAgents = 3; public int MaxSubAgents { get => _maxSubAgents; set { _maxSubAgents = value; OnPropertyChanged(); } } private string _pdfExportPath = ""; public string PdfExportPath { get => _pdfExportPath; set { _pdfExportPath = value; OnPropertyChanged(); } } private int _tipDurationSeconds = 5; public int TipDurationSeconds { get => _tipDurationSeconds; set { _tipDurationSeconds = value; OnPropertyChanged(); } } public bool ShortcutHelpUseThemeColor { get => _shortcutHelpUseThemeColor; set { _shortcutHelpUseThemeColor = value; OnPropertyChanged(); } } // ─── 테마 카드 목록 ─────────────────────────────────────────────────── public List ThemeCards { get; } = new() { new() { Key = "system", Name = "시스템", PreviewBackground = "#1E1E1E", PreviewText = "#FFFFFF", PreviewSubText = "#888888", PreviewAccent = "#0078D4", PreviewItem = "#2D2D2D", PreviewBorder = "#3D3D3D" }, new() { Key = "dark", Name = "Dark", PreviewBackground = "#1A1B2E", PreviewText = "#F0F0FF", PreviewSubText = "#7A7D9C", PreviewAccent = "#4B5EFC", PreviewItem = "#252637", PreviewBorder = "#2E2F4A" }, new() { Key = "light", Name = "Light", PreviewBackground = "#FAFAFA", PreviewText = "#1A1B2E", PreviewSubText = "#666680", PreviewAccent = "#4B5EFC", PreviewItem = "#F0F0F8", PreviewBorder = "#E0E0F0" }, new() { Key = "oled", Name = "OLED", PreviewBackground = "#000000", PreviewText = "#FFFFFF", PreviewSubText = "#888899", PreviewAccent = "#5C6EFF", PreviewItem = "#0A0A14", PreviewBorder = "#1A1A2E" }, new() { Key = "nord", Name = "Nord", PreviewBackground = "#2E3440", PreviewText = "#ECEFF4", PreviewSubText = "#D8DEE9", PreviewAccent = "#88C0D0", PreviewItem = "#3B4252", PreviewBorder = "#434C5E" }, new() { Key = "monokai", Name = "Monokai", PreviewBackground = "#272822", PreviewText = "#F8F8F2", PreviewSubText = "#75715E", PreviewAccent = "#A6E22E", PreviewItem = "#3E3D32", PreviewBorder = "#49483E" }, new() { Key = "catppuccin", Name = "Catppuccin", PreviewBackground = "#1E1E2E", PreviewText = "#CDD6F4", PreviewSubText = "#A6ADC8", PreviewAccent = "#CBA6F7", PreviewItem = "#313244", PreviewBorder = "#45475A" }, new() { Key = "sepia", Name = "Sepia", PreviewBackground = "#F5EFE0", PreviewText = "#3C2F1A", PreviewSubText = "#7A6040", PreviewAccent = "#C0822A", PreviewItem = "#EDE6D6", PreviewBorder = "#D8CCBA" }, new() { Key = "alfred", Name = "Indigo", PreviewBackground = "#26273B", PreviewText = "#EEEEFF", PreviewSubText = "#8888BB", PreviewAccent = "#8877EE", PreviewItem = "#3B3D60", PreviewBorder = "#40416A" }, new() { Key = "alfredlight", Name = "Frost", PreviewBackground = "#FFFFFF", PreviewText = "#1A1A2E", PreviewSubText = "#9090AA", PreviewAccent = "#5555EE", PreviewItem = "#E8E9FF", PreviewBorder = "#DCDCEE" }, new() { Key = "codex", Name = "Codex", PreviewBackground = "#FFFFFF", PreviewText = "#111111", PreviewSubText = "#6B7280", PreviewAccent = "#7C3AED", PreviewItem = "#F5F5F7", PreviewBorder = "#E5E7EB" }, new() { Key = "custom", Name = "커스텀", PreviewBackground = "#1A1B2E", PreviewText = "#F0F0FF", PreviewSubText = "#7A7D9C", PreviewAccent = "#4B5EFC", PreviewItem = "#252637", PreviewBorder = "#2E2F4A" }, }; // ─── 커스텀 색상 행 목록 ────────────────────────────────────────────── public List ColorRows { get; } // ─── 프롬프트 템플릿 ────────────────────────────────────────────────── public ObservableCollection PromptTemplates { get; } = new(); // ─── 배치 명령 ──────────────────────────────────────────────────────── public ObservableCollection BatchCommands { get; } = new(); private string _newBatchKey = ""; private string _newBatchCommand = ""; private bool _newBatchShowWindow; public string NewBatchKey { get => _newBatchKey; set { _newBatchKey = value; OnPropertyChanged(); } } public string NewBatchCommand { get => _newBatchCommand; set { _newBatchCommand = value; OnPropertyChanged(); } } public bool NewBatchShowWindow { get => _newBatchShowWindow; set { _newBatchShowWindow = value; OnPropertyChanged(); } } public bool AddBatchCommand() { if (string.IsNullOrWhiteSpace(_newBatchKey) || string.IsNullOrWhiteSpace(_newBatchCommand)) return false; if (BatchCommands.Any(b => b.Key.Equals(_newBatchKey.Trim(), StringComparison.OrdinalIgnoreCase))) return false; BatchCommands.Add(new BatchCommandModel { Key = _newBatchKey.Trim(), Command = _newBatchCommand.Trim(), ShowWindow = _newBatchShowWindow }); NewBatchKey = ""; NewBatchCommand = ""; NewBatchShowWindow = false; return true; } public void RemoveBatchCommand(BatchCommandModel cmd) => BatchCommands.Remove(cmd); // ─── 빠른 실행 단축키 ───────────────────────────────────────────────── public ObservableCollection AppShortcuts { get; } = new(); private string _newKey = ""; private string _newDescription = ""; private string _newTarget = ""; private string _newType = "app"; public string NewKey { get => _newKey; set { _newKey = value; OnPropertyChanged(); } } public string NewDescription { get => _newDescription; set { _newDescription = value; OnPropertyChanged(); } } public string NewTarget { get => _newTarget; set { _newTarget = value; OnPropertyChanged(); } } public string NewType { get => _newType; set { _newType = value; OnPropertyChanged(); } } // ─── 이벤트 ─────────────────────────────────────────────────────────── public event EventHandler? ThemePreviewRequested; public event EventHandler? SaveCompleted; public SettingsViewModel(SettingsService service) { _service = service; var s = service.Settings; _hotkey = s.Hotkey; _maxResults = s.Launcher.MaxResults; _opacity = s.Launcher.Opacity; _selectedThemeKey = (s.Launcher.Theme ?? "system").ToLowerInvariant(); _launcherPosition = s.Launcher.Position; _webSearchEngine = s.Launcher.WebSearchEngine; _snippetAutoExpand = s.Launcher.SnippetAutoExpand; _language = s.Launcher.Language; _indexSpeed = s.IndexSpeed ?? "normal"; // 기능 토글 로드 _showNumberBadges = s.Launcher.ShowNumberBadges; _enableFavorites = s.Launcher.EnableFavorites; _enableRecent = s.Launcher.EnableRecent; _enableActionMode = s.Launcher.EnableActionMode; _closeOnFocusLost = s.Launcher.CloseOnFocusLost; _rememberPosition = s.Launcher.RememberPosition; _showPrefixBadge = s.Launcher.ShowPrefixBadge; _enableIconAnimation = s.Launcher.EnableIconAnimation; _enableRainbowGlow = s.Launcher.EnableRainbowGlow; _enableSelectionGlow = s.Launcher.EnableSelectionGlow; _enableRandomPlaceholder = s.Launcher.EnableRandomPlaceholder; _showLauncherBorder = s.Launcher.ShowLauncherBorder; _showWidgetPerf = s.Launcher.ShowWidgetPerf; _showWidgetPomo = s.Launcher.ShowWidgetPomo; _showWidgetNote = s.Launcher.ShowWidgetNote; _showWidgetWeather = s.Launcher.ShowWidgetWeather; _showWidgetCalendar = s.Launcher.ShowWidgetCalendar; _showWidgetBattery = s.Launcher.ShowWidgetBattery; _shortcutHelpUseThemeColor = s.Launcher.ShortcutHelpUseThemeColor; _enableTextAction = s.Launcher.EnableTextAction; // v1.7.1: 파일 대화상자 통합 기본값을 false로 변경 (브라우저 업로드 오작동 방지) // 기존 저장값이 true이면 false로 재설정 _enableFileDialogIntegration = false; s.Launcher.EnableFileDialogIntegration = false; _enableClipboardAutoCategory = s.Launcher.EnableClipboardAutoCategory; _maxPinnedClipboardItems = s.Launcher.MaxPinnedClipboardItems; _textActionTranslateLanguage = s.Launcher.TextActionTranslateLanguage; _maxSubAgents = s.Llm.MaxSubAgents; _pdfExportPath = s.Llm.PdfExportPath; _tipDurationSeconds = s.Llm.TipDurationSeconds; // LLM 설정 로드 var llm = s.Llm; _llmService = NormalizeServiceKey(llm.Service); _llmStreaming = llm.Streaming; _llmMaxContextTokens = Math.Clamp(llm.MaxContextTokens, 1024, 1_000_000); _llmRetentionDays = llm.RetentionDays; _llmTemperature = llm.Temperature; _defaultAgentPermission = PermissionModeCatalog.NormalizeGlobalMode(llm.DefaultAgentPermission); _defaultOutputFormat = llm.DefaultOutputFormat; _defaultMood = string.IsNullOrEmpty(llm.DefaultMood) ? "modern" : llm.DefaultMood; _autoPreview = llm.AutoPreview; _maxAgentIterations = llm.MaxAgentIterations > 0 ? llm.MaxAgentIterations : 25; _maxRetryOnError = llm.MaxRetryOnError > 0 ? llm.MaxRetryOnError : 3; _enableProactiveContextCompact = llm.EnableProactiveContextCompact; _contextCompactTriggerPercent = llm.ContextCompactTriggerPercent > 0 ? Math.Clamp(llm.ContextCompactTriggerPercent, 50, 95) : 80; _aiEnabled = _service.Settings.AiEnabled; _operationMode = string.IsNullOrWhiteSpace(_service.Settings.OperationMode) ? "internal" : _service.Settings.OperationMode; _agentTheme = string.IsNullOrWhiteSpace(llm.AgentTheme) ? "system" : llm.AgentTheme; _agentThemePreset = string.IsNullOrWhiteSpace(llm.AgentThemePreset) ? "claw" : llm.AgentThemePreset; _agentLogLevel = llm.AgentLogLevel; _agentUiExpressionLevel = (llm.AgentUiExpressionLevel ?? "balanced").Trim().ToLowerInvariant() switch { "rich" => "rich", "simple" => "simple", _ => "balanced", }; _planDiffSeverityMediumCount = llm.PlanDiffSeverityMediumCount > 0 ? Math.Clamp(llm.PlanDiffSeverityMediumCount, 1, 20) : 2; _planDiffSeverityHighCount = llm.PlanDiffSeverityHighCount > 0 ? Math.Clamp(llm.PlanDiffSeverityHighCount, 1, 30) : 5; _planDiffSeverityMediumRatioPercent = llm.PlanDiffSeverityMediumRatioPercent > 0 ? Math.Clamp(llm.PlanDiffSeverityMediumRatioPercent, 1, 100) : 25; _planDiffSeverityHighRatioPercent = llm.PlanDiffSeverityHighRatioPercent > 0 ? Math.Clamp(llm.PlanDiffSeverityHighRatioPercent, 1, 100) : 60; _agentDecisionLevel = llm.AgentDecisionLevel; _enableMultiPassDocument = llm.EnableMultiPassDocument; _enableCoworkVerification = llm.EnableCoworkVerification; _enableFilePathHighlight = llm.EnableFilePathHighlight; _folderDataUsage = string.IsNullOrEmpty(llm.FolderDataUsage) ? "active" : llm.FolderDataUsage; _enableAuditLog = llm.EnableAuditLog; _enableAgentMemory = llm.EnableAgentMemory; _enableProjectRules = llm.EnableProjectRules; _maxMemoryEntries = llm.MaxMemoryEntries; _enableImageInput = llm.EnableImageInput; _maxImageSizeKb = llm.MaxImageSizeKb > 0 ? llm.MaxImageSizeKb : 5120; _enableToolHooks = llm.EnableToolHooks; _toolHookTimeoutMs = llm.ToolHookTimeoutMs > 0 ? llm.ToolHookTimeoutMs : 10000; _enableHookInputMutation = llm.EnableHookInputMutation; _enableHookPermissionUpdate = llm.EnableHookPermissionUpdate; _enableSkillSystem = llm.EnableSkillSystem; _enableForkSkillDelegationEnforcement = llm.EnableForkSkillDelegationEnforcement; _skillsFolderPath = llm.SkillsFolderPath; _slashPopupPageSize = llm.SlashPopupPageSize > 0 ? Math.Clamp(llm.SlashPopupPageSize, 3, 10) : 6; _maxFavoriteSlashCommands = llm.MaxFavoriteSlashCommands > 0 ? Math.Clamp(llm.MaxFavoriteSlashCommands, 1, 30) : 10; _maxRecentSlashCommands = llm.MaxRecentSlashCommands > 0 ? Math.Clamp(llm.MaxRecentSlashCommands, 5, 50) : 20; _enableDragDropAiActions = llm.EnableDragDropAiActions; _dragDropAutoSend = llm.DragDropAutoSend; _enableCodeReview = llm.Code.EnableCodeReview; _enableAutoRouter = llm.EnableAutoRouter; _autoRouterConfidence = llm.AutoRouterConfidence; _enableChatRainbowGlow = llm.EnableChatRainbowGlow; _notifyOnComplete = llm.NotifyOnComplete; _showTips = llm.ShowTips; _devMode = llm.DevMode; // 저장된 설정 유지 (한 번 켜면 유지, 끄면 하위 기능 비활성) _devModeStepApproval = llm.DevModeStepApproval; _workflowVisualizer = llm.WorkflowVisualizer; _freeTierMode = llm.FreeTierMode; _freeTierDelaySeconds = llm.FreeTierDelaySeconds > 0 ? llm.FreeTierDelaySeconds : 4; _showTotalCallStats = llm.ShowTotalCallStats; // 서비스별 독립 설정 로드 _ollamaEndpoint = llm.OllamaEndpoint; _ollamaModel = llm.OllamaModel; _vllmEndpoint = llm.VllmEndpoint; _vllmModel = llm.VllmModel; _vllmAllowInsecureTls = llm.VllmAllowInsecureTls; _geminiModel = string.IsNullOrEmpty(llm.GeminiModel) ? "gemini-2.5-flash" : llm.GeminiModel; _sigmoidModel = string.IsNullOrEmpty(llm.ClaudeModel) ? "cl" + "aude-sonnet-4-6" : llm.ClaudeModel; // API 키 로드: 서비스별 독립 저장 _geminiApiKey = llm.GeminiApiKey; _sigmoidApiKey = llm.ClaudeApiKey; if (llm.EncryptionEnabled) { _ollamaApiKey = string.IsNullOrEmpty(llm.OllamaApiKey) ? "" : "(저장됨)"; _vllmApiKey = string.IsNullOrEmpty(llm.VllmApiKey) ? "" : "(저장됨)"; } else { _ollamaApiKey = llm.OllamaApiKey; _vllmApiKey = llm.VllmApiKey; } // 기존 단일 설정에서 마이그레이션 (최초 1회) if (string.IsNullOrEmpty(llm.OllamaEndpoint) && !string.IsNullOrEmpty(llm.Endpoint) && llm.Service == "ollama") { _ollamaEndpoint = llm.Endpoint; _ollamaModel = llm.Model; if (!llm.EncryptionEnabled) _ollamaApiKey = llm.EncryptedApiKey; } if (string.IsNullOrEmpty(llm.VllmEndpoint) && !string.IsNullOrEmpty(llm.Endpoint) && llm.Service == "vllm") { _vllmEndpoint = llm.Endpoint; _vllmModel = llm.Model; if (!llm.EncryptionEnabled) _vllmApiKey = llm.EncryptedApiKey; } if (string.IsNullOrEmpty(llm.GeminiApiKey) && !string.IsNullOrEmpty(llm.ApiKey) && llm.Service == "gemini") { _geminiApiKey = llm.ApiKey; _geminiModel = llm.Model; } if (string.IsNullOrEmpty(llm.ClaudeApiKey) && !string.IsNullOrEmpty(llm.ApiKey) && NormalizeServiceKey(llm.Service) == "claude") { _sigmoidApiKey = llm.ApiKey; _sigmoidModel = llm.Model; } // 차단 경로/확장자 로드 foreach (var p in llm.BlockedPaths) BlockedPaths.Add(p); foreach (var e in llm.BlockedExtensions) BlockedExtensions.Add(e); // 등록 모델 로드 foreach (var rm in llm.RegisteredModels) RegisteredModels.Add(new RegisteredModelRow { Alias = rm.Alias, EncryptedModelName = rm.EncryptedModelName, Service = rm.Service, Endpoint = rm.Endpoint, ApiKey = rm.ApiKey, AllowInsecureTls = rm.AllowInsecureTls, AuthType = rm.AuthType ?? "bearer", Cp4dUrl = rm.Cp4dUrl ?? "", Cp4dUsername = rm.Cp4dUsername ?? "", Cp4dPassword = rm.Cp4dPassword ?? "", }); // 프롬프트 템플릿 로드 foreach (var pt in llm.PromptTemplates) PromptTemplates.Add(new PromptTemplateRow { Name = pt.Name, Content = pt.Content, Icon = pt.Icon }); foreach (var card in ThemeCards) card.IsSelected = card.Key == _selectedThemeKey; // 알림 설정 로드 _reminderEnabled = s.Reminder.Enabled; _reminderCorner = s.Reminder.Corner; _reminderIntervalMinutes = s.Reminder.IntervalMinutes; _reminderDisplaySeconds = s.Reminder.DisplaySeconds; // 캡처 설정 로드 _capPrefix = string.IsNullOrWhiteSpace(s.ScreenCapture.Prefix) ? "cap" : s.ScreenCapture.Prefix; _capGlobalHotkeyEnabled = s.ScreenCapture.GlobalHotkeyEnabled; _capGlobalHotkey = string.IsNullOrWhiteSpace(s.ScreenCapture.GlobalHotkey) ? "PrintScreen" : s.ScreenCapture.GlobalHotkey; _capGlobalMode = string.IsNullOrWhiteSpace(s.ScreenCapture.GlobalHotkeyMode) ? "screen" : s.ScreenCapture.GlobalHotkeyMode; _capScrollDelayMs = s.ScreenCapture.ScrollDelayMs > 0 ? s.ScreenCapture.ScrollDelayMs : 120; // 클립보드 히스토리 설정 로드 _clipboardEnabled = s.ClipboardHistory.Enabled; _clipboardMaxItems = s.ClipboardHistory.MaxItems; // 시스템 명령 설정 로드 var sys = s.SystemCommands; _sysShowLock = sys.ShowLock; _sysShowSleep = sys.ShowSleep; _sysShowRestart = sys.ShowRestart; _sysShowShutdown = sys.ShowShutdown; _sysShowHibernate = sys.ShowHibernate; _sysShowLogout = sys.ShowLogout; _sysShowRecycleBin = sys.ShowRecycleBin; // 시스템 명령 별칭 로드 var ca = sys.CommandAliases; _aliasLock = FormatAliases(ca, "lock"); _aliasSleep = FormatAliases(ca, "sleep"); _aliasRestart = FormatAliases(ca, "restart"); _aliasShutdown = FormatAliases(ca, "shutdown"); _aliasHibernate = FormatAliases(ca, "hibernate"); _aliasLogout = FormatAliases(ca, "logout"); _aliasRecycle = FormatAliases(ca, "recycle"); // 인덱스 경로 로드 foreach (var path in s.IndexPaths) IndexPaths.Add(path); // 인덱스 확장자 로드 foreach (var ext in s.IndexExtensions) IndexExtensions.Add(ext); // 스니펫 로드 foreach (var sn in s.Snippets) Snippets.Add(new SnippetRowModel { Key = sn.Key, Name = sn.Name, Content = sn.Content }); // 배치 명령 로드 foreach (var alias in s.Aliases.Where(a => a.Type == "batch")) { BatchCommands.Add(new BatchCommandModel { Key = alias.Key, Command = alias.Target, ShowWindow = alias.ShowWindow }); } // 기존 aliases 로드 (app/url/folder type만) foreach (var alias in s.Aliases.Where(a => a.Type is "app" or "url" or "folder")) { AppShortcuts.Add(new AppShortcutModel { Key = alias.Key, Description = alias.Description ?? "", Target = alias.Target, Type = alias.Type }); } var c = s.Launcher.CustomTheme ?? new CustomThemeColors(); ColorRows = new List { new("런처 배경", nameof(CustomThemeColors.LauncherBackground), c.LauncherBackground), new("항목 배경", nameof(CustomThemeColors.ItemBackground), c.ItemBackground), new("선택 항목 배경", nameof(CustomThemeColors.ItemSelectedBackground), c.ItemSelectedBackground), new("호버 배경", nameof(CustomThemeColors.ItemHoverBackground), c.ItemHoverBackground), new("기본 텍스트", nameof(CustomThemeColors.PrimaryText), c.PrimaryText), new("보조 텍스트", nameof(CustomThemeColors.SecondaryText), c.SecondaryText), new("플레이스홀더", nameof(CustomThemeColors.PlaceholderText), c.PlaceholderText), new("강조색", nameof(CustomThemeColors.AccentColor), c.AccentColor), new("구분선", nameof(CustomThemeColors.SeparatorColor), c.SeparatorColor), new("힌트 배경", nameof(CustomThemeColors.HintBackground), c.HintBackground), new("힌트 텍스트", nameof(CustomThemeColors.HintText), c.HintText), new("테두리", nameof(CustomThemeColors.BorderColor), c.BorderColor), new("스크롤바", nameof(CustomThemeColors.ScrollbarThumb), c.ScrollbarThumb), new("그림자", nameof(CustomThemeColors.ShadowColor), c.ShadowColor), }; _customWindowCornerRadius = c.WindowCornerRadius; _customItemCornerRadius = c.ItemCornerRadius; } // ─── 커스텀 테마 모서리 라운딩 ─────────────────────────────────────────── private int _customWindowCornerRadius = 20; private int _customItemCornerRadius = 10; public int CustomWindowCornerRadius { get => _customWindowCornerRadius; set { _customWindowCornerRadius = Math.Clamp(value, 0, 30); OnPropertyChanged(); } } public int CustomItemCornerRadius { get => _customItemCornerRadius; set { _customItemCornerRadius = Math.Clamp(value, 0, 20); OnPropertyChanged(); } } // ─── 인덱스 경로 ────────────────────────────────────────────────────── public ObservableCollection IndexPaths { get; } = new(); public void AddIndexPath(string path) { var trimmed = path.Trim(); if (string.IsNullOrWhiteSpace(trimmed)) return; if (IndexPaths.Any(p => p.Equals(trimmed, StringComparison.OrdinalIgnoreCase))) return; IndexPaths.Add(trimmed); } public void RemoveIndexPath(string path) => IndexPaths.Remove(path); // ─── 인덱스 확장자 ────────────────────────────────────────────────── public ObservableCollection IndexExtensions { get; } = new(); public void AddExtension(string ext) { var trimmed = ext.Trim().ToLowerInvariant(); if (string.IsNullOrWhiteSpace(trimmed)) return; if (!trimmed.StartsWith(".")) trimmed = "." + trimmed; if (IndexExtensions.Any(e => e.Equals(trimmed, StringComparison.OrdinalIgnoreCase))) return; IndexExtensions.Add(trimmed); } public void RemoveExtension(string ext) => IndexExtensions.Remove(ext); public void BrowseIndexPath() { using var dlg = new FolderBrowserDialog { Description = "인덱스할 폴더 선택", UseDescriptionForTitle = true, ShowNewFolderButton = false }; if (dlg.ShowDialog() != DialogResult.OK) return; AddIndexPath(dlg.SelectedPath); } // ─── 빠른 실행 단축키 메서드 ────────────────────────────────────────── public bool AddShortcut() { if (string.IsNullOrWhiteSpace(_newKey) || string.IsNullOrWhiteSpace(_newTarget)) return false; // 중복 키 확인 if (AppShortcuts.Any(s => s.Key.Equals(_newKey.Trim(), StringComparison.OrdinalIgnoreCase))) return false; AppShortcuts.Add(new AppShortcutModel { Key = _newKey.Trim(), Description = _newDescription.Trim(), Target = _newTarget.Trim(), Type = _newType }); NewKey = ""; NewDescription = ""; NewTarget = ""; NewType = "app"; return true; } public void RemoveShortcut(AppShortcutModel shortcut) => AppShortcuts.Remove(shortcut); /// 파일 선택 대화상자. 선택 시 NewTarget에 자동 설정. public void BrowseTarget() { using var dlg = new OpenFileDialog { Filter = "실행 파일 (*.exe)|*.exe|모든 파일 (*.*)|*.*", Title = "앱 선택" }; if (dlg.ShowDialog() != DialogResult.OK) return; NewTarget = dlg.FileName; NewType = "app"; if (string.IsNullOrWhiteSpace(NewDescription)) NewDescription = System.IO.Path.GetFileNameWithoutExtension(dlg.FileName); } public void SelectTheme(string key) { SelectedThemeKey = key; ThemePreviewRequested?.Invoke(this, EventArgs.Empty); } public void PickColor(ColorRowModel row) { using var dlg = new ColorDialog { FullOpen = true }; try { var color = (Color)ColorConverter.ConvertFromString(row.Hex); dlg.Color = System.Drawing.Color.FromArgb(color.R, color.G, color.B); } catch { /* 기본값 사용 */ } if (dlg.ShowDialog() != DialogResult.OK) return; row.Hex = $"#{dlg.Color.R:X2}{dlg.Color.G:X2}{dlg.Color.B:X2}"; if (_selectedThemeKey == "custom") ThemePreviewRequested?.Invoke(this, EventArgs.Empty); } // 시스템 예약 프리픽스 (핸들러에서 이미 사용 중인 키) private static readonly HashSet ReservedPrefixes = new(StringComparer.OrdinalIgnoreCase) { "=", "?", "#", "$", ";", "@", "~", ">", "!", "emoji", "color", "recent", "note", "uninstall", "kill", "media", "info", "*", "json", "encode", "port", "env", "snap", "help", "pick", "date", "svc", "pipe", "journal", "routine", "batch", "diff", "win", "stats", "fav", "rename", "monitor", "scaffold", }; /// 설정 저장 전 프리픽스/키워드 충돌을 검사합니다. 충돌 시 메시지를 반환합니다. public string? ValidateBeforeSave() { // 캡처 프리픽스 충돌 검사 var cap = string.IsNullOrWhiteSpace(_capPrefix) ? "cap" : _capPrefix.Trim(); if (cap != "cap" && ReservedPrefixes.Contains(cap)) return $"캡처 프리픽스 '{cap}'은(는) 이미 사용 중인 예약어입니다."; // 빠른 실행 별칭 키 중복 검사 var aliasKeys = AppShortcuts.Select(s => s.Key.ToLowerInvariant()).ToList(); var duplicateAlias = aliasKeys.GroupBy(k => k).FirstOrDefault(g => g.Count() > 1); if (duplicateAlias != null) return $"빠른 실행 키워드 '{duplicateAlias.Key}'이(가) 중복되었습니다."; // 빠른 실행 별칭 키 vs 예약 프리픽스 충돌 foreach (var key in aliasKeys) { if (ReservedPrefixes.Contains(key)) return $"빠른 실행 키워드 '{key}'은(는) 시스템 예약어와 충돌합니다."; } // 배치 명령 키 중복 검사 var batchKeys = BatchCommands.Select(b => b.Key.ToLowerInvariant()).ToList(); var duplicateBatch = batchKeys.GroupBy(k => k).FirstOrDefault(g => g.Count() > 1); if (duplicateBatch != null) return $"배치 명령 키워드 '{duplicateBatch.Key}'이(가) 중복되었습니다."; return null; // 문제 없음 } public void Save() { // 충돌 검사 var conflict = ValidateBeforeSave(); if (conflict != null) { CustomMessageBox.Show( conflict, "AX Copilot — 설정 저장 오류", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning); return; } var s = _service.Settings; s.Hotkey = _hotkey; s.Launcher.MaxResults = _maxResults; s.Launcher.Opacity = _opacity; s.Launcher.Theme = _selectedThemeKey; s.Launcher.Position = _launcherPosition; s.Launcher.WebSearchEngine = _webSearchEngine; s.Launcher.SnippetAutoExpand = _snippetAutoExpand; s.Launcher.Language = _language; L10n.SetLanguage(_language); // 기능 토글 저장 s.Launcher.ShowNumberBadges = _showNumberBadges; s.Launcher.EnableFavorites = _enableFavorites; s.Launcher.EnableRecent = _enableRecent; s.Launcher.EnableActionMode = _enableActionMode; s.Launcher.CloseOnFocusLost = _closeOnFocusLost; s.Launcher.RememberPosition = _rememberPosition; s.Launcher.ShowPrefixBadge = _showPrefixBadge; s.Launcher.EnableIconAnimation = _enableIconAnimation; s.Launcher.EnableRainbowGlow = _enableRainbowGlow; s.Launcher.EnableSelectionGlow = _enableSelectionGlow; s.Launcher.EnableRandomPlaceholder = _enableRandomPlaceholder; s.Launcher.ShowLauncherBorder = _showLauncherBorder; s.Launcher.ShowWidgetPerf = _showWidgetPerf; s.Launcher.ShowWidgetPomo = _showWidgetPomo; s.Launcher.ShowWidgetNote = _showWidgetNote; s.Launcher.ShowWidgetWeather = _showWidgetWeather; s.Launcher.ShowWidgetCalendar = _showWidgetCalendar; s.Launcher.ShowWidgetBattery = _showWidgetBattery; s.Launcher.ShortcutHelpUseThemeColor = _shortcutHelpUseThemeColor; s.Launcher.EnableTextAction = _enableTextAction; s.Launcher.EnableFileDialogIntegration = _enableFileDialogIntegration; s.Launcher.EnableClipboardAutoCategory = _enableClipboardAutoCategory; s.Launcher.MaxPinnedClipboardItems = _maxPinnedClipboardItems; s.Launcher.TextActionTranslateLanguage = _textActionTranslateLanguage; s.Llm.MaxSubAgents = _maxSubAgents; s.Llm.PdfExportPath = _pdfExportPath; s.Llm.TipDurationSeconds = _tipDurationSeconds; // LLM 공통 설정 저장 s.Llm.Service = _llmService; s.Llm.Streaming = _llmStreaming; s.Llm.MaxContextTokens = Math.Clamp(_llmMaxContextTokens, 1024, 1_000_000); s.Llm.RetentionDays = _llmRetentionDays; s.Llm.Temperature = _llmTemperature; s.Llm.DefaultAgentPermission = PermissionModeCatalog.NormalizeGlobalMode(_defaultAgentPermission); s.Llm.DefaultOutputFormat = _defaultOutputFormat; s.Llm.DefaultMood = _defaultMood; s.Llm.AutoPreview = _autoPreview; s.Llm.MaxAgentIterations = _maxAgentIterations; s.Llm.MaxRetryOnError = _maxRetryOnError; s.Llm.EnableProactiveContextCompact = _enableProactiveContextCompact; s.Llm.ContextCompactTriggerPercent = _contextCompactTriggerPercent; s.AiEnabled = _aiEnabled; s.OperationMode = _operationMode; s.Llm.AgentTheme = _agentTheme; s.Llm.AgentThemePreset = _agentThemePreset; s.Llm.AgentLogLevel = _agentLogLevel; s.Llm.AgentUiExpressionLevel = _agentUiExpressionLevel; s.Llm.PlanDiffSeverityMediumCount = _planDiffSeverityMediumCount; s.Llm.PlanDiffSeverityHighCount = _planDiffSeverityHighCount; s.Llm.PlanDiffSeverityMediumRatioPercent = _planDiffSeverityMediumRatioPercent; s.Llm.PlanDiffSeverityHighRatioPercent = _planDiffSeverityHighRatioPercent; s.Llm.AgentDecisionLevel = _agentDecisionLevel; s.Llm.EnableMultiPassDocument = _enableMultiPassDocument; s.Llm.EnableCoworkVerification = _enableCoworkVerification; s.Llm.EnableFilePathHighlight = _enableFilePathHighlight; s.Llm.FolderDataUsage = _folderDataUsage; s.Llm.EnableAuditLog = _enableAuditLog; s.Llm.EnableAgentMemory = _enableAgentMemory; s.Llm.EnableProjectRules = _enableProjectRules; s.Llm.MaxMemoryEntries = _maxMemoryEntries; s.Llm.EnableImageInput = _enableImageInput; s.Llm.MaxImageSizeKb = _maxImageSizeKb; s.Llm.EnableToolHooks = _enableToolHooks; s.Llm.ToolHookTimeoutMs = _toolHookTimeoutMs; s.Llm.EnableHookInputMutation = _enableHookInputMutation; s.Llm.EnableHookPermissionUpdate = _enableHookPermissionUpdate; s.Llm.EnableSkillSystem = _enableSkillSystem; s.Llm.EnableForkSkillDelegationEnforcement = _enableForkSkillDelegationEnforcement; s.Llm.SkillsFolderPath = _skillsFolderPath; s.Llm.SlashPopupPageSize = _slashPopupPageSize; s.Llm.MaxFavoriteSlashCommands = _maxFavoriteSlashCommands; s.Llm.MaxRecentSlashCommands = _maxRecentSlashCommands; s.Llm.EnableDragDropAiActions = _enableDragDropAiActions; s.Llm.DragDropAutoSend = _dragDropAutoSend; s.Llm.Code.EnableCodeReview = _enableCodeReview; s.Llm.EnableAutoRouter = _enableAutoRouter; s.Llm.AutoRouterConfidence = _autoRouterConfidence; s.Llm.EnableChatRainbowGlow = _enableChatRainbowGlow; s.Llm.NotifyOnComplete = _notifyOnComplete; s.Llm.ShowTips = _showTips; s.Llm.DevMode = _devMode; s.Llm.DevModeStepApproval = _devModeStepApproval; s.Llm.WorkflowVisualizer = _workflowVisualizer; s.Llm.FreeTierMode = _freeTierMode; s.Llm.FreeTierDelaySeconds = _freeTierDelaySeconds; s.Llm.ShowTotalCallStats = _showTotalCallStats; // 서비스별 독립 설정 저장 s.Llm.OllamaEndpoint = _ollamaEndpoint; s.Llm.OllamaModel = _ollamaModel; s.Llm.VllmEndpoint = _vllmEndpoint; s.Llm.VllmModel = _vllmModel; s.Llm.VllmAllowInsecureTls = _vllmAllowInsecureTls; s.Llm.GeminiModel = _geminiModel; s.Llm.ClaudeModel = _sigmoidModel; s.Llm.GeminiApiKey = _geminiApiKey; s.Llm.ClaudeApiKey = _sigmoidApiKey; // 내부 서비스 API 키 저장 (암호화 분기) if (!string.IsNullOrEmpty(_ollamaApiKey) && _ollamaApiKey != "(저장됨)") s.Llm.OllamaApiKey = CryptoService.EncryptIfEnabled(_ollamaApiKey, s.Llm.EncryptionEnabled); if (!string.IsNullOrEmpty(_vllmApiKey) && _vllmApiKey != "(저장됨)") s.Llm.VllmApiKey = CryptoService.EncryptIfEnabled(_vllmApiKey, s.Llm.EncryptionEnabled); // 활성 서비스의 설정을 기존 호환 필드에도 동기화 (LlmService.cs 호환) switch (_llmService) { case "ollama": s.Llm.Endpoint = _ollamaEndpoint; s.Llm.Model = _ollamaModel; s.Llm.EncryptedApiKey = s.Llm.OllamaApiKey; break; case "vllm": s.Llm.Endpoint = _vllmEndpoint; s.Llm.Model = _vllmModel; s.Llm.EncryptedApiKey = s.Llm.VllmApiKey; break; case "gemini": s.Llm.ApiKey = _geminiApiKey; s.Llm.Model = _geminiModel; break; case "claude": case "sigmoid": s.Llm.ApiKey = _sigmoidApiKey; s.Llm.Model = _sigmoidModel; break; } // 등록 모델 저장 s.Llm.RegisteredModels = RegisteredModels .Where(rm => !string.IsNullOrWhiteSpace(rm.Alias)) .Select(rm => new RegisteredModel { Alias = rm.Alias, EncryptedModelName = rm.EncryptedModelName, Service = rm.Service, Endpoint = rm.Endpoint, ApiKey = rm.ApiKey, AllowInsecureTls = rm.AllowInsecureTls, AuthType = rm.AuthType ?? "bearer", Cp4dUrl = rm.Cp4dUrl ?? "", Cp4dUsername = rm.Cp4dUsername ?? "", Cp4dPassword = rm.Cp4dPassword ?? "", }) .ToList(); // 프롬프트 템플릿 저장 s.Llm.PromptTemplates = PromptTemplates .Where(pt => !string.IsNullOrWhiteSpace(pt.Name)) .Select(pt => new PromptTemplate { Name = pt.Name, Content = pt.Content, Icon = pt.Icon }) .ToList(); // 인덱스 경로 + 확장자 저장 s.IndexPaths = IndexPaths.ToList(); s.IndexExtensions = IndexExtensions.ToList(); s.IndexSpeed = _indexSpeed; // 커스텀 색상 + 모양 저장 var c = s.Launcher.CustomTheme ??= new CustomThemeColors(); foreach (var row in ColorRows) { var prop = typeof(CustomThemeColors).GetProperty(row.Property); prop?.SetValue(c, row.Hex); } c.WindowCornerRadius = _customWindowCornerRadius; c.ItemCornerRadius = _customItemCornerRadius; // 빠른 실행 단축키 저장: // batch/api/clipboard type은 그대로 유지, app/url/folder는 ViewModel 내용으로 교체 var otherAliases = s.Aliases .Where(a => a.Type is not ("app" or "url" or "folder" or "batch")) .ToList(); s.Aliases = otherAliases .Concat(AppShortcuts.Select(sc => new AliasEntry { Key = sc.Key, Type = sc.Type, Target = sc.Target, Description = string.IsNullOrWhiteSpace(sc.Description) ? null : sc.Description })) .Concat(BatchCommands.Select(b => new AliasEntry { Key = b.Key, Type = "batch", Target = b.Command, ShowWindow = b.ShowWindow })) .ToList(); // 스니펫 저장 s.Snippets = Snippets.Select(sn => new SnippetEntry { Key = sn.Key, Name = sn.Name, Content = sn.Content }).ToList(); // 알림 설정 저장 s.Reminder.Enabled = _reminderEnabled; s.Reminder.Corner = _reminderCorner; s.Reminder.IntervalMinutes = _reminderIntervalMinutes; s.Reminder.DisplaySeconds = _reminderDisplaySeconds; // 캡처 설정 저장 s.ScreenCapture.Prefix = string.IsNullOrWhiteSpace(_capPrefix) ? "cap" : _capPrefix.Trim(); s.ScreenCapture.GlobalHotkeyEnabled = _capGlobalHotkeyEnabled; s.ScreenCapture.GlobalHotkey = string.IsNullOrWhiteSpace(_capGlobalHotkey) ? "PrintScreen" : _capGlobalHotkey.Trim(); s.ScreenCapture.GlobalHotkeyMode = _capGlobalMode; s.ScreenCapture.ScrollDelayMs = Math.Max(50, _capScrollDelayMs); // 클립보드 히스토리 설정 저장 s.ClipboardHistory.Enabled = _clipboardEnabled; s.ClipboardHistory.MaxItems = _clipboardMaxItems; // 시스템 명령 설정 저장 var sc = s.SystemCommands; sc.ShowLock = _sysShowLock; sc.ShowSleep = _sysShowSleep; sc.ShowRestart = _sysShowRestart; sc.ShowShutdown = _sysShowShutdown; sc.ShowHibernate = _sysShowHibernate; sc.ShowLogout = _sysShowLogout; sc.ShowRecycleBin = _sysShowRecycleBin; // 시스템 명령 별칭 저장 var cmdAliases = new Dictionary>(); void SaveAlias(string key, string val) { var list = ParseAliases(val); if (list.Count > 0) cmdAliases[key] = list; } SaveAlias("lock", _aliasLock); SaveAlias("sleep", _aliasSleep); SaveAlias("restart", _aliasRestart); SaveAlias("shutdown", _aliasShutdown); SaveAlias("hibernate", _aliasHibernate); SaveAlias("logout", _aliasLogout); SaveAlias("recycle", _aliasRecycle); sc.CommandAliases = cmdAliases; _service.Save(); SaveCompleted?.Invoke(this, EventArgs.Empty); } // ─── 스니펫 설정 ────────────────────────────────────────────────────────── public ObservableCollection Snippets { get; } = new(); private string _newSnippetKey = ""; private string _newSnippetName = ""; private string _newSnippetContent = ""; public string NewSnippetKey { get => _newSnippetKey; set { _newSnippetKey = value; OnPropertyChanged(); } } public string NewSnippetName { get => _newSnippetName; set { _newSnippetName = value; OnPropertyChanged(); } } public string NewSnippetContent { get => _newSnippetContent; set { _newSnippetContent = value; OnPropertyChanged(); } } public bool AddSnippet() { if (string.IsNullOrWhiteSpace(_newSnippetKey) || string.IsNullOrWhiteSpace(_newSnippetContent)) return false; if (Snippets.Any(sn => sn.Key.Equals(_newSnippetKey.Trim(), StringComparison.OrdinalIgnoreCase))) return false; Snippets.Add(new SnippetRowModel { Key = _newSnippetKey.Trim(), Name = _newSnippetName.Trim(), Content = _newSnippetContent.Trim() }); NewSnippetKey = ""; NewSnippetName = ""; NewSnippetContent = ""; return true; } public void RemoveSnippet(SnippetRowModel row) => Snippets.Remove(row); // ─── 클립보드 히스토리 설정 ──────────────────────────────────────────────── private bool _clipboardEnabled; private int _clipboardMaxItems; public bool ClipboardEnabled { get => _clipboardEnabled; set { _clipboardEnabled = value; OnPropertyChanged(); } } public int ClipboardMaxItems { get => _clipboardMaxItems; set { _clipboardMaxItems = value; OnPropertyChanged(); } } // ─── 스크린 캡처 설정 ────────────────────────────────────────────────────── private string _capPrefix = "cap"; private bool _capGlobalHotkeyEnabled; private string _capGlobalHotkey = "PrintScreen"; private string _capGlobalMode = "screen"; private int _capScrollDelayMs = 120; public string CapPrefix { get => _capPrefix; set { _capPrefix = value; OnPropertyChanged(); } } public void ResetCapPrefix() => CapPrefix = "cap"; public bool CapGlobalHotkeyEnabled { get => _capGlobalHotkeyEnabled; set { _capGlobalHotkeyEnabled = value; OnPropertyChanged(); } } public string CapGlobalHotkey { get => _capGlobalHotkey; set { _capGlobalHotkey = value; OnPropertyChanged(); } } public string CapGlobalMode { get => _capGlobalMode; set { _capGlobalMode = value; OnPropertyChanged(); } } public int CapScrollDelayMs { get => _capScrollDelayMs; set { _capScrollDelayMs = value; OnPropertyChanged(); OnPropertyChanged(nameof(CapScrollDelayMsStr)); } } /// ComboBox SelectedValue와 string 바인딩 용도 public string CapScrollDelayMsStr { get => _capScrollDelayMs.ToString(); set { if (int.TryParse(value, out int v)) CapScrollDelayMs = v; } } public void ResetCapGlobalHotkey() { CapGlobalHotkey = "PrintScreen"; CapGlobalMode = "screen"; } // ─── 잠금 해제 알림 설정 ────────────────────────────────────────────────── private bool _reminderEnabled; private string _reminderCorner = "bottom-right"; private int _reminderIntervalMinutes = 60; private int _reminderDisplaySeconds = 15; public List GetReminderCategories() => _service.Settings.Reminder.EnabledCategories; public bool ReminderEnabled { get => _reminderEnabled; set { _reminderEnabled = value; OnPropertyChanged(); } } public string ReminderCorner { get => _reminderCorner; set { _reminderCorner = value; OnPropertyChanged(); } } public int ReminderIntervalMinutes { get => _reminderIntervalMinutes; set { _reminderIntervalMinutes = value; OnPropertyChanged(); } } public int ReminderDisplaySeconds { get => _reminderDisplaySeconds; set { _reminderDisplaySeconds = value; OnPropertyChanged(); } } // ─── 시스템 명령 설정 ────────────────────────────────────────────────────── private bool _sysShowLock; private bool _sysShowSleep; private bool _sysShowRestart; private bool _sysShowShutdown; private bool _sysShowHibernate; private bool _sysShowLogout; private bool _sysShowRecycleBin; public bool SysShowLock { get => _sysShowLock; set { _sysShowLock = value; OnPropertyChanged(); } } public bool SysShowSleep { get => _sysShowSleep; set { _sysShowSleep = value; OnPropertyChanged(); } } public bool SysShowRestart { get => _sysShowRestart; set { _sysShowRestart = value; OnPropertyChanged(); } } public bool SysShowShutdown { get => _sysShowShutdown; set { _sysShowShutdown = value; OnPropertyChanged(); } } public bool SysShowHibernate { get => _sysShowHibernate; set { _sysShowHibernate = value; OnPropertyChanged(); } } public bool SysShowLogout { get => _sysShowLogout; set { _sysShowLogout = value; OnPropertyChanged(); } } public bool SysShowRecycleBin { get => _sysShowRecycleBin; set { _sysShowRecycleBin = value; OnPropertyChanged(); } } // ─── 시스템 명령 별칭 (쉼표 구분 문자열) ─────────────────────────────────── private string _aliasLock = ""; private string _aliasSleep = ""; private string _aliasRestart = ""; private string _aliasShutdown = ""; private string _aliasHibernate = ""; private string _aliasLogout = ""; private string _aliasRecycle = ""; public string AliasLock { get => _aliasLock; set { _aliasLock = value; OnPropertyChanged(); } } public string AliasSleep { get => _aliasSleep; set { _aliasSleep = value; OnPropertyChanged(); } } public string AliasRestart { get => _aliasRestart; set { _aliasRestart = value; OnPropertyChanged(); } } public string AliasShutdown { get => _aliasShutdown; set { _aliasShutdown = value; OnPropertyChanged(); } } public string AliasHibernate { get => _aliasHibernate; set { _aliasHibernate = value; OnPropertyChanged(); } } public string AliasLogout { get => _aliasLogout; set { _aliasLogout = value; OnPropertyChanged(); } } public string AliasRecycle { get => _aliasRecycle; set { _aliasRecycle = value; OnPropertyChanged(); } } public void ResetSystemCommandAliases() { AliasLock = ""; AliasSleep = ""; AliasRestart = ""; AliasShutdown = ""; AliasHibernate = ""; AliasLogout = ""; AliasRecycle = ""; } private static string FormatAliases(Dictionary> dict, string key) => dict.TryGetValue(key, out var list) ? string.Join(", ", list) : ""; private static List ParseAliases(string input) => input.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Where(s => !string.IsNullOrWhiteSpace(s)) .ToList(); public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); } // ─── 프롬프트 템플릿 행 모델 ───────────────────────────────────────────────────── public class PromptTemplateRow : INotifyPropertyChanged { private string _name = ""; private string _content = ""; private string _icon = "\uE8BD"; public string Name { get => _name; set { _name = value; OnPropertyChanged(); } } public string Content { get => _content; set { _content = value; OnPropertyChanged(); OnPropertyChanged(nameof(Preview)); } } public string Icon { get => _icon; set { _icon = value; OnPropertyChanged(); } } public string Preview => Content.Length > 60 ? Content[..57].Replace("\n", " ") + "…" : Content.Replace("\n", " "); public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); } // ─── 등록 모델 행 모델 ────────────────────────────────────────────────────────── public class RegisteredModelRow : INotifyPropertyChanged { private string _alias = ""; private string _encryptedModelName = ""; private string _service = "ollama"; private string _endpoint = ""; private string _apiKey = ""; private bool _allowInsecureTls; /// UI 표시용 별칭 public string Alias { get => _alias; set { _alias = value; OnPropertyChanged(); } } /// 암호화된 실제 모델명 (PortableEncrypt) public string EncryptedModelName { get => _encryptedModelName; set { _encryptedModelName = value; OnPropertyChanged(); OnPropertyChanged(nameof(MaskedModelName)); } } /// 서비스 타입 (ollama / vllm) public string Service { get => _service; set { _service = value; OnPropertyChanged(); OnPropertyChanged(nameof(ServiceLabel)); } } /// 이 모델 전용 서버 엔드포인트. 비어있으면 기본 엔드포인트 사용. public string Endpoint { get => _endpoint; set { _endpoint = value; OnPropertyChanged(); OnPropertyChanged(nameof(EndpointDisplay)); } } /// 이 모델 전용 API 키. 비어있으면 기본 API 키 사용. public string ApiKey { get => _apiKey; set { _apiKey = value; OnPropertyChanged(); } } /// 이 모델 요청 시 TLS 인증서 검증을 생략합니다. public bool AllowInsecureTls { get => _allowInsecureTls; set { _allowInsecureTls = value; OnPropertyChanged(); } } // ── CP4D 인증 필드 ────────────────────────────────────────────────── private string _authType = "bearer"; private string _cp4dUrl = ""; private string _cp4dUsername = ""; private string _cp4dPassword = ""; /// 인증 방식. bearer | ibm_iam | cp4d public string AuthType { get => _authType; set { _authType = value; OnPropertyChanged(); OnPropertyChanged(nameof(AuthLabel)); } } /// CP4D 서버 URL public string Cp4dUrl { get => _cp4dUrl; set { _cp4dUrl = value; OnPropertyChanged(); } } /// CP4D 사용자 이름 public string Cp4dUsername { get => _cp4dUsername; set { _cp4dUsername = value; OnPropertyChanged(); } } /// CP4D 비밀번호 (암호화 저장) public string Cp4dPassword { get => _cp4dPassword; set { _cp4dPassword = value; OnPropertyChanged(); } } /// 인증 방식 라벨 public string AuthLabel => (_authType ?? "bearer").ToLowerInvariant() switch { "cp4d" => "CP4D", "ibm_iam" => "IBM IAM", _ => "Bearer", }; /// UI에 표시할 엔드포인트 요약 public string EndpointDisplay => string.IsNullOrEmpty(_endpoint) ? "(기본 서버)" : _endpoint; /// UI에 표시할 마스킹된 모델명 public string MaskedModelName => string.IsNullOrEmpty(_encryptedModelName) ? "(미등록)" : "••••••••"; /// 서비스 라벨 public string ServiceLabel => _service == "vllm" ? "vLLM" : "Ollama"; public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); } // ─── 스니펫 행 모델 ──────────────────────────────────────────────────────────── public class SnippetRowModel : INotifyPropertyChanged { private string _key = ""; private string _name = ""; private string _content = ""; public string Key { get => _key; set { _key = value; OnPropertyChanged(); } } public string Name { get => _name; set { _name = value; OnPropertyChanged(); } } public string Content { get => _content; set { _content = value; OnPropertyChanged(); } } public string Preview => Content.Length > 50 ? Content[..47].Replace("\n", " ") + "…" : Content.Replace("\n", " "); public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); }