auto-save 2026-05-12 20:10 (~3)

This commit is contained in:
2026-05-12 20:10:22 +08:00
parent ca0d6f1bfd
commit 138d68d876
3 changed files with 118 additions and 44 deletions

View File

@@ -9,7 +9,9 @@ import { NodeShell, type NodeStatus, type NodeKind } from "./node-shell"
import { type Job, frameUrl, videoUrl } from "@/lib/api"
export interface NodeData {
job: Job | null
job: Job | null // 当前 active job
jobs: Job[] // 所有 job 列表
activeJobId: string | null
submitting: boolean
analyzing: boolean
selectedFrames: Set<number>
@@ -20,6 +22,7 @@ export interface NodeData {
onExpandFrame: (idx: number) => void
onAddManualFrame: (t: number) => void
onOpenVideoLightbox: () => void
onSwitchJob: (id: string) => void
}
/* ---- 状态映射工具 ---- */
@@ -79,37 +82,62 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an
return (
<div className="relative" style={{ width: 320 }}>
{/* 视频缩略图浮于节点上方 — 跟关键帧缩略图同尺寸(小),点击稍微放大可选帧 */}
{hasVideo && job && !videoExpanded && (
<div className="absolute left-0 right-0 flex justify-center" style={{ bottom: "calc(100% + 12px)" }}>
{/* 视频缩略图浮条 — 每个 job 一张 + 末尾「+」按钮再上传 */}
{!videoExpanded && d.jobs.length > 0 && (
<div className="absolute left-0 right-0 flex justify-center items-end gap-1.5 flex-wrap" style={{ bottom: "calc(100% + 12px)" }}>
{d.jobs.map((j) => {
const isActive = j.id === d.activeJobId
const ready = !!j.video_url
return (
<button
key={j.id}
type="button"
onClick={(e) => {
e.stopPropagation()
if (isActive && ready) setVideoExpanded(true)
else d.onSwitchJob(j.id)
}}
title={ready ? `${j.width}×${j.height} · ${j.duration.toFixed(1)}s · ${isActive ? "点击展开" : "点击切换"}` : "下载中…"}
className={`group relative rounded-md overflow-hidden border shadow-lg transition hover:-translate-y-0.5 ${
isActive ? "border-violet-400 ring-2 ring-violet-400/60" : "border-white/25"
}`}
style={{ width: 80, aspectRatio: ready ? `${j.width}/${j.height}` : "9/16" }}
>
{ready ? (
<video
src={videoUrl(j.id)}
muted
loop
playsInline
preload="metadata"
className="block w-full h-full object-cover bg-black"
onMouseEnter={(e) => (e.target as HTMLVideoElement).play().catch(() => {})}
onMouseLeave={(e) => {
const v = e.target as HTMLVideoElement
v.pause()
v.currentTime = 0
}}
/>
) : (
<div className="w-full h-full bg-black/60 flex items-center justify-center">
<Loader2 className="h-4 w-4 animate-spin text-white/60" />
</div>
)}
<div className="absolute bottom-0.5 right-0.5 bg-black/70 text-white text-[9px] font-mono px-1 py-0.5 rounded">
{ready ? `${j.duration.toFixed(1)}s` : "…"}
</div>
</button>
)
})}
{/* + 再加一个 */}
<button
type="button"
onClick={(e) => { e.stopPropagation(); setVideoExpanded(true) }}
title="点击展开 · 可拖时间轴选帧"
className="group relative rounded-md overflow-hidden border border-white/30 shadow-lg hover:-translate-y-0.5 transition"
style={{ width: 80 }}
onClick={(e) => { e.stopPropagation(); fileRef.current?.click() }}
title="再上传一个视频"
className="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: 36, height: 64 }}
>
<video
src={videoUrl(job.id)}
muted
loop
playsInline
preload="metadata"
className="block w-full bg-black"
style={{ aspectRatio: `${job.width}/${job.height}` }}
onMouseEnter={(e) => {
const v = e.target as HTMLVideoElement
v.play().catch(() => {})
}}
onMouseLeave={(e) => {
const v = e.target as HTMLVideoElement
v.pause()
v.currentTime = 0
}}
/>
<div className="absolute bottom-0.5 right-0.5 bg-black/70 text-white text-[9px] font-mono px-1 py-0.5 rounded">
{job.duration.toFixed(1)}s
</div>
<Plus className="h-4 w-4" />
</button>
</div>
)}