文档:恢复音频生成共享分流并停止跟踪本地方案
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Failing after 2m48s
Project CI / Native shell tests (pull_request) Successful in 14m43s

新增共享音频 Composer 重构方案并收口权威设计
更新实施记录、决策日志、踩坑记录与文档索引
停止跟踪 local-docs 中的 T5 实施方案并保留本地文件
This commit is contained in:
2026-08-06 03:03:26 +00:00
parent 17bf54f211
commit 3a363c98ff
7 changed files with 197 additions and 261 deletions
+1
View File
@@ -20,6 +20,7 @@
- [图片画布编辑器 MVP 接入方案](./technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md)
- [图片画布编辑器前端拆分计划](./technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md)
- [画板音乐生成入口设计](./【编辑器】画板音乐生成入口设计-2026-06-18.md)
- [音频生成 Composer 恢复共享分流方案](./project-memory/plans/【前端重构】音频生成面板恢复共享分流方案-2026-08-06.md)
- [BGM 提示词优化 T6 测试与发布门禁](./【实施记录】BGM生成提示词优化T6测试与发布门禁-2026-08-05.md)
- [画布 Agent 对话面板](./【编辑器】画布Agent对话面板-2026-07-03.md)
- [画布 Agent 会话消息存 OSS](./adr/【ADR】画布Agent会话消息存OSS-2026-07-03.md)
@@ -0,0 +1,171 @@
# 音频生成 Composer 恢复共享分流方案
日期:`2026-08-06`
状态:`已规划,尚未实施`
## 一、目标
图片画布的音效与背景音乐恢复使用同一个音频 composer。`ImageCanvasGenerationComposerView.tsx` 内只保留一个 `ImageCanvasAudioGenerationComposerView`,并在组件内定义:
```ts
const isSoundEffect = dialog.mode === 'audio-sound-effect';
```
`isSoundEffect === true` 渲染现有 SFX 分支,`isSoundEffect === false` 渲染现有 BGM V1 分支。删除完整的独立 BGM composer 文件,但保留确有独立职责的 BGM 纯模型、助手 controller 和预设跑马灯组件。
这次只调整前端视图组织方式,不改变任何生成契约、业务规则、请求时序、计费或持久化语义。
## 二、范围与非目标
### 2.1 必须保留的 BGM 能力
- `gpt_description_prompt` 可见原文、Unicode `White_Space` canonicalization 和 200 字生成限制。
- 30 个预设、字符计数、AI 补全、一键简化和单层交换式撤销。
- AI 处理锁、同步正式提交锁、`generating` 锁和迟到响应隔离。
- 稳定 dialog ID、账号 / 项目 scope 校验和按 dialog ID 写回。
- 固定 `Suno`、当前动态泥点价格和隐藏 `make_instrumental` 的现状。
### 2.2 必须保持不变的现有 SFX 能力
- 固定 Vidu `audio1.0`Prompt 同值映射到现有 `prompt + sound` 请求字段。
- `210` 秒、步长 `1` 秒、默认 `5` 秒。
- 当前 Prompt 规范化、全空白时回退“游戏音效”、1500 字限制、价格和失败态。
- 现有提交 callback、生成占位和完成链路。
- SFX 中不出现 Suno、BGM 预设、AI 补全、一键简化、BGM 字符计数或撤销。
### 2.3 本次明确不做
- 不实现 SFX V2 独有的一键优化、自动中译英、ElevenLabs、自动时长、30 秒、Loop 或 SFX 预设。
- 不修改 BGM 或 SFX 需求原文。
- 不修改 BFF、队列、`platform-audio`、External v1、OpenAPI、SpacetimeDB schema、计费或重试。
- 不把 BGM Prompt controller 泛化为音频通用 controller。
- 不新建配置驱动的 composer 框架、第二套音频组件或其它顺带重构。
## 三、当前问题
当前 `ImageCanvasGenerationComposerView.tsx` 分别渲染 SFX 内部组件和 `ImageCanvasBackgroundMusicGenerationComposerView.tsx`。这层完整视图拆分让同一个音频入口形成两套 composer 边界,而 SFX V2 需求已经表明预设、AI 写回、撤销和锁定等交互并非 BGM 永久独占,继续用“BGM 专属交互较多”作为完整组件分叉理由不再成立。
同时,最近一次合并把共享架构中的 `isSoundEffect` 条件表达式带回了当前 SFX-only 组件,却没有带回变量定义,形成 `isSoundEffect is not defined`。该问题不是增加一个局部常量后就可以收口的长期架构问题;本次重构应恢复单一音频组件,使变量与它控制的两个分支重新处于同一组件边界。
## 四、目标结构
```text
ImageCanvasEditorView
└─ useImageCanvasGenerationSurface
└─ ImageCanvasGenerationComposerView
└─ ImageCanvasAudioGenerationComposerView
├─ isSoundEffect === true → 现有 SFX UI 与行为
└─ isSoundEffect === false → 现有 BGM V1 UI 与行为
└─ ImageCanvasBackgroundMusicPresetMarquee
```
继续保留的 BGM 专项模块:
- `ImageCanvasBackgroundMusicPromptModel.ts`canonicalization、计数、动作资格和 dialog 类型收窄。
- `useImageCanvasBackgroundMusicPromptAssist.ts`:按 dialog ID 隔离的异步助手状态与提交锁。
- `ImageCanvasBackgroundMusicPresetModel.ts`30 个预设与追加规则。
- `ImageCanvasBackgroundMusicPresetMarquee.tsx`:展开、滚动、hover、触摸和 reduced-motion。
删除的完整视图模块:
- `ImageCanvasBackgroundMusicGenerationComposerView.tsx`
- `ImageCanvasBackgroundMusicGenerationComposerView.test.tsx`
删除测试文件不等于删除覆盖;其中全部用例必须迁入总 composer 测试。
## 五、实现边界
### 5.1 单一渲染入口
`ImageCanvasGenerationComposerView``audio-sound-effect``audio-background-music` 只保留一个渲染条件和一个 `ImageCanvasAudioGenerationComposerView` 调用。组件内部以 `isSoundEffect` 选择两棵现有表单子树,不为本次重构新造配置层。
共享组件使用基于 dialog ID 与 mode 的稳定 `key`。连续切换 BGM dialog 时,预设展开、滚动、hover 和触摸状态不得继承;从 SFX 切到 BGM 时,音效时长菜单状态也不得泄漏。
### 5.2 Hook 与类型安全
- React Hook 不得放进 `isSoundEffect` 条件分支。`useState``useRef``useId``useImageCanvasFloatingOptionDismiss` 保持固定调用顺序。
- `GenerateDialogState` 不是可判别联合。BGM 分支继续通过 `toBackgroundMusicGenerationDialog` 取得带稳定 ID 的 `BackgroundMusicGenerationDialogState`
- BGM 缺少稳定 ID 或 `backgroundMusicPromptAssist` 时失败关闭,不渲染不完整的 BGM 表单;SFX 不依赖这两个条件。
### 5.3 两套状态写回不得合并
- SFX 继续走现有 `setGenerateDialog` 路径,保持按 mode 更新、失败态复位和时长写回语义。
- BGM 继续走 `updateCanvasGenerationDialogById(dialog.id, updater)`,不能降级成只比较 mode。助手响应、预设、撤销和提交锁仍按稳定 dialog ID 隔离。
- `backgroundMusicPromptAssist` 可以继续由 surface 传给共享 composer,但只允许 BGM 分支读取或调用。
### 5.4 锁定与提交资格不得串用
|分支|锁定条件|生成资格|
|---|---|---|
|SFX|沿用现有 `dialog.status === 'generating'`|沿用现有 SFX 提交入口和默认 Prompt 规则|
|BGM|AI processing、`submitting``generating` 任一成立|沿用 canonical Prompt 的有效字符与 `1200` code point 规则|
BGM 使用 `PlatformTextField + readOnly + aria-invalid`SFX 继续使用 `AutoGrowTextArea + disabled`。本次不统一这两个输入控件,也不改错误、可访问名称或按钮文案。
## 六、预计代码改动
|文件|最小改动|
|---|---|
|`ImageCanvasGenerationComposerView.tsx`|恢复共享 `ImageCanvasAudioGenerationComposerView``isSoundEffect`;移入现有 BGM JSX、常量和依赖;删除独立 BGM import 与双渲染入口|
|`ImageCanvasGenerationComposerView.test.tsx`|迁入独立 BGM composer 的全部测试,并增加双 mode 隔离与切换回归|
|`ImageCanvasBackgroundMusicGenerationComposerView.tsx`|删除|
|`ImageCanvasBackgroundMusicGenerationComposerView.test.tsx`|覆盖迁完后删除|
|`useImageCanvasBackgroundMusicPromptAssist.ts`|只修正指向独立 composer 的历史注释;不改 controller 行为|
以下生产文件预计不改:`useImageCanvasGenerationSurface.tsx` 的现有 props 接线、BGM Prompt / 预设模型、预设跑马灯、submission workflow、submission model、dialog model、`src/index.css` 的现有 BGM 样式,以及全部后端代码。
如实施时发现必须超出该清单才能保持现有行为,应先停下并重新确认边界,不能借本次组件归并顺带重构。
## 七、测试迁移与回归矩阵
### 7.1 BGM 原覆盖完整迁移
独立组件测试中的下列覆盖必须逐项迁入 `ImageCanvasGenerationComposerView.test.tsx`
- canonical preview 计数、超限展示和不改写输入框。
- 0 / 1 / 2 个有效字符及 200 / 201 / 2000 / 2001 边界。
- 补全、简化、撤销和预设按正确 dialog ID 路由。
- `preparePreset` 拒绝时不写回,预设成功时沿用追加和清快照语义。
- 助手错误与生成错误的展示顺序。
- Suno、泥点价格、生成允许 / 拒绝。
- `completing``simplifying``submitting``generating` 锁定。
- 完整单层撤销按钮矩阵和现有可访问属性。
### 7.2 mode 隔离与切换
- SFX 渲染时不存在 BGM 控件,也不存在本次明确排除的 SFX V2 控件。
- BGM 渲染时不存在 Vidu 和音效时长控件。
- SFX 仍保持 `audio1.0`、2–10 秒、默认 5 秒、当前价格和既有提交参数。
- BGM dialog A 展开预设后切到 dialog B,局部展开与滚动状态不继承。
- SFX 与 BGM 相互切换时,菜单、锁和 Prompt 助手状态不跨 mode 泄漏。
- 缺少稳定 ID 或 controller 的 BGM 失败关闭;同样条件不影响 SFX 正常渲染。
### 7.3 最小验证命令
```powershell
npm run test -- src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasBackgroundMusicPromptModel.test.ts src/components/image-editor/ImageCanvasBackgroundMusicPresetModel.test.ts src/components/image-editor/ImageCanvasBackgroundMusicPresetMarquee.test.tsx src/components/image-editor/useImageCanvasBackgroundMusicPromptAssist.test.tsx
npm run test -- src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx
npm run typecheck
npm run check:encoding
git diff --check
```
删除旧测试文件后,还要确认测试收集清单中不再引用它。若本次改动触发其它现有图片画布测试失败,只修复由共享 composer 归并直接造成的回归,不扩大到无关模块。
## 八、实施顺序
1. 在总 composer 内恢复共享音频组件、`isSoundEffect` 和单一音频渲染入口。
2. 原样迁入 BGM 分支,保留按 ID 写回、锁定、错误顺序、Suno 与预设行为。
3. 迁移独立 BGM 组件测试,并补齐 SFX/BGM 隔离与切换用例。
4. 删除独立 BGM composer 及其测试文件,修正相关注释与文档引用。
5. 运行定向测试、typecheck、编码检查和差异检查;实现完成后再把实际结果补入 T6 后续记录。
## 九、完成定义
- 两个音频 mode 均从同一个 `ImageCanvasAudioGenerationComposerView` 渲染,且组件内存在唯一的 `isSoundEffect` 分流。
- 独立完整 BGM composer 文件和独立测试文件已删除,原测试覆盖无遗漏地迁入总 composer。
- BGM V1 所有已交付能力与正式提交语义不变。
- 现有 SFX UI、Prompt、Vidu、时长、价格、校验和提交链路无回归。
- 没有实现任何 SFX V2 独有功能,没有修改需求原文或后端契约。
- 规定的定向测试、typecheck、编码检查和 `git diff --check` 全部通过。
@@ -6614,3 +6614,10 @@
- 遗留(建议单开,不在本次范围):`loadProjectCoverImage` 里无 timeout / 无 AbortSignal 的 `new Image()` 本身仍是隐患,自动保存路径一样会踩。本次只是把它移出生成链的关键路径,没有消除它。
- 影响范围:`useImageCanvasProjectPersistence.ts``flushProjectPersistence`。不改服务端、不改契约。
- 验证方式:既有用例「flush 等待封面缓存」翻转为「flush 不等封面、但封面链照常跑完并完成上传与资源登记」;新增「封面永不 settle 时 flush 仍返回」——用永不 resolve 的 blob 模拟 `new Image()` 不 settle,并断言 `createProjectCoverSnapshotBlob` 确实被调用过以防用例空过。已实证:回退修复后新用例报 `expected 'false' to be 'true'`。运行 `npx vitest run src/components/image-editor src/components/platform-entry src/services`101 文件 / 1241 项)、`npm run typecheck``npm run lint:eslint``npm run check:encoding`
## 2026-08-06 音效与背景音乐恢复共享音频 Composer
- 决策:图片画布的 `audio-sound-effect``audio-background-music` 只保留一个 `ImageCanvasAudioGenerationComposerView`,组件内以 `isSoundEffect = dialog.mode === 'audio-sound-effect'` 分流。撤销的是完整 `ImageCanvasBackgroundMusicGenerationComposerView` 这一层视图拆分,不撤销 BGM Prompt 纯模型、助手 controller、预设模型或预设跑马灯的独立职责。
- 业务隔离:共享组件不等于共享规则。SFX 继续使用 Vidu `audio1.0`、2–10 秒、默认 5 秒、现有 Prompt 回退、1500 字限制、价格和提交链路;BGM 继续使用 canonical Prompt、200 字生成限制、30 个预设、AI 补全 / 简化、单层撤销、提交锁和 Suno。BGM 按 dialog ID 写回,SFX 继续走现有 `setGenerateDialog`,两条路径不得互换。
- 非目标:本次只规划视图归并,不实现 SFX V2 的 ElevenLabs、中译英、自动时长、30 秒、Loop、一键优化或预设,不修改任何后端、External v1、Schema、计费或需求原文,也不新建配置驱动的 composer 框架。
- 实施状态:当前代码仍保留独立 BGM composer,待按 `docs/project-memory/plans/【前端重构】音频生成面板恢复共享分流方案-2026-08-06.md` 落地。完成前不得把本决策写成已通过的实现结论;完成时必须迁移全部独立组件测试并同时证明 SFX 与 BGM 两个分支无状态和控件泄漏。
@@ -4201,3 +4201,10 @@
- 原因:两个跨模块测试读写同一进程全局状态,却没有共用隔离边界;只给 accept 后取得的 stream 设置 read timeout 无法约束 accept 本身,payload 读取也缺少总 deadline。
- 处理:全部全局 sink 测试共用一把 test-only 串行锁,并由 RAII guard 在 `Drop` 中无条件清空;测试统一使用 `manifest_invalidation_sink_isolation_` 前缀。relay fixture 对 accept 和 payload 分别使用非阻塞轮询与总 deadline,不使用固定 sleep;生产 loopback、token、连接 / 写入超时和 payload 大小校验保持不变。
- 验证:用 `--test-threads=2` 重复运行统一 filter,覆盖正常 relay、无事件 accept 超时、不完整 payload 超时、panic 展开清理,以及 GUI owner attach 配置与 guard 清理。
## 共享音频 Composer 架构冲突不能按单行选边(2026-08-06)
- 现象:master 的音频 composer 同时承载 SFX 与 BGM,并在组件内定义 `isSoundEffect`;功能分支把 BGM 拆成独立组件后,原组件变成 SFX-only。合并时只把 master 的条件占位表达式带回 SFX-only 组件,没有带回变量定义,最终在测试渲染阶段报 `isSoundEffect is not defined`
- 原因:冲突两侧代表不同组件架构,逐行保留看似有用的 JSX 会把一个架构中的局部条件拼进另一个架构。import 排序、格式检查和只覆盖单一 mode 的测试都不能证明这种组合成立。
- 处理:先确定权威组件边界,再按完整调用链解决冲突。图片画布音频入口当前决策是恢复一个共享 `ImageCanvasAudioGenerationComposerView`,由组件内 `isSoundEffect` 分流;BGM/SFX 的 validator、写回、锁和提交契约仍分别保持。不要只补一个常量后继续维持已经废弃的双 composer 边界。
- 验证:同时渲染 `audio-sound-effect``audio-background-music`,覆盖两个 mode 的正向控件和互斥负向断言、dialog / mode 切换、BGM 稳定 ID 与 controller 缺失的失败关闭,并运行 `ImageCanvasGenerationComposerView.test.tsx` 与 typecheck。
@@ -144,3 +144,9 @@ API 安全响应摘要:
- 本地 API mock 链路 `27/27` 通过,并确认请求 body 的 `model``gpt-5.6-luna`
- 真实 VectorEngine smoke 已尝试:`GET /v1/models``POST /v1/chat/completions` 均在建立 HTTP 连接前以 `fetch failed` 结束,没有返回状态码或模型响应;该结果只能说明当前环境网络不可达,不能判定 `gpt-5.6-luna` 被上游拒绝或支持。
- 网络恢复后重试成功:补全和简化各发送一条最小 Chat Completions 请求,均返回 HTTP 200、`model=gpt-5.6-luna``finish_reason=stop`,并解析出完整四字段 JSON object;补全候选 `126` code points,简化候选 `79` code points。由此确认当前模型和两条助手请求形态可以跑通。
## 2026-08-06 组件架构后续修订
本文前述 T6 数字与文件范围是当时实施完成后的历史记录,不因后续组件归并而改写。新的权威方向是让 SFX 与 BGM 恢复使用 `ImageCanvasGenerationComposerView.tsx` 内同一个音频 composer,并由 `isSoundEffect` 分流;详见[音频生成 Composer 恢复共享分流方案](./project-memory/plans/【前端重构】音频生成面板恢复共享分流方案-2026-08-06.md)。
该修订目前只完成规划,尚未实施。落地时必须把 `ImageCanvasBackgroundMusicGenerationComposerView.test.tsx` 的全部覆盖迁入 `ImageCanvasGenerationComposerView.test.tsx` 后再删除旧测试文件,并同时重跑 BGM 完整动作矩阵、SFX 现有 Vidu / 时长 / Prompt 回退回归、surface 接线、submission workflow、typecheck、编码检查与差异检查。不得把原 T6 的历史通过数字直接当作共享 composer 已通过的证据。
@@ -2,13 +2,15 @@
日期:`2026-06-18`
更新时间:`2026-08-05`
更新时间:`2026-08-06`
## 范围
本次只在 `/editor/canvas` 图片画布编辑器内新增底部 `生成音乐` 入口,用于生成完整游戏音效或游戏背景音乐。该入口属于画板生成类工具,不新增平台玩法入口、不进入作品发布链路,也不修改现有视觉小说音频生成开关。
2026-08-04 起,本文增加 BGM Prompt 优化 V1.0 口径。该切片只修改 `audio-background-music``audio-sound-effect` 的 UI、Prompt 回退、Vidu `audio1.0` 请求、`2-10` 秒时长和 1500 字限制全部保持不变。共用组件若需要调整,必须按生成器模式隔离行为不得把 BGM 规则扩散到 SFX。
2026-08-04 起,本文增加 BGM Prompt 优化 V1.0 口径。该切片只修改 `audio-background-music``audio-sound-effect` 的 UI、Prompt 回退、Vidu `audio1.0` 请求、`2-10` 秒时长和 1500 字限制全部保持不变。音效与背景音乐使用同一个音频 composer,并由组件内的 `isSoundEffect = dialog.mode === 'audio-sound-effect'` 隔离行为不得把 BGM 规则扩散到 SFX。
2026-08-06 的组件架构修订只撤销完整 BGM composer 的独立视图边界。共享音频 composer 的 SFX 分支保留当前 Vidu、时长、默认 Prompt、1500 字和价格行为,BGM 分支保留本文规定的预设、计数、AI 补全 / 简化、撤销、锁定、canonical Prompt 和 Suno 行为。BGM 纯状态模型、助手 controller 与预设跑马灯仍可保持独立职责;本轮不实现 SFX V2 的 ElevenLabs、中译英、自动时长、30 秒、Loop、一键优化或预设等独有能力。具体实施边界见[音频生成 Composer 恢复共享分流方案](./project-memory/plans/【前端重构】音频生成面板恢复共享分流方案-2026-08-06.md)。
## 入口与交互
@@ -371,4 +373,5 @@ POST /api/editor/audios/background-music/prompts/simplifications
- BGM 助手入口测试覆盖 canonical 2000 字允许简化、2001 字返回 `400` 且不调用 LLM;两个助手路由 body 超过 `32 KiB` 时返回 `413`;连续合法请求不因本功能新增限流器返回 `429`
- 两个助手成功路由分别产生 `editor_background_music_prompt_completion` / `editor_background_music_prompt_simplification` tracking event,均为 `module_key = editor`、User scope;失败响应沿用普通 route tracking 只记录成功的现状。
- BGM Suno body 仍只包含 `mv``gpt_description_prompt``make_instrumental`,固定 `Suno` 胶囊和动态泥点价格不变;SFX 的 Vidu body、默认 Prompt、时长与 1500 字限制无回归。
- `audio-sound-effect``audio-background-music` 必须由同一个音频 composer 渲染,并在组件内通过 `isSoundEffect` 分支。SFX 不得渲染 BGM 控件或尚未实施的 SFX V2 控件,BGM 不得渲染 Vidu 与音效时长控件;两个 mode 相互切换时,菜单、预设滚动、锁和助手状态不得跨分支泄漏。
- 本切片的定向前端、shared-contracts、`api-server``platform-audio` 和端到端 Prompt 等值测试通过,并执行 `npm run typecheck`、对应 Rust 定向测试、`npm run check:encoding``git diff --check`