auto-save 2026-05-12 19:14 (~3)

This commit is contained in:
2026-05-12 19:14:42 +08:00
parent 67bbdae12c
commit 30a4c46e1d
3 changed files with 112 additions and 97 deletions

View File

@@ -223,6 +223,13 @@
"message": "auto-save 2026-05-12 19:03 (~1)", "message": "auto-save 2026-05-12 19:03 (~1)",
"hash": "50d6390", "hash": "50d6390",
"files_changed": 1 "files_changed": 1
},
{
"ts": "2026-05-12T19:09:08+08:00",
"type": "commit",
"message": "auto-save 2026-05-12 19:08 (~3)",
"hash": "67bbdae",
"files_changed": 3
} }
] ]
} }

View File

@@ -226,7 +226,7 @@ export default function Home() {
return ( return (
<> <>
<div className="canvas-bg" /> <div className="canvas-bg" />
<main className="relative h-screen w-screen overflow-hidden flex flex-col"> <main className="relative h-screen w-screen overflow-hidden flex">
{/* 右上工具 */} {/* 右上工具 */}
<header className="absolute top-3 right-6 z-30 flex items-center gap-2 pointer-events-auto"> <header className="absolute top-3 right-6 z-30 flex items-center gap-2 pointer-events-auto">
{job && ( {job && (
@@ -240,12 +240,15 @@ export default function Home() {
<ThemeToggle /> <ThemeToggle />
</header> </header>
{/* 上区:折叠看板 — 默认仅一条 tile 栏,点击 tile 才展开 */} {/* 左侧:竖向 tile 看板(固定 200px 宽) */}
<section className="relative z-10 flex-shrink-0"> <aside
className="relative z-10 flex-shrink-0 border-r border-white/5 bg-black/20 backdrop-blur-xl overflow-y-auto"
style={{ width: 200 }}
>
<Dashboard data={nodeData} /> <Dashboard data={nodeData} />
</section> </aside>
{/* 区:紧凑 DAG 节点流图(撑满剩余度) */} {/* 区:紧凑 DAG 节点流图(撑满剩余度) */}
<section className="relative flex-1 min-h-0"> <section className="relative flex-1 min-h-0">
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}

View File

@@ -140,7 +140,7 @@ export function Dashboard({ data }: Props) {
} }
const closeTile = (_key: string) => setExpanded(new Set()) const closeTile = (_key: string) => setExpanded(new Set())
const Tile = ({ tkey, rowSpan }: { tkey: string; rowSpan?: boolean }) => { const Tile = ({ tkey }: { tkey: string }) => {
const t = TILES.find((x) => x.key === tkey)! const t = TILES.find((x) => x.key === tkey)!
const state = colState[t.key] const state = colState[t.key]
const isOpen = expanded.has(t.key) const isOpen = expanded.has(t.key)
@@ -150,52 +150,53 @@ export function Dashboard({ data }: Props) {
type="button" type="button"
onClick={() => toggleTile(t.key)} onClick={() => toggleTile(t.key)}
title={colSummary[t.key]} title={colSummary[t.key]}
className={`group rounded-md overflow-hidden border flex items-stretch transition ${ className={`group w-full rounded-md overflow-hidden border flex items-stretch transition ${
rowSpan ? "row-span-2" : "" isOpen ? "border-violet-400/60 ring-2 ring-violet-400/40" : "border-white/10 hover:border-white/20"
} ${isOpen ? "border-violet-400/60 ring-2 ring-violet-400/40" : "border-white/10 hover:border-white/20"}`} }`}
style={{ height: rowSpan ? "auto" : 30 }} style={{ height: 36 }}
> >
<div className="px-2 flex items-center gap-1.5" style={{ background: TYPE_GRAD[t.type] }}> <div className="px-2.5 flex items-center gap-2 flex-1" style={{ background: TYPE_GRAD[t.type] }}>
<span className="text-white/70 text-[9px] font-mono">{String(t.step).padStart(2, "0")}</span> <span className="text-white/70 text-[9.5px] font-mono shrink-0">{String(t.step).padStart(2, "0")}</span>
<span className="text-white">{t.icon}</span> <span className="text-white shrink-0">{t.icon}</span>
<span className="text-white text-[11.5px] font-medium whitespace-nowrap">{t.title}</span> <span className="text-white text-[12px] font-medium truncate">{t.title}</span>
</div> </div>
<div className="px-1.5 flex items-center gap-1 bg-black/40"> <div className="px-2 flex items-center gap-1 bg-black/40 shrink-0">
<span className={`h-1.5 w-1.5 rounded-full ${STATE_DOT[state]}`} /> <span className={`h-1.5 w-1.5 rounded-full ${STATE_DOT[state]}`} />
<ChevronDown className={`h-3 w-3 text-white/60 transition ${isOpen ? "rotate-180" : ""}`} /> <ChevronDown className={`h-3 w-3 text-white/60 transition ${isOpen ? "-rotate-90" : "-rotate-90"}`} />
</div> </div>
</button> </button>
) )
} }
return ( return (
<div className="w-full"> <div className="h-full flex flex-col gap-1.5 p-2.5">
{/* Tile Bar — DAG 拓扑布局input/download/split → (关键帧路 / 转录路) → compose */} <div className="text-[9.5px] uppercase tracking-[0.25em] text-[var(--text-faint)] px-1 pb-0.5">Pipeline</div>
<div className="px-3 pt-2 pb-1.5 flex items-center gap-1.5"> {/* 主线input / download / split */}
<Tile tkey="input" /> <Tile tkey="input" />
<Tile tkey="download" /> <Tile tkey="download" />
<Tile tkey="split" /> <Tile tkey="split" />
{/* 分叉:上下两路 */} {/* 分叉:上路 关键帧/生图/生视频 */}
<div className="flex-1 grid grid-rows-2 gap-1 mx-1"> <div className="border-l-2 border-violet-400/30 pl-2 ml-2 space-y-1.5">
<div className="flex gap-1.5 items-center"> <div className="text-[9px] uppercase tracking-widest text-[var(--text-faint)]"></div>
<Tile tkey="keyframe" /> <Tile tkey="keyframe" />
<Tile tkey="imagegen" /> <Tile tkey="imagegen" />
<Tile tkey="videogen" /> <Tile tkey="videogen" />
</div>
<div className="flex gap-1.5 items-center">
<Tile tkey="asr" />
<Tile tkey="translate" />
<Tile tkey="rewrite" />
</div>
</div>
<Tile tkey="compose" />
</div> </div>
{/* 分叉:下路 转录/翻译/改写 */}
<div className="border-l-2 border-pink-400/30 pl-2 ml-2 space-y-1.5">
<div className="text-[9px] uppercase tracking-widest text-[var(--text-faint)]"></div>
<Tile tkey="asr" />
<Tile tkey="translate" />
<Tile tkey="rewrite" />
</div>
{/* 合流 */}
<Tile tkey="compose" />
{/* 展开面板 — 从屏幕左侧滑出,竖向 sidebar drawer */} {/* 展开面板 — 紧贴左侧 sidebar 右侧滑出,竖向单列 */}
{expanded.size > 0 && ( {expanded.size > 0 && (
<div <div
className="fixed z-40 left-4 transition-transform duration-200" className="fixed z-40 transition-transform duration-200"
style={{ top: 80, bottom: 16, width: 380 }} style={{ left: 220, top: 16, bottom: 16, width: 380 }}
> >
{TILES.filter((t) => expanded.has(t.key)).map((t) => ( {TILES.filter((t) => expanded.has(t.key)).map((t) => (
<section <section
@@ -435,81 +436,85 @@ export function Dashboard({ data }: Props) {
) )
)} )}
{/* ---- Rewrite ---- */} {/* ---- Rewrite — Kanban ---- */}
{key === "rewrite" && ( {key === "rewrite" && (
<div className="grid grid-cols-2 gap-3 max-w-2xl"> <>
<MiniCard> <KanbanCard tone="green" tags={["产品信息"]} title="SKG 产品卖点">
<div className="text-[10px] uppercase tracking-widest text-[var(--text-faint)] mb-1.5"></div>
<textarea <textarea
rows={4} rows={5}
placeholder="SKG 产品关键卖点(占位)" placeholder="粘贴 SKG 产品关键卖点(占位)"
disabled disabled
className="w-full text-[12px] px-2 py-1.5 rounded-md bg-black/30 border border-dashed border-white/10 placeholder:text-[var(--text-faint)] text-[var(--text-strong)] resize-none opacity-70" className="w-full text-[12px] px-2 py-1.5 rounded-md bg-black/30 border border-dashed border-white/10 placeholder:text-[var(--text-faint)] text-[var(--text-strong)] resize-none opacity-70 mt-1"
/> />
</MiniCard> </KanbanCard>
<MiniCard> <KanbanCard tone="green" tags={["模型"]} title="gemini-2.5-pro">
<div className="text-[10px] uppercase tracking-widest text-[var(--text-faint)] mb-1.5"> & </div> <div className="text-[11px] text-[var(--text-soft)]"> + </div>
<div className="text-[11.5px] text-[var(--text-soft)] mb-1"><span className="font-mono text-[var(--text-strong)]">gemini-2.5-pro</span></div> <div className="kanban-meta"></div>
<div className="text-[10.5px] text-[var(--text-faint)]"></div> </KanbanCard>
</MiniCard> </>
</div>
)} )}
{/* ---- ImageGen ---- */} {/* ---- ImageGen — Kanban ---- */}
{key === "imagegen" && ( {key === "imagegen" && (
<div className="space-y-3"> <>
<MiniCard> <KanbanCard tone="rose" tags={["推荐"]} title="nano-banana-pro">
<div className="grid grid-cols-2 gap-2 text-[11.5px]"> <div className="text-[11px] text-[var(--text-soft)]">Gemini 3 Pro Image · SKG /v1/images/generations</div>
<div className="rounded-md bg-violet-500/10 border border-violet-400/30 px-2.5 py-2"> </KanbanCard>
<div className="text-[9.5px] text-violet-300 uppercase tracking-widest"></div> <KanbanCard tone="rose" tags={["备选"]} title="gpt-image-2">
<div className="text-[var(--text-strong)]">nano-banana-pro</div> <div className="text-[11px] text-[var(--text-soft)]">OpenAI · SKG </div>
<div className="text-[10px] text-[var(--text-faint)]">Gemini 3 Pro Image</div> </KanbanCard>
</div>
<div className="rounded-md bg-white/[0.04] border border-white/10 px-2.5 py-2">
<div className="text-[9.5px] text-[var(--text-faint)] uppercase tracking-widest"></div>
<div className="text-[var(--text-strong)]">gpt-image-2</div>
<div className="text-[10px] text-[var(--text-faint)]">OpenAI</div>
</div>
</div>
</MiniCard>
{data.selectedFrames.size === 0 ? ( {data.selectedFrames.size === 0 ? (
<div className="text-[11.5px] text-[var(--text-faint)] py-3 text-center"></div> <KanbanCard tone="pink" tags={["待启动"]} title="未选关键帧">
<div className="text-[11px] text-[var(--text-soft)]"> 1+ 1 </div>
</KanbanCard>
) : ( ) : (
<div className="grid grid-cols-5 gap-3"> Array.from({ length: data.selectedFrames.size }).map((_, i) => (
{Array.from({ length: data.selectedFrames.size }).map((_, i) => ( <KanbanCard
<MiniCard key={i} className="aspect-video flex items-center justify-center"> key={i}
<span className="text-[11px] text-[var(--text-faint)]">#{i + 1} </span> tone="pink"
</MiniCard> tags={[`生成图 ${i + 1}`]}
))} title={`分镜 ${i + 1} → AI 图`}
</div> >
<div className="aspect-video bg-black/40 rounded-md flex items-center justify-center text-[11px] text-[var(--text-faint)]">
</div>
</KanbanCard>
))
)} )}
</div> </>
)} )}
{/* ---- VideoGen ---- */} {/* ---- VideoGen — Kanban ---- */}
{key === "videogen" && ( {key === "videogen" && (
<div className="grid grid-cols-3 gap-3 max-w-2xl"> <>
{["Sora 2 · SKG", "Seedance · 外部", "Kling · 外部"].map((m) => ( <KanbanCard tone="violet" tags={["SKG 网关"]} title="Sora 2">
<MiniCard key={m}> <div className="text-[11px] text-[var(--text-soft)]">/v1/videos IT</div>
<div className="text-[10.5px] text-[var(--text-soft)]">{m}</div> </KanbanCard>
</MiniCard> <KanbanCard tone="violet" tags={["外部"]} title="Seedance">
))} <div className="text-[11px] text-[var(--text-soft)]"> · API key</div>
</div> </KanbanCard>
<KanbanCard tone="violet" tags={["外部"]} title="Kling">
<div className="text-[11px] text-[var(--text-soft)]"> · API key</div>
</KanbanCard>
<KanbanCard tone="rose" tags={["占位"]} title="生成视频列表">
<div className="text-[11px] text-[var(--text-soft)]"> 1 5-10s prompt</div>
</KanbanCard>
</>
)} )}
{/* ---- Compose ---- */} {/* ---- Compose — Kanban ---- */}
{key === "compose" && ( {key === "compose" && (
<div className="grid grid-cols-2 gap-3 max-w-2xl"> <>
<MiniCard className="aspect-video flex items-center justify-center"> <KanbanCard tone="cyan" tags={["成品"]} title="最终视频">
<span className="text-[11.5px] text-[var(--text-faint)]"> · </span> <div className="aspect-video bg-black/40 rounded-md flex items-center justify-center text-[11.5px] text-[var(--text-faint)] mt-1">
</MiniCard>
<MiniCard>
<div className="text-[11px] text-[var(--text-soft)]">
+ / TTS
</div> </div>
<div className="text-[10px] text-[var(--text-faint)] mt-1"> ffmpeg · API</div> </KanbanCard>
</MiniCard> <KanbanCard tone="cyan" tags={["ffmpeg"]} title="字幕 + TTS 合成">
</div> <div className="text-[11px] text-[var(--text-soft)]"> + + TTS mp4</div>
<div className="kanban-meta"> · API </div>
</KanbanCard>
</>
)} )}
</div> </div>
) )