Some checks failed
Release Gate / gate (push) Has been cancelled
IBM Cloud 계열 vLLM 연결에서 등록 모델 인증 방식이 Bearer와 CP4D만 지원하던 문제를 점검하고, IBM IAM 토큰 교환 경로를 추가했습니다. - RegisteredModel/AuthType에 ibm_iam 경로를 반영했습니다. - IbmIamTokenService를 추가해 API 키를 IAM access token으로 교환한 뒤 Bearer 헤더로 적용하도록 했습니다. - 모델 등록 다이얼로그, 설정 ViewModel, AX Agent 오버레이 모델 목록에도 IBM IAM 표시를 추가했습니다. - README.md와 docs/DEVELOPMENT.md에 2026-04-06 14:06 (KST) 기준 이력을 반영했습니다. 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0 / 오류 0)
2101 lines
83 KiB
C#
2101 lines
83 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.Services.Agent;
|
|
using AxCopilot.Themes;
|
|
using AxCopilot.Views;
|
|
|
|
namespace AxCopilot.ViewModels;
|
|
|
|
/// <summary>
|
|
/// 설정 카드에 표시할 테마 미리보기 데이터
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 커스텀 테마 단일 색상 항목 (색상 편집 탭 1행 1색)
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 빠른 실행 단축키 항목 (키워드 → 앱/URL/폴더)
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 배치 명령 항목 (> 프리픽스)
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>CodeSettings 바인딩용 프로퍼티. XAML에서 {Binding Code.EnableLsp} 등으로 접근.</summary>
|
|
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<RegisteredModelRow> 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<string> BlockedPaths { get; } = new();
|
|
public ObservableCollection<string> 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<ThemeCardModel> 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<ColorRowModel> ColorRows { get; }
|
|
|
|
// ─── 프롬프트 템플릿 ──────────────────────────────────────────────────
|
|
public ObservableCollection<PromptTemplateRow> PromptTemplates { get; } = new();
|
|
|
|
// ─── 배치 명령 ────────────────────────────────────────────────────────
|
|
public ObservableCollection<BatchCommandModel> 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<AppShortcutModel> 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<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;
|
|
}
|
|
|
|
// ─── 커스텀 테마 모서리 라운딩 ───────────────────────────────────────────
|
|
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<string> 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<string> 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);
|
|
|
|
/// <summary>파일 선택 대화상자. 선택 시 NewTarget에 자동 설정.</summary>
|
|
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<string> 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",
|
|
};
|
|
|
|
/// <summary>설정 저장 전 프리픽스/키워드 충돌을 검사합니다. 충돌 시 메시지를 반환합니다.</summary>
|
|
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<string, List<string>>();
|
|
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<SnippetRowModel> 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)); }
|
|
}
|
|
|
|
/// <summary>ComboBox SelectedValue와 string 바인딩 용도</summary>
|
|
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<string> 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<string, List<string>> dict, string key)
|
|
=> dict.TryGetValue(key, out var list) ? string.Join(", ", list) : "";
|
|
|
|
private static List<string> 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;
|
|
|
|
/// <summary>UI 표시용 별칭</summary>
|
|
public string Alias
|
|
{
|
|
get => _alias;
|
|
set { _alias = value; OnPropertyChanged(); }
|
|
}
|
|
|
|
/// <summary>암호화된 실제 모델명 (PortableEncrypt)</summary>
|
|
public string EncryptedModelName
|
|
{
|
|
get => _encryptedModelName;
|
|
set { _encryptedModelName = value; OnPropertyChanged(); OnPropertyChanged(nameof(MaskedModelName)); }
|
|
}
|
|
|
|
/// <summary>서비스 타입 (ollama / vllm)</summary>
|
|
public string Service
|
|
{
|
|
get => _service;
|
|
set { _service = value; OnPropertyChanged(); OnPropertyChanged(nameof(ServiceLabel)); }
|
|
}
|
|
|
|
/// <summary>이 모델 전용 서버 엔드포인트. 비어있으면 기본 엔드포인트 사용.</summary>
|
|
public string Endpoint
|
|
{
|
|
get => _endpoint;
|
|
set { _endpoint = value; OnPropertyChanged(); OnPropertyChanged(nameof(EndpointDisplay)); }
|
|
}
|
|
|
|
/// <summary>이 모델 전용 API 키. 비어있으면 기본 API 키 사용.</summary>
|
|
public string ApiKey
|
|
{
|
|
get => _apiKey;
|
|
set { _apiKey = value; OnPropertyChanged(); }
|
|
}
|
|
|
|
/// <summary>이 모델 요청 시 TLS 인증서 검증을 생략합니다.</summary>
|
|
public bool AllowInsecureTls
|
|
{
|
|
get => _allowInsecureTls;
|
|
set { _allowInsecureTls = value; OnPropertyChanged(); }
|
|
}
|
|
|
|
// ── CP4D 인증 필드 ──────────────────────────────────────────────────
|
|
|
|
private string _authType = "bearer";
|
|
private string _cp4dUrl = "";
|
|
private string _cp4dUsername = "";
|
|
private string _cp4dPassword = "";
|
|
|
|
/// <summary>인증 방식. bearer | ibm_iam | cp4d</summary>
|
|
public string AuthType
|
|
{
|
|
get => _authType;
|
|
set { _authType = value; OnPropertyChanged(); OnPropertyChanged(nameof(AuthLabel)); }
|
|
}
|
|
|
|
/// <summary>CP4D 서버 URL</summary>
|
|
public string Cp4dUrl
|
|
{
|
|
get => _cp4dUrl;
|
|
set { _cp4dUrl = value; OnPropertyChanged(); }
|
|
}
|
|
|
|
/// <summary>CP4D 사용자 이름</summary>
|
|
public string Cp4dUsername
|
|
{
|
|
get => _cp4dUsername;
|
|
set { _cp4dUsername = value; OnPropertyChanged(); }
|
|
}
|
|
|
|
/// <summary>CP4D 비밀번호 (암호화 저장)</summary>
|
|
public string Cp4dPassword
|
|
{
|
|
get => _cp4dPassword;
|
|
set { _cp4dPassword = value; OnPropertyChanged(); }
|
|
}
|
|
|
|
/// <summary>인증 방식 라벨</summary>
|
|
public string AuthLabel => (_authType ?? "bearer").ToLowerInvariant() switch
|
|
{
|
|
"cp4d" => "CP4D",
|
|
"ibm_iam" => "IBM IAM",
|
|
_ => "Bearer",
|
|
};
|
|
|
|
/// <summary>UI에 표시할 엔드포인트 요약</summary>
|
|
public string EndpointDisplay => string.IsNullOrEmpty(_endpoint) ? "(기본 서버)" : _endpoint;
|
|
|
|
/// <summary>UI에 표시할 마스킹된 모델명</summary>
|
|
public string MaskedModelName => string.IsNullOrEmpty(_encryptedModelName) ? "(미등록)" : "••••••••";
|
|
|
|
/// <summary>서비스 라벨</summary>
|
|
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));
|
|
}
|