ChatWindow.MoodMenu.cs (BtnSettings_Click 개선): - Shift+클릭 조건 제거 → 기어 버튼 클릭 시 AgentSettingsPanel 직접 열기 - Ctrl+클릭으로 전역 SettingsWindow 열기 (기존 동작 유지) - ToggleSettingsPanel(): _toolRegistry 패널에 전달, SettingsChanged 이벤트 연결 - OnSettingsPanelChanged() 신규: 설정 변경 시 ModelLabel·AnalyzerButton 즉시 갱신 ChatWindow.TabSwitching.cs: - UpdateTabUI(): 설정 패널 열린 상태이면 UpdateActiveTab() 자동 호출 - 탭 전환 시 탭별 전용 설정(Cowork 검증/Code LSP 등) 패널 자동 반영 ChatWindow.xaml: - 설정 버튼 ToolTip: "설정 패널 (Ctrl+클릭: 전역 설정)"으로 UX 안내 추가 AgentSettingsPanel.xaml.cs: - LoadFromSettings(): ToolRegistry? 매개변수 추가 (외부 레지스트리 주입) - BuildToolToggles(): 외부 레지스트리 우선, 없으면 ToolRegistry.CreateDefault() 폴백 - 반영 방식 개선: 리플렉션 해킹 제거 → 실제 ChatWindow._toolRegistry 참조 docs/NEXT_ROADMAP.md: - Phase 17-UI-A 완료 항목 추가 (변경 파일 테이블, 개선 효과) 빌드: 경고 0, 오류 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
284 lines
9.8 KiB
C#
284 lines
9.8 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
using AxCopilot.Services;
|
|
|
|
namespace AxCopilot.Views.Controls;
|
|
|
|
/// <summary>
|
|
/// Phase 32-A: 우측 슬라이드인 설정 패널.
|
|
/// ChatWindow 내부에서 AX Agent 설정을 인라인으로 제어합니다.
|
|
/// CC의 Claude.ai 설정 패널과 동등한 UX.
|
|
/// </summary>
|
|
public partial class AgentSettingsPanel : UserControl
|
|
{
|
|
private static App? CurrentApp => System.Windows.Application.Current as App;
|
|
|
|
private bool _isOpen;
|
|
private bool _isLoading = true; // 초기 로드 중 이벤트 발생 방지
|
|
|
|
/// <summary>설정 패널 열림/닫힘 상태.</summary>
|
|
public bool IsOpen
|
|
{
|
|
get => _isOpen;
|
|
set
|
|
{
|
|
if (_isOpen == value) return;
|
|
_isOpen = value;
|
|
AnimateSlide(value);
|
|
}
|
|
}
|
|
|
|
/// <summary>설정 변경 시 발생하는 이벤트.</summary>
|
|
public event EventHandler? SettingsChanged;
|
|
|
|
/// <summary>닫기 요청 이벤트.</summary>
|
|
public event EventHandler? CloseRequested;
|
|
|
|
public AgentSettingsPanel()
|
|
{
|
|
InitializeComponent();
|
|
Visibility = Visibility.Collapsed;
|
|
}
|
|
|
|
/// <summary>현재 설정값으로 UI를 초기화합니다.</summary>
|
|
public void LoadFromSettings(SettingsService settings, string activeTab,
|
|
Services.Agent.ToolRegistry? toolRegistry = null)
|
|
{
|
|
_isLoading = true;
|
|
|
|
var llm = settings.Settings.Llm;
|
|
|
|
TxtCurrentTab.Text = activeTab;
|
|
TxtServiceName.Text = llm.Service ?? "Ollama";
|
|
TxtModelName.Text = llm.Model ?? "unknown";
|
|
TxtApiEndpoint.Text = llm.OllamaEndpoint ?? "";
|
|
|
|
SliderMaxIterations.Value = llm.MaxAgentIterations > 0 ? llm.MaxAgentIterations : 25;
|
|
TxtMaxIterations.Text = ((int)SliderMaxIterations.Value).ToString();
|
|
|
|
SliderMaxRetry.Value = llm.MaxRetryOnError > 0 ? llm.MaxRetryOnError : 3;
|
|
TxtMaxRetry.Text = ((int)SliderMaxRetry.Value).ToString();
|
|
|
|
ChkParallelTools.IsChecked = llm.EnableParallelTools;
|
|
|
|
SliderAutoCompact.Value = llm.AutoCompactThreshold > 0 ? llm.AutoCompactThreshold : 80;
|
|
TxtAutoCompact.Text = ((int)SliderAutoCompact.Value).ToString();
|
|
|
|
ChkDevMode.IsChecked = llm.DevMode;
|
|
|
|
// 탭별 설정 표시
|
|
PanelCoworkSettings.Visibility = activeTab == "Cowork" ? Visibility.Visible : Visibility.Collapsed;
|
|
PanelCodeSettings.Visibility = activeTab == "Code" ? Visibility.Visible : Visibility.Collapsed;
|
|
|
|
if (activeTab == "Cowork")
|
|
{
|
|
ChkCoworkVerify.IsChecked = llm.EnableCoworkVerification;
|
|
}
|
|
else if (activeTab == "Code")
|
|
{
|
|
ChkCodeLsp.IsChecked = llm.Code?.EnableLsp ?? true;
|
|
ChkCodeVerify.IsChecked = llm.Code?.EnableCodeVerification ?? false;
|
|
}
|
|
|
|
// 도구 토글 동적 생성
|
|
BuildToolToggles(settings, toolRegistry);
|
|
|
|
_isLoading = false;
|
|
}
|
|
|
|
/// <summary>활성 탭 변경 시 UI를 업데이트합니다.</summary>
|
|
public void UpdateActiveTab(string tab)
|
|
{
|
|
TxtCurrentTab.Text = tab;
|
|
PanelCoworkSettings.Visibility = tab == "Cowork" ? Visibility.Visible : Visibility.Collapsed;
|
|
PanelCodeSettings.Visibility = tab == "Code" ? Visibility.Visible : Visibility.Collapsed;
|
|
}
|
|
|
|
private void BuildToolToggles(SettingsService settings,
|
|
Services.Agent.ToolRegistry? externalRegistry = null)
|
|
{
|
|
PanelToolToggles.Children.Clear();
|
|
|
|
// Phase 17-UI: 외부에서 전달된 레지스트리 우선, 없으면 독립 생성
|
|
var registry = externalRegistry ?? Services.Agent.ToolRegistry.CreateDefault();
|
|
if (registry == null) return;
|
|
|
|
var disabled = new HashSet<string>(
|
|
settings.Settings.Llm.DisabledTools ?? new List<string>(),
|
|
StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (var tool in registry.All.OrderBy(t => t.Name))
|
|
{
|
|
var grid = new Grid { Margin = new Thickness(0, 0, 0, 4) };
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
|
|
|
var label = new TextBlock
|
|
{
|
|
Text = tool.Name,
|
|
FontSize = 11,
|
|
Foreground = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
};
|
|
Grid.SetColumn(label, 0);
|
|
|
|
var toggle = new CheckBox
|
|
{
|
|
IsChecked = !disabled.Contains(tool.Name),
|
|
Tag = tool.Name,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
};
|
|
if (TryFindResource("ToggleSwitch") is Style toggleStyle)
|
|
toggle.Style = toggleStyle;
|
|
toggle.Checked += ToolToggle_Changed;
|
|
toggle.Unchecked += ToolToggle_Changed;
|
|
Grid.SetColumn(toggle, 1);
|
|
|
|
grid.Children.Add(label);
|
|
grid.Children.Add(toggle);
|
|
PanelToolToggles.Children.Add(grid);
|
|
}
|
|
}
|
|
|
|
#region ── 이벤트 핸들러 ──
|
|
|
|
private void BtnClose_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
{
|
|
IsOpen = false;
|
|
CloseRequested?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
private void BtnServiceSelector_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
{
|
|
// TODO: 서비스 선택 팝업 (Ollama/vLLM/Gemini/Claude)
|
|
}
|
|
|
|
private void BtnModelSelector_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
{
|
|
// TODO: 모델 선택 팝업 — ChatWindow의 기존 모델 선택 로직 재사용
|
|
}
|
|
|
|
private void TxtApiEndpoint_LostFocus(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_isLoading) return;
|
|
SaveSetting(s => s.Llm.OllamaEndpoint = TxtApiEndpoint.Text);
|
|
}
|
|
|
|
private void SliderMaxIterations_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
|
{
|
|
if (_isLoading || TxtMaxIterations == null) return;
|
|
var val = (int)SliderMaxIterations.Value;
|
|
TxtMaxIterations.Text = val.ToString();
|
|
SaveSetting(s => s.Llm.MaxAgentIterations = val);
|
|
}
|
|
|
|
private void SliderMaxRetry_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
|
{
|
|
if (_isLoading || TxtMaxRetry == null) return;
|
|
var val = (int)SliderMaxRetry.Value;
|
|
TxtMaxRetry.Text = val.ToString();
|
|
SaveSetting(s => s.Llm.MaxRetryOnError = val);
|
|
}
|
|
|
|
private void ChkParallelTools_Changed(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_isLoading) return;
|
|
SaveSetting(s => s.Llm.EnableParallelTools = ChkParallelTools.IsChecked == true);
|
|
}
|
|
|
|
private void ChkCoworkVerify_Changed(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_isLoading) return;
|
|
SaveSetting(s => s.Llm.EnableCoworkVerification = ChkCoworkVerify.IsChecked == true);
|
|
}
|
|
|
|
private void ChkCodeLsp_Changed(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_isLoading) return;
|
|
SaveSetting(s => { if (s.Llm.Code != null) s.Llm.Code.EnableLsp = ChkCodeLsp.IsChecked == true; });
|
|
}
|
|
|
|
private void ChkCodeVerify_Changed(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_isLoading) return;
|
|
SaveSetting(s => { if (s.Llm.Code != null) s.Llm.Code.EnableCodeVerification = ChkCodeVerify.IsChecked == true; });
|
|
}
|
|
|
|
private void SliderAutoCompact_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
|
{
|
|
if (_isLoading || TxtAutoCompact == null) return;
|
|
var val = (int)SliderAutoCompact.Value;
|
|
TxtAutoCompact.Text = val.ToString();
|
|
SaveSetting(s => s.Llm.AutoCompactThreshold = val);
|
|
}
|
|
|
|
private void ChkProjectRules_Changed(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_isLoading) return;
|
|
// 프로젝트 규칙 활성/비활성 — 현재 세션에만 영향
|
|
}
|
|
|
|
private void ChkDevMode_Changed(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_isLoading) return;
|
|
SaveSetting(s => s.Llm.DevMode = ChkDevMode.IsChecked == true);
|
|
}
|
|
|
|
private void ToolToggle_Changed(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_isLoading || sender is not CheckBox chk || chk.Tag is not string toolName) return;
|
|
|
|
SaveSetting(s =>
|
|
{
|
|
s.Llm.DisabledTools ??= new List<string>();
|
|
if (chk.IsChecked == true)
|
|
s.Llm.DisabledTools.Remove(toolName);
|
|
else if (!s.Llm.DisabledTools.Contains(toolName))
|
|
s.Llm.DisabledTools.Add(toolName);
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ── 설정 저장 & 애니메이션 ──
|
|
|
|
private void SaveSetting(Action<Models.AppSettings> apply)
|
|
{
|
|
var svc = CurrentApp?.SettingsService;
|
|
if (svc == null) return;
|
|
|
|
apply(svc.Settings);
|
|
svc.Save();
|
|
SettingsChanged?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
private void AnimateSlide(bool open)
|
|
{
|
|
if (open)
|
|
{
|
|
Visibility = Visibility.Visible;
|
|
var anim = new DoubleAnimation(300, 0, TimeSpan.FromMilliseconds(200))
|
|
{
|
|
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }
|
|
};
|
|
SlideTransform.BeginAnimation(TranslateTransform.XProperty, anim);
|
|
}
|
|
else
|
|
{
|
|
var anim = new DoubleAnimation(0, 300, TimeSpan.FromMilliseconds(150))
|
|
{
|
|
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn }
|
|
};
|
|
anim.Completed += (_, _) =>
|
|
{
|
|
if (!_isOpen) Visibility = Visibility.Collapsed;
|
|
};
|
|
SlideTransform.BeginAnimation(TranslateTransform.XProperty, anim);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|