Files
AnotherReplayReader/AIProviderSettingsControl.xaml.cs
T
2026-07-06 13:48:26 +02:00

380 lines
13 KiB
C#

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();
RefreshProviderList();
if (_settings.Providers.Count > 0)
{
_providerListBox.SelectedIndex = 0;
}
}
// ---------- public API ----------
public AiRequestContext? GetCurrentContext()
{
if (_currentProvider is null || _currentModel is null)
{
return null;
}
return new AiRequestContext(_currentProvider, _currentModel);
}
// ---------- Provider 列表管理 ----------
private void RefreshProviderList()
{
_providerListBox.ItemsSource = new ObservableCollection<AiProvider>(_settings.Providers);
_providerListBox.DisplayMemberPath = "Name";
}
private void OnProviderSelectionChanged(object sender, SelectionChangedEventArgs e)
{
_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();
RefreshModelList();
}
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.Save();
RefreshProviderList();
if (_settings.Providers.Count > 0)
{
_providerListBox.SelectedIndex = 0;
}
else
{
_currentProvider = 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.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<ModelDisplayItem>(items);
if (items.Count > 0)
{
_modelComboBox.SelectedIndex = 0;
}
else
{
ClearModelFields();
}
}
private void OnModelSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selected = _modelComboBox.SelectedItem as ModelDisplayItem;
_currentModel = selected?.Model;
if (_currentModel is null)
{
ClearModelFields();
return;
}
_modelIdBox.Text = _currentModel.ModelId;
_contextLengthBox.Text = _currentModel.ContextLength.ToString();
_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,
IsStream = known.IsStream,
ExtraParameters = new Dictionary<string, object>(known.ExtraParameters)
});
}
else
{
_currentProvider.Models.Add(new AiModel
{
ModelId = id,
ContextLength = 0,
IsStream = false
});
}
}
}
_settings.Save();
RefreshModelList();
_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<ModelDisplayItem>()
.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();
}
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;
}
_currentModel.IsStream = _supportsSseCheck.IsChecked == true;
try
{
var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(
_extraParamsBox.Text);
if (dict is not null)
{
_currentModel.ExtraParameters = dict;
}
}
catch
{
MessageBox.Show("高级参数格式错误,应为 JSON 键值对",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
_settings.Save();
RefreshModelList();
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<string, object>(known.ExtraParameters);
_currentModel.IsStream = known.IsStream;
_currentModel.ContextLength = known.ContextLength;
RefreshModelList();
_modelComboBox.SelectedItem = _modelComboBox.Items
.OfType<ModelDisplayItem>()
.FirstOrDefault(m => m.Model.ModelId == _currentModel.ModelId);
MessageBox.Show("已从已知模板填充", "信息", MessageBoxButton.OK);
}
else
{
MessageBox.Show("未找到已知模板", "信息", MessageBoxButton.OK);
}
}
private void ClearModelFields()
{
_modelIdBox.Text = "";
_contextLengthBox.Text = "";
_supportsSseCheck.IsChecked = false;
_extraParamsBox.Text = "";
}
}
}