Files
geo/server/internal/tenant/transport/knowledge_handler.go
T
root 39ef1a6b7f feat(knowledge): add retry endpoint and 20-char text name limit
Add POST /api/tenant/knowledge/items/retry/:id to re-queue failed parse
tasks, plus a 20-rune cap on text item names with matching frontend
validation. Wires the retry button into the knowledge table with a
colored status tag for clearer state feedback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:19:08 +08:00

221 lines
5.6 KiB
Go

package transport
import (
"io"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/geo-platform/tenant-api/internal/bootstrap"
"github.com/geo-platform/tenant-api/internal/shared/response"
"github.com/geo-platform/tenant-api/internal/tenant/app"
)
type KnowledgeHandler struct {
svc *app.KnowledgeService
}
func NewKnowledgeHandler(a *bootstrap.App) *KnowledgeHandler {
return &KnowledgeHandler{
svc: app.NewKnowledgeService(
a.DB,
a.RetrievalProvider,
a.VectorStore,
a.ObjectStorage,
a.LLM,
a.Logger,
a.Config().Retrieval,
a.Config().LLM,
a.Config().Generation.QueueSize,
a.Config().Generation.WorkerConcurrency,
).WithConfigProvider(a.ConfigStore),
}
}
func (h *KnowledgeHandler) ListGroups(c *gin.Context) {
data, err := h.svc.ListGroups(c.Request.Context())
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *KnowledgeHandler) CreateGroup(c *gin.Context) {
var req app.KnowledgeGroupRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error()))
return
}
data, err := h.svc.CreateGroup(c.Request.Context(), req)
if err != nil {
response.Error(c, err)
return
}
response.SuccessWithStatus(c, http.StatusCreated, data)
}
func (h *KnowledgeHandler) UpdateGroup(c *gin.Context) {
groupID, err := strconv.ParseInt(c.Param("gid"), 10, 64)
if err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_id", "knowledge group id must be a number"))
return
}
var req app.KnowledgeGroupRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error()))
return
}
if err := h.svc.UpdateGroup(c.Request.Context(), groupID, req); err != nil {
response.Error(c, err)
return
}
response.Success(c, nil)
}
func (h *KnowledgeHandler) DeleteGroup(c *gin.Context) {
groupID, err := strconv.ParseInt(c.Param("gid"), 10, 64)
if err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_id", "knowledge group id must be a number"))
return
}
if err := h.svc.DeleteGroup(c.Request.Context(), groupID); err != nil {
response.Error(c, err)
return
}
response.Success(c, nil)
}
func (h *KnowledgeHandler) ListItems(c *gin.Context) {
var groupID *int64
if raw := c.Query("group_id"); raw != "" {
parsed, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_group_id", "group id must be a number"))
return
}
groupID = &parsed
}
data, err := h.svc.ListItems(c.Request.Context(), groupID)
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *KnowledgeHandler) GetItemDetail(c *gin.Context) {
itemID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_id", "knowledge item id must be a number"))
return
}
data, err := h.svc.GetItemDetail(c.Request.Context(), itemID)
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *KnowledgeHandler) CreateTextItem(c *gin.Context) {
var req app.KnowledgeTextItemRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error()))
return
}
data, err := h.svc.CreateTextItem(c.Request.Context(), req)
if err != nil {
response.Error(c, err)
return
}
response.SuccessWithStatus(c, http.StatusCreated, data)
}
func (h *KnowledgeHandler) CreateURLItem(c *gin.Context) {
var req app.KnowledgeURLItemRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error()))
return
}
data, err := h.svc.CreateURLItem(c.Request.Context(), req)
if err != nil {
response.Error(c, err)
return
}
response.SuccessWithStatus(c, http.StatusCreated, data)
}
func (h *KnowledgeHandler) CreateFileItem(c *gin.Context) {
groupID, err := strconv.ParseInt(c.PostForm("group_id"), 10, 64)
if err != nil || groupID <= 0 {
response.Error(c, response.ErrBadRequest(40001, "invalid_group_id", "group id must be a number"))
return
}
name := c.PostForm("name")
fileHeader, err := c.FormFile("file")
if err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_file", "file is required"))
return
}
file, err := fileHeader.Open()
if err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_file", "file cannot be opened"))
return
}
defer file.Close()
content, err := io.ReadAll(file)
if err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_file", "file cannot be read"))
return
}
data, err := h.svc.CreateFileItem(c.Request.Context(), groupID, name, fileHeader.Filename, content)
if err != nil {
response.Error(c, err)
return
}
response.SuccessWithStatus(c, http.StatusCreated, data)
}
func (h *KnowledgeHandler) RetryItem(c *gin.Context) {
itemID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_id", "knowledge item id must be a number"))
return
}
data, err := h.svc.RetryItem(c.Request.Context(), itemID)
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *KnowledgeHandler) DeleteItem(c *gin.Context) {
itemID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.Error(c, response.ErrBadRequest(40001, "invalid_id", "knowledge item id must be a number"))
return
}
if err := h.svc.DeleteItem(c.Request.Context(), itemID); err != nil {
response.Error(c, err)
return
}
response.Success(c, nil)
}