auto-save 2026-05-14 02:52 (~4)

This commit is contained in:
2026-05-14 02:53:06 +08:00
parent b5d8eb19a1
commit 3ab9da094a
4 changed files with 177 additions and 49 deletions

View File

@@ -104,6 +104,8 @@ function clamp(value: number, min: number, max: number) {
return Math.max(min, Math.min(max, value))
}
const THUMBNAIL_HEIGHT = 176
function canvasThumbnailAnchor(root: HTMLDivElement | null, target: HTMLElement) {
if (!root) return { x: 160, y: 0 }
const rootRect = root.getBoundingClientRect()
@@ -356,7 +358,7 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an
onClick={(e) => { e.stopPropagation(); fileRef.current?.click() }}
title="再上传一个视频"
className="shrink-0 rounded-md border border-dashed border-white/30 hover:border-white/50 bg-white/[0.04] hover:bg-white/[0.08] inline-flex items-center justify-center text-white/60 hover:text-white transition"
style={{ width: 88, height: 160 }}
style={{ width: 96, height: THUMBNAIL_HEIGHT }}
>
<Plus className="h-4 w-4" />
</button>
@@ -370,7 +372,7 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an
className={`group relative shrink-0 rounded-md overflow-visible border shadow-lg transition hover:-translate-y-0.5 ${
isActive ? "border-violet-400 ring-2 ring-violet-400/60" : "border-white/25"
}`}
style={{ height: 160, aspectRatio: aspectStr }}
style={{ height: THUMBNAIL_HEIGHT, aspectRatio: aspectStr }}
onMouseEnter={(e) => setHoverPreviewJob({ id: j.id, ...canvasThumbnailAnchor(rootRef.current, e.currentTarget) })}
onMouseLeave={() => setHoverPreviewJob(null)}
>
@@ -765,7 +767,7 @@ export function VisualLabNode({ data, selected }: any) {
? isSelected ? "border-emerald-400 ring-2 ring-emerald-400/60" : "border-white/30 dark:border-white/20"
: p.borderClass
} ${p.kind === "cutout" ? "bg-white" : "bg-black"}`}
style={{ height: 150, aspectRatio: aspect }}
style={{ height: THUMBNAIL_HEIGHT, aspectRatio: aspect }}
onMouseEnter={(e) => setHoverPreview({ id: p.id, ...canvasThumbnailAnchor(rootRef.current, e.currentTarget) })}
onMouseLeave={() => setHoverPreview(null)}
>
@@ -819,9 +821,9 @@ export function VisualLabNode({ data, selected }: any) {
d.onCopyImage?.({ kind: "keyframe", frame_idx: p.frameIdx, label: `${p.label} 关键帧` })
}}
title="复制此图(到分镜头编排工作台插槽粘贴)"
className="absolute top-1 left-1 z-[70] h-5 w-5 rounded-full bg-violet-500/90 text-white shadow-md backdrop-blur inline-flex items-center justify-center text-[10px] leading-none transition hover:bg-violet-400 hover:scale-110"
className="absolute top-1.5 left-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-violet-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-violet-400"
>
📋
<Copy className="h-3.5 w-3.5" />
</button>
)}
@@ -839,9 +841,9 @@ export function VisualLabNode({ data, selected }: any) {
})
}}
title="复制此图(到分镜头编排工作台插槽粘贴)"
className="absolute top-1 left-1 z-[70] h-5 w-5 rounded-full bg-violet-500/90 text-white shadow-md backdrop-blur inline-flex items-center justify-center text-[10px] leading-none transition hover:bg-violet-400 hover:scale-110"
className="absolute top-1.5 left-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-violet-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-violet-400"
>
📋
<Copy className="h-3.5 w-3.5" />
</button>
)}
@@ -855,10 +857,10 @@ export function VisualLabNode({ data, selected }: any) {
void navigator.clipboard?.writeText(video.prompt).catch(() => {})
toast.success("已复制视频 prompt")
}}
className="absolute left-1 top-1 z-[70] h-5 w-5 rounded-full bg-violet-500/90 text-white shadow-md backdrop-blur inline-flex items-center justify-center transition hover:bg-violet-400 hover:scale-110"
className="absolute left-1.5 top-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-violet-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-violet-400"
title="复制视频 prompt"
>
<Copy className="h-2.5 w-2.5" />
<Copy className="h-3.5 w-3.5" />
</button>
)}
@@ -870,9 +872,9 @@ export function VisualLabNode({ data, selected }: any) {
if (confirm(`删除${p.label}?相关清洗 / 抠图 / 生成图都会一并清除。`)) d.onDeleteFrame?.(p.frameIdx)
}}
title="删除该关键帧"
className="absolute top-1 right-1 z-[70] h-5 w-5 rounded-full bg-black/70 text-white/80 backdrop-blur inline-flex items-center justify-center opacity-0 transition hover:bg-rose-500 hover:text-white group-hover:opacity-100"
className="absolute top-1.5 right-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-rose-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-rose-400"
>
<X className="h-3 w-3" />
<Trash2 className="h-3.5 w-3.5" />
</button>
)}
@@ -886,9 +888,9 @@ export function VisualLabNode({ data, selected }: any) {
}
}}
title="删除该提取图"
className="absolute top-1 right-1 z-[70] h-5 w-5 rounded-full bg-black/70 text-white/85 backdrop-blur inline-flex items-center justify-center opacity-0 transition hover:bg-rose-500 hover:text-white group-hover:opacity-100"
className="absolute top-1.5 right-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-rose-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-rose-400"
>
<X className="h-3 w-3" />
<Trash2 className="h-3.5 w-3.5" />
</button>
)}
@@ -899,10 +901,10 @@ export function VisualLabNode({ data, selected }: any) {
e.stopPropagation()
d.onDeleteVideo?.(p.videoId)
}}
className="absolute right-1 top-1 z-[70] h-5 w-5 rounded-full bg-rose-500/90 text-white shadow-md backdrop-blur inline-flex items-center justify-center transition hover:bg-rose-400 hover:scale-110"
className="absolute right-1.5 top-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-rose-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-rose-400"
title="删除这个视频任务"
>
<Trash2 className="h-2.5 w-2.5" />
<Trash2 className="h-3.5 w-3.5" />
</button>
)}
</div>
@@ -1022,7 +1024,7 @@ export function KeyframeNode({ data, selected }: any) {
: "border-white/30 dark:border-white/20"
}`}
style={{
height: 160,
height: THUMBNAIL_HEIGHT,
aspectRatio: d.job && d.job.height > 0
? `${d.job.width}/${d.job.height}`
: "16/9",
@@ -1078,13 +1080,13 @@ export function KeyframeNode({ data, selected }: any) {
label: `分镜 ${f.index + 1} 关键帧`,
})
}}
title="📋 复制此图(到分镜头编排工作台插槽粘贴)"
className="absolute top-1 left-1 h-5 w-5 rounded-full bg-violet-500/90 backdrop-blur text-white shadow-md hover:bg-violet-400 hover:scale-110 inline-flex items-center justify-center transition z-[70] text-[10px] leading-none"
title="复制此图(到分镜头编排工作台插槽粘贴)"
className="absolute top-1.5 left-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-violet-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-violet-400"
>
📋
<Copy className="h-3.5 w-3.5" />
</button>
)}
{/* 删除按钮:hover 时右上角浮出 */}
{/* 删除按钮:常驻可见 */}
{d.onDeleteFrame && (
<button
onClick={(e) => {
@@ -1094,9 +1096,9 @@ export function KeyframeNode({ data, selected }: any) {
}
}}
title="删除该关键帧"
className="absolute top-1 right-1 h-5 w-5 rounded-full bg-black/70 backdrop-blur text-white/80 hover:bg-rose-500 hover:text-white inline-flex items-center justify-center opacity-0 group-hover:opacity-100 transition z-[70]"
className="absolute top-1.5 right-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-rose-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-rose-400"
>
<X className="h-3 w-3" />
<Trash2 className="h-3.5 w-3.5" />
</button>
)}
</div>
@@ -1544,7 +1546,7 @@ export function StoryboardNode({ data, selected }: any) {
<div
key={key}
className="group relative shrink-0 rounded-md border border-violet-300/50 overflow-visible transition shadow-lg hover:-translate-y-0.5 bg-white"
style={{ height: 160, aspectRatio: aspect }}
style={{ height: THUMBNAIL_HEIGHT, aspectRatio: aspect }}
onMouseEnter={(e) => setHoverPreviewCutout({ id: key, ...canvasThumbnailAnchor(rootRef.current, e.currentTarget) })}
onMouseLeave={() => setHoverPreviewCutout(null)}
>
@@ -1579,13 +1581,13 @@ export function StoryboardNode({ data, selected }: any) {
label: p.name,
})
}}
title="📋 复制此图(到分镜头编排工作台插槽粘贴)"
className="absolute top-1 left-1 h-5 w-5 rounded-full bg-violet-500/90 backdrop-blur text-white shadow-md hover:bg-violet-400 hover:scale-110 inline-flex items-center justify-center transition z-[70] text-[10px] leading-none"
title="复制此图(到分镜头编排工作台插槽粘贴)"
className="absolute top-1.5 left-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-violet-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-violet-400"
>
📋
<Copy className="h-3.5 w-3.5" />
</button>
)}
{/* 右上:删除hover 浮出) */}
{/* 右上:删除 */}
{d.onDeleteCutout && (
<button
onClick={(e) => {
@@ -1595,9 +1597,9 @@ export function StoryboardNode({ data, selected }: any) {
}
}}
title="删除该提取图"
className="absolute top-1 right-1 h-5 w-5 rounded-full bg-black/70 backdrop-blur text-white/85 hover:bg-rose-500 hover:text-white inline-flex items-center justify-center opacity-0 group-hover:opacity-100 transition z-[70]"
className="absolute top-1.5 right-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-rose-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-rose-400"
>
<X className="h-3 w-3" />
<Trash2 className="h-3.5 w-3.5" />
</button>
)}
</div>
@@ -1695,7 +1697,7 @@ export function VideoGenNode({ data, selected }: any) {
className={`group relative shrink-0 rounded-md border overflow-visible transition shadow-lg hover:-translate-y-0.5 bg-black ${
ready ? "border-emerald-300/60" : v.status === "failed" ? "border-rose-300/70" : "border-violet-300/55"
}`}
style={{ height: 160, aspectRatio: aspect }}
style={{ height: THUMBNAIL_HEIGHT, aspectRatio: aspect }}
onMouseEnter={(e) => setHoverPreviewVideo({ id: v.id, ...canvasThumbnailAnchor(rootRef.current, e.currentTarget) })}
onMouseLeave={() => setHoverPreviewVideo(null)}
>
@@ -1747,10 +1749,10 @@ export function VideoGenNode({ data, selected }: any) {
void navigator.clipboard?.writeText(v.prompt).catch(() => {})
toast.success("已复制视频 prompt")
}}
className="absolute left-1 top-1 z-[70] h-5 w-5 rounded-full bg-violet-500/90 text-white shadow-md backdrop-blur inline-flex items-center justify-center transition hover:bg-violet-400 hover:scale-110"
className="absolute left-1.5 top-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-violet-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-violet-400"
title="复制视频 prompt"
>
<Copy className="h-2.5 w-2.5" />
<Copy className="h-3.5 w-3.5" />
</button>
<button
type="button"
@@ -1758,10 +1760,10 @@ export function VideoGenNode({ data, selected }: any) {
e.stopPropagation()
d.onDeleteVideo?.(v.id)
}}
className="absolute right-1 top-1 z-[70] h-5 w-5 rounded-full bg-rose-500/90 text-white shadow-md backdrop-blur inline-flex items-center justify-center transition hover:bg-rose-400 hover:scale-110"
className="absolute right-1.5 top-1.5 z-[70] inline-flex h-7 w-7 items-center justify-center rounded-full bg-rose-500/95 text-white shadow-lg backdrop-blur transition hover:scale-110 hover:bg-rose-400"
title="删除这个视频任务"
>
<Trash2 className="h-2.5 w-2.5" />
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
)})}