Files
AX-Copilot-Codex/src/AxCopilot/Views/AgentSettingsWindow.xaml.cs
lacvet 72a8c0d541
Some checks failed
Release Gate / gate (push) Has been cancelled
빌드 부산물 추적 해제와 AX Agent 대기열·composer UI 정리
- .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개
2026-04-04 23:03:42 +09:00

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);
}
}