창 위치 기억 (Feature 1):
- AppSettings.cs: LauncherSettings에 RememberPosition, LastLeft, LastTop 프로퍼티 추가
- SettingsViewModel.cs/.Properties.cs/.Methods.cs: RememberPosition 바인딩 프로퍼티 연동
- LauncherWindow.Animations.cs: CenterOnScreen() — RememberPosition ON 시 저장 좌표 복원
- LauncherWindow.Shell.cs: Window_Deactivated — 비활성화 시 현재 위치 비동기 저장
- SettingsWindow.xaml: 런처 탭 › "마지막 위치 기억" 토글 추가
파일 아이콘 표시 (Feature 2):
- Services/IconCacheService.cs (신규, 192줄): Shell32 SHGetFileInfo로 아이콘 추출,
%LOCALAPPDATA%\AxCopilot\IconCache\에 PNG 캐시, WarmUp()으로 앱 시작 시 미리 준비
- Core/CommandResolver.cs: 퍼지 검색 결과에 IconCacheService.GetIconPath() 연결
- Handlers/FileBrowserHandler.cs: 상위폴더·폴더·파일 항목에 IconCacheService 연결
- App.xaml.cs: SystemIdle 시점에 IconCacheService.WarmUp() 호출
퍼지 검색 랭킹 개선 (Feature 3):
- Services/UsageRankingService.cs 전면 개선: 기존 int 횟수 → UsageRecord{Count, LastUsedMs}
- GetScore() 반환형 int → double, 30일 반감기 지수 감쇠(decay=exp(-days/43.3)) 적용
- 구형 usage.json 자동 마이그레이션 (count만 있는 형식 → 신규 형식)
- GetTopItems() / SortByUsage() 점수 기준 정렬로 업데이트
미리보기 패널 (Feature 4):
- ViewModels/LauncherViewModel.cs: PreviewText, HasPreview 프로퍼티 + UpdatePreviewAsync()
클립보드 텍스트(최대 400자) 및 텍스트 파일(최초 6줄) 미리보기, 80ms 디바운스
- Views/LauncherWindow.xaml: RowDefinitions 7→8개, Row5에 PreviewPanel Border 삽입,
IndexStatusText Row5→6, WidgetBar Row6→7, ToastOverlay RowSpan 3→4
빌드: 경고 0, 오류 0
327 lines
15 KiB
C#
327 lines
15 KiB
C#
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<ColorRowModel>
|
|
{
|
|
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;
|
|
}
|
|
}
|