From 95fbb0cbc6a844030db7d4735b81ab42276d76ce Mon Sep 17 00:00:00 2001 From: kang Date: Thu, 14 May 2026 02:31:01 +0800 Subject: [PATCH] auto-save 2026-05-14 02:30 (+2, ~4) --- .memory/worklog.json | 13 + .../page-2026-05-13T18-29-57-259Z.yml | 36 +++ .../page-2026-05-13T18-30-00-447Z.yml | 245 ++++++++++++++++++ docs/source-analysis.html | 60 ++--- web/app/globals.css | 29 +++ web/app/page.tsx | 37 +-- 6 files changed, 364 insertions(+), 56 deletions(-) create mode 100644 .playwright-mcp/page-2026-05-13T18-29-57-259Z.yml create mode 100644 .playwright-mcp/page-2026-05-13T18-30-00-447Z.yml diff --git a/.memory/worklog.json b/.memory/worklog.json index c526768..31b740d 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -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 } ] } diff --git a/.playwright-mcp/page-2026-05-13T18-29-57-259Z.yml b/.playwright-mcp/page-2026-05-13T18-29-57-259Z.yml new file mode 100644 index 0000000..151684a --- /dev/null +++ b/.playwright-mcp/page-2026-05-13T18-29-57-259Z.yml @@ -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" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-13T18-30-00-447Z.yml b/.playwright-mcp/page-2026-05-13T18-30-00-447Z.yml new file mode 100644 index 0000000..a688d27 --- /dev/null +++ b/.playwright-mcp/page-2026-05-13T18-30-00-447Z.yml @@ -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] \ No newline at end of file diff --git a/docs/source-analysis.html b/docs/source-analysis.html index d6cd146..f4dc303 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -557,7 +557,7 @@
4

Vision 识别

识别场景和候选元素,只是候选,不应锁死。

5

元素提取

编辑/新增/删除元素,对元素反复生成提取图。

6

元素改造

把参考主体、场景、动作和 SKG 产品放入分镜结构。

-
7

生成视频

用分镜 4 图槽、改造目标和时长调用 Seedance / Kling / Veo 3 生视频 API,结果回写到 Video Gen 节点。

+
7

生成视频

用分镜 4 图槽、改造目标和时长调用 Seedance / Kling / Veo 3 生视频 API,结果回写到画面工作台节点。

8

合成成品

片段、字幕、配音、转场合成最终 mp4。当前未实现。

@@ -570,7 +570,7 @@ - + @@ -595,9 +595,9 @@
前端主链路:
 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
             
适合怎么描述“输入/下载/视频就绪这个节点应该如何提示用户下一步”。
-
你看到的区域镜头拆解节点上方关键帧
-
主要源码KeyframeNode 内嵌 FrameLightbox;后端 /frames/describe/cleanup
-
适合怎么描述“关键帧详情面板在无限画布上怎么展示、缩放、跟随;缩略图 hover 原尺寸预览要贴缩略图上边缘,不是贴节点卡片上边缘”。
+
你看到的区域画面工作台 · Visual Lab
+
主要源码VisualLabNode in web/components/nodes/index.tsx;它汇总关键帧、元素 cutout 和视频任务缩略图。
+
适合怎么描述“画布上只保留一个视觉展示卡片;缩略图 hover 原尺寸预览贴缩略图上边缘,点击再进入镜头处理 / 分镜编排 / 视频任务操作”。
你看到的区域元素列表和提取图
主要源码FrameLightbox;类型 KeyElement;接口 addElement/updateElement/deleteElement/cutoutElement/deleteCutout
适合怎么描述“Vision 识别出来的是候选,用户要能修正、重复提取、删除错误元素”。
-
-
你看到的区域元素改造 · Storyboard 节点
-
主要源码StoryboardNode;上方元素缩略图来自所有已提取 cutouts。
-
适合怎么描述“这里是素材入口,不是最终视频编辑器;缩略图 hover 预览仍在无限画布里,并贴当前缩略图的上边缘”。
-
-
-
你看到的区域顶部分镜条
-
主要源码StoryboardBar;只展示 selectedFrames,不负责提取元素。
-
适合怎么描述“选好的分镜如何按时间序组织,以及如何进入具体分镜编排”。
-
你看到的区域顶部分镜头编排下拉面板
主要源码StoryboardWorkbench;保存到 frame.storyboard;接口 PUT /storyboard
@@ -736,16 +726,10 @@ api/main.py
- - - - - - - - - - + + + + @@ -760,10 +744,10 @@ api/main.py - - - - + + + +
web/app/page.tsx产品工作台主状态:jobs、activeJobId、selectedFrames、clipboard、ReactFlow 节点和边。
web/components/nodes/index.tsxDAG 节点定义:Input、Keyframe、ASR、Translate、Rewrite、Storyboard、VideoGen、Compose。
web/components/nodes/index.tsxDAG 节点定义:Input、VisualLab、Audio、Compose、KeyframePanel;旧 Keyframe/Storyboard/VideoGen 组件保留但不再挂主画布。
web/components/lightbox.tsx镜头拆解和元素提取的主工作面板:清洗、识别、元素编辑、区域提取、抠图。
web/components/storyboard-bar.tsx顶部分镜编排条:展示选入编排的关键帧,并作为唯一分镜导航。
web/components/storyboard-workbench.tsx顶部分镜编排条下方的明细区:4 图槽、改造目标、时长、自动保存。
page.tsxInputNodeapi/main.py
镜头拆解 / 元素提取关键帧选择、清洗、Vision 候选、元素编辑、区域提取、元素 cutout。不要把 Vision 结果当最终结论;不要点击元素就跳走。KeyframeNodeFrameLightbox、元素接口
元素改造 Storyboard展示可用元素素材,承接“参考元素 → SKG 画面”的入口。不要等同于最终视频生成;不要暗示复刻原视频。StoryboardNodeStoryboardBar画面工作台 Visual Lab在一个画布卡片里展示关键帧、元素 cutout 和视频任务;点击缩略图进入对应处理面板。不要在主卡片里堆复杂表单;主卡片只做状态总览和入口。VisualLabNodeFrameLightboxStoryboardWorkbench、视频任务接口
分镜工作台ASRNodeTranslateNodeRewriteNode、ASR 接口
Video Gen / Compose承载生视频任务状态和完成后的 MP4。分镜工作台提交任务,Video Gen 节点只展示任务和结果。VideoGenNode/storyboard/videogenerated_videosVideo / Compose视频任务状态展示在 Visual Lab;Compose 承载最终合成。不要把 Compose 提前变成视频生成控制台。VisualLabNode/storyboard/videogenerated_videosComposeNode
@@ -831,6 +815,18 @@ api/main.py

变更记录

这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。

+
+
+

2026-05-14 · 三个视觉节点合并为画面工作台

+ VisualLab + Canvas +
+
+

问题:镜头拆解、元素改造、生成视频三个卡片在主画布上占同等权重,但它们只是视觉素材状态展示和入口;真正处理都在点击后的工作台/面板中完成。

+

改动:新增 VisualLabNode,把关键帧、元素 cutout、视频任务缩略图合并到一个“画面工作台 · Visual Lab”卡片里;DAG 从 Input → Keyframe → Storyboard → VideoGen → Compose 简化为 Input → VisualLab → Compose,音频线仍独立。

+

影响:web/app/page.tsxweb/components/nodes/index.tsxdocs/source-analysis.html。底层数据、接口和旧节点组件暂不删除。

+
+

2026-05-14 · 修复节点右下角缩放点击偏移

diff --git a/web/app/globals.css b/web/app/globals.css index ec8157a..d550dd8 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -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); +} diff --git a/web/app/page.tsx b/web/app/page.tsx index 1c24234..e24e647 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -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 = { 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])