From 67bbdae12cccec2b9c42852ff88c8dd38a9bb953 Mon Sep 17 00:00:00 2001 From: kang Date: Tue, 12 May 2026 19:09:08 +0800 Subject: [PATCH] auto-save 2026-05-12 19:08 (~3) --- .memory/worklog.json | 7 ++ web/app/globals.css | 5 ++ web/components/dashboard.tsx | 130 ++++++++++++++++------------------- 3 files changed, 72 insertions(+), 70 deletions(-) diff --git a/.memory/worklog.json b/.memory/worklog.json index acea326..bcdbc19 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -216,6 +216,13 @@ "message": "auto-save 2026-05-12 18:57 (~2)", "hash": "684930d", "files_changed": 2 + }, + { + "ts": "2026-05-12T19:03:35+08:00", + "type": "commit", + "message": "auto-save 2026-05-12 19:03 (~1)", + "hash": "50d6390", + "files_changed": 1 } ] } diff --git a/web/app/globals.css b/web/app/globals.css index dbba155..1034afd 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -308,6 +308,11 @@ 50% { box-shadow: 0 0 0 6px transparent; opacity: 0.7; } } +@keyframes drawer-in { + from { transform: translateX(-24px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + /* 覆盖 ReactFlow 内置 reserved type(input/output/default/group)的默认白底 提高 specificity:用 .react-flow 前缀确保盖过内置 CSS 变量 */ .react-flow .react-flow__node-input, diff --git a/web/components/dashboard.tsx b/web/components/dashboard.tsx index 267040c..1c6402c 100644 --- a/web/components/dashboard.tsx +++ b/web/components/dashboard.tsx @@ -80,7 +80,6 @@ export function Dashboard({ data }: Props) { const [videoT, setVideoT] = useState(0) const [addingFrame, setAddingFrame] = useState(false) const [expanded, setExpanded] = useState>(new Set()) - const [panelPos, setPanelPos] = useState<{ left: number; top: number } | null>(null) const tileRefs = useRef>({}) const fileRef = useRef(null) const videoRef = useRef(null) @@ -135,21 +134,11 @@ export function Dashboard({ data }: Props) { { key: "compose", title: "合成", type: "output", icon: , step: 10 }, ] - // 单选展开:toggle 同一 key = 收起;点其他 key = 切换;并记录 tile 位置让 panel 跟随 + // 单选展开:toggle 同一 key = 收起;点其他 key = 切换 const toggleTile = (key: string) => { - if (expanded.has(key)) { - setExpanded(new Set()) - setPanelPos(null) - return - } - setExpanded(new Set([key])) - const el = tileRefs.current[key] - if (el) { - const r = el.getBoundingClientRect() - setPanelPos({ left: r.left, top: r.bottom + 6 }) - } + setExpanded((prev) => (prev.has(key) ? new Set() : new Set([key]))) } - const closeTile = (_key: string) => { setExpanded(new Set()); setPanelPos(null) } + const closeTile = (_key: string) => setExpanded(new Set()) const Tile = ({ tkey, rowSpan }: { tkey: string; rowSpan?: boolean }) => { const t = TILES.find((x) => x.key === tkey)! @@ -202,14 +191,17 @@ export function Dashboard({ data }: Props) { - {/* 展开面板 — 跟随被点 tile 的位置,从 tile 正下方冒出(fixed 定位) */} - {expanded.size > 0 && panelPos && ( -
+ {/* 展开面板 — 从屏幕左侧滑出,竖向 sidebar drawer */} + {expanded.size > 0 && ( +
{TILES.filter((t) => expanded.has(t.key)).map((t) => (
@@ -240,17 +232,16 @@ export function Dashboard({ data }: Props) { return (
- {/* ---- Input ---- */} + {/* ---- Input — Kanban ---- */} {key === "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-black/40 border border-white/10 outline-none text-[var(--text-strong)] placeholder:text-[var(--text-faint)] focus:ring-2 focus:ring-[var(--ring)] disabled:opacity-40" + className="w-full text-[12px] px-2.5 py-1.5 rounded-md bg-black/30 border border-white/15 outline-none text-[var(--text-strong)] placeholder:text-[var(--text-faint)] focus:ring-2 focus:ring-[var(--ring)] disabled:opacity-40 mt-1" />
-
+ {hasVideo && ( - -
下一步
+ {job && ( -
- {job.url.startsWith("upload://") ? `📎 ${job.url.slice(9)}` : job.url} +
+ + {job.url.startsWith("upload://") ? `📎 ${job.url.slice(9)}` : job.url} +
)} - + )} -
+ )} - {/* ---- Download ---- */} - {key === "download" && hasVideo && job && ( -
- - -
- -
元数据
+ {/* ---- Download — Kanban ---- */} + {key === "download" && ( + !hasVideo ? ( + +
TikTok / yt-dlp 兼容站点
+
+ ) : ( + <> + + +
-
分辨率
{job.width}×{job.height}
-
时长
{job.duration.toFixed(1)}s
-
来源
{job.url.startsWith("upload://") ? "上传" : "yt-dlp"}
+
分辨率
{job!.width}×{job!.height}
+
时长
{job!.duration.toFixed(1)}s
+
来源
{job!.url.startsWith("upload://") ? "上传" : "TK"}
-
-
-
- )} - {key === "download" && !hasVideo && ( -
{isDownloading ? "yt-dlp 下载中…" : "等待提交"}
+
+ + ) )} - {/* ---- Split ---- */} + {/* ---- Split — Kanban ---- */} {key === "split" && ( -
- -
视频流
-
→ 关键帧抽取
-
ffmpeg fast seek + Laplacian 评分
-
- -
音频流
-
→ ASR (16kHz mono wav)
-
ffmpeg -vn -ac 1 -ar 16000
-
-
+ <> + +
fast seek + Laplacian 方差评分
+
+ +
16kHz mono · pcm_s16le wav
+
+ -vn -ac 1 -ar 16000 +
+
+ )} {/* ---- Keyframe — Kanban 卡片 ---- */}