From 6a2cbb1b49be44c8d29e04cb9b1638130f512792 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 24 Jan 2026 20:58:15 +0800 Subject: [PATCH] feat: use image URLs instead of Base64 for canvas image operations - Modified ImageNode to upload images to storage bucket before use - Updated RMBG API to support imageUrl parameter (fetch from URL and convert to Base64 internally) - Updated Enhance API to support imageUrl parameter - Updated canvas service layer to use imageUrl for all image processing APIs - All processed images are saved to storage bucket and user gallery --- app/api/enhance/route.ts | 35 +++++++++++++--- app/api/rmbg/route.ts | 33 ++++++++++++--- components/canvas/nodes/ImageNode.tsx | 59 ++++++++++++++++++++++---- services/canvas/index.ts | 60 +++++++++++++++++++++++---- 4 files changed, 159 insertions(+), 28 deletions(-) diff --git a/app/api/enhance/route.ts b/app/api/enhance/route.ts index 7736e43..adf9129 100644 --- a/app/api/enhance/route.ts +++ b/app/api/enhance/route.ts @@ -366,7 +366,7 @@ export async function POST(request: NextRequest) { } } - // 2. 获取请求体(Base64图片或FormData) + // 2. 获取请求体(支持 imageUrl、image_base64 或 FormData) console.log("[Enhance API] 开始获取请求体..."); const contentType = request.headers.get("content-type") || ""; console.log("[Enhance API] Content-Type:", contentType); @@ -377,18 +377,41 @@ export async function POST(request: NextRequest) { let messageId: string | null = null; // 消息ID(可选) if (contentType.includes("application/json")) { - // JSON格式:Base64图片 console.log("[Enhance API] 解析JSON请求体..."); const body = await request.json(); - console.log("[Enhance API] JSON请求体解析完成,image_base64长度:", body.image_base64?.length || 0); - imageBase64 = body.image_base64; prompt = body.prompt || "图片变清晰处理"; chatId = body.chatId || null; messageId = body.messageId || null; - if (!imageBase64) { + // 优先使用 imageUrl,其次使用 image_base64 + if (body.imageUrl) { + // 从 URL 获取图片并转换为 Base64 + console.log(`[Enhance API] 从 URL 获取图片: ${body.imageUrl}`); + try { + const imageResponse = await fetch(body.imageUrl); + if (!imageResponse.ok) { + return NextResponse.json( + { error: `无法获取图片: ${imageResponse.status} ${imageResponse.statusText}` }, + { status: 400 } + ); + } + const arrayBuffer = await imageResponse.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + imageBase64 = buffer.toString("base64"); + console.log(`[Enhance API] 图片已转换为 Base64,大小: ${buffer.length} bytes`); + } catch (fetchError) { + console.error("[Enhance API] 获取图片失败:", fetchError); + return NextResponse.json( + { error: "无法获取图片,请检查图片链接是否有效" }, + { status: 400 } + ); + } + } else if (body.image_base64) { + console.log("[Enhance API] JSON请求体解析完成,image_base64长度:", body.image_base64?.length || 0); + imageBase64 = body.image_base64; + } else { return NextResponse.json( - { error: "缺少image_base64参数" }, + { error: "缺少 imageUrl 或 image_base64 参数" }, { status: 400 } ); } diff --git a/app/api/rmbg/route.ts b/app/api/rmbg/route.ts index 59e07c1..d0c822e 100644 --- a/app/api/rmbg/route.ts +++ b/app/api/rmbg/route.ts @@ -120,7 +120,7 @@ export async function POST(request: NextRequest) { ); } - // 2. 获取请求体(Base64图片或FormData) + // 2. 获取请求体(支持 imageUrl、image_base64 或 FormData) const contentType = request.headers.get("content-type") || ""; let imageBase64: string; @@ -129,16 +129,39 @@ export async function POST(request: NextRequest) { let messageId: string | null = null; // 消息ID(可选) if (contentType.includes("application/json")) { - // JSON格式:Base64图片 const body = await request.json(); - imageBase64 = body.image_base64; prompt = body.prompt || "抠图处理"; chatId = body.chatId || null; messageId = body.messageId || null; - if (!imageBase64) { + // 优先使用 imageUrl,其次使用 image_base64 + if (body.imageUrl) { + // 从 URL 获取图片并转换为 Base64 + console.log(`[RMBG] 从 URL 获取图片: ${body.imageUrl}`); + try { + const imageResponse = await fetch(body.imageUrl); + if (!imageResponse.ok) { + return NextResponse.json( + { error: `无法获取图片: ${imageResponse.status} ${imageResponse.statusText}` }, + { status: 400 } + ); + } + const arrayBuffer = await imageResponse.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + imageBase64 = buffer.toString("base64"); + console.log(`[RMBG] 图片已转换为 Base64,大小: ${buffer.length} bytes`); + } catch (fetchError) { + console.error("[RMBG] 获取图片失败:", fetchError); + return NextResponse.json( + { error: "无法获取图片,请检查图片链接是否有效" }, + { status: 400 } + ); + } + } else if (body.image_base64) { + imageBase64 = body.image_base64; + } else { return NextResponse.json( - { error: "缺少image_base64参数" }, + { error: "缺少 imageUrl 或 image_base64 参数" }, { status: 400 } ); } diff --git a/components/canvas/nodes/ImageNode.tsx b/components/canvas/nodes/ImageNode.tsx index 93e5f63..a6603ed 100644 --- a/components/canvas/nodes/ImageNode.tsx +++ b/components/canvas/nodes/ImageNode.tsx @@ -34,19 +34,62 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { const deleteNode = useCanvasStore((state) => state.deleteNode); const addNode = useCanvasStore((state) => state.addNode); - // 处理图片上传 - const handleImageUpload = (e: React.ChangeEvent) => { + const [isUploading, setIsUploading] = useState(false); + + // 处理图片上传 - 上传到存储桶,不使用 Base64 + const handleImageUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; - const reader = new FileReader(); - reader.onload = (event) => { - const src = event.target?.result as string; + // 验证文件类型 + if (!file.type.startsWith('image/')) { + toast.error('请上传图片文件'); + return; + } + + // 验证文件大小 (8MB) + if (file.size > 8 * 1024 * 1024) { + toast.error('图片大小不能超过 8MB'); + return; + } + + setIsUploading(true); + // 先显示上传中状态 + updateNode(node.id, { + data: { ...data, status: 'generating' }, + }); + + try { + // 上传到存储桶 + const result = await imageGenerationService.uploadImage(file); + + if (result.success && result.data?.url) { + updateNode(node.id, { + data: { + ...data, + src: result.data.url, + status: 'completed' + }, + }); + toast.success('图片上传成功'); + } else { + updateNode(node.id, { + data: { ...data, status: 'failed' }, + }); + toast.error(result.error || '图片上传失败'); + } + } catch (error) { + console.error('图片上传失败:', error); updateNode(node.id, { - data: { ...data, src, status: 'completed' }, + data: { ...data, status: 'failed' }, }); - }; - reader.readAsDataURL(file); + toast.error('图片上传失败,请重试'); + } finally { + setIsUploading(false); + } + + // 重置 input + e.target.value = ''; }; // 处理图片编辑操作 - 集成我们现有的 API diff --git a/services/canvas/index.ts b/services/canvas/index.ts index 8459cde..dcf882f 100644 --- a/services/canvas/index.ts +++ b/services/canvas/index.ts @@ -108,6 +108,36 @@ export const canvasService = { // 图片生成服务 export const imageGenerationService = { + // 上传图片到存储桶 + async uploadImage(file: File): Promise> { + try { + const formData = new FormData(); + formData.append('file', file); + + const response = await fetch('/api/files/upload', { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + const errorData = await response.json(); + return { success: false, error: errorData.error || '上传失败' }; + } + + const data = await response.json(); + return { + success: true, + data: { + url: data.url, + pathname: data.pathname + } + }; + } catch (error) { + console.error('[Canvas] 图片上传失败:', error); + return { success: false, error: '图片上传失败' }; + } + }, + // AI 生成图片 async generateImage(params: { prompt: string; @@ -130,47 +160,59 @@ export const imageGenerationService = { } }, - // 图片抠图 + // 图片抠图 - 使用图片URL async removeBackground(imageUrl: string): Promise> { try { const response = await fetch('/api/rmbg', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ imageUrl }), + body: JSON.stringify({ imageUrl }), // 使用 imageUrl 参数 }); const data = await response.json(); - return data; + if (data.success && data.original_url) { + return { success: true, data: { url: data.original_url } }; + } + return { success: false, error: data.error || '抠图失败' }; } catch (error) { + console.error('[Canvas] 抠图失败:', error); return { success: false, error: '抠图失败' }; } }, - // 图片增强 + // 图片增强 - 使用图片URL async enhanceImage(imageUrl: string): Promise> { try { const response = await fetch('/api/enhance', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ imageUrl }), + body: JSON.stringify({ imageUrl }), // 使用 imageUrl 参数 }); const data = await response.json(); - return data; + if (data.success && data.original_url) { + return { success: true, data: { url: data.original_url } }; + } + return { success: false, error: data.error || '图片增强失败' }; } catch (error) { + console.error('[Canvas] 图片增强失败:', error); return { success: false, error: '图片增强失败' }; } }, - // 图片扩展 + // 图片扩展 - 使用图片URL async extendImage(imageUrl: string, direction?: string): Promise> { try { const response = await fetch('/api/extend', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ imageUrl, direction }), + body: JSON.stringify({ image_url: imageUrl, direction }), // extend API 使用 image_url 参数名 }); const data = await response.json(); - return data; + if (data.success && data.original_url) { + return { success: true, data: { url: data.original_url } }; + } + return { success: false, error: data.error || '图片扩展失败' }; } catch (error) { + console.error('[Canvas] 图片扩展失败:', error); return { success: false, error: '图片扩展失败' }; } },