l_ai_knowledge/internal/handler/knowledge.go

482 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"knowlege-lsxd/internal/errors"
"knowlege-lsxd/internal/logger"
"knowlege-lsxd/internal/types"
"knowlege-lsxd/internal/types/interfaces"
"github.com/gin-gonic/gin"
)
// KnowledgeHandler processes HTTP requests related to knowledge resources
type KnowledgeHandler struct {
kgService interfaces.KnowledgeService
kbService interfaces.KnowledgeBaseService
}
// NewKnowledgeHandler creates a new knowledge handler instance
func NewKnowledgeHandler(
kgService interfaces.KnowledgeService,
kbService interfaces.KnowledgeBaseService,
) *KnowledgeHandler {
return &KnowledgeHandler{kgService: kgService, kbService: kbService}
}
// validateKnowledgeBaseAccess validates access permissions to a knowledge base
// Returns the knowledge base, the knowledge base ID, and any errors encountered
func (h *KnowledgeHandler) validateKnowledgeBaseAccess(c *gin.Context) (*types.KnowledgeBase, string, error) {
ctx := c.Request.Context()
// Get knowledge base ID from URL path parameter
kbID := c.Param("id")
if kbID == "" {
logger.Error(ctx, "Knowledge base ID is empty")
return nil, "", errors.NewBadRequestError("Knowledge base ID cannot be empty")
}
// Get knowledge base details
kb, err := h.kbService.GetKnowledgeBaseByID(ctx, kbID)
if err != nil {
logger.ErrorWithFields(ctx, err, nil)
return nil, kbID, errors.NewInternalServerError(err.Error())
}
// Verify tenant permissions
if kb.TenantID != c.GetUint(types.TenantIDContextKey.String()) {
logger.Warnf(
ctx,
"Permission denied to access this knowledge base, tenant ID mismatch, "+
"requested tenant ID: %d, knowledge base tenant ID: %d",
c.GetUint(types.TenantIDContextKey.String()),
kb.TenantID,
)
return nil, kbID, errors.NewForbiddenError("Permission denied to access this knowledge base")
}
return kb, kbID, nil
}
// handleDuplicateKnowledgeError handles cases where duplicate knowledge is detected
// Returns true if the error was a duplicate error and was handled, false otherwise
func (h *KnowledgeHandler) handleDuplicateKnowledgeError(c *gin.Context,
err error, knowledge *types.Knowledge, duplicateType string,
) bool {
if dupErr, ok := err.(*types.DuplicateKnowledgeError); ok {
ctx := c.Request.Context()
logger.Warnf(ctx, "Detected duplicate %s: %s", duplicateType, dupErr.Error())
c.JSON(http.StatusConflict, gin.H{
"success": false,
"message": dupErr.Error(),
"data": knowledge, // knowledge contains the existing document
"code": fmt.Sprintf("duplicate_%s", duplicateType),
})
return true
}
return false
}
// CreateKnowledgeFromFile handles requests to create knowledge from an uploaded file
/**
* 从文件创建知识的处理函数
* @param c Gin上下文对象包含请求和响应信息
*/
func (h *KnowledgeHandler) CreateKnowledgeFromFile(c *gin.Context) {
ctx := c.Request.Context() // 获取请求上下文 // 记录开始创建知识的日志
logger.Info(ctx, "Start creating knowledge from file")
// Validate access to the knowledge base
_, kbID, err := h.validateKnowledgeBaseAccess(c)
if err != nil {
c.Error(err)
return
}
// Get the uploaded file
file, err := c.FormFile("file")
if err != nil {
logger.Error(ctx, "File upload failed", err)
c.Error(errors.NewBadRequestError("File upload failed").WithDetails(err.Error()))
return
}
logger.Infof(ctx, "File upload successful, filename: %s, size: %.2f KB", file.Filename, float64(file.Size)/1024)
logger.Infof(ctx, "Creating knowledge, knowledge base ID: %s, filename: %s", kbID, file.Filename)
// Parse metadata if provided
var metadata map[string]string
metadataStr := c.PostForm("metadata")
if metadataStr != "" {
if err := json.Unmarshal([]byte(metadataStr), &metadata); err != nil {
logger.Error(ctx, "Failed to parse metadata", err)
c.Error(errors.NewBadRequestError("Invalid metadata format").WithDetails(err.Error()))
return
}
logger.Infof(ctx, "Received file metadata: %v", metadata)
}
enableMultimodelForm := c.PostForm("enable_multimodel")
var enableMultimodel *bool
if enableMultimodelForm != "" {
parseBool, err := strconv.ParseBool(enableMultimodelForm)
if err != nil {
logger.Error(ctx, "Failed to parse enable_multimodel", err)
c.Error(errors.NewBadRequestError("Invalid enable_multimodel format").WithDetails(err.Error()))
return
}
enableMultimodel = &parseBool
}
// Create knowledge entry from the file
knowledge, err := h.kgService.CreateKnowledgeFromFile(ctx, kbID, file, metadata, enableMultimodel)
// Check for duplicate knowledge error
if err != nil {
if h.handleDuplicateKnowledgeError(c, err, knowledge, "file") {
return
}
if appErr, ok := errors.IsAppError(err); ok {
c.Error(appErr)
return
}
logger.ErrorWithFields(ctx, err, nil)
c.Error(errors.NewInternalServerError(err.Error()))
return
}
logger.Infof(ctx, "Knowledge created successfully, ID: %s, title: %s", knowledge.ID, knowledge.Title)
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": knowledge,
})
}
// CreateKnowledgeFromURL handles requests to create knowledge from a URL
func (h *KnowledgeHandler) CreateKnowledgeFromURL(c *gin.Context) {
ctx := c.Request.Context()
logger.Info(ctx, "Start creating knowledge from URL")
// Validate access to the knowledge base
_, kbID, err := h.validateKnowledgeBaseAccess(c)
if err != nil {
c.Error(err)
return
}
// Parse URL from request body
var req struct {
URL string `json:"url" binding:"required"`
EnableMultimodel *bool `json:"enable_multimodel"`
}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(ctx, "Failed to parse URL request", err)
c.Error(errors.NewBadRequestError(err.Error()))
return
}
logger.Infof(ctx, "Received URL request: %s", req.URL)
logger.Infof(ctx, "Creating knowledge from URL, knowledge base ID: %s, URL: %s", kbID, req.URL)
// Create knowledge entry from the URL
knowledge, err := h.kgService.CreateKnowledgeFromURL(ctx, kbID, req.URL, req.EnableMultimodel)
// Check for duplicate knowledge error
if err != nil {
if h.handleDuplicateKnowledgeError(c, err, knowledge, "url") {
return
}
logger.ErrorWithFields(ctx, err, nil)
c.Error(errors.NewInternalServerError(err.Error()))
return
}
logger.Infof(ctx, "Knowledge created successfully from URL, ID: %s, title: %s", knowledge.ID, knowledge.Title)
c.JSON(http.StatusCreated, gin.H{
"success": true,
"data": knowledge,
})
}
// GetKnowledge retrieves a knowledge entry by its ID
func (h *KnowledgeHandler) GetKnowledge(c *gin.Context) {
ctx := c.Request.Context()
logger.Info(ctx, "Start retrieving knowledge")
// Get knowledge ID from URL path parameter
id := c.Param("id")
if id == "" {
logger.Error(ctx, "Knowledge ID is empty")
c.Error(errors.NewBadRequestError("Knowledge ID cannot be empty"))
return
}
logger.Infof(ctx, "Retrieving knowledge, ID: %s", id)
knowledge, err := h.kgService.GetKnowledgeByID(ctx, id)
if err != nil {
logger.ErrorWithFields(ctx, err, nil)
c.Error(errors.NewInternalServerError(err.Error()))
return
}
logger.Infof(ctx, "Knowledge retrieved successfully, ID: %s, title: %s", knowledge.ID, knowledge.Title)
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": knowledge,
})
}
// ListKnowledge retrieves a paginated list of knowledge entries from a knowledge base
func (h *KnowledgeHandler) ListKnowledge(c *gin.Context) {
ctx := c.Request.Context()
logger.Info(ctx, "Start retrieving knowledge list")
// Get knowledge base ID from URL path parameter
kbID := c.Param("id")
if kbID == "" {
logger.Error(ctx, "Knowledge base ID is empty")
c.Error(errors.NewBadRequestError("Knowledge base ID cannot be empty"))
return
}
// Parse pagination parameters from query string
var pagination types.Pagination
if err := c.ShouldBindQuery(&pagination); err != nil {
logger.Error(ctx, "Failed to parse pagination parameters", err)
c.Error(errors.NewBadRequestError(err.Error()))
return
}
logger.Infof(ctx, "Retrieving knowledge list under knowledge base, knowledge base ID: %s, page: %d, page size: %d",
kbID, pagination.Page, pagination.PageSize)
// Retrieve paginated knowledge entries
result, err := h.kgService.ListPagedKnowledgeByKnowledgeBaseID(ctx, kbID, &pagination)
if err != nil {
logger.ErrorWithFields(ctx, err, nil)
c.Error(errors.NewInternalServerError(err.Error()))
return
}
logger.Infof(ctx, "Knowledge list retrieved successfully, knowledge base ID: %s, total: %d", kbID, result.Total)
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": result.Data,
"total": result.Total,
"page": result.Page,
"page_size": result.PageSize,
})
}
// DeleteKnowledge handles requests to delete a knowledge entry by its ID
func (h *KnowledgeHandler) DeleteKnowledge(c *gin.Context) {
ctx := c.Request.Context()
logger.Info(ctx, "Start deleting knowledge")
// Get knowledge ID from URL path parameter
id := c.Param("id")
if id == "" {
logger.Error(ctx, "Knowledge ID is empty")
c.Error(errors.NewBadRequestError("Knowledge ID cannot be empty"))
return
}
logger.Infof(ctx, "Deleting knowledge, ID: %s", id)
err := h.kgService.DeleteKnowledge(ctx, id)
if err != nil {
logger.ErrorWithFields(ctx, err, nil)
c.Error(errors.NewInternalServerError(err.Error()))
return
}
logger.Infof(ctx, "Knowledge deleted successfully, ID: %s", id)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Deleted successfully",
})
}
// DownloadKnowledgeFile handles requests to download a file associated with a knowledge entry
func (h *KnowledgeHandler) DownloadKnowledgeFile(c *gin.Context) {
ctx := c.Request.Context()
logger.Info(ctx, "Start downloading knowledge file")
// Get knowledge ID from URL path parameter
id := c.Param("id")
if id == "" {
logger.Error(ctx, "Knowledge ID is empty")
c.Error(errors.NewBadRequestError("Knowledge ID cannot be empty"))
return
}
logger.Infof(ctx, "Retrieving knowledge file, ID: %s", id)
// Get file content and filename
file, filename, err := h.kgService.GetKnowledgeFile(ctx, id)
if err != nil {
logger.ErrorWithFields(ctx, err, nil)
c.Error(errors.NewInternalServerError("Failed to retrieve file").WithDetails(err.Error()))
return
}
defer file.Close()
logger.Infof(ctx, "Knowledge file retrieved successfully, ID: %s, filename: %s", id, filename)
// Set response headers for file download
c.Header("Content-Description", "File Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
c.Header("Content-Type", "application/octet-stream")
c.Header("Expires", "0")
c.Header("Cache-Control", "must-revalidate")
c.Header("Pragma", "public")
// Stream file content to response
c.Stream(func(w io.Writer) bool {
if _, err := io.Copy(w, file); err != nil {
logger.Errorf(ctx, "Failed to send file: %v", err)
return false
}
logger.Debug(ctx, "File sending completed")
return false
})
}
// GetKnowledgeBatchRequest defines parameters for batch knowledge retrieval
type GetKnowledgeBatchRequest struct {
IDs []string `form:"ids" binding:"required"` // List of knowledge IDs
}
// GetKnowledgeBatch handles requests to retrieve multiple knowledge entries in a batch
func (h *KnowledgeHandler) GetKnowledgeBatch(c *gin.Context) {
ctx := c.Request.Context()
logger.Info(ctx, "Start batch retrieving knowledge")
// Get tenant ID from context
tenantID, ok := c.Get(types.TenantIDContextKey.String())
if !ok {
logger.Error(ctx, "Failed to get tenant ID")
c.Error(errors.NewUnauthorizedError("Unauthorized"))
return
}
// Parse request parameters from query string
var req GetKnowledgeBatchRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.Error(ctx, "Failed to parse request parameters", err)
c.Error(errors.NewBadRequestError("Invalid request parameters").WithDetails(err.Error()))
return
}
logger.Infof(
ctx,
"Batch retrieving knowledge, tenant ID: %d, number of knowledge IDs: %d",
tenantID.(uint), len(req.IDs),
)
// Retrieve knowledge entries in batch
knowledges, err := h.kgService.GetKnowledgeBatch(ctx, tenantID.(uint), req.IDs)
if err != nil {
logger.ErrorWithFields(ctx, err, nil)
c.Error(errors.NewInternalServerError("Failed to retrieve knowledge list").WithDetails(err.Error()))
return
}
logger.Infof(
ctx,
"Batch knowledge retrieval successful, requested count: %d, returned count: %d",
len(req.IDs), len(knowledges),
)
// Return results
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": knowledges,
})
}
func (h *KnowledgeHandler) UpdateKnowledge(c *gin.Context) {
ctx := c.Request.Context()
logger.Info(ctx, "Start Update knowledge")
// Get knowledge ID from URL path parameter
id := c.Param("id")
if id == "" {
logger.Error(ctx, "Knowledge ID is empty")
c.Error(errors.NewBadRequestError("Knowledge ID cannot be empty"))
return
}
var knowledge types.Knowledge
if err := c.ShouldBindJSON(&knowledge); err != nil {
logger.Error(ctx, "Failed to parse request parameters", err)
c.Error(errors.NewBadRequestError(err.Error()))
return
}
if err := h.kgService.UpdateKnowledge(ctx, &knowledge); err != nil {
logger.ErrorWithFields(ctx, err, nil)
c.Error(errors.NewInternalServerError(err.Error()))
return
}
logger.Infof(ctx, "Knowledge updated successfully, knowledge ID: %s", knowledge.ID)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Knowledge chunk updated successfully",
})
}
// UpdateImageInfo updates a chunk's properties
func (h *KnowledgeHandler) UpdateImageInfo(c *gin.Context) {
ctx := c.Request.Context()
logger.Info(ctx, "Start updating image info")
// Get knowledge ID from URL path parameter
id := c.Param("id")
if id == "" {
logger.Error(ctx, "Knowledge ID is empty")
c.Error(errors.NewBadRequestError("Knowledge ID cannot be empty"))
return
}
chunkID := c.Param("chunk_id")
if id == "" {
logger.Error(ctx, "Chunk ID is empty")
c.Error(errors.NewBadRequestError("Chunk ID cannot be empty"))
return
}
var request struct {
ImageInfo string `json:"image_info"`
}
if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(ctx, "Failed to parse request parameters", err)
c.Error(errors.NewBadRequestError(err.Error()))
return
}
// Update chunk properties
logger.Infof(ctx, "Updating knowledge chunk, knowledge ID: %s, chunk ID: %s", id, chunkID)
err := h.kgService.UpdateImageInfo(ctx, id, chunkID, request.ImageInfo)
if err != nil {
logger.ErrorWithFields(ctx, err, nil)
c.Error(errors.NewInternalServerError(err.Error()))
return
}
logger.Infof(ctx, "Knowledge chunk updated successfully, knowledge ID: %s, chunk ID: %s", id, chunkID)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Knowledge chunk image updated successfully",
})
}