收紧画布生成输入V2参数恢复
新增按action统一解码生成输入参数 将失效与不兼容参数回落到当前默认值 恢复和再次提交共用规范V2结果并展示告警 记录原地收紧决策与Review处理结果 补充历史模型和非法参数回归测试
This commit is contained in:
@@ -29,7 +29,9 @@ import {
|
||||
calculateEditorUiDesignPrice,
|
||||
calculateEditorVideoPrice,
|
||||
canOpenRedrawPanel,
|
||||
CANVAS_GENERATION_PARAMETER_FALLBACK_WARNING,
|
||||
CHARACTER_ANIMATION_MODEL,
|
||||
decodeCanvasGenerationInputs,
|
||||
DEFAULT_IMAGE_MODEL,
|
||||
DEFAULT_SOUND_EFFECT_MODEL,
|
||||
DEFAULT_VIDEO_MODEL,
|
||||
@@ -543,6 +545,195 @@ describe('ImageCanvasGenerationModel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps valid V2 recipes and supported image aliases canonical', () => {
|
||||
const valid = decodeCanvasGenerationInputs(
|
||||
buildImageGenerationInputs('森林城堡', [], {
|
||||
model: IMAGE_MODEL_GPT_IMAGE_2,
|
||||
aspectRatio: '16:9',
|
||||
imageSize: '2K',
|
||||
}),
|
||||
);
|
||||
expect(valid).toMatchObject({ ok: true, warnings: [] });
|
||||
|
||||
const alias = decodeCanvasGenerationInputs({
|
||||
version: 2,
|
||||
action: 'image.generate',
|
||||
fields: [
|
||||
{ id: 'prompt', title: '生成提示词', value: '像素村庄' },
|
||||
{ id: 'model', title: '模型', value: 'nanobanana2' },
|
||||
{ id: 'aspectRatio', title: '比例', value: '4:3' },
|
||||
{ id: 'imageSize', title: '尺寸', value: '0.5K' },
|
||||
],
|
||||
references: [],
|
||||
});
|
||||
expect(alias).toMatchObject({ ok: true, warnings: [] });
|
||||
expect(
|
||||
alias.ok
|
||||
? alias.inputs.fields.find((field) => field.id === 'model')?.value
|
||||
: null,
|
||||
).toBe(IMAGE_MODEL_NANOBANANA2);
|
||||
});
|
||||
|
||||
it('falls invalid image and workflow parameters back within V2', () => {
|
||||
const image = decodeCanvasGenerationInputs({
|
||||
version: 2,
|
||||
action: 'image.generate',
|
||||
fields: [
|
||||
{ id: 'prompt', title: '生成提示词', value: '森林城堡' },
|
||||
{ id: 'model', title: '模型', value: 'removed-image-model' },
|
||||
{ id: 'style', title: '风格', value: 'oil' },
|
||||
{ id: 'aspectRatio', title: '比例', value: '7:5' },
|
||||
{ id: 'imageSize', title: '尺寸', value: '8K' },
|
||||
],
|
||||
references: [],
|
||||
});
|
||||
expect(image.ok).toBe(true);
|
||||
if (!image.ok) {
|
||||
throw new Error('图片 V2 解码失败');
|
||||
}
|
||||
expect(
|
||||
Object.fromEntries(
|
||||
image.inputs.fields.map((field) => [field.id, field.value]),
|
||||
),
|
||||
).toMatchObject({
|
||||
model: DEFAULT_IMAGE_MODEL,
|
||||
style: 'none',
|
||||
aspectRatio: '1:1',
|
||||
imageSize: '1K',
|
||||
});
|
||||
expect(image.warnings).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ fieldIds: ['model'] }),
|
||||
expect.objectContaining({ fieldIds: ['style'] }),
|
||||
expect.objectContaining({ fieldIds: ['aspectRatio'] }),
|
||||
expect.objectContaining({ fieldIds: ['imageSize'] }),
|
||||
]),
|
||||
);
|
||||
expect(image.warnings[0]?.message).toBe(
|
||||
CANVAS_GENERATION_PARAMETER_FALLBACK_WARNING,
|
||||
);
|
||||
|
||||
const workflow = decodeCanvasGenerationInputs({
|
||||
version: 2,
|
||||
action: 'publication.generate',
|
||||
fields: [
|
||||
{ id: 'workflowId', title: '宣发类型', value: 'retired-workflow' },
|
||||
],
|
||||
references: [],
|
||||
});
|
||||
expect(workflow).toMatchObject({
|
||||
ok: true,
|
||||
inputs: {
|
||||
version: 2,
|
||||
action: 'publication.generate',
|
||||
fields: [
|
||||
{
|
||||
id: 'workflowId',
|
||||
title: '宣发类型',
|
||||
value: 'publication-cover-image',
|
||||
},
|
||||
],
|
||||
},
|
||||
warnings: [expect.objectContaining({ fieldIds: ['workflowId'] })],
|
||||
});
|
||||
});
|
||||
|
||||
it('falls retired and incompatible video parameters back to current defaults', () => {
|
||||
const decoded = decodeCanvasGenerationInputs({
|
||||
version: 2,
|
||||
action: 'video.generate',
|
||||
fields: [
|
||||
{ id: 'prompt', title: '视频描述', value: '追逐镜头' },
|
||||
{ id: 'model', title: '模型', value: 'veo3.1' },
|
||||
{ id: 'aspectRatio', title: '比例', value: '7:5' },
|
||||
{ id: 'resolution', title: '清晰度', value: '1080p' },
|
||||
{ id: 'durationSeconds', title: '时长', value: 99 },
|
||||
{ id: 'sound', title: '声音', value: 'surround' },
|
||||
{ id: 'webSearchEnabled', title: '联网搜索', value: 'yes' },
|
||||
],
|
||||
references: [],
|
||||
});
|
||||
expect(decoded.ok).toBe(true);
|
||||
if (!decoded.ok) {
|
||||
throw new Error('视频 V2 解码失败');
|
||||
}
|
||||
expect(
|
||||
Object.fromEntries(
|
||||
decoded.inputs.fields.map((field) => [field.id, field.value]),
|
||||
),
|
||||
).toMatchObject({
|
||||
model: DEFAULT_VIDEO_MODEL,
|
||||
aspectRatio: '16:9',
|
||||
resolution: '480p',
|
||||
durationSeconds: 4,
|
||||
sound: 'on',
|
||||
webSearchEnabled: true,
|
||||
});
|
||||
expect(decoded.warnings.map((warning) => warning.fieldIds)).toEqual(
|
||||
expect.arrayContaining([
|
||||
['model'],
|
||||
['aspectRatio'],
|
||||
['resolution'],
|
||||
['durationSeconds'],
|
||||
['sound'],
|
||||
['webSearchEnabled'],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('falls invalid sound and character animation parameters back to complete defaults', () => {
|
||||
const sound = decodeCanvasGenerationInputs({
|
||||
version: 2,
|
||||
action: 'audio.sound-effect.generate',
|
||||
fields: [
|
||||
{ id: 'prompt', title: 'prompt', value: '点击音' },
|
||||
{ id: 'model', title: 'model', value: 'retired-audio-model' },
|
||||
{ id: 'durationSeconds', title: 'duration', value: 11 },
|
||||
],
|
||||
references: [],
|
||||
});
|
||||
expect(sound).toMatchObject({
|
||||
ok: true,
|
||||
inputs: {
|
||||
fields: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'model', value: 'audio1.0' }),
|
||||
expect.objectContaining({ id: 'durationSeconds', value: 5 }),
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
const animation = decodeCanvasGenerationInputs({
|
||||
version: 2,
|
||||
action: 'character-animation.generate',
|
||||
fields: [
|
||||
{ id: 'prompt', title: '动作描述', value: '挥手' },
|
||||
{ id: 'resolution', title: '清晰度', value: '1080p' },
|
||||
{ id: 'ratio', title: '比例', value: '7:5' },
|
||||
{ id: 'frameCount', title: '帧数', value: 40 },
|
||||
{ id: 'durationSeconds', title: '时长', value: 6 },
|
||||
],
|
||||
references: [],
|
||||
});
|
||||
expect(animation).toMatchObject({
|
||||
ok: true,
|
||||
inputs: {
|
||||
fields: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 'resolution', value: '480p' }),
|
||||
expect.objectContaining({ id: 'ratio', value: 'same' }),
|
||||
expect.objectContaining({ id: 'frameCount', value: 32 }),
|
||||
expect.objectContaining({ id: 'durationSeconds', value: 4 }),
|
||||
]),
|
||||
},
|
||||
warnings: expect.arrayContaining([
|
||||
expect.objectContaining({ fieldIds: ['resolution'] }),
|
||||
expect.objectContaining({ fieldIds: ['ratio'] }),
|
||||
expect.objectContaining({
|
||||
fieldIds: ['frameCount', 'durationSeconds'],
|
||||
}),
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps generated prompts and quick edit options stable', () => {
|
||||
const prompt = buildSpecPrompt('ui', {
|
||||
playSetting: '消除玩法',
|
||||
|
||||
Reference in New Issue
Block a user