禁止快速编辑任务中继续框选

快速编辑进入生成中后移除框选工具和覆盖层
新增挂起请求回归测试覆盖任务开始后的锁定状态
更新图片画布快速编辑项目记忆与验证口径
This commit is contained in:
2026-07-16 22:23:29 +08:00
parent a0242e3547
commit ed1a972c9e
3 changed files with 56 additions and 8 deletions
@@ -380,9 +380,9 @@
- 现象:用户点击图片素材的“快速编辑”后,画布上额外出现 `Quick Edit Generator` 占位,像是新建了一个生成器;但用户预期是在原图下方框选区域、填写一个提示词和模型,然后直接修改当前图。
- 原因:快速编辑入口和提交链路误用了 `createQuickEditGenerationDialogDraft(...)` / `CanvasGenerationDialogState`,把“覆盖源图”的快速编辑伪装成会产出新图层的生成器占位。
- 处理:图片快速编辑必须走 `QuickEditPanelState`,打开时归档当前 active generation dialog 但不创建新的 `mode="quick-edit"` dialog;提交时调用 `/api/editor/images/edits`,把当前图片或带编号标注的图片作为 `sourceImageSrc`,成功后覆盖源图,失败时保留快速编辑面板。图片重绘、去背景、视频快速编辑等会产出新图层或异步占位的入口仍可走 generation dialog / placement 链路。
- 验证:`npm run test -- src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx src/components/image-editor/ImageCanvasQuickEditPanelView.test.tsx -- --runInBand`,以及按需运行 `npm run test -- src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx -t "快速编辑|quick edit" -- --runInBand`。
- 关联:`src/components/image-editor/useImageCanvasGenerationWorkflow.ts`、`src/components/image-editor/ImageCanvasGenerationSubmissionModel.ts`、`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts`、`src/services/image-editor/editorImageReference.ts`。
- 处理:图片快速编辑必须走 `QuickEditPanelState`,打开时归档当前 active generation dialog 但不创建新的 `mode="quick-edit"` dialog;提交时调用 `/api/editor/images/edits`,把当前图片或带编号标注的图片作为 `sourceImageSrc`,成功后覆盖源图,失败时保留快速编辑面板。快速编辑任务进入 `generating` 后必须移除框选工具和覆盖层,禁止继续新增框选;失败恢复面板后可继续调整框选再重试。图片重绘、去背景、视频快速编辑等会产出新图层或异步占位的入口仍可走 generation dialog / placement 链路。
- 验证:`npm run test -- src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx src/components/image-editor/ImageCanvasQuickEditPanelView.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx -- --runInBand`,以及按需运行 `npm run test -- src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx -t "快速编辑|quick edit" -- --runInBand`。
- 关联:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/components/image-editor/useImageCanvasGenerationWorkflow.ts`、`src/components/image-editor/ImageCanvasGenerationSubmissionModel.ts`、`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts`、`src/services/image-editor/editorImageReference.ts`。
## 图片画布快速编辑完成必须按目标图层回写
@@ -788,6 +788,53 @@ describe('ImageCanvasEditorView', () => {
expect(resumedPrompt.value).not.toContain('2号红色圈选框');
});
it('locks quick edit selection tools after the edit task starts', async () => {
editEditorImageMock.mockReturnValueOnce(new Promise(() => {}));
render(<ImageCanvasEditorView />);
const sourceLayer = (
await screen.findByAltText('画布图片:拼图素材')
).closest('button')!;
dispatchPointerEvent(sourceLayer, 'pointerdown', {
button: 0,
pointerId: 74,
clientX: 120,
clientY: 120,
});
fireEvent.click(screen.getByRole('button', { name: '快速编辑' }));
const quickEditDialog = screen.getByRole('dialog', {
name: '快速编辑图片',
});
fireEvent.change(within(quickEditDialog).getByLabelText('快速编辑提示词'), {
target: { value: '快速修图' },
});
fireEvent.click(
within(
screen.getByRole('toolbar', { name: '快速编辑框选工具' }),
).getByRole('button', { name: '矩形框选' }),
);
fireEvent.click(
within(quickEditDialog).getByRole('button', { name: '修改' }),
);
await waitFor(() => {
expect(editEditorImageMock).toHaveBeenCalledTimes(1);
});
expect(
screen.queryByRole('toolbar', { name: '快速编辑框选工具' }),
).toBeNull();
expect(
screen.queryByRole('application', { name: 'UI素材框选画布' }),
).toBeNull();
const submitButton = within(quickEditDialog).getByRole('button', {
name: '修改',
});
expect((submitButton as HTMLButtonElement).disabled).toBe(true);
expect(submitButton.textContent).toBe('生成中');
});
it('opens the login modal immediately when entering the editor while logged out', async () => {
const openLoginModal = vi.fn();
@@ -820,11 +820,12 @@ export function useImageCanvasGenerationWorkflow({
(layer) => layer.id === uiAssetExtractionState.sourceLayerId,
) ?? null)
: null;
const quickEditSelectionSourceLayer = quickEditSelectionState
? (layers.find(
(layer) => layer.id === quickEditSelectionState.sourceLayerId,
) ?? null)
: null;
const quickEditSelectionSourceLayer =
quickEditPanel?.status !== 'generating' && quickEditSelectionState
? (layers.find(
(layer) => layer.id === quickEditSelectionState.sourceLayerId,
) ?? null)
: null;
const characterAnimationSourceLayer = effectiveCharacterAnimationPanel
? (layers.find(
(layer) => layer.id === effectiveCharacterAnimationPanel.sourceLayerId,