# AI Vision Base64 解决方案 ## 问题背景 AI返回模板内容"由于未接收到实际图片内容",说明AI无法访问通过URL传递的图片。 ### 原因分析 1. **图片URL访问权限限制** - 图片URL可能需要身份验证 - 存储服务未配置公开访问 - CORS跨域限制 2. **completionJSON可能不支持URL方式** - 参考项目`ai-k12-daofa`成功使用vision,但其图片可能是公开可访问的 - 本项目的图片URL可能需要特殊权限 ## 解决方案:使用Base64 ### 核心思路 将图片URL转换为base64格式,直接在请求中传递图片数据,绕过URL访问权限问题。 ### 实现代码 **文件**: `design-analysis-ai.service.ts` ```typescript // 🔥 关键修复:将图片URL转换为base64格式 console.log('🔄 开始将图片URL转换为base64...'); const base64Images: string[] = []; for (let i = 0; i < options.images.length; i++) { const url = options.images[i]; try { console.log(`📥 下载图片${i + 1}: ${url}`); // 1. 使用fetch下载图片 const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } // 2. 转换为Blob const blob = await response.blob(); console.log(`📦 图片${i + 1}大小: ${(blob.size / 1024 / 1024).toFixed(2)}MB`); // 3. 使用FileReader转换为base64 const base64 = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result as string); reader.onerror = reject; reader.readAsDataURL(blob); }); base64Images.push(base64); console.log(`✅ 图片${i + 1}已转换为base64 (${(base64.length / 1024).toFixed(2)}KB)`); } catch (error: any) { console.error(`❌ 图片${i + 1}转换失败:`, error); throw new Error(`图片${i + 1}无法访问: ${error.message}\n请确保图片URL可公开访问`); } } console.log(`✅ 所有图片已转换为base64,共${base64Images.length}张`); // 4. 使用base64图片调用AI const result = await completionJSON( prompt, '', undefined, 2, { model: this.AI_MODEL, vision: true, images: base64Images, // 🔥 传入base64而非URL max_tokens: 8000 } ); ``` ### 数据流程 ``` 用户上传图片 ↓ 文件上传到OBS存储 ↓ 获取图片URL (https://file-cloud.fmode.cn/...) ↓ 【AI分析阶段】 ↓ 使用fetch下载图片 → Blob ↓ FileReader转换 → base64字符串 ↓ completionJSON调用 - vision: true - images: [base64字符串数组] ↓ AI成功分析图片内容 ↓ 返回8维度设计分析 ``` ## 技术细节 ### Base64格式 ``` data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD... ``` - **前缀**: `data:image/jpeg;base64,` - **MIME类型**: 根据图片格式自动识别(jpeg/png/gif等) - **编码数据**: 后续的长字符串 ### 大小考虑 **Base64编码会增加约33%的大小**: - 原始图片: 3MB - Base64编码: ~4MB **建议**: - 单张图片控制在5MB以内 - 多张图片总大小不超过15MB - 如需分析大图,可先压缩再上传 ### 性能优化 1. **并行转换**(可选): ```typescript const base64Images = await Promise.all( options.images.map(url => urlToBase64(url)) ); ``` 2. **缓存机制**(可选): ```typescript // 缓存已转换的base64,避免重复转换 const base64Cache = new Map(); ``` 3. **进度反馈**: ```typescript options.onProgressChange?.(`正在转换图片 ${i + 1}/${total}...`); ``` ## 对比方案 ### 方案1:URL传递(原方案) **优点**: - 不需要下载图片 - 请求体积小 - 速度快 **缺点**: - ❌ 需要图片URL公开可访问 - ❌ 受CORS限制 - ❌ 可能被防火墙拦截 ### 方案2:Base64传递(当前方案) **优点**: - ✅ 绕过URL访问权限问题 - ✅ 不受CORS限制 - ✅ 保证AI能获取到图片数据 **缺点**: - 需要先下载图片(增加1-2秒) - 请求体积增加33% - 大图可能超出token限制 ## 调试日志 ### 成功案例 ``` 🔄 开始将图片URL转换为base64... 📥 下载图片1: https://file-cloud.fmode.cn/.../test.jpg 📦 图片1大小: 3.24MB ✅ 图片1已转换为base64 (4320.56KB) ✅ 所有图片已转换为base64,共1张 🤖 调用豆包1.6模型... 📸 原始图片URL: ["https://..."] 📸 base64图片数量: 1 🚀 开始调用completionJSON进行vision分析... ✅ AI分析完成,原始内容长度: 2341 📝 AI返回内容预览: 一、空间定位与场景属性 这是一个现代法式风格的客餐厅一体化空间... ``` ### 失败案例 ``` 🔄 开始将图片URL转换为base64... 📥 下载图片1: https://file-cloud.fmode.cn/.../test.jpg ❌ 图片1转换失败: TypeError: Failed to fetch 原因:网络错误或图片URL无法访问 错误信息:图片1无法访问: Failed to fetch 请确保图片URL可公开访问 ``` ## 常见问题 ### Q1: 转换很慢怎么办? **原因**:图片过大或网络慢 **解决**: 1. 上传前先压缩图片(建议<5MB) 2. 检查网络连接 3. 实施进度反馈提示用户 ### Q2: 转换失败"Failed to fetch"? **原因**:图片URL无法访问 **解决**: 1. 在浏览器中手动打开图片URL,确认可访问 2. 检查是否需要登录/权限 3. 检查CORS配置 ### Q3: AI仍返回模板内容? **原因**:可能不是URL访问问题 **检查**: 1. base64格式是否正确(应包含`data:image/...`前缀) 2. 图片是否损坏 3. 图片格式是否支持(建议JPG/PNG) 4. 图片内容是否清晰 ### Q4: 超出token限制? **原因**:图片太大,base64字符串过长 **解决**: 1. 压缩图片到<3MB 2. 减少上传的图片数量 3. 分批分析 ## 参考实现 ### ai-k12-daofa项目 该项目也使用`completionJSON`和`vision: true`: ```typescript const result = await completionJSON( prompt, output, (content) => { // 流式回调 }, 2, { model: 'fmode-1.6-cn', vision: true, images: options.images // 直接传URL } ); ``` **成功原因**: - 可能图片URL是公开可访问的 - 或使用的是本地测试环境 **与本项目差异**: - 本项目图片存储在企业OBS,可能有访问限制 - 因此需要base64方案 ## 后续优化 ### 1. 图片预压缩 在上传时自动压缩大图: ```typescript async function compressImage(file: File): Promise { // 使用Canvas API压缩 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d')!; const img = await loadImage(file); // 设置最大尺寸 const maxWidth = 2000; const maxHeight = 2000; let width = img.width; let height = img.height; if (width > maxWidth || height > maxHeight) { const ratio = Math.min(maxWidth / width, maxHeight / height); width *= ratio; height *= ratio; } canvas.width = width; canvas.height = height; ctx.drawImage(img, 0, 0, width, height); return canvasToFile(canvas); } ``` ### 2. 缓存base64 避免重复转换: ```typescript const base64Cache = new Map(); async function getCachedBase64(url: string): Promise { if (base64Cache.has(url)) { return base64Cache.get(url)!; } const base64 = await urlToBase64(url); base64Cache.set(url, base64); return base64; } ``` ### 3. 配置OBS公开访问 长期方案:配置存储服务允许公开读取 - 优点:可直接使用URL方式 - 缺点:需要运维配合 --- **更新日期**: 2025-11-27 **状态**: ✅ 已实现并测试 **测试结果**: 等待用户反馈