Initial commit to new repository
This commit is contained in:
280
src/AxCopilot/Views/Controls/AgentSettingsPanel.xaml.cs
Normal file
280
src/AxCopilot/Views/Controls/AgentSettingsPanel.xaml.cs
Normal file
@@ -0,0 +1,280 @@
|
||||
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)
|
||||
{
|
||||
_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);
|
||||
|
||||
_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)
|
||||
{
|
||||
PanelToolToggles.Children.Clear();
|
||||
|
||||
var registry = CurrentApp?.GetType().GetProperty("ToolRegistry")?.GetValue(CurrentApp) as Services.Agent.ToolRegistry;
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user