using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text.Json; using System.Web.Routing; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; namespace AnotherReplayReader { public partial class AIProviderSettingsControl : UserControl { // ---------- fields ---------- private AiSettings _settings; private AiProvider? _currentProvider; private AiModel? _currentModel; // 为了下拉框显示,内部包装 private record ModelDisplayItem(AiModel Model) { public string DisplayText => $"{Model.ModelId}{(Model.ContextLength == 0 ? " (未知性能)" : "")}"; } // ---------- constructor ---------- public AIProviderSettingsControl() { InitializeComponent(); _settings = AiSettings.Load(); RefreshPromptFields(); RefreshProviderList(); if (_settings.Providers.Count > 0) { var lastSelection = _settings.ResolveLastSelection(); if (lastSelection is { } last) { // 恢复上次选中的 Provider 与模型。 // OnProviderSelectionChanged 会从持久化的 CurrentModelId 恢复模型; // 显式调用 SelectModel 作为双保险(模型仍存在则精确恢复)。 _providerListBox.SelectedItem = last.Provider; SelectModel(last.Model); } else { _providerListBox.SelectedIndex = 0; } } } // ---------- public API ---------- public AiRequestContext? GetCurrentContext() { if (_currentProvider is null || _currentModel is null) { return null; } return new AiRequestContext(_currentProvider, _currentModel); } public AiPromptSettings GetPromptSettings() { return _settings.Prompt; } /// /// 把当前选中的 Provider/模型保存到设置文件(供窗口关闭时调用, /// 下次打开 AI 设置页时恢复上次选择)。 /// public void SaveCurrentSelection() { if (_currentProvider is not null && _currentModel is not null) { _settings.SetCurrentSelection(_currentProvider, _currentModel); _settings.Save(); } } private void RefreshPromptFields() { _useCustomPromptCheck.IsChecked = _settings.Prompt.UseCustomSystemPrompt; _customPromptBox.Text = _settings.Prompt.CustomSystemPrompt; _additionalRulesBox.Text = _settings.Prompt.AdditionalRules; } private void OnApplyPromptClick(object sender, RoutedEventArgs e) { _settings.Prompt.UseCustomSystemPrompt = _useCustomPromptCheck.IsChecked == true; _settings.Prompt.CustomSystemPrompt = _customPromptBox.Text; _settings.Prompt.AdditionalRules = _additionalRulesBox.Text; _settings.Save(); MessageBox.Show("提示词配置已保存", "信息", MessageBoxButton.OK, MessageBoxImage.Information); } private void OnResetPromptClick(object sender, RoutedEventArgs e) { var result = MessageBox.Show( "确定要恢复内置 System Prompt 吗?自定义 System Prompt 会被清空,补充规则会保留。", "恢复默认提示词", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result != MessageBoxResult.Yes) { return; } _settings.Prompt.UseCustomSystemPrompt = false; _settings.Prompt.CustomSystemPrompt = string.Empty; _settings.Save(); RefreshPromptFields(); } private void OnClearAdditionalRulesClick(object sender, RoutedEventArgs e) { _settings.Prompt.AdditionalRules = string.Empty; _settings.Save(); RefreshPromptFields(); } // ---------- Provider 列表管理 ---------- private void RefreshProviderList() { _providerListBox.ItemsSource = new ObservableCollection(_settings.Providers); _providerListBox.DisplayMemberPath = "Name"; } private void OnProviderSelectionChanged(object sender, SelectionChangedEventArgs e) { // 记住当前模型选择,切换 Provider 后若新 Provider 存在同名模型则保持选择 var previousModelId = _settings.CurrentModelId; _currentProvider = _providerListBox.SelectedItem as AiProvider; if (_currentProvider is null) { return; } _providerNameBox.Text = _currentProvider.Name; _providerUrlBox.Text = _currentProvider.BaseUrl; _providerKeyBox.Password = _currentProvider.ApiKey; _temperatureBox.Text = _currentProvider.DefaultTemperature.ToString(); _topPBox.Text = _currentProvider.DefaultTopP.ToString(); _maxTokensBox.Text = _currentProvider.DefaultMaxTokens.ToString(); // 记录上次选中的 Provider(模型在 OnModelSelectionChanged 中记录) _settings.CurrentProviderName = _currentProvider.Name; RefreshModelList(); // 尝试恢复上一个选中的模型(启动恢复与 Provider 切换共用同一路径) if (!string.IsNullOrWhiteSpace(previousModelId)) { var previousItem = _modelComboBox.Items .OfType() .FirstOrDefault(m => string.Equals( m.Model.ModelId, previousModelId, StringComparison.OrdinalIgnoreCase)); if (previousItem is not null) { _modelComboBox.SelectedItem = previousItem; } } // 无任何模型/无可恢复目标时回退到第一个模型 if (_currentModel is null && _modelComboBox.Items.Count > 0) { _modelComboBox.SelectedIndex = 0; } } private void OnAddProviderClick(object sender, RoutedEventArgs e) { var newProvider = new AiProvider { Name = "新服务", BaseUrl = "https://api.openai.com/v1" }; _settings.Providers.Add(newProvider); _settings.Save(); RefreshProviderList(); _providerListBox.SelectedItem = newProvider; } private void OnRemoveProviderClick(object sender, RoutedEventArgs e) { if (_currentProvider is null) { return; } var result = MessageBox.Show( $"确定要删除服务 \"{_currentProvider.Name}\" 吗?", "确认删除", MessageBoxButton.YesNo); if (result != MessageBoxResult.Yes) { return; } _settings.Providers.Remove(_currentProvider); _settings.CurrentProviderName = null; _settings.CurrentModelId = null; _settings.Save(); RefreshProviderList(); if (_settings.Providers.Count > 0) { _providerListBox.SelectedIndex = 0; } else { _currentProvider = null; _currentModel = null; ClearProviderFields(); } } private void OnApplyProviderClick(object sender, RoutedEventArgs e) { if (_currentProvider is null) { return; } _currentProvider.Name = _providerNameBox.Text; _currentProvider.BaseUrl = _providerUrlBox.Text; _currentProvider.ApiKey = _providerKeyBox.Password; double.TryParse(_temperatureBox.Text, out double temp); _currentProvider.DefaultTemperature = temp; double.TryParse(_topPBox.Text, out double topP); _currentProvider.DefaultTopP = topP; int.TryParse(_maxTokensBox.Text, out int maxTokens); _currentProvider.DefaultMaxTokens = maxTokens; _settings.CurrentProviderName = _currentProvider.Name; _settings.Save(); RefreshProviderList(); _providerListBox.SelectedItem = _currentProvider; MessageBox.Show("服务配置已保存", "信息", MessageBoxButton.OK, MessageBoxImage.Information); } private void ClearProviderFields() { _providerNameBox.Text = ""; _providerUrlBox.Text = ""; _providerKeyBox.Password = ""; _temperatureBox.Text = ""; _topPBox.Text = ""; _maxTokensBox.Text = ""; _modelComboBox.ItemsSource = null; } // ---------- 模型管理 ---------- private void RefreshModelList() { if (_currentProvider is null) { _modelComboBox.ItemsSource = null; return; } var items = _currentProvider.Models .Select(m => new ModelDisplayItem(Model: m )) .ToList(); _modelComboBox.ItemsSource = new ObservableCollection(items); if (items.Count > 0) { _modelComboBox.SelectedIndex = 0; } else { // 当前 Provider 没有任何模型:清空当前模型,避免残留上一个 Provider 的模型 _currentModel = null; _settings.CurrentModelId = null; ClearModelFields(); } } /// /// 在模型下拉框中选中指定模型;找不到时回退到第一个模型。 /// 仅供启动恢复使用(避免刷新列表后触发首次默认选中)。 /// private void SelectModel(AiModel model) { var item = _modelComboBox.Items .OfType() .FirstOrDefault(m => string.Equals( m.Model.ModelId, model.ModelId, StringComparison.OrdinalIgnoreCase)); if (item is not null) { _modelComboBox.SelectedItem = item; } else if (_modelComboBox.Items.Count > 0) { _modelComboBox.SelectedIndex = 0; } } private void OnModelSelectionChanged(object sender, SelectionChangedEventArgs e) { var selected = _modelComboBox.SelectedItem as ModelDisplayItem; _currentModel = selected?.Model; if (_currentModel is null) { _settings.CurrentModelId = null; ClearModelFields(); return; } _settings.CurrentModelId = _currentModel.ModelId; _modelIdBox.Text = _currentModel.ModelId; _contextLengthBox.Text = _currentModel.ContextLength.ToString(); _contextBudgetBox.Text = _currentModel.ContextBudget is { } budget ? budget.ToString() : "0"; _supportsSseCheck.IsChecked = _currentModel.IsStream; // 显示 ExtraParameters 为缩进 JSON var json = JsonSerializer.Serialize( _currentModel.ExtraParameters, new JsonSerializerOptions { WriteIndented = true }); _extraParamsBox.Text = json; } private void OnFetchModelsClick(object sender, RoutedEventArgs e) { if (_currentProvider is null) { MessageBox.Show("请先选择一个服务"); return; } if (string.IsNullOrEmpty(_currentProvider.BaseUrl)) { MessageBox.Show("请先填写 Base URL"); return; } if (string.IsNullOrEmpty(_currentProvider.ApiKey)) { MessageBox.Show("请先填写 API Key"); return; } // 异步获取 Dispatcher.Invoke(async () => { try { _modelStatusText.Text = "正在获取模型列表..."; var ids = await AiModelFetcher.FetchModelsAsync( _currentProvider!.BaseUrl, _currentProvider.ApiKey); // 将获取的 ID 与现有模型合并,新 ID 若不存在则添加 var knownModels = KnownModels.GetAll(); foreach (var id in ids) { if (_currentProvider.Models.All(m => m.ModelId != id)) { var known = knownModels .OrderBy(k => KnownModels.GetSimilarity(k.ModelId, id)) .FirstOrDefault(); if (known != null && KnownModels.GetSimilarity(known.ModelId, id) >= KnownModels.SimilarityThreshold) { _currentProvider.Models.Add(new AiModel { ModelId = id, DisplayName = id, ContextLength = known.ContextLength, ContextBudget = known.ContextBudget, IsStream = known.IsStream, ExtraParameters = new Dictionary(known.ExtraParameters) }); } else { _currentProvider.Models.Add(new AiModel { ModelId = id, ContextLength = 0, IsStream = false }); } } } _settings.Save(); RefreshModelList(); // 保持上次选中的模型(若仍存在),否则回退到第一个 if (_settings.CurrentModelId is { } lastModelId) { var lastItem = _modelComboBox.Items .OfType() .FirstOrDefault(m => string.Equals( m.Model.ModelId, lastModelId, StringComparison.OrdinalIgnoreCase)); if (lastItem is not null) { _modelComboBox.SelectedItem = lastItem; } } _modelStatusText.Text = $"获取成功,共 {ids.Count} 个模型"; } catch (Exception ex) { _modelStatusText.Text = $"获取失败: {ex.Message}"; } }); } private void OnAddCustomModelClick(object sender, RoutedEventArgs e) { if (_currentProvider is null) { return; } var newModel = new AiModel { ModelId = "custom-model-id", ContextLength = 0, IsStream = false }; _currentProvider.Models.Add(newModel); _settings.Save(); RefreshModelList(); _modelComboBox.SelectedItem = _modelComboBox.Items .OfType() .Last(); } private void OnRemoveModelClick(object sender, RoutedEventArgs e) { if (_currentProvider is null || _currentModel is null) { return; } var result = MessageBox.Show( $"确定要删除模型 \"{_currentModel.ModelId}\" 吗?", "确认删除", MessageBoxButton.YesNo); if (result != MessageBoxResult.Yes) { return; } _currentProvider.Models.Remove(_currentModel); _settings.Save(); // 被删除模型不再是上次选择;RefreshModelList 会回退到第一个模型 _settings.CurrentModelId = null; RefreshModelList(); } private void OnApplyModelClick(object sender, RoutedEventArgs e) { if (_currentModel is null) { return; } _currentModel.ModelId = _modelIdBox.Text; if (int.TryParse(_contextLengthBox.Text, out int ctxLen)) { _currentModel.ContextLength = ctxLen; } if (int.TryParse(_contextBudgetBox.Text, out int budget) && budget > 0) { _currentModel.ContextBudget = budget; } else { _currentModel.ContextBudget = null; } _currentModel.IsStream = _supportsSseCheck.IsChecked == true; try { var dict = JsonSerializer.Deserialize>( _extraParamsBox.Text); if (dict is not null) { _currentModel.ExtraParameters = dict; } } catch { MessageBox.Show("高级参数格式错误,应为 JSON 键值对", "错误", MessageBoxButton.OK, MessageBoxImage.Error); return; } _settings.Save(); // 模型 ID 已修改:更新持久化选择,避免下一次启动仍按旧 ID 解析 _settings.CurrentModelId = _currentModel.ModelId; RefreshModelList(); _modelComboBox.SelectedItem = _modelComboBox.Items .OfType() .FirstOrDefault(m => string.Equals( m.Model.ModelId, _currentModel.ModelId, StringComparison.OrdinalIgnoreCase)); MessageBox.Show("模型修改已保存", "信息", MessageBoxButton.OK); } private void OnFillFromKnownModelsClick(object sender, RoutedEventArgs e) { if (_currentProvider is null || _currentModel is null) { return; } var known = KnownModels.GetAll() .OrderBy(k => KnownModels.GetSimilarity(k.ModelId, _currentModel.ModelId)) .FirstOrDefault(); if (known is not null && KnownModels.GetSimilarity(known.ModelId, _currentModel.ModelId) >= KnownModels.SimilarityThreshold) { _currentModel.ExtraParameters = new Dictionary(known.ExtraParameters); _currentModel.IsStream = known.IsStream; _currentModel.ContextLength = known.ContextLength; _currentModel.ContextBudget = known.ContextBudget; RefreshModelList(); _modelComboBox.SelectedItem = _modelComboBox.Items .OfType() .FirstOrDefault(m => string.Equals( m.Model.ModelId, _currentModel.ModelId, StringComparison.OrdinalIgnoreCase)); MessageBox.Show("已从已知模板填充", "信息", MessageBoxButton.OK); } else { MessageBox.Show("未找到已知模板", "信息", MessageBoxButton.OK); } } private void ClearModelFields() { _modelIdBox.Text = ""; _contextLengthBox.Text = ""; _contextBudgetBox.Text = ""; _supportsSseCheck.IsChecked = false; _extraParamsBox.Text = ""; } } }