feat: 更新项目代码

This commit is contained in:
2026-04-01 22:03:57 +08:00
parent 1c81d4e6ea
commit 284b5a5260
53 changed files with 6240 additions and 22665 deletions

View File

@@ -2,6 +2,7 @@ package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
@@ -75,8 +76,8 @@ func loadRuntimeDeepSeek() (apiBase, apiKey, defModel string, ok bool) {
return "", "", "", false
}
func CallDeepSeek(messages []ChatMessage, model string, maxRetries int) (string, error) {
// 首先尝试从SiteAIRuntime读取配置向后兼容
// openDeepSeekChatURL 解析 DeepSeek 兼容 /chat/completions 的完整 URL、密钥与最终落库模型名
func openDeepSeekChatURL(model string) (fullURL, apiKey, resolvedModel string, err error) {
if base, key, defModel, ok := loadRuntimeDeepSeek(); ok {
if model == "" {
model = defModel
@@ -84,65 +85,74 @@ func CallDeepSeek(messages []ChatMessage, model string, maxRetries int) (string,
if model == "" {
model = "deepseek-chat"
}
url := strings.TrimSuffix(base, "/") + "/chat/completions"
return callOpenAICompatible(url, key, model, messages, maxRetries, 90*time.Second)
return strings.TrimSuffix(base, "/") + "/chat/completions", key, model, nil
}
// 从新的AI配置表读取
if apiKey, apiBase, defaultModel, models, ok := loadAIConfig("deepseek"); ok {
if model == "" {
model = defaultModel
}
if model == "" {
model = "deepseek-chat"
}
// 验证模型是否在允许列表中
if len(models) > 0 {
allowed := false
for _, m := range models {
if m == model {
allowed = true
break
}
}
if !allowed {
model = models[0] // 使用第一个允许的模型
apiKey, apiBase, defaultModel, models, ok := loadAIConfig("deepseek")
if !ok {
return "", "", "", fmt.Errorf("DeepSeek配置未设置请在管理员后台配置API Key和Base URL")
}
if model == "" {
model = defaultModel
}
if model == "" {
model = "deepseek-chat"
}
if len(models) > 0 {
allowed := false
for _, m := range models {
if m == model {
allowed = true
break
}
}
url := strings.TrimSuffix(apiBase, "/") + "/chat/completions"
return callOpenAICompatible(url, apiKey, model, messages, maxRetries, 90*time.Second)
if !allowed {
model = models[0]
}
}
return strings.TrimSuffix(apiBase, "/") + "/chat/completions", apiKey, model, nil
}
return "", fmt.Errorf("DeepSeek配置未设置请在管理员后台配置API Key和Base URL")
func CallDeepSeek(messages []ChatMessage, model string, maxRetries int) (string, error) {
urlStr, key, m, err := openDeepSeekChatURL(model)
if err != nil {
return "", err
}
return callOpenAICompatible(urlStr, key, m, messages, maxRetries, 90*time.Second)
}
// openKimiChatURL 解析 Kimi /v1/chat/completions
func openKimiChatURL(model string) (fullURL, apiKey, resolvedModel string, err error) {
apiKey, apiBase, defaultModel, models, ok := loadAIConfig("kimi")
if !ok {
return "", "", "", fmt.Errorf("Kimi配置未设置请在管理员后台配置API Key和Base URL")
}
if model == "" {
model = defaultModel
}
if model == "" {
model = "kimi-k2-0905-preview"
}
if len(models) > 0 {
allowed := false
for _, m := range models {
if m == model {
allowed = true
break
}
}
if !allowed {
model = models[0]
}
}
return strings.TrimSuffix(apiBase, "/") + "/v1/chat/completions", apiKey, model, nil
}
func CallKimi(messages []ChatMessage, model string) (string, error) {
// 从新的AI配置表读取
if apiKey, apiBase, defaultModel, models, ok := loadAIConfig("kimi"); ok {
if model == "" {
model = defaultModel
}
if model == "" {
model = "kimi-k2-0905-preview"
}
// 验证模型是否在允许列表中
if len(models) > 0 {
allowed := false
for _, m := range models {
if m == model {
allowed = true
break
}
}
if !allowed {
model = models[0] // 使用第一个允许的模型
}
}
url := strings.TrimSuffix(apiBase, "/") + "/v1/chat/completions"
return callOpenAICompatible(url, apiKey, model, messages, 1, 30*time.Second)
urlStr, key, m, err := openKimiChatURL(model)
if err != nil {
return "", err
}
return "", fmt.Errorf("Kimi配置未设置请在管理员后台配置API Key和Base URL")
return callOpenAICompatible(urlStr, key, m, messages, 1, 30*time.Second)
}
func callOpenAICompatible(url, apiKey, model string, messages []ChatMessage, maxRetries int, timeout time.Duration) (string, error) {
@@ -211,3 +221,49 @@ func CallAI(provider, model string, messages []ChatMessage) (string, error) {
return "", fmt.Errorf("不支持的AI提供商: %s目前支持的提供商: deepseek, kimi", provider)
}
}
// OpenAIChatStream 向上游发起 stream:true 的请求;返回的 ReadCloser 需由调用方 Close。statusCode 非 200 时 body 已读完并关闭rc 为 nil。
func OpenAIChatStream(ctx context.Context, provider, model string, messages []ChatMessage) (rc io.ReadCloser, statusCode int, err error) {
var urlStr, apiKey, m string
switch provider {
case "deepseek":
urlStr, apiKey, m, err = openDeepSeekChatURL(model)
case "kimi":
urlStr, apiKey, m, err = openKimiChatURL(model)
default:
return nil, 0, fmt.Errorf("不支持的AI提供商: %s", provider)
}
if err != nil {
return nil, 0, err
}
streamBody := map[string]interface{}{
"model": m,
"messages": messages,
"temperature": 0.7,
"max_tokens": 2000,
"stream": true,
}
bodyBytes, jerr := json.Marshal(streamBody)
if jerr != nil {
return nil, 0, fmt.Errorf("序列化请求失败: %w", jerr)
}
req, rerr := http.NewRequestWithContext(ctx, "POST", urlStr, bytes.NewReader(bodyBytes))
if rerr != nil {
return nil, 0, rerr
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "text/event-stream")
client := &http.Client{}
resp, derr := client.Do(req)
if derr != nil {
return nil, 0, derr
}
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
resp.Body.Close()
return nil, resp.StatusCode, fmt.Errorf("%s", strings.TrimSpace(string(b)))
}
return resp.Body, http.StatusOK, nil
}