package llm import ( "encoding/base64" "fmt" "os" "path/filepath" "strings" ) var mediaTypes = map[string]string{ ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", } // maxImageBytes 限制单张图大小,避免一次请求爆掉 token/体积。 const maxImageBytes = 8 << 20 // LoadImage 读图并 base64 编码,返回 (b64, mediaType)。 func LoadImage(path string) (string, string, error) { ext := strings.ToLower(filepath.Ext(path)) mt, ok := mediaTypes[ext] if !ok { return "", "", fmt.Errorf("unsupported image type %q", ext) } data, err := os.ReadFile(path) if err != nil { return "", "", err } if len(data) > maxImageBytes { return "", "", fmt.Errorf("image too large: %d bytes (limit %d)", len(data), maxImageBytes) } return base64.StdEncoding.EncodeToString(data), mt, nil } // ImageEstimateTokens 是单张图在 token 粗估中的保守常数(1080p 截图约 1.1-1.6k tokens)。 const ImageEstimateTokens = 1400 // loadImageRef 统一解析图片引用:Filename 走缓存(优先),Path 直读(兼容旧格式)。 func loadImageRef(img Image) (b64, mime string, err error) { if img.Filename != "" && imageCache != nil { return imageCache.LoadWithMIME(img.Filename) } if img.Path != "" { return LoadImage(img.Path) } return "", "", fmt.Errorf("no image path or filename provided") } // imageUnavailable 文本占位(图片读取失败时各 provider 的统一降级文案)。 func imageUnavailable(img Image, err error) string { ref := img.Path if img.Filename != "" { ref = img.Filename } return "[image unavailable: " + ref + ": " + err.Error() + "]" }