Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions packages/compass-assistant/src/compass-assistant-drawer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import React, { useContext } from 'react';
import { DrawerSection } from '@mongodb-js/compass-components';
import React, { useCallback, useContext } from 'react';
import {
DrawerSection,
Icon,
IconButton,
showConfirmation,
} from '@mongodb-js/compass-components';
import { AssistantChat } from './assistant-chat';
import {
ASSISTANT_DRAWER_ID,
AssistantActionsContext,
AssistantContext,
} from './compass-assistant-provider';
import { usePreference } from 'compass-preferences-model/provider';
Expand All @@ -16,9 +22,23 @@ export const CompassAssistantDrawer: React.FunctionComponent<{
autoOpen?: boolean;
}> = ({ autoOpen }) => {
const chat = useContext(AssistantContext);
const { clearChat } = useContext(AssistantActionsContext);

const enableAIAssistant = usePreference('enableAIAssistant');

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',
});
if (confirmed) {
clearChat();
}
}, [clearChat]);

if (!enableAIAssistant) {
return null;
}
Expand All @@ -32,7 +52,27 @@ export const CompassAssistantDrawer: React.FunctionComponent<{
return (
<DrawerSection
id={ASSISTANT_DRAWER_ID}
title="MongoDB Assistant"
title={
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<span>MongoDB Assistant</span>
<IconButton
aria-label="Clear chat"
onClick={() => {
void handleClearChat();
}}
title="Clear chat"
data-testid="assistant-clear-chat"
>
<Icon glyph="Eraser" />
</IconButton>
</div>
}
label="MongoDB Assistant"
glyph="Sparkle"
autoOpen={autoOpen}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
screen,
userEvent,
waitFor,
waitForElementToBeRemoved,
within,
} from '@mongodb-js/testing-library-compass';
import {
AssistantProvider,
Expand Down Expand Up @@ -40,6 +42,19 @@ const TestComponent: React.FunctionComponent<{
};

describe('AssistantProvider', function () {
const mockMessages: AssistantMessage[] = [
{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'Test message' }],
},
{
id: '2',
role: 'assistant',
parts: [{ type: 'text', text: 'Test assistant message' }],
},
];

it('always renders children', function () {
render(<TestComponent chat={createMockChat({ messages: [] })} />, {
preferences: { enableAIAssistant: true },
Expand Down Expand Up @@ -92,18 +107,6 @@ describe('AssistantProvider', function () {
}

it('displays messages in the chat feed', async function () {
const mockMessages: AssistantMessage[] = [
{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'Test message' }],
},
{
id: '2',
role: 'assistant',
parts: [{ type: 'text', text: 'Test assistant message' }],
},
];
const mockChat = createMockChat({ messages: mockMessages });

await renderOpenAssistantDrawer(mockChat);
Expand Down Expand Up @@ -186,6 +189,66 @@ describe('AssistantProvider', function () {
expect(screen.getByText('Hello assistant!')).to.exist;
});
});

describe('clear chat button', function () {
it('clears the chat when the user clicks and confirms', async function () {
const mockChat = createMockChat({ messages: mockMessages });

await renderOpenAssistantDrawer(mockChat);

const clearButton = screen.getByTestId('assistant-clear-chat');
userEvent.click(clearButton);

await waitFor(() => {
expect(screen.getByTestId('confirmation-modal')).to.exist;
});

// There should be messages in the chat
expect(screen.getByTestId('assistant-message-1')).to.exist;
expect(screen.getByTestId('assistant-message-2')).to.exist;

const modal = screen.getByTestId('confirmation-modal');
const confirmButton = within(modal).getByText('Clear chat');
userEvent.click(confirmButton);

await waitForElementToBeRemoved(() =>
screen.getByTestId('confirmation-modal')
);

expect(mockChat.messages).to.be.empty;
expect(screen.queryByTestId('assistant-message-1')).to.not.exist;
expect(screen.queryByTestId('assistant-message-2')).to.not.exist;
});

it('does not clear the chat when the user clicks the button and cancels', async function () {
const mockChat = createMockChat({ messages: mockMessages });

await renderOpenAssistantDrawer(mockChat);

const clearButton = screen.getByTestId('assistant-clear-chat');
userEvent.click(clearButton);

await waitFor(() => {
expect(screen.getByTestId('confirmation-modal')).to.exist;
});

// There should be messages in the chat
expect(screen.getByTestId('assistant-message-1')).to.exist;
expect(screen.getByTestId('assistant-message-2')).to.exist;

const modal = screen.getByTestId('confirmation-modal');
const cancelButton = within(modal).getByText('Cancel');
userEvent.click(cancelButton);

await waitForElementToBeRemoved(() =>
screen.getByTestId('confirmation-modal')
);

expect(mockChat.messages).to.deep.equal(mockMessages);
expect(screen.getByTestId('assistant-message-1')).to.exist;
expect(screen.getByTestId('assistant-message-2')).to.exist;
});
});
});

describe('CompassAssistantProvider', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ type AssistantActionsContextType = {
namespace: string;
explainPlan: string;
}) => void;
clearChat: () => void;
};
export const AssistantActionsContext =
createContext<AssistantActionsContextType>({
interpretExplainPlan: () => {},
clearChat: () => {},
});

export function useAssistantActions(): AssistantActionsContextType & {
Expand Down Expand Up @@ -70,6 +72,9 @@ export const AssistantProvider: React.FunctionComponent<
{}
);
},
clearChat: () => {
chat.messages = [];
Copy link

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direct mutation of the chat.messages array may not trigger re-renders in React components that depend on this data. Consider using a proper state update method or immutable update pattern to ensure components re-render when messages are cleared.

Suggested change
chat.messages = [];
if (typeof chat.setMessages === 'function') {
chat.setMessages([]);
} else if (typeof chat.clearMessages === 'function') {
chat.clearMessages();
} else {
// Fallback: do nothing or log a warning
console.warn('No method to clear chat messages found.');
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will, this is a ai-sdk thing

Copy link
Contributor

@lerouxb lerouxb Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set messages(newMessages: UI_MESSAGE[]) {
this.#messages = [...newMessages];
this.#callMessagesCallbacks();
}

},
});
const { openDrawer } = useDrawerActions();

Expand Down
Loading