文档:冻结SFX生成优化V2实施口径

完善SFX V2权威设计、LLM参数、二进制与时长安全边界
新增可共享的T0至T6任务拆解、测试矩阵和迁移发布门禁
同步项目决策日志与文档索引
This commit is contained in:
2026-08-06 11:47:04 +00:00
parent b79f72391b
commit c72d295a84
4 changed files with 437 additions and 32 deletions
+1
View File
@@ -20,6 +20,7 @@
- [图片画布编辑器 MVP 接入方案](./technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md)
- [图片画布编辑器前端拆分计划](./technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md)
- [画板音乐生成入口设计](./【编辑器】画板音乐生成入口设计-2026-06-18.md)
- [SFX 生成优化 V2.0 任务拆解](./project-memory/plans/【实施计划】SFX生成优化V2.0任务拆解-2026-08-06.md)
- [音频生成 Composer 恢复共享分流方案](./project-memory/plans/【前端重构】音频生成面板恢复共享分流方案-2026-08-06.md)
- [BGM 提示词优化 T6 测试与发布门禁](./【实施记录】BGM生成提示词优化T6测试与发布门禁-2026-08-05.md)
- [画布 Agent 对话面板](./【编辑器】画布Agent对话面板-2026-07-03.md)
@@ -0,0 +1,234 @@
# SFX 生成优化 V2.0 任务拆解
日期:`2026-08-06`
状态:`T0 暂未通过`
开发分支:`feat/sound_opt`
代码基线:`b79f72391ba95d85c3e1dedb0c2109b28b719b91`
权威设计:[`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`](../../【编辑器】画板音乐生成入口设计-2026-06-18.md)
决策入口:[`docs/project-memory/shared-memory/decision-log.md`](../shared-memory/decision-log.md)
> 本文是通过 Git 共享的脱敏实施计划。代码、OpenAPI、测试和运行配置只实现权威设计中冻结的最终口径,不依赖任何未进入仓库的本地资料。
## 文档可见性边界
- `local-docs/` 只供当前机器本地使用,由本机 Git exclude 排除,不进入仓库;其他开发者通过 Git 无法看到、读取或核验其中任何文件。
- 除本节用于声明隔离边界外,仓库中的 tracked 文档不得链接、引用、摘录或把 `local-docs/` 中的文件作为来源、证据或前置阅读材料;代码、OpenAPI、测试、配置和提交信息也不得依赖其内容。
- 所有参与实现、审查、测试和发布所需的规则与证据,必须自包含地写入 tracked 权威设计、决策日志或本共享计划。团队成员不需要、也不应被要求访问本地资料才能开工或验收。
## T0 退出条件
T0 只冻结设计、决策、任务归属、迁移 / 回滚门禁和安全记录;不要求当前 Vidu V1 代码、OpenAPI 或实际 API 在 T0 与 SFX V2 设计一致。实现差距在 T1–T5 收敛,T6 验收。
| T0 条件 | 状态 | 证据 / 剩余动作 |
| --- | --- | --- |
| 权威 SFX V2 设计已进入 tracked `docs/` | 已完成 | 画板音乐生成入口设计的 SFX V2 章节 |
| T1–T6 计划、基线、测试和迁移门禁已通过 Git 共享 | 已完成 | 本文 |
| External v1 `model` 完整矩阵已冻结 | 已完成 | 本文“请求与幂等口径” |
| 英文化具有 LLM 语义判断和程序 Script 门禁 | 已完成 | 本文“Prompt 与 LLM 口径” |
| 一键优化与翻译的 `max_output_tokens` completion 总预算和 `length` 行为已冻结 | 已完成 | 两类请求均为 `2048 × 4 = 8192`,预算包含 reasoning 与可见输出,见本文“Prompt 与 LLM 口径” |
| 旧 Vidu 队列 drain、发布顺序和回滚门禁已冻结 | 已完成 | 本文“发布与回滚” |
| 已识别的旧凭据已轮换失效,且有不含秘密值的审计证据 | 待外部完成 | 填写下方安全审计记录 |
| 业务 owner 与 API owner 已对最终口径签字 | 待外部完成 | 填写下方 owner 签字记录 |
T0 全部行变为“已完成”前,T1–T5 不进入实现提交。
## 安全审计与 owner 签字记录
该记录只保存日期、责任人 / 工单标识和布尔结论,禁止写入账号、密码、Key、Token、Cookie 或任何可恢复凭据的值。
| 记录 | 日期 | 责任人 / 工单 | 结论 |
| --- | --- | --- | --- |
| 旧凭据轮换 | 待填 | 待填 | 待确认旧凭据已失效 |
| 本地资料隔离 | `2026-08-06` | 本机 Git exclude | 已确认仅本机可见、未被 Git 跟踪且不作为团队证据源 |
| 业务 owner 签字 | 待填 | 待填 | 待确认 |
| API owner 签字 | 待填 | 待填 | 待确认 |
## 目标和非目标
### 目标
-`/editor/canvas` 现有 `audio-sound-effect` 分支把新 SFX 任务从 Vidu `audio1.0` 切换为 ElevenLabs `eleven_text_to_sound_v2`
- 复用共享音频 composer、现有生成队列、计费、OSS、资源、素材库和画布完成态。
- 增加 52 个预设、一键优化、单层交换撤销、Worker 内统一英文化、自动 / 手动时长和 Loop。
- 稳定保存 `prompt = userPrompt``actual_prompt = actualPrompt`、实际时长、Loop、模型、provider 和平台 Task ID。
- 同批演进站内 DTO、External v1 OpenAPI、幂等语义、定价配置、部署配置和测试。
### 非目标
- 不修改 BGM Suno、BGM Prompt 助手、BGM 预设、提交锁或定价行为。
- 不新建 SFX 独立页面、平行 composer 或第二套音频业务真相。
- 不新建平行编辑器音频 DTO、正式生成 handler、BFF 或 `/api/editor/audios/*/generations` 路由;原地演进 `server-rs/crates/shared-contracts/src/assets.rs``server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs` 的现有正式链路。
- 不开放 Prompt Influence UI;服务端固定 `0.3`
- 不为新 SFX 任务提供 Vidu fallback,不删除其它未迁移调用方仍使用的 Vidu 通用能力。
- 不新增 SpacetimeDB 表或列,不向 External v1 暴露 Prompt 优化或翻译助手。
- 不执行未授权的真实付费生成。
## 当前 V1 差距与任务归属
| 领域 | 基线状态 | 收敛任务 |
| --- | --- | --- |
| SFX Prompt | `trim()`、空值回退“游戏音效”、1500 上限 | T1 实现 Unicode canonicalization、2048 上限和无默认回退 |
| SFX UI | textarea、Vidu 胶囊、210 整数时长 | T4 增加 52 预设、优化 / 撤销、自动 / 手动时长和 Loop |
| SFX DTO | `prompt + model + duration: u8` | T1 / T5 演进固定模型、nullable 小数时长、Loop 和响应字段 |
| Prompt 语义 | `prompt == actual_prompt` | T2 / T5 分离 userPrompt 和 actualPrompt |
| provider | Vidu submit + poll + URL download | T3 增加 ElevenLabs 同步二进制 adapterT5 接线 |
| 实际时长 | 请求时长同时作为结果时长 | T3 探测 MP3,T5 写回实际值 |
| Loop | 不存在 | T1 契约、T4 UI、T5 持久化与详情 |
| External v1 | nullable model 默认旧 `audio1.0`duration 为 210 integer | T1 类型基础,T5 同批修改 Rust / OpenAPI / 幂等与结果 |
| 定价 | 旧模型键 5 泥点 | T5 增加新模型键并保持 5 泥点 / 次 |
| 详情 | 通用 Prompt / Model / 时长 / Task | T5 增加中英 Prompt、Loop 和历史 Vidu 分支 |
## 冻结产品与技术口径
### Prompt 与 LLM 口径
- `userPrompt``actualPrompt` 上限均为 2048 Unicode code points。
- 只删除首尾 Unicode `White_Space`,不做 NFC、内部空白折叠、换行转换、标点替换或静默截断。
- 一键优化固定 `gpt-5.6-luna``reasoning_effort = medium`Worker 翻译固定同模型、`reasoning_effort = low`。两类请求分别按各自 2048 Unicode code point 候选上限的 4 倍,固定 `max_output_tokens = 8192`;它是隐藏 reasoning token 与可见 JSON 输出 token 共享的 completion 总预算,不是可见正文保证,也不按实际输入长度缩小。两者均不发送 temperature 或 function tools。
- 翻译 envelope 固定 `prompt / isEnglish / isFaithfulTranslation / isDirectGenerationFormat / hasAddedOrRemovedRequirement`,只接受完整 `response.text` 中的唯一 JSON object。
- `isEnglish = true` 作为 LLM 语义判断,程序侧另外要求:候选至少含一个 Script=Latin 的 alphabetic code point,且所有 alphabetic code point 的 Script 均为 LatinCommon / Inherited 数字、标点、空白和符号允许。
- 日文假名、韩文、西里尔、希腊、阿拉伯等非 Latin alphabetic Script 候选失败。测试必须覆盖中文、英文、中英混合输入,以及 actualPrompt 2048 / 2049 边界。
- 首轮成功响应但候选不合格或 `finish_reason = length` 时,使用同一 userPrompt 唯一重试;首轮 `content_filter` 和 transport 最终失败不开启第二业务语义轮。
### 请求与幂等口径
- 手动时长默认 `5s`、范围 `0.5-30s`、UI 步进 `0.1s`;自动时长默认关闭,开启时发送 null 并保留最近手动值。
- Loop 默认 false,是独立 API 参数;系统不根据 Prompt 推断、同步或校验 Loop。
- provider body 固定 `text / model_id / duration_seconds / loop / prompt_influence=0.3`query 固定 `output_format=mp3_44100_128`
- provider POST 不 retry,浏览器正式 POST 不 unsafe retry,队列 `max_attempts = 1`,一个平台 job 最多一次 ElevenLabs POST。
External v1 `model` 先删除首尾 Unicode `White_Space`,再按大小写敏感矩阵 canonicalize
| 输入 | 结果 | canonical queue payload |
| --- | --- | --- |
| omitted / `null` / 空串 / 纯空白 | 接受 | `eleven_text_to_sound_v2` |
| 首尾空白包围的新模型 | 接受 | `eleven_text_to_sound_v2` |
| `eleven_text_to_sound_v2` | 接受 | `eleven_text_to_sound_v2` |
| `audio1.0` | `400 BAD_REQUEST` | 不入队 |
| 其它未知非空值 | `400 BAD_REQUEST` | 不入队 |
所有接受形态在定价、预扣和 enqueue 前收敛为同一个 model 字段,不得产生不同幂等 payload。拒绝形态必须证明零入队、零预扣、零 LLM 和零 provider。
### 结果、计费与数据
- 服务端重建 SFX V2 `generation_inputs_json`,不信任客户端的 actualPrompt、实际时长、model 或 Loop。
- 成功响应的 MP3 必须按现有 `MAX_GENERATED_AUDIO_BYTES = 40 MiB` 有界读取并验证,探测实际时长且只以独立技术异常上限 `600s` 拒绝过长结果;不把请求最大 `30s` 当作响应上限。平台 taskId 使用 operation / queue job ID,不伪造 provider task ID。
- 新模型按次保持 5 泥点,以后端入队时冻结价格为真相。
- 不修改 SpacetimeDB schema,复用 `prompt / actual_prompt / generation_inputs_json` 和画布 layout。
## 任务包
### T0:权威设计、共享计划、安全记录与迁移口径
- 仅修改 tracked 文档,不实现功能代码。
- 完成权威设计、本共享计划、决策日志、对 V1 差距的 T1–T5 归属、drain / 发布 / 回滚门禁。
- 安全审计与 owner 签字记录必须真实、脱敏并可审计,未填写时 T0 保持未通过。
### T1Prompt 规则、52 预设、共享契约与 metadata 基础
- 实现前后端 Unicode canonicalization、code point 计数、2048 边界和无默认 Prompt 回退。
- 增加 40 + 12 预设纯模型,锁定数量、ID、分类和可见文案。
- 原地演进现有 TypeScript / Rust 音频 DTOfixed model、nullable 小数 duration、Loop、实际时长和 V2 metadata;不得新增同义 DTO 或平行正式生成契约。
- 实现 External `model` canonicalizer 的纯函数与矩阵测试;实际 OpenAPI / handler 接线属于 T5。
### T2:一键优化 BFF 和 Worker 翻译 service
- 增加登录态 SFX Prompt 优化 BFF,固定 Luna + Medium + `max_output_tokens = 8192` completion 总预算,32 KiB body limit,严格唯一 JSON envelope,不调用音频 provider 或正式计费。
- 增加仅 Worker 可调用的 Luna + Low 翻译 service,每次业务尝试固定 `max_output_tokens = 8192` completion 总预算,严格 `isEnglish` + Unicode Script 门禁、保真判断和最多一次业务重试。
- 测试覆盖中文 / 英文 / 中英混合输入,日文 / 韩文 / 西里尔等非 Latin 字母候选,actualPrompt 2048 / 2049,请求体精确 token 上限,以及优化直接拒绝 `length`、翻译首轮 `length` 重试一次 / 第二轮 `length` 最终失败和 `content_filter / transport` 行为。
### T3ElevenLabs adapter、配置、二进制与时长探测
-`platform-audio` 增加独立 ElevenLabs settings、endpoint normalizer、request builder 和 direct binary client,不伪装 Vidu / Suno poll task。
- 固定 model、influence、format、header 与 query;按 `40 MiB` 做 Content-Length 预检和 `limit + 1` 流式读取,执行 MIME / MP3 验证和纯 Rust duration probe,并以 `600s` 作为独立技术异常时长上限。
- 配置增加 `ELEVENLABS_BASE_URL / ELEVENLABS_API_KEY / ELEVENLABS_REQUEST_TIMEOUT_MS`Key 只在服务端。
- 测试断言 429 / 5xx / timeout / 读取失败都只有一次 provider POST,不执行真实付费请求。
### T4SFX 前端 controller、预设与参数 UI
- 新增 SFX Prompt 纯模型、预设纯模型和 dialog-scoped controller;抽取音频预设跑马灯内核,BGM / SFX 保留各自 wrapper。
- 在共享 composer 的 SFX 分支增加计数、52 预设、一键优化、单层交换撤销、自动 / 手动时长、Loop 和 ElevenLabs 胶囊。
- 在第一个 await 前取得 AI / 提交 operation,只锁当前 SFX dialog;迟到响应和 scope 切换不写新面板。
- T4 不切换 provider,不是可发布切点;与 T5 同一发布列车。
### T5:正式提交、Worker、计费、持久化、详情和 External v1
- 前端提交冻结 canonical Prompt、duration 和 Loop,正式 POST 不 unsafe retry。
- 原地演进 `server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs` 的现有 handler,在定价 / 预扣 / enqueue 前 canonicalize External model,入队 payload 不含提前翻译的 actualPrompt;保留现有路由注册、queue / inline 分流和计费边界。
- Worker 执行翻译、单次 ElevenLabs、MP3 时长探测、OSS 和权威 metadata / 画布写回;任一阶段失败进入现有退款链路。
- 实现新模型定价键、历史 Vidu 只读 / 重绘兼容、中英 Prompt + Loop 详情和真实时长。
- 同批更新 External v1 Rust DTO / handler / OpenAPI / Idempotency-Key 重放 / compact result;任一字段不一致时 T5 不完成。
### T6:测试、文档、灰度和发布门禁
- 汇总 T1T5 分层测试,增加 mock LLM + mock ElevenLabs + mock OSS 失败矩阵、端到端等值、刷新 / 重绘、计费退款、无重试、External 幂等和 BGM 回归。
- 更新后端架构、前端专题、开发运维和共享项目记忆。
- 执行定向 TypeScript / Rust / OpenAPI、`npm run typecheck``npm run check:encoding``git diff --check``npm run check:spacetime-schema``npm run dev:api-server` + `/healthz`
- 不将 mock 测试写成真实 provider 验收,不执行未授权付费生成。
## 依赖和发布列车
```text
T0 -> T1
T1 -> T2 + T3 + T4
T2 + T3 + T4 -> T5
T5 -> T6
```
- T2 / T3 可在 T1 契约稳定后并行;T4 可与两者后半程并行。
- T4 与 T5 之间不存在可发布切点。
- 不修改 SpacetimeDB schema;如实际实现发现必须修改,立即停止并按 schema 迁移规则重新评审,不得带入本计划默认实施。
## 测试门禁
| 层级 | 必要覆盖 |
| --- | --- |
| canonical | 空 / 全 Unicode White_Space、U+0085、U+200B、U+FEFF、换行、组合字符、ZWJ emoji、2048 / 2049 |
| 预设 | 40 + 12、ID / label 唯一、文案等值、逗号追加、重复、清快照、超限保文 |
| 优化 | Luna + Medium + `max_output_tokens=8192` completion 总预算请求体、唯一 JSON、布尔门禁、length 直接失败、content_filter、无 tool call、无候选泄漏、dialog / scope 迟到响应 |
| 翻译 | 每轮 Luna + Low + `max_output_tokens=8192` completion 总预算请求体,中文 / 英文 / 中英混合输入,日文 / 韩文 / 西里尔候选,isEnglish + Script 门禁,2048 / 2049,首轮 length 唯一重试、第二轮 length 最终失败且 provider 0 次 |
| External model | omitted / null / 空串 / 纯空白 / 包围空白新模型 / 显式新模型共用幂等 payload;`audio1.0` / 未知值为 400 + 零副作用 |
| ElevenLabs | auto / manual × Loop false / true,固定 model / influence / formatKey 不泄漏,网络 / HTTP / body 失败均只有一次 POST |
| 二进制与时长 | `40 MiB` 接受 / `40 MiB + 1 byte` 拒绝,Content-Length / chunked 超限、空 / HTML / JSON / 损坏 MP3、允许与 fallback MIME;有限正时长、30.5 / 60 / 600s 接受,>600s / NaN / 无穷拒绝 |
| 持久化 | prompt / actual_prompt / model / provider / task / actual duration / Loop 权威等值,客户端伪造值失效 |
| 计费 | 余额不足零 LLM / provider;翻译 / provider / MP3 / OSS / DB 失败一次退款 |
| 回归 | BGM Suno、助手、预设、锁和定价不变;其它 Vidu 调用方仍可编译和测试 |
## 发布与回滚
### 发布前
- 不打印值地确认生产 `ELEVENLABS_BASE_URL / ELEVENLABS_API_KEY / ELEVENLABS_REQUEST_TIMEOUT_MS` 均已配置。
- 确认定价 override 包含 `eleven_text_to_sound_v2` 且价格已批准。
- 只读查询 `editor_sound_effect_generation` 的 queued / running 旧 Vidu payload。非零时先 drain,不得让新 Worker 按 V2 nullable duration / Loop payload 解析旧任务。
- 先部署 api-server / worker,再部署 web;两者之间使用维护窗或暂时关闭 SFX 提交入口。
- External v1 变更提前通知调用方并完成 contract smoke。
### 观测
- 区分 `translation_invalid / translation_upstream_failed / elevenlabs_http_failed / invalid_audio / duration_probe_failed / oss_failed / writeback_failed`
- 只记录 operation ID、阶段、HTTP status、耗时、响应字节数和实际时长;不记录 Key 或完整 provider 错误正文。
- 对账 job 完成数、退款数、ElevenLabs 调用数和完成资源数,识别重复调用和孤儿资源。
### 回滚
- 回滚时不自动切回 Vidu;先停止新 SFX 入队。
- 等待或人工收口 V2 queued / running job,避免旧 Worker 无法解析 V2 payload。
- 协同回滚 web、api-server、worker 和 External v1 文档,禁止只回滚一层。
- 新生成的 ElevenLabs 素材继续按通用 audio / model / generation inputs 只读展示,不做数据迁移回滚。
- 没有 SpacetimeDB schema 变更,回滚不执行表迁移或字段删除。
## 完成定义
- T0 的全部 tracked 文档和可审计记录完整后才允许 T1–T5 实现提交。
- T1–T5 完成各自分层测试,T6 完成全部发布门禁。
- 新编辑器 SFX 不调用 Vidu,历史 Vidu 数据仍可读和按新模型重绘。
- 翻译最终失败时 ElevenLabs 调用为 0;成功 job 最多一次 provider POST。
- MP3 经过有界读取、验证和实际时长探测,权威 metadata 跨队列、OSS、素材、画布、响应和刷新一致。
- External v1 Rust、OpenAPI、幂等 payload、副作用和最终响应逐字段一致。
- 配置、日志、fixture、差异和提交不包含真实账号、Key、Token、Cookie 或其它凭据值。
@@ -6710,3 +6710,20 @@
- 业务隔离:共享组件不等于共享规则。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 框架。
- 实施状态:已恢复共享音频 composer,独立完整 BGM composer 及其测试文件已删除,原覆盖完整迁入总 composer。Prompt / 预设 / controller / 总 composer `121/121`、surface 与 submission workflow `72/72` 通过,typecheck、变更文件 ESLint、Prettier、编码检查和差异检查通过;没有修改后端、契约或需求原文,也没有实现 SFX V2 独有功能。
## 2026-08-06 SFX 生成优化 V2.0 T0 设计与迁移口径
- 权威入口:SFX V2 的可编码规则已完整融合到 `docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`。实现、审查、测试和发布只以该 tracked 权威设计、本条决策和共享实施计划为依据,不依赖团队通过 Git 无法取得的本地资料。
- 共享视图边界不变:`audio-sound-effect``audio-background-music` 继续共用 `ImageCanvasAudioGenerationComposerView`,通过 `isSoundEffect` 分流;SFX 和 BGM 的 Prompt 模型、controller、预设 wrapper、锁和提交契约分别维护,不新建独立页面或第二套音频系统。
- 后端入口边界:正式生成原地演进 `server-rs/crates/shared-contracts/src/assets.rs` 的现有音频 DTO 与 `server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs` 的现有 handler,保留既有 `/api/editor/audios/*/generations` 路由、queue / inline 分流、计费和队列边界;不得新建平行 DTO、正式生成 BFF、handler 或第二套路由。只有 SFX Prompt 优化内部路由、Worker 翻译 service 和 ElevenLabs adapter 是新增能力。
- provider 迁移:新编辑器 SFX 任务固定使用 ElevenLabs `eleven_text_to_sound_v2`,不提供模型选择、Vidu fallback 或 `audio1.0` alias。Vidu builder / 轮询仅保留给历史素材和其它未迁移调用方;历史素材可读,重绘新任务使用 ElevenLabs。
- Prompt 真相:`prompt` 表示用户可见且确认的 canonical `userPrompt``actual_prompt` 表示 Worker 严格验收后实际提交给 ElevenLabs 的英文 `actualPrompt`。两者均以 2048 Unicode code points 为上限,只删除首尾 Unicode `White_Space`,不做其它规范化、默认 Prompt 回退或静默截断。
- 可见交互:SFX 固定 40 个事件预设 + 12 个补充要求。空 Prompt 直接写入;非空 Prompt 末尾已是 Unicode 标点时直接追加,否则使用中文逗号 `` 分隔。允许重复,不保留选中态,不去重或截断;点击预设清除旧快照。
- Prompt 助手:一键优化使用 `gpt-5.6-luna``reasoning_effort = medium`Worker 正式英文化使用同模型、`reasoning_effort = low`。两类候选上限均为 2048 Unicode code points,因此分别固定 `max_output_tokens = 2048 × 4 = 8192`;真实 VectorEngine Chat 探测已确认该字段限制隐藏 reasoning token 与可见输出 token 共享的 completion 总预算,不包含输入 prompt token,也不是可见正文保证。预算不按实际输入长度动态缩小;两者均不发送 temperature 或 function tools,只接受完整 `response.text` 中唯一 JSON object 的严格 envelope。翻译除要求 `isEnglish = true` 外,程序侧还要求候选至少含一个 Latin alphabetic code point,且所有 alphabetic code point 都属于 Latin Script;日文假名、韩文、西里尔、希腊和阿拉伯等非 Latin 字母均失败。优化只有一个业务语义轮,`finish_reason = length` 直接失败;翻译首轮成功响应但候选不合格或 `length` 时使用同一 `userPrompt`、同一 `8192` 上限唯一重试,第二轮 `length` 最终失败,`content_filter` 和 transport 最终失败不开启第二业务语义轮。翻译最终失败时 ElevenLabs 请求数必须为 0。
- 撤销与锁:一键优化成功产生一层 canonical Prompt 交换快照;优化失败清除本次临时快照,不恢复更早快照。AI 操作和正式提交使用 dialog ID、账号 + 项目 scope、同步 operation ID 与 `AbortController`;只锁当前 SFX dialog。正式提交在第一个 `await` 前冻结 Prompt / duration / LoopAPI 接受后结束 `submitting`、进入现有 `queued/generating` 占位,不把接受任务写成生成已完成。
- 时长与 Loop:首次打开默认手动时长模式、`5s`、Loop false;手动范围 `0.5-30s`UI 步进 `0.1s`。自动模式发送 `duration_seconds = null`,禁用 slider 但保留最近手动值。Loop 是独立 API 布尔参数;系统不根据 Prompt 推断、同步或校验 LoopPrompt 文本与 Loop 开关不建立业务一致性门禁。
- ElevenLabs 契约:`POST /v1/sound-generation`body 固定 `text / model_id / duration_seconds / loop / prompt_influence=0.3`query 固定 `output_format=mp3_44100_128``xi-api-key` 只在服务端 header 注入。provider POST 不自动重试,浏览器正式 POST 不 unsafe retry,队列 `max_attempts = 1`,一个平台 job 最多一次 ElevenLabs POST。成功响应复用现有 `MAX_GENERATED_AUDIO_BYTES = 40 MiB` 做 Content-Length 预检和 `limit + 1` 流式读取,验证 MIME 与真实 MP3,并探测有限正实际时长;实际时长仅受独立技术异常上限 `600s` 约束,不与请求最大 `30s` 比较,`30.5s-600s` 的合法结果可接受。平台使用 operation / queue job ID 作为 `taskId`,不伪造 provider task ID。
- 结果真相:服务端重建 SFX V2 `generation_inputs_json`,写入 `userPrompt / actualPrompt / model / durationMode / requestedDurationSeconds / actualDurationSeconds / loop`;实际英文 Prompt、实际时长、model 和 Loop 不信任客户端自报。信息弹窗展示用户 Prompt、实际英文 Prompt、模型、实际时长、Loop 和平台 Task ID;历史 Vidu 数据不误标英文 Prompt。
- 计费、External v1 与 schema:新模型键 `eleven_text_to_sound_v2` 保持 5 泥点 / 次,后端入队时冻结价格为真相。External v1 的 duration 演进为可选 / nullable `0.5-30 number`Loop 缺省 false`model` 的 omitted / null / 空串 / 纯空白 / 首尾空白包围的新模型 / 显式新模型统一 canonicalize 为 `eleven_text_to_sound_v2`,并产生相同幂等 payload。显式旧 `audio1.0` 和未知非空值返回 `400 BAD_REQUEST`,且必须为零入队、零预扣、零 LLM、零 providerRust、OpenAPI、幂等重放与最终响应必须在 T5 同批变更。本次不修改 SpacetimeDB schema,复用现有 `prompt``actual_prompt``generation_inputs_json` 和画布 layout。
- 共享计划:脱敏 T1–T6 任务、当前基线、测试矩阵、旧 Vidu 队列 drain、发布与回滚门禁记录在 `docs/project-memory/plans/【实施计划】SFX生成优化V2.0任务拆解-2026-08-06.md`。T0 只验收设计、决策、共享计划、差距归属和安全记录,不要求当前 Vidu V1 代码、OpenAPI 或实际 API 已与 SFX V2 设计一致;实现差距归入 T1–T5,T6 统一验收。
- 安全与状态:机器本地未跟踪资料不得进入提交,也不得被仓库文档链接、引用或作为团队证据源;ElevenLabs Key 只允许从服务端私密环境配置读取,不进入浏览器、日志、fixture、共享文档或 Git。旧凭据轮换失效的脱敏审计证据、业务 owner 和 API owner 签字尚未填写,因此 T0 暂未通过,T1–T5 不放行;不得伪造日期、责任人、工单或签字。
@@ -8,9 +8,11 @@
本次只在 `/editor/canvas` 图片画布编辑器内新增底部 `生成音乐` 入口,用于生成完整游戏音效或游戏背景音乐。该入口属于画板生成类工具,不新增平台玩法入口、不进入作品发布链路,也不修改现有视觉小说音频生成开关。
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-04 起,本文增加 BGM Prompt 优化 V1.0 口径。2026-08-06 起,本文同时作为 SFX 生成优化 V2.0 的权威工程设计;实现、审查、测试和发布只以本文冻结的字段、模型、参数、状态和迁移口径为依据
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)
音效与背景音乐继续使用同一个音频 composer,并由组件内的 `isSoundEffect = dialog.mode === 'audio-sound-effect'` 隔离行为。共享视图不等于共享业务规则:BGM 继续使用 Suno、200 字 canonical Prompt、30 个预设、AI 补全 / 简化、单层撤销和方案 A 提交锁;SFX V2 固定使用 ElevenLabs `eleven_text_to_sound_v2`、52 个预设、一键优化、自动中译英、自动 / 手动时长和 Loop。两条路径的 Prompt 模型、controller、预设 wrapper、锁和提交契约必须分别维护,不得交叉复用业务状态
SFX V2 当前只完成产品与技术口径冻结,T0 暂未通过,不表示功能已上线。T0 只验收权威设计、tracked 共享计划、决策记录、对当前 V1 代码 / OpenAPI 差距的 T1–T5 归属和可审计安全记录;不要求代码或实际 API 在 T0 与设计一致。T0 通过前不放行 T1–T5。在正式切换完成前,当前运行代码仍是 Vidu SFX V1 行为。历史 Vidu 素材继续只读展示;重绘时使用历史用户 Prompt 打开 SFX V2 面板,新任务统一走 ElevenLabs,不回退 Vidu。
## 入口与交互
@@ -19,17 +21,19 @@
- `生成游戏音效`
- `生成游戏背景音乐`
3. 选择某一项后创建独立 `generation-dialog` 画布生成对象,并通过现有 placement 模型避让已有图层和占位。
4. 面板 UI 复用 `生成角色形象` 的紧凑结构:上方为字段区,底部为参数 / 模型 / 生成按钮区,不写规则说明类文案。音效参数按钮靠左下角,固定模型胶囊紧贴生成按钮;背景音乐字段区增加字符计数、可展开预设词条、AI 补全、一键简化和单层撤销,底部保留现有动态泥点价格、固定 `Suno` 模型胶囊和生成按钮
4. 面板 UI 复用 `生成角色形象` 的紧凑结构:上方为字段区,底部为参数 / 模型 / 生成按钮区,不写规则说明类文案。SFX 字段区增加字符计数、可展开的 52 个预设、一键优化和单层撤销,底部增加自动 / 手动时长、Loop、固定 `ElevenLabs` 模型胶囊和动态泥点价格;BGM 字段区保留字符计数、预设、AI 补全、一键简化和单层撤销,底部保留固定 `Suno` 模型胶囊和动态泥点价格
5. 生成中隐藏设置面板,只保留画布中的音频生成占位;失败后恢复面板并展示短错误。
## 面板字段
### 生成游戏音效
- `prompt`:用户输入的音效提示词。前端与 BFF 对内仍使用 `prompt`,提交到 VectorEngine Vidu 时同时写入 `prompt``sound` 同值,兼容线上网关对 `sound` 字段的反序列化要求
- `model`:固定默认 `audio1.0`UI 以禁用态模型胶囊显示为 `Vidu`,位置紧贴生成按钮;暂不展示 Suno 文生音效模型入口
- `duration`:Vidu 音频时长,UI 以一个无标题参数按钮展示当前秒数,点击后展开与视频时长一致的滑动条,范围 `2-10` 秒,步进 `1` 秒,默认 `5`
- 提交到 VectorEngine 时映射为 Vidu 文生音频字段:`model: "audio1.0"``prompt``sound: prompt``duration`、可选 `seed`。当前编辑器音效固定使用 Vidu `audio1.0`,不再走 Suno `task: "sound"` / `metadata_params` 文生音效契约
- `prompt`:用户输入框确认的原始语言音效描述,语义为 `userPrompt`,继续复用对外请求和持久化字段 `prompt`。消费动作前只删除首尾 Unicode `White_Space` code point,不改写内部空白、换行、标点、零宽字符或 Unicode 形式;按 Unicode code point 计数,合法范围为 `1-2048`。空值和全空白不得回退“游戏音效”
- `actualPrompt`Worker 在正式生成时对冻结的 `userPrompt` 进行统一英文化并严格验收后得到的英文 Prompt,继续复用持久化字段 `actual_prompt`。前端不提交 `actualPrompt`ElevenLabs 只接收验收通过的 `actualPrompt`
- `model`:新任务固定 `eleven_text_to_sound_v2`UI 以禁用态模型胶囊显示 `ElevenLabs`,客户端不决定模型。历史 `audio1.0` 只作旧素材展示和其它未迁移调用方的兼容标识,不是新编辑器 SFX 任务的 alias 或 fallback
- `duration``null` 表示自动时长,有限数值表示手动时长。首次打开面板默认处于手动模式,手动值默认 `5s`,范围 `0.5-30s`UI 步进 `0.1s`。切到自动模式后禁用 slider 但保留最近手动值,关闭自动后恢复该值。服务端只校验有限值与范围,不把 UI 步进扩大成 provider 精度限制
- `loop`:独立布尔参数,默认 `false`,由页面开关原样冻结并传入 ElevenLabs。Prompt 是自由文本,系统不从 Prompt 推断、同步或校验 LoopPrompt 文本与 Loop 开关不建立业务一致性门禁。
- `prompt_influence`:服务端固定 `0.3`,不向前端开放滑杆或请求字段。输出格式固定为 query `output_format=mp3_44100_128`
### 生成游戏背景音乐
@@ -39,6 +43,130 @@
- 前端请求继续使用 `gptDescriptionPrompt`Rust DTO 继续使用 `gpt_description_prompt`;不得因“唯一最终 Prompt”语义把请求字段改名为 `actualPrompt`。响应中的 `actualPrompt` / `actual_prompt` 继续保留既有生成审计语义。
- `gpt_description_prompt` 按 Apifox 契约限制 200 个 Unicode code point。前端、BFF 和 `platform-audio` 允许且必须执行同一套首尾 Unicode 空白清理;除此之外不得执行 Unicode 规范化、内部空白折叠、内部换行转换、标点替换或静默截断。
## SFX 生成优化 V2.0
### Prompt 规范化、计数与字段语义
- `canonicalUserPrompt = 删除当前输入首尾属于 Unicode White_Space 属性的 code point`。TypeScript 使用 `\p{White_Space}` 边界判定,Rust 使用 `char::is_whitespace`;不得直接使用会额外删除 `U+FEFF` 的 JavaScript `String.trim()` 代替该契约。
- 内部空格、CR / LF、标点、`U+200B``U+FEFF`、组合字符和 ZWJ emoji 原样保留;不做 NFC、空白折叠、换行转换、标点替换或静默截断。`U+0085` 在首尾属于应删除空白。
- 字符数按 Unicode code point 计算。canonical Prompt 为空时禁用一键优化和正式生成,BFF 也返回 `400 BAD_REQUEST``1-2048` 允许优化和生成;超过 2048 时完整保留文本但禁用并拒绝请求。
- 输入期间允许暂存边界空白;点击预设、一键优化、撤销或正式生成时才在同步动作内 canonicalize 并写回输入框。
- 持久化语义固定为 `record.prompt = canonical userPrompt``record.actual_prompt = validated actualPrompt`,不新增同义数据库列,不得用英文覆盖 `prompt`
### 52 个预设与追加规则
预设分为 40 个事件预设和 12 个补充要求;每项固定 `id / category / group / label / prompt`,只有下表 `Prompt` 可见文本写入输入框。ID、分类、分组、颜色和预设元数据不进入正式 Prompt、生成请求或持久化记录。
| 分类 | 分组 | 词条 | Prompt |
| --- | --- | --- | --- |
| 事件预设 | UI 与操作 | 轻触按钮 | 柔和的按钮点击声 |
| 事件预设 | UI 与操作 | 确认操作 | 明亮的确认提示音 |
| 事件预设 | UI 与操作 | 返回取消 | 轻微下降的取消提示音 |
| 事件预设 | UI 与操作 | 页面切换 | 快速掠过的界面切换声 |
| 事件预设 | UI 与操作 | 通知提醒 | 清晰柔和的通知提示音 |
| 事件预设 | UI 与操作 | 操作错误 | 短促克制的错误提示音 |
| 事件预设 | 拾取与奖励 | 金币拾取 | 金币拾取时清脆的金属叮当声 |
| 事件预设 | 拾取与奖励 | 道具拾取 | 拾取道具时轻快的提示音 |
| 事件预设 | 拾取与奖励 | 获得奖励 | 奖励出现时明亮的提示音 |
| 事件预设 | 拾取与奖励 | 宝箱开启 | 金属锁扣弹开,随后响起明亮的奖励提示音 |
| 事件预设 | 拾取与奖励 | 解锁内容 | 锁定状态解除,随后响起解锁提示音 |
| 事件预设 | 拾取与奖励 | 稀有掉落 | 稀有物品出现时闪耀的奖励提示音 |
| 事件预设 | 成长与结果 | 物品合成 | 两件物品融合,随后响起明亮的完成提示音 |
| 事件预设 | 成长与结果 | 角色升级 | 能量快速上升,随后响起明亮的升级提示音 |
| 事件预设 | 成长与结果 | 任务完成 | 任务完成提示音,随后响起简短的奖励音符 |
| 事件预设 | 成长与结果 | 成就达成 | 明亮的成就提示音,随后响起简短的庆祝音符 |
| 事件预设 | 成长与结果 | 挑战胜利 | 明亮的胜利提示音,随后响起短暂的庆祝音符 |
| 事件预设 | 成长与结果 | 挑战失败 | 低沉的失败提示音 |
| 事件预设 | 角色与战斗 | 角色跳跃 | 角色轻盈跳起的声音 |
| 事件预设 | 角色与战斗 | 角色落地 | 角色落地时轻微的撞击声 |
| 事件预设 | 角色与战斗 | 轻度受击 | 轻微撞击的受击声 |
| 事件预设 | 角色与战斗 | 重度受击 | 沉重有力的撞击声 |
| 事件预设 | 角色与战斗 | 攻击挥动 | 武器快速挥过空气的呼啸声 |
| 事件预设 | 角色与战斗 | 攻击命中 | 武器击中目标的清晰撞击声 |
| 事件预设 | 角色与战斗 | 格挡成功 | 武器碰撞,随后被挡开的金属声 |
| 事件预设 | 角色与战斗 | 物体破碎 | 物体撞击地面后快速破碎的声音 |
| 事件预设 | 技能与状态 | 技能蓄力 | 能量逐渐聚集的低沉嗡鸣声 |
| 事件预设 | 技能与状态 | 魔法释放 | 柔和的魔法能量扩散,带有圆润空灵的闪光声 |
| 事件预设 | 技能与状态 | 治疗恢复 | 柔和能量扩散,带有温暖圆润的提示音 |
| 事件预设 | 技能与状态 | 护盾生成 | 能量向外展开,形成稳定的护盾声 |
| 事件预设 | 技能与状态 | 瞬间移动 | 能量快速收缩,随后以短促的空气抽离声消失 |
| 事件预设 | 技能与状态 | 冰冻技能 | 冰霜能量扩散,随后响起清脆的冻结声 |
| 事件预设 | 技能与状态 | 火焰技能 | 火焰迅速喷发,带有短促的燃烧声 |
| 事件预设 | 技能与状态 | 状态强化 | 能量逐渐上升,形成稳定明亮的提示音 |
| 事件预设 | 机关与场景互动 | 门开启 | 门锁解除,随后厚重的木门缓慢打开 |
| 事件预设 | 机关与场景互动 | 机关启动 | 机关解锁,随后齿轮开始转动 |
| 事件预设 | 机关与场景互动 | 拉杆触发 | 拉杆被扳动,随后远处机关启动 |
| 事件预设 | 机关与场景互动 | 石块移动 | 大型石块缓慢移动时低沉的摩擦声 |
| 事件预设 | 机关与场景互动 | 传送门开启 | 能量旋转聚集,随后响起持续、空灵的传送门展开声 |
| 事件预设 | 机关与场景互动 | 倒计时警告 | 逐渐加快的倒计时提示音 |
| 补充要求 | 风格方向 | 休闲可爱 | 轻快可爱的卡通风格 |
| 补充要求 | 风格方向 | 复古街机 | 复古街机风格 |
| 补充要求 | 风格方向 | 科幻电子 | 干净的科幻电子音色 |
| 补充要求 | 风格方向 | 奇幻魔法 | 柔和梦幻的魔法音色 |
| 补充要求 | 风格方向 | 写实自然 | 自然真实的声音质感 |
| 补充要求 | 风格方向 | 卡通夸张 | 夸张鲜明的卡通风格 |
| 补充要求 | 反馈要求 | 轻柔反馈 | 轻柔克制 |
| 补充要求 | 反馈要求 | 有力反馈 | 更有力的撞击感 |
| 补充要求 | 反馈要求 | 短促反馈 | 短促的单次声音 |
| 补充要求 | 反馈要求 | 两段递进 | 由弱到强的两段变化 |
| 补充要求 | 反馈要求 | 干净突出 | 主体声音清晰,减少杂音 |
| 补充要求 | 反馈要求 | 柔和不刺耳 | 圆润柔和,避免尖锐高频 |
点击预设时:
1. 先 canonicalize 并写回当前 Prompt。
2. 当前 Prompt 为空时直接写入预设 `prompt`
3. 当前 Prompt 非空时,末尾已是 Unicode 标点类别则直接追加;否则先追加中文逗号 ``,再追加预设文本。
4. 允许重复点击同一词条,不保留选中状态,不去重,不截断。
5. 点击任意预设清除旧撤销快照;追加后超限时保留完整文本,只进入通用过长状态。
6. 跑马灯、展开状态和滚动位置是临时 UI,不写入画布 layout。
### 一键优化、撤销与前端锁
- 一键优化只走登录态内部 BFF `POST /api/editor/audios/sound-effects/prompts/optimizations`,请求为 `{ currentPrompt: string }`,成功响应为 `{ prompt: string, charCount: number }`。该路由不向 External v1 开放,不调用 ElevenLabs、不创建正式任务、不触发 SFX 生成扣费。
- 优化固定使用 `gpt-5.6-luna`、OpenAI Chat、`reasoning_effort = medium``max_output_tokens = 8192`,不发送 temperature 或 function tools`8192` 按候选 Prompt 上限 `2048 Unicode code points × 4` 固定计算,是隐藏 reasoning token 与可见 JSON 输出 token 共享的 completion 总预算,不是 8192 个可见正文 token 的保证,也不按本次输入长度动态缩小。服务端固定 `32 KiB` body limitcanonical 字符范围为 `1-2048`
- LLM 必须返回唯一 JSON object,内部 envelope 固定包含 `prompt: string``isDirectWritebackFormat: boolean``isContentComplete: boolean``hasObviousFragment: boolean``hasGenerationParameterContent: boolean`。完整 `response.text` 只允许 JSON whitespace 包围的单一 object;拒绝代码块、前后解释、多个 JSON 值、tool call、子串提取和自动修复。
- 候选 canonicalize 后必须非空、不超过 2048、至少含一个 Han code point,且四个布尔值依次为 `true / true / false / false``finish_reason = length``content_filter` 均失败,不写回候选。失败响应不暴露未通过候选或内部 envelope。
- AI 开始前 canonicalize 并写回 Prompt,以该值取代旧快照作为本次临时快照。成功后转为单层可撤销快照;失败保留请求前 Prompt、清除临时快照,不恢复更早快照。
- 手动编辑 AI 结果后撤销仍可用;再次优化以当前 canonical Prompt 取代旧快照;撤销时当前 canonical Prompt 与快照互换,允许在两个版本间反复切换。快照只包含 Prompt,不包含时长、Loop、预设滚动或展开状态。
- 使用 dialog ID、账号 + 项目 scope、同步 operation ID 和 `AbortController`隔离并发;旧响应迟到、dialog 关闭或 scope 切换后不得写入当前面板。优化中锁定当前 SFX 输入框、预设、滚动、参数、撤销和生成,不锁整个画布。
- 正式生成在点击事件的第一个 `await` 之前同步取得当前 dialog 提交锁,canonicalize 并写回 Prompt,校验并冻结 Prompt、duration mode、手动时长和 Loop。同一 dialog 重复点击必须忽略。
- API 拒绝时解锁,原 scope 仍匹配则恢复面板并保留已写回 Prompt 与旧撤销快照;scope 已失效则不写旧 UI。API 接受并创建 job 后 `submitting` 结束,进入现有 `queued/generating` 占位并隐藏 composer;Worker 失败后按现有生成占位失败路径恢复面板并允许基于原冻结参数重试。“提交成功”只表示 API 已创建任务,不表示音频已生成完成。
### Worker 翻译、ElevenLabs 和结果真相
- API 只把 canonical `userPrompt` 入队,翻译必须在 Worker 内执行,不在入队前同步生成英文。翻译的每次业务尝试固定使用 `gpt-5.6-luna`、OpenAI Chat、`reasoning_effort = low``max_output_tokens = 8192`,不发送 temperature 或 function tools`8192` 同样按 `actualPrompt` 上限 `2048 Unicode code points × 4` 固定计算,是隐藏 reasoning 与可见 JSON 输出共享的 completion 总预算,不因重试或本次输入长度改变,也不表示可见正文一定可使用 8192 tokens。
- 翻译内部 envelope 固定为 `prompt: string``isEnglish: boolean``isFaithfulTranslation: boolean``isDirectGenerationFormat: boolean``hasAddedOrRemovedRequirement: boolean`。每次必须对完整 `response.text` 做单一 JSON object 全量解析;候选 canonicalize 后必须非空、不超过 2048,四个布尔值必须为 `true / true / true / false`
- `isEnglish = true` 是 LLM 语义判断,程序侧还必须执行 Unicode Script 门禁:候选至少包含一个 Script=Latin 的 alphabetic code point,且所有 alphabetic code point 的 Script 都是 LatinCommon / Inherited 的数字、标点、空白和声音设计符号允许保留。Han、Hiragana、Katakana、Hangul、Cyrillic、Greek、Arabic 等非 Latin 字母都失败。
- 第一次成功响应的候选为空、`isEnglish != true`、非英文 Script 门禁失败、结构非法、格式 / 保真判断失败、超过 2048 Unicode code points 或 `finish_reason = length` 时,使用同一份原始 `userPrompt` 进行唯一一次业务重试;不得把第一次候选当作新事实源。第二次仍使用 `max_output_tokens = 8192`,第二次 `finish_reason = length` 按最终翻译失败处理。首轮 `content_filter` 直接失败;transport、timeout 或上游最终失败只使用该轮 `LlmClient` 内部 transport retry,不额外开启第二业务语义轮。
- 第二次任何失败都是最终翻译失败;不返回候选,不调用 ElevenLabs,job 进入失败 / 退款链路。只有验收通过后才允许构造 `actualPrompt` 并调用 provider。
- ElevenLabs endpoint 为 `POST /v1/sound-generation`,鉴权 `xi-api-key` 只在服务端 header 注入。body 固定包含 `text = actualPrompt``model_id = eleven_text_to_sound_v2``duration_seconds = null | frozen manual value``loop = frozen boolean``prompt_influence = 0.3`query 固定 `output_format=mp3_44100_128`。调用方不能覆盖 model、influence 或 output format。
- ElevenLabs 没有本链路可用的幂等键,所以 provider POST 不做自动 retry;浏览器正式生成 POST 也保持 0 次 unsafe retry,队列 `max_attempts = 1`。一个平台 job 最多调用一次 ElevenLabs。
- 成功响应是 MP3 二进制。复用现有 `MAX_GENERATED_AUDIO_BYTES = 40 * 1024 * 1024`,即 `40 MiB``Content-Length` 存在且大于该值时在读取前拒绝;长度头缺失或未超限时仍有界流式读取到 `MAX_GENERATED_AUDIO_BYTES + 1`,实际累计达到 `40 MiB + 1 byte` 时失败,不得先无限读入内存。空 body、超限、HTML / JSON 错误页、损坏 MP3 和无法探测正时长的响应均失败。明确接受 `audio/mpeg` / `audio/mp3``application/octet-stream` 或缺失 Content-Type 只有在真实 MP3 探测成功时才可接受,显式非音频类型不能仅靠扩展名回退通过。
- 使用纯 Rust MP3 探测获得实际时长,持久化和响应的 `durationSeconds` 必须来自 MP3 而不是请求时长。实际时长必须是有限正数且不大于独立技术异常上限 `600s``600s` 允许,任何大于 `600s` 的结果拒绝。该上限不由请求最大 `30s` 推导,也不要求实际时长接近请求值;通过 MP3、MIME、字节和时长门禁的 `30.5s-600s` 结果均可接受。
- ElevenLabs 无 provider task ID。queue 模式使用 `external_generation_job.job_id` 作为平台 operation / `taskId`inline 兼容模式在 provider 调用前生成平台 task ID,不得伪造 ElevenLabs task ID。provider 固定 `elevenlabs`model 固定 `eleven_text_to_sound_v2`
- 预扣成功后才允许翻译和 provider 调用。翻译、provider、二进制验证、时长探测、OSS、资源 / 素材 / 画布写回任一失败都进入现有失败退款边界。
### 权威元数据、详情、计费与外部契约
- 成功后由服务端重建 `generation_inputs_json`。通用 `fields` 至少包含“用户描述”、“实际英文提示词”和“Loop”;强类型 `soundEffect` read model 固定包含 `schemaVersion = 2``userPrompt``actualPrompt``model``durationMode``requestedDurationSeconds``actualDurationSeconds``loop`
- `actualPrompt`、实际时长、model 和 Loop 必须由服务端运行结果构造,不信任客户端自报。项目资源、账号素材和画布 layer 共用同一份权威元数据;不修改 SpacetimeDB schema,继续复用现有 `prompt``actual_prompt``generation_inputs_json` 和画布 layout。
- SFX V2 信息弹窗稳定展示用户原始 Prompt、实际英文 Prompt、生成模型 `eleven_text_to_sound_v2`、实际时长、Loop 和平台 Task ID。历史 Vidu 素材只显示旧字段,不得把与 `prompt` 相同的历史 `actual_prompt` 误标成“实际英文提示词”。
- 新模型按次价格固定保持 `5` 泥点,新定价键为 `eleven_text_to_sound_v2`、单位 `perGeneration`。旧 `audio1.0` 定价键只保留历史配置兼容,新编辑器请求只读新键;正式扣费以后端入队时冻结的价格快照为真相。
- 站内与 External v1 继续复用同一 SFX 请求契约:`prompt`、固定 `model = eleven_text_to_sound_v2`、可选 / nullable `duration: 0.5-30 number``loop: boolean = false`。进入队列前将 duration 省略与 null 统一序列化为 null,将 Loop 缺省统一为 false,再计算幂等 payload。
- External v1 `model` 先删除首尾 Unicode `White_Space`,再按下表进入大小写敏感的 canonicalization;不做 alias、自动纠错或静默 provider 回退。
| External `model` 输入 | 结果 | canonical queue payload |
| --- | --- | --- |
| 省略 / `null` / 空串 / 纯 Unicode 空白 | 接受 | `eleven_text_to_sound_v2` |
| 首尾空白包围的 `eleven_text_to_sound_v2` | 接受 | `eleven_text_to_sound_v2` |
| `eleven_text_to_sound_v2` | 接受 | `eleven_text_to_sound_v2` |
| `audio1.0` | `400 BAD_REQUEST` | 不入队 |
| 其它未知非空值 | `400 BAD_REQUEST` | 不入队 |
- 所有接受形态在定价、预扣和 enqueue 前收敛为同一 canonical model,因而 omitted / null / 空白 / 显式新模型不得产生不同幂等 payload。`audio1.0` 与未知模型必须在入队前失败,并由测试证明零入队、零预扣、零 LLM 和零 provider。成功响应增加可选 `durationSeconds``loop`Rust DTO、`docs/openapi/genarrative-external-v1.openapi.json``202 / poll / final response`、Idempotency-Key 重放测试和 compact result 必须同批保持一致。
- ElevenLabs 配置只允许从服务端 `ELEVENLABS_BASE_URL``ELEVENLABS_API_KEY``ELEVENLABS_REQUEST_TIMEOUT_MS` 读取,Key 不进入浏览器、请求体、日志、fixture、共享文档或 Git。运行配置缺失时失败关闭,不回退 Vidu。
## BGM Prompt 优化 V1.0
### 唯一可见最终 Prompt、首尾空白与字符口径
@@ -223,7 +351,7 @@ idle
- generated 私有音频资源播放前必须通过 `/api/assets/read-url` 换签;画布卡片不得直接把 `/generated-*` 或 generated OSS 私有地址交给 `<audio>` 裸请求。
- 音频元数据弹窗使用 `时长`,不使用图片 / 视频的分辨率语义;音频生成占位不显示分辨率或时长角标。
- 音频图层右上角标签显示在信息按钮左侧,和其他素材卡右上角信息区保持一致。
- 元数据弹窗按音频显示 `音频信息` / `音频类型` / `时长`,生成输入快照只展示用户面板字段;BGM 快照保存输入框已经写回的 canonical Prompt,不保存助手系统模板、内部引导或预设元数据。
- 元数据弹窗按音频显示 `音频信息` / `音频类型` / `时长`。BGM 生成输入快照保存输入框已经写回的 canonical Prompt,不保存助手系统模板、内部引导或预设元数据;SFX V2 快照由服务端权威构造用户 Prompt、实际英文 Prompt、请求参数、实际时长和 Loop,不保存预设 ID、助手 envelope、撤销快照或未验收翻译候选
- 音频图层上方浮动工具栏只保留 `改造``下载按钮`。点击 `改造` 后打开对应的音效或背景音乐生成面板,不展示参考图组件;面板底部模型与参数位置和原生成入口一致,并允许继续修改后再次生成,新结果落在原音频旁边。
## 前端与 BFF 契约
@@ -234,8 +362,9 @@ idle
POST /api/editor/audios/sound-effects/generations
{
prompt: string,
model: "audio1.0",
duration: number
model: "eleven_text_to_sound_v2",
duration?: number | null,
loop?: boolean
}
```
@@ -247,7 +376,18 @@ POST /api/editor/audios/background-music/generations
}
```
BGM Prompt 助手新增个登录态内部 BFF
SFX Prompt 助手新增个登录态内部 BFF
```ts
POST /api/editor/audios/sound-effects/prompts/optimizations
{
currentPrompt: string
}
```
SFX 优化成功响应复用 `{ prompt: string, charCount: number }`;该路由不进入 External v1。
BGM Prompt 助手保留两个登录态内部 BFF:
```ts
POST /api/editor/audios/background-music/prompts/completions
@@ -273,15 +413,16 @@ POST /api/editor/audios/background-music/prompts/simplifications
```
- 客户端不得提交 `maxChars``targetChars`、模型名、预设 ID、分组、颜色、内部模板或格式 / 完整性 / 残句判断;这些由服务端固定或由内部 LLM 结果产生。
- 两个助手 BFF 不调用 Suno、钱包扣费、正式 generation queue、OSS、素材库,也不在 handler 中同步执行 SpacetimeDB 业务写入;成功路由的通用 tracking 仍由现有本机 outbox 异步承接。
- 个助手请求中的 `currentPrompt` 必须是前端已经写回输入框的 canonical Prompt;响应 `prompt` 也必须先执行同一 canonicalization`charCount` 是响应 canonical Prompt 的 Unicode code point 数。
- 三个 Prompt 助手 BFF 不调用 Suno 或 ElevenLabs、钱包扣费、正式 generation queue、OSS、素材库,也不在 handler 中同步执行 SpacetimeDB 业务写入;成功路由的通用 tracking 仍由现有本机 outbox 异步承接。
- 个助手请求中的 `currentPrompt` 必须是前端已经写回输入框的 canonical Prompt;响应 `prompt` 也必须先执行各自已冻结的 canonicalization`charCount` 是响应 canonical Prompt 的 Unicode code point 数。
- 补全与简化成功响应都只包含上面的 `prompt` / `charCount` 业务字段;内部四字段 envelope、`originalPrompt`、目标字数、业务语义轮信息和未通过候选均不得出现在成功或失败响应中。失败继续使用现有 API 错误 envelope。
- 简化 BFF 只接受总字符数为 201–2000 且至少含 1 个有效字符的 canonical `currentPrompt`;超过 2000 返回现有 `400 BAD_REQUEST` 错误 envelope,并标记字段 `currentPrompt`
- 个助手路由都设置 `32 KiB` HTTP 请求体上限。请求体超过该上限时保留 Axum `413 PAYLOAD_TOO_LARGE`,不得被通用 JSON rejection 降为 `400`;字符上限与原始 body 上限分别校验,不能互相替代。
- 个助手路由都设置 `32 KiB` HTTP 请求体上限。请求体超过该上限时保留 Axum `413 PAYLOAD_TOO_LARGE`,不得被通用 JSON rejection 降为 `400`;字符上限与原始 body 上限分别校验,不能互相替代。
- 不增加 Prompt 助手专属的用户级、IP 级、时间窗口或令牌桶限流,也不新增本功能主动产生的 `429` / `Retry-After`。现有 api-server 全局并发背压、前端防重复操作、Nginx 保护和上游真实 `429` 的安全映射保持不变。
- 个成功路由必须进入 `tracking.rs` 显式静态映射:补全使用 `event_key = editor_background_music_prompt_completion`,简化使用 `event_key = editor_background_music_prompt_simplification`者都使用 `module_key = editor`、User scope。普通 route tracking 继续只记录成功响应,不在助手 handler 中新增同步埋点副作用。
- 个成功路由必须进入 `tracking.rs` 显式静态映射:SFX 优化使用 `event_key = editor_sound_effect_prompt_optimization`BGM 补全使用 `event_key = editor_background_music_prompt_completion`BGM 简化使用 `event_key = editor_background_music_prompt_simplification`者都使用 `module_key = editor`、User scope。普通 route tracking 继续只记录成功响应,不在助手 handler 中新增同步埋点副作用。
- BGM 正式提交必须使用输入框已经写回的 canonical Prompt,不得额外拼接用户不可见内容,也不得把空 Prompt 回退为“游戏背景音乐”。
- BGM 正式 POST 不使用现有允许 unsafe method 的自动重试,保证一次点击不会由 client 内部重发。本文不新增 Idempotency-Key、持久提交账本或服务端 dedupe;其它生成 mode 的 retry 行为保持不变。
- SFX 正式 POST 同样不允许 client 内部 unsafe retry;队列稳定 request identity 与 External v1 `Idempotency-Key` 仍按现有 namespace 分别维护。
统一响应:
@@ -301,48 +442,55 @@ POST /api/editor/audios/background-music/prompts/simplifications
taskId: string,
priceMudPoints: number,
audioKind: "sound-effect" | "background-music",
durationSeconds?: number | null
durationSeconds?: number | null,
loop?: boolean | null
}
```
- BGM 成功响应必须同时填充 `prompt``actualPrompt`,两者都与本次 canonical Prompt 逐 code point 等值,不得携带助手模板、内部引导或隐藏前后缀;共享响应类型为兼容 SFX 与历史数据仍可保留 `actualPrompt` 可选。
- SFX V2 成功响应的 `prompt` 必须等于 canonical `userPrompt``actualPrompt` 必须等于验收通过且实际提交给 ElevenLabs 的英文 Prompt`durationSeconds` 必须来自 MP3 探测,`loop` 必须等于本次冻结并发送的布尔值。
- 默认 queue 模式的首次生成响应继续只携带现有 `queueState`;上面的完整音频响应是 inline 或 worker 内部完成边界,不要求给 queue 首次响应或普通 metadata-only job result 新增 Prompt 字段。
## 后端实现
-`shared-contracts/src/assets.rs` 增加编辑器音频请求 / 响应 DTO
-`shared-contracts` 增加最小 BGM Prompt 助手请求 / 响应 DTO;前后端共享 `currentPrompt``prompt``charCount` 字段,不把内部 LLM 判断暴露给客户端。
本节是对现有编辑器音频链路的原地演进说明,不是新建入口清单。正式生成继续复用 `server-rs/crates/shared-contracts/src/assets.rs` 中现有编辑器音频 DTO,以及 `server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs` 中现有 generation handler、queue / inline 分流和既有路由注册;禁止为 SFX V2 新建平行 DTO、平行正式生成 handler、第二组 `/api/editor/audios/*/generations` 路由或绕过现有计费 / 队列的入口。只有 SFX Prompt 优化内部路由、Worker 翻译 service 和 ElevenLabs adapter 是本切片新增的独立能力
-`shared-contracts/src/assets.rs` 原地演进现有编辑器音频请求 / 响应 DTO,增加 fixed model、nullable 小数 duration、Loop、实际时长和 V2 metadata;不得创建同义 DTO 或第二套请求 / 响应契约。
- 保留并复用 `shared-contracts` 现有最小 BGM Prompt 助手请求 / 响应 DTO;前后端继续共享 `currentPrompt``prompt``charCount` 字段,不把内部 LLM 判断暴露给客户端。SFX 优化只增加其自身最小助手 DTO,不复制正式生成 DTO。
- 前端、`api-server``platform-audio` 必须实现同一 BGM canonicalization 语义并复用同一组跨语言测试向量:只删除首尾 Unicode `White_Space`,保留内部空白和全部其它 code point。该操作必须幂等,不得直接混用语义不同的 TypeScript / Rust 原生 `trim`
-`platform-audio` 增加编辑器专用 body builder submit 函数
-`platform-audio` 现有编辑器音频 adapter 边界内原地演进 body builder / submit 能力
- 背景音乐 body 使用 `mv``gpt_description_prompt``make_instrumental`
- 背景音乐在 body builder 边界防御性执行幂等 canonicalization,再按 canonical Prompt 检查至少一个有效字符和最多 200 个 Unicode code point;校验通过后用 canonical Prompt 构造 Suno body。不得复用语义不同的 `normalize_limited_text`,不得提供默认 Prompt。
- Suno 音乐接口路径固定为 `/suno/submit/music``VECTOR_ENGINE_BASE_URL` 即使配置为带 `/v1` 的图片接口根,也要在 `platform-audio` 中归一为根路径后再拼接,避免误请求 `/v1/suno/submit/music`
- 音效 body 使用 Vidu 文生音频契约:提交 `/ent/v2/text2audio`,请求体包含 `model: "audio1.0"``prompt``sound: prompt``duration` 和可选 `seed``model``prompt` 为文档必填,`sound` 用于兼容线上网关实际校验,`prompt` 最长 1500 字符,`duration` 按 Vidu 文档限制在 `2-10`
- 编辑器音效轮询使用 Vidu 路径 `/ent/v2/tasks/{taskId}/creations`,不使用 Suno `/suno/fetch/{taskId}`Suno 文生音效 `task: "sound"` 暂不从编辑器入口暴露
- 新编辑器音效使用独立 ElevenLabs 直接二进制 adapter,不伪装成 Vidu / Suno 的 submit + poll 任务。adapter 负责 endpoint 归一、`xi-api-key` header、固定 query / body、单次 POST、有界二进制读取、MP3 验证和时长探测
- Vidu `audio1.0` 的 body builder、轮询和下载能力仅保留给历史展示和其它未迁移调用方;新 `audio-sound-effect` 任务不进入 `/ent/v2/text2audio` `/ent/v2/tasks/{taskId}/creations`不使用 Suno `task: "sound"`
- Suno 提交成功后的任务 ID 兼容与 wav clip 轮询逻辑只保留给背景音乐链路;`/suno/fetch/{taskId}` 返回 `audiopipe.suno.ai/?item_id=...` 时,该地址只作为 clip id 来源,不作为最终下载文件,后端继续调用 `/suno/act/wav/{clipId}` 获取稳定 wav URL,避免 worker 在不完整 chunked body 上卡满超时。
- VectorEngine 音频响应的 `code` 需要兼容 `"success"``"ok"``"0"``"200"` 以及数字 `0` / `200`;HTTP 非 2xx 时后端错误信息应透出安全的上游状态和短响应摘要,避免前端只显示笼统提交失败。
-`api-server` 增加编辑器音频 BFF
-`api-server/src/vector_engine_audio_generation/generation.rs` 原地演进现有编辑器音频 generation handler;以下正式路由保持原路径和现有注册,不新增平行 BFF
- `/api/editor/audios/sound-effects/generations`
- `/api/editor/audios/background-music/generations`
- `api-server` 增加登录态内部 BGM Prompt 助手 BFF
- 保留并复用 `api-server` 现有登录态内部 BGM Prompt 助手 BFF
- `POST /api/editor/audios/background-music/prompts/completions`
- `POST /api/editor/audios/background-music/prompts/simplifications`
-`api-server` 增加登录态内部 SFX Prompt 优化 BFF `POST /api/editor/audios/sound-effects/prompts/optimizations`,并增加仅 Worker 可调用的 SFX 翻译 service;不暴露同步翻译 HTTP 路由。
- Prompt 助手 BFF 在入站和 LLM 候选出站边界执行 BGM canonicalization;服务端字符数、0 / 1 / 2 个有效字符规则、正式生成 200 字限制和简化 201–2000 字资格都基于 canonical Prompt。助手使用现有编辑器专用 LLM client、`gpt-5.6-luna` 请求模型、固定 `reasoning_effort=medium` 和显式 OpenAI Chat 协议;画布 Agent 其它调用仍使用 `gpt-5.4-mini`。补全固定执行一个业务语义轮,简化按 `180 -> 170` 最多两个业务语义轮,并按“一键简化”章节冻结 `originalPrompt`、派生每轮 `currentPrompt``LlmClient` 在单轮内部执行的 transport retry 不计入业务语义轮数,简化第一轮 transport、超时或上游失败不进入 170 字轮。服务端负责模板组装、在正文解析或候选提取前检查 `finish_reason`、canonical 字符校验、对完整 `response.text` 中单个 JSON object 的 `serde_json` 全量解析、补全与简化共用的内部 envelope 校验、执行格式 / 完整性 / 残句三个布尔判断和现有 API 错误 envelope;不自行猜测三个语义判断,也不向客户端返回未通过候选。助手不发送 function tools,不接受 tool call,不从代码块或解释中截取 JSON,不自动修复,也不做运行时双协议 fallback。
- `finish_reason` 检查复用并公开 `platform-llm` 现有 API-kind-aware 未完成原因 predicate;不得在 `LlmClient` 全局拒绝普通纯文本响应,也不得改变其它调用方既有的长文本降级行为。
- 个助手路由使用各自的 `32 KiB` body limit,并在 `tracking.rs` 中注册上述 User-scope 成功事件;不增加助手专属限流器、本地额度计数或功能级 `429`
- 个助手路由使用各自的 `32 KiB` body limit,并在 `tracking.rs` 中注册上述 User-scope 成功事件;不增加助手专属限流器、本地额度计数或功能级 `429`
- Prompt 助手继续复用 `LlmClient` 现有失败原文日志行为。本需求不增加请求级日志开关、脱敏、metadata-only 模式或相关上线门禁。
- BGM generation BFF 在入站时防御性执行同一幂等 canonicalization,规范化后校验有效字符和 200 字限制。登录态站内 handler 在 queue / inline 分流前把 canonical Prompt 和固定 `make_instrumental:true` 写入本次请求值,确保正式 generation queue 请求载荷、持久化记录和内部完成响应等值;删除空 Prompt 默认回退。该站内收口不改变 External v1 的路由、OpenAPI、Idempotency-Key 或 payload 等值语义。助手模板只用于生成输入框可见候选,不得进入正式队列或 Suno 请求。SFX 继续使用现有规范化、回退、Vidu body 和 1500 字限制。
- BFF 复用现有 `vector_engine_audio_generation` 的任务轮询、下载、OSS 持久化和计费包装。客户端不提交 BGM `priceMudPoints`;服务端按现役动态定价配置解析并在入队时冻结本次价格,后续计费、资产成本和内部完成响应复用该冻结值。T5 不修改价格或计费规则
- 现有 generation handler 的 BGM 分支在入站时防御性执行同一幂等 canonicalization,规范化后校验有效字符和 200 字限制。登录态站内 handler 在 queue / inline 分流前把 canonical Prompt 和固定 `make_instrumental:true` 写入本次请求值,确保正式 generation queue 请求载荷、持久化记录和内部完成响应等值;删除空 Prompt 默认回退。该站内收口不改变 External v1 的路由、OpenAPI、Idempotency-Key 或 payload 等值语义。助手模板只用于生成输入框可见候选,不得进入正式队列或 Suno 请求。
- 同一现有 generation handler 的 SFX 分支入站后执行 SFX canonicalization、`1-2048` code point、固定模型、nullable duration 和 Loop 校验,再构造 canonical queue payload 与冻结价格。Worker 负责翻译、单次 ElevenLabs、MP3 验证 / 时长探测、OSS 与权威写回,并复用现有预扣、退款、lease 和 fencing 边界
- 客户端不提交音频 `priceMudPoints`;服务端按现役动态定价配置解析并在入队时冻结本次价格,后续计费、资产成本和内部完成响应复用该冻结值。BGM 价格不变;SFX V2 以新模型键保持 5 泥点 / 次。
- 生成音频持久化后返回 OSS `objectKey``assetObjectId`;前端保存素材库时继续使用 `audioSrc` 作为兼容路径,并把 OSS 身份写入素材记录。
- 本切片不修改 SpacetimeDB schema,不新增 Prompt 助手持久化表,不向 `/api/external/v1` 暴露助手,也不修改 External v1 OpenAPI 或 `platform-llm` 日志策略。
- SFX V2 不修改 SpacetimeDB schema,不新增 Prompt 助手持久化表,不向 `/api/external/v1` 暴露助手,也不修改 `platform-llm` 日志策略;正式 External v1 SFX 生成请求 / 响应的 OpenAPI 必须在 T5 与 Rust DTO 同批更新
## 验收
- 底部工具栏显示 `生成音乐`
- 点击 `生成音乐` 只出现选项框,不立刻创建占位。
- 点击 `生成游戏音效` 后出现音效面板文本字段为 `prompt`;底部左侧只有一个无标题时长参数按钮,右侧固定 `Vidu` 模型胶囊和生成按钮。
- 音效时长使用滑动条选择 `2-10` 秒,步进 `1` 秒,默认 `5` 秒,提交到 BFF 的字段为 `duration`
- 音效面板模型显示 `Vidu`,提交 `model: "audio1.0"`;后端转发到 Vidu 时同时携带 `prompt``sound`;不显示 Suno 文生音效模型或 Suno 音效入口
- 点击 `生成游戏音效` 后出现 SFX V2 面板文本字段为 `prompt`,显示 `0 / 2048` Unicode code point 计数、52 个固定预设、一键优化、单层撤销、自动 / 手动时长和 Loop,右侧固定显示 `ElevenLabs` 模型胶囊和生成按钮。
- SFX 首次打开自动时长关闭、手动时长为 `5s`、Loop 为 false;手动 slider 范围 `0.5-30s`、步进 `0.1s`,自动模式禁用 slider 并保留最近手动值
- SFX 面板提交 `model: "eleven_text_to_sound_v2"`、canonical `prompt``duration: null | number``loop: boolean`;服务端只向 ElevenLabs 发送 Worker 验收后的英文 `actualPrompt`,不再调用 Vidu 或 Suno 音效契约
- SFX 一键优化的预设写入、中文逗号追加、重复点击、清快照、超限保文、AI 成功 / 失败 / 迟到响应、交换撤销和当前 dialog 全控件锁定都按 SFX V2 章节验收。
- 点击 `生成游戏背景音乐` 后出现背景音乐面板,字段为 `gpt_description_prompt`,右侧固定显示 `Suno` 模型胶囊,不展示 `make_instrumental`
- 音效提交到 `/api/editor/audios/sound-effects/generations`,背景音乐提交到 `/api/editor/audios/background-music/generations`
- 成功后画布新增音频卡,能通过卡片中央播放按钮播放,底部进度、时间和音量控件可操作。
@@ -372,6 +520,11 @@ POST /api/editor/audios/background-music/prompts/simplifications
- BGM 边界测试覆盖 200 / 201 个纯 Unicode `White_Space` 均归一为空并禁止三动作、大量边界空白包围 `A` 后只允许生成、`A` 加 199 个内部空格再加 `B` 后只允许简化、201 个 U+200B 或 U+FEFF 只允许简化、边界空白包围 200 个 `A` 后允许补全和生成,以及 TypeScript 与 Rust 对 U+0085、U+200B 和 U+FEFF 的一致行为。
- 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 相互切换时,菜单、预设滚动、锁和助手状态不得跨分支泄漏。
- BGM Suno body 仍只包含 `mv``gpt_description_prompt``make_instrumental`,固定 `Suno` 胶囊和动态泥点价格不变;历史和其它未迁移 Vidu 调用方的 builder / 轮询能力保持可用,但新编辑器 SFX 任务只调用 ElevenLabs
- `audio-sound-effect``audio-background-music` 必须由同一个音频 composer 渲染,并在组件内通过 `isSoundEffect` 分支。SFX 不得渲染 BGM 的补全 / 简化、Suno 模型和 BGM 字符规则;BGM 不得渲染 SFX 的一键优化、Loop、ElevenLabs 模型和 SFX 时长控件两个 mode 相互切换时,菜单、预设滚动、锁、快照和助手状态不得跨分支泄漏。
- SFX V2 翻译失败时 ElevenLabs 请求数为 0;成功时每个平台 job 最多一次 ElevenLabs POST`prompt / actual_prompt`、实际时长、Loop、model、provider 和 Task ID 在队列、素材、画布、响应、刷新和重绘后保持权威一致。
- SFX 两类 LLM 请求契约测试分别断言:一键优化每次请求为 Luna + Medium + `max_output_tokens = 8192`,翻译每次业务尝试为 Luna + Low + `max_output_tokens = 8192`,两者都不含 temperature;测试命名和说明必须把该字段解释为包含 reasoning 的 completion 总预算。优化遇到 `finish_reason = length` 直接失败且不写回;翻译首轮 `length` 只重试一次,第二轮 `length` 最终失败且 ElevenLabs 请求数为 0。
- SFX V2 翻译测试覆盖中文、英文和中英混合 userPrompt 统一英文化,覆盖日文假名、韩文和西里尔字母候选被 Script 门禁拒绝,并锁定 actualPrompt 2048 / 2049 Unicode code point 边界。
- SFX V2 二进制测试锁定 `40 MiB` / `40 MiB + 1 byte`、有 / 无 / 伪造 Content-Length、chunked 超限、允许 / fallback / 显式错误 MIME 和真实 MP3 探测;时长测试锁定有限正数、`30.5s``60s``600s` 均接受,`>600s`、NaN 和无穷值拒绝,不把请求 `30s` 当作响应硬上限。
- External v1 的 nullable duration、Loop、固定模型、幂等重放和最终响应必须与 Rust 实现及 OpenAPI 逐字段一致。`model` 的 omitted / null / 空串 / 纯空白 / 包围空白新模型 / 显式新模型必须收敛为同一幂等 payload;`audio1.0` 与未知值必须返回 `400`、零入队、零预扣、零 LLM 和零 provider。
- 本切片的定向前端、shared-contracts、`api-server``platform-audio` 和端到端 Prompt 等值测试通过,并执行 `npm run typecheck`、对应 Rust 定向测试、`npm run check:encoding``git diff --check`