package handler import ( "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/yuxingu/digital-psychology/apps/api/internal/middleware" asksvc "github.com/yuxingu/digital-psychology/apps/api/internal/service/ask" "github.com/yuxingu/digital-psychology/apps/api/pkg/response" ) // AskHandler exposes AI 成长助手 APIs. type AskHandler struct { Svc *asksvc.Service } // Register mounts ask routes. func (h *AskHandler) Register(rg *gin.RouterGroup) { rg.GET("/ask/quota", h.GetQuota) rg.POST("/ask/threads", h.CreateThread) rg.GET("/ask/threads/:id/messages", h.ListMessages) rg.POST("/ask/threads/:id/messages", h.SendMessage) } // GetQuota handles GET /ask/quota. func (h *AskHandler) GetQuota(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } q, err := h.Svc.GetQuota(c.Request.Context(), userID) if err != nil { response.Fail(c, http.StatusInternalServerError, 50000, err.Error()) return } response.OK(c, q) } // CreateThread handles POST /ask/threads. func (h *AskHandler) CreateThread(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } var req struct { ProfileID string `json:"profile_id" binding:"required"` Scene string `json:"scene"` } if err := c.ShouldBindJSON(&req); err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid request") return } pid, err := uuid.Parse(req.ProfileID) if err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid profile_id") return } th, err := h.Svc.CreateThread(c.Request.Context(), userID, asksvc.CreateThreadInput{ ProfileID: pid, Scene: req.Scene, }) if err != nil { response.Fail(c, http.StatusBadRequest, 40010, err.Error()) return } response.OK(c, th) } // ListMessages handles GET /ask/threads/:id/messages. func (h *AskHandler) ListMessages(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } tid, err := uuid.Parse(c.Param("id")) if err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid id") return } items, err := h.Svc.ListMessages(c.Request.Context(), userID, tid) if err != nil { response.Fail(c, http.StatusNotFound, 40410, err.Error()) return } response.OK(c, gin.H{"items": items}) } // SendMessage handles POST /ask/threads/:id/messages. func (h *AskHandler) SendMessage(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } tid, err := uuid.Parse(c.Param("id")) if err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid id") return } var req struct { Content string `json:"content" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid request") return } out, err := h.Svc.SendMessage(c.Request.Context(), userID, tid, req.Content) if err != nil { if asksvc.IsQuotaExhausted(err) { response.Fail(c, http.StatusPaymentRequired, 40210, "问答次数已用完,可开通成长会员获取更多次数") return } response.Fail(c, http.StatusBadRequest, 40011, err.Error()) return } response.OK(c, out) }