From e990a5523ec23cd1fb60681dff0f89b2662f8614 Mon Sep 17 00:00:00 2001 From: akumatus Date: Thu, 6 Mar 2025 03:19:02 +0000 Subject: [PATCH] fix(core): can not clear chat-panel history (#10634) Close [BS-2754](https://linear.app/affine-design/issue/BS-2754). ### What Changed? Use the latest session id and display the corresponding historical messages. --- .../core/src/blocksuite/ai/actions/types.ts | 3 +- .../src/blocksuite/ai/chat-panel/index.ts | 35 ++++---- .../affine-cloud-copilot/e2e/copilot.spec.ts | 86 +++++++++++++++++-- 3 files changed, 98 insertions(+), 26 deletions(-) diff --git a/packages/frontend/core/src/blocksuite/ai/actions/types.ts b/packages/frontend/core/src/blocksuite/ai/actions/types.ts index 462bc13b9..b8c7caace 100644 --- a/packages/frontend/core/src/blocksuite/ai/actions/types.ts +++ b/packages/frontend/core/src/blocksuite/ai/actions/types.ts @@ -2,6 +2,7 @@ import type { ChatHistoryOrder, CopilotContextDoc, CopilotContextFile, + CopilotSessionType, getCopilotHistoriesQuery, RequestOptions, } from '@affine/graphql'; @@ -305,7 +306,7 @@ declare global { workspaceId: string, docId?: string, options?: { action?: boolean } - ) => Promise<{ id: string; promptName: string }[] | undefined>; + ) => Promise; updateSession: (sessionId: string, promptName: string) => Promise; } diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts index 70a4ecc85..93d1a0907 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts @@ -29,7 +29,6 @@ import type { DocSearchMenuConfig, } from './chat-config'; import type { - ChatAction, ChatContextValue, ChatItem, DocChip, @@ -150,9 +149,10 @@ export class ChatPanel extends SignalWatcher( const items: ChatItem[] = actions ? [...actions] : []; - if (histories?.at(-1)) { - const history = histories.at(-1); - if (!history) return; + const history = histories?.find( + history => history.sessionId === this._chatSessionId + ); + if (history) { items.push(...history.messages); AIProvider.LAST_ROOT_SESSION_ID = history.sessionId; } @@ -286,13 +286,12 @@ export class ChatPanel extends SignalWatcher( cancelText: 'Cancel', }) ) { + const actionIds = this.chatContextValue.items + .filter(item => 'sessionId' in item) + .map(item => item.sessionId); await AIProvider.histories?.cleanup(this.doc.workspace.id, this.doc.id, [ - this._chatSessionId ?? '', - ...( - this.chatContextValue.items.filter( - item => 'sessionId' in item - ) as ChatAction[] - ).map(item => item.sessionId), + ...(this._chatSessionId ? [this._chatSessionId] : []), + ...(actionIds || []), ]); notification.toast('History cleared'); await this._updateHistory(); @@ -308,12 +307,16 @@ export class ChatPanel extends SignalWatcher( if (!userId) return; this.isLoading = true; - const sessions = await AIProvider.session?.getSessions( - this.doc.workspace.id, - this.doc.id - ); - if (sessions?.length) { - this._chatSessionId = sessions?.[0].id; + const sessions = ( + (await AIProvider.session?.getSessions( + this.doc.workspace.id, + this.doc.id, + { action: false } + )) || [] + ).filter(session => !session.parentSessionId); + + if (sessions && sessions.length) { + this._chatSessionId = sessions.at(-1)?.id; await this._updateHistory(); } this.isLoading = false; diff --git a/tests/affine-cloud-copilot/e2e/copilot.spec.ts b/tests/affine-cloud-copilot/e2e/copilot.spec.ts index 60e083519..ea770eb5f 100644 --- a/tests/affine-cloud-copilot/e2e/copilot.spec.ts +++ b/tests/affine-cloud-copilot/e2e/copilot.spec.ts @@ -103,6 +103,19 @@ const clearChat = async (page: Page) => { await page.waitForTimeout(500); }; +const collectHistory = async (page: Page) => { + const chatPanel = await page.waitForSelector('.chat-panel-messages'); + return Promise.all( + Array.from(await chatPanel.$$('.message')).map(async m => ({ + name: await m.$('.user-info').then(i => i?.innerText()), + content: await m + .$('chat-text') + .then(t => t?.$('editor-host')) + .then(e => e?.innerText()), + })) + ); +}; + const collectChat = async (page: Page) => { await page.waitForTimeout(ONE_SECOND); const chatPanel = await page.waitForSelector('.chat-panel-messages'); @@ -117,15 +130,7 @@ const collectChat = async (page: Page) => { const lastMessage = await chatPanel.$$('.message').then(m => m[m.length - 1]); await lastMessage.waitForSelector('chat-copy-more'); await page.waitForTimeout(200); - return Promise.all( - Array.from(await chatPanel.$$('.message')).map(async m => ({ - name: await m.$('.user-info').then(i => i?.innerText()), - content: await m - .$('chat-text') - .then(t => t?.$('editor-host')) - .then(e => e?.innerText()), - })) - ); + return collectHistory(page); }; const focusToEditor = async (page: Page) => { @@ -372,6 +377,18 @@ test.describe('chat panel', () => { ).toStrictEqual(contents); }); + test('can save chat to block and clear history', async ({ page }) => { + await collectChat(page); + expect(await getPageMode(page)).toBe('page'); + await page.getByTestId('action-save-chat-to-block').click(); + await page.waitForSelector('affine-edgeless-ai-chat'); + + await page.reload(); + await page.waitForTimeout(200); + await clearChat(page); + expect((await collectChat(page)).length).toBe(0); + }); + test('chat in center peek', async ({ page }) => { const contents = (await collectChat(page)).map(m => m.content); await page.getByTestId('action-save-chat-to-block').click(); @@ -678,6 +695,7 @@ test.describe('chat with block', () => { await createLocalWorkspace({ name: 'test' }, page); await clickNewPageButton(page); await pasteTextToPageEditor(page, 'Mac Mini'); + await openChat(page); }); test.beforeEach(async ({ page }) => { @@ -750,6 +768,19 @@ test.describe('chat with block', () => { } else { expect(await collectTextAnswer(page)).toBeTruthy(); } + // TODO some actions do not have history yet + if ( + option !== 'Generate presentation' && + option !== 'Brainstorm ideas with mind map' + ) { + const history = await collectHistory(page); + expect(history.length).toBe(1); + expect(history[0].name).toBe('AFFiNE AI'); + const discard = await page.waitForSelector('.ai-item-discard'); + await discard.click(); + await clearChat(page); + expect((await collectHistory(page)).length).toBe(0); + } }); } }); @@ -887,6 +918,43 @@ test.describe('chat with block', () => { } }); }); + + test('clear history', async ({ page }) => { + await page.reload(); + await clickSideBarAllPageButton(page); + await page.waitForTimeout(200); + await createLocalWorkspace({ name: 'test' }, page); + await clickNewPageButton(page); + await focusToEditor(page); + await page.keyboard.type('Mac Mini'); + await openChat(page); + + await makeChat(page, 'hello'); + await collectHistory(page); + + await page.waitForSelector('affine-paragraph').then(i => i.click()); + await page.keyboard.press('ControlOrMeta+A'); + await page + .waitForSelector('page-editor editor-toolbar ask-ai-icon', { + state: 'attached', + timeout: 10000, + }) + .then(b => b.click()); + await disableEditorBlank(page); + await page + .waitForSelector( + `.ai-item-${`Fix spelling`.replaceAll(' ', '-').toLowerCase()}` + ) + .then(i => i.click()); + await collectTextAnswer(page); + + await page.reload(); + await page.waitForTimeout(1000); + const history = await collectHistory(page); + expect(history.length).toBe(3); + await clearChat(page); + expect((await collectHistory(page)).length).toBe(0); + }); }); test.describe('chat with doc', () => {