using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using AxCopilot.Services;
namespace AxCopilot.Views.Controls;
///
/// Phase 32-A: 우측 슬라이드인 설정 패널.
/// ChatWindow 내부에서 AX Agent 설정을 인라인으로 제어합니다.
/// CC의 Claude.ai 설정 패널과 동등한 UX.
///
public partial class AgentSettingsPanel : UserControl
{
private static App? CurrentApp => System.Windows.Application.Current as App;
private bool _isOpen;
private bool _isLoading = true; // 초기 로드 중 이벤트 발생 방지
/// 설정 패널 열림/닫힘 상태.
public bool IsOpen
{
get => _isOpen;
set
{
if (_isOpen == value) return;
_isOpen = value;
AnimateSlide(value);
}
}
/// 설정 변경 시 발생하는 이벤트.
public event EventHandler? SettingsChanged;
/// 닫기 요청 이벤트.
public event EventHandler? CloseRequested;
public AgentSettingsPanel()
{
InitializeComponent();
Visibility = Visibility.Collapsed;
}
/// 현재 설정값으로 UI를 초기화합니다.
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;
}
/// 활성 탭 변경 시 UI를 업데이트합니다.
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(
settings.Settings.Llm.DisabledTools ?? new List(),
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 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 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 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();
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 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
}