Merge branch 'master' into codex/infinite-canvas
Project CI / Repository checks (pull_request) Successful in 58s
Project CI / Frontend tests (pull_request) Successful in 3m17s
Project CI / Backend tests (pull_request) Successful in 4m14s
Project CI / Native shell tests (pull_request) Successful in 14m29s

This commit is contained in:
2026-08-11 12:47:19 +08:00
15 changed files with 376 additions and 682 deletions
File diff suppressed because one or more lines are too long
@@ -130,7 +130,10 @@ export type CanvasLayer = {
id: string;
resourceId: string;
resourcePersistenceState?:
'pending' | 'registered' | 'self-contained-local' | 'unresolved-local';
| 'pending'
| 'registered'
| 'self-contained-local'
| 'unresolved-local';
title: string;
src: string;
mediaType?: CanvasMediaType;
@@ -263,7 +266,6 @@ export type GenerateDialogState = {
assetLabel?: string;
status: 'idle' | 'generating' | 'pending-confirmation' | 'failed';
composerOpen?: boolean;
isRedrawAsNewArtifact?: boolean;
sourceLayerId?: string;
generatedLayerId?: string;
specType?: SpecGenerationType;
@@ -443,7 +445,6 @@ export type CanvasContextMenuState =
export type QuickEditPanelState = {
referenceUploadContextId?: string;
mode?: 'quick-edit' | 'redraw';
sourceLayerId: string;
prompt: string;
assetLabel?: string;
@@ -470,7 +471,12 @@ export type CropExpandPanelState = {
};
export type CropExpandRatioValue =
'free' | '1:1' | '4:3' | '3:4' | '16:9' | '9:16';
| 'free'
| '1:1'
| '4:3'
| '3:4'
| '16:9'
| '9:16';
export type CropExpandResizeHandle =
| 'left'
@@ -1169,8 +1169,9 @@ export function ImageCanvasEditorView({
imageSequenceDurationMs: asset.imageSequenceDurationMs ?? undefined,
assetKind: canvasAssetKindOrNull(asset.assetKind),
generationInputs: generationInputsOrNull(asset.generationInputs),
generationInputsHydrationState:
hydrateCanvasGenerationInputsResult(asset.generationInputs).state,
generationInputsHydrationState: hydrateCanvasGenerationInputsResult(
asset.generationInputs,
).state,
},
]);
setAssetFolders((currentFolders) =>
@@ -1750,7 +1751,7 @@ export function ImageCanvasEditorView({
: null;
}
if (target === 'quick-edit-reference') {
return quickEditPanel && quickEditPanel.mode !== 'redraw'
return quickEditPanel
? {
label: '快速编辑参考图',
currentCount: imageReferenceCount(
@@ -21,10 +21,10 @@ import {
createGenerateDialogDraft,
createIconGenerationDialogDraft,
createLayerGenerationDialogDraft,
createLegacyImageRedrawGenerationDialogDraft,
createPublicationGenerationDialogDraft,
createQuickEditGenerationDialogDraft,
createQuickEditPanelDraft,
createRedrawPanelDraft,
createSameSourceGenerationDialogDraft,
createSceneGenerationDialogDraft,
createSoundEffectGenerationDialogDraft,
@@ -469,7 +469,6 @@ describe('ImageCanvasGenerationDialogModel', () => {
imageSize: '1K',
});
expect(createQuickEditPanelDraft(sourceLayer)).toEqual({
mode: 'quick-edit',
sourceLayerId: 'layer-source',
prompt: '',
size: '1024x768',
@@ -479,7 +478,13 @@ describe('ImageCanvasGenerationDialogModel', () => {
quickEditReferences: [],
status: 'idle',
});
expect(createRedrawPanelDraft(sourceLayer).prompt).toBe('原图提示');
expect(
createLegacyImageRedrawGenerationDialogDraft({
sourceLayer,
canvasSize: { width: 960, height: 720 },
viewport: { x: 0, y: 0, scale: 1 },
}),
).toMatchObject({ mode: 'generate', prompt: '原图提示' });
expect(createCharacterAnimationPanelDraft(sourceLayer)).toEqual({
sourceLayerId: 'layer-source',
promptText: '',
@@ -590,21 +595,23 @@ describe('ImageCanvasGenerationDialogModel', () => {
for (const testCase of cases) {
expect(
createRedrawPanelDraft(
createLayer({
createLegacyImageRedrawGenerationDialogDraft({
sourceLayer: createLayer({
prompt: '【系统内置】完整拼接提示词,包含默认规则和模型约束。',
generationInputs: {
fields: testCase.fields,
references: [],
},
}),
).prompt,
canvasSize: { width: 960, height: 720 },
viewport: { x: 0, y: 0, scale: 1 },
}).prompt,
).toBe(testCase.expectedPrompt);
}
expect(
createRedrawPanelDraft(
createLayer({
createLegacyImageRedrawGenerationDialogDraft({
sourceLayer: createLayer({
prompt: '【系统内置】固定素材提取提示词',
generationInputs: {
fields: [
@@ -618,7 +625,9 @@ describe('ImageCanvasGenerationDialogModel', () => {
references: [],
},
}),
).prompt,
canvasSize: { width: 960, height: 720 },
viewport: { x: 0, y: 0, scale: 1 },
}).prompt,
).toBe('');
});
@@ -1269,7 +1278,7 @@ describe('ImageCanvasGenerationDialogModel', () => {
});
});
it('restores quick-edit references and the persisted image model', () => {
it('does not restore in-place image edits as remodel generators', () => {
const editSource = createLayer({
id: 'layer-edit-source',
resourceId: 'resource-edit-source',
@@ -1279,55 +1288,44 @@ describe('ImageCanvasGenerationDialogModel', () => {
resourceId: 'resource-style-reference',
title: '风格参考',
});
const editResult = createLayer({
id: 'layer-edit-result',
resourceId: 'resource-edit-result',
sourceType: 'generated',
generationInputs: {
version: 2,
action: 'image.edit',
fields: [
{ id: 'prompt', title: '提示词', value: '改成雨天' },
{ id: 'model', title: '模型', value: IMAGE_MODEL_NANOBANANA2 },
{ id: 'aspectRatio', title: '比例', value: '16:9' },
{ id: 'imageSize', title: '尺寸', value: '2K' },
],
references: [
{
id: 'source',
title: '原图',
refType: 'project-resource',
refId: 'resource-edit-source',
},
{
id: 'reference',
title: '参考图 1',
refType: 'project-resource',
refId: 'resource-style-reference',
},
],
},
});
expect(
createLayerGenerationDialogDraft({
sourceLayer: createLayer({
id: 'layer-edit-result',
resourceId: 'resource-edit-result',
sourceType: 'generated',
generationInputs: {
version: 2,
action: 'image.edit',
fields: [
{ id: 'prompt', title: '提示词', value: '改成雨天' },
{ id: 'model', title: '模型', value: IMAGE_MODEL_NANOBANANA2 },
{ id: 'aspectRatio', title: '比例', value: '16:9' },
{ id: 'imageSize', title: '尺寸', value: '2K' },
],
references: [
{
id: 'source',
title: '原图',
refType: 'project-resource',
refId: 'resource-edit-source',
},
{
id: 'reference',
title: '参考图 1',
refType: 'project-resource',
refId: 'resource-style-reference',
},
],
},
}),
sourceLayer: editResult,
availableLayers: [editSource, styleReference],
canvasSize: { width: 960, height: 720 },
viewport: { x: 0, y: 0, scale: 1 },
}),
).toMatchObject({
mode: 'quick-edit',
prompt: '改成雨天',
imageModel: IMAGE_MODEL_NANOBANANA2,
aspectRatio: '16:9',
imageSize: '2K',
generationReferences: [
expect.objectContaining({
id: 'canvas-layer-style-reference',
label: '风格参考',
}),
],
});
).toBeNull();
});
it('uses the V2 source reference ahead of a conflicting legacy animation source', () => {
@@ -1315,40 +1315,6 @@ function createNormalizedGenerationDialogDraft({
: null;
}
if (inputs.action === 'image.edit') {
const source = resolveNormalizedReferences(
sourceLayer,
'source',
availableLayers,
)[0];
const sourceLayerForEdit = source
? (availableLayers ?? []).find(
(layer) => createCanvasLayerReference(layer).id === source.id,
)
: undefined;
if (!sourceLayerForEdit) {
return null;
}
const size = resolveEditorImageGenerationPixelSize({
model: getNormalizedString(sourceLayer, 'model', DEFAULT_IMAGE_MODEL),
aspectRatio: getNormalizedString(sourceLayer, 'aspectRatio', '1:1'),
imageSize: getNormalizedString(sourceLayer, 'imageSize', '1K'),
});
return {
...createQuickEditGenerationDialogDraft({
sourceLayer: sourceLayerForEdit,
prompt,
references,
model: getNormalizedString(sourceLayer, 'model', DEFAULT_IMAGE_MODEL),
aspectRatio: getNormalizedString(sourceLayer, 'aspectRatio', '1:1'),
imageSize: getNormalizedString(sourceLayer, 'imageSize', '1K'),
frame: size,
}),
composerOpen: true,
isRedrawAsNewArtifact: true,
};
}
return null;
}
@@ -1956,7 +1922,6 @@ export function createQuickEditPanelDraft(
dimensionOptions.imageSizes[0] ??
'1K');
return {
mode: 'quick-edit',
sourceLayerId: sourceLayer.id,
prompt: '',
size: formatImageSizeValue(
@@ -2021,20 +1986,29 @@ export function createQuickEditGenerationDialogDraft({
};
}
export function createRedrawPanelDraft(
sourceLayer: CanvasLayer,
options: {
imageModel?: string;
aspectRatio?: string;
imageSize?: string;
} = {},
): QuickEditPanelState {
return {
...createQuickEditPanelDraft(sourceLayer, options),
mode: 'redraw',
model: normalizeEditorImageModel(options.imageModel ?? sourceLayer.model),
prompt: resolveUserGenerationPromptSnapshot(sourceLayer),
};
export function createLegacyImageRedrawGenerationDialogDraft({
sourceLayer,
canvasSize,
viewport,
sourceDialog,
}: {
sourceLayer: CanvasLayer;
canvasSize: CanvasSize;
viewport: CanvasViewport;
sourceDialog?: CanvasGenerationDialogState | null;
}): Omit<CanvasGenerationDialogState, 'id'> {
return placeDraftBesideSourceLayer(
restoreSharedImageOptions(
{
...createGenerateDialogDraft({ canvasSize, viewport }),
prompt: resolveUserGenerationPromptSnapshot(sourceLayer),
sourceLayerId: sourceLayer.id,
},
sourceLayer,
sourceDialog,
),
sourceLayer,
);
}
export function createCharacterAnimationPanelDraft(
@@ -2261,11 +2235,7 @@ export function appendQuickEditReference(
panel: QuickEditPanelState | null,
layer: CanvasLayer,
): QuickEditPanelState | null {
if (
!panel ||
panel.mode === 'redraw' ||
getReferenceMediaType(layer) !== 'image'
) {
if (!panel || getReferenceMediaType(layer) !== 'image') {
return panel;
}
return {
@@ -10,7 +10,6 @@ import {
createCharacterAnimationResultLayer,
createGeneratedResultLayer,
createIconSpritesheetResultLayers,
createQuickEditResultLayer,
createVideoResultLayer,
} from './ImageCanvasGenerationLayerModel';
@@ -232,57 +231,6 @@ describe('ImageCanvasGenerationLayerModel', () => {
});
});
it('creates quick edit layers centered on the generating placeholder', () => {
const sourceLayer = createSourceLayer();
const layer = createQuickEditResultLayer({
generated: createGenerated({ width: 1536, height: 1024 }),
generatedIndex: 10,
sourceLayer,
generationInputs: createGenerationInputs(),
frame: {
x: 600,
y: 300,
width: 320,
height: 240,
originalWidth: 1536,
originalHeight: 1024,
},
});
expect(layer).toMatchObject({
id: 'layer-quick-edit-10',
x: -8,
y: -92,
width: 1536,
height: 1024,
});
});
it('creates quick edit layers with source group and asset kind preserved', () => {
const sourceLayer = createSourceLayer();
const layer = createQuickEditResultLayer({
generated: createGenerated({ width: 1536, height: 1024 }),
generatedIndex: 9,
sourceLayer,
generationInputs: createGenerationInputs(),
});
expect(layer).toMatchObject({
id: 'layer-quick-edit-9',
resourceId: 'local-resource-quick-edit-9',
title: '源图 快速编辑',
x: 472,
y: 140,
width: 1536,
height: 1024,
originalWidth: 1536,
originalHeight: 1024,
sourceResourceId: 'resource-source',
groupId: 'group-a',
assetKind: 'spec',
});
});
it('applies quick edits to the source layer in place', () => {
const sourceLayer = createSourceLayer({
width: 320,
@@ -32,16 +32,6 @@ type GeneratedResultLayerOptions = {
generationInputs?: CanvasGenerationInputs;
};
type QuickEditResultLayerOptions = {
generated: EditorImageGenerationResult;
generatedIndex: number;
sourceLayer: CanvasLayer;
generationInputs: CanvasGenerationInputs;
mode?: 'quick-edit' | 'redraw';
frame?: GenerateDialogState['placeholder'];
title?: string;
};
type IconSpritesheetResultLayerOptions = {
generated: EditorIconSpritesheetGenerationResult;
iconResults?: EditorIconSpritesheetIconResult[] | null;
@@ -183,67 +173,6 @@ export function createGeneratedResultLayer({
);
}
export function createQuickEditResultLayer({
generated,
generatedIndex,
sourceLayer,
generationInputs,
mode = 'quick-edit',
frame,
title,
}: QuickEditResultLayerOptions): CanvasLayer {
const originalWidth =
frame?.originalWidth ||
generated.width ||
sourceLayer.originalWidth ||
1024;
const originalHeight =
frame?.originalHeight ||
generated.height ||
sourceLayer.originalHeight ||
1024;
const { width, height } = resolveLayerResolutionSize(
originalWidth,
originalHeight,
{
width: sourceLayer.width,
height: sourceLayer.height,
},
);
const frameX =
frame && frame.width > 0
? frame.x + frame.width / 2 - width / 2
: undefined;
const frameY =
frame && frame.height > 0
? frame.y + frame.height / 2 - height / 2
: undefined;
return applyGeneratedMetadata(
{
id: `layer-quick-edit-${generatedIndex}`,
resourceId: `local-resource-quick-edit-${generatedIndex}`,
title:
title ??
`${sourceLayer.title} ${mode === 'redraw' ? '重绘' : '快速编辑'}`,
src: generated.imageSrc,
x: frameX ?? sourceLayer.x + sourceLayer.width + 32,
y: frameY ?? sourceLayer.y,
width,
height,
originalWidth,
originalHeight,
zIndex: generatedIndex + 10,
sourceType: generated.sourceType,
sourceResourceId: sourceLayer.resourceId,
groupId: sourceLayer.groupId,
assetKind: sourceLayer.assetKind,
},
generated,
generationInputs,
);
}
export function applyImageEditResultToSourceLayer({
generated,
sourceLayer,
@@ -100,10 +100,7 @@ describe('ImageCanvasQuickEditPanelView', () => {
/>,
);
setPlainTextEditorValue(
screen.getByLabelText('快速编辑提示词'),
'新提示',
);
setPlainTextEditorValue(screen.getByLabelText('快速编辑提示词'), '新提示');
expect(screen.getByLabelText('当前提示词').textContent).toBe('新提示');
expect(screen.getByLabelText('当前尺寸').textContent).toBe('1024x1024');
@@ -141,9 +138,7 @@ describe('ImageCanvasQuickEditPanelView', () => {
/>,
);
expect(screen.getByRole('alert').textContent).toContain(
'图集快速编辑失败',
);
expect(screen.getByRole('alert').textContent).toContain('图集快速编辑失败');
});
it('submits without rendering a standalone close button', () => {
@@ -171,27 +166,6 @@ describe('ImageCanvasQuickEditPanelView', () => {
).toBeNull();
});
it('shows configured mud point price inside redraw submit button', () => {
render(
<QuickEditPanelHarness
initialPanel={{
sourceLayerId: 'layer-a',
prompt: '重绘提示',
size: '1024x1024',
aspectRatio: '1:1',
imageSize: '1K',
model: 'gpt-image-2',
status: 'idle',
mode: 'redraw',
}}
/>,
);
expect(screen.getByRole('button', { name: '生成' }).textContent).toBe(
'生成3泥点',
);
});
it('hides quick edit references from the simplified panel', () => {
render(
<QuickEditPanelHarness
@@ -51,9 +51,8 @@ function resetFailedPanelStatus<
function createQuickEditDialog(
panel: QuickEditPanelState,
): GenerateDialogState {
const isQuickEdit = (panel.mode ?? 'quick-edit') === 'quick-edit';
return {
mode: isQuickEdit ? 'quick-edit' : 'generate',
mode: 'quick-edit',
prompt: panel.prompt,
status: panel.status,
errorMessage: panel.errorMessage,
@@ -107,17 +106,11 @@ export function ImageCanvasQuickEditPanelView({
hasPendingImageReferenceUploads = false,
onSubmit,
}: ImageCanvasQuickEditPanelViewProps) {
const isRedraw = panel.mode === 'redraw';
const isImageQuickEdit = !isRedraw && sourceLayer.mediaType !== 'video';
const isImageQuickEdit = sourceLayer.mediaType !== 'video';
const referenceCount = panel.quickEditReferences?.length ?? 0;
const canAddReferences =
!isRedraw &&
referenceCount <
resolveExtraImageReferenceLimit(
panel.model,
QUICK_EDIT_REFERENCE_LIMIT,
1,
);
resolveExtraImageReferenceLimit(panel.model, QUICK_EDIT_REFERENCE_LIMIT, 1);
const quickEditDialog = createQuickEditDialog(panel);
return (
@@ -154,21 +147,19 @@ export function ImageCanvasQuickEditPanelView({
onRememberImageModel={onRememberImageModel}
hasPendingImageReferenceUploads={hasPendingImageReferenceUploads}
onSubmit={onSubmit}
dialogLabel={isRedraw ? '重绘图片' : '快速编辑图片'}
includeDimensions={isRedraw || isImageQuickEdit}
dialogLabel="快速编辑图片"
includeDimensions={isImageQuickEdit}
includeModel={true}
includeReferences={false}
promptLabel={isRedraw ? '重绘提示词' : '快速编辑提示词'}
promptPlaceholder={
isRedraw ? '把画面重绘成什么样?' : '你希望素材如何修改?'
}
optionLabelPrefix={isRedraw ? '重绘' : '快速编辑'}
promptLabel="快速编辑提示词"
promptPlaceholder="你希望素材如何修改?"
optionLabelPrefix="快速编辑"
dimensionRatioAriaLabelPrefix="比例"
dimensionSizeAriaLabelPrefix="尺寸"
referenceUploadTarget="quick-edit-reference"
referenceButtonLabel={isRedraw ? '' : '添加快速编辑参考图'}
referenceButtonAriaLabel={isRedraw ? undefined : '添加快速编辑参考图'}
referenceMenuLabel={isRedraw ? '' : '快速编辑参考图来源'}
referenceButtonLabel="添加快速编辑参考图"
referenceButtonAriaLabel="添加快速编辑参考图"
referenceMenuLabel="快速编辑参考图来源"
referenceSlotTone="quick-edit"
referenceSlotDisabled={!canAddReferences}
showReferenceAddButton={false}
@@ -180,9 +171,9 @@ export function ImageCanvasQuickEditPanelView({
promptClassName="image-canvas-editor__generation-prompt image-canvas-editor__quick-edit-prompt"
referenceSlotClassName="image-canvas-editor__quick-edit-reference"
submitButtonClassName="image-canvas-editor__generation-submit image-canvas-editor__quick-edit-submit"
submitLabel={isRedraw ? '生成' : '修改'}
submitAriaLabel={isRedraw ? '生成' : '修改'}
submittingStatusLabel={isRedraw ? '生成中' : '修改中'}
submitLabel="修改"
submitAriaLabel="修改"
submittingStatusLabel="修改中"
renderEditorPortal={renderEditorPortal}
buildPortalMenuStyle={buildPortalMenuStyle}
/>
@@ -98,9 +98,7 @@ describe('ImageCanvasSelectedLayerToolbarView', () => {
);
expect(props.onRemoveBackground).toHaveBeenCalledWith(props.selectedLayer);
expect(props.onPerfectPixel).toHaveBeenCalledWith(props.selectedLayer);
expect(props.onOpenRedrawPanel).toHaveBeenCalledWith(
props.selectedLayer,
);
expect(props.onOpenRedrawPanel).toHaveBeenCalledWith(props.selectedLayer);
expect(props.onDownloadLayer).toHaveBeenCalledWith(props.selectedLayer);
expect(
within(toolbar).queryByRole('button', { name: '删除图片' }),
@@ -391,7 +389,7 @@ describe('ImageCanvasSelectedLayerToolbarView', () => {
expect(screen.queryByRole('button', { name: '改造' })).toBeNull();
});
it('hides redraw for in-place quick-edit results', () => {
it('keeps quick edit but hides redraw for in-place quick-edit results', () => {
renderSelectedToolbar({
selectedLayer: createLayer({
generationInputs: {
@@ -285,7 +285,7 @@ export function applyQuickEditReferenceUpload({
panel: QuickEditPanelState | null;
references: CharacterReferenceImage[];
}): QuickEditPanelState | null {
if (!panel || panel.mode === 'redraw') {
if (!panel) {
return panel;
}
return {
@@ -901,7 +901,6 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
render(
<SubmissionWorkflowHarness
initialQuickEditPanel={{
mode: 'quick-edit',
sourceLayerId: 'layer-source',
prompt: '改成雨天',
assetLabel: ' 雨天勇者 ',
@@ -939,7 +938,6 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
render(
<SubmissionWorkflowHarness
initialQuickEditPanel={{
mode: 'quick-edit',
sourceLayerId: 'layer-source',
prompt: '改成雨天',
size: '2048x1152',
@@ -965,7 +963,6 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
<SubmissionWorkflowHarness
initialLayers={[createLayer({ assetKind: 'icon' })]}
initialQuickEditPanel={{
mode: 'quick-edit',
sourceLayerId: 'layer-source',
prompt: '修改图标',
size: '1024x1024',
@@ -993,7 +990,6 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
<SubmissionWorkflowHarness
initialLayers={[createLayer({ assetKind: 'icon-spritesheet' })]}
initialQuickEditPanel={{
mode: 'quick-edit',
sourceLayerId: 'layer-source',
prompt: '调整图集布局',
size: '1024x1024',
@@ -1218,7 +1214,6 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
render(
<SubmissionWorkflowHarness
initialQuickEditPanel={{
mode: 'quick-edit',
sourceLayerId: 'layer-source',
prompt: '对1号红色圈选框里的内容做以下修改:改成蓝色',
size: '1024x768',
@@ -1411,70 +1406,6 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
expect(refreshWalletBalance).toHaveBeenCalledTimes(1);
});
it('submits character redraws through the character image prompt contract', async () => {
generateEditorImageMock.mockResolvedValueOnce(
createGenerated({
prompt: '角色换成蓝色披风',
warning: {
code: 'postprocess-failed-source-preserved',
reason: '生成任务成功,后处理失败。',
},
}),
);
render(
<SubmissionWorkflowHarness
projectId="editor-project-1"
initialLayers={[
createLayer({
assetKind: 'character',
prompt: '原始角色设定',
}),
]}
initialQuickEditPanel={{
mode: 'redraw',
sourceLayerId: 'layer-source',
prompt: ' 角色换成蓝色披风 ',
assetLabel: ' 蓝披风勇者 ',
size: '1024x768',
model: 'gpt-image-2',
status: 'idle',
}}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '提交快速编辑' }));
await waitFor(() => {
expect(generateEditorImageMock).toHaveBeenCalledWith(
expect.objectContaining({
prompt: '角色换成蓝色披风',
size: '1024x768',
aspectRatio: '4:3',
imageSize: '1K',
kind: 'character',
model: 'gpt-image-2',
referenceImageSrcs: ['resource-source'],
assetKind: 'character',
assetLabel: '蓝披风勇者',
canvasCompletion: expect.objectContaining({
title: '蓝披风勇者',
}),
}),
);
});
await waitFor(() => {
expect(screen.getByTestId('layers').textContent).toContain(
'layer-quick-edit-1:蓝披风勇者:resource-source:character',
);
});
expect(screen.getByTestId('quick-edit').textContent).toBe(
'layer-source:idle:角色换成蓝色披风:-',
);
expect(screen.getByTestId('generation-warning').textContent).toBe(
'生成任务成功,后处理失败。',
);
});
it('submits video quick edits through the video API with the source video', async () => {
const applyProjectSnapshot = vi.fn();
const upsertGeneratedAsset = vi.fn();
@@ -1523,7 +1454,6 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
}),
]}
initialQuickEditPanel={{
mode: 'quick-edit',
sourceLayerId: 'layer-source',
prompt: '把当前图改成雨天',
size: '854x480',
@@ -1683,7 +1613,6 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
}),
]}
initialQuickEditPanel={{
mode: 'quick-edit',
sourceLayerId: 'layer-source',
prompt: '让当前图跳得更高',
size: '1024x1024',
@@ -57,12 +57,10 @@ import {
createCharacterAnimationResultLayer,
createGeneratedResultLayer,
createIconSpritesheetResultLayers,
createQuickEditResultLayer,
createVideoResultLayer,
} from './ImageCanvasGenerationLayerModel';
import {
buildCharacterAnimationGenerationInputs,
buildEditGenerationInputs,
buildQuickEditGenerationInputs,
buildUiDesignAssetExtractionGenerationInputs,
buildVideoGenerationInputs,
@@ -872,81 +870,6 @@ export function useImageCanvasGenerationSubmissionWorkflow({
],
);
const addQuickEditResultLayer = useCallback(
(
generated: Parameters<typeof createQuickEditResultLayer>[0]['generated'],
sourceLayer: CanvasLayer,
generationInputs: CanvasGenerationInputs,
mode: QuickEditPanelState['mode'] = 'quick-edit',
options: {
frame?: GenerateDialogState['placeholder'];
dialogId?: string;
title?: string;
} = {},
) => {
if (
options.dialogId &&
!hasCanvasGenerationDialogById(options.dialogId)
) {
return;
}
layerCounterRef.current += 1;
const generatedIndex = layerCounterRef.current;
const nextLayer = createQuickEditResultLayer({
generated,
generatedIndex,
sourceLayer,
generationInputs,
mode,
frame: options.frame,
title: options.title,
});
addGeneratedLayersToCanvas([nextLayer]);
selectSingleLayer(mode === 'redraw' ? sourceLayer.id : nextLayer.id);
setActiveSidebarPanel('layers');
if (mode === 'redraw') {
setQuickEditPanel((currentPanel) =>
currentPanel?.sourceLayerId === sourceLayer.id
? {
...currentPanel,
mode: 'redraw',
status: 'idle',
errorMessage: undefined,
}
: currentPanel,
);
} else {
setQuickEditPanel(null);
setActiveTool('select');
if (options.dialogId) {
updateCanvasGenerationDialogById(
options.dialogId,
(currentDialog) => ({
...currentDialog,
status: 'idle',
composerOpen: false,
generatedLayerId: nextLayer.id,
errorMessage: undefined,
}),
);
}
}
fitLayers([sourceLayer, nextLayer], { captureHistory: false });
},
[
addGeneratedLayersToCanvas,
fitLayers,
layerCounterRef,
selectSingleLayer,
setActiveSidebarPanel,
setActiveTool,
setQuickEditPanel,
updateCanvasGenerationDialogById,
hasCanvasGenerationDialogById,
],
);
const applyQuickEditResultToSourceLayer = useCallback(
(
generated: Parameters<
@@ -1507,15 +1430,11 @@ export function useImageCanvasGenerationSubmissionWorkflow({
return;
}
const panelMode = quickEditPanel.mode ?? 'quick-edit';
const quickEditResultTitle = resolveGenerationAssetLabel(
quickEditPanel.assetLabel,
`${quickEditSourceLayer.title} ${panelMode === 'redraw' ? '重绘' : '快速编辑'}`,
`${quickEditSourceLayer.title} 快速编辑`,
);
const quickEditReferences =
panelMode === 'redraw' || panelMode === 'quick-edit'
? []
: (quickEditPanel.quickEditReferences ?? []);
const quickEditReferences: CharacterReferenceImage[] = [];
const quickEditAspectRatio =
quickEditPanel.aspectRatio ??
inferEditorImageAspectRatio(
@@ -1542,13 +1461,9 @@ export function useImageCanvasGenerationSubmissionWorkflow({
imageSize: quickEditImageSize,
});
const quickEditSize = `${quickEditOutputSize.width}x${quickEditOutputSize.height}`;
const basePrompt =
quickEditPanel.prompt.trim() ||
(panelMode === 'redraw' ? '重绘图片' : '快速编辑图片');
const normalizedPrompt = panelMode === 'redraw' ? basePrompt : basePrompt;
const normalizedPrompt = quickEditPanel.prompt.trim() || '快速编辑图片';
let quickEditDialogId: string | undefined;
const shouldUseCanvasPlaceholder =
panelMode === 'quick-edit' &&
isVideoQuickEditSource(quickEditSourceLayer);
if (shouldUseCanvasPlaceholder) {
const quickEditDraft = createQuickEditGenerationDialogDraft({
@@ -1594,21 +1509,11 @@ export function useImageCanvasGenerationSubmissionWorkflow({
}
try {
const needsExtraReferenceImageSrcs =
panelMode === 'quick-edit' ||
panelMode === 'redraw' ||
isVideoQuickEditSource(quickEditSourceLayer);
const extraReferenceImageSrcs = needsExtraReferenceImageSrcs
? await Promise.all(
quickEditReferences.map((reference) =>
resolveEditorGenerationMediaReference(
reference,
'image',
projectId,
),
),
)
: [];
const extraReferenceImageSrcs = await Promise.all(
quickEditReferences.map((reference) =>
resolveEditorGenerationMediaReference(reference, 'image', projectId),
),
);
const quickEditFrame = quickEditDialogId
? getGeneratingDialogPlaceholder({
id: quickEditDialogId,
@@ -1625,10 +1530,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
originalWidth: quickEditOutputSize.width,
originalHeight: quickEditOutputSize.height,
});
if (
panelMode === 'quick-edit' &&
isVideoQuickEditSource(quickEditSourceLayer)
) {
if (isVideoQuickEditSource(quickEditSourceLayer)) {
const videoModel = DEFAULT_VIDEO_MODEL;
const videoResolution = '480p' as const;
const videoDurationSeconds = 4 as const;
@@ -1712,7 +1614,6 @@ export function useImageCanvasGenerationSubmissionWorkflow({
return;
}
const quickEditSelectionMarks =
panelMode === 'quick-edit' &&
quickEditSelectionState?.sourceLayerId === quickEditSourceLayer.id
? quickEditSelectionState.marks
: [];
@@ -1738,85 +1639,38 @@ export function useImageCanvasGenerationSubmissionWorkflow({
projectId,
);
}
const generationInputs =
panelMode === 'redraw'
? buildEditGenerationInputs(
'重绘提示词',
normalizedPrompt,
quickEditSourceLayer,
{
model: normalizedQuickEditModel,
aspectRatio: quickEditAspectRatio,
imageSize: quickEditImageSize,
},
)
: buildQuickEditGenerationInputs(
'快速编辑提示词',
normalizedPrompt,
quickEditSourceLayer,
quickEditReferences,
{
model: normalizedQuickEditModel,
aspectRatio: quickEditAspectRatio,
imageSize: quickEditImageSize,
},
);
const isCharacterRedraw =
panelMode === 'redraw' &&
quickEditSourceLayer.assetKind === 'character';
const generated =
panelMode === 'quick-edit'
? await runEditorGenerationWithWalletRefresh(
editEditorImage({
prompt: normalizedPrompt,
sourceImageSrc: sourceReferenceImageSrc,
size: quickEditSize,
model: normalizedQuickEditModel,
aspectRatio: quickEditAspectRatio,
imageSize: quickEditImageSize,
...(extraReferenceImageSrcs.length
? { referenceImageSrcs: extraReferenceImageSrcs }
: {}),
projectId,
assetKind: quickEditSourceLayer.assetKind,
generationInputs,
assetFolderId,
assetLabel: quickEditResultTitle,
sourceResourceId: quickEditSourceLayer.resourceId,
targetLayerId: quickEditSourceLayer.id,
}),
onWalletBalanceMayHaveChanged,
)
: await runEditorGenerationWithWalletRefresh(
generateEditorImage({
prompt: normalizedPrompt,
size: quickEditSize,
aspectRatio: quickEditAspectRatio,
imageSize: quickEditImageSize,
kind: isCharacterRedraw ? 'character' : 'quick-edit',
model: normalizedQuickEditModel,
referenceImageSrcs: [
...extraReferenceImageSrcs,
sourceReferenceImageSrc,
],
projectId,
assetKind: quickEditSourceLayer.assetKind,
generationInputs,
assetFolderId,
assetLabel: quickEditResultTitle,
sourceResourceId: quickEditSourceLayer.resourceId,
...(projectId && quickEditCanvasCompletionPlaceholder
? {
canvasCompletion: {
dialogId: quickEditDialogId,
title: quickEditResultTitle,
placeholder: quickEditCanvasCompletionPlaceholder,
},
}
: {}),
}),
onWalletBalanceMayHaveChanged,
);
const generationInputs = buildQuickEditGenerationInputs(
'快速编辑提示词',
normalizedPrompt,
quickEditSourceLayer,
quickEditReferences,
{
model: normalizedQuickEditModel,
aspectRatio: quickEditAspectRatio,
imageSize: quickEditImageSize,
},
);
const generated = await runEditorGenerationWithWalletRefresh(
editEditorImage({
prompt: normalizedPrompt,
sourceImageSrc: sourceReferenceImageSrc,
size: quickEditSize,
model: normalizedQuickEditModel,
aspectRatio: quickEditAspectRatio,
imageSize: quickEditImageSize,
...(extraReferenceImageSrcs.length
? { referenceImageSrcs: extraReferenceImageSrcs }
: {}),
projectId,
assetKind: quickEditSourceLayer.assetKind,
generationInputs,
assetFolderId,
assetLabel: quickEditResultTitle,
sourceResourceId: quickEditSourceLayer.resourceId,
targetLayerId: quickEditSourceLayer.id,
}),
onWalletBalanceMayHaveChanged,
);
notifyEditorGenerationWarning(
generated.warning?.reason,
onGenerationWarning,
@@ -1831,45 +1685,25 @@ export function useImageCanvasGenerationSubmissionWorkflow({
onGenerationWarning,
)
) {
if (panelMode === 'quick-edit') {
setQuickEditPanel(null);
setActiveTool('select');
setActiveSidebarPanel('layers');
}
setQuickEditPanel(null);
setActiveTool('select');
setActiveSidebarPanel('layers');
return;
}
if (generated.project && applyProjectSnapshot) {
applyProjectSnapshot(generated.project);
if (panelMode === 'quick-edit') {
setQuickEditPanel(null);
setActiveTool('select');
setActiveSidebarPanel('layers');
}
setQuickEditPanel(null);
setActiveTool('select');
setActiveSidebarPanel('layers');
return;
}
if (panelMode === 'quick-edit') {
applyQuickEditResultToSourceLayer(
generated,
quickEditSourceLayer,
generationInputs,
quickEditDialogId,
quickEditResultTitle,
);
} else {
addQuickEditResultLayer(
generated,
quickEditSourceLayer,
generationInputs,
panelMode,
quickEditDialogId
? {
frame: quickEditFrame,
dialogId: quickEditDialogId,
title: quickEditResultTitle,
}
: { title: quickEditResultTitle },
);
}
applyQuickEditResultToSourceLayer(
generated,
quickEditSourceLayer,
generationInputs,
quickEditDialogId,
quickEditResultTitle,
);
} catch (error) {
if (quickEditDialogId) {
updateCanvasGenerationDialogById(
@@ -1894,7 +1728,6 @@ export function useImageCanvasGenerationSubmissionWorkflow({
});
}
}, [
addQuickEditResultLayer,
addVideoResultLayer,
applyQuickEditResultToSourceLayer,
applyProjectSnapshot,
@@ -2121,7 +1954,6 @@ export function useImageCanvasGenerationSubmissionWorkflow({
generationInputs: submissionPlan.generationInputs,
});
} else if (submissionPlan.kind === 'quick-edit') {
const isRedrawAsNewArtifact = Boolean(dialog.isRedrawAsNewArtifact);
const quickEditSelectionMarks =
quickEditSelectionState?.sourceLayerId ===
submissionPlan.sourceLayer.id
@@ -2166,11 +1998,6 @@ export function useImageCanvasGenerationSubmissionWorkflow({
),
),
);
const quickEditPlaceholderSize =
getCanvasCompletionPlaceholderSizeFromPlan({
sourceLayer: submissionPlan.sourceLayer,
outputSize: submissionPlan.editInput.size,
});
const generated = await runEditorGenerationWithWalletRefresh(
editEditorImage({
prompt: submissionPlan.normalizedPrompt,
@@ -2185,25 +2012,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
assetFolderId,
assetLabel: submissionPlan.result.title,
sourceResourceId: submissionPlan.sourceLayer.resourceId,
...(isRedrawAsNewArtifact
? {
canvasCompletion: {
dialogId: canvasDialog?.id,
title: submissionPlan.result.title,
placeholder:
getGeneratingDialogPlaceholder(dialog) ??
buildRightSideCanvasCompletionPlaceholder(
submissionPlan.sourceLayer,
{
width: quickEditPlaceholderSize.width,
height: quickEditPlaceholderSize.height,
originalWidth: quickEditPlaceholderSize.width,
originalHeight: quickEditPlaceholderSize.height,
},
),
},
}
: { targetLayerId: submissionPlan.sourceLayer.id }),
targetLayerId: submissionPlan.sourceLayer.id,
}),
onWalletBalanceMayHaveChanged,
);
@@ -2225,21 +2034,13 @@ export function useImageCanvasGenerationSubmissionWorkflow({
}
return;
}
if (isRedrawAsNewArtifact) {
addGeneratedResultLayer(generated, {
sourceLayer: submissionPlan.sourceLayer,
title: submissionPlan.result.title,
generationInputs: submissionPlan.result.generationInputs,
});
} else {
applyQuickEditResultToSourceLayer(
generated,
submissionPlan.sourceLayer,
submissionPlan.result.generationInputs,
canvasDialog?.id,
submissionPlan.result.title,
);
}
applyQuickEditResultToSourceLayer(
generated,
submissionPlan.sourceLayer,
submissionPlan.result.generationInputs,
canvasDialog?.id,
submissionPlan.result.title,
);
} else if (submissionPlan.kind === 'icon-spec') {
const iconSpecInput = submissionPlan.input;
const canvasCompletionPlaceholder =
@@ -500,6 +500,14 @@ function GenerationWorkflowHarness({
)
.join('|')}
</span>
<span data-testid="layer-placements">
{layers
.map(
(layer) =>
`${layer.id}:${layer.x}:${layer.y}:${layer.width}:${layer.height}`,
)
.join('|')}
</span>
<span data-testid="dialog">
{activeDialog
? `${activeDialog.mode}:${activeDialog.status}:${activeDialog.composerOpen !== false ? 'open' : 'closed'}:${activeDialog.generatedLayerId ?? '-'}:${activeDialog.placeholder ? 'placeholder' : '-'}`
@@ -563,7 +571,7 @@ function GenerationWorkflowHarness({
</span>
<span data-testid="quick-edit-image-options">
{workflow.quickEditPanel
? `${workflow.quickEditPanel.mode ?? 'quick-edit'}:${workflow.quickEditPanel.model}:${workflow.quickEditPanel.aspectRatio ?? '-'}:${workflow.quickEditPanel.imageSize ?? '-'}`
? `quick-edit:${workflow.quickEditPanel.model}:${workflow.quickEditPanel.aspectRatio ?? '-'}:${workflow.quickEditPanel.imageSize ?? '-'}`
: '-'}
</span>
<span data-testid="crop-expand">
@@ -1563,14 +1571,14 @@ describe('useImageCanvasGenerationWorkflow', () => {
);
});
it('opens generated layer remodel with user input snapshot instead of internal prompt', () => {
it('opens legacy image remodel with user input snapshot in the default image tool', () => {
render(<GenerationWorkflowHarness />);
fireEvent.click(screen.getByRole('button', { name: '打开用户快照改造' }));
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
expect(screen.getByTestId('dialog').textContent).toBe(
'quick-edit:idle:open:-:placeholder',
'generate:idle:open:-:placeholder',
);
expect(screen.getByTestId('dialog-prompt').textContent).toBe(
'用户的视频描述',
@@ -1592,19 +1600,176 @@ describe('useImageCanvasGenerationWorkflow', () => {
);
});
it('opens image redraw as a new canvas generator next to the source layer', () => {
render(<GenerationWorkflowHarness />);
it('submits legacy image remodel without an origin action through the default image tool', async () => {
generateEditorImageMock.mockResolvedValueOnce(
createGenerated({ prompt: '旧版森林场景' }),
);
render(
<GenerationWorkflowHarness
initialLayers={[
createLayer({
sourceType: 'generated',
prompt: '【系统内置】旧版森林场景完整提示词',
model: IMAGE_MODEL_GPT_IMAGE_2,
generationInputs: {
fields: [{ title: '生成提示词', value: '旧版森林场景' }],
references: [],
},
}),
]}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '打开生成' }));
fireEvent.click(screen.getByRole('button', { name: '打开图片改造' }));
expect(screen.getByTestId('dialog').textContent).toBe(
'quick-edit:idle:open:-:placeholder',
'generate:idle:open:-:placeholder',
);
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
expect(screen.getByTestId('placeholder').textContent).toContain('1024:768');
const expectedResultPlacement =
screen.getByTestId('placeholder').textContent;
fireEvent.click(screen.getByRole('button', { name: '提交生成' }));
await waitFor(() => {
expect(generateEditorImageMock).toHaveBeenCalledWith(
expect.objectContaining({
prompt: '旧版森林场景',
model: IMAGE_MODEL_GPT_IMAGE_2,
generationInputs: expect.objectContaining({
version: 2,
action: 'image.generate',
references: [],
}),
}),
);
});
const request = generateEditorImageMock.mock.calls[0]?.[0];
expect(request).not.toHaveProperty('referenceImageSrcs');
expect(request).not.toHaveProperty('sourceResourceId');
await waitFor(() => {
expect(screen.getByTestId('layers').textContent).toContain(
'layer-generated-1:生成图片 1:-:-',
);
});
expect(screen.getByTestId('layer-placements').textContent).toContain(
`layer-generated-1:${expectedResultPlacement}`,
);
expect(screen.getByTestId('dialog').textContent).toBe(
'generate:idle:open:layer-generated-1:placeholder',
);
expect(screen.getByTestId('generation-dialogs').textContent).toBe(
'generation-dialog-1:generate:closed|generation-dialog-2:quick-edit:open',
'generation-dialog-1:generate:open',
);
});
it('submits legacy character remodel through the character tool selected by asset kind', async () => {
generateEditorImageMock.mockResolvedValueOnce(
createGenerated({ prompt: '红发骑士' }),
);
render(
<GenerationWorkflowHarness
initialLayers={[
createLayer({
title: '角色形象',
sourceType: 'generated',
assetKind: 'character',
prompt: '【系统内置】红发骑士完整提示词',
model: IMAGE_MODEL_GPT_IMAGE_2,
generationInputs: {
fields: [{ title: '角色设定', value: '红发骑士' }],
references: [],
},
}),
]}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '打开图片改造' }));
expect(screen.getByTestId('dialog').textContent).toBe(
'character:idle:open:-:placeholder',
);
expect(screen.getByTestId('dialog-prompt').textContent).toBe('红发骑士');
const expectedResultPlacement =
screen.getByTestId('placeholder').textContent;
fireEvent.click(screen.getByRole('button', { name: '选择角色规范' }));
fireEvent.click(screen.getByRole('button', { name: '提交生成' }));
await waitFor(() => {
expect(generateEditorImageMock).toHaveBeenCalledWith(
expect.objectContaining({
prompt: '红发骑士',
kind: 'character',
model: IMAGE_MODEL_GPT_IMAGE_2,
referenceImageSrcs: ['resource-character-spec'],
generationInputs: expect.objectContaining({
version: 2,
action: 'character.generate',
references: [
expect.objectContaining({
id: 'specReference',
refType: 'project-resource',
refId: 'resource-character-spec',
}),
],
}),
}),
);
});
await waitFor(() => {
expect(screen.getByTestId('layers').textContent).toContain(
'layer-generated-1:角色形象 1:-:character',
);
});
expect(screen.getByTestId('layer-placements').textContent).toContain(
`layer-generated-1:${expectedResultPlacement}`,
);
expect(screen.getByTestId('dialog').textContent).toBe(
'character:idle:open:layer-generated-1:placeholder',
);
expect(screen.getByTestId('generation-dialogs').textContent).toBe(
'generation-dialog-1:character:open',
);
});
it('rejects image.edit remodel even when the internal callback is invoked', () => {
render(
<GenerationWorkflowHarness
initialLayers={[
createLayer({
id: 'layer-edit-result',
resourceId: 'resource-edit-result',
sourceType: 'generated',
generationInputs: {
version: 2,
action: 'image.edit',
fields: [
{ id: 'prompt', title: '修改要求', value: '改成雨天' },
{ id: 'model', title: '模型', value: 'gpt-image-2' },
{ id: 'aspectRatio', title: '比例', value: '4:3' },
{ id: 'imageSize', title: '尺寸', value: '1K' },
],
references: [
{
id: 'source',
title: '原图',
refType: 'project-resource',
refId: 'resource-edit-source',
},
],
},
}),
]}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '打开图片改造' }));
expect(screen.getByTestId('dialog').textContent).toBe('-');
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
expect(screen.getByTestId('reference-pick-warning').textContent).toBe(
'快速编辑结果不支持改造,请继续使用快速编辑。',
);
});
@@ -5150,7 +5315,10 @@ describe('useImageCanvasGenerationWorkflow', () => {
fireEvent.click(screen.getByRole('button', { name: '打开图片改造' }));
expect(screen.getByTestId('dialog').textContent).not.toBe('-');
expect(screen.getByTestId('dialog').textContent).toBe(
'generate:idle:open:-:placeholder',
);
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
expect(screen.getByTestId('reference-pick-warning').textContent).toBe(
'已按旧版数据恢复,部分参数可能使用当前默认值。',
);
@@ -69,10 +69,10 @@ import {
createEditDialogDraft,
createGenerateDialogDraft,
createIconGenerationDialogDraft,
createLegacyImageRedrawGenerationDialogDraft,
createPublicationGenerationDialogDraft,
createQuickEditGenerationDialogDraft,
createQuickEditPanelDraft,
createRedrawPanelDraft,
createSameSourceGenerationDialogDraft,
createSceneGenerationDialogDraft,
createSoundEffectGenerationDialogDraft,
@@ -105,7 +105,6 @@ import {
normalizeEditorImageModel,
resizeGenerationPlaceholderToImageSelection,
resolveDialogExtraImageReferenceLimit,
resolveEditorImageGenerationPixelSize,
resolveExtraImageReferenceLimit,
UI_EXTRA_REFERENCE_LIMIT,
} from './ImageCanvasGenerationModel';
@@ -1084,14 +1083,15 @@ export function useImageCanvasGenerationWorkflow({
const refreshTaskList = useCallback(() => {
setTaskListRefreshKey((key) => key + 1);
}, []);
const backgroundMusicPromptAssist =
useImageCanvasBackgroundMusicPromptAssist({
const backgroundMusicPromptAssist = useImageCanvasBackgroundMusicPromptAssist(
{
canvasGenerationDialogs,
getCanvasGenerationDialogById,
updateCanvasGenerationDialogById,
currentUserId,
projectId,
});
},
);
const soundEffectPromptAssist = useImageCanvasSoundEffectPromptAssist({
canvasGenerationDialogs,
getCanvasGenerationDialogById,
@@ -1850,10 +1850,10 @@ export function useImageCanvasGenerationWorkflow({
);
const hasInvalidVersionedGenerationInputs = Boolean(
sourceLayer.generationInputsHydrationState === 'invalid-versioned' ||
sourceLayer.generationInputs &&
(sourceLayer.generationInputs.version !== undefined ||
sourceLayer.generationInputs.action !== undefined) &&
!decodedGenerationInputs.ok,
(sourceLayer.generationInputs &&
(sourceLayer.generationInputs.version !== undefined ||
sourceLayer.generationInputs.action !== undefined) &&
!decodedGenerationInputs.ok),
);
if (hasInvalidVersionedGenerationInputs) {
showGenerationWarning(
@@ -1865,6 +1865,10 @@ export function useImageCanvasGenerationWorkflow({
? decodedGenerationInputs.inputs
: null;
const isNormalizedGenerationInputs = normalizedGenerationInputs !== null;
if (normalizedGenerationInputs?.action === 'image.edit') {
showGenerationWarning('快速编辑结果不支持改造,请继续使用快速编辑。');
return;
}
const hasParameterFallbacks = decodedGenerationInputs.ok
? decodedGenerationInputs.warnings.length > 0
: false;
@@ -1994,9 +1998,7 @@ export function useImageCanvasGenerationWorkflow({
selectSingleLayer(sourceLayer.id);
openCanvasGenerationDialog(audioDraft);
setActiveTool('music');
showGenerationWarning(
'已按旧版数据恢复,部分参数可能使用当前默认值。',
);
showGenerationWarning('已按旧版数据恢复,部分参数可能使用当前默认值。');
return;
}
if (sourceLayer.mediaType === 'video') {
@@ -2008,9 +2010,7 @@ export function useImageCanvasGenerationWorkflow({
selectSingleLayer(sourceLayer.id);
openCanvasGenerationDialog(videoDraft);
setActiveTool('video');
showGenerationWarning(
'已按旧版数据恢复,部分参数可能使用当前默认值。',
);
showGenerationWarning('已按旧版数据恢复,部分参数可能使用当前默认值。');
return;
}
if (
@@ -2022,36 +2022,18 @@ export function useImageCanvasGenerationWorkflow({
showGenerationWarning('未找到角色动作关联的原角色图层,无法改造。');
return;
}
const redrawPanel = createRedrawPanelDraft(sourceLayer, {
imageModel: sourceDialog?.imageModel,
aspectRatio: sourceDialog?.aspectRatio,
imageSize: sourceDialog?.imageSize,
});
const redrawSize = resolveEditorImageGenerationPixelSize({
model: redrawPanel.model,
aspectRatio: redrawPanel.aspectRatio,
imageSize: redrawPanel.imageSize,
});
openPlacedCanvasGenerationDialog({
...createQuickEditGenerationDialogDraft({
openPlacedCanvasGenerationDialog(
createLegacyImageRedrawGenerationDialogDraft({
sourceLayer,
prompt: redrawPanel.prompt,
model: redrawPanel.model,
aspectRatio: redrawPanel.aspectRatio,
imageSize: redrawPanel.imageSize,
frame: {
width: redrawSize.width,
height: redrawSize.height,
},
canvasSize,
viewport,
sourceDialog,
}),
composerOpen: true,
});
);
setQuickEditPanel(null);
selectSingleLayer(null);
setActiveTool('generate');
showGenerationWarning(
'已按旧版数据恢复,部分参数可能使用当前默认值。',
);
showGenerationWarning('已按旧版数据恢复,部分参数可能使用当前默认值。');
return;
}
},