auto-save 2026-05-13 20:18 (~4)

This commit is contained in:
2026-05-13 20:18:24 +08:00
parent 0b6a463943
commit 40a665a578
4 changed files with 106 additions and 16 deletions

View File

@@ -941,23 +941,94 @@ export function StoryboardNode({ data, selected }: any) {
/* ============================================================
9. VideoGenNode (placeholder)
============================================================ */
export function VideoGenNode({ selected }: any) {
export function VideoGenNode({ data, selected }: any) {
const d: NodeData = data
const drafts = d.videoDrafts ?? []
const status: NodeStatus = drafts.length > 0 ? "done" : "pending"
const aspect = d.job && (d.job.width ?? 0) > 0 && (d.job.height ?? 0) > 0
? `${d.job.width}/${d.job.height}`
: "9/16"
return (
<NodeShell
type="ai" status="pending"
icon={<Film className="h-4 w-4" />}
title="生成视频 · Video Gen"
subtitle="STEP 7 · 首帧 + 动作 prompt"
selected={selected}
>
<div className="grid grid-cols-3 gap-1.5 text-[10.5px]">
{["Seedance", "Kling", "Veo 3"].map((m) => (
<div key={m} className="rounded-md border border-dashed border-black/15 dark:border-white/10 px-2 py-1.5 text-center text-[var(--text-faint)]">
<span className="text-[var(--text-strong)] text-[11px]">{m}</span>
<div className="relative" style={{ width: 280 }}>
{drafts.length > 0 && (
<div
className="absolute left-0 right-0 grid grid-cols-3 gap-1.5"
style={{ bottom: "calc(100% + 12px)" }}
>
{drafts.slice(0, 6).map((v, i) => (
<div
key={v.id}
className="group relative rounded-md border border-rose-300/55 transition shadow-lg hover:-translate-y-0.5 bg-black"
style={{ aspectRatio: aspect }}
>
<button
type="button"
onClick={(e) => {
e.stopPropagation()
void navigator.clipboard?.writeText(v.prompt).catch(() => {})
}}
title={`${v.label} · 点击复制视频 prompt`}
className="absolute inset-0 w-full h-full overflow-hidden rounded-md bg-black"
>
<img
src={v.poster_url}
alt={v.label}
className="absolute inset-0 w-full h-full object-cover"
/>
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/90 to-transparent px-1.5 py-1 text-left">
<div className="truncate text-[9.5px] font-semibold text-white"> {i + 1}</div>
<div className="truncate text-[8.5px] font-mono text-white/60">{v.duration.toFixed(1)}s</div>
</div>
</button>
<div
className="pointer-events-none absolute opacity-0 group-hover:opacity-100 scale-95 group-hover:scale-100 transition-all duration-150 z-[60]"
style={{
bottom: "calc(100% + 10px)",
left: "50%",
transform: "translateX(-50%)",
transformOrigin: "bottom center",
}}
>
<div className="rounded-lg overflow-hidden border-2 border-rose-300/60 bg-black shadow-2xl" style={{ width: 300 }}>
<div style={{ aspectRatio: aspect }}>
<img src={v.poster_url} alt="" className="w-full h-full object-cover" />
</div>
<div className="space-y-1 bg-black/90 px-2 py-1.5 text-white">
<div className="flex items-center justify-between gap-2 text-[10.5px]">
<span className="truncate">{v.label}</span>
<span className="shrink-0 font-mono text-white/55">{v.provider}</span>
</div>
<div className="line-clamp-3 text-[9.5px] leading-snug text-white/55">
{v.prompt}
</div>
</div>
</div>
</div>
</div>
))}
</div>
)}
<NodeShell
type="ai" status={status}
icon={<Film className="h-4 w-4" />}
title="生成视频 · Video Gen"
subtitle={`STEP 7 · 首帧 + 动作 prompt${drafts.length > 0 ? ` · ${drafts.length} 个任务` : ""}`}
selected={selected}
>
<div className="grid grid-cols-3 gap-1.5 text-[10.5px]">
{["Seedance", "Kling", "Veo 3"].map((m) => (
<div key={m} className="rounded-md border border-dashed border-black/15 dark:border-white/10 px-2 py-1.5 text-center text-[var(--text-faint)]">
<span className="text-[var(--text-strong)] text-[11px]">{m}</span>
</div>
))}
</div>
{drafts.length > 0 && (
<div className="mt-2 rounded-md border border-rose-300/25 bg-rose-500/10 px-2 py-1.5 text-[10.5px] text-[var(--text-soft)]">
{drafts.length} prompt ·
</div>
))}
</div>
</NodeShell>
)}
</NodeShell>
</div>
)
}