diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index a74189419..4dc46c268 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -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`。 ## 图片画布快速编辑完成必须按目标图层回写 diff --git a/src/components/image-editor/ImageCanvasEditorView.test.tsx b/src/components/image-editor/ImageCanvasEditorView.test.tsx index 9ac688e74..076f94df8 100644 --- a/src/components/image-editor/ImageCanvasEditorView.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.test.tsx @@ -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(); + + 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(); diff --git a/src/components/image-editor/useImageCanvasGenerationWorkflow.ts b/src/components/image-editor/useImageCanvasGenerationWorkflow.ts index e0c37d02e..c36ddb4d2 100644 --- a/src/components/image-editor/useImageCanvasGenerationWorkflow.ts +++ b/src/components/image-editor/useImageCanvasGenerationWorkflow.ts @@ -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,