Files

148 lines
5.1 KiB
Go
Raw Permalink Normal View History

2026-08-14 13:17:03 +08:00
package handler
import (
"context"
"time"
"github.com/gin-gonic/gin"
"nl-game-api-gin/internal/ai"
"nl-game-api-gin/internal/config"
"nl-game-api-gin/internal/model"
"nl-game-api-gin/internal/service"
"nl-game-api-gin/pkg/resp"
)
// aiProviderConf 单个提供方的当前生效配置(后台展示用)
type aiProviderConf struct {
APIKey string `json:"api_key"` // 生效中的 API Key仅超管可见
BaseURL string `json:"base_url"` // 生效中的接口地址
Model string `json:"model"` // 生效中的模型名
KeySource string `json:"key_source"` // Key 来源:后台配置/配置文件/未配置
}
// effectiveAIConf 计算某提供方的生效配置:数据库非空项覆盖 config.yaml
func effectiveAIConf(provider string) aiProviderConf {
var base config.LLMConf
var kKey, kBase, kModel string
if provider == ai.ProviderSpark {
base, kKey, kBase, kModel = config.C.AI.Spark, model.ConfKeyAISparkKey, model.ConfKeyAISparkBase, model.ConfKeyAISparkModel
} else {
base, kKey, kBase, kModel = config.C.AI.DeepSeek, model.ConfKeyAIDeepSeekKey, model.ConfKeyAIDeepSeekBase, model.ConfKeyAIDeepSeekModel
}
out := aiProviderConf{APIKey: base.APIKey, BaseURL: base.BaseURL, Model: base.Model, KeySource: "配置文件"}
if v := service.GetConfig(kKey, ""); v != "" {
out.APIKey, out.KeySource = v, "后台配置"
}
if v := service.GetConfig(kBase, ""); v != "" {
out.BaseURL = v
}
if v := service.GetConfig(kModel, ""); v != "" {
out.Model = v
}
if out.APIKey == "" {
out.KeySource = "未配置"
}
return out
}
// AdminAIConfig 后台查看当前全站AI模型选择、两家大模型的生效配置与获取指引
func AdminAIConfig(c *gin.Context) {
resp.OK(c, gin.H{
"provider": ai.GlobalProvider(), // 全站统一的对战AI提供方玩家前台只选难度
"spark": effectiveAIConf(ai.ProviderSpark),
"deepseek": effectiveAIConf(ai.ProviderDeepSeek),
// 申请指引(前端展示)
"guide": gin.H{
"spark": "讯飞开放平台 console.xfyun.cn 创建应用后在「Spark Lite → HTTP 服务接口认证信息」复制 APIPassword 填入即可Lite 模型免费",
"deepseek": "DeepSeek 开放平台 platform.deepseek.com 注册后创建 API Keysk- 开头),按 token 计费",
},
})
}
// aiSaveReq 保存AI配置请求体保存后立即生效无需重启
type aiSaveReq struct {
Provider *string `json:"provider"` // 全站AI提供方rule/spark/deepseeknil=不修改)
Spark *struct {
APIKey *string `json:"api_key"` // 留空=清除后台配置回退到配置文件
BaseURL *string `json:"base_url"` // 接口地址
Model *string `json:"model"` // 模型名
} `json:"spark"`
DeepSeek *struct {
APIKey *string `json:"api_key"`
BaseURL *string `json:"base_url"`
Model *string `json:"model"`
} `json:"deepseek"`
}
// AdminSaveAIConfig 后台:保存大模型 API Key 等配置到数据库(覆盖 config.yaml
func AdminSaveAIConfig(c *gin.Context) {
var req aiSaveReq
if err := c.ShouldBindJSON(&req); err != nil {
resp.Fail(c, "参数有误")
return
}
// 切换全站AI模型新建房间立即生效已开局房间不受影响
if req.Provider != nil {
p := *req.Provider
if p != ai.ProviderRule && p != ai.ProviderSpark && p != ai.ProviderDeepSeek {
resp.Fail(c, "AI提供方只能是 rule / spark / deepseek")
return
}
service.SetConfig(model.ConfKeyAIProvider, p)
}
if req.Spark != nil {
saveIfSet(model.ConfKeyAISparkKey, req.Spark.APIKey)
saveIfSet(model.ConfKeyAISparkBase, req.Spark.BaseURL)
saveIfSet(model.ConfKeyAISparkModel, req.Spark.Model)
}
if req.DeepSeek != nil {
saveIfSet(model.ConfKeyAIDeepSeekKey, req.DeepSeek.APIKey)
saveIfSet(model.ConfKeyAIDeepSeekBase, req.DeepSeek.BaseURL)
saveIfSet(model.ConfKeyAIDeepSeekModel, req.DeepSeek.Model)
}
resp.OK(c, gin.H{
"provider": ai.GlobalProvider(),
"spark": effectiveAIConf(ai.ProviderSpark),
"deepseek": effectiveAIConf(ai.ProviderDeepSeek),
})
}
// saveIfSet 请求里带了该字段才写库nil=不动,空串=清除让其回退配置文件)
func saveIfSet(key string, val *string) {
if val != nil {
service.SetConfig(key, *val)
}
}
// aiTestReq 连通性测试请求体
type aiTestReq struct {
Provider string `json:"provider" binding:"required,oneof=spark deepseek"` // 要测试的提供方
}
// AdminTestAI 后台:向大模型发一条测试消息,验证 Key 是否可用并测量延迟
func AdminTestAI(c *gin.Context) {
var req aiTestReq
if err := c.ShouldBindJSON(&req); err != nil {
resp.Fail(c, "参数有误")
return
}
client := ai.ClientForTest(req.Provider)
if client == nil {
resp.Fail(c, "该提供方还没有配置 API Key")
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
defer cancel()
start := time.Now()
reply, err := client.Chat(ctx, "你是连通性测试助手。", "请只回复两个字:在线", 0.1)
if err != nil {
resp.Fail(c, "连接失败:"+err.Error())
return
}
resp.OK(c, gin.H{
"reply": reply, // 模型回复内容
"latency_ms": time.Since(start).Milliseconds(), // 往返延迟(毫秒)
})
}