auto-save 2026-05-14 02:30 (+2, ~4)

This commit is contained in:
2026-05-14 02:31:01 +08:00
parent eace01e94a
commit 95fbb0cbc6
6 changed files with 364 additions and 56 deletions

View File

@@ -2901,6 +2901,19 @@
"type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令claude · 1 项未提交变更 · 最近提交auto-save 2026-05-14 02:19 (~4)",
"files_changed": 1
},
{
"ts": "2026-05-14T02:25:30+08:00",
"type": "commit",
"message": "auto-save 2026-05-14 02:25 (~2)",
"hash": "eace01e",
"files_changed": 2
},
{
"ts": "2026-05-13T18:28:48Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 3 项未提交变更 · 最近提交auto-save 2026-05-14 02:25 (~2)",
"files_changed": 3
}
]
}

View File

@@ -0,0 +1,36 @@
- generic [active] [ref=e1]:
- button "Open Next.js Dev Tools" [ref=e7] [cursor=pointer]:
- img [ref=e8]
- main [ref=e14]:
- button "自动排版 · 保留每个节点的尺寸,重新排好间距和列布局" [ref=e16]:
- img [ref=e17]
- application [ref=e24]:
- group [ref=e27]:
- generic [ref=e29]:
- generic [ref=e30]:
- img [ref=e32]
- generic [ref=e35]: 输入 · Input
- button "钉住 · 锁定位置与尺寸" [ref=e38]:
- img [ref=e39]
- generic [ref=e42]:
- generic [ref=e43]: STEP 1 · 待运行
- textbox "粘贴 TikTok 链接" [ref=e44]
- generic [ref=e45]:
- button "提交链接" [disabled] [ref=e46]
- button "上传" [ref=e47]:
- img [ref=e48]
- text: 上传
- generic "拖动调整宽度" [ref=e52]
- generic "拖动调整大小(宽 × 高)" [ref=e53]
- img
- generic "Control Panel" [ref=e54]:
- button "Zoom In" [ref=e55] [cursor=pointer]:
- img [ref=e56]
- button "Zoom Out" [ref=e58] [cursor=pointer]:
- img [ref=e59]
- button "Fit View" [ref=e61] [cursor=pointer]:
- img [ref=e62]
- button "Toggle Interactivity" [ref=e64] [cursor=pointer]:
- img [ref=e65]
- img "Mini Map" [ref=e68]
- region "Notifications alt+T"

View File

@@ -0,0 +1,245 @@
- generic [active] [ref=e1]:
- generic [ref=e6] [cursor=pointer]:
- button "Open Next.js Dev Tools" [ref=e7]:
- img [ref=e8]
- generic [ref=e71]:
- button "Open issues overlay" [ref=e72]:
- generic [ref=e73]:
- generic [ref=e74]: "0"
- generic [ref=e75]: "1"
- generic [ref=e76]: Issue
- button "Collapse issues badge" [ref=e77]:
- img [ref=e78]
- main [ref=e14]:
- button "自动排版 · 保留每个节点的尺寸,重新排好间距和列布局" [ref=e16]:
- img [ref=e17]
- button "切到明亮主题" [ref=e81]:
- img [ref=e82]
- application [ref=e24]:
- generic [ref=e26]:
- generic:
- generic:
- img:
- group "Edge from input to visual" [ref=e88] [cursor=pointer]
- img:
- group "Edge from input to audio" [ref=e91] [cursor=pointer]
- img:
- group "Edge from visual to compose" [ref=e94] [cursor=pointer]
- img:
- group "Edge from audio to compose" [ref=e97] [cursor=pointer]
- generic:
- group [ref=e27]:
- generic [ref=e28]:
- generic [ref=e100]:
- button "再上传一个视频" [ref=e101]:
- img [ref=e102]
- button "64.5s" [ref=e104]:
- generic [ref=e106]: 64.5s
- button "72.4s" [ref=e108]:
- generic [ref=e110]: 72.4s
- button "64.5s" [ref=e112]:
- generic [ref=e114]: 64.5s
- button "71.4s" [ref=e116]:
- generic [ref=e118]: 71.4s
- button "72.4s" [ref=e120]:
- generic [ref=e122]: 72.4s
- button "71.4s" [ref=e124]:
- generic [ref=e126]: 71.4s
- button "71.4s" [ref=e128]:
- generic [ref=e130]: 71.4s
- button "71.4s" [ref=e132]:
- generic [ref=e134]: 71.4s
- button "71.4s" [ref=e136]:
- generic [ref=e138]: 71.4s
- button "71.4s" [ref=e140]:
- generic [ref=e142]: 71.4s
- button "8.0s" [ref=e144]:
- generic [ref=e146]: 8.0s
- button "8.0s" [ref=e148]:
- generic [ref=e150]: 8.0s
- button "8.0s" [ref=e152]:
- generic [ref=e154]: 8.0s
- button "8.0s" [ref=e156]:
- generic [ref=e158]: 8.0s
- button "…" [ref=e160]:
- img [ref=e162]
- generic [ref=e164]:
- button "…" [ref=e166]:
- img [ref=e168]
- generic [ref=e170]:
- button "…" [ref=e172]:
- img [ref=e174]
- generic [ref=e176]:
- generic [ref=e29]:
- generic [ref=e30]:
- img [ref=e32]
- generic [ref=e35]: 输入 · Input
- generic [ref=e36]:
- img [ref=e177]
- button "钉住 · 锁定位置与尺寸" [ref=e38]:
- img [ref=e39]
- generic [ref=e42]:
- generic [ref=e43]: STEP 1 · 视频就绪 · 完成
- textbox "再加一个 TK 链接" [ref=e180]
- generic [ref=e45]:
- button "+ 加链接" [disabled] [ref=e181]
- button "再传一个" [ref=e182]:
- img [ref=e48]
- text: 再传一个
- generic [ref=e183]:
- generic [ref=e184]: 1080×1920 · 64.5s
- generic [ref=e185]: 🔗 链接
- button "重新解析" [ref=e186]
- generic "拖动调整宽度" [ref=e52]
- generic "拖动调整大小(宽 × 高)" [ref=e53]
- group [ref=e187]:
- generic [ref=e188]:
- generic [ref=e189]:
- generic [ref=e190]:
- button "分镜 10 1.66s" [ref=e191]:
- img "分镜 10" [ref=e192]
- generic [ref=e193]: 1.66s
- button "📋" [ref=e194]
- button "删除该关键帧" [ref=e195]:
- img [ref=e196]
- generic [ref=e199]:
- button "分镜 1 24.73s" [ref=e200]:
- img "分镜 1" [ref=e201]
- generic [ref=e202]: 24.73s
- button "📋" [ref=e203]
- button "删除该关键帧" [ref=e204]:
- img [ref=e205]
- generic [ref=e208]:
- button "分镜 2 33.61s" [ref=e209]:
- img "分镜 2" [ref=e210]
- generic [ref=e211]: 33.61s
- button "📋" [ref=e212]
- button "删除该关键帧" [ref=e213]:
- img [ref=e214]
- generic [ref=e217]:
- button "分镜 3 37.70s" [ref=e218]:
- img "分镜 3" [ref=e219]
- generic [ref=e220]: 37.70s
- button "📋" [ref=e221]
- button "删除该关键帧" [ref=e222]:
- img [ref=e223]
- generic [ref=e226]:
- button "分镜 4 39.42s" [ref=e227]:
- img "分镜 4" [ref=e228]
- generic [ref=e229]: 39.42s
- button "📋" [ref=e230]
- button "删除该关键帧" [ref=e231]:
- img [ref=e232]
- generic [ref=e235]:
- button "分镜 5 43.13s" [ref=e236]:
- img "分镜 5" [ref=e237]
- generic [ref=e238]: 43.13s
- button "📋" [ref=e239]
- button "删除该关键帧" [ref=e240]:
- img [ref=e241]
- generic [ref=e244]:
- button "分镜 6 45.05s" [ref=e245]:
- img "分镜 6" [ref=e246]
- generic [ref=e247]: 45.05s
- button "📋" [ref=e248]
- button "删除该关键帧" [ref=e249]:
- img [ref=e250]
- generic [ref=e253]:
- button "分镜 7 53.60s" [ref=e254]:
- img "分镜 7" [ref=e255]
- generic [ref=e256]: 53.60s
- button "📋" [ref=e257]
- button "删除该关键帧" [ref=e258]:
- img [ref=e259]
- generic [ref=e262]:
- button "分镜 8 55.96s" [ref=e263]:
- img "分镜 8" [ref=e264]
- generic [ref=e265]: 55.96s
- button "📋" [ref=e266]
- button "删除该关键帧" [ref=e267]:
- img [ref=e268]
- generic [ref=e271]:
- button "分镜 9 58.39s" [ref=e272]:
- img "分镜 9" [ref=e273]
- generic [ref=e274]: 58.39s
- button "📋" [ref=e275]
- button "删除该关键帧" [ref=e276]:
- img [ref=e277]
- generic [ref=e280]:
- button "透明骷髅 元素" [ref=e281]:
- img "透明骷髅" [ref=e282]
- generic [ref=e283]: 元素
- button "📋" [ref=e284]
- button "删除该提取图" [ref=e285]:
- img [ref=e286]
- generic [ref=e289]:
- generic [ref=e291]:
- img [ref=e293]
- generic [ref=e298]: 画面工作台 · Visual Lab
- generic [ref=e299]:
- img [ref=e300]
- button "钉住 · 锁定位置与尺寸" [ref=e304]:
- img [ref=e305]
- generic [ref=e308]:
- generic [ref=e309]: STEP 2-7 · 10 帧 · 完成
- generic [ref=e310]:
- button "10 关键帧" [ref=e311]:
- generic [ref=e312]: "10"
- generic [ref=e313]: 关键帧
- button "1 元素 / 编排" [ref=e314]:
- generic [ref=e315]: "1"
- generic [ref=e316]: 元素 / 编排
- generic [ref=e317]:
- generic [ref=e318]: "0"
- generic [ref=e319]: 视频任务
- generic [ref=e320]: 1 已清洗 · 1 已抠图 · 0/10 入编排 · 0 已完成
- generic "拖动调整宽度" [ref=e322]
- generic "拖动调整大小(宽 × 高)" [ref=e323]
- group [ref=e324]:
- generic [ref=e325]:
- generic [ref=e327]:
- img [ref=e329]
- generic [ref=e332]: 音频处理 · Audio
- button "钉住 · 锁定位置与尺寸" [ref=e335]:
- img [ref=e336]
- generic [ref=e339]:
- generic [ref=e340]: STEP 3 · 待运行
- generic [ref=e341]:
- text: 音轨 → ASR 转录 → 英中翻译 → 接 SKG 卖点改写文案
- text: Gemini 2.5 Flash
- generic "拖动调整宽度" [ref=e343]
- generic "拖动调整大小(宽 × 高)" [ref=e344]
- group [ref=e345]:
- generic [ref=e346]:
- generic [ref=e348]:
- img [ref=e350]
- generic [ref=e354]: 合成成品 · Compose
- button "钉住 · 锁定位置与尺寸" [ref=e357]:
- img [ref=e358]
- generic [ref=e361]:
- generic [ref=e362]: STEP 8 · ffmpeg + 字幕 · 待运行
- generic [ref=e363]:
- text: 视频片段 + 字幕 / TTS
- text: → 最终 mp4 输出
- generic "拖动调整宽度" [ref=e364]
- generic "拖动调整大小(宽 × 高)" [ref=e365]
- img
- generic "Control Panel" [ref=e54]:
- button "Zoom In" [ref=e55] [cursor=pointer]:
- img [ref=e56]
- button "Zoom Out" [ref=e58] [cursor=pointer]:
- img [ref=e59]
- button "Fit View" [ref=e61] [cursor=pointer]:
- img [ref=e62]
- button "Toggle Interactivity" [ref=e64] [cursor=pointer]:
- img [ref=e65]
- img "Mini Map" [ref=e68]
- region "Notifications alt+T":
- list:
- listitem [ref=e369]:
- img [ref=e371]
- generic [ref=e374]: 已自动排版 · 保留每个节点的尺寸
- listitem [ref=e375]:
- img [ref=e377]
- generic [ref=e380]: 📥 视频已就绪 — 请点 Input 节点里的「点这里开始解析」按钮
- alert [ref=e381]

View File

@@ -557,7 +557,7 @@
<div class="step"><div class="num">4</div><h3>Vision 识别</h3><p>识别场景和候选元素,只是候选,不应锁死。</p></div>
<div class="step"><div class="num">5</div><h3>元素提取</h3><p>编辑/新增/删除元素,对元素反复生成提取图。</p></div>
<div class="step"><div class="num">6</div><h3>元素改造</h3><p>把参考主体、场景、动作和 SKG 产品放入分镜结构。</p></div>
<div class="step"><div class="num">7</div><h3>生成视频</h3><p>用分镜 4 图槽、改造目标和时长调用 Seedance / Kling / Veo 3 生视频 API结果回写到 Video Gen 节点。</p></div>
<div class="step"><div class="num">7</div><h3>生成视频</h3><p>用分镜 4 图槽、改造目标和时长调用 Seedance / Kling / Veo 3 生视频 API结果回写到画面工作台节点。</p></div>
<div class="step"><div class="num">8</div><h3>合成成品</h3><p>片段、字幕、配音、转场合成最终 mp4。当前未实现。</p></div>
</div>
</section>
@@ -570,7 +570,7 @@
<table>
<tbody>
<tr><td><code>web/app/page.tsx</code></td><td>产品工作台主状态jobs、activeJobId、selectedFrames、clipboard、ReactFlow 节点和边。</td></tr>
<tr><td><code>web/components/nodes/index.tsx</code></td><td>DAG 节点定义Input、Keyframe、ASR、Translate、Rewrite、StoryboardVideoGen、Compose</td></tr>
<tr><td><code>web/components/nodes/index.tsx</code></td><td>DAG 节点定义Input、VisualLab、Audio、Compose、KeyframePanel旧 Keyframe/Storyboard/VideoGen 组件保留但不再挂主画布</td></tr>
<tr><td><code>web/components/lightbox.tsx</code></td><td>镜头拆解和元素提取的主工作面板:清洗、识别、元素编辑、区域提取、抠图。</td></tr>
<tr><td><code>web/components/storyboard-bar.tsx</code></td><td>顶部分镜编排条:展示选入编排的关键帧,并作为唯一分镜导航。</td></tr>
<tr><td><code>web/components/storyboard-workbench.tsx</code></td><td>顶部分镜编排条下方的明细区4 图槽、改造目标、时长、自动保存。</td></tr>
@@ -595,9 +595,9 @@
<pre>前端主链路:
web/app/page.tsx
-> ReactFlow 节点web/components/nodes/index.tsx
-> 画布内镜头拆解面板KeyframeNode 内嵌 web/components/lightbox.tsx
-> 顶部分镜条:web/components/storyboard-bar.tsx
-> 分镜工作台web/components/storyboard-workbench.tsx
-> 画布Input → VisualLab / Audio → Compose
-> 画布内镜头拆解面板VisualLabNode 打开 keyframePanel内嵌 web/components/lightbox.tsx
-> 分镜工作台web/components/storyboard-workbench.tsx(底层保留)
-> API 契约web/lib/api.ts
后端主链路:
@@ -616,25 +616,15 @@ api/main.py
<div><strong>适合怎么描述</strong><span>“输入/下载/视频就绪这个节点应该如何提示用户下一步”。</span></div>
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>镜头拆解节点上方关键帧</span></div>
<div><strong>主要源码</strong><span><code>KeyframeNode</code> 内嵌 <code>FrameLightbox</code>;后端 <code>/frames</code><code>/describe</code><code>/cleanup</code></span></div>
<div><strong>适合怎么描述</strong><span>关键帧详情面板在无限画布上怎么展示、缩放、跟随;缩略图 hover 原尺寸预览贴缩略图上边缘,不是贴节点卡片上边缘”。</span></div>
<div><strong>你看到的区域</strong><span>画面工作台 · Visual Lab</span></div>
<div><strong>主要源码</strong><span><code>VisualLabNode</code> in <code>web/components/nodes/index.tsx</code>;它汇总关键帧、元素 cutout 和视频任务缩略图</span></div>
<div><strong>适合怎么描述</strong><span>画布上只保留一个视觉展示卡片;缩略图 hover 原尺寸预览贴缩略图上边缘,点击再进入镜头处理 / 分镜编排 / 视频任务操作”。</span></div>
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>元素列表和提取图</span></div>
<div><strong>主要源码</strong><span><code>FrameLightbox</code>;类型 <code>KeyElement</code>;接口 <code>addElement/updateElement/deleteElement/cutoutElement/deleteCutout</code></span></div>
<div><strong>适合怎么描述</strong><span>“Vision 识别出来的是候选,用户要能修正、重复提取、删除错误元素”。</span></div>
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>元素改造 · Storyboard 节点</span></div>
<div><strong>主要源码</strong><span><code>StoryboardNode</code>;上方元素缩略图来自所有已提取 cutouts。</span></div>
<div><strong>适合怎么描述</strong><span>“这里是素材入口,不是最终视频编辑器;缩略图 hover 预览仍在无限画布里,并贴当前缩略图的上边缘”。</span></div>
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>顶部分镜条</span></div>
<div><strong>主要源码</strong><span><code>StoryboardBar</code>;只展示 selectedFrames不负责提取元素。</span></div>
<div><strong>适合怎么描述</strong><span>“选好的分镜如何按时间序组织,以及如何进入具体分镜编排”。</span></div>
</div>
<div class="flow-row">
<div><strong>你看到的区域</strong><span>顶部分镜头编排下拉面板</span></div>
<div><strong>主要源码</strong><span><code>StoryboardWorkbench</code>;保存到 <code>frame.storyboard</code>;接口 <code>PUT /storyboard</code></span></div>
@@ -736,16 +726,10 @@ api/main.py
<td><code>page.tsx</code><code>InputNode</code><code>api/main.py</code></td>
</tr>
<tr>
<td><span class="tag orange">镜头拆解 / 元素提取</span></td>
<td>关键帧选择、清洗、Vision 候选、元素编辑、区域提取、元素 cutout</td>
<td>不要把 Vision 结果当最终结论;不要点击元素就跳走</td>
<td><code>KeyframeNode</code><code>FrameLightbox</code>元素接口</td>
</tr>
<tr>
<td><span class="tag violet">元素改造 Storyboard</span></td>
<td>展示可用元素素材,承接“参考元素 → SKG 画面”的入口。</td>
<td>不要等同于最终视频生成;不要暗示复刻原视频。</td>
<td><code>StoryboardNode</code><code>StoryboardBar</code></td>
<td><span class="tag violet">画面工作台 Visual Lab</span></td>
<td>在一个画布卡片里展示关键帧、元素 cutout 和视频任务;点击缩略图进入对应处理面板</td>
<td>不要在主卡片里堆复杂表单;主卡片只做状态总览和入口</td>
<td><code>VisualLabNode</code><code>FrameLightbox</code><code>StoryboardWorkbench</code>、视频任务接口</td>
</tr>
<tr>
<td><span class="tag violet">分镜工作台</span></td>
@@ -760,10 +744,10 @@ api/main.py
<td><code>ASRNode</code><code>TranslateNode</code><code>RewriteNode</code>、ASR 接口</td>
</tr>
<tr>
<td><span class="tag green">Video Gen / Compose</span></td>
<td>承载生视频任务状态和完成后的 MP4</td>
<td>分镜工作台提交任务Video Gen 节点只展示任务和结果</td>
<td><code>VideoGenNode</code><code>/storyboard/video</code><code>generated_videos</code></td>
<td><span class="tag green">Video / Compose</span></td>
<td>视频任务状态展示在 Visual LabCompose 承载最终合成</td>
<td>不要把 Compose 提前变成视频生成控制台</td>
<td><code>VisualLabNode</code><code>/storyboard/video</code><code>generated_videos</code><code>ComposeNode</code></td>
</tr>
</tbody>
</table>
@@ -831,6 +815,18 @@ api/main.py
<h2>变更记录</h2>
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
<div class="changelog">
<article class="change">
<header>
<h3>2026-05-14 · 三个视觉节点合并为画面工作台</h3>
<span class="tag violet">VisualLab</span>
<span class="tag blue">Canvas</span>
</header>
<div class="body">
<p><strong>问题:</strong>镜头拆解、元素改造、生成视频三个卡片在主画布上占同等权重,但它们只是视觉素材状态展示和入口;真正处理都在点击后的工作台/面板中完成。</p>
<p><strong>改动:</strong>新增 <code>VisualLabNode</code>,把关键帧、元素 cutout、视频任务缩略图合并到一个“画面工作台 · Visual Lab”卡片里DAG 从 <code>Input → Keyframe → Storyboard → VideoGen → Compose</code> 简化为 <code>Input → VisualLab → Compose</code>,音频线仍独立。</p>
<p><strong>影响:</strong><code>web/app/page.tsx</code><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。底层数据、接口和旧节点组件暂不删除。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 修复节点右下角缩放点击偏移</h3>

View File

@@ -380,3 +380,32 @@
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--divider); border-radius: 4px; }
::-webkit-scrollbar-track { background: transparent; }
/* 节点内缩略图浮条(横滚)专用:加粗 + 紫色高亮 + 大可拖区,避免在画布 zoom 下拖不到 */
.react-flow__node .overflow-x-auto {
scrollbar-color: rgba(167, 139, 250, 0.65) rgba(255, 255, 255, 0.08);
scrollbar-width: auto;
}
.react-flow__node .overflow-x-auto::-webkit-scrollbar {
height: 14px;
}
.react-flow__node .overflow-x-auto::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.06);
border-radius: 8px;
margin: 0 4px;
border: 1px solid rgba(255, 255, 255, 0.08);
}
.react-flow__node .overflow-x-auto::-webkit-scrollbar-thumb {
background: rgba(167, 139, 250, 0.55);
border-radius: 8px;
border: 2px solid rgba(255, 255, 255, 0.18);
min-width: 48px;
background-clip: padding-box;
}
.react-flow__node .overflow-x-auto::-webkit-scrollbar-thumb:hover {
background: rgba(167, 139, 250, 0.85);
border-color: rgba(255, 255, 255, 0.3);
}
.react-flow__node .overflow-x-auto::-webkit-scrollbar-thumb:active {
background: rgba(217, 70, 239, 0.95);
}

View File

@@ -9,13 +9,11 @@ import {
import { Toaster, toast } from "sonner"
import { LayoutGrid } from "lucide-react"
import {
InputNode, KeyframeNode, AudioNode,
StoryboardNode, VideoGenNode, ComposeNode, KeyframePanelNode,
InputNode, VisualLabNode, AudioNode,
ComposeNode, KeyframePanelNode,
type NodeData,
} from "@/components/nodes"
import { ThemeToggle } from "@/components/theme-toggle"
import { StoryboardBar } from "@/components/storyboard-bar"
import { StoryboardWorkbench } from "@/components/storyboard-workbench"
import {
addManualFrame, analyzeJob, createJob, getJob, listJobs, uploadJob, deleteFrame, deleteGeneratedImage,
deleteGeneratedVideo, deleteCutout, generateStoryboardVideo,
@@ -25,10 +23,8 @@ import { VideoLightbox } from "@/components/video-lightbox"
const NODE_TYPES = {
input: InputNode,
keyframe: KeyframeNode,
visual: VisualLabNode,
audio: AudioNode,
storyboard: StoryboardNode,
videogen: VideoGenNode,
compose: ComposeNode,
keyframePanel: KeyframePanelNode,
}
@@ -36,15 +32,13 @@ const NODE_TYPES = {
const KEYFRAME_PANEL_ID = "keyframe-detail-panel"
// 合并 input + download + split 为一个节点
// 分叉:上路 input → keyframe → storyboard → videogen
// 分叉:上路 input → visual lab
// 下路 input → audio ──────────────────────────→ compose
const LAYOUT: Array<{ id: string; type: keyof typeof NODE_TYPES; x: number; y: number; w: number }> = [
{ id: "input", type: "input", x: 40, y: 240, w: 320 },
{ id: "keyframe", type: "keyframe", x: 460, y: 60, w: 360 },
{ id: "visual", type: "visual", x: 460, y: 60, w: 620 },
{ id: "audio", type: "audio", x: 460, y: 440, w: 320 },
{ id: "storyboard", type: "storyboard", x: 880, y: 60, w: 360 },
{ id: "videogen", type: "videogen", x: 1260, y: 60, w: 280 },
{ id: "compose", type: "compose", x: 1640, y: 240, w: 320 },
{ id: "compose", type: "compose", x: 1160, y: 240, w: 320 },
]
const NODE_SIZES_KEY = "skg-tk:node-sizes:v2"
@@ -74,11 +68,9 @@ function loadNodePins(): string[] {
}
const EDGES_RAW: Array<[string, string]> = [
["input", "keyframe"],
["input", "visual"],
["input", "audio"],
["keyframe", "storyboard"],
["storyboard", "videogen"],
["videogen", "compose"],
["visual", "compose"],
["audio", "compose"],
]
@@ -521,9 +513,7 @@ export default function Home() {
// 按管线列分组(顶 → 底):图层 1 输入 → 5 合成
const COLUMNS: string[][] = [
["input"],
["keyframe", "audio"],
["storyboard"],
["videogen"],
["visual", "audio"],
["compose"],
]
const GAP_X = 80
@@ -602,11 +592,11 @@ export default function Home() {
let shouldFocusNewPanel = false
setNodes((prev) => {
const keyframeNode = prev.find((n) => n.id === "keyframe")
const visualNode = prev.find((n) => n.id === "visual")
const inputNode = prev.find((n) => n.id === "input")
const defaultPosition = {
x: (inputNode?.position.x ?? 40) - 820,
y: (keyframeNode?.position.y ?? 60),
y: (visualNode?.position.y ?? 60),
}
const exists = prev.some((n) => n.id === KEYFRAME_PANEL_ID)
if (exists) {
@@ -637,7 +627,7 @@ export default function Home() {
if (shouldFocusNewPanel) {
window.setTimeout(() => {
flowRef.current?.fitView?.({
nodes: [{ id: KEYFRAME_PANEL_ID }, { id: "keyframe" }],
nodes: [{ id: KEYFRAME_PANEL_ID }, { id: "visual" }],
padding: 0.18,
duration: 260,
})
@@ -649,11 +639,10 @@ export default function Home() {
useEffect(() => {
const doneOf: Record<string, boolean> = {
input: !!job?.video_url,
keyframe: !!job && job.frames.length > 0,
visual: !!job && (job.frames.length > 0 || (job.generated_videos?.length ?? 0) > 0),
asr: !!job && job.transcript.length > 0,
translate: !!job && (job.transcript.some((s) => s.zh) ?? false),
rewrite: !!job && (job.transcript.some((s) => s.zh) ?? false),
storyboard: selectedFrames.size > 0,
}
setEdges((prev) => prev.map((e) => ({ ...e, animated: !!doneOf[e.source] })))
}, [job, setEdges])