实现 SFX V2 T1 契约与提示词基础

统一前后端 SFX Prompt Unicode canonicalization、字符计数和 2048 边界
固化 52 个音效预设、追加规则及跨语言测试向量
演进共享音频请求响应、duration、Loop 和 V2 metadata 契约
增加 External model 输入矩阵纯函数并保持正式接线归属 T5
补齐前端提交、读取、深拷贝与请求边界回归测试
更新共享计划并标记 T1 完成且当前不可发布
This commit is contained in:
2026-08-06 13:05:48 +00:00
parent 6fd731f56c
commit cd80c085ff
24 changed files with 1559 additions and 97 deletions
@@ -1752,9 +1752,9 @@ describe('editorProjectClient', () => {
height: 120,
sourceType: 'generated',
prompt: '金币掉落叮当声',
actualPrompt: '金币掉落叮当声',
model: 'audio1.0',
provider: 'VectorEngine',
actualPrompt: 'A bright coin pickup chime',
model: 'eleven_text_to_sound_v2',
provider: 'elevenlabs',
taskId: 'sound-task-1',
audioKind: 'sound-effect',
asset: {
@@ -1771,8 +1771,9 @@ describe('editorProjectClient', () => {
const result = await generateEditorSoundEffect({
prompt: '金币掉落叮当声',
model: 'audio1.0',
duration: 7,
model: 'eleven_text_to_sound_v2',
duration: 7.5,
loop: true,
assetFolderId: 'project',
assetLabel: '游戏音效 1',
});
@@ -1786,8 +1787,9 @@ describe('editorProjectClient', () => {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: '金币掉落叮当声',
model: 'audio1.0',
duration: 7,
model: 'eleven_text_to_sound_v2',
duration: 7.5,
loop: true,
assetFolderId: 'project',
assetLabel: '游戏音效 1',
}),
@@ -1800,24 +1802,23 @@ describe('editorProjectClient', () => {
);
});
it('sends editor sound effect duration to Vidu instead of legacy type and tempo', async () => {
it('canonicalizes omitted duration to null and omitted loop to false', async () => {
requestJsonMock.mockResolvedValueOnce({
audioSrc: '/generated-character-drafts/editor-audios/sfx-null.mp3',
width: 420,
height: 120,
sourceType: 'generated',
prompt: '按钮确认短促音',
actualPrompt: '按钮确认短促音',
model: 'audio1.0',
provider: 'VectorEngine',
actualPrompt: 'A short confirmation click',
model: 'eleven_text_to_sound_v2',
provider: 'elevenlabs',
taskId: 'sound-task-null',
audioKind: 'sound-effect',
});
await generateEditorSoundEffect({
prompt: '按钮确认短促音',
model: 'audio1.0',
duration: 5,
model: 'eleven_text_to_sound_v2',
});
expect(requestJsonMock).toHaveBeenCalledWith(
@@ -1825,8 +1826,9 @@ describe('editorProjectClient', () => {
expect.objectContaining({
body: JSON.stringify({
prompt: '按钮确认短促音',
model: 'audio1.0',
duration: 5,
model: 'eleven_text_to_sound_v2',
duration: null,
loop: false,
}),
}),
'生成游戏音效失败',
@@ -1834,6 +1836,66 @@ describe('editorProjectClient', () => {
);
});
it('accepts the 0.5 and 30 second sound effect duration boundaries', async () => {
requestJsonMock.mockResolvedValue({});
for (const duration of [0.5, 30]) {
await generateEditorSoundEffect({
prompt: '按钮确认短促音',
model: 'eleven_text_to_sound_v2',
duration,
});
}
expect(requestJsonMock).toHaveBeenNthCalledWith(
1,
'/api/editor/audios/sound-effects/generations',
expect.objectContaining({
body: JSON.stringify({
prompt: '按钮确认短促音',
model: 'eleven_text_to_sound_v2',
duration: 0.5,
loop: false,
}),
}),
'生成游戏音效失败',
expect.any(Object),
);
expect(requestJsonMock).toHaveBeenNthCalledWith(
2,
'/api/editor/audios/sound-effects/generations',
expect.objectContaining({
body: JSON.stringify({
prompt: '按钮确认短促音',
model: 'eleven_text_to_sound_v2',
duration: 30,
loop: false,
}),
}),
'生成游戏音效失败',
expect.any(Object),
);
});
it('rejects invalid sound effect durations before the request', async () => {
for (const duration of [
0.49,
30.01,
Number.NaN,
Number.POSITIVE_INFINITY,
]) {
await expect(
generateEditorSoundEffect({
prompt: '按钮确认短促音',
model: 'eleven_text_to_sound_v2',
duration,
}),
).rejects.toThrow('游戏音效时长必须在 0.5-30 秒之间');
}
expect(requestJsonMock).not.toHaveBeenCalled();
});
it('generates editor background music through the backend BFF', async () => {
const representativeComplexPromptCase =
backgroundMusicPromptCanonicalizationCases.find(