[Phase50] PlanViewerWindow·SettingsWindow 분리 — 6개 파일
변경 파일: - PlanViewerWindow.StepRenderer.cs: 616 → 425줄 (RenderSteps/SwapSteps/EditStep 유지) - PlanViewerWindow.EditButtons.cs (신규): BuildApprovalButtons, BuildExecutionButtons, BuildCloseButton, ShowEditInput, CreateMiniButton, CreateActionButton (197줄) - SettingsWindow.AgentConfig.cs: 608 → 303줄 (모델/스킬/템플릿 관리 유지) - SettingsWindow.AiToggle.cs (신규): ApplyAiEnabledState, AiEnabled_Changed, NetworkMode_Changed, StepApprovalCheckBox_Checked, BtnClearMemory_Click (316줄) - SettingsWindow.AgentHooks.cs: 605 → 334줄 (훅 관리 유지) - SettingsWindow.McpAdvanced.cs (신규): MCP 서버 관리, 감사 로그, 폴백 모델, LoadAdvancedSettings/SaveAdvancedSettings (271줄) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
200
src/AxCopilot/Views/PlanViewerWindow.EditButtons.cs
Normal file
200
src/AxCopilot/Views/PlanViewerWindow.EditButtons.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -422,195 +422,4 @@ internal sealed partial class PlanViewerWindow
|
|||||||
textBox.Focus();
|
textBox.Focus();
|
||||||
textBox.SelectAll();
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,309 +300,4 @@ public partial class SettingsWindow
|
|||||||
if (result == MessageBoxResult.Yes)
|
if (result == MessageBoxResult.Yes)
|
||||||
_vm.PromptTemplates.Remove(row);
|
_vm.PromptTemplates.Remove(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── AI 기능 활성화 토글 ────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// <summary>AI 기능 토글 상태를 UI와 설정에 반영합니다.</summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -331,275 +331,4 @@ public partial class SettingsWindow
|
|||||||
HookListPanel.Children.Add(card);
|
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<string> 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<List<Models.McpServerEntry>>(
|
|
||||||
McpServersBox.Text) ?? new();
|
|
||||||
}
|
|
||||||
catch (Exception) { /* JSON 파싱 실패 시 기존 유지 */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 도구 비활성 목록 저장
|
|
||||||
if (_toolCardsLoaded)
|
|
||||||
llm.DisabledTools = _disabledTools.ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
316
src/AxCopilot/Views/SettingsWindow.AiToggle.cs
Normal file
316
src/AxCopilot/Views/SettingsWindow.AiToggle.cs
Normal file
@@ -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 기능 활성화 토글 + 네트워크 모드 ────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>AI 기능 토글 상태를 UI와 설정에 반영합니다.</summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
284
src/AxCopilot/Views/SettingsWindow.McpAdvanced.cs
Normal file
284
src/AxCopilot/Views/SettingsWindow.McpAdvanced.cs
Normal file
@@ -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<string> 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<List<Models.McpServerEntry>>(
|
||||||
|
McpServersBox.Text) ?? new();
|
||||||
|
}
|
||||||
|
catch (Exception) { /* JSON 파싱 실패 시 기존 유지 */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 도구 비활성 목록 저장
|
||||||
|
if (_toolCardsLoaded)
|
||||||
|
llm.DisabledTools = _disabledTools.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user