wip
This commit is contained in:
@@ -0,0 +1,380 @@
|
||||
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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user