fix: 彻底禁止base64存储 - 所有图片/视频上传均先传服务器获取URL

This commit is contained in:
Your Name 2026-02-03 11:02:05 +08:00
parent 7fdaa5d9b5
commit 98bc457082
4 changed files with 121 additions and 15 deletions

View File

@ -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} (已标注)` : '已标注图片',

View File

@ -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<HTMLInputElement>) => {
// 处理视频上传 - 上传到服务器获取 URL
const handleVideoUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
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' },
});
}
};
// 播放/暂停

View File

@ -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",
},

View File

@ -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);