From aa5ad08b9ba615e6bfe0ff95074d869efd4616a9 Mon Sep 17 00:00:00 2001 From: kang Date: Tue, 12 May 2026 18:29:59 +0800 Subject: [PATCH] auto-save 2026-05-12 18:29 (+1, ~1) --- web/app/page.tsx | 82 ++++--- web/components/dashboard.tsx | 411 +++++++++++++++++++++++++++++++++++ 2 files changed, 449 insertions(+), 44 deletions(-) create mode 100644 web/components/dashboard.tsx diff --git a/web/app/page.tsx b/web/app/page.tsx index 0aaea6a..95f108a 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -13,6 +13,7 @@ import { type NodeData, } from "@/components/nodes" import { ThemeToggle } from "@/components/theme-toggle" +import { Dashboard } from "@/components/dashboard" import { addManualFrame, analyzeJob, createJob, getJob, uploadJob, type Job } from "@/lib/api" import { FrameLightbox } from "@/components/lightbox" @@ -225,54 +226,47 @@ export default function Home() { return ( <>
-
- {/* 顶部栏 */} -
-
-
SKG · AI Material Pipeline
-

- TK 二创工作台 - / Node Workflow -

-
-
- {job && ( -
- JOB - {job.id.slice(0, 8)} - · - {job.message || job.status} -
- )} - -
+
+ {/* 右上工具 */} +
+ {job && ( +
+ JOB + {job.id.slice(0, 8)} + · + {job.message || job.status} +
+ )} +
- {/* 画布 */} - - - - - + {/* 上区:看板(60vh)— 10 列映射底部节点 */} +
+ +
- {/* 底部说明 */} -
- MVP 第一冲刺:步骤 1-6 已通 · 7-10 占位 · 拖拽节点 · 滚轮缩放 -
+ {/* 下区:紧凑 DAG 节点流图 */} +
+ + + + + +
- + {/* Lightbox 看大图 */} {job && ( diff --git a/web/components/dashboard.tsx b/web/components/dashboard.tsx new file mode 100644 index 0000000..b1ba948 --- /dev/null +++ b/web/components/dashboard.tsx @@ -0,0 +1,411 @@ +"use client" +import { useRef, useState } from "react" +import { + Link2, Upload, Download, Scissors, Image as ImageIcon, + Mic, Languages, FileEdit, Sparkles, Film, FileVideo, Loader2, Plus, Check, +} from "lucide-react" +import { type Job, frameUrl, videoUrl } from "@/lib/api" +import { type NodeData } from "@/components/nodes" + +const COL_W = 280 + +type ColType = "input" | "process" | "ai" | "output" +const TYPE_GRAD: Record = { + input: "linear-gradient(135deg, #6366f1, #a855f7)", + process: "linear-gradient(135deg, #f59e0b, #ef4444)", + ai: "linear-gradient(135deg, #d946ef, #ec4899)", + output: "linear-gradient(135deg, #10b981, #06b6d4)", +} + +type ColState = "pending" | "running" | "done" | "failed" +const STATE_DOT: Record = { + pending: "bg-white/20", + running: "bg-violet-400 animate-pulse", + done: "bg-emerald-400", + failed: "bg-red-400", +} + +function ColumnHeader({ type, title, label, state }: { type: ColType; title: string; label: string; state: ColState }) { + return ( +
+
+ {title} + +
+
+ {label} +
+
+ ) +} + +function MiniCard({ children, className = "", onClick }: { children: React.ReactNode; className?: string; onClick?: (e: React.MouseEvent) => void }) { + return ( +
+ {children} +
+ ) +} + +interface Props { + data: NodeData +} + +export function Dashboard({ data }: Props) { + const { job } = data + const [url, setUrl] = useState("") + const [videoT, setVideoT] = useState(0) + const [addingFrame, setAddingFrame] = useState(false) + const fileRef = useRef(null) + const videoRef = useRef(null) + + /* 状态推导 */ + const hasVideo = !!job?.video_url + const isDownloading = job?.status === "downloading" || job?.status === "created" + const isSplitting = job?.status === "splitting" + const isAnalyzing = !!job && ["splitting", "frames_extracted", "transcribing"].includes(job.status) + const hasFrames = (job?.frames.length ?? 0) > 0 + const hasTranscript = (job?.transcript.length ?? 0) > 0 + const hasZh = job?.transcript.some((s) => s.zh) ?? false + const isFailed = job?.status === "failed" + + /* 每列状态 */ + const colState: Record = { + input: !job ? "pending" : "done", + download: !job ? "pending" : isDownloading ? "running" : hasVideo ? "done" : isFailed && job.progress < 30 ? "failed" : "pending", + split: !job ? "pending" : isSplitting ? "running" : hasFrames ? "done" : isFailed && job.progress >= 30 && job.progress < 50 ? "failed" : "pending", + keyframe: !job ? "pending" : (isSplitting && !hasFrames) ? "running" : hasFrames ? "done" : isFailed && job.progress >= 50 && job.progress < 70 ? "failed" : "pending", + asr: !job ? "pending" : job.status === "transcribing" ? "running" : hasTranscript ? "done" : isFailed && job.progress >= 70 ? "failed" : "pending", + translate: !job ? "pending" : job.status === "transcribing" ? "running" : hasZh ? "done" : "pending", + rewrite: "pending", + imagegen: "pending", + videogen: "pending", + compose: "pending", + } + + return ( +
+
+ + {/* 1. Input */} +
+ +
+ + setUrl(e.target.value)} + placeholder="粘贴 TikTok 链接" + disabled={isDownloading || data.submitting} + className="w-full text-[12px] px-2.5 py-1.5 rounded-md bg-white/60 dark:bg-black/40 border border-black/10 dark:border-white/10 outline-none text-[var(--text-strong)] placeholder:text-[var(--text-faint)] focus:ring-2 focus:ring-[var(--ring)] disabled:opacity-40" + /> +
+ + + { + const f = e.target.files?.[0] + if (f) data.onUploadFile(f) + e.target.value = "" + }} + /> +
+
+ {hasVideo && ( + + + + )} + {job && ( + +
来源
+
+ {job.url.startsWith("upload://") ? `📎 ${job.url.slice(9)}` : job.url} +
+
+ )} +
+
+ + {/* 2. Download */} +
+ +
+ +
+ {job?.url.startsWith("upload://") ? "本地上传 · 跳过下载" : "TikTok / yt-dlp 兼容站点"} +
+
+ {hasVideo && job && ( + <> + + + +
+
+
分辨率
+
{job.width}×{job.height}
+
+
+
时长
+
{job.duration.toFixed(1)}s
+
+
+
+ + )} +
+
+ + {/* 3. Split */} +
+ +
+ +
视频流
+
→ 关键帧抽取
+
+ +
音频流
+
→ ASR (16kHz mono wav)
+
+
+
+ + {/* 4. Keyframe — 5 张缩略图 */} +
+ +
+ {hasVideo && job && ( + + + + )} + {!hasFrames ? ( + +
+ 等待解析后抽取(默认 5 张) +
+
+ ) : ( + job!.frames.map((f) => { + const isSel = data.selectedFrames.has(f.index) + return ( + + +
+ 分镜 {f.index + 1} + +
+
+ ) + }) + )} +
+
+ + {/* 5. ASR */} +
+ +
+ {!hasTranscript ? ( + +
+ {colState.asr === "running" ? "Gemini 转录中…" : "等待关键帧抽取"} +
+
+ ) : ( + job!.transcript.map((s) => ( + +
+ {s.start.toFixed(1)}s → {s.end.toFixed(1)}s +
+
{s.en}
+
+ )) + )} +
+
+ + {/* 6. Translate */} +
+ +
+ {!hasZh ? ( + +
等待 ASR 完成
+
+ ) : ( + job!.transcript.map((s) => ( + +
+ {s.start.toFixed(1)}s → {s.end.toFixed(1)}s +
+
{s.zh || 翻译中…}
+
+ )) + )} +
+
+ + {/* 7. Rewrite */} +
+ +
+ +
产品信息
+