feat: align controlled AI sessions and MVP controls
This commit is contained in:
120
backend/internal/logic/ai/handlers.go
Normal file
120
backend/internal/logic/ai/handlers.go
Normal file
@@ -0,0 +1,120 @@
|
||||
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 会话操作失败,请稍后重试")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user