Files
agent/backend/internal/logic/ai/handlers.go

121 lines
3.4 KiB
Go

package ai
import (
"errors"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"senlinai-agent/backend/internal/httpx"
"senlinai-agent/backend/internal/logic/auth"
"senlinai-agent/backend/internal/models"
)
// SessionDTO 是普通项目 AI 会话的公开契约,不携带数据库主键或自动创建对象的 ID。
type SessionDTO struct {
ID string `json:"id"`
ProjectID string `json:"projectId"`
Title string `json:"title"`
Context string `json:"context"`
Status string `json:"status"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type createSessionRequest struct {
Title string `json:"title"`
Context string `json:"context"`
}
// Handler 注册受认证、受项目所有权保护的 AI 会话接口。
type Handler struct {
service *SessionService
}
func NewHandler(service *SessionService) *Handler {
return &Handler{service: service}
}
func (h *Handler) Register(router gin.IRouter) {
router.GET("/projects/:projectId/ai-sessions", h.list)
router.POST("/projects/:projectId/ai-sessions", h.create)
}
func (h *Handler) list(c *gin.Context) {
userID, projectIdentity, ok := aiRequestContext(c)
if !ok {
return
}
sessions, err := h.service.List(userID, projectIdentity)
if err != nil {
writeAIError(c, err)
return
}
items := make([]SessionDTO, 0, len(sessions))
for _, session := range sessions {
items = append(items, sessionDTO(session))
}
c.JSON(http.StatusOK, items)
}
func (h *Handler) create(c *gin.Context) {
userID, projectIdentity, ok := aiRequestContext(c)
if !ok {
return
}
var input createSessionRequest
if err := c.ShouldBindJSON(&input); err != nil {
httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效")
return
}
session, err := h.service.Create(userID, projectIdentity, input.Title, input.Context)
if err != nil {
writeAIError(c, err)
return
}
c.JSON(http.StatusCreated, sessionDTO(*session))
}
func aiRequestContext(c *gin.Context) (uint, string, bool) {
userID, ok := auth.CurrentUserID(c)
if !ok {
httpx.Error(c, http.StatusUnauthorized, "unauthorized", "未登录或登录已失效")
return 0, "", false
}
projectIdentity, ok := httpx.IdentityParam(c, "projectId")
if !ok {
return 0, "", false
}
return userID, projectIdentity, true
}
func sessionDTO(session models.SenlinAgentAISession) SessionDTO {
return SessionDTO{
ID: session.Identity,
ProjectID: session.ProjectIdentity,
Title: session.Title,
Context: session.Context,
Status: aiSessionStatus(session),
CreatedAt: session.CreatedAt.UTC(),
UpdatedAt: session.UpdatedAt.UTC(),
}
}
func writeAIError(c *gin.Context, err error) {
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
httpx.Error(c, http.StatusNotFound, "not_found", "项目不存在或无权访问")
case errors.Is(err, ErrInvalidSession):
httpx.Error(c, http.StatusBadRequest, "invalid_request", "请输入 AI 会话标题")
case errors.Is(err, ErrAIRateLimited):
httpx.Error(c, http.StatusTooManyRequests, "ai_rate_limited", "AI 请求过于频繁,请稍后重试")
case errors.Is(err, ErrAIKeyMissing):
httpx.Error(c, http.StatusServiceUnavailable, "ai_key_missing", "尚未配置可用的 AI 密钥")
default:
log.Printf("ai session request failed: %v", err)
httpx.Error(c, http.StatusInternalServerError, "internal_error", "AI 会话操作失败,请稍后重试")
}
}