From f737327f12df0fc16f1f4e77bd064e8505f67f67 Mon Sep 17 00:00:00 2001 From: yoyoyohamapi <8338436+yoyoyohamapi@users.noreply.github.com> Date: Wed, 14 May 2025 03:16:55 +0000 Subject: [PATCH] feat(core): show stop modal if clickoutside during generating (#12227) ### TL;DR feat: show stop model if click-outside during ai generating >CLOSE AI-89 ## Summary by CodeRabbit - **New Features** - Added a confirmation dialog when attempting to stop AI content generation by clicking outside the panel, ensuring users can confirm or cancel the stop action. - **Tests** - Introduced an end-to-end test to verify the confirmation dialog appears and AI generation stops as expected when clicking outside during generation. --- .../ai/widgets/ai-panel/ai-panel.ts | 34 ++++++++++++++++++- .../e2e/chat-with/text.spec.ts | 15 ++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts index a9568dbd07..cab8ba7938 100644 --- a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts +++ b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts @@ -113,7 +113,11 @@ export class AffineAIPanelWidget extends WidgetComponent { }; private readonly _clickOutside = () => { - this._discardWithConfirmation(); + if (this.state === 'generating') { + this._stopWithConfirmation(); + } else { + this._discardWithConfirmation(); + } }; private _discardModalAbort: AbortController | null = null; @@ -169,6 +173,16 @@ export class AffineAIPanelWidget extends WidgetComponent { ctx: unknown = null; + private readonly _stopWithConfirmation = () => { + this.showStopModal() + .then(stop => { + if (stop) { + this.stopGenerating(); + } + }) + .catch(console.error); + }; + private readonly _discardWithConfirmation = () => { if (this.state === 'hidden') { return; @@ -275,6 +289,24 @@ export class AffineAIPanelWidget extends WidgetComponent { this._autoUpdatePosition(reference); }; + showStopModal = () => { + const notification = this.host.std.getOptional(NotificationProvider); + if (!notification) { + return Promise.resolve(true); + } + this._clearDiscardModal(); + this._discardModalAbort = new AbortController(); + return notification + .confirm({ + title: 'Stop generating', + message: 'AI is generating content. Do you want to stop generating?', + cancelText: 'Cancel', + confirmText: 'Stop', + abort: this._abortController.signal, + }) + .finally(() => (this._discardModalAbort = null)); + }; + showDiscardModal = () => { const notification = this.host.std.getOptional(NotificationProvider); if (!notification) { diff --git a/tests/affine-cloud-copilot/e2e/chat-with/text.spec.ts b/tests/affine-cloud-copilot/e2e/chat-with/text.spec.ts index e261323063..fdb78db5bd 100644 --- a/tests/affine-cloud-copilot/e2e/chat-with/text.spec.ts +++ b/tests/affine-cloud-copilot/e2e/chat-with/text.spec.ts @@ -21,6 +21,21 @@ test.describe('AIChatWith/Text', () => { await expect(page.getByTestId('ai-generating')).not.toBeVisible(); }); + test('should support stop generating when click outside', async ({ + loggedInPage: page, + utils, + }) => { + await utils.editor.askAIWithText(page, 'Panda'); + await page.getByTestId('action-generate-image').click(); + await expect(page.getByTestId('ai-generating')).toBeVisible(); + await page.mouse.click(0, 0); + await expect( + page.getByText('AI is generating content. Do you want to stop generating') + ).toBeVisible(); + await page.getByTestId('confirm-modal-confirm').click(); + await expect(page.getByTestId('ai-generating')).not.toBeVisible(); + }); + test('should support copy answer', async ({ loggedInPage: page, utils }) => { const { translate } = await utils.editor.askAIWithText(page, 'Apple'); const { answer } = await translate('German');