diff --git a/src/AxCopilot/Views/PlanViewerWindow.EditButtons.cs b/src/AxCopilot/Views/PlanViewerWindow.EditButtons.cs
new file mode 100644
index 0000000..d554027
--- /dev/null
+++ b/src/AxCopilot/Views/PlanViewerWindow.EditButtons.cs
@@ -0,0 +1,200 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace AxCopilot.Views;
+
+internal sealed partial class PlanViewerWindow
+{
+ // ════════════════════════════════════════════════════════════
+ // 버튼 빌더 + 유틸리티
+ // ════════════════════════════════════════════════════════════
+
+ private void BuildApprovalButtons()
+ {
+ _btnPanel.Children.Clear();
+ var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
+ ?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
+
+ var approveBtn = CreateActionButton("\uE73E", "승인", accentBrush, Brushes.White, true);
+ approveBtn.MouseLeftButtonUp += (_, _) =>
+ {
+ _tcs?.TrySetResult(null);
+ SwitchToExecutionMode();
+ };
+ _btnPanel.Children.Add(approveBtn);
+
+ var editBtn = CreateActionButton("\uE70F", "수정 요청", accentBrush, accentBrush, false);
+ editBtn.MouseLeftButtonUp += (_, _) => ShowEditInput();
+ _btnPanel.Children.Add(editBtn);
+
+ var reconfirmBtn = CreateActionButton("\uE72C", "재확인",
+ Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
+ Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White, false);
+ reconfirmBtn.MouseLeftButtonUp += (_, _) =>
+ _tcs?.TrySetResult("계획을 다시 검토하고 더 구체적으로 수정해주세요.");
+ _btnPanel.Children.Add(reconfirmBtn);
+
+ var cancelBrush = new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
+ var cancelBtn = CreateActionButton("\uE711", "취소", cancelBrush, cancelBrush, false);
+ cancelBtn.MouseLeftButtonUp += (_, _) => { _tcs?.TrySetResult("취소"); Hide(); };
+ _btnPanel.Children.Add(cancelBtn);
+ }
+
+ private void BuildExecutionButtons()
+ {
+ _btnPanel.Children.Clear();
+ var secondaryText = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
+ var hideBtn = CreateActionButton("\uE921", "숨기기", secondaryText,
+ Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White, false);
+ hideBtn.MouseLeftButtonUp += (_, _) => Hide();
+ _btnPanel.Children.Add(hideBtn);
+ }
+
+ private void BuildCloseButton()
+ {
+ _btnPanel.Children.Clear();
+ var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
+ ?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
+ var closeBtn = CreateActionButton("\uE73E", "닫기", accentBrush, Brushes.White, true);
+ closeBtn.MouseLeftButtonUp += (_, _) => Hide();
+ _btnPanel.Children.Add(closeBtn);
+ }
+
+ private void ShowEditInput()
+ {
+ var editPanel = new Border
+ {
+ Margin = new Thickness(20, 0, 20, 12),
+ Padding = new Thickness(12, 8, 12, 8),
+ CornerRadius = new CornerRadius(10),
+ Background = Application.Current.TryFindResource("ItemBackground") as Brush
+ ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40)),
+ };
+ var editStack = new StackPanel();
+ editStack.Children.Add(new TextBlock
+ {
+ Text = "수정 사항을 입력하세요:",
+ FontSize = 11.5,
+ Foreground = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
+ Margin = new Thickness(0, 0, 0, 6),
+ });
+ var textBox = new TextBox
+ {
+ MinHeight = 44,
+ MaxHeight = 120,
+ AcceptsReturn = true,
+ TextWrapping = TextWrapping.Wrap,
+ FontSize = 13,
+ Background = Application.Current.TryFindResource("LauncherBackground") as Brush
+ ?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E)),
+ Foreground = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White,
+ CaretBrush = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White,
+ BorderBrush = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray,
+ BorderThickness = new Thickness(1),
+ Padding = new Thickness(10, 8, 10, 8),
+ };
+ editStack.Children.Add(textBox);
+
+ var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
+ ?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
+ var sendBtn = new Border
+ {
+ Background = accentBrush,
+ CornerRadius = new CornerRadius(8),
+ Padding = new Thickness(14, 6, 14, 6),
+ Margin = new Thickness(0, 8, 0, 0),
+ Cursor = Cursors.Hand,
+ HorizontalAlignment = HorizontalAlignment.Right,
+ Child = new TextBlock
+ {
+ Text = "전송", FontSize = 12.5, FontWeight = FontWeights.SemiBold, Foreground = Brushes.White,
+ },
+ };
+ sendBtn.MouseEnter += (s, _) => ((Border)s).Opacity = 0.85;
+ sendBtn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0;
+ sendBtn.MouseLeftButtonUp += (_, _) =>
+ {
+ var feedback = textBox.Text.Trim();
+ if (string.IsNullOrEmpty(feedback)) return;
+ _tcs?.TrySetResult(feedback);
+ };
+ editStack.Children.Add(sendBtn);
+ editPanel.Child = editStack;
+
+ if (_btnPanel.Parent is Grid parentGrid)
+ {
+ for (int i = parentGrid.Children.Count - 1; i >= 0; i--)
+ {
+ if (parentGrid.Children[i] is Border b && b.Tag?.ToString() == "EditPanel")
+ parentGrid.Children.RemoveAt(i);
+ }
+ editPanel.Tag = "EditPanel";
+ Grid.SetRow(editPanel, 4); // row 4 = 하단 버튼 행 (toolBar 추가로 1 증가)
+ parentGrid.Children.Add(editPanel);
+ _btnPanel.Margin = new Thickness(20, 0, 20, 16);
+ textBox.Focus();
+ }
+ }
+
+ // ════════════════════════════════════════════════════════════
+ // 공통 버튼 팩토리
+ // ════════════════════════════════════════════════════════════
+
+ private static Border CreateMiniButton(string icon, Brush fg, Brush hoverBg)
+ {
+ var btn = new Border
+ {
+ Width = 24, Height = 24,
+ CornerRadius = new CornerRadius(6),
+ Background = Brushes.Transparent,
+ Cursor = Cursors.Hand,
+ Margin = new Thickness(1, 0, 1, 0),
+ Child = new TextBlock
+ {
+ Text = icon, FontFamily = ThemeResourceHelper.SegoeMdl2,
+ FontSize = 10, Foreground = fg,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ },
+ };
+ btn.MouseEnter += (s, _) => ((Border)s).Background = hoverBg;
+ btn.MouseLeave += (s, _) => ((Border)s).Background = Brushes.Transparent;
+ return btn;
+ }
+
+ private static Border CreateActionButton(string icon, string text, Brush borderColor,
+ Brush textColor, bool filled)
+ {
+ var color = ((SolidColorBrush)borderColor).Color;
+ var btn = new Border
+ {
+ CornerRadius = new CornerRadius(12),
+ Padding = new Thickness(16, 8, 16, 8),
+ Margin = new Thickness(4, 0, 4, 0),
+ Cursor = Cursors.Hand,
+ Background = filled ? borderColor
+ : new SolidColorBrush(Color.FromArgb(0x18, color.R, color.G, color.B)),
+ BorderBrush = filled ? Brushes.Transparent
+ : new SolidColorBrush(Color.FromArgb(0x80, color.R, color.G, color.B)),
+ BorderThickness = new Thickness(filled ? 0 : 1.2),
+ };
+ var sp = new StackPanel { Orientation = Orientation.Horizontal };
+ sp.Children.Add(new TextBlock
+ {
+ Text = icon, FontFamily = ThemeResourceHelper.SegoeMdl2,
+ FontSize = 12, Foreground = filled ? Brushes.White : textColor,
+ VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 6, 0),
+ });
+ sp.Children.Add(new TextBlock
+ {
+ Text = text, FontSize = 12.5, FontWeight = FontWeights.SemiBold,
+ Foreground = filled ? Brushes.White : textColor,
+ });
+ btn.Child = sp;
+ btn.MouseEnter += (s, _) => ((Border)s).Opacity = 0.85;
+ btn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0;
+ return btn;
+ }
+}
diff --git a/src/AxCopilot/Views/PlanViewerWindow.StepRenderer.cs b/src/AxCopilot/Views/PlanViewerWindow.StepRenderer.cs
index b5a52dc..1ba6d85 100644
--- a/src/AxCopilot/Views/PlanViewerWindow.StepRenderer.cs
+++ b/src/AxCopilot/Views/PlanViewerWindow.StepRenderer.cs
@@ -422,195 +422,4 @@ internal sealed partial class PlanViewerWindow
textBox.Focus();
textBox.SelectAll();
}
-
- // ════════════════════════════════════════════════════════════
- // 하단 버튼 빌드
- // ════════════════════════════════════════════════════════════
-
- private void BuildApprovalButtons()
- {
- _btnPanel.Children.Clear();
- var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
- ?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
-
- var approveBtn = CreateActionButton("\uE73E", "승인", accentBrush, Brushes.White, true);
- approveBtn.MouseLeftButtonUp += (_, _) =>
- {
- _tcs?.TrySetResult(null);
- SwitchToExecutionMode();
- };
- _btnPanel.Children.Add(approveBtn);
-
- var editBtn = CreateActionButton("\uE70F", "수정 요청", accentBrush, accentBrush, false);
- editBtn.MouseLeftButtonUp += (_, _) => ShowEditInput();
- _btnPanel.Children.Add(editBtn);
-
- var reconfirmBtn = CreateActionButton("\uE72C", "재확인",
- Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
- Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White, false);
- reconfirmBtn.MouseLeftButtonUp += (_, _) =>
- _tcs?.TrySetResult("계획을 다시 검토하고 더 구체적으로 수정해주세요.");
- _btnPanel.Children.Add(reconfirmBtn);
-
- var cancelBrush = new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
- var cancelBtn = CreateActionButton("\uE711", "취소", cancelBrush, cancelBrush, false);
- cancelBtn.MouseLeftButtonUp += (_, _) => { _tcs?.TrySetResult("취소"); Hide(); };
- _btnPanel.Children.Add(cancelBtn);
- }
-
- private void BuildExecutionButtons()
- {
- _btnPanel.Children.Clear();
- var secondaryText = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
- var hideBtn = CreateActionButton("\uE921", "숨기기", secondaryText,
- Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White, false);
- hideBtn.MouseLeftButtonUp += (_, _) => Hide();
- _btnPanel.Children.Add(hideBtn);
- }
-
- private void BuildCloseButton()
- {
- _btnPanel.Children.Clear();
- var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
- ?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
- var closeBtn = CreateActionButton("\uE73E", "닫기", accentBrush, Brushes.White, true);
- closeBtn.MouseLeftButtonUp += (_, _) => Hide();
- _btnPanel.Children.Add(closeBtn);
- }
-
- private void ShowEditInput()
- {
- var editPanel = new Border
- {
- Margin = new Thickness(20, 0, 20, 12),
- Padding = new Thickness(12, 8, 12, 8),
- CornerRadius = new CornerRadius(10),
- Background = Application.Current.TryFindResource("ItemBackground") as Brush
- ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40)),
- };
- var editStack = new StackPanel();
- editStack.Children.Add(new TextBlock
- {
- Text = "수정 사항을 입력하세요:",
- FontSize = 11.5,
- Foreground = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
- Margin = new Thickness(0, 0, 0, 6),
- });
- var textBox = new TextBox
- {
- MinHeight = 44,
- MaxHeight = 120,
- AcceptsReturn = true,
- TextWrapping = TextWrapping.Wrap,
- FontSize = 13,
- Background = Application.Current.TryFindResource("LauncherBackground") as Brush
- ?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E)),
- Foreground = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White,
- CaretBrush = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White,
- BorderBrush = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray,
- BorderThickness = new Thickness(1),
- Padding = new Thickness(10, 8, 10, 8),
- };
- editStack.Children.Add(textBox);
-
- var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
- ?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
- var sendBtn = new Border
- {
- Background = accentBrush,
- CornerRadius = new CornerRadius(8),
- Padding = new Thickness(14, 6, 14, 6),
- Margin = new Thickness(0, 8, 0, 0),
- Cursor = Cursors.Hand,
- HorizontalAlignment = HorizontalAlignment.Right,
- Child = new TextBlock
- {
- Text = "전송", FontSize = 12.5, FontWeight = FontWeights.SemiBold, Foreground = Brushes.White,
- },
- };
- sendBtn.MouseEnter += (s, _) => ((Border)s).Opacity = 0.85;
- sendBtn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0;
- sendBtn.MouseLeftButtonUp += (_, _) =>
- {
- var feedback = textBox.Text.Trim();
- if (string.IsNullOrEmpty(feedback)) return;
- _tcs?.TrySetResult(feedback);
- };
- editStack.Children.Add(sendBtn);
- editPanel.Child = editStack;
-
- if (_btnPanel.Parent is Grid parentGrid)
- {
- for (int i = parentGrid.Children.Count - 1; i >= 0; i--)
- {
- if (parentGrid.Children[i] is Border b && b.Tag?.ToString() == "EditPanel")
- parentGrid.Children.RemoveAt(i);
- }
- editPanel.Tag = "EditPanel";
- Grid.SetRow(editPanel, 4); // row 4 = 하단 버튼 행 (toolBar 추가로 1 증가)
- parentGrid.Children.Add(editPanel);
- _btnPanel.Margin = new Thickness(20, 0, 20, 16);
- textBox.Focus();
- }
- }
-
- // ════════════════════════════════════════════════════════════
- // 공통 버튼 팩토리
- // ════════════════════════════════════════════════════════════
-
- private static Border CreateMiniButton(string icon, Brush fg, Brush hoverBg)
- {
- var btn = new Border
- {
- Width = 24, Height = 24,
- CornerRadius = new CornerRadius(6),
- Background = Brushes.Transparent,
- Cursor = Cursors.Hand,
- Margin = new Thickness(1, 0, 1, 0),
- Child = new TextBlock
- {
- Text = icon, FontFamily = ThemeResourceHelper.SegoeMdl2,
- FontSize = 10, Foreground = fg,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- },
- };
- btn.MouseEnter += (s, _) => ((Border)s).Background = hoverBg;
- btn.MouseLeave += (s, _) => ((Border)s).Background = Brushes.Transparent;
- return btn;
- }
-
- private static Border CreateActionButton(string icon, string text, Brush borderColor,
- Brush textColor, bool filled)
- {
- var color = ((SolidColorBrush)borderColor).Color;
- var btn = new Border
- {
- CornerRadius = new CornerRadius(12),
- Padding = new Thickness(16, 8, 16, 8),
- Margin = new Thickness(4, 0, 4, 0),
- Cursor = Cursors.Hand,
- Background = filled ? borderColor
- : new SolidColorBrush(Color.FromArgb(0x18, color.R, color.G, color.B)),
- BorderBrush = filled ? Brushes.Transparent
- : new SolidColorBrush(Color.FromArgb(0x80, color.R, color.G, color.B)),
- BorderThickness = new Thickness(filled ? 0 : 1.2),
- };
- var sp = new StackPanel { Orientation = Orientation.Horizontal };
- sp.Children.Add(new TextBlock
- {
- Text = icon, FontFamily = ThemeResourceHelper.SegoeMdl2,
- FontSize = 12, Foreground = filled ? Brushes.White : textColor,
- VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 6, 0),
- });
- sp.Children.Add(new TextBlock
- {
- Text = text, FontSize = 12.5, FontWeight = FontWeights.SemiBold,
- Foreground = filled ? Brushes.White : textColor,
- });
- btn.Child = sp;
- btn.MouseEnter += (s, _) => ((Border)s).Opacity = 0.85;
- btn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0;
- return btn;
- }
}
diff --git a/src/AxCopilot/Views/SettingsWindow.AgentConfig.cs b/src/AxCopilot/Views/SettingsWindow.AgentConfig.cs
index fc0dd9d..c750fbc 100644
--- a/src/AxCopilot/Views/SettingsWindow.AgentConfig.cs
+++ b/src/AxCopilot/Views/SettingsWindow.AgentConfig.cs
@@ -300,309 +300,4 @@ public partial class SettingsWindow
if (result == MessageBoxResult.Yes)
_vm.PromptTemplates.Remove(row);
}
-
- // ─── AI 기능 활성화 토글 ────────────────────────────────────────────────
-
- /// AI 기능 토글 상태를 UI와 설정에 반영합니다.
- private void ApplyAiEnabledState(bool enabled, bool init = false)
- {
- // 토글 스위치 체크 상태 동기화 (init 시에는 이벤트 억제)
- if (AiEnabledToggle != null && AiEnabledToggle.IsChecked != enabled)
- {
- AiEnabledToggle.IsChecked = enabled;
- }
- // AX Agent 탭 가시성
- if (AgentTabItem != null)
- AgentTabItem.Visibility = enabled ? Visibility.Visible : Visibility.Collapsed;
- }
-
- private void AiEnabled_Changed(object sender, RoutedEventArgs e)
- {
- if (!IsLoaded) return;
- var tryEnable = AiEnabledToggle?.IsChecked == true;
-
- // 비활성화는 즉시 적용 (비밀번호 불필요)
- if (!tryEnable)
- {
- var app2 = CurrentApp;
- if (app2?.SettingsService?.Settings != null)
- {
- app2.SettingsService.Settings.AiEnabled = false;
- app2.SettingsService.Save();
- }
- ApplyAiEnabledState(false);
- return;
- }
-
- // 이미 활성화된 상태에서 설정 창이 열릴 때 토글 복원으로 인한 이벤트 → 비밀번호 불필요
- var currentApp = CurrentApp;
- if (currentApp?.SettingsService?.Settings.AiEnabled == true) return;
-
- // 새로 활성화하는 경우에만 비밀번호 확인
- var bgBrush = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1E, 0x1E, 0x2E));
- var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
- var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
- var borderBrush = TryFindResource("BorderColor") as Brush ?? new SolidColorBrush(Color.FromRgb(0x40, 0x40, 0x60));
- var itemBg = TryFindResource("ItemBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2A, 0x40));
-
- var dlg = new Window
- {
- Title = "AI 기능 활성화 — 비밀번호 확인",
- Width = 340, SizeToContent = SizeToContent.Height,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- Owner = this, ResizeMode = ResizeMode.NoResize,
- WindowStyle = WindowStyle.None, AllowsTransparency = true,
- Background = Brushes.Transparent,
- };
- 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 = "\U0001f512 AI 기능 활성화",
- FontSize = 15, FontWeight = FontWeights.SemiBold,
- Foreground = fgBrush, Margin = new Thickness(0, 0, 0, 12),
- });
- stack.Children.Add(new TextBlock
- {
- Text = "비밀번호를 입력하세요:",
- 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 == SettingsPassword)
- 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();
-
- if (dlg.ShowDialog() == true)
- {
- var app = CurrentApp;
- if (app?.SettingsService?.Settings != null)
- {
- app.SettingsService.Settings.AiEnabled = true;
- app.SettingsService.Save();
- }
- ApplyAiEnabledState(true);
- }
- else
- {
- // 취소/실패 — 토글 원상복구
- if (AiEnabledToggle != null) AiEnabledToggle.IsChecked = false;
- }
- }
-
- // ─── 사내/사외 모드 토글 ─────────────────────────────────────────────────────
-
- private const string SettingsPassword = "axgo123!";
-
- private void NetworkMode_Changed(object sender, RoutedEventArgs e)
- {
- if (!IsLoaded) return;
- var tryInternalMode = InternalModeToggle?.IsChecked == true; // true = 사내(차단), false = 사외(허용)
-
- // 사내 모드로 전환(차단 강화)은 비밀번호 불필요
- if (tryInternalMode)
- {
- var app2 = CurrentApp;
- if (app2?.SettingsService?.Settings != null)
- {
- app2.SettingsService.Settings.InternalModeEnabled = true;
- app2.SettingsService.Save();
- }
- return;
- }
-
- // 이미 사외 모드인 경우 토글 복원으로 인한 이벤트 → 비밀번호 불필요
- var currentApp = CurrentApp;
- if (currentApp?.SettingsService?.Settings.InternalModeEnabled == false) return;
-
- // 사외 모드 활성화(외부 허용)는 비밀번호 확인 필요
- var bgBrush = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1E, 0x1E, 0x2E));
- var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
- var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
- var borderBrush = TryFindResource("BorderColor") as Brush ?? new SolidColorBrush(Color.FromRgb(0x40, 0x40, 0x60));
- var itemBg = TryFindResource("ItemBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2A, 0x40));
-
- var dlg = new Window
- {
- Title = "사외 모드 활성화 — 비밀번호 확인",
- Width = 340, SizeToContent = SizeToContent.Height,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- Owner = this, ResizeMode = ResizeMode.NoResize,
- WindowStyle = WindowStyle.None, AllowsTransparency = true,
- Background = Brushes.Transparent,
- };
- 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 = "🌐 사외 모드 활성화",
- FontSize = 15, FontWeight = FontWeights.SemiBold,
- Foreground = fgBrush, Margin = new Thickness(0, 0, 0, 8),
- });
- stack.Children.Add(new TextBlock
- {
- Text = "사외 모드에서는 인터넷 검색과 외부 HTTP 접속이 허용됩니다.\n비밀번호를 입력하세요:",
- FontSize = 12, Foreground = subFgBrush, Margin = new Thickness(0, 0, 0, 10),
- TextWrapping = TextWrapping.Wrap,
- });
- 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 == SettingsPassword)
- 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();
-
- if (dlg.ShowDialog() == true)
- {
- var app = CurrentApp;
- if (app?.SettingsService?.Settings != null)
- {
- app.SettingsService.Settings.InternalModeEnabled = false;
- app.SettingsService.Save();
- }
- }
- else
- {
- // 취소/실패 — 토글 원상복구 (사내 모드 유지)
- if (InternalModeToggle != null) InternalModeToggle.IsChecked = true;
- }
- }
-
- private void StepApprovalCheckBox_Checked(object sender, RoutedEventArgs e)
- {
- if (sender is not CheckBox cb || !cb.IsChecked.GetValueOrDefault()) return;
- // 설정 창 로드 중 바인딩에 의한 자동 Checked 이벤트 무시 (이미 활성화된 상태 복원)
- if (!IsLoaded) return;
-
- // 테마 리소스 조회
- var bgBrush = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1E, 0x1E, 0x2E));
- var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
- var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
- var borderBrush = TryFindResource("BorderColor") as Brush ?? new SolidColorBrush(Color.FromRgb(0x40, 0x40, 0x60));
- var itemBg = TryFindResource("ItemBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2A, 0x40));
-
- var dlg = new Window
- {
- Title = "스텝 바이 스텝 승인 — 비밀번호 확인",
- Width = 340, SizeToContent = SizeToContent.Height,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- Owner = this, ResizeMode = ResizeMode.NoResize,
- WindowStyle = WindowStyle.None, AllowsTransparency = true,
- Background = Brushes.Transparent,
- };
- 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 = "\U0001f50d 스텝 바이 스텝 승인 활성화",
- FontSize = 15, FontWeight = FontWeights.SemiBold,
- Foreground = fgBrush, Margin = new Thickness(0, 0, 0, 12),
- });
- stack.Children.Add(new TextBlock
- {
- Text = "개발자 비밀번호를 입력하세요:",
- 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 == "mouse12#")
- 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();
-
- if (dlg.ShowDialog() != true)
- {
- cb.IsChecked = false;
- }
- }
-
- private void BtnClearMemory_Click(object sender, RoutedEventArgs e)
- {
- var result = CustomMessageBox.Show(
- "에이전트 메모리를 초기화하면 학습된 모든 규칙과 선호도가 삭제됩니다.\n계속하시겠습니까?",
- "에이전트 메모리 초기화",
- MessageBoxButton.YesNo,
- MessageBoxImage.Warning);
- if (result != MessageBoxResult.Yes) return;
-
- var app = CurrentApp;
- app?.MemoryService?.Clear();
- CustomMessageBox.Show("에이전트 메모리가 초기화되었습니다.", "완료", MessageBoxButton.OK, MessageBoxImage.Information);
- }
}
diff --git a/src/AxCopilot/Views/SettingsWindow.AgentHooks.cs b/src/AxCopilot/Views/SettingsWindow.AgentHooks.cs
index 5cec6c1..0346728 100644
--- a/src/AxCopilot/Views/SettingsWindow.AgentHooks.cs
+++ b/src/AxCopilot/Views/SettingsWindow.AgentHooks.cs
@@ -331,275 +331,4 @@ public partial class SettingsWindow
HookListPanel.Children.Add(card);
}
}
-
- // ─── MCP 서버 관리 ─────────────────────────────────────────────────
- private void BtnAddMcpServer_Click(object sender, RoutedEventArgs e)
- {
- var dlg = new InputDialog("MCP 서버 추가", "서버 이름:", placeholder: "예: my-mcp-server");
- dlg.Owner = this;
- if (dlg.ShowDialog() != true || string.IsNullOrWhiteSpace(dlg.ResponseText)) return;
-
- var name = dlg.ResponseText.Trim();
- var cmdDlg = new InputDialog("MCP 서버 추가", "실행 명령:", placeholder: "예: npx -y @anthropic/mcp-server");
- cmdDlg.Owner = this;
- if (cmdDlg.ShowDialog() != true || string.IsNullOrWhiteSpace(cmdDlg.ResponseText)) return;
-
- var entry = new Models.McpServerEntry { Name = name, Command = cmdDlg.ResponseText.Trim(), Enabled = true };
- _vm.Service.Settings.Llm.McpServers.Add(entry);
- BuildMcpServerCards();
- }
-
- private void BuildMcpServerCards()
- {
- if (McpServerListPanel == null) return;
- McpServerListPanel.Children.Clear();
-
- var servers = _vm.Service.Settings.Llm.McpServers;
- var primaryText = TryFindResource("PrimaryText") as System.Windows.Media.Brush ?? System.Windows.Media.Brushes.Black;
- var secondaryText = TryFindResource("SecondaryText") as System.Windows.Media.Brush ?? System.Windows.Media.Brushes.Gray;
- var accentBrush = TryFindResource("AccentColor") as System.Windows.Media.Brush ?? System.Windows.Media.Brushes.Blue;
-
- for (int i = 0; i < servers.Count; i++)
- {
- var srv = servers[i];
- var idx = i;
-
- var card = new Border
- {
- Background = TryFindResource("ItemBackground") as System.Windows.Media.Brush,
- CornerRadius = new CornerRadius(10),
- Padding = new Thickness(14, 10, 14, 10),
- Margin = new Thickness(0, 4, 0, 0),
- BorderBrush = TryFindResource("BorderColor") as System.Windows.Media.Brush,
- BorderThickness = new Thickness(1),
- };
-
- var grid = new Grid();
- grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
- grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
-
- var info = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
- info.Children.Add(new TextBlock
- {
- Text = srv.Name, FontSize = 13.5, FontWeight = FontWeights.SemiBold, Foreground = primaryText,
- });
- var detailSp = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 0) };
- detailSp.Children.Add(new Border
- {
- Background = srv.Enabled ? accentBrush : System.Windows.Media.Brushes.Gray,
- CornerRadius = new CornerRadius(4), Padding = new Thickness(6, 1, 6, 1), Margin = new Thickness(0, 0, 8, 0), Opacity = 0.8,
- Child = new TextBlock { Text = srv.Enabled ? "활성" : "비활성", FontSize = 10, Foreground = System.Windows.Media.Brushes.White, FontWeight = FontWeights.SemiBold },
- });
- detailSp.Children.Add(new TextBlock
- {
- Text = $"{srv.Command} {string.Join(" ", srv.Args)}", FontSize = 11,
- Foreground = secondaryText, VerticalAlignment = VerticalAlignment.Center,
- MaxWidth = 300, TextTrimming = TextTrimming.CharacterEllipsis,
- });
- info.Children.Add(detailSp);
- Grid.SetColumn(info, 0);
- grid.Children.Add(info);
-
- var btnPanel = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center };
-
- // 활성/비활성 토글
- var toggleBtn = new Button
- {
- Content = srv.Enabled ? "\uE73E" : "\uE711",
- FontFamily = new System.Windows.Media.FontFamily("Segoe MDL2 Assets"),
- FontSize = 12, ToolTip = srv.Enabled ? "비활성화" : "활성화",
- Background = System.Windows.Media.Brushes.Transparent, BorderThickness = new Thickness(0),
- Foreground = srv.Enabled ? accentBrush : System.Windows.Media.Brushes.Gray,
- Padding = new Thickness(6, 4, 6, 4), Cursor = Cursors.Hand,
- };
- toggleBtn.Click += (_, _) => { servers[idx].Enabled = !servers[idx].Enabled; BuildMcpServerCards(); };
- btnPanel.Children.Add(toggleBtn);
-
- // 삭제
- var delBtn = new Button
- {
- Content = "\uE74D", FontFamily = new System.Windows.Media.FontFamily("Segoe MDL2 Assets"),
- FontSize = 12, ToolTip = "삭제",
- Background = System.Windows.Media.Brushes.Transparent, BorderThickness = new Thickness(0),
- Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xDD, 0x44, 0x44)),
- Padding = new Thickness(6, 4, 6, 4), Cursor = Cursors.Hand,
- };
- delBtn.Click += (_, _) => { servers.RemoveAt(idx); BuildMcpServerCards(); };
- btnPanel.Children.Add(delBtn);
-
- Grid.SetColumn(btnPanel, 1);
- grid.Children.Add(btnPanel);
- card.Child = grid;
- McpServerListPanel.Children.Add(card);
- }
- }
-
- // ─── 감사 로그 폴더 열기 ────────────────────────────────────────────
- private void BtnOpenAuditLog_Click(object sender, RoutedEventArgs e)
- {
- try { System.Diagnostics.Process.Start("explorer.exe", Services.AuditLogService.GetAuditFolder()); } catch (Exception) { }
- }
-
- // ─── 폴백/MCP 텍스트 박스 로드/저장 ───────────────────────────────────
- private void BuildFallbackModelsPanel()
- {
- if (FallbackModelsPanel == null) return;
- FallbackModelsPanel.Children.Clear();
-
- var llm = _vm.Service.Settings.Llm;
- var fallbacks = llm.FallbackModels;
- var toggleStyle = TryFindResource("ToggleSwitch") as Style;
-
- // 서비스별로 모델 수집 (순서 고정: Ollama → vLLM → Gemini → Claude)
- var sections = new (string Service, string Label, string Color, List Models)[]
- {
- ("ollama", "Ollama", "#107C10", new()),
- ("vllm", "vLLM", "#0078D4", new()),
- ("gemini", "Gemini", "#4285F4", new()),
- ("claude", "Claude", "#8B5CF6", new()),
- };
-
- // RegisteredModels → ViewModel과 AppSettings 양쪽에서 수집 (저장 전에도 반영)
- // 1) ViewModel의 RegisteredModels (UI에서 방금 추가한 것 포함)
- foreach (var row in _vm.RegisteredModels)
- {
- var svc = (row.Service ?? "").ToLowerInvariant();
- var modelName = !string.IsNullOrEmpty(row.Alias) ? row.Alias : row.EncryptedModelName;
- var section = sections.FirstOrDefault(s => s.Service == svc);
- if (section.Models != null && !string.IsNullOrEmpty(modelName) && !section.Models.Contains(modelName))
- section.Models.Add(modelName);
- }
- // 2) AppSettings의 RegisteredModels (기존 저장된 것 — ViewModel에 없는 경우 보완)
- foreach (var m in llm.RegisteredModels)
- {
- var svc = (m.Service ?? "").ToLowerInvariant();
- var modelName = !string.IsNullOrEmpty(m.Alias) ? m.Alias : m.EncryptedModelName;
- var section = sections.FirstOrDefault(s => s.Service == svc);
- if (section.Models != null && !string.IsNullOrEmpty(modelName) && !section.Models.Contains(modelName))
- section.Models.Add(modelName);
- }
-
- // 현재 활성 모델 추가 (중복 제거)
- if (!string.IsNullOrEmpty(llm.OllamaModel) && !sections[0].Models.Contains(llm.OllamaModel))
- sections[0].Models.Add(llm.OllamaModel);
- if (!string.IsNullOrEmpty(llm.VllmModel) && !sections[1].Models.Contains(llm.VllmModel))
- sections[1].Models.Add(llm.VllmModel);
-
- // Gemini/Claude 고정 모델 목록
- foreach (var gm in new[] { "gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.0-flash", "gemini-1.5-pro", "gemini-1.5-flash" })
- if (!sections[2].Models.Contains(gm)) sections[2].Models.Add(gm);
- foreach (var cm in new[] { "claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5", "claude-sonnet-4-5" })
- if (!sections[3].Models.Contains(cm)) sections[3].Models.Add(cm);
-
- // 렌더링 — 모델이 없는 섹션도 헤더는 표시
- foreach (var (service, svcLabel, svcColor, models) in sections)
- {
- FallbackModelsPanel.Children.Add(new TextBlock
- {
- Text = svcLabel,
- FontSize = 11, FontWeight = FontWeights.SemiBold,
- Foreground = BrushFromHex(svcColor),
- Margin = new Thickness(0, 8, 0, 4),
- });
-
- if (models.Count == 0)
- {
- FallbackModelsPanel.Children.Add(new TextBlock
- {
- Text = "등록된 모델 없음",
- FontSize = 11, Foreground = Brushes.Gray, FontStyle = FontStyles.Italic,
- Margin = new Thickness(8, 2, 0, 4),
- });
- continue;
- }
-
- foreach (var modelName in models)
- {
- var fullKey = $"{service}:{modelName}";
-
- var row = new Grid { Margin = new Thickness(8, 2, 0, 2) };
- row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
- row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
-
- var label = new TextBlock
- {
- Text = modelName, FontSize = 12, FontFamily = ThemeResourceHelper.ConsolasCourierNew,
- VerticalAlignment = VerticalAlignment.Center,
- Foreground = TryFindResource("PrimaryText") as Brush ?? Brushes.Black,
- };
- Grid.SetColumn(label, 0);
- row.Children.Add(label);
-
- var captured = fullKey;
- var cb = new CheckBox
- {
- IsChecked = fallbacks.Contains(fullKey, StringComparer.OrdinalIgnoreCase),
- HorizontalAlignment = HorizontalAlignment.Right,
- VerticalAlignment = VerticalAlignment.Center,
- };
- if (toggleStyle != null) cb.Style = toggleStyle;
- cb.Checked += (_, _) =>
- {
- if (!fallbacks.Contains(captured)) fallbacks.Add(captured);
- FallbackModelsBox.Text = string.Join("\n", fallbacks);
- };
- cb.Unchecked += (_, _) =>
- {
- fallbacks.RemoveAll(x => x.Equals(captured, StringComparison.OrdinalIgnoreCase));
- FallbackModelsBox.Text = string.Join("\n", fallbacks);
- };
- Grid.SetColumn(cb, 1);
- row.Children.Add(cb);
-
- FallbackModelsPanel.Children.Add(row);
- }
- }
- }
-
- private void LoadAdvancedSettings()
- {
- var llm = _vm.Service.Settings.Llm;
- if (FallbackModelsBox != null)
- FallbackModelsBox.Text = string.Join("\n", llm.FallbackModels);
- BuildFallbackModelsPanel();
- if (McpServersBox != null)
- {
- try
- {
- var json = System.Text.Json.JsonSerializer.Serialize(llm.McpServers,
- new System.Text.Json.JsonSerializerOptions { WriteIndented = true,
- Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
- McpServersBox.Text = json;
- }
- catch (Exception) { McpServersBox.Text = "[]"; }
- }
- BuildMcpServerCards();
- BuildHookCards();
- }
-
- private void SaveAdvancedSettings()
- {
- var llm = _vm.Service.Settings.Llm;
- if (FallbackModelsBox != null)
- {
- llm.FallbackModels = FallbackModelsBox.Text
- .Split('\n', StringSplitOptions.RemoveEmptyEntries)
- .Select(s => s.Trim())
- .Where(s => s.Length > 0)
- .ToList();
- }
- if (McpServersBox != null && !string.IsNullOrWhiteSpace(McpServersBox.Text))
- {
- try
- {
- llm.McpServers = System.Text.Json.JsonSerializer.Deserialize>(
- McpServersBox.Text) ?? new();
- }
- catch (Exception) { /* JSON 파싱 실패 시 기존 유지 */ }
- }
-
- // 도구 비활성 목록 저장
- if (_toolCardsLoaded)
- llm.DisabledTools = _disabledTools.ToList();
- }
}
diff --git a/src/AxCopilot/Views/SettingsWindow.AiToggle.cs b/src/AxCopilot/Views/SettingsWindow.AiToggle.cs
new file mode 100644
index 0000000..ecdea59
--- /dev/null
+++ b/src/AxCopilot/Views/SettingsWindow.AiToggle.cs
@@ -0,0 +1,316 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using AxCopilot.Services;
+using AxCopilot.ViewModels;
+
+namespace AxCopilot.Views;
+
+public partial class SettingsWindow
+{
+ // ─── AI 기능 활성화 토글 + 네트워크 모드 ────────────────────────────────────
+
+ /// AI 기능 토글 상태를 UI와 설정에 반영합니다.
+ private void ApplyAiEnabledState(bool enabled, bool init = false)
+ {
+ // 토글 스위치 체크 상태 동기화 (init 시에는 이벤트 억제)
+ if (AiEnabledToggle != null && AiEnabledToggle.IsChecked != enabled)
+ {
+ AiEnabledToggle.IsChecked = enabled;
+ }
+ // AX Agent 탭 가시성
+ if (AgentTabItem != null)
+ AgentTabItem.Visibility = enabled ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ private void AiEnabled_Changed(object sender, RoutedEventArgs e)
+ {
+ if (!IsLoaded) return;
+ var tryEnable = AiEnabledToggle?.IsChecked == true;
+
+ // 비활성화는 즉시 적용 (비밀번호 불필요)
+ if (!tryEnable)
+ {
+ var app2 = CurrentApp;
+ if (app2?.SettingsService?.Settings != null)
+ {
+ app2.SettingsService.Settings.AiEnabled = false;
+ app2.SettingsService.Save();
+ }
+ ApplyAiEnabledState(false);
+ return;
+ }
+
+ // 이미 활성화된 상태에서 설정 창이 열릴 때 토글 복원으로 인한 이벤트 → 비밀번호 불필요
+ var currentApp = CurrentApp;
+ if (currentApp?.SettingsService?.Settings.AiEnabled == true) return;
+
+ // 새로 활성화하는 경우에만 비밀번호 확인
+ var bgBrush = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1E, 0x1E, 0x2E));
+ var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
+ var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
+ var borderBrush = TryFindResource("BorderColor") as Brush ?? new SolidColorBrush(Color.FromRgb(0x40, 0x40, 0x60));
+ var itemBg = TryFindResource("ItemBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2A, 0x40));
+
+ var dlg = new Window
+ {
+ Title = "AI 기능 활성화 — 비밀번호 확인",
+ Width = 340, SizeToContent = SizeToContent.Height,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ Owner = this, ResizeMode = ResizeMode.NoResize,
+ WindowStyle = WindowStyle.None, AllowsTransparency = true,
+ Background = Brushes.Transparent,
+ };
+ 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 = "\U0001f512 AI 기능 활성화",
+ FontSize = 15, FontWeight = FontWeights.SemiBold,
+ Foreground = fgBrush, Margin = new Thickness(0, 0, 0, 12),
+ });
+ stack.Children.Add(new TextBlock
+ {
+ Text = "비밀번호를 입력하세요:",
+ 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 == SettingsPassword)
+ 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();
+
+ if (dlg.ShowDialog() == true)
+ {
+ var app = CurrentApp;
+ if (app?.SettingsService?.Settings != null)
+ {
+ app.SettingsService.Settings.AiEnabled = true;
+ app.SettingsService.Save();
+ }
+ ApplyAiEnabledState(true);
+ }
+ else
+ {
+ // 취소/실패 — 토글 원상복구
+ if (AiEnabledToggle != null) AiEnabledToggle.IsChecked = false;
+ }
+ }
+
+ // ─── 사내/사외 모드 토글 ─────────────────────────────────────────────────────
+
+ private const string SettingsPassword = "axgo123!";
+
+ private void NetworkMode_Changed(object sender, RoutedEventArgs e)
+ {
+ if (!IsLoaded) return;
+ var tryInternalMode = InternalModeToggle?.IsChecked == true; // true = 사내(차단), false = 사외(허용)
+
+ // 사내 모드로 전환(차단 강화)은 비밀번호 불필요
+ if (tryInternalMode)
+ {
+ var app2 = CurrentApp;
+ if (app2?.SettingsService?.Settings != null)
+ {
+ app2.SettingsService.Settings.InternalModeEnabled = true;
+ app2.SettingsService.Save();
+ }
+ return;
+ }
+
+ // 이미 사외 모드인 경우 토글 복원으로 인한 이벤트 → 비밀번호 불필요
+ var currentApp = CurrentApp;
+ if (currentApp?.SettingsService?.Settings.InternalModeEnabled == false) return;
+
+ // 사외 모드 활성화(외부 허용)는 비밀번호 확인 필요
+ var bgBrush = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1E, 0x1E, 0x2E));
+ var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
+ var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
+ var borderBrush = TryFindResource("BorderColor") as Brush ?? new SolidColorBrush(Color.FromRgb(0x40, 0x40, 0x60));
+ var itemBg = TryFindResource("ItemBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2A, 0x40));
+
+ var dlg = new Window
+ {
+ Title = "사외 모드 활성화 — 비밀번호 확인",
+ Width = 340, SizeToContent = SizeToContent.Height,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ Owner = this, ResizeMode = ResizeMode.NoResize,
+ WindowStyle = WindowStyle.None, AllowsTransparency = true,
+ Background = Brushes.Transparent,
+ };
+ 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 = "🌐 사외 모드 활성화",
+ FontSize = 15, FontWeight = FontWeights.SemiBold,
+ Foreground = fgBrush, Margin = new Thickness(0, 0, 0, 8),
+ });
+ stack.Children.Add(new TextBlock
+ {
+ Text = "사외 모드에서는 인터넷 검색과 외부 HTTP 접속이 허용됩니다.\n비밀번호를 입력하세요:",
+ FontSize = 12, Foreground = subFgBrush, Margin = new Thickness(0, 0, 0, 10),
+ TextWrapping = TextWrapping.Wrap,
+ });
+ 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 == SettingsPassword)
+ 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();
+
+ if (dlg.ShowDialog() == true)
+ {
+ var app = CurrentApp;
+ if (app?.SettingsService?.Settings != null)
+ {
+ app.SettingsService.Settings.InternalModeEnabled = false;
+ app.SettingsService.Save();
+ }
+ }
+ else
+ {
+ // 취소/실패 — 토글 원상복구 (사내 모드 유지)
+ if (InternalModeToggle != null) InternalModeToggle.IsChecked = true;
+ }
+ }
+
+ private void StepApprovalCheckBox_Checked(object sender, RoutedEventArgs e)
+ {
+ if (sender is not CheckBox cb || !cb.IsChecked.GetValueOrDefault()) return;
+ // 설정 창 로드 중 바인딩에 의한 자동 Checked 이벤트 무시 (이미 활성화된 상태 복원)
+ if (!IsLoaded) return;
+
+ // 테마 리소스 조회
+ var bgBrush = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1E, 0x1E, 0x2E));
+ var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
+ var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
+ var borderBrush = TryFindResource("BorderColor") as Brush ?? new SolidColorBrush(Color.FromRgb(0x40, 0x40, 0x60));
+ var itemBg = TryFindResource("ItemBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2A, 0x40));
+
+ var dlg = new Window
+ {
+ Title = "스텝 바이 스텝 승인 — 비밀번호 확인",
+ Width = 340, SizeToContent = SizeToContent.Height,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ Owner = this, ResizeMode = ResizeMode.NoResize,
+ WindowStyle = WindowStyle.None, AllowsTransparency = true,
+ Background = Brushes.Transparent,
+ };
+ 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 = "\U0001f50d 스텝 바이 스텝 승인 활성화",
+ FontSize = 15, FontWeight = FontWeights.SemiBold,
+ Foreground = fgBrush, Margin = new Thickness(0, 0, 0, 12),
+ });
+ stack.Children.Add(new TextBlock
+ {
+ Text = "개발자 비밀번호를 입력하세요:",
+ 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 == "mouse12#")
+ 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();
+
+ if (dlg.ShowDialog() != true)
+ {
+ cb.IsChecked = false;
+ }
+ }
+
+ private void BtnClearMemory_Click(object sender, RoutedEventArgs e)
+ {
+ var result = CustomMessageBox.Show(
+ "에이전트 메모리를 초기화하면 학습된 모든 규칙과 선호도가 삭제됩니다.\n계속하시겠습니까?",
+ "에이전트 메모리 초기화",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning);
+ if (result != MessageBoxResult.Yes) return;
+
+ var app = CurrentApp;
+ app?.MemoryService?.Clear();
+ CustomMessageBox.Show("에이전트 메모리가 초기화되었습니다.", "완료", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+}
diff --git a/src/AxCopilot/Views/SettingsWindow.McpAdvanced.cs b/src/AxCopilot/Views/SettingsWindow.McpAdvanced.cs
new file mode 100644
index 0000000..4335135
--- /dev/null
+++ b/src/AxCopilot/Views/SettingsWindow.McpAdvanced.cs
@@ -0,0 +1,284 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace AxCopilot.Views;
+
+public partial class SettingsWindow
+{
+ // ─── MCP 서버 관리 + 고급 설정 ──────────────────────────────────────────
+
+ // ─── MCP 서버 관리 ─────────────────────────────────────────────────
+ private void BtnAddMcpServer_Click(object sender, RoutedEventArgs e)
+ {
+ var dlg = new InputDialog("MCP 서버 추가", "서버 이름:", placeholder: "예: my-mcp-server");
+ dlg.Owner = this;
+ if (dlg.ShowDialog() != true || string.IsNullOrWhiteSpace(dlg.ResponseText)) return;
+
+ var name = dlg.ResponseText.Trim();
+ var cmdDlg = new InputDialog("MCP 서버 추가", "실행 명령:", placeholder: "예: npx -y @anthropic/mcp-server");
+ cmdDlg.Owner = this;
+ if (cmdDlg.ShowDialog() != true || string.IsNullOrWhiteSpace(cmdDlg.ResponseText)) return;
+
+ var entry = new Models.McpServerEntry { Name = name, Command = cmdDlg.ResponseText.Trim(), Enabled = true };
+ _vm.Service.Settings.Llm.McpServers.Add(entry);
+ BuildMcpServerCards();
+ }
+
+ private void BuildMcpServerCards()
+ {
+ if (McpServerListPanel == null) return;
+ McpServerListPanel.Children.Clear();
+
+ var servers = _vm.Service.Settings.Llm.McpServers;
+ var primaryText = TryFindResource("PrimaryText") as System.Windows.Media.Brush ?? System.Windows.Media.Brushes.Black;
+ var secondaryText = TryFindResource("SecondaryText") as System.Windows.Media.Brush ?? System.Windows.Media.Brushes.Gray;
+ var accentBrush = TryFindResource("AccentColor") as System.Windows.Media.Brush ?? System.Windows.Media.Brushes.Blue;
+
+ for (int i = 0; i < servers.Count; i++)
+ {
+ var srv = servers[i];
+ var idx = i;
+
+ var card = new Border
+ {
+ Background = TryFindResource("ItemBackground") as System.Windows.Media.Brush,
+ CornerRadius = new CornerRadius(10),
+ Padding = new Thickness(14, 10, 14, 10),
+ Margin = new Thickness(0, 4, 0, 0),
+ BorderBrush = TryFindResource("BorderColor") as System.Windows.Media.Brush,
+ BorderThickness = new Thickness(1),
+ };
+
+ var grid = new Grid();
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
+
+ var info = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
+ info.Children.Add(new TextBlock
+ {
+ Text = srv.Name, FontSize = 13.5, FontWeight = FontWeights.SemiBold, Foreground = primaryText,
+ });
+ var detailSp = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 0) };
+ detailSp.Children.Add(new Border
+ {
+ Background = srv.Enabled ? accentBrush : System.Windows.Media.Brushes.Gray,
+ CornerRadius = new CornerRadius(4), Padding = new Thickness(6, 1, 6, 1), Margin = new Thickness(0, 0, 8, 0), Opacity = 0.8,
+ Child = new TextBlock { Text = srv.Enabled ? "활성" : "비활성", FontSize = 10, Foreground = System.Windows.Media.Brushes.White, FontWeight = FontWeights.SemiBold },
+ });
+ detailSp.Children.Add(new TextBlock
+ {
+ Text = $"{srv.Command} {string.Join(" ", srv.Args)}", FontSize = 11,
+ Foreground = secondaryText, VerticalAlignment = VerticalAlignment.Center,
+ MaxWidth = 300, TextTrimming = TextTrimming.CharacterEllipsis,
+ });
+ info.Children.Add(detailSp);
+ Grid.SetColumn(info, 0);
+ grid.Children.Add(info);
+
+ var btnPanel = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center };
+
+ // 활성/비활성 토글
+ var toggleBtn = new Button
+ {
+ Content = srv.Enabled ? "\uE73E" : "\uE711",
+ FontFamily = new System.Windows.Media.FontFamily("Segoe MDL2 Assets"),
+ FontSize = 12, ToolTip = srv.Enabled ? "비활성화" : "활성화",
+ Background = System.Windows.Media.Brushes.Transparent, BorderThickness = new Thickness(0),
+ Foreground = srv.Enabled ? accentBrush : System.Windows.Media.Brushes.Gray,
+ Padding = new Thickness(6, 4, 6, 4), Cursor = Cursors.Hand,
+ };
+ toggleBtn.Click += (_, _) => { servers[idx].Enabled = !servers[idx].Enabled; BuildMcpServerCards(); };
+ btnPanel.Children.Add(toggleBtn);
+
+ // 삭제
+ var delBtn = new Button
+ {
+ Content = "\uE74D", FontFamily = new System.Windows.Media.FontFamily("Segoe MDL2 Assets"),
+ FontSize = 12, ToolTip = "삭제",
+ Background = System.Windows.Media.Brushes.Transparent, BorderThickness = new Thickness(0),
+ Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0xDD, 0x44, 0x44)),
+ Padding = new Thickness(6, 4, 6, 4), Cursor = Cursors.Hand,
+ };
+ delBtn.Click += (_, _) => { servers.RemoveAt(idx); BuildMcpServerCards(); };
+ btnPanel.Children.Add(delBtn);
+
+ Grid.SetColumn(btnPanel, 1);
+ grid.Children.Add(btnPanel);
+ card.Child = grid;
+ McpServerListPanel.Children.Add(card);
+ }
+ }
+
+ // ─── 감사 로그 폴더 열기 ────────────────────────────────────────────
+ private void BtnOpenAuditLog_Click(object sender, RoutedEventArgs e)
+ {
+ try { System.Diagnostics.Process.Start("explorer.exe", Services.AuditLogService.GetAuditFolder()); } catch (Exception) { }
+ }
+
+ // ─── 폴백/MCP 텍스트 박스 로드/저장 ───────────────────────────────────
+ private void BuildFallbackModelsPanel()
+ {
+ if (FallbackModelsPanel == null) return;
+ FallbackModelsPanel.Children.Clear();
+
+ var llm = _vm.Service.Settings.Llm;
+ var fallbacks = llm.FallbackModels;
+ var toggleStyle = TryFindResource("ToggleSwitch") as Style;
+
+ // 서비스별로 모델 수집 (순서 고정: Ollama → vLLM → Gemini → Claude)
+ var sections = new (string Service, string Label, string Color, List Models)[]
+ {
+ ("ollama", "Ollama", "#107C10", new()),
+ ("vllm", "vLLM", "#0078D4", new()),
+ ("gemini", "Gemini", "#4285F4", new()),
+ ("claude", "Claude", "#8B5CF6", new()),
+ };
+
+ // RegisteredModels → ViewModel과 AppSettings 양쪽에서 수집 (저장 전에도 반영)
+ // 1) ViewModel의 RegisteredModels (UI에서 방금 추가한 것 포함)
+ foreach (var row in _vm.RegisteredModels)
+ {
+ var svc = (row.Service ?? "").ToLowerInvariant();
+ var modelName = !string.IsNullOrEmpty(row.Alias) ? row.Alias : row.EncryptedModelName;
+ var section = sections.FirstOrDefault(s => s.Service == svc);
+ if (section.Models != null && !string.IsNullOrEmpty(modelName) && !section.Models.Contains(modelName))
+ section.Models.Add(modelName);
+ }
+ // 2) AppSettings의 RegisteredModels (기존 저장된 것 — ViewModel에 없는 경우 보완)
+ foreach (var m in llm.RegisteredModels)
+ {
+ var svc = (m.Service ?? "").ToLowerInvariant();
+ var modelName = !string.IsNullOrEmpty(m.Alias) ? m.Alias : m.EncryptedModelName;
+ var section = sections.FirstOrDefault(s => s.Service == svc);
+ if (section.Models != null && !string.IsNullOrEmpty(modelName) && !section.Models.Contains(modelName))
+ section.Models.Add(modelName);
+ }
+
+ // 현재 활성 모델 추가 (중복 제거)
+ if (!string.IsNullOrEmpty(llm.OllamaModel) && !sections[0].Models.Contains(llm.OllamaModel))
+ sections[0].Models.Add(llm.OllamaModel);
+ if (!string.IsNullOrEmpty(llm.VllmModel) && !sections[1].Models.Contains(llm.VllmModel))
+ sections[1].Models.Add(llm.VllmModel);
+
+ // Gemini/Claude 고정 모델 목록
+ foreach (var gm in new[] { "gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.0-flash", "gemini-1.5-pro", "gemini-1.5-flash" })
+ if (!sections[2].Models.Contains(gm)) sections[2].Models.Add(gm);
+ foreach (var cm in new[] { "claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5", "claude-sonnet-4-5" })
+ if (!sections[3].Models.Contains(cm)) sections[3].Models.Add(cm);
+
+ // 렌더링 — 모델이 없는 섹션도 헤더는 표시
+ foreach (var (service, svcLabel, svcColor, models) in sections)
+ {
+ FallbackModelsPanel.Children.Add(new TextBlock
+ {
+ Text = svcLabel,
+ FontSize = 11, FontWeight = FontWeights.SemiBold,
+ Foreground = BrushFromHex(svcColor),
+ Margin = new Thickness(0, 8, 0, 4),
+ });
+
+ if (models.Count == 0)
+ {
+ FallbackModelsPanel.Children.Add(new TextBlock
+ {
+ Text = "등록된 모델 없음",
+ FontSize = 11, Foreground = Brushes.Gray, FontStyle = FontStyles.Italic,
+ Margin = new Thickness(8, 2, 0, 4),
+ });
+ continue;
+ }
+
+ foreach (var modelName in models)
+ {
+ var fullKey = $"{service}:{modelName}";
+
+ var row = new Grid { Margin = new Thickness(8, 2, 0, 2) };
+ row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
+ row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
+
+ var label = new TextBlock
+ {
+ Text = modelName, FontSize = 12, FontFamily = ThemeResourceHelper.ConsolasCourierNew,
+ VerticalAlignment = VerticalAlignment.Center,
+ Foreground = TryFindResource("PrimaryText") as Brush ?? Brushes.Black,
+ };
+ Grid.SetColumn(label, 0);
+ row.Children.Add(label);
+
+ var captured = fullKey;
+ var cb = new CheckBox
+ {
+ IsChecked = fallbacks.Contains(fullKey, StringComparer.OrdinalIgnoreCase),
+ HorizontalAlignment = HorizontalAlignment.Right,
+ VerticalAlignment = VerticalAlignment.Center,
+ };
+ if (toggleStyle != null) cb.Style = toggleStyle;
+ cb.Checked += (_, _) =>
+ {
+ if (!fallbacks.Contains(captured)) fallbacks.Add(captured);
+ FallbackModelsBox.Text = string.Join("\n", fallbacks);
+ };
+ cb.Unchecked += (_, _) =>
+ {
+ fallbacks.RemoveAll(x => x.Equals(captured, StringComparison.OrdinalIgnoreCase));
+ FallbackModelsBox.Text = string.Join("\n", fallbacks);
+ };
+ Grid.SetColumn(cb, 1);
+ row.Children.Add(cb);
+
+ FallbackModelsPanel.Children.Add(row);
+ }
+ }
+ }
+
+ private void LoadAdvancedSettings()
+ {
+ var llm = _vm.Service.Settings.Llm;
+ if (FallbackModelsBox != null)
+ FallbackModelsBox.Text = string.Join("\n", llm.FallbackModels);
+ BuildFallbackModelsPanel();
+ if (McpServersBox != null)
+ {
+ try
+ {
+ var json = System.Text.Json.JsonSerializer.Serialize(llm.McpServers,
+ new System.Text.Json.JsonSerializerOptions { WriteIndented = true,
+ Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
+ McpServersBox.Text = json;
+ }
+ catch (Exception) { McpServersBox.Text = "[]"; }
+ }
+ BuildMcpServerCards();
+ BuildHookCards();
+ }
+
+ private void SaveAdvancedSettings()
+ {
+ var llm = _vm.Service.Settings.Llm;
+ if (FallbackModelsBox != null)
+ {
+ llm.FallbackModels = FallbackModelsBox.Text
+ .Split('\n', StringSplitOptions.RemoveEmptyEntries)
+ .Select(s => s.Trim())
+ .Where(s => s.Length > 0)
+ .ToList();
+ }
+ if (McpServersBox != null && !string.IsNullOrWhiteSpace(McpServersBox.Text))
+ {
+ try
+ {
+ llm.McpServers = System.Text.Json.JsonSerializer.Deserialize>(
+ McpServersBox.Text) ?? new();
+ }
+ catch (Exception) { /* JSON 파싱 실패 시 기존 유지 */ }
+ }
+
+ // 도구 비활성 목록 저장
+ if (_toolCardsLoaded)
+ llm.DisabledTools = _disabledTools.ToList();
+ }
+}