From a5b1720c713a8b09465a708c53e71e1646177403 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 25 Sep 2025 17:30:37 +0200 Subject: [PATCH 1/4] chore(compass-assistant): hide clear chat button when messages are empty COMPASS-9788 --- .../src/compass-assistant-drawer.tsx | 82 +++++++++++++------ .../src/compass-assistant-provider.spec.tsx | 50 +++++++++++ .../src/compass-assistant-provider.tsx | 11 +-- 3 files changed, 107 insertions(+), 36 deletions(-) diff --git a/packages/compass-assistant/src/compass-assistant-drawer.tsx b/packages/compass-assistant/src/compass-assistant-drawer.tsx index fe0e0f0e107..0251366cc7a 100644 --- a/packages/compass-assistant/src/compass-assistant-drawer.tsx +++ b/packages/compass-assistant/src/compass-assistant-drawer.tsx @@ -7,17 +7,20 @@ import { IconButton, showConfirmation, spacing, + Tooltip, } from '@mongodb-js/compass-components'; import { AssistantChat } from './components/assistant-chat'; import { ASSISTANT_DRAWER_ID, - AssistantActionsContext, AssistantContext, + type AssistantMessage, } from './compass-assistant-provider'; import { useIsAIFeatureEnabled, usePreference, } from 'compass-preferences-model/provider'; +import { useChat } from './@ai-sdk/react/use-chat'; +import type { Chat } from './@ai-sdk/react/chat-react'; const assistantTitleStyles = css({ display: 'flex', @@ -54,25 +57,10 @@ export const CompassAssistantDrawer: React.FunctionComponent<{ hasNonGenuineConnections?: boolean; }> = ({ appName, autoOpen, hasNonGenuineConnections = false }) => { const chat = useContext(AssistantContext); - const { clearChat } = useContext(AssistantActionsContext); const enableAIAssistant = usePreference('enableAIAssistant'); const isAiFeatureEnabled = useIsAIFeatureEnabled(); - const handleClearChat = useCallback(async () => { - const confirmed = await showConfirmation({ - title: 'Clear this chat?', - description: - 'The current chat will be cleared, and chat history will not be retrievable.', - buttonText: 'Clear chat', - variant: 'danger', - 'data-testid': 'assistant-confirm-clear-chat-modal', - }); - if (confirmed) { - await clearChat?.(); - } - }, [clearChat]); - if (!enableAIAssistant || !isAiFeatureEnabled) { return null; } @@ -92,16 +80,7 @@ export const CompassAssistantDrawer: React.FunctionComponent<{ MongoDB Assistant Preview - { - void handleClearChat(); - }} - title="Clear chat" - data-testid="assistant-clear-chat" - > - - + } label="MongoDB Assistant" @@ -123,3 +102,54 @@ export const CompassAssistantDrawer: React.FunctionComponent<{ ); }; + +export const ClearChatButton: React.FunctionComponent<{ + chat: Chat; +}> = ({ chat }) => { + const { clearError, stop } = useChat({ chat }); + + const handleClearChat = useCallback(async () => { + const confirmed = await showConfirmation({ + title: 'Clear this chat?', + description: + 'The current chat will be cleared, and chat history will not be retrievable.', + buttonText: 'Clear chat', + variant: 'danger', + 'data-testid': 'assistant-confirm-clear-chat-modal', + }); + if (confirmed) { + await stop(); + clearError(); + chat.messages = chat.messages.filter( + (message) => message.metadata?.isPermanent + ); + } + }, [stop, clearError, chat]); + + const isChatEmpty = + chat.messages.filter((message) => !message.metadata?.isPermanent).length === + 0; + + if (isChatEmpty) { + return null; + } + + return ( + { + void handleClearChat(); + }} + title="Clear chat" + data-testid="assistant-clear-chat" + > + + + } + > + Clear chat + + ); +}; diff --git a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx index 0e1230d88cd..5eae6f261ed 100644 --- a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx +++ b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx @@ -485,6 +485,56 @@ describe('CompassAssistantProvider', function () { }); describe('clear chat button', function () { + it('is hidden when the chat is empty', async function () { + const mockChat = createMockChat({ messages: [] }); + await renderOpenAssistantDrawer({ chat: mockChat }); + expect(screen.queryByTestId('assistant-clear-chat')).to.not.exist; + }); + + it('is hidden when the chat has only permanent messages', async function () { + const mockChat = createMockChat({ + messages: mockMessages.map((message) => ({ + ...message, + metadata: { isPermanent: true }, + })), + }); + await renderOpenAssistantDrawer({ chat: mockChat }); + expect(screen.queryByTestId('assistant-clear-chat')).to.not.exist; + }); + + it('is visible when the chat has messages', async function () { + const mockChat = createMockChat({ messages: mockMessages }); + await renderOpenAssistantDrawer({ chat: mockChat }); + expect(screen.getByTestId('assistant-clear-chat')).to.exist; + }); + + it('appears after a message is sent', async function () { + const mockChat = new Chat({ + messages: [], + transport: { + sendMessages: sinon.stub().returns( + new Promise(() => { + return new ReadableStream({}); + }) + ), + reconnectToStream: sinon.stub(), + }, + }); + await renderOpenAssistantDrawer({ chat: mockChat }); + + expect(screen.queryByTestId('assistant-clear-chat')).to.not.exist; + + userEvent.type( + screen.getByPlaceholderText('Ask a question'), + 'Hello assistant' + ); + userEvent.click(screen.getByLabelText('Send message')); + + await waitFor(() => { + expect(screen.getByTestId('assistant-clear-chat')).to.exist; + }); + }); + it('clears the chat when the user clicks and confirms', async function () { const mockChat = createMockChat({ messages: mockMessages }); diff --git a/packages/compass-assistant/src/compass-assistant-provider.tsx b/packages/compass-assistant/src/compass-assistant-provider.tsx index 2f17904e6e0..e6fa73619ba 100644 --- a/packages/compass-assistant/src/compass-assistant-provider.tsx +++ b/packages/compass-assistant/src/compass-assistant-provider.tsx @@ -77,7 +77,6 @@ type AssistantActionsContextType = { connectionInfo: ConnectionInfo; error: Error; }) => void; - clearChat?: () => Promise; tellMoreAboutInsight?: (context: ProactiveInsightsContext) => void; ensureOptInAndSend?: ( message: SendMessage, @@ -88,7 +87,7 @@ type AssistantActionsContextType = { type AssistantActionsType = Omit< AssistantActionsContextType, - 'ensureOptInAndSend' | 'clearChat' + 'ensureOptInAndSend' > & { getIsAssistantEnabled: () => boolean; }; @@ -98,7 +97,6 @@ export const AssistantActionsContext = interpretExplainPlan: () => {}, interpretConnectionError: () => {}, tellMoreAboutInsight: () => {}, - clearChat: async () => {}, ensureOptInAndSend: async () => {}, }); @@ -215,13 +213,6 @@ export const AssistantProvider: React.FunctionComponent< 'performance insights', buildProactiveInsightsPrompt ), - clearChat: async () => { - await chat.stop(); - chat.clearError(); - chat.messages = chat.messages.filter( - (message) => message.metadata?.isPermanent - ); - }, ensureOptInAndSend: async ( message: SendMessage, options: SendOptions, From 549ea4c1339c5864207210b251534dc61b55e229 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 25 Sep 2025 17:37:54 +0200 Subject: [PATCH 2/4] chore: hide aria-label --- packages/compass-assistant/src/compass-assistant-drawer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/compass-assistant/src/compass-assistant-drawer.tsx b/packages/compass-assistant/src/compass-assistant-drawer.tsx index 0251366cc7a..a895ec5e00a 100644 --- a/packages/compass-assistant/src/compass-assistant-drawer.tsx +++ b/packages/compass-assistant/src/compass-assistant-drawer.tsx @@ -138,11 +138,12 @@ export const ClearChatButton: React.FunctionComponent<{ { void handleClearChat(); }} title="Clear chat" + aria-label="Clear chat" + aria-hidden={true} data-testid="assistant-clear-chat" > From 5ec82fd26e74b8a3fc4162e07b3c477e732daf63 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 25 Sep 2025 17:50:42 +0200 Subject: [PATCH 3/4] fix: use app name for request origin --- .../compass-assistant/src/compass-assistant-provider.tsx | 1 + packages/compass-assistant/src/docs-provider-transport.ts | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/packages/compass-assistant/src/compass-assistant-provider.tsx b/packages/compass-assistant/src/compass-assistant-provider.tsx index e6fa73619ba..d963877134f 100644 --- a/packages/compass-assistant/src/compass-assistant-provider.tsx +++ b/packages/compass-assistant/src/compass-assistant-provider.tsx @@ -280,6 +280,7 @@ export const CompassAssistantProvider = registerCompassPlugin( initialProps.chat ?? new Chat({ transport: new DocsProviderTransport({ + appName: initialProps.appNameForPrompt, instructions: buildConversationInstructionsPrompt({ target: initialProps.appNameForPrompt, }), diff --git a/packages/compass-assistant/src/docs-provider-transport.ts b/packages/compass-assistant/src/docs-provider-transport.ts index 9a7ae78200e..6a5849ae17c 100644 --- a/packages/compass-assistant/src/docs-provider-transport.ts +++ b/packages/compass-assistant/src/docs-provider-transport.ts @@ -17,17 +17,21 @@ export function shouldExcludeMessage({ metadata }: AssistantMessage) { export class DocsProviderTransport implements ChatTransport { private model: LanguageModel; + private appName: string; private instructions: string; constructor({ instructions, model, + appName, }: { instructions: string; model: LanguageModel; + appName: string; }) { this.instructions = instructions; this.model = model; + this.appName = appName; } static emptyStream = new ReadableStream({ @@ -60,6 +64,9 @@ export class DocsProviderTransport implements ChatTransport { model: this.model, messages: convertToModelMessages(filteredMessages), abortSignal: abortSignal, + headers: { + 'X-Request-Origin': this.appName, + }, providerOptions: { openai: { instructions: this.instructions, From 13619e266a4e41f213c226f72c64c9cec122ef72 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Fri, 26 Sep 2025 11:32:03 +0100 Subject: [PATCH 4/4] use the correct / agreed upon X-Request-Origin header --- .../src/compass-assistant-provider.spec.tsx | 17 ++++++++++++++--- .../src/compass-assistant-provider.tsx | 3 ++- .../src/docs-provider-transport.spec.ts | 1 + .../src/docs-provider-transport.ts | 10 +++++----- packages/compass-web/src/entrypoint.tsx | 1 + packages/compass/src/app/components/home.tsx | 1 + 6 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx index 5eae6f261ed..9f12549c284 100644 --- a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx +++ b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx @@ -85,7 +85,11 @@ const TestComponent: React.FunctionComponent<{ return ( - +
Provider children
- + {children}
@@ -659,7 +667,10 @@ describe('CompassAssistantProvider', function () { render( - + , { preferences: { diff --git a/packages/compass-assistant/src/compass-assistant-provider.tsx b/packages/compass-assistant/src/compass-assistant-provider.tsx index d963877134f..a702e023718 100644 --- a/packages/compass-assistant/src/compass-assistant-provider.tsx +++ b/packages/compass-assistant/src/compass-assistant-provider.tsx @@ -256,6 +256,7 @@ export const CompassAssistantProvider = registerCompassPlugin( children, }: PropsWithChildren<{ appNameForPrompt: string; + originForPrompt: string; chat?: Chat; atlasAiService?: AtlasAiService; }>) => { @@ -280,7 +281,7 @@ export const CompassAssistantProvider = registerCompassPlugin( initialProps.chat ?? new Chat({ transport: new DocsProviderTransport({ - appName: initialProps.appNameForPrompt, + origin: initialProps.originForPrompt, instructions: buildConversationInstructionsPrompt({ target: initialProps.appNameForPrompt, }), diff --git a/packages/compass-assistant/src/docs-provider-transport.spec.ts b/packages/compass-assistant/src/docs-provider-transport.spec.ts index 2143d429971..ed570d6d3be 100644 --- a/packages/compass-assistant/src/docs-provider-transport.spec.ts +++ b/packages/compass-assistant/src/docs-provider-transport.spec.ts @@ -62,6 +62,7 @@ describe('DocsProviderTransport', function () { }); abortController = new AbortController(); transport = new DocsProviderTransport({ + origin: 'mongodb-compass', instructions: 'Test instructions for MongoDB assistance', model: mockModel, }); diff --git a/packages/compass-assistant/src/docs-provider-transport.ts b/packages/compass-assistant/src/docs-provider-transport.ts index 6a5849ae17c..36e541c4b66 100644 --- a/packages/compass-assistant/src/docs-provider-transport.ts +++ b/packages/compass-assistant/src/docs-provider-transport.ts @@ -17,21 +17,21 @@ export function shouldExcludeMessage({ metadata }: AssistantMessage) { export class DocsProviderTransport implements ChatTransport { private model: LanguageModel; - private appName: string; + private origin: string; private instructions: string; constructor({ instructions, model, - appName, + origin, }: { instructions: string; model: LanguageModel; - appName: string; + origin: string; }) { this.instructions = instructions; this.model = model; - this.appName = appName; + this.origin = origin; } static emptyStream = new ReadableStream({ @@ -65,7 +65,7 @@ export class DocsProviderTransport implements ChatTransport { messages: convertToModelMessages(filteredMessages), abortSignal: abortSignal, headers: { - 'X-Request-Origin': this.appName, + 'X-Request-Origin': this.origin, }, providerOptions: { openai: { diff --git a/packages/compass-web/src/entrypoint.tsx b/packages/compass-web/src/entrypoint.tsx index 68cd0522b57..41b2e1f2e1a 100644 --- a/packages/compass-web/src/entrypoint.tsx +++ b/packages/compass-web/src/entrypoint.tsx @@ -502,6 +502,7 @@ const CompassWeb = ({ >