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/:id/ai-sessions", h.list) router.POST("/projects/:id/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, "id") 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 会话操作失败,请稍后重试") } }