修复宣发素材生成器刷新恢复
恢复画布布局中的 publication 生成器快照 补齐宣发素材卡片类型、游戏信息和参考图 hydrate 增加模型、面板和编辑器加载链路回归测试 更新图片画布持久化文档和共享踩坑记录
This commit is contained in:
@@ -276,6 +276,79 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('restores publication material composer fields and references from saved generator snapshots', async () => {
|
||||
loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({
|
||||
projectId: 'editor-project-publication-dialog',
|
||||
title: '宣发画布',
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
layers: [
|
||||
{
|
||||
itemType: 'generation-dialog',
|
||||
layerId: 'generation-dialog:generation-dialog-publication',
|
||||
resourceId: 'generation-dialog:generation-dialog-publication',
|
||||
dialog: {
|
||||
id: 'generation-dialog-publication',
|
||||
mode: 'publication',
|
||||
prompt: '马戏团午夜惊魂|非对称对抗|找到钥匙逃离',
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
publicationWorkflowId: 'publication-promo-poster',
|
||||
publicationGameInfo: {
|
||||
gameName: '马戏团午夜惊魂',
|
||||
gameCategories: '非对称对抗',
|
||||
gameDescription: '找到钥匙,开门逃离马戏团',
|
||||
},
|
||||
publicationReferences: [
|
||||
{
|
||||
id: 'publication-reference-1',
|
||||
label: '首图参考',
|
||||
src: '/generated-character-drafts/editor/reference.png',
|
||||
objectKey: 'generated-character-drafts/editor/reference.png',
|
||||
assetObjectId: 'asset-object-publication-reference',
|
||||
},
|
||||
],
|
||||
imageModel: 'gpt-image-2',
|
||||
aspectRatio: '16:9',
|
||||
imageSize: '2K',
|
||||
placeholder: {
|
||||
x: 282,
|
||||
y: 112,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
originalWidth: 1280,
|
||||
originalHeight: 720,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
resources: [],
|
||||
updatedAt: '2026-06-17T00:00:00.000Z',
|
||||
});
|
||||
|
||||
render(<ImageCanvasEditorView />);
|
||||
|
||||
const publicationDialog = await screen.findByRole('dialog', {
|
||||
name: '运营海报生成卡片',
|
||||
});
|
||||
expect(
|
||||
(within(publicationDialog).getByRole('textbox', {
|
||||
name: '运营海报游戏名',
|
||||
}) as HTMLInputElement).value,
|
||||
).toBe('马戏团午夜惊魂');
|
||||
expect(
|
||||
(within(publicationDialog).getByRole('textbox', {
|
||||
name: '运营海报游戏分类',
|
||||
}) as HTMLInputElement).value,
|
||||
).toBe('非对称对抗');
|
||||
expect(
|
||||
(within(publicationDialog).getByRole('textbox', {
|
||||
name: '运营海报一句话描述游戏',
|
||||
}) as HTMLTextAreaElement).value,
|
||||
).toBe('找到钥匙,开门逃离马戏团');
|
||||
expect(within(publicationDialog).getByLabelText('首图参考')).toBeTruthy();
|
||||
expect(screen.getByLabelText('宣发素材生成占位图')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('opens a canvas generation frame and composer before creating a generated layer', async () => {
|
||||
generateEditorImageMock.mockResolvedValueOnce({
|
||||
imageSrc: 'data:image/png;base64,ZmFrZS1pbWFnZQ==',
|
||||
|
||||
@@ -294,6 +294,71 @@ describe('ImageCanvasEditorModel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('restores publication material generator inputs from saved layout', () => {
|
||||
const dialog: CanvasGenerationDialogState = {
|
||||
id: 'generation-dialog-publication',
|
||||
mode: 'publication',
|
||||
prompt: '马戏团午夜惊魂|非对称对抗|找到钥匙逃离',
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
publicationWorkflowId: 'publication-promo-poster',
|
||||
publicationGameInfo: {
|
||||
gameName: '马戏团午夜惊魂',
|
||||
gameCategories: '非对称对抗',
|
||||
gameDescription: '找到钥匙,开门逃离马戏团',
|
||||
},
|
||||
publicationReferences: [
|
||||
{
|
||||
id: 'publication-reference-1',
|
||||
label: '首图参考',
|
||||
src: '/generated-character-drafts/editor/reference.png',
|
||||
objectKey: 'generated-character-drafts/editor/reference.png',
|
||||
assetObjectId: 'asset-object-publication-reference',
|
||||
},
|
||||
],
|
||||
imageModel: 'gpt-image-2',
|
||||
aspectRatio: '16:9',
|
||||
imageSize: '2K',
|
||||
generatedLayerId: 'layer-publication',
|
||||
placeholder: {
|
||||
x: 100,
|
||||
y: 120,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
originalWidth: 1280,
|
||||
originalHeight: 720,
|
||||
},
|
||||
};
|
||||
|
||||
const { generationDialogs } = splitCanvasLayoutItems(
|
||||
serializeCanvasLayout({
|
||||
layers: [],
|
||||
canvasGenerationDialogs: [dialog],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(generationDialogs).toHaveLength(1);
|
||||
expect(generationDialogs[0]).toMatchObject({
|
||||
id: 'generation-dialog-publication',
|
||||
mode: 'publication',
|
||||
publicationWorkflowId: 'publication-promo-poster',
|
||||
publicationGameInfo: {
|
||||
gameName: '马戏团午夜惊魂',
|
||||
gameCategories: '非对称对抗',
|
||||
gameDescription: '找到钥匙,开门逃离马戏团',
|
||||
},
|
||||
imageModel: 'gpt-image-2',
|
||||
generatedLayerId: 'layer-publication',
|
||||
});
|
||||
expect(generationDialogs[0]?.publicationReferences?.[0]).toMatchObject({
|
||||
id: 'publication-reference-1',
|
||||
label: '首图参考',
|
||||
src: '/generated-character-drafts/editor/reference.png',
|
||||
objectKey: 'generated-character-drafts/editor/reference.png',
|
||||
assetObjectId: 'asset-object-publication-reference',
|
||||
});
|
||||
});
|
||||
|
||||
it('serializes generation dialogs beside layers and splits them on load', () => {
|
||||
const layer: CanvasLayer = {
|
||||
id: 'layer-generated',
|
||||
|
||||
@@ -295,6 +295,15 @@ export function hydrateCanvasGenerationDialog(
|
||||
typeof description === 'string',
|
||||
)
|
||||
: undefined,
|
||||
publicationWorkflowId: hydratePublicationWorkflowId(
|
||||
snapshot.publicationWorkflowId,
|
||||
),
|
||||
publicationGameInfo: hydratePublicationGameInfo(
|
||||
snapshot.publicationGameInfo,
|
||||
),
|
||||
publicationReferences: hydrateCharacterReferences(
|
||||
snapshot.publicationReferences,
|
||||
),
|
||||
uiDesignSpecReference: hydrateCharacterReference(
|
||||
snapshot.uiDesignSpecReference,
|
||||
),
|
||||
@@ -742,6 +751,7 @@ function isCanvasGenerationDialogMode(
|
||||
value === 'spec' ||
|
||||
value === 'character' ||
|
||||
value === 'icon' ||
|
||||
value === 'publication' ||
|
||||
value === 'ui-design' ||
|
||||
value === 'quick-edit' ||
|
||||
value === 'character-animation' ||
|
||||
@@ -768,6 +778,30 @@ function isSpecGenerationType(
|
||||
);
|
||||
}
|
||||
|
||||
function hydratePublicationWorkflowId(
|
||||
value: unknown,
|
||||
): CanvasGenerationDialogState['publicationWorkflowId'] {
|
||||
return value === 'publication-cover-image' ||
|
||||
value === 'publication-detail-gallery' ||
|
||||
value === 'publication-promo-poster'
|
||||
? value
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function hydratePublicationGameInfo(
|
||||
value: unknown,
|
||||
): CanvasGenerationDialogState['publicationGameInfo'] {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
const snapshot = value as Record<string, unknown>;
|
||||
return {
|
||||
gameName: stringOrUndefined(snapshot.gameName) ?? '',
|
||||
gameCategories: stringOrUndefined(snapshot.gameCategories) ?? '',
|
||||
gameDescription: stringOrUndefined(snapshot.gameDescription) ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
function hydrateSpecFormValues(
|
||||
value: unknown,
|
||||
): CanvasGenerationDialogState['specValues'] {
|
||||
|
||||
@@ -277,6 +277,52 @@ describe('ImageCanvasGenerationComposerView', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('恢复宣发素材生成卡片的字段和参考图', () => {
|
||||
renderComposer({
|
||||
mode: 'publication',
|
||||
prompt: '马戏团午夜惊魂|非对称对抗|找到钥匙逃离',
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
publicationWorkflowId: 'publication-promo-poster',
|
||||
publicationGameInfo: {
|
||||
gameName: '马戏团午夜惊魂',
|
||||
gameCategories: '非对称对抗',
|
||||
gameDescription: '找到钥匙,开门逃离马戏团',
|
||||
},
|
||||
publicationReferences: [
|
||||
{
|
||||
id: 'publication-reference-1',
|
||||
label: '首图参考',
|
||||
src: '/generated-character-drafts/editor/reference.png',
|
||||
},
|
||||
],
|
||||
imageModel: 'gpt-image-2',
|
||||
aspectRatio: '16:9',
|
||||
imageSize: '2K',
|
||||
});
|
||||
|
||||
const panel = screen.getByRole('dialog', { name: '运营海报生成卡片' });
|
||||
expect(
|
||||
(within(panel).getByRole('textbox', {
|
||||
name: '运营海报游戏名',
|
||||
}) as HTMLInputElement).value,
|
||||
).toBe('马戏团午夜惊魂');
|
||||
expect(
|
||||
(within(panel).getByRole('textbox', {
|
||||
name: '运营海报游戏分类',
|
||||
}) as HTMLInputElement).value,
|
||||
).toBe('非对称对抗');
|
||||
expect(
|
||||
(within(panel).getByRole('textbox', {
|
||||
name: '运营海报一句话描述游戏',
|
||||
}) as HTMLTextAreaElement).value,
|
||||
).toBe('找到钥匙,开门逃离马戏团');
|
||||
expect(within(panel).getByLabelText('首图参考')).toBeTruthy();
|
||||
expect(
|
||||
within(panel).getByRole('button', { name: '删除首图参考' }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('生成图片参考图点击先弹来源菜单,不直接打开上传', () => {
|
||||
const onRequestUpload = vi.fn();
|
||||
const setIsGenerationReferenceMenuOpen = vi.fn();
|
||||
|
||||
Reference in New Issue
Block a user