Files
Enterprise-Knowledge-Base/internal/infrastructure/docparser/image_resolver.go
T
wizardchen 4192cfd072 refactor: simplify image handling in _resolve_images function
- Removed the image storage handling from the `_resolve_images` function, which now only decodes images and returns them as inline bytes.
- Updated the function's docstring to clarify that image persistence is managed by the Go App, and the return value for `image_dir_path` is always empty.
- Adjusted related gRPC and protobuf files to remove the `ConvertToPDF` method and its associated types, streamlining the API.
- Enhanced security measures in the frontend by implementing a placeholder for provider images and ensuring proper hydration of protected file images.

These changes improve the clarity and efficiency of image processing and API interactions within the application.
2026-03-03 22:23:23 +08:00

131 lines
3.4 KiB
Go

package docparser
import (
"context"
"log"
"path/filepath"
"regexp"
"strings"
"github.com/Tencent/WeKnora/internal/types"
"github.com/Tencent/WeKnora/internal/types/interfaces"
"github.com/google/uuid"
)
// StoredImage describes an image that has been saved to storage.
type StoredImage struct {
OriginalRef string // reference in the original markdown
ServingURL string // provider:// URL (e.g. local://images/xxx.png, minio://bucket/key)
MimeType string
}
// ImageResolver reads images from a DocReader ReadResult (inline bytes only)
// and saves them via FileService, replacing markdown references with unified URLs.
type ImageResolver struct {
// TenantID for storage path namespacing
TenantID uint64
}
// NewImageResolver creates a resolver.
func NewImageResolver() *ImageResolver {
return &ImageResolver{}
}
// ResolveAndStore reads images from the convert result, persists them via fileSvc,
// and replaces markdown references with provider:// URLs.
// It returns the updated markdown and a list of stored images.
func (r *ImageResolver) ResolveAndStore(
ctx context.Context,
result *types.ReadResult,
fileSvc interfaces.FileService,
tenantID uint64,
) (updatedMarkdown string, images []StoredImage, err error) {
markdown := result.MarkdownContent
if len(result.ImageRefs) == 0 {
return markdown, nil, nil
}
// Build a map of original_ref -> image ref for fast lookup
refMap := make(map[string]types.ImageRef)
for _, ref := range result.ImageRefs {
refMap[ref.OriginalRef] = ref
}
// Process each image reference found in the markdown
imgPattern := regexp.MustCompile(`!\[([^\]]*)\]\(([^)]+)\)`)
matches := imgPattern.FindAllStringSubmatchIndex(markdown, -1)
// Process in reverse order to preserve positions when replacing
for i := len(matches) - 1; i >= 0; i-- {
m := matches[i]
refPath := markdown[m[4]:m[5]] // group 2: the URL/path
// Skip already-resolved URLs (http/https, unified /files/, or provider:// scheme)
if strings.HasPrefix(refPath, "http://") || strings.HasPrefix(refPath, "https://") ||
isProviderScheme(refPath) {
continue
}
// Find inline image bytes from the result
ref, found := refMap[refPath]
if !found || len(ref.ImageData) == 0 {
continue
}
// Determine extension
ext := extFromMime(ref.MimeType)
if ext == "" {
ext = filepath.Ext(ref.Filename)
}
if ext == "" {
ext = ".png"
}
// Save via FileService — returns provider:// path
fileName := uuid.New().String() + ext
servingURL, saveErr := fileSvc.SaveBytes(ctx, ref.ImageData, tenantID, fileName, false)
if saveErr != nil {
log.Printf("WARN: failed to save image %s: %v", refPath, saveErr)
continue
}
images = append(images, StoredImage{
OriginalRef: refPath,
ServingURL: servingURL,
MimeType: ref.MimeType,
})
// Replace in markdown
markdown = markdown[:m[4]] + servingURL + markdown[m[5]:]
}
return markdown, images, nil
}
func extFromMime(mime string) string {
switch mime {
case "image/png":
return ".png"
case "image/jpeg":
return ".jpg"
case "image/gif":
return ".gif"
case "image/webp":
return ".webp"
case "image/bmp":
return ".bmp"
default:
return ""
}
}
// isProviderScheme checks if the path uses a provider:// scheme (local://, minio://, cos://, tos://).
func isProviderScheme(path string) bool {
for _, prefix := range []string{"local://", "minio://", "cos://", "tos://"} {
if strings.HasPrefix(path, prefix) {
return true
}
}
return false
}