合并图标规范与图集生成优化

合入 PR 137 的图标规范生成、稳定引用与图集提示词链路
保留主分支现行音效提交、Prompt 模块与结果压缩逻辑
解决 External Worker、生成提交和客户端测试冲突
This commit is contained in:
2026-08-08 21:44:15 +08:00
76 changed files with 7690 additions and 3063 deletions
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest';
import { EDITOR_ICON_DESCRIPTION_MAX_CHARS } from '../../services/image-editor/editorProjectClient';
import type { CanvasLayer } from './ImageCanvasEditorTypes';
import {
IMAGE_MODEL_GPT_IMAGE_2,
@@ -302,7 +303,7 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
);
});
it('builds spec generation plans with reference prompt semantics', () => {
it('builds icon spec generation plans without a client prompt or backend kind', () => {
const plan = buildImageGenerationSubmissionPlan({
dialog: {
mode: 'spec',
@@ -328,23 +329,47 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
});
expect(plan).toMatchObject({
kind: 'image',
kind: 'icon-spec',
normalizedPrompt: 'AI 生成图片',
input: {
size: '2048x1152',
model: 'gpt-image-2',
aspectRatio: '16:9',
imageSize: '2K',
kind: 'spec',
referenceImageSrcs: ['data:image/png;base64,ref'],
prompt: expect.stringContaining('参考图生成规范'),
playSetting: '休闲消除',
artStyle: '清爽卡通',
referenceId: 'resource-spec-ref',
generationInputs: {
fields: [
{ title: '玩法设定', value: '休闲消除' },
{ title: '美术风格', value: '清爽卡通' },
],
references: [
{
title: '参考图',
label: '参考.png',
refType: 'project-resource',
refId: 'resource-spec-ref',
},
],
},
},
result: {
assetKind: 'icon-spec',
title: '图标规范 8',
generationInputs: {
fields: [
{ title: '玩法设定', value: '休闲消除' },
{ title: '美术风格', value: '清爽卡通' },
],
references: [
{
title: '参考图',
label: '参考.png',
refType: 'project-resource',
refId: 'resource-spec-ref',
},
],
},
},
});
expect(plan.kind === 'image' ? plan.result.generationInputs : null).toEqual(
expect(plan.kind === 'icon-spec' ? plan.result.generationInputs : null).toEqual(
{
fields: [
{ title: '玩法设定', value: '休闲消除' },
@@ -362,13 +387,13 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
);
});
it('treats the renamed UI spec entry as an icon spec asset', () => {
it('does not add optional reference fields to an icon spec without a reference', () => {
const plan = buildImageGenerationSubmissionPlan({
dialog: {
mode: 'spec',
prompt: '',
status: 'idle',
specType: 'ui',
specType: 'icon',
specValues: {
playSetting: '消除类派对玩法',
artStyle: '糖果玻璃拟物',
@@ -382,17 +407,28 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
});
expect(plan).toMatchObject({
kind: 'image',
kind: 'icon-spec',
input: {
playSetting: '消除类派对玩法',
artStyle: '糖果玻璃拟物',
generationInputs: {
fields: [
{ title: '玩法设定', value: '消除类派对玩法' },
{ title: '美术风格', value: '糖果玻璃拟物' },
],
references: [],
},
},
result: {
assetKind: 'icon-spec',
title: '图标规范 2',
},
});
expect(plan.kind === 'image' ? plan.input : null).not.toHaveProperty(
expect(plan.kind === 'icon-spec' ? plan.input : null).not.toHaveProperty(
'referenceImageSrcs',
);
expect(plan.kind === 'image' ? plan.input.prompt : '').not.toContain(
'参考图生成规范',
expect(plan.kind === 'icon-spec' ? plan.input : null).not.toHaveProperty(
'prompt',
);
});
@@ -751,6 +787,25 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
});
});
it('returns an icon spritesheet error when the spec reference is not registered', () => {
const plan = buildIconSpritesheetGenerationSubmissionPlan({
mode: 'icon',
prompt: '返回按钮',
status: 'idle',
iconSpecReference: {
id: 'local-icon-spec',
label: '本地图标规范',
src: 'data:image/png;base64,spec',
resourceId: 'local-resource-icon-spec',
},
});
expect(plan).toEqual({
ok: false,
errorMessage: '参考图尚未登记为项目资源或素材,请重新选择或上传后再试',
});
});
it('returns an icon spritesheet error when descriptions are empty', () => {
const plan = buildIconSpritesheetGenerationSubmissionPlan({
mode: 'icon',
@@ -771,6 +826,25 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
});
});
it('returns an icon spritesheet error when the complete prompt is too long', () => {
const plan = buildIconSpritesheetGenerationSubmissionPlan({
mode: 'icon',
prompt: '图'.repeat(EDITOR_ICON_DESCRIPTION_MAX_CHARS + 1),
status: 'idle',
iconSpecReference: {
id: 'icon-spec',
label: '图标规范',
src: 'data:image/png;base64,spec',
resourceId: 'resource-icon-spec',
},
});
expect(plan).toEqual({
ok: false,
errorMessage: `素材描述不能超过 ${EDITOR_ICON_DESCRIPTION_MAX_CHARS} 个字符`,
});
});
it('builds icon spritesheet plans with the complete prompt and references', () => {
const plan = buildIconSpritesheetGenerationSubmissionPlan({
mode: 'icon',
@@ -802,7 +876,7 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
ok: true,
iconDescriptions: ['返回按钮\n\n设置按钮'],
input: {
referenceImageSrc: 'data:image/png;base64,spec',
referenceId: 'resource-icon-spec',
referenceImageSrcs: ['data:image/png;base64,ref'],
iconDescriptions: ['返回按钮\n\n设置按钮'],
model: 'gpt-image-2',