@@ -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 推断、同步或校验 Loop; Prompt 文本与 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 limit, canonical 字符范围为 `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 都是 Latin; Common / 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` 。