From abeff424f63b1e06ff8557fa169083aede5ffad3 Mon Sep 17 00:00:00 2001 From: kang Date: Thu, 14 May 2026 00:26:10 +0800 Subject: [PATCH] auto-save 2026-05-14 00:25 (+6, ~5) --- .memory/worklog.json | 13 + .../page-2026-05-13T16-21-19-323Z.yml | 20 + .../page-2026-05-13T16-21-22-043Z.yml | 351 +++++++++++++++++ .../page-2026-05-13T16-22-50-867Z.yml | 17 + .../page-2026-05-13T16-22-52-971Z.yml | 360 ++++++++++++++++++ .../page-2026-05-13T16-23-38-749Z.yml | 32 ++ .../page-2026-05-13T16-23-40-910Z.yml | 360 ++++++++++++++++++ web/app/page.tsx | 45 ++- web/components/nodes/index.tsx | 11 +- web/components/nodes/node-shell.tsx | 5 +- web/components/nodes/resize-handle.tsx | 105 +++-- 11 files changed, 1262 insertions(+), 57 deletions(-) create mode 100644 .playwright-mcp/page-2026-05-13T16-21-19-323Z.yml create mode 100644 .playwright-mcp/page-2026-05-13T16-21-22-043Z.yml create mode 100644 .playwright-mcp/page-2026-05-13T16-22-50-867Z.yml create mode 100644 .playwright-mcp/page-2026-05-13T16-22-52-971Z.yml create mode 100644 .playwright-mcp/page-2026-05-13T16-23-38-749Z.yml create mode 100644 .playwright-mcp/page-2026-05-13T16-23-40-910Z.yml diff --git a/.memory/worklog.json b/.memory/worklog.json index 9268558..37d65ab 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -2645,6 +2645,19 @@ "message": "auto-save 2026-05-14 00:14 (+10, ~3)", "hash": "6cd0713", "files_changed": 13 + }, + { + "ts": "2026-05-14T00:20:30+08:00", + "type": "commit", + "message": "auto-save 2026-05-14 00:20 (+4, ~4)", + "hash": "540107d", + "files_changed": 8 + }, + { + "ts": "2026-05-13T16:23:09Z", + "type": "session-heartbeat", + "message": "Claude 会话活跃 · 最近命令:claude · 5 项未提交变更 · 最近提交:auto-save 2026-05-14 00:20 (+4, ~4)", + "files_changed": 5 } ] } diff --git a/.playwright-mcp/page-2026-05-13T16-21-19-323Z.yml b/.playwright-mcp/page-2026-05-13T16-21-19-323Z.yml new file mode 100644 index 0000000..85c2de0 --- /dev/null +++ b/.playwright-mcp/page-2026-05-13T16-21-19-323Z.yml @@ -0,0 +1,20 @@ +- generic [active] [ref=e1]: + - main [ref=e3]: + - button "切到明亮主题" [ref=e5]: + - img [ref=e6] + - application [ref=e14]: + - img + - generic "Control Panel" [ref=e17]: + - button "Zoom In" [ref=e18] [cursor=pointer]: + - img [ref=e19] + - button "Zoom Out" [ref=e21] [cursor=pointer]: + - img [ref=e22] + - button "Fit View" [ref=e24] [cursor=pointer]: + - img [ref=e25] + - button "Toggle Interactivity" [ref=e27] [cursor=pointer]: + - img [ref=e28] + - img "Mini Map" [ref=e31] + - region "Notifications alt+T" + - button "Open Next.js Dev Tools" [ref=e38] [cursor=pointer]: + - img [ref=e39] + - alert [ref=e44] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-13T16-21-22-043Z.yml b/.playwright-mcp/page-2026-05-13T16-21-22-043Z.yml new file mode 100644 index 0000000..7e137f3 --- /dev/null +++ b/.playwright-mcp/page-2026-05-13T16-21-22-043Z.yml @@ -0,0 +1,351 @@ +- generic [active] [ref=e1]: + - main [ref=e3]: + - button "切到明亮主题" [ref=e5]: + - img [ref=e6] + - generic [ref=e12]: + - generic [ref=e47]: + - generic [ref=e48]: + - img [ref=e49] + - generic [ref=e54]: 分镜头编排 + - generic [ref=e55]: 1 分镜 · 3 元素 + - generic [ref=e56]: · 组织分镜画面 → 为生成视频做准备 + - button "展开编排" [ref=e58]: + - img [ref=e59] + - text: 展开编排 + - application [ref=e14]: + - generic [ref=e16]: + - generic: + - generic: + - img: + - group "Edge from input to keyframe" [ref=e61] [cursor=pointer] + - img: + - group "Edge from input to asr" [ref=e64] [cursor=pointer] + - img: + - group "Edge from asr to translate" + - img: + - group "Edge from translate to rewrite" [ref=e67] [cursor=pointer] + - img: + - group "Edge from keyframe to storyboard" [ref=e70] [cursor=pointer] + - img: + - group "Edge from rewrite to storyboard" [ref=e73] [cursor=pointer] + - img: + - group "Edge from storyboard to videogen" [ref=e76] [cursor=pointer] + - img: + - group "Edge from videogen to compose" [ref=e79] [cursor=pointer] + - img: + - group "Edge from rewrite to compose" [ref=e82] [cursor=pointer] + - generic: + - group [ref=e85]: + - generic [ref=e86]: + - generic [ref=e87]: + - button "再上传一个视频" [ref=e88]: + - img [ref=e89] + - button "72.4s" [ref=e90]: + - generic [ref=e92]: 72.4s + - button "64.5s" [ref=e93]: + - generic [ref=e95]: 64.5s + - button "71.4s" [ref=e96]: + - generic [ref=e98]: 71.4s + - button "72.4s" [ref=e99]: + - generic [ref=e101]: 72.4s + - button "64.5s" [ref=e102]: + - generic [ref=e104]: 64.5s + - button "71.4s" [ref=e105]: + - generic [ref=e107]: 71.4s + - button "71.4s" [ref=e108]: + - generic [ref=e110]: 71.4s + - button "71.4s" [ref=e111]: + - generic [ref=e113]: 71.4s + - button "71.4s" [ref=e114]: + - generic [ref=e116]: 71.4s + - button "71.4s" [ref=e117]: + - generic [ref=e119]: 71.4s + - button "8.0s" [ref=e120]: + - generic [ref=e122]: 8.0s + - button "8.0s" [ref=e123]: + - generic [ref=e125]: 8.0s + - button "8.0s" [ref=e126]: + - generic [ref=e128]: 8.0s + - button "8.0s" [ref=e129]: + - generic [ref=e131]: 8.0s + - button "…" [ref=e132]: + - img [ref=e134] + - generic [ref=e136]: … + - button "…" [ref=e137]: + - img [ref=e139] + - generic [ref=e141]: … + - button "…" [ref=e142]: + - img [ref=e144] + - generic [ref=e146]: … + - generic [ref=e147]: + - generic [ref=e148]: + - img [ref=e150] + - generic [ref=e153]: 输入 · Input + - img [ref=e155] + - generic [ref=e159]: + - generic [ref=e160]: STEP 1 · 视频就绪 · 完成 + - textbox "再加一个 TK 链接" [ref=e161] + - generic [ref=e162]: + - button "+ 加链接" [disabled] [ref=e163] + - button "再传一个" [ref=e164]: + - img [ref=e165] + - text: 再传一个 + - generic [ref=e168]: + - generic [ref=e169]: 576×1024 · 72.4s + - generic [ref=e170]: 📎 上传 + - button "重新解析" [ref=e171] + - generic "拖动调整宽度" [ref=e173] + - generic "拖动调整大小(宽 × 高)" [ref=e174] + - group [ref=e175]: + - generic [ref=e176]: + - generic [ref=e177]: + - generic [ref=e178]: + - button "frame 6 1.3s" [ref=e179]: + - img "frame 6" [ref=e180] + - generic [ref=e181]: 1.3s + - button "📋" [ref=e182] + - button "删除该关键帧" [ref=e183]: + - img [ref=e184] + - generic: + - generic: + - generic: + - generic: 分镜 7 + - generic: 1.33s + - generic [ref=e187]: + - button "frame 5 3 7.4s" [ref=e188]: + - img "frame 5" [ref=e189] + - generic "3 个元素已抠图" [ref=e191]: "3" + - generic [ref=e192]: 7.4s + - button "📋" [ref=e193] + - button "删除该关键帧" [ref=e194]: + - img [ref=e195] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e198]: + - button "frame 0 11.7s" [ref=e199]: + - img "frame 0" [ref=e200] + - generic [ref=e201]: 11.7s + - button "📋" [ref=e202] + - button "删除该关键帧" [ref=e203]: + - img [ref=e204] + - generic: + - generic: + - generic: + - generic: 分镜 1 + - generic: 11.68s + - generic [ref=e207]: + - button "frame 7 18.0s" [ref=e208]: + - img "frame 7" [ref=e209] + - generic [ref=e210]: 18.0s + - button "📋" [ref=e211] + - button "删除该关键帧" [ref=e212]: + - img [ref=e213] + - generic: + - generic: + - generic: + - generic: 分镜 8 + - generic: 18.00s + - generic [ref=e216]: + - button "frame 1 23.4s" [ref=e217]: + - img "frame 1" [ref=e218] + - generic [ref=e219]: 23.4s + - button "📋" [ref=e220] + - button "删除该关键帧" [ref=e221]: + - img [ref=e222] + - generic: + - generic: + - generic: + - generic: 分镜 2 + - generic: 23.37s + - generic [ref=e225]: + - button "frame 2 32.7s" [ref=e226]: + - img "frame 2" [ref=e227] + - generic [ref=e228]: 32.7s + - button "📋" [ref=e229] + - button "删除该关键帧" [ref=e230]: + - img [ref=e231] + - generic: + - generic: + - generic: + - generic: 分镜 3 + - generic: 32.72s + - generic [ref=e234]: + - button "frame 3 49.1s" [ref=e235]: + - img "frame 3" [ref=e236] + - generic [ref=e237]: 49.1s + - button "📋" [ref=e238] + - button "删除该关键帧" [ref=e239]: + - img [ref=e240] + - generic: + - generic: + - generic: + - generic: 分镜 4 + - generic: 49.08s + - generic [ref=e243]: + - button "frame 8 52.8s" [ref=e244]: + - img "frame 8" [ref=e245] + - generic [ref=e246]: 52.8s + - button "📋" [ref=e247] + - button "删除该关键帧" [ref=e248]: + - img [ref=e249] + - generic: + - generic: + - generic: + - generic: 分镜 9 + - generic: 52.80s + - generic [ref=e252]: + - button "frame 9 55.5s" [ref=e253]: + - img "frame 9" [ref=e254] + - generic [ref=e255]: 55.5s + - button "📋" [ref=e256] + - button "删除该关键帧" [ref=e257]: + - img [ref=e258] + - generic: + - generic: + - generic: + - generic: 分镜 10 + - generic: 55.50s + - generic [ref=e261]: + - button "frame 4 65.4s" [ref=e262]: + - img "frame 4" [ref=e263] + - generic [ref=e264]: 65.4s + - button "📋" [ref=e265] + - button "删除该关键帧" [ref=e266]: + - img [ref=e267] + - generic: + - generic: + - generic: + - generic: 分镜 5 + - generic: 65.43s + - generic [ref=e270]: + - generic [ref=e272]: + - img [ref=e274] + - generic [ref=e278]: 镜头拆解 · 元素提取 + - img [ref=e280] + - generic [ref=e284]: + - generic [ref=e285]: STEP 2 · 1/10 入编排 · 完成 + - generic [ref=e286]: + - text: 自动 10 张 · + - generic [ref=e287]: 0 已清洗 + - text: · + - generic [ref=e288]: 3/3 已抠图 + - text: 点缩略图 → 清洗水印 / 提取可借鉴元素 → 改造成 SKG 画面素材 + - generic "拖动调整宽度" [ref=e290] + - generic "拖动调整大小(宽 × 高)" [ref=e291] + - group [ref=e292]: + - generic [ref=e293]: + - generic [ref=e295]: + - img [ref=e297] + - generic [ref=e300]: 声音文案 · ASR + - generic [ref=e303]: + - generic [ref=e304]: STEP 3 · 可选文案轨 · 待运行 + - generic [ref=e305]: Gemini 2.5 · 英文带时间戳分段 + - generic "拖动调整宽度" [ref=e307] + - generic "拖动调整大小(宽 × 高)" [ref=e308] + - group [ref=e309]: + - generic [ref=e310]: + - generic [ref=e312]: + - img [ref=e314] + - generic [ref=e318]: 翻译理解 · Translate + - generic [ref=e321]: + - generic [ref=e322]: STEP 4 · EN → ZH · 待运行 + - generic [ref=e323]: 中文翻译 · 段落级 · 实时输出 + - generic "拖动调整宽度" [ref=e325] + - generic "拖动调整大小(宽 × 高)" [ref=e326] + - group [ref=e327]: + - generic [ref=e328]: + - generic [ref=e329]: + - generic [ref=e330]: + - button "病人骨骼" [ref=e331]: + - img "病人骨骼" [ref=e332] + - button "📋" [ref=e333] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e334]: + - button "医生骨骼" [ref=e335]: + - img "医生骨骼" [ref=e336] + - button "📋" [ref=e337] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e338]: + - button "检查台" [ref=e339]: + - img "检查台" [ref=e340] + - button "📋" [ref=e341] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e342]: + - generic [ref=e344]: + - img [ref=e346] + - generic [ref=e351]: 元素改造 · Storyboard + - img [ref=e353] + - generic [ref=e357]: + - generic [ref=e358]: STEP 6 · 参考元素 → SKG 画面 · 1 分镜 · 完成 + - generic [ref=e359]: + - text: 不是复刻原视频:先把参考图里的主体 / 场景 / 动作 / 道具拆出来,再替换成 SKG 产品画面。 + - generic [ref=e360]: 已有 3 个提取元素 · 1 个分镜进入编排 + - button "进入分镜编排" [ref=e361] + - generic "拖动调整宽度" [ref=e363] + - generic "拖动调整大小(宽 × 高)" [ref=e364] + - group [ref=e365]: + - generic [ref=e366]: + - generic [ref=e368]: + - img [ref=e370] + - generic [ref=e374]: 产品文案 · Rewrite + - generic [ref=e377]: + - generic [ref=e378]: STEP 5 · 接 SKG 卖点 · 待运行 + - textbox "粘贴 SKG 产品信息 / 关键卖点(可作为视频脚本和镜头动作参考)" [disabled] [ref=e379] + - generic [ref=e380]: 下一冲刺接入 + - generic "拖动调整宽度" [ref=e382] + - generic "拖动调整大小(宽 × 高)" [ref=e383] + - group [ref=e384]: + - generic [ref=e386]: + - generic [ref=e388]: + - img [ref=e390] + - generic [ref=e392]: 生成视频 · Video Gen + - generic [ref=e395]: + - generic [ref=e396]: STEP 7 · 首帧 + 动作 prompt · 待运行 + - generic [ref=e397]: + - generic [ref=e398]: Seedance + - generic [ref=e399]: Kling + - generic [ref=e400]: Veo 3 + - generic "拖动调整宽度" [ref=e402] + - generic "拖动调整大小(宽 × 高)" [ref=e403] + - group [ref=e404]: + - generic [ref=e405]: + - generic [ref=e407]: + - img [ref=e409] + - generic [ref=e413]: 合成成品 · Compose + - generic [ref=e416]: + - generic [ref=e417]: STEP 8 · ffmpeg + 字幕 · 待运行 + - generic [ref=e418]: + - text: 视频片段 + 字幕 / TTS + - text: → 最终 mp4 输出 + - generic "拖动调整宽度" [ref=e419] + - generic "拖动调整大小(宽 × 高)" [ref=e420] + - img + - generic "Control Panel" [ref=e17]: + - button "Zoom In" [ref=e18] [cursor=pointer]: + - img [ref=e19] + - button "Zoom Out" [ref=e21] [cursor=pointer]: + - img [ref=e22] + - button "Fit View" [ref=e24] [cursor=pointer]: + - img [ref=e25] + - button "Toggle Interactivity" [ref=e27] [cursor=pointer]: + - img [ref=e28] + - img "Mini Map" [ref=e31] + - region "Notifications alt+T" + - button "Open Next.js Dev Tools" [ref=e38] [cursor=pointer]: + - img [ref=e39] + - alert [ref=e44] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-13T16-22-50-867Z.yml b/.playwright-mcp/page-2026-05-13T16-22-50-867Z.yml new file mode 100644 index 0000000..62aaa00 --- /dev/null +++ b/.playwright-mcp/page-2026-05-13T16-22-50-867Z.yml @@ -0,0 +1,17 @@ +- generic [active] [ref=e1]: + - main [ref=e3]: + - application [ref=e6]: + - img + - generic "Control Panel" [ref=e9]: + - button "Zoom In" [ref=e10] [cursor=pointer]: + - img [ref=e11] + - button "Zoom Out" [ref=e13] [cursor=pointer]: + - img [ref=e14] + - button "Fit View" [ref=e16] [cursor=pointer]: + - img [ref=e17] + - button "Toggle Interactivity" [ref=e19] [cursor=pointer]: + - img [ref=e20] + - img "Mini Map" [ref=e23] + - region "Notifications alt+T" + - button "Open Next.js Dev Tools" [ref=e30] [cursor=pointer]: + - img [ref=e31] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-13T16-22-52-971Z.yml b/.playwright-mcp/page-2026-05-13T16-22-52-971Z.yml new file mode 100644 index 0000000..5f890f6 --- /dev/null +++ b/.playwright-mcp/page-2026-05-13T16-22-52-971Z.yml @@ -0,0 +1,360 @@ +- generic [active] [ref=e1]: + - generic [ref=e29] [cursor=pointer]: + - button "Open Next.js Dev Tools" [ref=e30]: + - img [ref=e31] + - generic [ref=e36]: + - button "Open issues overlay" [ref=e37]: + - generic [ref=e38]: + - generic [ref=e39]: "0" + - generic [ref=e40]: "1" + - generic [ref=e41]: Issue + - button "Collapse issues badge" [ref=e42]: + - img [ref=e43] + - main [ref=e46]: + - button "切到明亮主题" [ref=e48]: + - img [ref=e49] + - generic [ref=e55]: + - generic [ref=e58]: + - generic [ref=e59]: + - img [ref=e60] + - generic [ref=e65]: 分镜头编排 + - generic [ref=e66]: 1 分镜 · 3 元素 + - generic [ref=e67]: · 组织分镜画面 → 为生成视频做准备 + - button "展开编排" [ref=e69]: + - img [ref=e70] + - text: 展开编排 + - application [ref=e73]: + - generic [ref=e75]: + - generic: + - generic: + - img: + - group "Edge from input to keyframe" [ref=e76] [cursor=pointer] + - img: + - group "Edge from input to asr" [ref=e79] [cursor=pointer] + - img: + - group "Edge from asr to translate" + - img: + - group "Edge from translate to rewrite" [ref=e82] [cursor=pointer] + - img: + - group "Edge from keyframe to storyboard" [ref=e85] [cursor=pointer] + - img: + - group "Edge from rewrite to storyboard" [ref=e88] [cursor=pointer] + - img: + - group "Edge from storyboard to videogen" [ref=e91] [cursor=pointer] + - img: + - group "Edge from videogen to compose" [ref=e94] [cursor=pointer] + - img: + - group "Edge from rewrite to compose" [ref=e97] [cursor=pointer] + - generic: + - group [ref=e100]: + - generic [ref=e101]: + - generic [ref=e102]: + - button "再上传一个视频" [ref=e103]: + - img [ref=e104] + - button "72.4s" [ref=e105]: + - generic [ref=e107]: 72.4s + - button "64.5s" [ref=e108]: + - generic [ref=e110]: 64.5s + - button "71.4s" [ref=e111]: + - generic [ref=e113]: 71.4s + - button "72.4s" [ref=e114]: + - generic [ref=e116]: 72.4s + - button "64.5s" [ref=e117]: + - generic [ref=e119]: 64.5s + - button "71.4s" [ref=e120]: + - generic [ref=e122]: 71.4s + - button "71.4s" [ref=e123]: + - generic [ref=e125]: 71.4s + - button "71.4s" [ref=e126]: + - generic [ref=e128]: 71.4s + - button "71.4s" [ref=e129]: + - generic [ref=e131]: 71.4s + - button "71.4s" [ref=e132]: + - generic [ref=e134]: 71.4s + - button "8.0s" [ref=e135]: + - generic [ref=e137]: 8.0s + - button "8.0s" [ref=e138]: + - generic [ref=e140]: 8.0s + - button "8.0s" [ref=e141]: + - generic [ref=e143]: 8.0s + - button "8.0s" [ref=e144]: + - generic [ref=e146]: 8.0s + - button "…" [ref=e147]: + - img [ref=e149] + - generic [ref=e151]: … + - button "…" [ref=e152]: + - img [ref=e154] + - generic [ref=e156]: … + - button "…" [ref=e157]: + - img [ref=e159] + - generic [ref=e161]: … + - generic [ref=e162]: + - generic [ref=e163]: + - img [ref=e165] + - generic [ref=e168]: 输入 · Input + - img [ref=e170] + - generic [ref=e174]: + - generic [ref=e175]: STEP 1 · 视频就绪 · 完成 + - textbox "再加一个 TK 链接" [ref=e176] + - generic [ref=e177]: + - button "+ 加链接" [disabled] [ref=e178] + - button "再传一个" [ref=e179]: + - img [ref=e180] + - text: 再传一个 + - generic [ref=e183]: + - generic [ref=e184]: 576×1024 · 72.4s + - generic [ref=e185]: 📎 上传 + - button "重新解析" [ref=e186] + - generic "拖动调整宽度" [ref=e188] + - generic "拖动调整大小(宽 × 高)" [ref=e189] + - group [ref=e190]: + - generic [ref=e191]: + - generic [ref=e192]: + - generic [ref=e193]: + - button "frame 6 1.3s" [ref=e194]: + - img "frame 6" [ref=e195] + - generic [ref=e196]: 1.3s + - button "📋" [ref=e197] + - button "删除该关键帧" [ref=e198]: + - img [ref=e199] + - generic: + - generic: + - generic: + - generic: 分镜 7 + - generic: 1.33s + - generic [ref=e202]: + - button "frame 5 3 7.4s" [ref=e203]: + - img "frame 5" [ref=e204] + - generic "3 个元素已抠图" [ref=e206]: "3" + - generic [ref=e207]: 7.4s + - button "📋" [ref=e208] + - button "删除该关键帧" [ref=e209]: + - img [ref=e210] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e213]: + - button "frame 0 11.7s" [ref=e214]: + - img "frame 0" [ref=e215] + - generic [ref=e216]: 11.7s + - button "📋" [ref=e217] + - button "删除该关键帧" [ref=e218]: + - img [ref=e219] + - generic: + - generic: + - generic: + - generic: 分镜 1 + - generic: 11.68s + - generic [ref=e222]: + - button "frame 7 18.0s" [ref=e223]: + - img "frame 7" [ref=e224] + - generic [ref=e225]: 18.0s + - button "📋" [ref=e226] + - button "删除该关键帧" [ref=e227]: + - img [ref=e228] + - generic: + - generic: + - generic: + - generic: 分镜 8 + - generic: 18.00s + - generic [ref=e231]: + - button "frame 1 23.4s" [ref=e232]: + - img "frame 1" [ref=e233] + - generic [ref=e234]: 23.4s + - button "📋" [ref=e235] + - button "删除该关键帧" [ref=e236]: + - img [ref=e237] + - generic: + - generic: + - generic: + - generic: 分镜 2 + - generic: 23.37s + - generic [ref=e240]: + - button "frame 2 32.7s" [ref=e241]: + - img "frame 2" [ref=e242] + - generic [ref=e243]: 32.7s + - button "📋" [ref=e244] + - button "删除该关键帧" [ref=e245]: + - img [ref=e246] + - generic: + - generic: + - generic: + - generic: 分镜 3 + - generic: 32.72s + - generic [ref=e249]: + - button "frame 3 49.1s" [ref=e250]: + - img "frame 3" [ref=e251] + - generic [ref=e252]: 49.1s + - button "📋" [ref=e253] + - button "删除该关键帧" [ref=e254]: + - img [ref=e255] + - generic: + - generic: + - generic: + - generic: 分镜 4 + - generic: 49.08s + - generic [ref=e258]: + - button "frame 8 52.8s" [ref=e259]: + - img "frame 8" [ref=e260] + - generic [ref=e261]: 52.8s + - button "📋" [ref=e262] + - button "删除该关键帧" [ref=e263]: + - img [ref=e264] + - generic: + - generic: + - generic: + - generic: 分镜 9 + - generic: 52.80s + - generic [ref=e267]: + - button "frame 9 55.5s" [ref=e268]: + - img "frame 9" [ref=e269] + - generic [ref=e270]: 55.5s + - button "📋" [ref=e271] + - button "删除该关键帧" [ref=e272]: + - img [ref=e273] + - generic: + - generic: + - generic: + - generic: 分镜 10 + - generic: 55.50s + - generic [ref=e276]: + - button "frame 4 65.4s" [ref=e277]: + - img "frame 4" [ref=e278] + - generic [ref=e279]: 65.4s + - button "📋" [ref=e280] + - button "删除该关键帧" [ref=e281]: + - img [ref=e282] + - generic: + - generic: + - generic: + - generic: 分镜 5 + - generic: 65.43s + - generic [ref=e285]: + - generic [ref=e287]: + - img [ref=e289] + - generic [ref=e293]: 镜头拆解 · 元素提取 + - img [ref=e295] + - generic [ref=e299]: + - generic [ref=e300]: STEP 2 · 1/10 入编排 · 完成 + - generic [ref=e301]: + - text: 自动 10 张 · + - generic [ref=e302]: 0 已清洗 + - text: · + - generic [ref=e303]: 3/3 已抠图 + - text: 点缩略图 → 清洗水印 / 提取可借鉴元素 → 改造成 SKG 画面素材 + - generic "拖动调整宽度" [ref=e305] + - generic "拖动调整大小(宽 × 高)" [ref=e306] + - group [ref=e307]: + - generic [ref=e308]: + - generic [ref=e310]: + - img [ref=e312] + - generic [ref=e315]: 声音文案 · ASR + - generic [ref=e318]: + - generic [ref=e319]: STEP 3 · 可选文案轨 · 待运行 + - generic [ref=e320]: Gemini 2.5 · 英文带时间戳分段 + - generic "拖动调整宽度" [ref=e322] + - generic "拖动调整大小(宽 × 高)" [ref=e323] + - group [ref=e324]: + - generic [ref=e325]: + - generic [ref=e327]: + - img [ref=e329] + - generic [ref=e333]: 翻译理解 · Translate + - generic [ref=e336]: + - generic [ref=e337]: STEP 4 · EN → ZH · 待运行 + - generic [ref=e338]: 中文翻译 · 段落级 · 实时输出 + - generic "拖动调整宽度" [ref=e340] + - generic "拖动调整大小(宽 × 高)" [ref=e341] + - group [ref=e342]: + - generic [ref=e343]: + - generic [ref=e344]: + - generic [ref=e345]: + - button "病人骨骼" [ref=e346]: + - img "病人骨骼" [ref=e347] + - button "📋" [ref=e348] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e349]: + - button "医生骨骼" [ref=e350]: + - img "医生骨骼" [ref=e351] + - button "📋" [ref=e352] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e353]: + - button "检查台" [ref=e354]: + - img "检查台" [ref=e355] + - button "📋" [ref=e356] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e357]: + - generic [ref=e359]: + - img [ref=e361] + - generic [ref=e366]: 元素改造 · Storyboard + - img [ref=e368] + - generic [ref=e372]: + - generic [ref=e373]: STEP 6 · 参考元素 → SKG 画面 · 1 分镜 · 完成 + - generic [ref=e374]: + - text: 不是复刻原视频:先把参考图里的主体 / 场景 / 动作 / 道具拆出来,再替换成 SKG 产品画面。 + - generic [ref=e375]: 已有 3 个提取元素 · 1 个分镜进入编排 + - button "进入分镜编排" [ref=e376] + - generic "拖动调整宽度" [ref=e378] + - generic "拖动调整大小(宽 × 高)" [ref=e379] + - group [ref=e380]: + - generic [ref=e381]: + - generic [ref=e383]: + - img [ref=e385] + - generic [ref=e389]: 产品文案 · Rewrite + - generic [ref=e392]: + - generic [ref=e393]: STEP 5 · 接 SKG 卖点 · 待运行 + - textbox "粘贴 SKG 产品信息 / 关键卖点(可作为视频脚本和镜头动作参考)" [disabled] [ref=e394] + - generic [ref=e395]: 下一冲刺接入 + - generic "拖动调整宽度" [ref=e397] + - generic "拖动调整大小(宽 × 高)" [ref=e398] + - group [ref=e399]: + - generic [ref=e401]: + - generic [ref=e403]: + - img [ref=e405] + - generic [ref=e407]: 生成视频 · Video Gen + - generic [ref=e410]: + - generic [ref=e411]: STEP 7 · 首帧 + 动作 prompt · 待运行 + - generic [ref=e412]: + - generic [ref=e413]: Seedance + - generic [ref=e414]: Kling + - generic [ref=e415]: Veo 3 + - generic "拖动调整宽度" [ref=e417] + - generic "拖动调整大小(宽 × 高)" [ref=e418] + - group [ref=e419]: + - generic [ref=e420]: + - generic [ref=e422]: + - img [ref=e424] + - generic [ref=e428]: 合成成品 · Compose + - generic [ref=e431]: + - generic [ref=e432]: STEP 8 · ffmpeg + 字幕 · 待运行 + - generic [ref=e433]: + - text: 视频片段 + 字幕 / TTS + - text: → 最终 mp4 输出 + - generic "拖动调整宽度" [ref=e434] + - generic "拖动调整大小(宽 × 高)" [ref=e435] + - img + - generic "Control Panel" [ref=e436]: + - button "Zoom In" [ref=e437] [cursor=pointer]: + - img [ref=e438] + - button "Zoom Out" [ref=e440] [cursor=pointer]: + - img [ref=e441] + - button "Fit View" [ref=e443] [cursor=pointer]: + - img [ref=e444] + - button "Toggle Interactivity" [ref=e446] [cursor=pointer]: + - img [ref=e447] + - img "Mini Map" [ref=e450] + - region "Notifications alt+T" + - alert [ref=e460] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-13T16-23-38-749Z.yml b/.playwright-mcp/page-2026-05-13T16-23-38-749Z.yml new file mode 100644 index 0000000..8e71551 --- /dev/null +++ b/.playwright-mcp/page-2026-05-13T16-23-38-749Z.yml @@ -0,0 +1,32 @@ +- generic [active] [ref=e1]: + - button "Open Next.js Dev Tools" [ref=e7] [cursor=pointer]: + - img [ref=e8] + - main [ref=e14]: + - application [ref=e17]: + - group [ref=e20]: + - generic [ref=e22]: + - generic [ref=e23]: + - img [ref=e25] + - generic [ref=e28]: 输入 · Input + - generic [ref=e31]: + - generic [ref=e32]: STEP 1 · 待运行 + - textbox "粘贴 TikTok 链接" [ref=e33] + - generic [ref=e34]: + - button "提交链接" [disabled] [ref=e35] + - button "上传" [ref=e36]: + - img [ref=e37] + - text: 上传 + - generic "拖动调整宽度" [ref=e41] + - generic "拖动调整大小(宽 × 高)" [ref=e42] + - img + - generic "Control Panel" [ref=e43]: + - button "Zoom In" [ref=e44] [cursor=pointer]: + - img [ref=e45] + - button "Zoom Out" [ref=e47] [cursor=pointer]: + - img [ref=e48] + - button "Fit View" [ref=e50] [cursor=pointer]: + - img [ref=e51] + - button "Toggle Interactivity" [ref=e53] [cursor=pointer]: + - img [ref=e54] + - img "Mini Map" [ref=e57] + - region "Notifications alt+T" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-13T16-23-40-910Z.yml b/.playwright-mcp/page-2026-05-13T16-23-40-910Z.yml new file mode 100644 index 0000000..f82dcca --- /dev/null +++ b/.playwright-mcp/page-2026-05-13T16-23-40-910Z.yml @@ -0,0 +1,360 @@ +- generic [active] [ref=e1]: + - generic [ref=e6] [cursor=pointer]: + - button "Open Next.js Dev Tools" [ref=e7]: + - img [ref=e8] + - generic [ref=e60]: + - button "Open issues overlay" [ref=e61]: + - generic [ref=e62]: + - generic [ref=e63]: "0" + - generic [ref=e64]: "1" + - generic [ref=e65]: Issue + - button "Collapse issues badge" [ref=e66]: + - img [ref=e67] + - main [ref=e14]: + - button "切到明亮主题" [ref=e70]: + - img [ref=e71] + - generic [ref=e15]: + - generic [ref=e79]: + - generic [ref=e80]: + - img [ref=e81] + - generic [ref=e86]: 分镜头编排 + - generic [ref=e87]: 1 分镜 · 3 元素 + - generic [ref=e88]: · 组织分镜画面 → 为生成视频做准备 + - button "展开编排" [ref=e90]: + - img [ref=e91] + - text: 展开编排 + - application [ref=e17]: + - generic [ref=e19]: + - generic: + - generic: + - img: + - group "Edge from input to keyframe" [ref=e93] [cursor=pointer] + - img: + - group "Edge from input to asr" [ref=e96] [cursor=pointer] + - img: + - group "Edge from asr to translate" + - img: + - group "Edge from translate to rewrite" [ref=e99] [cursor=pointer] + - img: + - group "Edge from keyframe to storyboard" [ref=e102] [cursor=pointer] + - img: + - group "Edge from rewrite to storyboard" [ref=e105] [cursor=pointer] + - img: + - group "Edge from storyboard to videogen" [ref=e108] [cursor=pointer] + - img: + - group "Edge from videogen to compose" [ref=e111] [cursor=pointer] + - img: + - group "Edge from rewrite to compose" [ref=e114] [cursor=pointer] + - generic: + - group [ref=e20]: + - generic [ref=e21]: + - generic [ref=e117]: + - button "再上传一个视频" [ref=e118]: + - img [ref=e119] + - button "72.4s" [ref=e120]: + - generic [ref=e122]: 72.4s + - button "64.5s" [ref=e123]: + - generic [ref=e125]: 64.5s + - button "71.4s" [ref=e126]: + - generic [ref=e128]: 71.4s + - button "72.4s" [ref=e129]: + - generic [ref=e131]: 72.4s + - button "64.5s" [ref=e132]: + - generic [ref=e134]: 64.5s + - button "71.4s" [ref=e135]: + - generic [ref=e137]: 71.4s + - button "71.4s" [ref=e138]: + - generic [ref=e140]: 71.4s + - button "71.4s" [ref=e141]: + - generic [ref=e143]: 71.4s + - button "71.4s" [ref=e144]: + - generic [ref=e146]: 71.4s + - button "71.4s" [ref=e147]: + - generic [ref=e149]: 71.4s + - button "8.0s" [ref=e150]: + - generic [ref=e152]: 8.0s + - button "8.0s" [ref=e153]: + - generic [ref=e155]: 8.0s + - button "8.0s" [ref=e156]: + - generic [ref=e158]: 8.0s + - button "8.0s" [ref=e159]: + - generic [ref=e161]: 8.0s + - button "…" [ref=e162]: + - img [ref=e164] + - generic [ref=e166]: … + - button "…" [ref=e167]: + - img [ref=e169] + - generic [ref=e171]: … + - button "…" [ref=e172]: + - img [ref=e174] + - generic [ref=e176]: … + - generic [ref=e22]: + - generic [ref=e23]: + - img [ref=e25] + - generic [ref=e28]: 输入 · Input + - img [ref=e177] + - generic [ref=e31]: + - generic [ref=e32]: STEP 1 · 视频就绪 · 完成 + - textbox "再加一个 TK 链接" [ref=e180] + - generic [ref=e34]: + - button "+ 加链接" [disabled] [ref=e181] + - button "再传一个" [ref=e182]: + - img [ref=e37] + - text: 再传一个 + - generic [ref=e183]: + - generic [ref=e184]: 576×1024 · 72.4s + - generic [ref=e185]: 📎 上传 + - button "重新解析" [ref=e186] + - generic "拖动调整宽度" [ref=e41] + - generic "拖动调整大小(宽 × 高)" [ref=e42] + - group [ref=e187]: + - generic [ref=e188]: + - generic [ref=e189]: + - generic [ref=e190]: + - button "frame 6 1.3s" [ref=e191]: + - img "frame 6" [ref=e192] + - generic [ref=e193]: 1.3s + - button "📋" [ref=e194] + - button "删除该关键帧" [ref=e195]: + - img [ref=e196] + - generic: + - generic: + - generic: + - generic: 分镜 7 + - generic: 1.33s + - generic [ref=e199]: + - button "frame 5 3 7.4s" [ref=e200]: + - img "frame 5" [ref=e201] + - generic "3 个元素已抠图" [ref=e203]: "3" + - generic [ref=e204]: 7.4s + - button "📋" [ref=e205] + - button "删除该关键帧" [ref=e206]: + - img [ref=e207] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e210]: + - button "frame 0 11.7s" [ref=e211]: + - img "frame 0" [ref=e212] + - generic [ref=e213]: 11.7s + - button "📋" [ref=e214] + - button "删除该关键帧" [ref=e215]: + - img [ref=e216] + - generic: + - generic: + - generic: + - generic: 分镜 1 + - generic: 11.68s + - generic [ref=e219]: + - button "frame 7 18.0s" [ref=e220]: + - img "frame 7" [ref=e221] + - generic [ref=e222]: 18.0s + - button "📋" [ref=e223] + - button "删除该关键帧" [ref=e224]: + - img [ref=e225] + - generic: + - generic: + - generic: + - generic: 分镜 8 + - generic: 18.00s + - generic [ref=e228]: + - button "frame 1 23.4s" [ref=e229]: + - img "frame 1" [ref=e230] + - generic [ref=e231]: 23.4s + - button "📋" [ref=e232] + - button "删除该关键帧" [ref=e233]: + - img [ref=e234] + - generic: + - generic: + - generic: + - generic: 分镜 2 + - generic: 23.37s + - generic [ref=e237]: + - button "frame 2 32.7s" [ref=e238]: + - img "frame 2" [ref=e239] + - generic [ref=e240]: 32.7s + - button "📋" [ref=e241] + - button "删除该关键帧" [ref=e242]: + - img [ref=e243] + - generic: + - generic: + - generic: + - generic: 分镜 3 + - generic: 32.72s + - generic [ref=e246]: + - button "frame 3 49.1s" [ref=e247]: + - img "frame 3" [ref=e248] + - generic [ref=e249]: 49.1s + - button "📋" [ref=e250] + - button "删除该关键帧" [ref=e251]: + - img [ref=e252] + - generic: + - generic: + - generic: + - generic: 分镜 4 + - generic: 49.08s + - generic [ref=e255]: + - button "frame 8 52.8s" [ref=e256]: + - img "frame 8" [ref=e257] + - generic [ref=e258]: 52.8s + - button "📋" [ref=e259] + - button "删除该关键帧" [ref=e260]: + - img [ref=e261] + - generic: + - generic: + - generic: + - generic: 分镜 9 + - generic: 52.80s + - generic [ref=e264]: + - button "frame 9 55.5s" [ref=e265]: + - img "frame 9" [ref=e266] + - generic [ref=e267]: 55.5s + - button "📋" [ref=e268] + - button "删除该关键帧" [ref=e269]: + - img [ref=e270] + - generic: + - generic: + - generic: + - generic: 分镜 10 + - generic: 55.50s + - generic [ref=e273]: + - button "frame 4 65.4s" [ref=e274]: + - img "frame 4" [ref=e275] + - generic [ref=e276]: 65.4s + - button "📋" [ref=e277] + - button "删除该关键帧" [ref=e278]: + - img [ref=e279] + - generic: + - generic: + - generic: + - generic: 分镜 5 + - generic: 65.43s + - generic [ref=e282]: + - generic [ref=e284]: + - img [ref=e286] + - generic [ref=e290]: 镜头拆解 · 元素提取 + - img [ref=e292] + - generic [ref=e296]: + - generic [ref=e297]: STEP 2 · 1/10 入编排 · 完成 + - generic [ref=e298]: + - text: 自动 10 张 · + - generic [ref=e299]: 0 已清洗 + - text: · + - generic [ref=e300]: 3/3 已抠图 + - text: 点缩略图 → 清洗水印 / 提取可借鉴元素 → 改造成 SKG 画面素材 + - generic "拖动调整宽度" [ref=e302] + - generic "拖动调整大小(宽 × 高)" [ref=e303] + - group [ref=e304]: + - generic [ref=e305]: + - generic [ref=e307]: + - img [ref=e309] + - generic [ref=e312]: 声音文案 · ASR + - generic [ref=e315]: + - generic [ref=e316]: STEP 3 · 可选文案轨 · 待运行 + - generic [ref=e317]: Gemini 2.5 · 英文带时间戳分段 + - generic "拖动调整宽度" [ref=e319] + - generic "拖动调整大小(宽 × 高)" [ref=e320] + - group [ref=e321]: + - generic [ref=e322]: + - generic [ref=e324]: + - img [ref=e326] + - generic [ref=e330]: 翻译理解 · Translate + - generic [ref=e333]: + - generic [ref=e334]: STEP 4 · EN → ZH · 待运行 + - generic [ref=e335]: 中文翻译 · 段落级 · 实时输出 + - generic "拖动调整宽度" [ref=e337] + - generic "拖动调整大小(宽 × 高)" [ref=e338] + - group [ref=e339]: + - generic [ref=e340]: + - generic [ref=e341]: + - generic [ref=e342]: + - button "病人骨骼" [ref=e343]: + - img "病人骨骼" [ref=e344] + - button "📋" [ref=e345] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e346]: + - button "医生骨骼" [ref=e347]: + - img "医生骨骼" [ref=e348] + - button "📋" [ref=e349] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e350]: + - button "检查台" [ref=e351]: + - img "检查台" [ref=e352] + - button "📋" [ref=e353] + - generic: + - generic: + - generic: + - generic: 分镜 6 + - generic: 7.39s + - generic [ref=e354]: + - generic [ref=e356]: + - img [ref=e358] + - generic [ref=e363]: 元素改造 · Storyboard + - img [ref=e365] + - generic [ref=e369]: + - generic [ref=e370]: STEP 6 · 参考元素 → SKG 画面 · 1 分镜 · 完成 + - generic [ref=e371]: + - text: 不是复刻原视频:先把参考图里的主体 / 场景 / 动作 / 道具拆出来,再替换成 SKG 产品画面。 + - generic [ref=e372]: 已有 3 个提取元素 · 1 个分镜进入编排 + - button "进入分镜编排" [ref=e373] + - generic "拖动调整宽度" [ref=e375] + - generic "拖动调整大小(宽 × 高)" [ref=e376] + - group [ref=e377]: + - generic [ref=e378]: + - generic [ref=e380]: + - img [ref=e382] + - generic [ref=e386]: 产品文案 · Rewrite + - generic [ref=e389]: + - generic [ref=e390]: STEP 5 · 接 SKG 卖点 · 待运行 + - textbox "粘贴 SKG 产品信息 / 关键卖点(可作为视频脚本和镜头动作参考)" [disabled] [ref=e391] + - generic [ref=e392]: 下一冲刺接入 + - generic "拖动调整宽度" [ref=e394] + - generic "拖动调整大小(宽 × 高)" [ref=e395] + - group [ref=e396]: + - generic [ref=e398]: + - generic [ref=e400]: + - img [ref=e402] + - generic [ref=e404]: 生成视频 · Video Gen + - generic [ref=e407]: + - generic [ref=e408]: STEP 7 · 首帧 + 动作 prompt · 待运行 + - generic [ref=e409]: + - generic [ref=e410]: Seedance + - generic [ref=e411]: Kling + - generic [ref=e412]: Veo 3 + - generic "拖动调整宽度" [ref=e414] + - generic "拖动调整大小(宽 × 高)" [ref=e415] + - group [ref=e416]: + - generic [ref=e417]: + - generic [ref=e419]: + - img [ref=e421] + - generic [ref=e425]: 合成成品 · Compose + - generic [ref=e428]: + - generic [ref=e429]: STEP 8 · ffmpeg + 字幕 · 待运行 + - generic [ref=e430]: + - text: 视频片段 + 字幕 / TTS + - text: → 最终 mp4 输出 + - generic "拖动调整宽度" [ref=e431] + - generic "拖动调整大小(宽 × 高)" [ref=e432] + - img + - generic "Control Panel" [ref=e43]: + - button "Zoom In" [ref=e44] [cursor=pointer]: + - img [ref=e45] + - button "Zoom Out" [ref=e47] [cursor=pointer]: + - img [ref=e48] + - button "Fit View" [ref=e50] [cursor=pointer]: + - img [ref=e51] + - button "Toggle Interactivity" [ref=e53] [cursor=pointer]: + - img [ref=e54] + - img "Mini Map" [ref=e57] + - region "Notifications alt+T" + - alert [ref=e440] \ No newline at end of file diff --git a/web/app/page.tsx b/web/app/page.tsx index 18e6f1f..d990420 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -50,12 +50,14 @@ const LAYOUT: Array<{ id: string; type: keyof typeof NODE_TYPES; x: number; y: n { id: "compose", type: "compose", x: 1640, y: 240, w: 320 }, ] -const NODE_WIDTHS_KEY = "skg-tk:node-widths:v1" +const NODE_SIZES_KEY = "skg-tk:node-sizes:v2" -function loadNodeWidths(): Record { +type NodeSize = { w?: number; h?: number } + +function loadNodeSizes(): Record { if (typeof window === "undefined") return {} try { - const raw = window.localStorage.getItem(NODE_WIDTHS_KEY) + const raw = window.localStorage.getItem(NODE_SIZES_KEY) return raw ? JSON.parse(raw) : {} } catch { return {} @@ -445,27 +447,36 @@ export default function Home() { }), [job, jobs, activeJobId, submitting, analyzing, selectedFrames, expandedFrame, framePanelScale, framePanelPinned, handleSubmit, handleUpload, handleAnalyze, handleToggleFrame, handleOpenFramePanel, handleFramePanelScaleChange, handleAddManualFrame, handleSwitchJob, setJob, handleDeleteFrame, handleDeleteGenerated, handleDeleteVideo, handleCopyImage]) // 用 useNodesState 让 ReactFlow 自己管位置(避免轮询时重置 drag) - const savedWidths = useMemo(() => loadNodeWidths(), []) + const savedSizes = useMemo(() => loadNodeSizes(), []) const [nodes, setNodes, onNodesChange] = useNodesState( - LAYOUT.map((n) => ({ - id: n.id, - type: n.type, - position: { x: n.x, y: n.y }, - data: nodeData, - draggable: true, - width: savedWidths[n.id] ?? n.w, - })), + LAYOUT.map((n) => { + const s = savedSizes[n.id] ?? {} + const w = s.w ?? n.w + const h = s.h + return { + id: n.id, + type: n.type, + position: { x: n.x, y: n.y }, + data: nodeData, + draggable: true, + width: w, + ...(typeof h === "number" ? { height: h } : {}), + style: { width: w, ...(typeof h === "number" ? { height: h } : {}) }, + } + }), ) - // 持久化每个节点宽度到 localStorage(KeyframePanelNode 自己管尺寸,不写回) + // 持久化每个节点宽 / 高到 localStorage(KeyframePanelNode 自己管尺寸,不写回) useEffect(() => { - const widths: Record = {} + const sizes: Record = {} for (const n of nodes) { if (n.id === KEYFRAME_PANEL_ID) continue - const w = n.width - if (typeof w === "number") widths[n.id] = Math.round(w) + const w = typeof n.width === "number" ? Math.round(n.width) : undefined + const h = typeof n.height === "number" ? Math.round(n.height) : undefined + if (w === undefined && h === undefined) continue + sizes[n.id] = { ...(w !== undefined ? { w } : {}), ...(h !== undefined ? { h } : {}) } } - try { window.localStorage.setItem(NODE_WIDTHS_KEY, JSON.stringify(widths)) } catch {} + try { window.localStorage.setItem(NODE_SIZES_KEY, JSON.stringify(sizes)) } catch {} }, [nodes]) const [edges, setEdges, onEdgesChange] = useEdgesState( EDGES_RAW.map(([from, to], i) => ({ diff --git a/web/components/nodes/index.tsx b/web/components/nodes/index.tsx index 7bfe792..de75261 100644 --- a/web/components/nodes/index.tsx +++ b/web/components/nodes/index.tsx @@ -103,7 +103,7 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an const inputLocked = isDownloading || d.submitting return ( -
+
{/* 多视频缩略图浮条 — 「+」在最左,job 按时间倒序(最新靠左高亮),统一高度 64,宽度按视频原比例,一行横滚。 浮条宽度 = 节点宽度(节点拖宽后浮条同步变宽,可见更多缩略图,少滚动)。 */} {!videoExpanded && d.jobs.length > 0 && ( @@ -222,7 +222,6 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an icon={} title="输入 · Input" subtitle={isDownloading ? "STEP 1 · 下载中" : hasVideo ? "STEP 1 · 视频就绪" : "STEP 1"} - width={320} selected={selected} hasTarget={false} > @@ -367,7 +366,7 @@ export function KeyframeNode({ data, selected }: any) { const aspectStr = d.job && d.job.height > 0 ? `${d.job.width}/${d.job.height}` : "9/16" return ( -
+
{/* 缩略图浮条(节点上方,最多 5 个一行,多行向上扩展) */} {frames.length > 0 && jobId && (
} title="镜头拆解 · 元素提取" subtitle={`STEP 2 · ${frames.length ? `${d.selectedFrames.size}/${frames.length} 入编排` : "等待抽取"}`} - width={KEYFRAME_WIDTH} selected={selected} > {frames.length > 0 ? (() => { @@ -836,7 +834,7 @@ export function StoryboardNode({ data, selected }: any) { const aspect = job && job.height > 0 ? `${job.width}/${job.height}` : "9/16" return ( -
+
{/* 节点上方:所有元素 crop 图(编排输入素材)· 跟 keyframe 节点样式一致 */} {elementCrops.length > 0 && job && (
} title="元素改造 · Storyboard" subtitle={`STEP 6 · 参考元素 → SKG 画面${storyboardCount > 0 ? ` · ${storyboardCount} 分镜` : ""}`} - width={IMAGEGEN_WIDTH} selected={selected} >
@@ -972,7 +969,7 @@ export function VideoGenNode({ data, selected }: any) { return e } return ( -
+
{videos.length > 0 && (
{hasTarget && } @@ -76,6 +76,7 @@ export function NodeShell({ {hasSource && } +
) } diff --git a/web/components/nodes/resize-handle.tsx b/web/components/nodes/resize-handle.tsx index 603faf2..2d8fc9c 100644 --- a/web/components/nodes/resize-handle.tsx +++ b/web/components/nodes/resize-handle.tsx @@ -2,49 +2,92 @@ import { type PointerEvent } from "react" import { useReactFlow } from "@xyflow/react" -/** 节点右边缘 resize 把手:手写 pointer 事件改 node.width,自适应 viewport zoom,不走 xyflow NodeResizeControl(在 glass-node overflow:hidden 下不工作)。 */ -export function ResizeRight({ minWidth = 240, maxWidth = 1200 }: { minWidth?: number; maxWidth?: number }) { - const { setNodes, getZoom } = useReactFlow() +interface Bounds { + minWidth?: number + maxWidth?: number + minHeight?: number + maxHeight?: number +} - const onPointerDown = (e: PointerEvent) => { - e.preventDefault() - e.stopPropagation() - const target = e.currentTarget - const nodeEl = target.closest(".react-flow__node") as HTMLElement | null - const nodeId = nodeEl?.getAttribute("data-id") - if (!nodeId || !nodeEl) return - target.setPointerCapture(e.pointerId) +/** 拖动期间持续更新 ReactFlow node 的 width/height,同时写到 style.width/height 让 wrapper 真实渲染。 + * 自适应 viewport zoom。不走 xyflow NodeResizeControl(glass-node overflow:hidden + reserved-type CSS 干扰下不响应)。 */ +function startResize( + e: PointerEvent, + setNodes: ReturnType["setNodes"], + getZoom: ReturnType["getZoom"], + axis: "x" | "y" | "xy", + bounds: Required, +) { + e.preventDefault() + e.stopPropagation() + const target = e.currentTarget + const nodeEl = target.closest(".react-flow__node") as HTMLElement | null + const nodeId = nodeEl?.getAttribute("data-id") + if (!nodeId || !nodeEl) return + target.setPointerCapture(e.pointerId) - const startX = e.clientX - const zoom = getZoom() || 1 - const startWidth = parseFloat(getComputedStyle(nodeEl).width) / zoom + const startX = e.clientX + const startY = e.clientY + const zoom = getZoom() || 1 + const startWidth = parseFloat(getComputedStyle(nodeEl).width) / zoom + const startHeight = parseFloat(getComputedStyle(nodeEl).height) / zoom - const onMove = (ev: globalThis.PointerEvent) => { - const dx = (ev.clientX - startX) / zoom - const next = Math.max(minWidth, Math.min(maxWidth, startWidth + dx)) - setNodes((nodes) => nodes.map((n) => (n.id === nodeId ? { ...n, width: next, style: { ...n.style, width: next } } : n))) - } - const onUp = () => { - window.removeEventListener("pointermove", onMove) - window.removeEventListener("pointerup", onUp) - try { target.releasePointerCapture(e.pointerId) } catch {} - } - window.addEventListener("pointermove", onMove) - window.addEventListener("pointerup", onUp) + const onMove = (ev: globalThis.PointerEvent) => { + const dx = (ev.clientX - startX) / zoom + const dy = (ev.clientY - startY) / zoom + const wantW = axis === "y" ? null : Math.max(bounds.minWidth, Math.min(bounds.maxWidth, startWidth + dx)) + const wantH = axis === "x" ? null : Math.max(bounds.minHeight, Math.min(bounds.maxHeight, startHeight + dy)) + setNodes((nodes) => + nodes.map((n) => { + if (n.id !== nodeId) return n + const nextStyle = { ...n.style } + const patch: Record = {} + if (wantW !== null) { patch.width = wantW; nextStyle.width = wantW } + if (wantH !== null) { patch.height = wantH; nextStyle.height = wantH } + return { ...n, ...patch, style: nextStyle } + }), + ) } + const onUp = () => { + window.removeEventListener("pointermove", onMove) + window.removeEventListener("pointerup", onUp) + try { target.releasePointerCapture(e.pointerId) } catch {} + } + window.addEventListener("pointermove", onMove) + window.addEventListener("pointerup", onUp) +} +/** 右边缘把手 — 只改宽度 */ +export function ResizeRight({ minWidth = 240, maxWidth = 1400 }: Bounds) { + const { setNodes, getZoom } = useReactFlow() return (
startResize(e, setNodes, getZoom, "x", { minWidth, maxWidth, minHeight: 0, maxHeight: 0 })} title="拖动调整宽度" className="nodrag absolute z-20 hover:bg-violet-400/60 active:bg-violet-400/80 transition rounded-r" + style={{ right: 0, top: 12, bottom: 12, width: 6, cursor: "ew-resize", touchAction: "none" }} + /> + ) +} + +/** 右下角把手 — 同时改宽和高 */ +export function ResizeBR({ + minWidth = 240, maxWidth = 1400, minHeight = 120, maxHeight = 1400, +}: Bounds) { + const { setNodes, getZoom } = useReactFlow() + return ( +
startResize(e, setNodes, getZoom, "xy", { minWidth, maxWidth, minHeight, maxHeight })} + title="拖动调整大小(宽 × 高)" + className="nodrag absolute z-30 hover:bg-violet-400/80 active:bg-violet-400 transition" style={{ right: 0, - top: 12, - bottom: 12, - width: 6, - cursor: "ew-resize", + bottom: 0, + width: 14, + height: 14, + cursor: "nwse-resize", touchAction: "none", + background: "linear-gradient(135deg, transparent 50%, rgba(167,139,250,0.55) 50%)", }} /> )