实现 SFX V2 T4 生成面板交互
新增独立 SFX Prompt 助手状态机与请求隔离、单层撤销。 抽取通用音频预设跑马灯并接入 SFX 预设、计数和优化入口。 实现自动/手动时长、Loop、模型展示及同步提交参数冻结。 恢复项目布局状态,补齐回归测试并更新 T4 权威文档与共享决策。
This commit is contained in:
@@ -4,6 +4,7 @@ import soundEffectPromptCanonicalizationCases from '../../../packages/shared/tes
|
||||
import {
|
||||
canonicalizeSoundEffectPrompt,
|
||||
countSoundEffectPromptCodePoints,
|
||||
createSoundEffectPromptStateModel,
|
||||
SOUND_EFFECT_PROMPT_MAX_CODE_POINTS,
|
||||
validateSoundEffectPrompt,
|
||||
} from './ImageCanvasSoundEffectPromptModel';
|
||||
@@ -67,4 +68,94 @@ describe('ImageCanvasSoundEffectPromptModel', () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('creates one dialog-scoped optimization operation and swaps one undo snapshot', () => {
|
||||
const model = createSoundEffectPromptStateModel();
|
||||
const started = model.beginOperation({
|
||||
dialogId: 'dialog-a',
|
||||
status: 'optimizing',
|
||||
prompt: '\u0085金币落地声\u3000',
|
||||
});
|
||||
expect(started.started).toBe(true);
|
||||
expect(started.canonicalPrompt).toBe('金币落地声');
|
||||
expect(started.state).toMatchObject({
|
||||
status: 'optimizing',
|
||||
undoPromptSnapshot: null,
|
||||
temporaryPromptSnapshot: '金币落地声',
|
||||
});
|
||||
expect(
|
||||
model.beginOperation({
|
||||
dialogId: 'dialog-a',
|
||||
status: 'optimizing',
|
||||
prompt: '另一个输入',
|
||||
}).reason,
|
||||
).toBe('duplicate');
|
||||
|
||||
const resolved = model.resolveAiOperation(
|
||||
started.operation!,
|
||||
'清脆明亮的金币落地声',
|
||||
);
|
||||
expect(resolved).toMatchObject({
|
||||
applied: true,
|
||||
prompt: '清脆明亮的金币落地声',
|
||||
state: {
|
||||
status: 'idle',
|
||||
undoPromptSnapshot: '金币落地声',
|
||||
temporaryPromptSnapshot: null,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
model.swapUndoSnapshot('dialog-a', '清脆明亮的金币落地声'),
|
||||
).toMatchObject({
|
||||
applied: true,
|
||||
prompt: '金币落地声',
|
||||
state: { undoPromptSnapshot: '清脆明亮的金币落地声' },
|
||||
});
|
||||
});
|
||||
|
||||
it('clears the replaced snapshot when a later optimization fails', () => {
|
||||
const model = createSoundEffectPromptStateModel();
|
||||
const first = model.beginOperation({
|
||||
dialogId: 'dialog-a',
|
||||
status: 'optimizing',
|
||||
prompt: '原始描述',
|
||||
});
|
||||
model.resolveAiOperation(first.operation!, '第一次优化');
|
||||
const second = model.beginOperation({
|
||||
dialogId: 'dialog-a',
|
||||
status: 'optimizing',
|
||||
prompt: '手动修改后的描述',
|
||||
});
|
||||
expect(second.state.undoPromptSnapshot).toBeNull();
|
||||
expect(model.rejectOperation(second.operation!)).toMatchObject({
|
||||
applied: true,
|
||||
prompt: '手动修改后的描述',
|
||||
state: {
|
||||
status: 'idle',
|
||||
undoPromptSnapshot: null,
|
||||
temporaryPromptSnapshot: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps submission and preset state isolated by dialog ID', () => {
|
||||
const model = createSoundEffectPromptStateModel();
|
||||
const submission = model.beginOperation({
|
||||
dialogId: 'dialog-a',
|
||||
status: 'submitting',
|
||||
prompt: ' 提交描述 ',
|
||||
});
|
||||
expect(submission.started).toBe(true);
|
||||
expect(model.preparePreset('dialog-a', '不应写入').applied).toBe(false);
|
||||
expect(model.preparePreset('dialog-b', ' 另一面板 ').prompt).toBe(
|
||||
'另一面板',
|
||||
);
|
||||
expect(
|
||||
model.completeSubmittingOperation(submission.operation!),
|
||||
).toMatchObject({
|
||||
applied: true,
|
||||
prompt: '提交描述',
|
||||
state: { status: 'idle' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user