Remove video canvas demo shells and fix media upload timeouts

- Starter/toolbox presets no longer seed fake generated results; demo
  fallback media URLs in tool actions now fail with honest guidance
- Delete dead demo scene components, fake director console, demo
  history constants and unreachable submit toast fallbacks
- Smart edit dialog accepts desktop drag-and-drop video files
- Raise media upload client timeout to 10 minutes and surface
  friendly timeout errors instead of raw 'signal timed out'
This commit is contained in:
134 2026-09-18 17:40:49 +08:00
parent 3002704d77
commit a4354dc535
3 changed files with 388 additions and 2329 deletions

File diff suppressed because it is too large Load Diff

View File

@ -90,6 +90,23 @@ export function SmartEditDialog({
[zoom, setZoom] = useState(1),
[snap, setSnap] = useState(true);
const [configure, setConfigure] = useState(false);
const [videoDragActive, setVideoDragActive] = useState(false);
const videoDragDepthRef = useRef(0);
const uploadVideoFiles = async (files: File[]) => {
if (!files.length) return;
if (files.length + draft.assets.length > 12) {
setError("最多 12 个素材");
return;
}
setBusy(true);
try {
for (const file of files) await upload(file, "video");
} catch (e) {
setError((e as Error).message);
} finally {
setBusy(false);
}
};
const applied = useRef(planTaskId);
useEffect(() => {
if (planTaskId && planTaskId !== applied.current) {
@ -112,7 +129,7 @@ export function SmartEditDialog({
fetch("/api/canvas/smart-edit/capabilities", { signal: controller.signal })
.then(async (r) => {
const data = await r.json();
if (!r.ok) throw Error(data.error);
if (!r.ok) throw new Error(data.error);
setCap(data);
})
.catch((e) => {
@ -185,17 +202,26 @@ export function SmartEditDialog({
setTimeout(() => URL.revokeObjectURL(url), 1000);
};
const upload = async (file: File, kind: "video" | "audio") => {
if (file.size > 125 * 1024 * 1024) throw Error("单个素材不得超过 125 MiB");
if (file.size > 125 * 1024 * 1024) throw new Error("单个素材不得超过 125 MiB");
const form = new FormData();
form.append("file", file);
const response = await fetch("/api/canvas/media/upload", {
method: "POST",
body: form,
signal: AbortSignal.timeout(120000),
});
let response: Response;
try {
response = await fetch("/api/canvas/media/upload", {
method: "POST",
body: form,
signal: AbortSignal.timeout(600000),
});
} catch (e) {
throw new Error(
e instanceof DOMException && (e.name === "TimeoutError" || e.name === "AbortError")
? "上传超时,请检查网络后重试,或改用更小的素材"
: "上传失败,请检查网络后重试",
);
}
const result = await response.json();
const url = result.url || result.data?.url;
if (!response.ok || !url) throw Error(result.error || "上传失败");
if (!response.ok || !url) throw new Error(result.error || "上传失败");
if (kind === "audio") patch({ music: { url, gain: 0.2, start: 0 } });
else
patch({
@ -308,7 +334,43 @@ export function SmartEditDialog({
aria-modal="true"
aria-label="智能剪辑"
className="fixed inset-0 z-[160] flex flex-col bg-[#171717] text-white"
onDragEnter={(e) => {
if (!Array.from(e.dataTransfer.types).includes("Files")) return;
e.preventDefault();
videoDragDepthRef.current += 1;
setVideoDragActive(true);
}}
onDragOver={(e) => {
if (!Array.from(e.dataTransfer.types).includes("Files")) return;
e.preventDefault();
e.dataTransfer.dropEffect = "copy";
}}
onDragLeave={(e) => {
videoDragDepthRef.current = Math.max(0, videoDragDepthRef.current - 1);
if (!videoDragDepthRef.current) setVideoDragActive(false);
}}
onDrop={(e) => {
if (!Array.from(e.dataTransfer.types).includes("Files")) return;
e.preventDefault();
videoDragDepthRef.current = 0;
setVideoDragActive(false);
const files = Array.from(e.dataTransfer.files).filter((file) =>
file.type.startsWith("video/"),
);
if (!files.length) {
setError("请拖入 MP4、MOV 或 WebM 视频素材");
return;
}
void uploadVideoFiles(files);
}}
>
{videoDragActive ? (
<div className="pointer-events-none fixed inset-0 z-[300] flex items-center justify-center border-4 border-dashed border-cyan-300/70 bg-cyan-300/5">
<span className="rounded-lg bg-black/70 px-4 py-2 text-sm">
松开以上传视频素材
</span>
</div>
) : null}
<header className="flex shrink-0 items-center justify-between border-b border-white/15 px-5 py-3">
<h2>智能剪辑 · {plan?.title || "从素材到成片"}</h2>
{plan && (
@ -371,18 +433,7 @@ export function SmartEditDialog({
onChange={async (e) => {
const files = Array.from(e.target.files || []);
e.target.value = "";
if (files.length + draft.assets.length > 12) {
setError("最多 12 个素材");
return;
}
setBusy(true);
try {
for (const file of files) await upload(file, "video");
} catch (e) {
setError((e as Error).message);
} finally {
setBusy(false);
}
await uploadVideoFiles(files);
}}
/>
</label>

View File

@ -236,14 +236,23 @@ export function SmartEditNodeComposer({
try {
const body = new FormData();
body.append("file", file);
const response = await fetch("/api/canvas/media/upload", {
method: "POST",
body,
signal: AbortSignal.timeout(120000),
});
let response: Response;
try {
response = await fetch("/api/canvas/media/upload", {
method: "POST",
body,
signal: AbortSignal.timeout(600000),
});
} catch (e) {
throw new Error(
e instanceof DOMException && (e.name === "TimeoutError" || e.name === "AbortError")
? "上传超时,请检查网络后重试,或改用更小的素材"
: "上传失败,请检查网络后重试",
);
}
const data = await response.json(),
url = data.url || data.data?.url;
if (!response.ok || !url) throw Error(data.error || "音乐上传失败");
if (!response.ok || !url) throw new Error(data.error || "音乐上传失败");
onDraft({ music: { url, gain: 0.2, start: 0 } });
} catch (e) {
setError(e instanceof Error ? e.message : "音乐上传失败");