From 2d7044b57eb50119ac4273d4899bf0ccd2d93783 Mon Sep 17 00:00:00 2001 From: Haruko386 Date: Mon, 1 Jun 2026 11:22:08 +0800 Subject: [PATCH] feat[Go] implement api/v1/thumbnails API (#15416) ### What problem does this PR solve? As title ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) - [x] New Feature (non-breaking change which adds functionality --- internal/handler/document.go | 57 ++++++++++++++++++++++++++++++++++++ internal/router/router.go | 5 ++++ internal/service/document.go | 40 +++++++++++++++++++++++-- 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/internal/handler/document.go b/internal/handler/document.go index 7c1e5ad4ab..369cad5a0e 100644 --- a/internal/handler/document.go +++ b/internal/handler/document.go @@ -18,8 +18,11 @@ package handler import ( "encoding/json" + "errors" "fmt" + "mime" "net/http" + "path/filepath" "ragflow/internal/common" "ragflow/internal/entity" "strconv" @@ -31,6 +34,8 @@ import ( "ragflow/internal/service" ) +var IMG_BASE64_PREFIX = "data:image/png;base64," + // DocumentHandler document handler type DocumentHandler struct { documentService *service.DocumentService @@ -120,6 +125,58 @@ func (h *DocumentHandler) GetDocumentByID(c *gin.Context) { }) } +// GetThumbnail Get thumbnails for documents. +func (h *DocumentHandler) GetThumbnail(c *gin.Context) { + _, errorCode, errorMessage := GetUser(c) + if errorCode != common.CodeSuccess { + jsonError(c, errorCode, errorMessage) + return + } + + id := c.Query("doc_ids") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "error": errors.New("invalid document id"), + }) + return + } + + result, err := h.documentService.GetThumbnail(id) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{ + "error": fmt.Errorf("thumbnail not found"), + }) + return + } + + if result.Thumbnail != nil && *result.Thumbnail != "" { + newThumbURL := fmt.Sprintf("/api/v1/documents/images/%s-%s", result.KbID, *result.Thumbnail) + result.Thumbnail = &newThumbURL + } + + c.JSON(http.StatusOK, gin.H{ + "code": common.CodeSuccess, + "data": map[string]interface{}{result.ID: result.Thumbnail}, + "message": "success", + }) +} + +// GetDocumentImage returns a document image from object storage. +func (h *DocumentHandler) GetDocumentImage(c *gin.Context) { + imageID := c.Param("image_id") + data, err := h.documentService.GetDocumentImage(imageID) + if err != nil { + jsonError(c, common.CodeDataError, "Image not found.") + return + } + + contentType := mime.TypeByExtension(strings.ToLower(filepath.Ext(imageID))) + if contentType == "" { + contentType = "image/JPEG" + } + c.Data(http.StatusOK, contentType, data) +} + // UpdateDocument update document // @Summary Update Document // @Description Update document info diff --git a/internal/router/router.go b/internal/router/router.go index b41ab52042..86eec9ba54 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -116,6 +116,9 @@ func (r *Router) Setup(engine *gin.Engine) { // Register apiNoAuth.POST("/users", r.userHandler.Register) + + // Document images are embedded directly in pages and match Python's public route. + apiNoAuth.GET("/documents/images/:image_id", r.documentHandler.GetDocumentImage) } // Protected routes @@ -411,6 +414,8 @@ func (r *Router) Setup(engine *gin.Engine) { doc.POST("/delete_meta", r.documentHandler.DeleteMeta) // Internal API only for GO } + v1.GET("/thumbnails", r.documentHandler.GetThumbnail) + // Chunk routes chunk := v1.Group("/chunk") { diff --git a/internal/service/document.go b/internal/service/document.go index 8950772a8f..592e553a6c 100644 --- a/internal/service/document.go +++ b/internal/service/document.go @@ -21,8 +21,10 @@ import ( "fmt" "ragflow/internal/common" "ragflow/internal/entity" + "ragflow/internal/storage" "regexp" "sort" + "strings" "time" "ragflow/internal/dao" @@ -102,6 +104,27 @@ type DocumentResponse struct { UpdatedAt string `json:"updated_at"` } +type ThumbnailResponse struct { + ID string `json:"id"` + Thumbnail *string `json:"thumbnail,omitempty"` + KbID string `json:"kb_id"` +} + +// GetDocumentImage retrieves an image object from storage. +func (s *DocumentService) GetDocumentImage(imageID string) ([]byte, error) { + parts := strings.Split(imageID, "-") + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return nil, fmt.Errorf("Image not found.") + } + + storageImpl := storage.GetStorageFactory().GetStorage() + if storageImpl == nil { + return nil, fmt.Errorf("storage not initialized") + } + + return storageImpl.Get(parts[0], parts[1]) +} + // CreateDocument create document func (s *DocumentService) CreateDocument(req *CreateDocumentRequest) (*entity.Document, error) { document := &entity.Document{ @@ -182,6 +205,19 @@ func (s *DocumentService) ListDocuments(page, pageSize int) ([]*DocumentResponse return responses, total, nil } +func (s *DocumentService) GetThumbnail(docID string) (*ThumbnailResponse, error) { + document, err := s.documentDAO.GetByID(docID) + if err != nil { + return nil, err + } + + var result ThumbnailResponse + result.ID = document.ID + result.Thumbnail = document.Thumbnail + result.KbID = document.KbID + return &result, nil +} + // ListDocumentsByDatasetID list documents by knowledge base ID func (s *DocumentService) ListDocumentsByDatasetID(kbID string, page, pageSize int) ([]*entity.DocumentListItem, int64, error) { offset := (page - 1) * pageSize @@ -282,7 +318,7 @@ func (s *DocumentService) ParseDocuments(datasetID, userID string, docIDs []stri } // Send task to message queue - + } common.Info(fmt.Sprintf("parse documents, dataset: %s, documents: %v", datasetID, docIDs)) @@ -425,7 +461,7 @@ func (s *DocumentService) DeleteDocumentAllMetadata(docID string) error { // Build condition to match the document condition := map[string]interface{}{ - "id": docID, + "id": docID, "kb_id": doc.KbID, }