재구성 AX Agent 설정과 채팅 UI를 Claude형 구조로
Some checks failed
Release Gate / gate (push) Has been cancelled

This commit is contained in:
2026-04-04 17:48:51 +09:00
parent 90c2f15e96
commit a027ea4f9a
6000 changed files with 11532 additions and 94063 deletions

View File

@@ -15,6 +15,7 @@ namespace AxCopilot.Views;
/// <summary>AX Agent 창. 데스크톱 코파일럿 스타일 — 사이드바 + 카테고리 분류 + 타임라인.</summary>
public partial class ChatWindow : Window
{
private const string UnifiedAdminPassword = "axgo123!";
private readonly SettingsService _settings;
private readonly ChatStorageService _storage;
private readonly DraftQueueProcessorService _draftQueueProcessor = new();
@@ -76,7 +77,7 @@ public partial class ChatWindow : Window
private readonly DispatcherTimer _typingTimer;
private int _displayedLength; // 현재 화면에 표시된 글자 수
private ResourceDictionary? _agentThemeDictionary;
private AgentSettingsWindow? _agentSettingsWindow;
private bool _isOverlaySettingsSyncing;
private sealed class ConversationMeta
{
@@ -984,6 +985,15 @@ public partial class ChatWindow : Window
ApplyAgentThemeResources();
ApplyExpressionLevelUi();
ApplySidebarStateForActiveTab(animated: false);
if (CurrentTabTitle != null)
{
CurrentTabTitle.Text = _activeTab switch
{
"Cowork" => "AX Agent · Cowork",
"Code" => "AX Agent · Code",
_ => "AX Agent · Chat",
};
}
// 폴더 바는 Cowork/Code 탭에서만 표시
if (FolderBar != null)
@@ -1892,9 +1902,9 @@ public partial class ChatWindow : Window
return button;
}
actionRow.Children.Add(CreateActionButton("활용하지 않음", "#FEF2F2", "#991B1B", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Deny)));
actionRow.Children.Add(CreateActionButton("소극 활용", "#EEF2FF", "#1D4ED8", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Default)));
actionRow.Children.Add(CreateActionButton("적극 활용", "#ECFDF5", "#166534", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.AcceptEdits)));
actionRow.Children.Add(CreateActionButton("읽기 전용", "#FEF2F2", "#991B1B", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Deny)));
actionRow.Children.Add(CreateActionButton("권한 요청", "#EEF2FF", "#1D4ED8", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.Default)));
actionRow.Children.Add(CreateActionButton("편집 자동 승인", "#ECFDF5", "#166534", () => SetToolPermissionOverride(latestDenied.ToolName!, PermissionModeCatalog.AcceptEdits)));
actionRow.Children.Add(CreateActionButton("예외 해제", "#F3F4F6", "#374151", () => SetToolPermissionOverride(latestDenied.ToolName!, null)));
deniedStack.Children.Add(actionRow);
}
@@ -2117,7 +2127,6 @@ public partial class ChatWindow : Window
BtnPermission_Click(this, new RoutedEventArgs());
}
private bool _permissionTopBannerDismissed; // 상단 권한 배너 닫힘 상태
private string _lastPermissionBannerMode = "";
private bool GetPermissionPopupSectionExpanded(string sectionKey, bool defaultValue = false)
@@ -2137,7 +2146,6 @@ public partial class ChatWindow : Window
private void BtnPermissionTopBannerClose_Click(object sender, RoutedEventArgs e)
{
_permissionTopBannerDismissed = true;
if (PermissionTopBanner != null)
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
@@ -2167,7 +2175,6 @@ public partial class ChatWindow : Window
if (!string.Equals(_lastPermissionBannerMode, perm, StringComparison.OrdinalIgnoreCase))
{
_permissionTopBannerDismissed = false;
_lastPermissionBannerMode = perm;
}
@@ -2182,10 +2189,10 @@ public partial class ChatWindow : Window
PermissionTopBanner.BorderBrush = BrushFromHex("#86EFAC");
PermissionTopBannerIcon.Text = "\uE73E";
PermissionTopBannerIcon.Foreground = activeColor;
PermissionTopBannerTitle.Text = "현재 권한 모드 · 적극 활용";
PermissionTopBannerTitle.Text = "현재 권한 모드 · 편집 자동 승인";
PermissionTopBannerTitle.Foreground = BrushFromHex("#166534");
PermissionTopBannerText.Text = "파일 편집 도구는 자동 승인하고, 명령 실행은 계속 확인합니다.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBannerText.Text = "모든 파일 편집 자동 승인합니다. 명령 실행은 계속 확인합니다.";
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
}
else if (perm == PermissionModeCatalog.Deny)
@@ -2198,10 +2205,10 @@ public partial class ChatWindow : Window
PermissionTopBanner.BorderBrush = BrushFromHex("#86EFAC");
PermissionTopBannerIcon.Text = "\uE73E";
PermissionTopBannerIcon.Foreground = denyColor;
PermissionTopBannerTitle.Text = "현재 권한 모드 · 활용하지 않음";
PermissionTopBannerTitle.Text = "현재 권한 모드 · 읽기 전용";
PermissionTopBannerTitle.Foreground = denyColor;
PermissionTopBannerText.Text = "파일 읽기만 허용하고 생성/수정/삭제는 차단합니다.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
}
else if (perm == PermissionModeCatalog.BypassPermissions)
@@ -2214,10 +2221,10 @@ public partial class ChatWindow : Window
PermissionTopBanner.BorderBrush = BrushFromHex("#FDBA74");
PermissionTopBannerIcon.Text = "\uE814";
PermissionTopBannerIcon.Foreground = autoColor;
PermissionTopBannerTitle.Text = "현재 권한 모드 · 완전 자동";
PermissionTopBannerTitle.Text = "현재 권한 모드 · 권한 건너뛰기";
PermissionTopBannerTitle.Foreground = autoColor;
PermissionTopBannerText.Text = "권한 확인을 대부분 생략합니다. 민감한 작업 전에는 설정을 다시 확인하세요.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBannerText.Text = "모든 권한을 허용합니다. 민감한 작업 전에는 설정을 다시 확인하세요.";
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
}
else if (perm == PermissionModeCatalog.DontAsk)
@@ -2233,7 +2240,7 @@ public partial class ChatWindow : Window
PermissionTopBannerTitle.Text = "현재 권한 모드 · 질문 없이 진행";
PermissionTopBannerTitle.Foreground = dangerColor;
PermissionTopBannerText.Text = "권한 확인을 거의 생략합니다. 민감한 작업 전에는 설정을 다시 확인하세요.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
}
else
@@ -2253,20 +2260,20 @@ public partial class ChatWindow : Window
PermissionTopBanner.BorderBrush = BrushFromHex("#C7D2FE");
PermissionTopBannerIcon.Text = "\uE7C3";
PermissionTopBannerIcon.Foreground = BrushFromHex("#4338CA");
PermissionTopBannerTitle.Text = "현재 권한 모드 · 계획 중심";
PermissionTopBannerTitle.Text = "현재 권한 모드 · 계획 모드";
PermissionTopBannerTitle.Foreground = BrushFromHex("#4338CA");
PermissionTopBannerText.Text = "쓰기 작업은 제한하고, 먼저 계획과 승인 흐름을 우선합니다.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBannerText.Text = "변경 전에 계획을 먼저 만들고 승인 흐름을 우선합니다.";
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
else if (perm == PermissionModeCatalog.Default)
{
PermissionTopBanner.BorderBrush = BrushFromHex("#BFDBFE");
PermissionTopBannerIcon.Text = "\uE8D7";
PermissionTopBannerIcon.Foreground = BrushFromHex("#1D4ED8");
PermissionTopBannerTitle.Text = "현재 권한 모드 · 소극 활용";
PermissionTopBannerTitle.Text = "현재 권한 모드 · 권한 요청";
PermissionTopBannerTitle.Foreground = BrushFromHex("#1D4ED8");
PermissionTopBannerText.Text = "변경 전 확인하고, 필요한 경우에만 파일 접근을 진행합니다.";
PermissionTopBanner.Visibility = _permissionTopBannerDismissed ? Visibility.Collapsed : Visibility.Visible;
PermissionTopBannerText.Text = "변경하기 전에 항상 확인합니다.";
PermissionTopBanner.Visibility = Visibility.Collapsed;
}
else
{
@@ -5667,8 +5674,20 @@ public partial class ChatWindow : Window
if (!_slashVisibleItemByAbsoluteIndex.TryGetValue(_slashPalette.SelectedIndex, out var item))
return;
var bounds = item.TransformToAncestor(SlashScrollViewer)
.TransformBounds(new Rect(0, 0, item.ActualWidth, item.ActualHeight));
if (!IsVisualDescendantOf(item, SlashScrollViewer))
return;
Rect bounds;
try
{
bounds = item.TransformToAncestor(SlashScrollViewer)
.TransformBounds(new Rect(0, 0, item.ActualWidth, item.ActualHeight));
}
catch
{
// 렌더 트리 갱신 중에는 transform이 실패할 수 있어 조용히 무시.
return;
}
if (bounds.Top < 0)
SlashScrollViewer.ScrollToVerticalOffset(SlashScrollViewer.VerticalOffset + bounds.Top - 8);
@@ -5676,6 +5695,22 @@ public partial class ChatWindow : Window
SlashScrollViewer.ScrollToVerticalOffset(SlashScrollViewer.VerticalOffset + (bounds.Bottom - SlashScrollViewer.ViewportHeight) + 8);
}
private static bool IsVisualDescendantOf(DependencyObject? child, DependencyObject? parent)
{
if (child == null || parent == null)
return false;
var current = child;
while (current != null)
{
if (ReferenceEquals(current, parent))
return true;
current = VisualTreeHelper.GetParent(current);
}
return false;
}
private void UpdateSlashSelectionVisualState()
{
if (_slashVisibleItemByAbsoluteIndex.Count == 0)
@@ -12997,66 +13032,17 @@ public partial class ChatWindow : Window
private void OpenAgentSettingsWindow()
{
if (_agentSettingsWindow != null)
RefreshOverlaySettingsPanel();
AgentSettingsOverlay.Visibility = Visibility.Visible;
InlineSettingsPanel.Visibility = Visibility.Collapsed;
SetOverlaySection("common");
Dispatcher.BeginInvoke(() =>
{
try
{
if (_agentSettingsWindow.IsVisible)
{
_agentSettingsWindow.Activate();
return;
}
}
catch
{
// ignore stale window instance
}
_agentSettingsWindow = null;
}
var win = new AgentSettingsWindow(_settings);
if (IsLoaded && IsVisible)
win.Owner = this;
_agentSettingsWindow = win;
win.Closed += (_, _) => _agentSettingsWindow = null;
try
{
win.Resources.MergedDictionaries.Insert(0, new ResourceDictionary
{
Source = BuildAgentThemeDictionaryUri(),
});
}
catch
{
// 테마 사전 로드 실패 시에도 설정창 자체는 열리도록 유지
}
bool changed;
try
{
changed = win.ShowDialog() == true;
}
catch
{
// 모달 창 오픈에 실패하면 일반 창으로라도 설정 접근을 보장
try
{
win.Show();
win.Activate();
}
catch { }
return;
}
if (!changed)
return;
_appState.LoadFromSettings(_settings);
ApplyAgentThemeResources();
UpdateSidebarModeMenu();
UpdateModelLabel();
RefreshInlineSettingsPanel();
UpdateTabUI();
ShowToast("AX Agent 설정이 저장되었습니다.");
if (CmbOverlayModel.Items.Count > 0)
CmbOverlayModel.Focus();
else
InputBox.Focus();
}, DispatcherPriority.Input);
}
public void OpenAgentSettingsFromExternal()
@@ -13069,6 +13055,515 @@ public partial class ChatWindow : Window
}, DispatcherPriority.Input);
}
private void BtnOverlaySettingsClose_Click(object sender, RoutedEventArgs e)
{
AgentSettingsOverlay.Visibility = Visibility.Collapsed;
InputBox.Focus();
}
private void OverlayNav_Checked(object sender, RoutedEventArgs e)
{
if (sender is not RadioButton rb || rb.Tag is not string tag)
return;
SetOverlaySection(tag);
}
private void SetOverlaySection(string tag)
{
if (OverlaySectionService == null || OverlaySectionQuick == null || OverlaySectionDetail == null)
return;
var section = string.IsNullOrWhiteSpace(tag) ? "common" : tag.Trim().ToLowerInvariant();
OverlaySectionService.Visibility = section == "service" ? Visibility.Visible : Visibility.Collapsed;
OverlaySectionQuick.Visibility = section == "permission" ? Visibility.Visible : Visibility.Collapsed;
OverlaySectionDetail.Visibility = section == "service" ? Visibility.Collapsed : Visibility.Visible;
var showCommon = section == "common";
var showPermission = section == "permission";
var showAdvanced = section == "advanced";
if (OverlayAiEnabledRow != null)
OverlayAiEnabledRow.Visibility = showCommon ? Visibility.Visible : Visibility.Collapsed;
if (OverlayThemePanel != null)
OverlayThemePanel.Visibility = showCommon ? Visibility.Visible : Visibility.Collapsed;
if (OverlayModelEditorPanel != null)
OverlayModelEditorPanel.Visibility = showCommon ? Visibility.Visible : Visibility.Collapsed;
if (OverlayAnchorPermission != null)
OverlayAnchorPermission.Visibility = showPermission ? Visibility.Visible : Visibility.Collapsed;
if (OverlayFolderDataUsageRow != null)
OverlayFolderDataUsageRow.Visibility = showCommon || showPermission ? Visibility.Visible : Visibility.Collapsed;
if (OverlayTlsRow != null)
OverlayTlsRow.Visibility = showCommon || showPermission ? Visibility.Visible : Visibility.Collapsed;
if (OverlayAnchorAdvanced != null)
OverlayAnchorAdvanced.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
if (OverlayMaxContextTokensRow != null)
OverlayMaxContextTokensRow.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
if (OverlayMaxRetryRow != null)
OverlayMaxRetryRow.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
if (OverlayAdvancedTogglePanel != null)
OverlayAdvancedTogglePanel.Visibility = showAdvanced ? Visibility.Visible : Visibility.Collapsed;
}
private void RefreshOverlaySettingsPanel()
{
if (CmbOverlayService == null || CmbOverlayModel == null)
return;
var llm = _settings.Settings.Llm;
var service = (llm.Service ?? "ollama").ToLowerInvariant();
var models = GetModelCandidates(service);
_isOverlaySettingsSyncing = true;
try
{
CmbOverlayService.Items.Clear();
foreach (var svc in new[] { "ollama", "vllm", "gemini", "claude" })
{
CmbOverlayService.Items.Add(new ComboBoxItem
{
Content = ServiceLabel(svc),
Tag = svc
});
}
CmbOverlayService.SelectedItem = CmbOverlayService.Items
.OfType<ComboBoxItem>()
.FirstOrDefault(i => string.Equals(i.Tag as string, service, StringComparison.OrdinalIgnoreCase));
CmbOverlayModel.Items.Clear();
foreach (var model in models)
{
CmbOverlayModel.Items.Add(new ComboBoxItem
{
Content = model.Label,
Tag = model.Id
});
}
CmbOverlayModel.SelectedItem = CmbOverlayModel.Items
.OfType<ComboBoxItem>()
.FirstOrDefault(i => string.Equals(i.Tag as string, llm.Model, StringComparison.OrdinalIgnoreCase));
if (CmbOverlayModel.SelectedItem == null && CmbOverlayModel.Items.Count > 0)
CmbOverlayModel.SelectedIndex = 0;
if (TxtOverlayModelInput != null)
TxtOverlayModelInput.Text = llm.Model ?? "";
if (ChkOverlayAiEnabled != null)
ChkOverlayAiEnabled.IsChecked = _settings.Settings.AiEnabled;
if (TxtOverlayServiceEndpoint != null)
TxtOverlayServiceEndpoint.Text = GetOverlayServiceEndpoint(service);
if (TxtOverlayServiceApiKey != null)
TxtOverlayServiceApiKey.Password = GetOverlayServiceApiKey(service);
if (ChkOverlayVllmAllowInsecureTls != null)
ChkOverlayVllmAllowInsecureTls.IsChecked = llm.VllmAllowInsecureTls;
if (TxtOverlayContextCompactTriggerPercent != null)
TxtOverlayContextCompactTriggerPercent.Text = Math.Clamp(llm.ContextCompactTriggerPercent, 10, 95).ToString();
if (TxtOverlayMaxContextTokens != null)
TxtOverlayMaxContextTokens.Text = Math.Max(1024, llm.MaxContextTokens).ToString();
if (TxtOverlayMaxRetryOnError != null)
TxtOverlayMaxRetryOnError.Text = Math.Clamp(llm.MaxRetryOnError, 0, 10).ToString();
if (ChkOverlayEnableProactiveCompact != null)
ChkOverlayEnableProactiveCompact.IsChecked = llm.EnableProactiveContextCompact;
if (ChkOverlayEnableSkillSystem != null)
ChkOverlayEnableSkillSystem.IsChecked = llm.EnableSkillSystem;
if (ChkOverlayEnableToolHooks != null)
ChkOverlayEnableToolHooks.IsChecked = llm.EnableToolHooks;
if (ChkOverlayEnableHookInputMutation != null)
ChkOverlayEnableHookInputMutation.IsChecked = llm.EnableHookInputMutation;
if (ChkOverlayEnableHookPermissionUpdate != null)
ChkOverlayEnableHookPermissionUpdate.IsChecked = llm.EnableHookPermissionUpdate;
if (ChkOverlayEnableCoworkVerification != null)
ChkOverlayEnableCoworkVerification.IsChecked = llm.EnableCoworkVerification;
if (ChkOverlayEnableCodeVerification != null)
ChkOverlayEnableCodeVerification.IsChecked = llm.Code.EnableCodeVerification;
if (ChkOverlayEnableParallelTools != null)
ChkOverlayEnableParallelTools.IsChecked = llm.EnableParallelTools;
RefreshOverlayThemeCards();
RefreshOverlayServiceCards();
RefreshOverlayModeButtons();
RefreshOverlayServiceFieldLabels(service);
BuildOverlayModelChips(service);
}
finally
{
_isOverlaySettingsSyncing = false;
}
}
private void CmbOverlayService_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isOverlaySettingsSyncing || CmbOverlayService.SelectedItem is not ComboBoxItem serviceItem || serviceItem.Tag is not string service)
return;
var llm = _settings.Settings.Llm;
llm.Service = service;
var candidates = GetModelCandidates(service);
if (candidates.Count > 0 && !candidates.Any(m => m.Id == llm.Model))
llm.Model = candidates[0].Id;
_settings.Save();
_appState.LoadFromSettings(_settings);
UpdateModelLabel();
RefreshInlineSettingsPanel();
RefreshOverlaySettingsPanel();
}
private void CmbOverlayModel_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isOverlaySettingsSyncing || CmbOverlayModel.SelectedItem is not ComboBoxItem modelItem || modelItem.Tag is not string modelId)
return;
_settings.Settings.Llm.Model = modelId;
_settings.Save();
_appState.LoadFromSettings(_settings);
UpdateModelLabel();
RefreshInlineSettingsPanel();
}
private void RefreshOverlayThemeCards()
{
var selected = (_settings.Settings.Llm.AgentTheme ?? "system").ToLowerInvariant();
SetOverlayCardSelection(OverlayThemeSystemCard, selected == "system");
SetOverlayCardSelection(OverlayThemeLightCard, selected == "light");
SetOverlayCardSelection(OverlayThemeDarkCard, selected == "dark");
}
private void RefreshOverlayServiceCards()
{
var service = (_settings.Settings.Llm.Service ?? "ollama").ToLowerInvariant();
SetOverlayCardSelection(OverlaySvcOllamaCard, service == "ollama");
SetOverlayCardSelection(OverlaySvcVllmCard, service == "vllm");
SetOverlayCardSelection(OverlaySvcGeminiCard, service == "gemini");
SetOverlayCardSelection(OverlaySvcClaudeCard, service is "claude" or "sigmoid");
}
private void RefreshOverlayModeButtons()
{
var llm = _settings.Settings.Llm;
BtnOverlayOperationMode.Content = OperationModePolicy.Normalize(_settings.Settings.OperationMode) == OperationModePolicy.ExternalMode
? "사외 모드"
: "사내 모드";
BtnOverlayFolderDataUsage.Content = _folderDataUsage switch
{
"active" => "적극 활용",
"passive" => "소극 활용",
_ => "활용하지 않음",
};
BtnOverlayPermission.Content = PermissionModeCatalog.ToDisplayLabel(PermissionModeCatalog.NormalizeGlobalMode(llm.FilePermission));
BtnOverlayPlanMode.Content = PlanModeLabel(llm.PlanMode);
BtnOverlayReasoning.Content = ReasoningLabel(llm.AgentDecisionLevel);
}
private void RefreshOverlayServiceFieldLabels(string service)
{
if (OverlayEndpointLabel == null || OverlayEndpointHint == null || OverlayApiKeyLabel == null || OverlayApiKeyHint == null)
return;
switch (service)
{
case "ollama":
OverlayEndpointLabel.Text = "Ollama 서버 주소";
OverlayEndpointHint.Text = "사내 로컬 Ollama 기본 주소를 입력합니다.";
OverlayApiKeyLabel.Text = "Ollama API 키";
OverlayApiKeyHint.Text = "사내 게이트웨이를 쓰는 경우에만 입력합니다.";
break;
case "vllm":
OverlayEndpointLabel.Text = "vLLM 서버 주소";
OverlayEndpointHint.Text = "OpenAI 호환 엔드포인트 주소를 입력합니다.";
OverlayApiKeyLabel.Text = "vLLM API 키";
OverlayApiKeyHint.Text = "사내 인증 게이트웨이를 쓰는 경우에만 입력합니다.";
break;
case "gemini":
OverlayEndpointLabel.Text = "기본 서버 주소";
OverlayEndpointHint.Text = "Gemini는 기본 주소를 사용합니다. 비워두면 기본값을 사용합니다.";
OverlayApiKeyLabel.Text = "Gemini API 키";
OverlayApiKeyHint.Text = "외부 호출에 필요한 키를 입력합니다.";
break;
default:
OverlayEndpointLabel.Text = "기본 서버 주소";
OverlayEndpointHint.Text = "Claude는 기본 주소를 사용합니다. 비워두면 기본값을 사용합니다.";
OverlayApiKeyLabel.Text = "Claude API 키";
OverlayApiKeyHint.Text = "외부 호출에 필요한 키를 입력합니다.";
break;
}
}
private string GetOverlayServiceEndpoint(string service)
{
var llm = _settings.Settings.Llm;
return service switch
{
"ollama" => llm.OllamaEndpoint ?? "",
"vllm" => llm.VllmEndpoint ?? "",
"gemini" => llm.Endpoint ?? "",
"claude" or "sigmoid" => llm.Endpoint ?? "",
_ => llm.Endpoint ?? ""
};
}
private string GetOverlayServiceApiKey(string service)
{
var llm = _settings.Settings.Llm;
return service switch
{
"ollama" => llm.OllamaApiKey ?? "",
"vllm" => llm.VllmApiKey ?? "",
"gemini" => llm.GeminiApiKey ?? "",
"claude" or "sigmoid" => llm.ClaudeApiKey ?? "",
_ => llm.ApiKey ?? ""
};
}
private void BuildOverlayModelChips(string service)
{
if (OverlayModelChipPanel == null)
return;
OverlayModelChipPanel.Children.Clear();
foreach (var model in GetModelCandidates(service))
{
var captured = model.Id;
var border = new Border
{
Cursor = Cursors.Hand,
CornerRadius = new CornerRadius(10),
BorderThickness = new Thickness(1),
BorderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray,
Background = Brushes.Transparent,
Padding = new Thickness(10, 6, 10, 6),
Margin = new Thickness(0, 0, 8, 8),
Child = new TextBlock
{
Text = model.Label,
FontSize = 11,
Foreground = TryFindResource("PrimaryText") as Brush ?? Brushes.Black,
}
};
border.MouseLeftButtonUp += (_, _) =>
{
if (TxtOverlayModelInput != null)
TxtOverlayModelInput.Text = captured;
};
OverlayModelChipPanel.Children.Add(border);
}
}
private void SetOverlayCardSelection(Border border, bool selected)
{
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.DodgerBlue;
var normal = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
border.BorderBrush = selected ? accent : normal;
border.Background = selected
? (TryFindResource("HintBackground") as Brush ?? Brushes.Transparent)
: Brushes.Transparent;
}
private void OverlayThemeSystemCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_settings.Settings.Llm.AgentTheme = "system";
RefreshOverlayThemeCards();
}
private void OverlayThemeLightCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_settings.Settings.Llm.AgentTheme = "light";
RefreshOverlayThemeCards();
}
private void OverlayThemeDarkCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_settings.Settings.Llm.AgentTheme = "dark";
RefreshOverlayThemeCards();
}
private void OverlaySvcOllamaCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("ollama");
private void OverlaySvcVllmCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("vllm");
private void OverlaySvcGeminiCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("gemini");
private void OverlaySvcClaudeCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetOverlayService("claude");
private void SetOverlayService(string service)
{
_settings.Settings.Llm.Service = service;
RefreshOverlaySettingsPanel();
}
private void BtnOverlayOperationMode_Click(object sender, RoutedEventArgs e)
{
var next = OperationModePolicy.Normalize(_settings.Settings.OperationMode) == OperationModePolicy.ExternalMode
? OperationModePolicy.InternalMode
: OperationModePolicy.ExternalMode;
if (!PromptOverlayPasswordDialog("운영 모드 변경", "사내/사외 모드 변경", "비밀번호를 입력하세요."))
return;
_settings.Settings.OperationMode = next;
RefreshOverlayModeButtons();
}
private void BtnOverlayFolderDataUsage_Click(object sender, RoutedEventArgs e)
{
_folderDataUsage = _folderDataUsage switch
{
"none" => "passive",
"passive" => "active",
_ => "none",
};
RefreshOverlayModeButtons();
}
private void BtnOverlaySave_Click(object sender, RoutedEventArgs e)
{
var llm = _settings.Settings.Llm;
var service = (llm.Service ?? "ollama").Trim().ToLowerInvariant();
var endpoint = TxtOverlayServiceEndpoint?.Text.Trim() ?? "";
var apiKey = TxtOverlayServiceApiKey?.Password ?? "";
_settings.Settings.AiEnabled = ChkOverlayAiEnabled?.IsChecked == true;
llm.Model = TxtOverlayModelInput?.Text.Trim() ?? llm.Model;
llm.VllmAllowInsecureTls = ChkOverlayVllmAllowInsecureTls?.IsChecked == true;
llm.EnableProactiveContextCompact = ChkOverlayEnableProactiveCompact?.IsChecked == true;
llm.ContextCompactTriggerPercent = ParseOverlayInt(TxtOverlayContextCompactTriggerPercent?.Text, 80, 10, 95);
llm.MaxContextTokens = ParseOverlayInt(TxtOverlayMaxContextTokens?.Text, 4096, 1024, 200000);
llm.MaxRetryOnError = ParseOverlayInt(TxtOverlayMaxRetryOnError?.Text, 3, 0, 10);
llm.EnableSkillSystem = ChkOverlayEnableSkillSystem?.IsChecked == true;
llm.EnableToolHooks = ChkOverlayEnableToolHooks?.IsChecked == true;
llm.EnableHookInputMutation = ChkOverlayEnableHookInputMutation?.IsChecked == true;
llm.EnableHookPermissionUpdate = ChkOverlayEnableHookPermissionUpdate?.IsChecked == true;
llm.EnableCoworkVerification = ChkOverlayEnableCoworkVerification?.IsChecked == true;
llm.Code.EnableCodeVerification = ChkOverlayEnableCodeVerification?.IsChecked == true;
llm.EnableParallelTools = ChkOverlayEnableParallelTools?.IsChecked == true;
llm.FolderDataUsage = _folderDataUsage;
llm.AgentUiExpressionLevel = "rich";
switch (service)
{
case "ollama":
llm.OllamaEndpoint = endpoint;
llm.OllamaApiKey = apiKey;
llm.Endpoint = endpoint;
llm.ApiKey = apiKey;
llm.OllamaModel = llm.Model;
break;
case "vllm":
llm.VllmEndpoint = endpoint;
llm.VllmApiKey = apiKey;
llm.Endpoint = endpoint;
llm.ApiKey = apiKey;
llm.VllmModel = llm.Model;
break;
case "gemini":
llm.Endpoint = endpoint;
llm.GeminiApiKey = apiKey;
llm.ApiKey = apiKey;
llm.GeminiModel = llm.Model;
break;
case "claude":
case "sigmoid":
llm.Endpoint = endpoint;
llm.ClaudeApiKey = apiKey;
llm.ApiKey = apiKey;
llm.ClaudeModel = llm.Model;
break;
}
_settings.Save();
_appState.LoadFromSettings(_settings);
ApplyAgentThemeResources();
UpdatePermissionUI();
UpdateDataUsageUI();
SaveConversationSettings();
RefreshInlineSettingsPanel();
UpdateModelLabel();
UpdateTabUI();
AgentSettingsOverlay.Visibility = Visibility.Collapsed;
ShowToast("AX Agent 설정이 저장되었습니다.");
}
private void BtnOpenFullSettings_Click(object sender, RoutedEventArgs e)
{
if (System.Windows.Application.Current is App app)
app.OpenSettingsFromChat();
}
private bool PromptOverlayPasswordDialog(string title, string header, string message)
{
var bgBrush = TryFindResource("LauncherBackground") as Brush ?? Brushes.White;
var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
var itemBg = TryFindResource("ItemBackground") as Brush ?? Brushes.WhiteSmoke;
var dlg = new Window
{
Title = title,
Width = 340,
SizeToContent = SizeToContent.Height,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = this,
ResizeMode = ResizeMode.NoResize,
WindowStyle = WindowStyle.None,
AllowsTransparency = true,
Background = Brushes.Transparent,
ShowInTaskbar = false,
};
var border = new Border
{
Background = bgBrush,
CornerRadius = new CornerRadius(12),
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
Padding = new Thickness(20),
};
var stack = new StackPanel();
stack.Children.Add(new TextBlock { Text = header, FontSize = 15, FontWeight = FontWeights.SemiBold, Foreground = fgBrush, Margin = new Thickness(0, 0, 0, 12) });
stack.Children.Add(new TextBlock { Text = message, FontSize = 12, Foreground = subFgBrush, Margin = new Thickness(0, 0, 0, 6) });
var pwBox = new PasswordBox
{
FontSize = 14,
Padding = new Thickness(8, 6, 8, 6),
Background = itemBg,
Foreground = fgBrush,
BorderBrush = borderBrush,
PasswordChar = '*',
};
stack.Children.Add(pwBox);
var btnRow = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(0, 16, 0, 0) };
var cancelBtn = new Button { Content = "취소", Padding = new Thickness(16, 6, 16, 6), Margin = new Thickness(0, 0, 8, 0) };
cancelBtn.Click += (_, _) => dlg.DialogResult = false;
btnRow.Children.Add(cancelBtn);
var okBtn = new Button { Content = "확인", Padding = new Thickness(16, 6, 16, 6), IsDefault = true };
okBtn.Click += (_, _) =>
{
if (pwBox.Password == UnifiedAdminPassword)
dlg.DialogResult = true;
else
{
pwBox.Clear();
pwBox.Focus();
}
};
btnRow.Children.Add(okBtn);
stack.Children.Add(btnRow);
border.Child = stack;
dlg.Content = border;
dlg.Loaded += (_, _) => pwBox.Focus();
return dlg.ShowDialog() == true;
}
private static int ParseOverlayInt(string? text, int fallback, int min, int max)
{
if (!int.TryParse(text, out var value))
value = fallback;
return Math.Clamp(value, min, max);
}
private void BtnInlineSettingsClose_Click(object sender, RoutedEventArgs e)
=> InlineSettingsPanel.Visibility = Visibility.Collapsed;
@@ -13108,6 +13603,7 @@ public partial class ChatWindow : Window
_settings.Save();
_appState.LoadFromSettings(_settings);
RefreshInlineSettingsPanel();
RefreshOverlaySettingsPanel();
}
private void BtnInlineReasoning_Click(object sender, RoutedEventArgs e)
@@ -13117,6 +13613,7 @@ public partial class ChatWindow : Window
_settings.Save();
_appState.LoadFromSettings(_settings);
RefreshInlineSettingsPanel();
RefreshOverlaySettingsPanel();
}
private void BtnInlinePlanMode_Click(object sender, RoutedEventArgs e)
@@ -13126,6 +13623,7 @@ public partial class ChatWindow : Window
_settings.Save();
_appState.LoadFromSettings(_settings);
RefreshInlineSettingsPanel();
RefreshOverlaySettingsPanel();
}
private void BtnInlinePermission_Click(object sender, RoutedEventArgs e)
@@ -13137,6 +13635,7 @@ public partial class ChatWindow : Window
UpdatePermissionUI();
SaveConversationSettings();
RefreshInlineSettingsPanel();
RefreshOverlaySettingsPanel();
}
private void BtnInlineSkill_Click(object sender, RoutedEventArgs e)