auto-save 2026-05-12 19:14 (~3)
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user