auto-save 2026-05-12 19:36 (~3)
This commit is contained in:
@@ -251,6 +251,13 @@
|
||||
"message": "auto-save 2026-05-12 19:25 (~4)",
|
||||
"hash": "c159668",
|
||||
"files_changed": 4
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-12T19:31:21+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-12 19:31 (~2)",
|
||||
"hash": "ecef988",
|
||||
"files_changed": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ export function Dashboard({ data }: Props) {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ---- Download — Kanban ---- */}
|
||||
{/* ---- Download — 只显示元数据,视频播放器移到 Keyframe section ---- */}
|
||||
{key === "download" && (
|
||||
!hasVideo ? (
|
||||
<KanbanCard tone="orange" tags={["yt-dlp"]} title={isDownloading ? "下载中…" : "等待提交"}>
|
||||
@@ -305,20 +305,19 @@ export function Dashboard({ data }: Props) {
|
||||
</KanbanCard>
|
||||
) : (
|
||||
<>
|
||||
<KanbanCard tone="orange" tags={["视频源"]} title={job!.url.startsWith("upload://") ? "本地上传" : "yt-dlp 下载"}>
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={videoUrl(job!.id)}
|
||||
controls
|
||||
onTimeUpdate={(e) => setVideoT((e.target as HTMLVideoElement).currentTime)}
|
||||
className="block w-full bg-black rounded-md mt-1"
|
||||
/>
|
||||
<KanbanCard tone="orange" tags={["视频源", job!.url.startsWith("upload://") ? "上传" : "yt-dlp"]} title={job!.url.startsWith("upload://") ? "本地上传" : "TK 下载"}>
|
||||
<div className="text-[11px] text-[var(--text-soft)] truncate font-mono mt-1">
|
||||
{job!.url.startsWith("upload://") ? `📎 ${job!.url.slice(9)}` : `🔗 ${job!.url}`}
|
||||
</div>
|
||||
</KanbanCard>
|
||||
<KanbanCard tone="amber" tags={["元数据"]} title="视频信息">
|
||||
<div className="grid grid-cols-3 gap-3 text-[11px] font-mono">
|
||||
<div><div className="text-[var(--text-faint)] text-[9.5px]">分辨率</div><div className="text-[var(--text-strong)] text-[12.5px] mt-0.5">{job!.width}×{job!.height}</div></div>
|
||||
<div><div className="text-[var(--text-faint)] text-[9.5px]">时长</div><div className="text-[var(--text-strong)] text-[12.5px] mt-0.5">{job!.duration.toFixed(1)}s</div></div>
|
||||
<div><div className="text-[var(--text-faint)] text-[9.5px]">来源</div><div className="text-[var(--text-strong)] text-[12.5px] mt-0.5">{job!.url.startsWith("upload://") ? "上传" : "TK"}</div></div>
|
||||
<div><div className="text-[var(--text-faint)] text-[9.5px]">大小</div><div className="text-[var(--text-strong)] text-[12.5px] mt-0.5">~9MB</div></div>
|
||||
</div>
|
||||
<div className="kanban-meta">
|
||||
要看视频 / 选帧请打开「关键帧」
|
||||
</div>
|
||||
</KanbanCard>
|
||||
</>
|
||||
@@ -340,25 +339,40 @@ export function Dashboard({ data }: Props) {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ---- Keyframe — Kanban 卡片 ---- */}
|
||||
{/* ---- Keyframe — Kanban 卡片(视频播放器 + 加帧 + 缩略图都在这里) ---- */}
|
||||
{key === "keyframe" && (
|
||||
<div className="space-y-3">
|
||||
{hasVideo && job && (
|
||||
<KanbanCard tone="green" tags={["手动加帧"]} title="从视频任意时间点抽 1 张">
|
||||
<button
|
||||
type="button"
|
||||
disabled={addingFrame}
|
||||
onClick={async () => {
|
||||
const t = videoRef.current?.currentTime ?? videoT
|
||||
setAddingFrame(true)
|
||||
try { await data.onAddManualFrame(t) } finally { setAddingFrame(false) }
|
||||
}}
|
||||
className="mt-1 w-full text-[12px] py-1.5 rounded-md bg-emerald-500 hover:bg-emerald-400 text-white disabled:opacity-50 inline-flex items-center justify-center gap-1.5"
|
||||
>
|
||||
{addingFrame ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Plus className="h-3.5 w-3.5" />}
|
||||
{addingFrame ? "抽帧中…" : `把 ${videoT.toFixed(1)}s 加为关键帧`}
|
||||
</button>
|
||||
</KanbanCard>
|
||||
<>
|
||||
<KanbanCard tone="violet" tags={["视频"]} title="拖时间轴选帧">
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={videoUrl(job.id)}
|
||||
controls
|
||||
onTimeUpdate={(e) => setVideoT((e.target as HTMLVideoElement).currentTime)}
|
||||
className="block w-full bg-black rounded-md mt-1"
|
||||
/>
|
||||
<div className="kanban-meta">
|
||||
<span className="font-mono">当前 {videoT.toFixed(2)}s</span>
|
||||
<span className="ml-auto">{job.width}×{job.height} · {job.duration.toFixed(1)}s</span>
|
||||
</div>
|
||||
</KanbanCard>
|
||||
<KanbanCard tone="green" tags={["手动加帧"]} title="把当前时间点抽为关键帧">
|
||||
<button
|
||||
type="button"
|
||||
disabled={addingFrame}
|
||||
onClick={async () => {
|
||||
const t = videoRef.current?.currentTime ?? videoT
|
||||
setAddingFrame(true)
|
||||
try { await data.onAddManualFrame(t) } finally { setAddingFrame(false) }
|
||||
}}
|
||||
className="mt-1 w-full text-[12.5px] py-2 rounded-md bg-emerald-500 hover:bg-emerald-400 text-white disabled:opacity-50 inline-flex items-center justify-center gap-1.5 font-medium"
|
||||
>
|
||||
{addingFrame ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Plus className="h-3.5 w-3.5" />}
|
||||
{addingFrame ? "抽帧中…" : `+ 把 ${videoT.toFixed(1)}s 加为关键帧`}
|
||||
</button>
|
||||
</KanbanCard>
|
||||
</>
|
||||
)}
|
||||
{!hasFrames ? (
|
||||
<KanbanCard tone="pink" tags={["分镜"]} title="等待解析后抽取">
|
||||
|
||||
@@ -128,46 +128,19 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 已下载:内嵌视频 + 解析按钮 */}
|
||||
{/* 已下载:仅元数据 + 解析按钮(视频播放器和加帧 controls 在左侧看板 Keyframe section) */}
|
||||
{hasVideo && job && (
|
||||
<>
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={videoUrl(job.id)}
|
||||
controls
|
||||
onTimeUpdate={(e) => setVideoT((e.target as HTMLVideoElement).currentTime)}
|
||||
className="w-full aspect-video rounded-md bg-black border border-black/10 dark:border-white/10"
|
||||
/>
|
||||
<div className="mt-2 flex items-center justify-between text-[10.5px] font-mono text-[var(--text-faint)]">
|
||||
<span>{job.width}×{job.height} · {job.duration.toFixed(1)}s</span>
|
||||
<span className="truncate ml-2 max-w-[120px]">
|
||||
{job.url.startsWith("upload://") ? `📎 ${job.url.slice(9)}` : "🔗"}
|
||||
</span>
|
||||
<div className="rounded-md bg-black/30 border border-black/10 dark:border-white/10 px-3 py-2.5">
|
||||
<div className="flex items-center justify-between text-[10.5px] font-mono text-[var(--text-faint)] mb-1">
|
||||
<span>视频已下载</span>
|
||||
<span>{job.url.startsWith("upload://") ? "📎 上传" : "🔗 链接"}</span>
|
||||
</div>
|
||||
<div className="text-[var(--text-strong)] text-[13px] font-mono">
|
||||
{job.width}×{job.height} · {job.duration.toFixed(1)}s
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 手动拖加帧(已抽过帧才出现) */}
|
||||
{hasFrames && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={addingFrame}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation()
|
||||
const t = videoRef.current?.currentTime ?? 0
|
||||
setAddingFrame(true)
|
||||
try {
|
||||
await d.onAddManualFrame(t)
|
||||
} finally {
|
||||
setAddingFrame(false)
|
||||
}
|
||||
}}
|
||||
className="mt-2 w-full text-[11.5px] py-2 rounded-md border border-dashed border-emerald-400/40 bg-emerald-400/5 hover:bg-emerald-400/10 text-emerald-300 dark:text-emerald-300 disabled:opacity-50 flex items-center justify-center gap-1.5"
|
||||
title="把视频当前播放时间点的画面加为新关键帧"
|
||||
>
|
||||
{addingFrame ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Plus className="h-3.5 w-3.5" />}
|
||||
{addingFrame ? "抽帧中…" : `+ 把 ${videoT.toFixed(1)}s 这一帧加为关键帧`}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={isAnalyzing || d.analyzing}
|
||||
|
||||
Reference in New Issue
Block a user