From 98bc4570823c95415e90b2cb60c9c71935394c42 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 3 Feb 2026 11:02:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=BD=BB=E5=BA=95=E7=A6=81=E6=AD=A2base?= =?UTF-8?q?64=E5=AD=98=E5=82=A8=20-=20=E6=89=80=E6=9C=89=E5=9B=BE=E7=89=87?= =?UTF-8?q?/=E8=A7=86=E9=A2=91=E4=B8=8A=E4=BC=A0=E5=9D=87=E5=85=88?= =?UTF-8?q?=E4=BC=A0=E6=9C=8D=E5=8A=A1=E5=99=A8=E8=8E=B7=E5=8F=96URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/canvas/nodes/ImageNode.tsx | 31 +++++++++++++++++-- components/canvas/nodes/VideoNode.tsx | 44 +++++++++++++++++++++------ components/multimodal-input.tsx | 29 ++++++++++++++++-- components/top-navbar.tsx | 32 +++++++++++++++++-- 4 files changed, 121 insertions(+), 15 deletions(-) diff --git a/components/canvas/nodes/ImageNode.tsx b/components/canvas/nodes/ImageNode.tsx index 501f1fe..3e1ff4e 100644 --- a/components/canvas/nodes/ImageNode.tsx +++ b/components/canvas/nodes/ImageNode.tsx @@ -458,16 +458,43 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { }; // 图片标注完成回调 - const handleAnnotateComplete = (annotatedImageUrl: string) => { + const handleAnnotateComplete = async (annotatedImageUrl: string) => { setAnnotatorOpen(false); + // 如果仍然是 base64 格式,先上传到服务器(ImageAnnotator 应该已经处理了,这里作为保护) + let finalUrl = annotatedImageUrl; + if (annotatedImageUrl.startsWith("data:")) { + try { + toast.info("正在上传图片..."); + const response = await fetch(annotatedImageUrl); + const blob = await response.blob(); + const formData = new FormData(); + formData.append("file", blob, `annotated-${Date.now()}.png`); + const uploadResponse = await fetch("/api/files/upload", { + method: "POST", + body: formData, + }); + if (uploadResponse.ok) { + const result = await uploadResponse.json(); + finalUrl = result.url; + } else { + toast.error("上传失败"); + return; + } + } catch (error) { + console.error("上传标注图片失败:", error); + toast.error("上传失败"); + return; + } + } + // 更新当前节点,标记为已标注图片 // 标注后的图片会带有视觉标记(箭头、圆圈、文字等) // 这些标记指示了用户希望 AI 重点关注/修改的区域 updateNode(node.id, { data: { ...data, - src: annotatedImageUrl, + src: finalUrl, hasAnnotations: true, // 🆕 标记为已标注 originalSrc: data.originalSrc || data.src, // 保留原始图片 prompt: data.prompt ? `${data.prompt} (已标注)` : '已标注图片', diff --git a/components/canvas/nodes/VideoNode.tsx b/components/canvas/nodes/VideoNode.tsx index 3271dfb..57eb50c 100644 --- a/components/canvas/nodes/VideoNode.tsx +++ b/components/canvas/nodes/VideoNode.tsx @@ -32,8 +32,8 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) { const updateNode = useCanvasStore((state) => state.updateNode); const deleteNode = useCanvasStore((state) => state.deleteNode); - // 处理视频上传 - const handleVideoUpload = (e: React.ChangeEvent) => { + // 处理视频上传 - 上传到服务器获取 URL + const handleVideoUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; @@ -43,14 +43,40 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) { return; } - const reader = new FileReader(); - reader.onload = (event) => { - const src = event.target?.result as string; - updateNode(node.id, { - data: { ...data, src, status: 'completed' }, + // 更新状态为上传中 + updateNode(node.id, { + data: { ...data, status: 'generating' }, + }); + + try { + // 上传视频到服务器 + const formData = new FormData(); + formData.append('file', file); + + const response = await fetch('/api/video/upload', { + method: 'POST', + body: formData, }); - }; - reader.readAsDataURL(file); + + if (!response.ok) { + throw new Error('上传失败'); + } + + const result = await response.json(); + + // 使用服务器返回的 URL + updateNode(node.id, { + data: { ...data, src: result.url, status: 'completed' }, + }); + + toast.success('视频上传成功'); + } catch (error) { + console.error('视频上传失败:', error); + toast.error('视频上传失败,请重试'); + updateNode(node.id, { + data: { ...data, status: 'failed' }, + }); + } }; // 播放/暂停 diff --git a/components/multimodal-input.tsx b/components/multimodal-input.tsx index 9410f0f..17ea135 100644 --- a/components/multimodal-input.tsx +++ b/components/multimodal-input.tsx @@ -797,13 +797,38 @@ function PureMultimodalInput({ }); toast.success(`已添加标注:${annotation.description}`); }} - onDrawingComplete={(annotatedImageUrl) => { + onDrawingComplete={async (annotatedImageUrl) => { // 将标注后的图片替换原图片(或添加为新附件) const fileName = `annotated-${Date.now()}.png`; + + // 如果仍然是 base64 格式,先上传到服务器 + let finalUrl = annotatedImageUrl; + if (annotatedImageUrl.startsWith("data:")) { + try { + toast.info("正在上传图片..."); + const response = await fetch(annotatedImageUrl); + const blob = await response.blob(); + const formData = new FormData(); + formData.append("file", blob, fileName); + const uploadResponse = await fetch("/api/files/upload", { + method: "POST", + body: formData, + }); + if (uploadResponse.ok) { + const result = await uploadResponse.json(); + finalUrl = result.url; + } + } catch (error) { + console.error("上传标注图片失败:", error); + toast.error("上传失败"); + return; + } + } + setAttachments((prev) => [ ...prev, { - url: annotatedImageUrl, + url: finalUrl, name: fileName, contentType: "image/png", }, diff --git a/components/top-navbar.tsx b/components/top-navbar.tsx index 1ada419..78a07e2 100644 --- a/components/top-navbar.tsx +++ b/components/top-navbar.tsx @@ -15,6 +15,7 @@ import { } from "@/components/ui/dropdown-menu"; import { ImageAnnotator } from "@/components/image-annotator"; import { VideoPlayer } from "@/components/video-player"; +import { toast } from "sonner"; type RankingItem = { rank: number; @@ -130,10 +131,37 @@ export function TopNavbar({ }; // 处理图片编辑完成 - const handleImageEditorConfirm = (annotatedImageUrl: string) => { + const handleImageEditorConfirm = async (annotatedImageUrl: string) => { + // 如果仍然是 base64 格式,先上传到服务器(ImageAnnotator 应该已经处理了,这里作为保护) + let finalUrl = annotatedImageUrl; + if (annotatedImageUrl.startsWith("data:")) { + try { + toast.info("正在上传图片..."); + const response = await fetch(annotatedImageUrl); + const blob = await response.blob(); + const formData = new FormData(); + formData.append("file", blob, `edited-${Date.now()}.png`); + const uploadResponse = await fetch("/api/files/upload", { + method: "POST", + body: formData, + }); + if (uploadResponse.ok) { + const result = await uploadResponse.json(); + finalUrl = result.url; + } else { + toast.error("上传失败"); + return; + } + } catch (error) { + console.error("上传图片失败:", error); + toast.error("上传失败"); + return; + } + } + // 将编辑后的图片存储到 sessionStorage,以便在聊天页面中使用 try { - sessionStorage.setItem("pending-edited-image", annotatedImageUrl); + sessionStorage.setItem("pending-edited-image", finalUrl); } catch (error) { // sessionStorage不可用(如隐私模式),使用URL参数作为备选方案 console.warn("sessionStorage不可用,尝试使用URL参数:", error);