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.Themes; using AxCopilot.Views; namespace AxCopilot.ViewModels; public partial class SettingsViewModel : INotifyPropertyChanged { private readonly SettingsService _service; internal SettingsService Service => _service; // ─── 작업 복사본 ─────────────────────────────────────────────────────── 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 _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 _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 string _geminiApiKey = ""; private string _geminiModel = "gemini-2.5-flash"; private string _claudeApiKey = ""; private string _claudeModel = "claude-sonnet-4-6"; // ─── 이벤트 ─────────────────────────────────────────────────────────── 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; _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 = llm.Service; _llmStreaming = llm.Streaming; _llmMaxContextTokens = llm.MaxContextTokens; _llmRetentionDays = llm.RetentionDays; _llmTemperature = llm.Temperature; _defaultAgentPermission = 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; _agentLogLevel = llm.AgentLogLevel; _agentDecisionLevel = llm.AgentDecisionLevel; _planMode = string.IsNullOrEmpty(llm.PlanMode) ? "off" : llm.PlanMode; _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; _enableSkillSystem = llm.EnableSkillSystem; _skillsFolderPath = llm.SkillsFolderPath; _slashPopupPageSize = llm.SlashPopupPageSize > 0 ? Math.Clamp(llm.SlashPopupPageSize, 3, 10) : 6; _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; _geminiModel = string.IsNullOrEmpty(llm.GeminiModel) ? "gemini-2.5-flash" : llm.GeminiModel; _claudeModel = string.IsNullOrEmpty(llm.ClaudeModel) ? "claude-sonnet-4-6" : llm.ClaudeModel; // API 키 로드: 서비스별 독립 저장 _geminiApiKey = llm.GeminiApiKey; _claudeApiKey = 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) && llm.Service == "claude") { _claudeApiKey = llm.ApiKey; _claudeModel = 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, 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 h in s.CustomHotkeys) CustomHotkeys.Add(new HotkeyRowModel { Hotkey = h.Hotkey, Target = h.Target, Label = h.Label, Type = h.Type }); // 스니펫 로드 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; } }