Some checks failed
Release Gate / gate (push) Has been cancelled
- .gitignore에 bin/obj/publish 및 IDE/OS/비밀정보 패턴 추가 - Git 인덱스에서 publish 및 src 하위 bin/obj 빌드 부산물 추적을 해제하여 저장소 노이즈를 정리 - DraftQueue를 실행 대기/최근 결과 섹션과 상태 요약 pill 구조로 재정리 - composer 상단 모델/컨텍스트/프리셋 줄과 하단 작업 위치 칩 UI를 더 평평한 시각 언어로 통일 - 워크스페이스·브랜치·워크트리 패널에 공통 row 및 요약 strip을 적용해 panel UX를 정돈 - README.md와 docs/DEVELOPMENT.md, docs/AGENT_ROADMAP.md, AGENTS.md 이력을 갱신 검증 - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ - 경고 0개, 오류 0개
422 lines
16 KiB
C#
422 lines
16 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using AxCopilot.Models;
|
|
using AxCopilot.Services;
|
|
using AxCopilot.Services.Agent;
|
|
|
|
namespace AxCopilot.Views;
|
|
|
|
public partial class AgentSettingsWindow : Window
|
|
{
|
|
private const string UnifiedAdminPassword = "axgo123!";
|
|
|
|
private readonly SettingsService _settings;
|
|
private readonly LlmSettings _llm;
|
|
private string _permissionMode = PermissionModeCatalog.Deny;
|
|
private string _planMode = "off";
|
|
private string _reasoningMode = "detailed";
|
|
private string _folderDataUsage = "active";
|
|
private string _operationMode = OperationModePolicy.InternalMode;
|
|
private string _selectedModel = string.Empty;
|
|
|
|
public AgentSettingsWindow(SettingsService settings)
|
|
{
|
|
_settings = settings;
|
|
_llm = _settings.Settings.Llm;
|
|
InitializeComponent();
|
|
LoadFromSettings();
|
|
}
|
|
|
|
private void LoadFromSettings()
|
|
{
|
|
_selectedModel = _llm.Model ?? "";
|
|
ModelInput.Text = _selectedModel;
|
|
_permissionMode = PermissionModeCatalog.NormalizeGlobalMode(_llm.FilePermission);
|
|
_planMode = string.IsNullOrWhiteSpace(_llm.PlanMode) ? "off" : _llm.PlanMode;
|
|
_reasoningMode = string.IsNullOrWhiteSpace(_llm.AgentDecisionLevel) ? "detailed" : _llm.AgentDecisionLevel;
|
|
_folderDataUsage = string.IsNullOrWhiteSpace(_llm.FolderDataUsage) ? "active" : _llm.FolderDataUsage;
|
|
_operationMode = OperationModePolicy.Normalize(_settings.Settings.OperationMode);
|
|
|
|
ChkEnableProactiveCompact.IsChecked = _llm.EnableProactiveContextCompact;
|
|
TxtContextCompactTriggerPercent.Text = Math.Clamp(_llm.ContextCompactTriggerPercent, 10, 95).ToString();
|
|
TxtMaxContextTokens.Text = Math.Max(1024, _llm.MaxContextTokens).ToString();
|
|
TxtMaxRetryOnError.Text = Math.Clamp(_llm.MaxRetryOnError, 0, 10).ToString();
|
|
|
|
ChkEnableSkillSystem.IsChecked = _llm.EnableSkillSystem;
|
|
ChkEnableToolHooks.IsChecked = _llm.EnableToolHooks;
|
|
ChkEnableHookInputMutation.IsChecked = _llm.EnableHookInputMutation;
|
|
ChkEnableHookPermissionUpdate.IsChecked = _llm.EnableHookPermissionUpdate;
|
|
ChkEnableCoworkVerification.IsChecked = _llm.EnableCoworkVerification;
|
|
ChkEnableCodeVerification.IsChecked = _llm.Code.EnableCodeVerification;
|
|
ChkEnableParallelTools.IsChecked = _llm.EnableParallelTools;
|
|
|
|
RefreshServiceCards();
|
|
RefreshThemeCards();
|
|
RefreshModeLabels();
|
|
BuildModelChips();
|
|
}
|
|
|
|
private void RefreshThemeCards()
|
|
{
|
|
var selected = (_llm.AgentTheme ?? "system").ToLowerInvariant();
|
|
SetCardSelection(ThemeSystemCard, selected == "system");
|
|
SetCardSelection(ThemeLightCard, selected == "light");
|
|
SetCardSelection(ThemeDarkCard, selected == "dark");
|
|
}
|
|
|
|
private void RefreshServiceCards()
|
|
{
|
|
var service = (_llm.Service ?? "ollama").ToLowerInvariant();
|
|
SetCardSelection(SvcOllamaCard, service == "ollama");
|
|
SetCardSelection(SvcVllmCard, service == "vllm");
|
|
SetCardSelection(SvcGeminiCard, service == "gemini");
|
|
SetCardSelection(SvcClaudeCard, service is "claude" or "sigmoid");
|
|
}
|
|
|
|
private void RefreshModeLabels()
|
|
{
|
|
BtnOperationMode.Content = BuildOperationModeLabel(_operationMode);
|
|
BtnPermissionMode.Content = PermissionModeCatalog.ToDisplayLabel(_permissionMode);
|
|
BtnPlanMode.Content = BuildPlanModeLabel(_planMode);
|
|
BtnReasoningMode.Content = BuildReasoningModeLabel(_reasoningMode);
|
|
BtnFolderDataUsage.Content = BuildFolderDataUsageLabel(_folderDataUsage);
|
|
AdvancedPanel.Visibility = Visibility.Visible;
|
|
}
|
|
|
|
private static string BuildOperationModeLabel(string mode)
|
|
{
|
|
return OperationModePolicy.Normalize(mode) == OperationModePolicy.ExternalMode
|
|
? "사외 모드"
|
|
: "사내 모드";
|
|
}
|
|
|
|
private static string BuildPlanModeLabel(string mode)
|
|
{
|
|
return (mode ?? "off").ToLowerInvariant() switch
|
|
{
|
|
"always" => "항상 계획",
|
|
"auto" => "자동 계획",
|
|
_ => "끄기",
|
|
};
|
|
}
|
|
|
|
private static string BuildReasoningModeLabel(string mode)
|
|
{
|
|
return (mode ?? "detailed").ToLowerInvariant() switch
|
|
{
|
|
"minimal" => "낮음",
|
|
"normal" => "중간",
|
|
_ => "높음",
|
|
};
|
|
}
|
|
|
|
private static string BuildFolderDataUsageLabel(string mode)
|
|
{
|
|
return (mode ?? "none").ToLowerInvariant() switch
|
|
{
|
|
"active" => "적극 활용",
|
|
"passive" => "소극 활용",
|
|
_ => "활용하지 않음",
|
|
};
|
|
}
|
|
|
|
private void SetCardSelection(Border border, bool selected)
|
|
{
|
|
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.DodgerBlue;
|
|
var normal = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
|
border.BorderBrush = selected ? accent : normal;
|
|
border.Background = selected
|
|
? (TryFindResource("HintBackground") as Brush ?? Brushes.Transparent)
|
|
: Brushes.Transparent;
|
|
}
|
|
|
|
private void BuildModelChips()
|
|
{
|
|
ModelChipPanel.Children.Clear();
|
|
var models = GetModelCandidates(_llm.Service);
|
|
foreach (var model in models)
|
|
{
|
|
var captured = model;
|
|
var border = new Border
|
|
{
|
|
Cursor = Cursors.Hand,
|
|
CornerRadius = new CornerRadius(10),
|
|
BorderThickness = new Thickness(1),
|
|
BorderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray,
|
|
Background = Brushes.Transparent,
|
|
Padding = new Thickness(10, 6, 10, 6),
|
|
Margin = new Thickness(0, 0, 8, 8),
|
|
Child = new TextBlock
|
|
{
|
|
Text = model,
|
|
FontSize = 11,
|
|
Foreground = TryFindResource("PrimaryText") as Brush ?? Brushes.White,
|
|
},
|
|
};
|
|
SetCardSelection(border, string.Equals(captured, _selectedModel, StringComparison.OrdinalIgnoreCase));
|
|
border.MouseLeftButtonUp += (_, _) =>
|
|
{
|
|
_selectedModel = captured;
|
|
ModelInput.Text = captured;
|
|
BuildModelChips();
|
|
};
|
|
ModelChipPanel.Children.Add(border);
|
|
}
|
|
}
|
|
|
|
private List<string> GetModelCandidates(string? service)
|
|
{
|
|
var key = (service ?? "ollama").ToLowerInvariant();
|
|
var result = new List<string>();
|
|
|
|
foreach (var m in _llm.RegisteredModels)
|
|
{
|
|
if (!string.Equals(m.Service, key, StringComparison.OrdinalIgnoreCase))
|
|
continue;
|
|
if (!string.IsNullOrWhiteSpace(m.Alias) && !result.Contains(m.Alias))
|
|
result.Add(m.Alias);
|
|
}
|
|
|
|
void AddIf(string? value)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(value) && !result.Contains(value))
|
|
result.Add(value);
|
|
}
|
|
|
|
if (key == "ollama") AddIf(_llm.OllamaModel);
|
|
else if (key == "vllm") AddIf(_llm.VllmModel);
|
|
else if (key == "gemini") AddIf(_llm.GeminiModel);
|
|
else AddIf(_llm.ClaudeModel);
|
|
|
|
return result;
|
|
}
|
|
|
|
private void SetService(string service)
|
|
{
|
|
_llm.Service = service;
|
|
RefreshServiceCards();
|
|
BuildModelChips();
|
|
}
|
|
|
|
private void SetTheme(string theme)
|
|
{
|
|
_llm.AgentTheme = theme;
|
|
RefreshThemeCards();
|
|
}
|
|
|
|
private static string CycleOperationMode(string current)
|
|
{
|
|
return OperationModePolicy.Normalize(current) == OperationModePolicy.ExternalMode
|
|
? OperationModePolicy.InternalMode
|
|
: OperationModePolicy.ExternalMode;
|
|
}
|
|
|
|
private bool PromptPasswordDialog(string title, string header, string message)
|
|
{
|
|
var bgBrush = TryFindResource("LauncherBackground") as Brush ?? Brushes.White;
|
|
var fgBrush = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
|
|
var subFgBrush = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
|
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
|
var itemBg = TryFindResource("ItemBackground") as Brush ?? Brushes.WhiteSmoke;
|
|
|
|
var dlg = new Window
|
|
{
|
|
Title = title,
|
|
Width = 340,
|
|
SizeToContent = SizeToContent.Height,
|
|
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
|
Owner = this,
|
|
ResizeMode = ResizeMode.NoResize,
|
|
WindowStyle = WindowStyle.None,
|
|
AllowsTransparency = true,
|
|
Background = Brushes.Transparent,
|
|
ShowInTaskbar = false,
|
|
};
|
|
|
|
var border = new Border
|
|
{
|
|
Background = bgBrush,
|
|
CornerRadius = new CornerRadius(12),
|
|
BorderBrush = borderBrush,
|
|
BorderThickness = new Thickness(1),
|
|
Padding = new Thickness(20),
|
|
};
|
|
|
|
var stack = new StackPanel();
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = header,
|
|
FontSize = 15,
|
|
FontWeight = FontWeights.SemiBold,
|
|
Foreground = fgBrush,
|
|
Margin = new Thickness(0, 0, 0, 12),
|
|
});
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = message,
|
|
FontSize = 12,
|
|
Foreground = subFgBrush,
|
|
Margin = new Thickness(0, 0, 0, 6),
|
|
});
|
|
|
|
var pwBox = new PasswordBox
|
|
{
|
|
FontSize = 14,
|
|
Padding = new Thickness(8, 6, 8, 6),
|
|
Background = itemBg,
|
|
Foreground = fgBrush,
|
|
BorderBrush = borderBrush,
|
|
PasswordChar = '*',
|
|
};
|
|
stack.Children.Add(pwBox);
|
|
|
|
var btnRow = new StackPanel
|
|
{
|
|
Orientation = Orientation.Horizontal,
|
|
HorizontalAlignment = HorizontalAlignment.Right,
|
|
Margin = new Thickness(0, 16, 0, 0),
|
|
};
|
|
|
|
var cancelBtn = new Button { Content = "취소", Padding = new Thickness(16, 6, 16, 6), Margin = new Thickness(0, 0, 8, 0) };
|
|
cancelBtn.Click += (_, _) => dlg.DialogResult = false;
|
|
btnRow.Children.Add(cancelBtn);
|
|
|
|
var okBtn = new Button { Content = "확인", Padding = new Thickness(16, 6, 16, 6), IsDefault = true };
|
|
okBtn.Click += (_, _) =>
|
|
{
|
|
if (pwBox.Password == UnifiedAdminPassword)
|
|
dlg.DialogResult = true;
|
|
else
|
|
{
|
|
pwBox.Clear();
|
|
pwBox.Focus();
|
|
}
|
|
};
|
|
btnRow.Children.Add(okBtn);
|
|
stack.Children.Add(btnRow);
|
|
|
|
border.Child = stack;
|
|
dlg.Content = border;
|
|
dlg.Loaded += (_, _) => pwBox.Focus();
|
|
return dlg.ShowDialog() == true;
|
|
}
|
|
|
|
private void ThemeSystemCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetTheme("system");
|
|
private void ThemeLightCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetTheme("light");
|
|
private void ThemeDarkCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetTheme("dark");
|
|
|
|
private void SvcOllamaCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetService("ollama");
|
|
private void SvcVllmCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetService("vllm");
|
|
private void SvcGeminiCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetService("gemini");
|
|
private void SvcClaudeCard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => SetService("claude");
|
|
|
|
private void BtnOperationMode_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var next = CycleOperationMode(_operationMode);
|
|
if (!PromptPasswordDialog(
|
|
"운영 모드 변경",
|
|
"사내/사외 모드 변경",
|
|
"비밀번호를 입력하세요."))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_operationMode = next;
|
|
RefreshModeLabels();
|
|
}
|
|
|
|
private void BtnPermissionMode_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_permissionMode = PermissionModeCatalog.NormalizeGlobalMode(_permissionMode) switch
|
|
{
|
|
PermissionModeCatalog.Deny => PermissionModeCatalog.Default,
|
|
PermissionModeCatalog.Default => PermissionModeCatalog.AcceptEdits,
|
|
PermissionModeCatalog.AcceptEdits => PermissionModeCatalog.Plan,
|
|
PermissionModeCatalog.Plan => PermissionModeCatalog.BypassPermissions,
|
|
// 권한 표면 모드는 코어 4단계만 순환합니다.
|
|
PermissionModeCatalog.BypassPermissions => PermissionModeCatalog.Deny,
|
|
PermissionModeCatalog.DontAsk => PermissionModeCatalog.Deny,
|
|
_ => PermissionModeCatalog.Deny,
|
|
};
|
|
RefreshModeLabels();
|
|
}
|
|
|
|
private void BtnPlanMode_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_planMode = _planMode switch
|
|
{
|
|
"off" => "auto",
|
|
"auto" => "always",
|
|
_ => "off",
|
|
};
|
|
RefreshModeLabels();
|
|
}
|
|
|
|
private void BtnReasoningMode_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_reasoningMode = _reasoningMode switch
|
|
{
|
|
"minimal" => "normal",
|
|
"normal" => "detailed",
|
|
_ => "minimal",
|
|
};
|
|
RefreshModeLabels();
|
|
}
|
|
|
|
private void BtnFolderDataUsage_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_folderDataUsage = _folderDataUsage switch
|
|
{
|
|
"none" => "passive",
|
|
"passive" => "active",
|
|
_ => "none",
|
|
};
|
|
RefreshModeLabels();
|
|
}
|
|
|
|
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_llm.Model = string.IsNullOrWhiteSpace(_selectedModel) ? (_llm.Model ?? "") : _selectedModel.Trim();
|
|
_llm.FilePermission = _permissionMode;
|
|
_llm.PlanMode = _planMode;
|
|
_llm.AgentDecisionLevel = _reasoningMode;
|
|
_llm.FolderDataUsage = _folderDataUsage;
|
|
_llm.AgentUiExpressionLevel = "rich";
|
|
|
|
_llm.EnableProactiveContextCompact = ChkEnableProactiveCompact.IsChecked == true;
|
|
_llm.ContextCompactTriggerPercent = ParseInt(TxtContextCompactTriggerPercent.Text, 80, 10, 95);
|
|
_llm.MaxContextTokens = ParseInt(TxtMaxContextTokens.Text, 4096, 1024, 200000);
|
|
_llm.MaxRetryOnError = ParseInt(TxtMaxRetryOnError.Text, 3, 0, 10);
|
|
|
|
_llm.EnableSkillSystem = ChkEnableSkillSystem.IsChecked == true;
|
|
_llm.EnableToolHooks = ChkEnableToolHooks.IsChecked == true;
|
|
_llm.EnableHookInputMutation = ChkEnableHookInputMutation.IsChecked == true;
|
|
_llm.EnableHookPermissionUpdate = ChkEnableHookPermissionUpdate.IsChecked == true;
|
|
_llm.EnableCoworkVerification = ChkEnableCoworkVerification.IsChecked == true;
|
|
_llm.Code.EnableCodeVerification = ChkEnableCodeVerification.IsChecked == true;
|
|
_llm.EnableParallelTools = ChkEnableParallelTools.IsChecked == true;
|
|
|
|
_settings.Settings.OperationMode = OperationModePolicy.Normalize(_operationMode);
|
|
_settings.Save();
|
|
DialogResult = true;
|
|
Close();
|
|
}
|
|
|
|
private void BtnOpenFullSettings_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (System.Windows.Application.Current is App app)
|
|
app.OpenSettingsFromChat();
|
|
}
|
|
|
|
private void BtnClose_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => Close();
|
|
|
|
private static int ParseInt(string? text, int fallback, int min, int max)
|
|
{
|
|
if (!int.TryParse(text, out var value))
|
|
value = fallback;
|
|
return Math.Clamp(value, min, max);
|
|
}
|
|
}
|