440 lines
15 KiB
C#
440 lines
15 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();
|
|
RefreshPromptFields();
|
|
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);
|
|
}
|
|
|
|
public AiPromptSettings GetPromptSettings()
|
|
{
|
|
return _settings.Prompt;
|
|
}
|
|
|
|
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<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();
|
|
_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<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;
|
|
}
|
|
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<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;
|
|
_currentModel.ContextBudget = known.ContextBudget;
|
|
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 = "";
|
|
_contextBudgetBox.Text = "";
|
|
_supportsSseCheck.IsChecked = false;
|
|
_extraParamsBox.Text = "";
|
|
}
|
|
}
|
|
}
|