121 lines
3.4 KiB
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/: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 会话操作失败,请稍后重试")
|
|
}
|
|
}
|