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 handler
import (
"fmt"
"io"
"log"
"net/http"
"strings"
@@ -51,8 +52,14 @@ func validateTextLen(text string, label string) (string, error) {
return t, nil
}
// POST /api/aimodelapp/chat
func (h *AIModelHandler) Chat(c *gin.Context) {
type aiModelChatInput struct {
Messages []service.ChatMessage
Provider string
Model string
}
// bindAIModelChat 绑定并校验统一 chat / chat/stream 请求体;失败时已写入 JSON 响应。
func bindAIModelChat(c *gin.Context) (aiModelChatInput, bool) {
var req struct {
Messages []service.ChatMessage `json:"messages"`
Provider string `json:"provider"`
@@ -60,7 +67,7 @@ func (h *AIModelHandler) Chat(c *gin.Context) {
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求数据为空"})
return
return aiModelChatInput{}, false
}
if req.Provider == "" {
req.Provider = "deepseek"
@@ -68,44 +75,99 @@ func (h *AIModelHandler) Chat(c *gin.Context) {
if req.Model == "" {
req.Model = "deepseek-chat"
}
// 模型白名单校验
if models, ok := allowedModels[req.Provider]; !ok || !models[req.Model] {
c.JSON(http.StatusBadRequest, gin.H{"error": "不支持的模型"})
return
return aiModelChatInput{}, false
}
if len(req.Messages) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "消息内容不能为空"})
return
return aiModelChatInput{}, false
}
if len(req.Messages) > maxChatMsgCount {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("消息数量不能超过 %d 条", maxChatMsgCount)})
return
return aiModelChatInput{}, false
}
// 校验每条消息的长度
for _, m := range req.Messages {
if len(m.Content) > maxInputLen {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("单条消息长度不能超过 %d 字符", maxInputLen)})
return
return aiModelChatInput{}, false
}
}
return aiModelChatInput{Messages: req.Messages, Provider: req.Provider, Model: req.Model}, true
}
content, err := service.CallAI(req.Provider, req.Model, req.Messages)
// POST /api/aimodelapp/chat
func (h *AIModelHandler) Chat(c *gin.Context) {
in, ok := bindAIModelChat(c)
if !ok {
return
}
content, err := service.CallAI(in.Provider, in.Model, in.Messages)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"content": content,
"provider": req.Provider,
"model": req.Model,
"provider": in.Provider,
"model": in.Model,
"timestamp": time.Now().Format(time.RFC3339),
})
}
// POST /api/aimodelapp/chat/stream — SSE 透传上游 OpenAI 兼容流data: {...}\\n\\n / [DONE]
func (h *AIModelHandler) ChatStream(c *gin.Context) {
in, ok := bindAIModelChat(c)
if !ok {
return
}
upstream, statusCode, err := service.OpenAIChatStream(c.Request.Context(), in.Provider, in.Model, in.Messages)
if err != nil {
if statusCode == 0 {
c.JSON(http.StatusInternalServerError, gin.H{"error": safeAIError(err)})
return
}
c.JSON(http.StatusBadGateway, gin.H{"error": safeAIError(err)})
return
}
defer upstream.Close()
hdr := c.Writer.Header()
hdr.Set("Content-Type", "text/event-stream; charset=utf-8")
hdr.Set("Cache-Control", "no-cache")
hdr.Set("Connection", "keep-alive")
hdr.Set("X-Accel-Buffering", "no")
c.Status(http.StatusOK)
flusher, ok := c.Writer.(http.Flusher)
if !ok {
_, _ = io.Copy(c.Writer, upstream)
return
}
buf := make([]byte, 8192)
for {
select {
case <-c.Request.Context().Done():
return
default:
}
n, rerr := upstream.Read(buf)
if n > 0 {
if _, werr := c.Writer.Write(buf[:n]); werr != nil {
return
}
flusher.Flush()
}
if rerr == io.EOF {
return
}
if rerr != nil {
return
}
}
}
// POST /api/aimodelapp/name-analysis
func (h *AIModelHandler) NameAnalysis(c *gin.Context) {
var req struct {