Files
yawan f7fc992f57
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
2026-09-22 16:19:06 +08:00

206 lines
7.0 KiB
Go

package handler
import (
"errors"
"io"
"net/http"
"path/filepath"
"strconv"
"strings"
"github.com/Tencent/WeKnora/internal/application/service"
apperrors "github.com/Tencent/WeKnora/internal/errors"
"github.com/Tencent/WeKnora/internal/logger"
"github.com/Tencent/WeKnora/internal/types"
"github.com/gin-gonic/gin"
)
type DigitalHumanHandler struct{ service *service.DigitalHumanService }
func NewDigitalHumanHandler(svc *service.DigitalHumanService) *DigitalHumanHandler {
return &DigitalHumanHandler{service: svc}
}
func digitalHumanHTTPError(c *gin.Context, err error) {
var inputError *types.DigitalHumanInputError
switch {
case errors.Is(err, types.ErrDigitalHumanNotFound):
c.Error(apperrors.NewNotFoundError(err.Error()))
case errors.Is(err, types.ErrDigitalHumanIdentity):
c.Error(apperrors.NewUnauthorizedError(err.Error()))
case errors.Is(err, types.ErrDigitalHumanConflict):
c.Error(apperrors.NewConflictError(err.Error()))
case errors.As(err, &inputError):
c.Error(apperrors.NewBadRequestError(inputError.Error()))
default:
logger.ErrorWithFields(c.Request.Context(), err, map[string]interface{}{"feature": "digital_human"})
c.Error(apperrors.NewInternalServerError("数字人操作失败,请稍后重试"))
}
}
func (h *DigitalHumanHandler) List(c *gin.Context) {
offset, _ := strconv.Atoi(c.Query("offset"))
limit, _ := strconv.Atoi(c.Query("limit"))
data, err := h.service.List(c.Request.Context(), offset, limit)
if err != nil {
digitalHumanHTTPError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": data})
}
func (h *DigitalHumanHandler) Get(c *gin.Context) {
data, err := h.service.Get(c.Request.Context(), c.Param("id"))
if err != nil {
digitalHumanHTTPError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": data})
}
func (h *DigitalHumanHandler) Create(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 2<<20)
var input service.DigitalHumanInput
if err := c.ShouldBindJSON(&input); err != nil {
c.Error(apperrors.NewBadRequestError("请填写有效的数字人资料"))
return
}
data, err := h.service.Create(c.Request.Context(), input)
if err != nil {
digitalHumanHTTPError(c, err)
return
}
c.JSON(http.StatusCreated, gin.H{"success": true, "data": data})
}
func (h *DigitalHumanHandler) Update(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 2<<20)
var input service.DigitalHumanInput
if err := c.ShouldBindJSON(&input); err != nil {
c.Error(apperrors.NewBadRequestError("请填写有效的数字人资料"))
return
}
data, err := h.service.Update(c.Request.Context(), c.Param("id"), input)
if err != nil {
digitalHumanHTTPError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": data})
}
func (h *DigitalHumanHandler) Delete(c *gin.Context) {
if err := h.service.Delete(c.Request.Context(), c.Param("id")); err != nil {
digitalHumanHTTPError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
}
func (h *DigitalHumanHandler) Review(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 32<<10)
var input struct {
Version int `json:"version"`
Approve *bool `json:"approve"`
Note string `json:"note"`
}
if err := c.ShouldBindJSON(&input); err != nil || input.Approve == nil || input.Version < 1 {
c.Error(apperrors.NewBadRequestError("请选择审核版本和通过或退回"))
return
}
data, err := h.service.Review(c.Request.Context(), c.Param("id"), input.Version, *input.Approve, input.Note)
if err != nil {
digitalHumanHTTPError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": data})
}
func (h *DigitalHumanHandler) Bind(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 4096)
var input struct {
AgentID string `json:"agent_id"`
}
if err := c.ShouldBindJSON(&input); err != nil || input.AgentID == "" {
c.Error(apperrors.NewBadRequestError("请选择 Agent"))
return
}
if err := h.service.Bind(c.Request.Context(), c.Param("id"), input.AgentID); err != nil {
digitalHumanHTTPError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
}
func (h *DigitalHumanHandler) Unbind(c *gin.Context) {
if err := h.service.Unbind(c.Request.Context(), c.Param("id"), c.Param("agent_id")); err != nil {
digitalHumanHTTPError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
}
func (h *DigitalHumanHandler) Distill(c *gin.Context) {
var input service.DigitalHumanDistillInput
if strings.HasPrefix(c.ContentType(), "multipart/form-data") {
// Authorize before accepting or parsing potentially large private documents.
if err := h.service.CheckOwner(c.Request.Context(), c.Param("id")); err != nil {
digitalHumanHTTPError(c, err)
return
}
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, service.DigitalHumanUploadLimit)
if err := c.Request.ParseMultipartForm(4 << 20); err != nil {
c.Error(apperrors.NewBadRequestError("上传材料总大小不能超过50MB"))
return
}
defer c.Request.MultipartForm.RemoveAll()
if len(c.Request.MultipartForm.File["files"]) > service.DigitalHumanFileCountLimit {
c.Error(apperrors.NewBadRequestError("每次最多上传99份材料"))
return
}
input.Advice, input.ModelID = c.PostForm("advice"), c.PostForm("model_id")
input.Revision, _ = strconv.ParseInt(c.PostForm("revision"), 10, 64)
if text := strings.TrimSpace(c.PostForm("text")); text != "" {
input.Sources = append(input.Sources, types.DigitalHumanSource{Name: "补充原稿", Text: text})
}
for _, header := range c.Request.MultipartForm.File["files"] {
if !service.DigitalHumanUploadSupported(header.Filename) {
c.Error(apperrors.NewBadRequestError("支持 Word、PDF、PPT、MD、TXT、SRT、VTT、RTF、ODT、CSV、JSON 和 HTML 材料"))
return
}
file, err := header.Open()
if err != nil {
c.Error(apperrors.NewBadRequestError("无法读取上传材料"))
return
}
data, readErr := io.ReadAll(io.LimitReader(file, service.DigitalHumanFileLimit+1))
file.Close()
if readErr != nil || len(data) > service.DigitalHumanFileLimit || len(data) == 0 {
c.Error(apperrors.NewBadRequestError("单份材料须有内容且不能超过20MB"))
return
}
input.Files = append(input.Files, service.DigitalHumanUpload{Name: filepath.Base(header.Filename), Data: data})
}
} else {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 2<<20)
if err := c.ShouldBindJSON(&input); err != nil {
c.Error(apperrors.NewBadRequestError("请提供有效的蒸馏原稿和建议"))
return
}
}
job, err := h.service.Distill(c.Request.Context(), c.Param("id"), input)
if err != nil {
digitalHumanHTTPError(c, err)
return
}
c.JSON(http.StatusAccepted, gin.H{"success": true, "data": job})
}
func (h *DigitalHumanHandler) Job(c *gin.Context) {
job, err := h.service.Job(c.Request.Context(), c.Param("id"), c.Param("job_id"))
if err != nil {
digitalHumanHTTPError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": job})
}