diff --git a/docs/README.md b/docs/README.md index 518c38c74..dd71e8670 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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) diff --git a/docs/project-memory/plans/【前端重构】音频生成面板恢复共享分流方案-2026-08-06.md b/docs/project-memory/plans/【前端重构】音频生成面板恢复共享分流方案-2026-08-06.md new file mode 100644 index 000000000..3f3cf17f7 --- /dev/null +++ b/docs/project-memory/plans/【前端重构】音频生成面板恢复共享分流方案-2026-08-06.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` 请求字段。 +- `2–10` 秒、步长 `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 的有效字符与 `1–200` 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` 全部通过。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 6a096a85b..acc20ec4c 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -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 两个分支无状态和控件泄漏。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 6f5734eeb..ed6c5aff8 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -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。 diff --git a/docs/【实施记录】BGM生成提示词优化T6测试与发布门禁-2026-08-05.md b/docs/【实施记录】BGM生成提示词优化T6测试与发布门禁-2026-08-05.md index b07bbd139..1e6fab090 100644 --- a/docs/【实施记录】BGM生成提示词优化T6测试与发布门禁-2026-08-05.md +++ b/docs/【实施记录】BGM生成提示词优化T6测试与发布门禁-2026-08-05.md @@ -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 已通过的证据。 diff --git a/docs/【编辑器】画板音乐生成入口设计-2026-06-18.md b/docs/【编辑器】画板音乐生成入口设计-2026-06-18.md index 48f8ade15..8417cf8ea 100644 --- a/docs/【编辑器】画板音乐生成入口设计-2026-06-18.md +++ b/docs/【编辑器】画板音乐生成入口设计-2026-06-18.md @@ -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`。 diff --git a/local-docs/【实施方案】BGM生成提示词优化T5正式提交与方案A锁-2026-08-05.md b/local-docs/【实施方案】BGM生成提示词优化T5正式提交与方案A锁-2026-08-05.md deleted file mode 100644 index 10b197dfc..000000000 --- a/local-docs/【实施方案】BGM生成提示词优化T5正式提交与方案A锁-2026-08-05.md +++ /dev/null @@ -1,259 +0,0 @@ -# BGM 生成提示词优化 T5 正式提交与方案 A 锁实施方案 - -日期:`2026-08-05` - -状态:实施完成,待提交 - -开发分支:`codex/bgm-generation-opt-v1` - -## 一、文档定位 - -本文承接 T5 的具体工程规划、现役链路调研、改动边界、测试设计和后续实施记录。 - -- 阶段级目标、依赖关系和总体完成定义继续保留在[总任务拆解](./【实施计划】BGM生成提示词优化V1.0任务拆解-2026-08-04.md)。 -- 产品与技术口径以[画板音乐生成入口设计](../docs/【编辑器】画板音乐生成入口设计-2026-06-18.md)为权威。本次修订只补齐该既有口径在现役 queue / inline、scope 所有权和动态价格链路上的实现边界,不新增产品能力。 -- T4 的组件与交互细节留在[T4 界面与预设交互实施方案](./【实施方案】BGM生成提示词优化T4界面与预设交互-2026-08-04.md);该文第十二节列出的交接风险由本文承接并给出解法。 -- 本文不得反向修改需求文件,也不得用实现便利覆盖权威设计。 - -## 二、实施基线 - -已完成提交: - -- T3:`825b0ccf6` -- T3-R2:`3fbdf210b` -- T0 补差:`0dc1eed38` -- T4:`d825d8181` - -### 2.1 现役正式提交链路调研结论 - -以下为实施前的实测事实,不是推测: - -1. `submitImageGeneration`(`useImageCanvasGenerationSubmissionWorkflow.ts`)服务 `edit` / `image` / `character` / `video` / `audio-sound-effect` / `audio-background-music` 六种 mode,入口没有任何同步锁。现有防重依赖“重渲染后生成按钮 `disabled`、composer 卸载”,属于渲染后生效的状态防护,不满足方案 A 要求的同步互斥。 -2. 入口第一行是 `dialog.prompt.trim() || 默认回退`。它同时踩三个问题:JavaScript `String.trim()` 会额外删除首尾 `U+FEFF`,与 Rust 侧不一致;空值回退为“游戏背景音乐”形成用户不可见 Prompt;紧接着**同步**把 dialog 置为 `status:'generating'`、`composerOpen:false`。 -3. 第 3 点会直接打死提交锁:Prompt 助手 hook 的 dialog 清理 effect 看到 `composerOpen === false` 就 `clearActiveOperation(abort:true)`,把刚 claim 的 `submitting` operation reject 掉;随后 `finishSubmission` 找不到 active operation 返回 `false`,锁形同虚设,dialog 重新打开后还能再次提交。 -4. `buildImageGenerationSubmissionPlan` 内部还会独立调用一次 `getDialogDefaultPrompt`(`ImageCanvasGenerationSubmissionModel.ts`)。只删除入口那一处回退不够,默认文案会从这里回到请求体。 -5. 后端生成记录 `prompt` / `actual_prompt`、canvas layer 和内部完成响应已经取自 `normalized.gpt_description_prompt`;但 queue 入口虽然先计算了 `normalized`,实际传给 `enqueue_editor_generation_job_for_caller` 的仍是原始 `payload`。因此“只替换 normalize 即可让队列等值”的旧判断错误,站内 BGM handler 必须在入队前把 canonical Prompt 和固定 `make_instrumental:true` 写回本次入队 payload。 -6. Rust 的 `str::trim()` 语义与本需求 canonicalizer 一致(都按 `char::is_whitespace` 删除两端)。真正的跨语言不一致只存在于 TypeScript 侧;BFF 和 `platform-audio` 仍需防御性调用已有 BGM 专用 validator,以拒绝空值和超限值。 -7. BGM composer 的提交锁完全来自 `assistState.status === 'submitting'`,与 `dialog.status` 无关。因此 T5 不需要新增 dialog 状态:提交期间 `dialog.status` 保持 `idle`,面板由助手状态锁定。 -8. 状态模型对 `submitting` 的 `rejectOperation` 会保留 `undoPromptSnapshot`。“后端拒绝时保留提交前撤销快照”已经具备,T5 不需要修改纯状态模型。 -9. `generateEditorBackgroundMusic` 当前给正式 POST 配置了 `retryUnsafeMethods:true`、最多两次 transport retry,而站内 queue 每次请求都会生成新的 job ID。UI 同步锁无法阻止 client 内部重发;T5 必须仅对 BGM 正式 POST关闭这项既有自动重试,不能为此新增幂等协议或改动其它生成 mode。 -10. `finishSubmission` 返回 `false` 既可能表示 dialog 被删除,也可能表示账号 / 项目 scope 已变化。正式任务不能因此被取消,但本地 dialog、Prompt、composer 和结果层也不能继续只按 `generation-dialog-N` 写回,因为该 ID 不是跨项目全局唯一。 -11. 生产和默认配置走 queue:首次成功响应只携带 `queueState`,此时即可判定站内任务已创建。兼容 inline 模式不会暴露中间接受响应,HTTP Promise 要到生成完成后才返回,不能把两种模式写成同一个可观察时序。 -12. `vector_engine_audio_generation/tests.rs` 当前没有被父模块挂载,不能作为 T5 验收依据。新增 Rust 用例必须放进实际编译的模块内测试或真正挂载的精简测试模块。 - -## 三、既有口径下的实现收口 - -### 3.1 提交锁与 composer 生命周期 - -T4 交接要求“后端接受后才隐藏 composer”,同时要求普通 `composerOpen=false` 不得提前 reject `submitting`。两者分别处理正常接受时序和用户主动归档 / 切面板,不是新增交互: - -- 当前 BGM 面板保持打开时,提交期间继续可见并全锁;queue 模式收到任务已创建的成功响应后才结束 `submitting` 并切入现有占位。 -- 用户在等待期间归档或切走面板时,只把原 dialog 变为 inactive / closed,不取消已经发出的正式请求,也不主动重新激活该 dialog。 -- dialog 被删除或账号 / 项目 scope 改变时,可以移除临时助手状态;这不代表正式请求已取消。 - -### 3.2 正式任务处理与本地 UI 写回必须分开 - -点击时冻结本次 `{账号、项目、dialogId、operation、canonical Prompt、submission plan}`。后续异步阶段遵循两条独立规则: - -1. 已发出的正式请求和后端已经创建的任务继续按现役链路处理,不能因 `finishSubmission` 返回 `false` 被误判为取消。 -2. `status`、`composerOpen`、Prompt、错误信息、本地 `applyProjectSnapshot` / asset upsert 和结果层都属于 UI 写回;每次跨 `await` 后写入前都必须重新确认仍是原账号、原项目和原 dialog。scope 已变化或 dialog 已删除时跳过本地 UI 写回,不能按同名 ID 修改新 scope。 - -普通 `composerOpen=false` 且 scope / dialog 仍匹配时,`finishSubmission` 仍可结算。若随后失败: - -- 原 dialog 仍是当前打开面板:恢复 `failed + composerOpen:true`。 -- 原 dialog 已被用户归档或切走:写入失败状态和 canonical Prompt,但保留关闭 / inactive,不抢回焦点。 -- 原 scope 已失效或 dialog 已删除:不再写本地 UI。 - -因此,`finishSubmission` 的返回值不能取消正式任务,但也不能被解释成“所有本地结果都可无条件写回”。 - -### 3.3 可观察提交时点与 transport 边界 - -```text -点击生成 -→ 同步 beginSubmission:canonicalize、写回、资格校验、冻结 scope 与请求值 -→ 只发送一次 BGM 正式 POST,不执行 unsafe 自动重试 -├─ queue 接受 → finishSubmission({ operation, accepted: true }) → 原 UI 目标仍有效时切 generating / 隐藏 composer → 现有轮询 -├─ 首次 POST 非成功或抛错 → finishSubmission({ operation, accepted: false }) → 原 UI 目标仍有效时记录失败 -└─ inline 兼容模式 → 无中间接受响应,保持 submitting 到现有终态响应,再结算并处理结果 -``` - -- T5 不新增 Idempotency-Key、持久提交账本、服务端 dedupe 或新的响应阶段;“一次点击一次请求”通过同步 UI 锁和关闭 BGM 正式 POST 的既有 unsafe retry 实现。 -- 请求返回 transport 错误时,客户端不能证明服务端一定未接受;T5 不自动重发。用户之后再次明确点击属于新的提交,不在本切片引入跨页面、跨 scope 的 durable exactly-once 设计。 -- 保留现有 BGM 正式请求超时;不新增重试、超时或限流。 - -## 四、改动边界 - -- 只改 `audio-background-music`。`edit` / `image` / `character` / `video` / `audio-sound-effect` 的提交代码路径、文案、默认 Prompt 和错误展示一字不动。 -- 实现方式为在 `submitImageGeneration` 内增加 BGM 前置分支,分支结束后复用现有 audio 结果处理(队列、占位、素材库、项目刷新、`addAudioResultLayer`),不复制这段逻辑,也不新建平行提交函数。 -- 不新增 dialog 状态、不修改纯状态模型、不新增持久提交锁或全局画布锁。 -- 只移除 BGM 正式 POST 的 `EDITOR_REQUEST_RETRY_OPTIONS`;不修改共享 retry 配置或其它请求。 -- 不新增 Idempotency-Key、dedupe、请求账本、响应字段或 queue 状态。 -- 站内 BGM handler 的 queue payload 执行 canonical 写回;不改变 External v1 的请求、幂等键或 payload 等值语义。 -- 不修改 Suno 三字段结构、固定模型、现役动态价格配置和计费包装;客户端不提交价格,服务端继续在入队时冻结价格。 -- “正式请求不含助手隐藏元数据”只约束本功能前端构造的 submission payload 和固定三字段 Suno body;不在 T5 新增通用 `generationInputs` allowlist 或恶意客户端清洗器。 -- 不修改 SpacetimeDB schema、External v1、External OpenAPI 或 LLM 日志策略。 -- 不增加新的重试、超时或限流。 - -## 五、目标文件结构 - -|文件|T5 职责| -|---|---| -|`useImageCanvasBackgroundMusicPromptAssist.ts`|清理 effect 豁免 `submitting`;`finishSubmission` 不再要求 composer 打开| -|`useImageCanvasGenerationWorkflow.ts`|把现有完整 Prompt 助手 controller 和当前 scope 接入 submission workflow| -|`useImageCanvasGenerationSubmissionWorkflow.ts`|BGM 前置分支:同步取锁、冻结 scope / 请求值;分离正式任务处理与 scope-safe UI 写回| -|`ImageCanvasGenerationSubmissionModel.ts`|BGM 分支使用冻结的 canonical Prompt,不再经过默认回退| -|`editorProjectClient.ts`|仅移除 BGM 正式 POST 的 unsafe 自动重试,保留现有超时| -|`server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs`|BGM 入站改用 canonical 校验;站内 handler 用 canonical payload 进入 queue| -|`server-rs/crates/platform-audio/src/request.rs`|BGM Suno body builder 防御性 canonicalization| -|对应现役测试文件|覆盖 client 单次发送、scope 所有权、queue payload、body 与持久化等值;不依赖未挂载的 `vector_engine_audio_generation/tests.rs`| - -## 六、T5A:助手生命周期豁免 - -### 6.1 修改 - -- 清理 effect 的第二个循环:active operation 为 `submitting` 时跳过,不 abort、不 reject、不移除记录。 -- `finishSubmission`:移除“composer 仍打开”的前置条件;operation、scope 和 BGM dialog 仍匹配即可结算。 -- 返回值只表示这次助手 operation 是否由原 UI 目标成功结算。正式请求已经发出后,该值不负责取消任务;submission workflow 仍需在后续每个 UI 写回点重新检查冻结 scope。 -- 其余 6 处 `composerOpen` 判断保持不变。AI 操作入口在 `submitting` 期间已由 `isSubmissionLocked` 挡住,迟到响应、预设、撤销和 `beginSubmission` 的语义不受影响。 - -### 6.2 验收 - -- 提交期间把 `composerOpen` 置 `false`(选中其它图层、归档、切面板),`submitting` 不被 reject,`finishSubmission` 仍能结算。 -- 提交期间删除 dialog:UI 状态移除,`finishSubmission` 返回 `false`,不抛错。 -- 提交期间切换账号或项目:助手状态被 reset,`finishSubmission` 返回 `false`;正式任务继续,但旧回调不能写入新 scope 的同 ID dialog。 -- T3 / T3-R2 既有的 operation ID、跨 dialog、迟到响应、超时和 scope 用例继续通过。 - -## 七、T5B:前端 BGM 提交分支 - -### 7.1 同步阶段(必须早于任何 `await`) - -1. 用类型收窄取得带稳定 ID 的 BGM dialog;不是 BGM 则走现有通用路径。 -2. `beginSubmission(dialog.id)` 在同一个同步边界内完成 canonicalize、输入框写回和正式生成资格校验;状态模型继续是唯一资格判定源,不在 workflow 再写第二套 0 / 200 校验。 -3. 返回 `null` 表示重复点击、scope / dialog 不合格或 Prompt 无正式生成资格,直接结束;canonical 写回按现有 controller 结果保留,不发送请求。 -4. claim 成功后冻结 `{账号、项目、dialogId、operation、canonical Prompt}`,并以该 Prompt 构造 submission plan;BGM 分支不得再经过原生 `trim` 或 `getDialogDefaultPrompt`。 -5. 不在同步阶段切换 `status` / `composerOpen`。 - -### 7.2 异步阶段 - -- 调用 `generateEditorBackgroundMusic` 一次;该 client 不再配置 unsafe retry。 -- queue 首次成功响应代表任务已创建:先调用 `finishSubmission({ operation, accepted: true })`;原 UI 目标仍有效时再设 `status:'generating'`、`composerOpen:false`,随后复用现有 queue 轮询、项目刷新、素材库和结果处理。 -- 首次 POST 非成功或抛错:先调用 `finishSubmission({ operation, accepted: false })`。原 dialog 仍是当前面板时恢复 `failed + composerOpen:true`;已归档 / 切走时保留关闭状态并记录失败;scope 已变化或 dialog 已删除时不写本地 UI。 -- queue 已接受后的终态成功 / 失败继续走现有正式链路;每次应用 project snapshot、upsert asset、写 dialog 或本地结果层前重新检查冻结 scope 和 dialog。scope 已失效时跳过这些前端后续写回,后端已经接受的任务继续处理;T5 不新增跨 scope 的恢复或刷新机制。 -- inline 兼容模式没有 `queueState`:面板保持 `submitting` 到现有 HTTP 终态响应;成功后结算并直接处理现有结果,失败按上一条恢复,不新增中间响应或占位协议。 - -### 7.3 验收 - -- 同一 dialog 快速重复点击只产生一次正式 POST;queue 模式只创建一个 queue job 并沿用该 job 的现有计费链路,inline 模式沿用现有终态处理;retryable HTTP 状态或 transport error 也不会由 client 自动重发。 -- 空 Prompt、全 Unicode 空白 Prompt 不产生正式请求。 -- 1 个有效字符可以提交;201 个 code point 不能提交。 -- 提交期间输入框、预设、展开收起、箭头、AI 补全、一键简化、撤销和生成全部锁定,面板保持可见。 -- 后端拒绝后 canonical Prompt 和撤销快照保留;当前面板恢复,用户已经归档 / 切走的面板不被自动激活。 -- 后端接受后,原 UI 目标仍有效时才进入占位;画布其它功能全程可用。 -- 账号 / 项目 A 的迟到成功或失败不能修改账号 / 项目 B 的同 ID dialog;queue 已接受后再切 scope 也遵守该规则。 -- inline 模式在终态响应前保持 `submitting`,不为兼容模式新增协议。 -- 其余 5 种 mode 的提交行为无差异。 - -## 八、T5C:后端 canonicalization - -### 8.1 修改 - -- `normalize_editor_background_music_request_with_pricing` 改用 BGM canonical 校验函数:canonicalize、至少 1 个有效字符、不超过 200 个 code point,返回 canonical Prompt。 -- 登录态站内 `generate_editor_background_music` handler 在 queue / inline 分流前,用上述结果构造本次 canonical payload:覆盖 `gpt_description_prompt`,并固定 `make_instrumental:true`。queue 分支必须把这份 payload 交给现有 enqueue helper,不能继续序列化原始请求。 -- 不在共享 External v1 调用边界新增 payload 改写;External v1 路由、Idempotency-Key、OpenAPI 和现有 payload 等值判断保持不变。 -- `build_editor_background_music_task_body` 执行同一套幂等 canonicalization 后再构造 Suno body。 -- BGM 请求不接收客户端价格。服务端继续按现役动态定价解析,并把本次价格冻结到 queue job;后续计费 target、资产成本和内部完成响应继续复用该冻结价格,不硬编码 `5`。 -- SFX 继续使用现有 `normalize_limited_text`,Vidu body、默认 Prompt、时长与 1500 字限制不变。 - -### 8.2 验收 - -- 空 Prompt、全空白 Prompt 返回 `400`,不创建任务、不扣费。 -- 201 个 code point 拒绝;200 个通过。 -- 站内 queue 的实际 `request_payload_json.gptDescriptionPrompt` 与 canonical Prompt 逐 code point 等值,`makeInstrumental` 为 `true`;不能只断言 normalize 返回值。 -- 首尾 `U+0085` 等 Unicode `White_Space` 被删除;`U+200B`、`U+FEFF`、组合字符和 ZWJ emoji 不被误删;内部空格与 LF / CRLF 原样保留。 -- 连续两次 canonicalization 结果一致。 -- Suno body 仍只含 `mv`、`gpt_description_prompt`、`make_instrumental`。 -- queue job 的 `price_mud_points`、计费 target 和内部完成响应使用同一次服务端冻结价格;前端请求中没有 `priceMudPoints`。 -- External v1 契约与幂等回归无差异。 -- SFX 相关 Rust 测试无回归。 - -## 九、T5D:端到端等值 - -- 逐层断言写回后的输入框、前端 BFF 请求、站内 generation queue payload、Suno body、`editor_project_resource`、`editor_asset`、canvas layer 以及内部 `EditorAudioGenerateResponse` 的 Prompt 字段逐 code point 完全一致。 -- queue 首次响应仍只断言现有 `queueState`;普通站内 job 的 `result_payload_json` 继续使用现有 metadata-only 形态,不为 T5 增加 `prompt` / `actualPrompt` 字段。`prompt` / `actualPrompt` 的完成响应断言落在 inline 或 worker 内部生成响应边界。 -- 断言本功能前端构造的正式 submission payload 不含预设 ID、分组、颜色、助手系统模板、内部引导、AI 快照或格式 / 完整性 / 残句判断字段;Suno body 的 key 集合精确等于三个固定字段。 -- 不把上条扩展成 BFF 对任意 `generationInputs` 的通用 allowlist / sanitizer,本切片只保证本功能客户端不发送这些隐藏元数据。 -- 用例至少覆盖:含首尾边界空白、内部换行和零宽字符的 Prompt;仅 1 个有效字符的 Prompt;恰好 200 个 code point 的 Prompt。 - -## 十、测试计划 - -|层级|场景|期望| -|---|---|---| -|助手 hook|提交期间 `composerOpen=false`|`submitting` 不被 reject,`finishSubmission` 可结算| -|助手 hook|提交期间删除 dialog|移除 UI 状态,`finishSubmission` 返回 `false`,不抛错| -|助手 hook|提交期间切换账号 / 项目|状态 reset;正式任务继续,旧 UI 不写入新 scope| -|BGM client|retryable HTTP 状态 / transport error|一次调用只发送一次 POST,不执行 unsafe retry| -|前端提交|双击生成|一次 client 调用、一次正式请求| -|前端提交|空 / 全空白 Prompt|不发请求,保留 canonical 文本| -|前端提交|1 个有效字符 / 200 / 201|前两者可提交,201 拒绝| -|前端提交|当前面板后端拒绝|面板恢复,canonical Prompt 与撤销快照保留| -|前端提交|归档 / 切面板后拒绝|记录失败但保持关闭,不抢焦点| -|前端提交|queue 接受|先结算再切占位,画布其它功能可用| -|前端提交|queue 接受后切 scope,再返回终态|后端已创建任务不被视为取消;新 scope 同 ID dialog 与本地画布不被旧回调修改| -|前端提交|inline 成功 / 失败|终态响应前保持 `submitting`,随后走现有结果 / 失败链路| -|后端|空、全空白、201|`400`,不创建任务| -|后端|`U+200B` / `U+FEFF` / 组合字符 / ZWJ emoji|不被误删| -|后端|幂等性|两次 canonicalization 结果一致| -|后端 queue|站内边界空白 Prompt|实际 `request_payload_json` 使用 canonical Prompt,固定 instrumental,冻结动态价格| -|后端 queue|一次站内正式 POST|创建一个 queue job,并沿用该 job 的单次计费链路| -|平台 body|边界空白 + 内部换行 + 零宽字符|三个固定字段,Prompt 逐 code point 等值| -|端到端|边界空白 + 内部换行 + 零宽字符|前端、queue、Suno、resource、asset、canvas 与内部完成响应等值| -|回归|External v1|路由、OpenAPI、Idempotency-Key 与 payload 等值语义无差异| -|回归|SFX|Vidu body、默认 Prompt、时长与 1500 字限制不变| -|回归|图片 / 视频 / 角色 / edit|提交路径与文案无差异| - -Rust 测试必须落入 `cargo test -p api-server` 实际会编译执行的位置,例如 `generation.rs` / `publish.rs` 的内联测试或新挂载的精简测试模块;不得把未挂载的 `vector_engine_audio_generation/tests.rs` 当作通过证据。T5 不借此清理整份历史测试文件,也不扩展计费系统。 - -## 十一、建议实施顺序 - -1. T5A:助手生命周期豁免(可与 T5C 并行)。 -2. T5C:站内 canonical queue payload 与 `platform-audio` 防御性 canonicalization。 -3. BGM client:移除正式 POST 的 unsafe retry;补 controller / scope 接线。 -4. T5B:前端 BGM 提交分支和 scope-safe UI 写回(依赖 T5A)。 -5. T5D:queue / inline 分流与端到端等值测试。 -6. 定向测试、类型检查、ESLint、Rust 定向测试、编码检查与差异检查。 -7. 真实浏览器验证:以默认 queue 模式走通“提交成功进占位”“当前面板失败恢复”“归档后失败不抢焦点”三条路径;inline 兼容语义由定向测试覆盖,不新增联调协议。 - -建议一个提交:`前后端:收紧BGM生成提交链路`。 - -## 十二、风险与交接 - -- 本切片是发布切点。T4 与 T5 之间不可发布,因为 T4 面板已按 canonical 口径展示,而正式提交仍走旧 `trim` 与默认回退。 -- 空 Prompt 收紧后,历史上依赖默认回退成功的空提交会被前端拦截并被后端拒绝。这是需求明确要求的行为变化。 -- 提交期间面板保持可见是既定口径,但与当前“点击立即切占位”的手感不同,需要在浏览器验证中确认无违和。 -- 正式请求没有可取消的 `AbortController`。请求发出后即使 dialog 被删除或 scope 切换,也可能已经被后端接受并扣费;实现不得把这类情况当作“已取消”。 -- BGM 正式 POST 关闭 unsafe retry 后,transport 结果未知时不会自动重发;T5 不引入 durable 幂等或对账系统。用户随后再次明确点击是新提交,这是本切片保留的既有边界。 -- scope 切换会清理临时助手锁。正式任务继续由后端处理,但旧异步回调必须停止本地 UI 写回;T5 不新增跨页面持久锁。 -- inline 兼容模式无法观察“上游任务已创建但尚未完成”的中间时刻,因此锁会保持到现有 HTTP 终态响应;生产和默认 queue 行为不受该兼容边界影响。 -- 现役 BGM 价格由服务端配置解析并在入队时冻结;T5 不修改价格、计费规则或钱包账本。 -- T6 需要汇总本切片测试,并复核完成定义中与提交链路相关的条目。 - -## 十三、实施记录 - -|日期|阶段|状态|记录| -|---|---|---|---| -|2026-08-05|T5 初始规划|已被本轮修订取代|完成正式提交链路初查;后续评审确认“queue 已使用 normalized”“权威设计无需修改”两项判断不成立。| -|2026-08-05|T5 规划修订|完成|收口 unsafe POST retry、scope-safe UI 写回、站内 canonical queue payload、queue / inline 可观察时序、动态价格和有效测试落点;明确不新增幂等协议、持久锁、响应字段、通用 sanitizer 或计费设计。| -|2026-08-05|T5A 助手生命周期|完成|修改 `useImageCanvasBackgroundMusicPromptAssist.ts` 及定向测试:`submitting` 不再因普通 composer 关闭被清理,dialog 删除和 scope 切换仍清理,`finishSubmission` 继续校验 operation、scope 与 BGM dialog。| -|2026-08-05|T5B / T5D 正式提交与等值链路|完成|修改 BGM submission model、submission workflow、workflow 接线、BGM `generationInputs` helper、正式 client 及定向测试:同步 claim 早于首个 `await`,canonical Prompt 是唯一请求值,关闭 unsafe POST retry;queue / inline 按既定时序结算;冻结 scope 版本和 UI 所有权,覆盖首次响应及 queue 终态前的 `A → B → A` 往返、当前面板失败恢复和归档后不抢焦点;其它生成 mode 保持原路径。| -|2026-08-05|T5C 后端 canonicalization|完成|修改站内 BGM handler、queue 准备边界、`platform-audio` Suno body builder 及现役测试:登录态 queue / inline 使用 canonical Prompt 并固定 `makeInstrumental=true`,queue 准备边界绑定 canonical payload 与动态冻结价格,实际 queue serializer 对该 payload 序列化;External v1 继续保留原始 payload 和既有幂等语义,SFX 未改。| -|2026-08-05|T5 修复前验证|完成|前端 5 个定向测试文件 `193/193` 通过,`npm run typecheck` 与变更文件 ESLint 通过;`platform-audio` `31/31`、窄口径 `api-server` BGM 定向 `28/28`、登录态 queue 准备 `1/1`、External v1 `7/7` 通过。该记录保留当时命令口径,后续验收以本表最新记录为准。| -|2026-08-05|T5 评审修复|完成|拆分钱包账号所有权、任务列表账号内项目所有权(同时校验 `currentUserId + projectId`)与 dialog/canvas 完整所有权;真实 transport 层覆盖 BGM 正式 POST 遇到 `429`、`503` 和 `TypeError` 均只发送一次;补齐 External v1 BGM 原始 payload 保留、同原值幂等重放及 canonical 等价值冲突回归。未修改 SFX、其它生成 mode、通用 retry、计费、Schema、External 路由 / DTO / OpenAPI 或 worker。| -|2026-08-05|T5 修复后验证|完成|前端 6 个定向测试文件 `199/199` 通过,其中真实 transport `3/3`;`npm run typecheck` 与变更文件 ESLint 通过;`platform-audio` `31/31`、广义 `cargo test -p api-server background_music` `35/35`、External v1 既有回归 `7/7` 通过;Rust 格式、编码与 `git diff --check` 通过。真实 queue 浏览器 smoke 会创建正式任务并可能扣费,本切片不执行。| - -后续每次实施只在本节追加: - -- 完成的切片; -- 实际修改文件; -- 验证命令与结果; -- 尚存风险; -- 对下一阶段的交接。