fix: 彻底禁止base64存储 - 所有图片/视频上传均先传服务器获取URL
This commit is contained in:
parent
7fdaa5d9b5
commit
98bc457082
@ -458,16 +458,43 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 图片标注完成回调
|
// 图片标注完成回调
|
||||||
const handleAnnotateComplete = (annotatedImageUrl: string) => {
|
const handleAnnotateComplete = async (annotatedImageUrl: string) => {
|
||||||
setAnnotatorOpen(false);
|
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 重点关注/修改的区域
|
// 这些标记指示了用户希望 AI 重点关注/修改的区域
|
||||||
updateNode(node.id, {
|
updateNode(node.id, {
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
src: annotatedImageUrl,
|
src: finalUrl,
|
||||||
hasAnnotations: true, // 🆕 标记为已标注
|
hasAnnotations: true, // 🆕 标记为已标注
|
||||||
originalSrc: data.originalSrc || data.src, // 保留原始图片
|
originalSrc: data.originalSrc || data.src, // 保留原始图片
|
||||||
prompt: data.prompt ? `${data.prompt} (已标注)` : '已标注图片',
|
prompt: data.prompt ? `${data.prompt} (已标注)` : '已标注图片',
|
||||||
|
|||||||
@ -32,8 +32,8 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
|||||||
const updateNode = useCanvasStore((state) => state.updateNode);
|
const updateNode = useCanvasStore((state) => state.updateNode);
|
||||||
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
||||||
|
|
||||||
// 处理视频上传
|
// 处理视频上传 - 上传到服务器获取 URL
|
||||||
const handleVideoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleVideoUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
@ -43,14 +43,40 @@ export function VideoNode({ node, isSelected }: VideoNodeProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = new FileReader();
|
// 更新状态为上传中
|
||||||
reader.onload = (event) => {
|
updateNode(node.id, {
|
||||||
const src = event.target?.result as string;
|
data: { ...data, status: 'generating' },
|
||||||
updateNode(node.id, {
|
});
|
||||||
data: { ...data, src, status: 'completed' },
|
|
||||||
|
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' },
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 播放/暂停
|
// 播放/暂停
|
||||||
|
|||||||
@ -797,13 +797,38 @@ function PureMultimodalInput({
|
|||||||
});
|
});
|
||||||
toast.success(`已添加标注:${annotation.description}`);
|
toast.success(`已添加标注:${annotation.description}`);
|
||||||
}}
|
}}
|
||||||
onDrawingComplete={(annotatedImageUrl) => {
|
onDrawingComplete={async (annotatedImageUrl) => {
|
||||||
// 将标注后的图片替换原图片(或添加为新附件)
|
// 将标注后的图片替换原图片(或添加为新附件)
|
||||||
const fileName = `annotated-${Date.now()}.png`;
|
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) => [
|
setAttachments((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
url: annotatedImageUrl,
|
url: finalUrl,
|
||||||
name: fileName,
|
name: fileName,
|
||||||
contentType: "image/png",
|
contentType: "image/png",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { ImageAnnotator } from "@/components/image-annotator";
|
import { ImageAnnotator } from "@/components/image-annotator";
|
||||||
import { VideoPlayer } from "@/components/video-player";
|
import { VideoPlayer } from "@/components/video-player";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
type RankingItem = {
|
type RankingItem = {
|
||||||
rank: number;
|
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,以便在聊天页面中使用
|
// 将编辑后的图片存储到 sessionStorage,以便在聊天页面中使用
|
||||||
try {
|
try {
|
||||||
sessionStorage.setItem("pending-edited-image", annotatedImageUrl);
|
sessionStorage.setItem("pending-edited-image", finalUrl);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// sessionStorage不可用(如隐私模式),使用URL参数作为备选方案
|
// sessionStorage不可用(如隐私模式),使用URL参数作为备选方案
|
||||||
console.warn("sessionStorage不可用,尝试使用URL参数:", error);
|
console.warn("sessionStorage不可用,尝试使用URL参数:", error);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user