using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
namespace AnotherReplayReader
{
///
/// 服务端点配置(如 DeepSeek 官方、NVIDIA NIM)
///
public class AiProvider
{
public string Name { get; set; } = string.Empty;
public string BaseUrl { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
public List Models { get; set; } = [];
public double DefaultTemperature { get; set; } = 0.35;
public double DefaultTopP { get; set; } = 0.95;
public int DefaultMaxTokens { get; set; } = 16384;
}
///
/// AI 分析提示词配置。
///
public class AiPromptSettings
{
public bool UseCustomSystemPrompt { get; set; }
public string CustomSystemPrompt { get; set; } = string.Empty;
public string AdditionalRules { get; set; } = string.Empty;
}
///
/// 模型条目
///
public class AiModel
{
public string ModelId { get; set; } = string.Empty;
public string? DisplayName { get; set; }
public bool IsStream { get; set; }
public int ContextLength { get; set; } // 0 表示未知
///
/// 一次请求的总 token 软上限(prompt + 输出/推理余量)。
/// null 或 0 表示使用档位默认值:≥1M 上下文 → 160K;200K~256K → 100K;更小 → 0(不支持长录像)。
///
public int? ContextBudget { get; set; }
public Dictionary ExtraParameters { get; set; } = [];
///
/// 构建最终请求参数(合并 Provider 默认值、模型特有参数和运行时覆盖)
///
public Dictionary BuildRequestParams(
AiProvider provider,
double? temperatureOverride = null,
double? topPOverride = null,
int? maxTokensOverride = null)
{
var parameters = new Dictionary
{
["model"] = ModelId,
["temperature"] = temperatureOverride ?? provider.DefaultTemperature,
["top_p"] = topPOverride ?? provider.DefaultTopP,
["max_tokens"] = maxTokensOverride ?? provider.DefaultMaxTokens,
["stream"] = IsStream
};
foreach (var kv in this.ExtraParameters)
{
parameters[kv.Key] = kv.Value;
}
return parameters;
}
}
///
/// 每次请求前的动态配置上下文(当前选中的 Provider 和 Model)
///
public record AiRequestContext(AiProvider Provider, AiModel Model)
{
public Dictionary BuildRequestParams(
double? temperatureOverride = null,
double? topPOverride = null,
int? maxTokensOverride = null)
{
return Model.BuildRequestParams(Provider,
temperatureOverride, topPOverride, maxTokensOverride);
}
}
///
/// 内置已知模型信息(提供商无关,纯模型参数模板)
///
public static class KnownModels
{
public const int SimilarityThreshold = 80;
public static int GetSimilarity(string sourceModelId, string targetModelId)
{
// prefer exact match
if (sourceModelId.Equals(targetModelId, StringComparison.OrdinalIgnoreCase))
{
return 100;
}
// match the part after slash, e.g. "deepseek-ai/deepseek-v4-flash" vs "deepseek-v4-flash"
// if last part matches, return 90
var sourceModelIdLastPart = sourceModelId.Split('/').LastOrDefault() ?? sourceModelId;
var targetModelIdLastPart = targetModelId.Split('/').LastOrDefault() ?? targetModelId;
if (sourceModelIdLastPart.Equals(targetModelIdLastPart, StringComparison.OrdinalIgnoreCase))
{
return 90;
}
return 0;
}
///
/// 返回一组已知模型,包含正确的 ExtraParameters。
/// 调用方可按需复制到 Provider 的 Models 列表中。
///
public static List GetAll()
{
return
[
// DeepSeek 官方
new()
{
ModelId = "deepseek-v4-flash",
DisplayName = "DeepSeek V4 Flash",
IsStream = true,
ContextLength = 1_000_000,
ExtraParameters = new()
{
["thinking"] = new { type = "enabled" },
["reasoning_effort"] = "high"
}
},
new()
{
ModelId = "deepseek-v4-pro",
DisplayName = "DeepSeek V4 Pro",
IsStream = true,
ContextLength = 1_000_000,
ExtraParameters = new()
{
["thinking"] = new { type = "enabled" },
["reasoning_effort"] = "high"
}
},
// NVIDIA NIM 上的 DeepSeek 模型
new()
{
ModelId = "deepseek-ai/deepseek-v4-flash",
DisplayName = "DeepSeek V4 Flash (NIM)",
IsStream = true,
ContextLength = 1_000_000,
ExtraParameters = new()
{
// ["chat_template_kwargs"] = new { thinking = true },
["thinking"] = new { type = "enabled" },
["reasoning_effort"] = "high",
}
},
new()
{
ModelId = "deepseek-ai/deepseek-v4-pro",
DisplayName = "DeepSeek V4 Pro (NIM)",
IsStream = true,
ContextLength = 1_000_000,
ExtraParameters = new()
{
// ["chat_template_kwargs"] = new { thinking = true },
["thinking"] = new { type = "enabled" },
["reasoning_effort"] = "high",
}
},
// NVIDIA Nemotron
new()
{
ModelId = "nvidia/nemotron-3-super-120b-a12b",
DisplayName = "Nemotron Super 120B (NIM)",
IsStream = true,
ContextLength = 1_000_000,
ExtraParameters = new()
{
["reasoning_budget"] = 16384
}
},
// Minimax
new()
{
ModelId = "minimaxai/minimax-m3",
DisplayName = "MiniMax-M3 (NIM)",
IsStream = false,
ContextLength = 1_000_000,
ExtraParameters = []
},
// Kimi
new()
{
ModelId = "moonshotai/kimi-k2.6",
DisplayName = "Kimi-K2.6 (NIM)",
IsStream = false,
ContextLength = 256_000,
ExtraParameters = []
},
// Google DiffusionGemma
new()
{
ModelId = "google/diffusiongemma-26b-a4b-it",
DisplayName = "DiffusionGemma 26B A4B IT (NIM)",
IsStream = false,
ContextLength = 250_000,
ExtraParameters = new()
{
["chat_template_kwargs"] = new { enable_thinking = true },
}
},
// OpenAI 兼容
new()
{
ModelId = "openai/gpt-oss-120b",
DisplayName = "GPT OSS 120B (NIM)",
IsStream = true,
ContextLength = 128_000,
ExtraParameters = new()
{
["reasoning_effort"] = "medium"
}
}
];
}
}
///
/// 从 OpenAI 兼容的 /v1/models 端点获取可用模型 ID 列表
///
public static class AiModelFetcher
{
public static async Task> FetchModelsAsync(
string baseUrl, string apiKey)
{
baseUrl = baseUrl.TrimEnd('/') + "/";
var models = new List();
using var client = new HttpClient();
var request = new HttpRequestMessage(
HttpMethod.Get, new Uri(new(baseUrl), "models"));
request.Headers.Add("Authorization", $"Bearer {apiKey}");
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
throw new Exception(
$"获取模型列表失败: HTTP {(int)response.StatusCode}");
}
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("data", out var dataArray))
{
foreach (var item in dataArray.EnumerateArray())
{
if (item.TryGetProperty("id", out var idProp))
{
var id = idProp.GetString();
if (!string.IsNullOrEmpty(id))
{
models.Add(id!);
}
}
}
}
return models;
}
}
///
/// 全局 AI 配置(多个 Provider)的持久化管理
///
public class AiSettings
{
public List Providers { get; set; } = [];
public AiPromptSettings Prompt { get; set; } = new();
// 以下两个不持久化,由 UI 层维护当前选中项
[System.Text.Json.Serialization.JsonIgnore]
public int CurrentProviderIndex { get; set; }
private static readonly string ConfigPath = Path.Combine(
AppContext.BaseDirectory,
"AnotherReplayReader.ai_settings.json");
public static AiSettings Load()
{
try
{
if (File.Exists(ConfigPath))
{
var json = File.ReadAllText(ConfigPath);
var settings = JsonSerializer.Deserialize(json);
if (settings is { } value && value.Providers.Count > 0)
{
value.Prompt ??= new AiPromptSettings();
return settings;
}
}
}
catch (Exception ex)
{
// 返回默认配置
Debug.Instance.DebugMessage += $"加载 AI 配置失败: {ex}\r\n";
}
// 返回默认配置:包含两个常用 Provider,各附一个内置模型
var defaults = new AiSettings();
var nimProvider = new AiProvider
{
Name = "NVIDIA NIM",
BaseUrl = "https://integrate.api.nvidia.com/v1",
ApiKey = "",
Models =
[
KnownModels.GetAll().First(m => m.ModelId == "deepseek-ai/deepseek-v4-flash")
]
};
var deepseekProvider = new AiProvider
{
Name = "DeepSeek 官方",
BaseUrl = "https://api.deepseek.com",
ApiKey = "",
Models =
[
KnownModels.GetAll().First(m => m.ModelId == "deepseek-v4-flash")
]
};
defaults.Providers.Add(nimProvider);
defaults.Providers.Add(deepseekProvider);
return defaults;
}
public void Save()
{
var dir = Path.GetDirectoryName(ConfigPath);
if (dir is not null)
{
Directory.CreateDirectory(dir);
}
var json = JsonSerializer.Serialize(this, new JsonSerializerOptions
{
WriteIndented = true
});
File.WriteAllText(ConfigPath, json);
}
}
}