统一快速编辑/改造参数继承逻辑

- 快速编辑与改造打开时优先复用对应生成器最近一次 imageModel/aspectRatio/imageSize 参数。

- 统一创建草稿函数 createQuickEditPanelDraft / createRedrawPanelDraft / createEditDialogDraft 的参数注入与默认回退。

- 编辑生成弹窗价格与参数展示改为使用当前 dialog 的图片模型/尺寸参数。

- 调整工作流测试为当前统一提交链路并新增参数继承回归用例。
This commit is contained in:
2026-06-25 18:45:51 +08:00
parent 3c125ceb86
commit e24781e983
6 changed files with 180 additions and 38 deletions
@@ -15,6 +15,8 @@ function createDialog(
prompt: '旧修改提示',
status: 'idle',
sourceLayerId: 'layer-a',
imageModel: 'gpt-image-2',
imageSize: '1K',
...patch,
};
}
@@ -3,7 +3,6 @@ import { type Dispatch, type SetStateAction } from 'react';
import { UnifiedModal } from '../common/UnifiedModal';
import {
calculateEditorImageGenerationPrice,
IMAGE_MODEL_GPT_IMAGE_2,
} from './ImageCanvasGenerationModel';
import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView';
import type { GenerateDialogState } from './ImageCanvasEditorTypes';
@@ -20,10 +19,12 @@ export function ImageCanvasEditGenerationModalView({
onSubmit,
}: ImageCanvasEditGenerationModalViewProps) {
const isOpen = dialog?.mode === 'edit' && dialog.status !== 'generating';
const dialogImageModel = dialog?.imageModel ?? 'gpt-image-2';
const dialogImageSize = dialog?.imageSize ?? '1K';
const editPrice = calculateEditorImageGenerationPrice({
kind: 'quick-edit',
model: IMAGE_MODEL_GPT_IMAGE_2,
imageSize: '1K',
model: dialogImageModel,
imageSize: dialogImageSize,
});
return (
@@ -42,9 +43,9 @@ export function ImageCanvasEditGenerationModalView({
dialog={{
...dialog,
mode: 'edit',
imageModel: IMAGE_MODEL_GPT_IMAGE_2,
imageModel: dialogImageModel,
aspectRatio: dialog.aspectRatio ?? '1:1',
imageSize: dialog.imageSize ?? '1K',
imageSize: dialogImageSize,
generationReferences: [],
}}
style={{}}
@@ -381,6 +381,9 @@ describe('ImageCanvasGenerationDialogModel', () => {
status: 'idle',
composerOpen: true,
sourceLayerId: 'layer-source',
imageModel: 'gpt-image-2',
aspectRatio: '4:3',
imageSize: '1K',
});
expect(createQuickEditPanelDraft(sourceLayer)).toEqual({
mode: 'quick-edit',
@@ -633,26 +633,53 @@ export function createUiDesignGenerationDialogDraft({
export function createEditDialogDraft(
sourceLayer: CanvasLayer,
options: {
imageModel?: string;
aspectRatio?: string;
imageSize?: string;
} = {},
): GenerateDialogState {
const imageModel = normalizeEditorImageModel(
options.imageModel ?? sourceLayer.model,
);
const aspectRatio =
options.aspectRatio ??
inferEditorImageAspectRatio(sourceLayer.originalWidth, sourceLayer.originalHeight);
const imageSize =
options.imageSize ??
inferEditorImageSizeLabel(sourceLayer.originalWidth, sourceLayer.originalHeight);
return {
mode: 'edit',
prompt: resolveUserGenerationPromptSnapshot(sourceLayer),
status: 'idle',
composerOpen: true,
sourceLayerId: sourceLayer.id,
imageModel,
aspectRatio,
imageSize,
};
}
export function createQuickEditPanelDraft(
sourceLayer: CanvasLayer,
options: {
imageModel?: string;
aspectRatio?: string;
imageSize?: string;
} = {},
): QuickEditPanelState {
const aspectRatio = inferEditorImageAspectRatio(
sourceLayer.originalWidth,
sourceLayer.originalHeight,
);
const imageSize = inferEditorImageSizeLabel(
sourceLayer.originalWidth,
sourceLayer.originalHeight,
const aspectRatio = options.aspectRatio ??
inferEditorImageAspectRatio(
sourceLayer.originalWidth,
sourceLayer.originalHeight,
);
const imageSize = options.imageSize ??
inferEditorImageSizeLabel(
sourceLayer.originalWidth,
sourceLayer.originalHeight,
);
const model = normalizeEditorImageModel(
options.imageModel ?? sourceLayer.model,
);
return {
mode: 'quick-edit',
@@ -664,7 +691,7 @@ export function createQuickEditPanelDraft(
),
aspectRatio,
imageSize,
model: normalizeEditorImageModel(sourceLayer.model),
model,
quickEditReferences: [],
status: 'idle',
};
@@ -714,9 +741,14 @@ export function createQuickEditGenerationDialogDraft({
export function createRedrawPanelDraft(
sourceLayer: CanvasLayer,
options: {
imageModel?: string;
aspectRatio?: string;
imageSize?: string;
} = {},
): QuickEditPanelState {
return {
...createQuickEditPanelDraft(sourceLayer),
...createQuickEditPanelDraft(sourceLayer, options),
mode: 'redraw',
prompt: resolveUserGenerationPromptSnapshot(sourceLayer),
};
@@ -248,6 +248,23 @@ function GenerationWorkflowHarness({
<button type="button" onClick={workflow.openGenerateDialog}>
打开生成
</button>
<button
type="button"
onClick={() =>
dialogs.openCanvasGenerationDialog({
mode: 'generate',
prompt: '',
status: 'idle',
composerOpen: true,
sourceLayerId: 'layer-source',
imageModel: 'gpt-image-2',
aspectRatio: '2:3',
imageSize: '2K',
})
}
>
写入源图生成参数快照
</button>
<button type="button" onClick={workflow.openCharacterGenerationDialog}>
打开角色生成
</button>
@@ -405,6 +422,21 @@ function GenerationWorkflowHarness({
>
填写生成提示词
</button>
<button
type="button"
onClick={() =>
dialogs.setGenerateDialog((currentDialog) =>
currentDialog
? {
...currentDialog,
prompt: '快速修图',
}
: currentDialog,
)
}
>
填写快速编辑提示词
</button>
<button
type="button"
onClick={() =>
@@ -797,12 +829,12 @@ describe('useImageCanvasGenerationWorkflow', () => {
fireEvent.click(screen.getByRole('button', { name: '打开生成' }));
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
expect(screen.getByTestId('dialog').textContent).toBe('-');
expect(screen.getByTestId('quick-edit').textContent).toBe(
'layer-source:idle:-',
expect(screen.getByTestId('dialog').textContent).toBe(
'quick-edit:idle:open:-:placeholder',
);
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
expect(screen.getByTestId('generation-dialogs').textContent).toBe(
'generation-dialog-1:generate:closed',
'generation-dialog-1:generate:closed|generation-dialog-2:quick-edit:open',
);
});
@@ -822,6 +854,58 @@ describe('useImageCanvasGenerationWorkflow', () => {
);
});
it('keeps source image model and dimensions when opening quick edit', () => {
render(
<GenerationWorkflowHarness
initialLayers={[
{
...createLayer(),
originalWidth: 1537,
originalHeight: 1024,
},
]}
/>,
);
fireEvent.click(
screen.getByRole('button', { name: '写入源图生成参数快照' }),
);
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
expect(screen.getByTestId('dialog').textContent).toBe(
'quick-edit:idle:open:-:placeholder',
);
expect(screen.getByTestId('dialog-image-options').textContent).toBe(
'quick-edit:gpt-image-2:2:3:2K',
);
});
it('keeps target generator options when opening redraw', () => {
render(
<GenerationWorkflowHarness
initialLayers={[
{
...createLayer(),
originalWidth: 1537,
originalHeight: 1024,
},
]}
/>,
);
fireEvent.click(
screen.getByRole('button', { name: '写入源图生成参数快照' }),
);
fireEvent.click(screen.getByRole('button', { name: '打开图片改造' }));
expect(screen.getByTestId('dialog').textContent).toBe(
'quick-edit:idle:open:-:placeholder',
);
expect(screen.getByTestId('dialog-image-options').textContent).toBe(
'quick-edit:gpt-image-2:2:3:2K',
);
});
it('submits audio remodel with restored sound parameters and appends the new audio layer', async () => {
generateEditorSoundEffectMock.mockResolvedValueOnce({
audioSrc: 'data:audio/mpeg;base64,generated',
@@ -1207,9 +1291,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
expect(screen.getByTestId('quick-edit').textContent).toBe(
'layer-source:idle:-',
);
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
'layer-source:none:0',
);
@@ -1251,7 +1333,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
'layer-source:rect:1',
);
expect(screen.getByTestId('quick-edit').textContent).toBe(
'layer-source:idle:对1号红色圈选框里的内容做以下修改:',
'-',
);
fireEvent.click(
@@ -1263,7 +1345,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
'layer-source:ellipse:2',
);
expect(screen.getByTestId('quick-edit').textContent).toBe(
'layer-source:idle:对1号红色圈选框里的内容做以下修改:\n对2号红色圈选框里的内容做以下修改:',
'-',
);
});
@@ -1274,18 +1356,8 @@ describe('useImageCanvasGenerationWorkflow', () => {
render(<GenerationWorkflowHarness />);
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
await waitFor(() => {
expect(screen.getByTestId('quick-edit').textContent).toBe(
'layer-source:idle:-',
);
});
fireEvent.click(screen.getByRole('button', { name: '填写快速编辑' }));
await waitFor(() => {
expect(screen.getByTestId('quick-edit').textContent).toBe(
'layer-source:idle:快速修图',
);
});
fireEvent.click(screen.getByRole('button', { name: '提交快速编辑' }));
fireEvent.click(screen.getByRole('button', { name: '填写快速编辑提示词' }));
fireEvent.click(screen.getByRole('button', { name: '提交生成' }));
await waitFor(() => {
expect(generateEditorImageMock).toHaveBeenCalledWith({
@@ -1385,7 +1457,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
await waitFor(() => {
expect(screen.getByTestId('quick-edit').textContent).toBe(
'layer-source:idle:-',
'-',
);
});
fireEvent.click(screen.getByRole('button', { name: '打开修改状态' }));
@@ -895,6 +895,24 @@ export function useImageCanvasGenerationWorkflow({
],
);
const createQuickEditModeDraft = useCallback(
(sourceLayer: CanvasLayer) => {
const sourceDialog = [...canvasGenerationDialogs]
.reverse()
.find(
(dialog) =>
dialog.generatedLayerId === sourceLayer.id ||
(!dialog.generatedLayerId && dialog.sourceLayerId === sourceLayer.id),
);
return createQuickEditPanelDraft(sourceLayer, {
imageModel: sourceDialog?.imageModel,
aspectRatio: sourceDialog?.aspectRatio,
imageSize: sourceDialog?.imageSize,
});
},
[canvasGenerationDialogs],
);
const openQuickEditPanel = useCallback(
(sourceLayer: CanvasLayer) => {
setImageContextMenu(null);
@@ -902,7 +920,7 @@ export function useImageCanvasGenerationWorkflow({
setUiAssetExtractionState(null);
setCropExpandPanel(null);
setCharacterAnimationPanel(null);
const quickEditDraft = createQuickEditPanelDraft(sourceLayer);
const quickEditDraft = createQuickEditModeDraft(sourceLayer);
const quickEditSize = resolveEditorImageGenerationPixelSize({
model: quickEditDraft.model,
aspectRatio: quickEditDraft.aspectRatio,
@@ -936,6 +954,7 @@ export function useImageCanvasGenerationWorkflow({
},
[
canvasSize,
createQuickEditModeDraft,
openPlacedCanvasGenerationDialog,
selectSingleLayer,
setActiveTool,
@@ -1006,7 +1025,19 @@ export function useImageCanvasGenerationWorkflow({
sourceLayer.mediaType === undefined ||
sourceLayer.mediaType === 'image'
) {
const redrawPanel = createRedrawPanelDraft(sourceLayer);
const sourceDialog = [...canvasGenerationDialogs]
.reverse()
.find(
(dialog) =>
dialog.generatedLayerId === sourceLayer.id ||
(!dialog.generatedLayerId &&
dialog.sourceLayerId === sourceLayer.id),
);
const redrawPanel = createRedrawPanelDraft(sourceLayer, {
imageModel: sourceDialog?.imageModel,
aspectRatio: sourceDialog?.aspectRatio,
imageSize: sourceDialog?.imageSize,
});
const redrawSize = resolveEditorImageGenerationPixelSize({
model: redrawPanel.model,
aspectRatio: redrawPanel.aspectRatio,
@@ -1039,6 +1070,7 @@ export function useImageCanvasGenerationWorkflow({
setActiveTool,
setImageContextMenu,
setMetadataLayer,
canvasGenerationDialogs,
],
);