diff --git a/package-lock.json b/package-lock.json index b84f3a9c0b3..cfe9f31f248 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47176,6 +47176,7 @@ "@mongodb-js/atlas-service": "^0.56.0", "@mongodb-js/compass-app-registry": "^9.4.20", "@mongodb-js/compass-components": "^1.49.0", + "@mongodb-js/compass-generative-ai": "^0.51.0", "@mongodb-js/compass-logging": "^1.7.12", "@mongodb-js/compass-telemetry": "^1.14.0", "@mongodb-js/connection-info": "^0.17.1", @@ -49348,11 +49349,11 @@ "@mongodb-js/atlas-service": "^0.56.0", "@mongodb-js/compass-app-registry": "^9.4.20", "@mongodb-js/compass-components": "^1.49.0", - "@mongodb-js/compass-connections": "^1.71.0", "@mongodb-js/compass-intercom": "^0.35.0", "@mongodb-js/compass-logging": "^1.7.12", "@mongodb-js/compass-telemetry": "^1.14.0", "@mongodb-js/compass-utils": "^0.9.11", + "@mongodb-js/connection-info": "^0.17.2", "bson": "^6.10.4", "compass-preferences-model": "^2.51.0", "mongodb": "^6.19.0", @@ -49364,7 +49365,6 @@ "zod": "^3.25.76" }, "devDependencies": { - "@mongodb-js/connection-info": "^0.17.2", "@mongodb-js/eslint-config-compass": "^1.4.7", "@mongodb-js/mocha-config-compass": "^1.7.0", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -60584,6 +60584,7 @@ "@mongodb-js/atlas-service": "^0.56.0", "@mongodb-js/compass-app-registry": "^9.4.20", "@mongodb-js/compass-components": "^1.49.0", + "@mongodb-js/compass-generative-ai": "^0.51.0", "@mongodb-js/compass-logging": "^1.7.12", "@mongodb-js/compass-telemetry": "^1.14.0", "@mongodb-js/connection-info": "^0.17.1", @@ -62107,7 +62108,6 @@ "@mongodb-js/atlas-service": "^0.56.0", "@mongodb-js/compass-app-registry": "^9.4.20", "@mongodb-js/compass-components": "^1.49.0", - "@mongodb-js/compass-connections": "^1.71.0", "@mongodb-js/compass-intercom": "^0.35.0", "@mongodb-js/compass-logging": "^1.7.12", "@mongodb-js/compass-telemetry": "^1.14.0", diff --git a/packages/compass-assistant/package.json b/packages/compass-assistant/package.json index 48d2832bdcb..39b5b420f2d 100644 --- a/packages/compass-assistant/package.json +++ b/packages/compass-assistant/package.json @@ -56,6 +56,7 @@ "@mongodb-js/compass-telemetry": "^1.14.0", "@mongodb-js/connection-info": "^0.17.1", "@mongodb-js/compass-logging": "^1.7.12", + "@mongodb-js/compass-generative-ai": "^0.51.0", "mongodb-connection-string-url": "^3.0.1", "ai": "^5.0.26", "compass-preferences-model": "^2.51.0", diff --git a/packages/compass-assistant/src/assistant-chat.spec.tsx b/packages/compass-assistant/src/assistant-chat.spec.tsx index 7fc97e6986b..768f338c799 100644 --- a/packages/compass-assistant/src/assistant-chat.spec.tsx +++ b/packages/compass-assistant/src/assistant-chat.spec.tsx @@ -8,7 +8,11 @@ import { import { AssistantChat } from './assistant-chat'; import { expect } from 'chai'; import { createMockChat } from '../test/utils'; -import type { AssistantMessage } from './compass-assistant-provider'; +import { + AssistantActionsContext, + type AssistantMessage, +} from './compass-assistant-provider'; +import sinon from 'sinon'; describe('AssistantChat', function () { const mockMessages: AssistantMessage[] = [ @@ -38,10 +42,30 @@ describe('AssistantChat', function () { } = {} ) { const chat = createMockChat({ messages, status }); - const result = render(); + + // The chat component does not use chat.sendMessage() directly, it uses + // ensureOptInAndSend() via the AssistantActionsContext. + const ensureOptInAndSendStub = sinon + .stub() + .callsFake(async (message, options, callback) => { + // call the callback so we can test the tracking + callback(); + + await chat.sendMessage(message, options); + }); + + const assistantActionsContext = { + ensureOptInAndSend: ensureOptInAndSendStub, + }; + const result = render( + + + + ); return { result, chat, + ensureOptInAndSendStub, }; } @@ -156,8 +180,8 @@ describe('AssistantChat', function () { ); }); - it('calls sendMessage when form is submitted', async function () { - const { chat, result } = renderWithChat([]); + it('calls ensureOptInAndSend when form is submitted', async function () { + const { ensureOptInAndSendStub, result } = renderWithChat([]); const { track } = result; const inputField = screen.getByPlaceholderText( 'Ask MongoDB Assistant a question' @@ -167,8 +191,7 @@ describe('AssistantChat', function () { userEvent.type(inputField, 'What is aggregation?'); userEvent.click(sendButton); - expect(chat.sendMessage.calledWith({ text: 'What is aggregation?' })).to.be - .true; + expect(ensureOptInAndSendStub.called).to.be.true; await waitFor(() => { expect(track).to.have.been.calledWith('Assistant Prompt Submitted', { @@ -193,7 +216,7 @@ describe('AssistantChat', function () { }); it('trims whitespace from input before sending', async function () { - const { chat, result } = renderWithChat([]); + const { ensureOptInAndSendStub, result } = renderWithChat([]); const { track } = result; const inputField = screen.getByPlaceholderText( @@ -203,8 +226,7 @@ describe('AssistantChat', function () { userEvent.type(inputField, ' What is sharding? '); userEvent.click(screen.getByLabelText('Send message')); - expect(chat.sendMessage.calledWith({ text: 'What is sharding?' })).to.be - .true; + expect(ensureOptInAndSendStub.called).to.be.true; await waitFor(() => { expect(track).to.have.been.calledWith('Assistant Prompt Submitted', { @@ -213,8 +235,8 @@ describe('AssistantChat', function () { }); }); - it('does not call sendMessage when input is empty or whitespace-only', function () { - const { chat } = renderWithChat([]); + it('does not call ensureOptInAndSend when input is empty or whitespace-only', function () { + const { ensureOptInAndSendStub } = renderWithChat([]); const inputField = screen.getByPlaceholderText( 'Ask MongoDB Assistant a question' @@ -223,12 +245,12 @@ describe('AssistantChat', function () { // Test empty input userEvent.click(chatForm); - expect(chat.sendMessage.notCalled).to.be.true; + expect(ensureOptInAndSendStub.notCalled).to.be.true; // Test whitespace-only input userEvent.type(inputField, ' '); userEvent.click(chatForm); - expect(chat.sendMessage.notCalled).to.be.true; + expect(ensureOptInAndSendStub.notCalled).to.be.true; }); it('displays user and assistant messages with different styling', function () { diff --git a/packages/compass-assistant/src/assistant-chat.tsx b/packages/compass-assistant/src/assistant-chat.tsx index 5a509f1cd88..f2c88747b32 100644 --- a/packages/compass-assistant/src/assistant-chat.tsx +++ b/packages/compass-assistant/src/assistant-chat.tsx @@ -1,5 +1,6 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useContext } from 'react'; import type { AssistantMessage } from './compass-assistant-provider'; +import { AssistantActionsContext } from './compass-assistant-provider'; import type { Chat } from './@ai-sdk/react/chat-react'; import { useChat } from './@ai-sdk/react/use-chat'; import { @@ -121,7 +122,8 @@ export const AssistantChat: React.FunctionComponent = ({ }) => { const track = useTelemetry(); const darkMode = useDarkMode(); - const { messages, sendMessage, status, error, clearError } = useChat({ + const { ensureOptInAndSend } = useContext(AssistantActionsContext); + const { messages, status, error, clearError } = useChat({ chat, onError: (error) => { track('Assistant Response Failed', () => ({ @@ -147,13 +149,14 @@ export const AssistantChat: React.FunctionComponent = ({ (messageBody: string) => { const trimmedMessageBody = messageBody.trim(); if (trimmedMessageBody) { - track('Assistant Prompt Submitted', { - user_input_length: trimmedMessageBody.length, + void ensureOptInAndSend?.({ text: trimmedMessageBody }, {}, () => { + track('Assistant Prompt Submitted', { + user_input_length: trimmedMessageBody.length, + }); }); - void sendMessage({ text: trimmedMessageBody }); } }, - [sendMessage, track] + [track, ensureOptInAndSend] ); const handleFeedback = useCallback( diff --git a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx index 357ee8a249c..16c9abb9145 100644 --- a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx +++ b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx @@ -9,7 +9,6 @@ import { within, } from '@mongodb-js/testing-library-compass'; import { - AssistantProvider, CompassAssistantProvider, useAssistantActions, type AssistantMessage, @@ -22,23 +21,74 @@ import { DrawerAnchor, DrawerContentProvider, } from '@mongodb-js/compass-components'; +import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; import type { AtlasService } from '@mongodb-js/atlas-service/provider'; import { CompassAssistantDrawer } from './compass-assistant-drawer'; import { createMockChat } from '../test/utils'; +import { type AtlasAiService } from '@mongodb-js/compass-generative-ai/provider'; + +function createMockProvider({ + mockAtlasService, + mockAtlasAiService, + mockAtlasAuthService, +}: { + mockAtlasService?: any; + mockAtlasAiService?: any; + mockAtlasAuthService?: any; +} = {}) { + if (!mockAtlasService) { + mockAtlasService = { + assistantApiEndpoint: sinon + .stub() + .returns('https://example.com/assistant/api/v1'), + }; + } + + if (!mockAtlasAiService) { + mockAtlasAiService = { + ensureAiFeatureAccess: sinon.stub().resolves(), + }; + } + + if (!mockAtlasAuthService) { + mockAtlasAuthService = {}; + } + + return CompassAssistantProvider.withMockServices({ + atlasService: mockAtlasService as unknown as AtlasService, + atlasAiService: mockAtlasAiService as unknown as AtlasAiService, + atlasAuthService: mockAtlasAuthService as unknown as AtlasAuthService, + }); +} -// Test component that renders AssistantProvider with children +// Test component that renders CompassAssistantProvider (and AssistantProvider) with children const TestComponent: React.FunctionComponent<{ chat: Chat; autoOpen?: boolean; -}> = ({ chat, autoOpen }) => { + mockAtlasService?: any; + mockAtlasAiService?: any; + mockAtlasAuthService?: any; +}> = ({ + chat, + autoOpen, + mockAtlasService, + mockAtlasAiService, + mockAtlasAuthService, +}) => { + const MockedProvider = createMockProvider({ + mockAtlasService: mockAtlasService as unknown as AtlasService, + mockAtlasAiService: mockAtlasAiService as unknown as AtlasAiService, + mockAtlasAuthService: mockAtlasAuthService as unknown as AtlasAuthService, + }); + return ( - +
Provider children
-
+
); }; @@ -46,16 +96,18 @@ const TestComponent: React.FunctionComponent<{ describe('useAssistantActions', function () { const createWrapper = (chat: Chat) => { function TestWrapper({ children }: { children: React.ReactNode }) { + const MockedProvider = createMockProvider(); + return ( - {children} + {children} ); } return TestWrapper; }; - it('returns empty object when AI features are disabled via isAIFeatureEnabled', function () { + it('returns mostly empty object when AI features are disabled via isAIFeatureEnabled', function () { const { result } = renderHook(() => useAssistantActions(), { wrapper: createWrapper(createMockChat({ messages: [] })), preferences: { @@ -67,10 +119,10 @@ describe('useAssistantActions', function () { }, }); - expect(result.current).to.deep.equal({}); + expect(result.current).to.have.keys(['getIsAssistantEnabled']); }); - it('returns empty object when enableGenAIFeaturesAtlasOrg is disabled', function () { + it('returns mostly empty object when enableGenAIFeaturesAtlasOrg is disabled', function () { const { result } = renderHook(() => useAssistantActions(), { wrapper: createWrapper(createMockChat({ messages: [] })), preferences: { @@ -81,10 +133,10 @@ describe('useAssistantActions', function () { }, }); - expect(result.current).to.deep.equal({}); + expect(result.current).to.have.keys(['getIsAssistantEnabled']); }); - it('returns empty object when cloudFeatureRolloutAccess is disabled', function () { + it('returns mostly empty object when cloudFeatureRolloutAccess is disabled', function () { const { result } = renderHook(() => useAssistantActions(), { wrapper: createWrapper(createMockChat({ messages: [] })), preferences: { @@ -95,10 +147,10 @@ describe('useAssistantActions', function () { }, }); - expect(result.current).to.deep.equal({}); + expect(result.current).to.have.keys(['getIsAssistantEnabled']); }); - it('returns empty object when enableAIAssistant preference is disabled', function () { + it('returns mostly empty object when enableAIAssistant preference is disabled', function () { const { result } = renderHook(() => useAssistantActions(), { wrapper: createWrapper(createMockChat({ messages: [] })), preferences: { @@ -109,7 +161,7 @@ describe('useAssistantActions', function () { }, }); - expect(result.current).to.deep.equal({}); + expect(result.current).to.have.keys(['getIsAssistantEnabled']); }); it('returns actions when both AI features and assistant flag are enabled', function () { @@ -127,11 +179,10 @@ describe('useAssistantActions', function () { expect(result.current.interpretExplainPlan).to.be.a('function'); expect(result.current.interpretConnectionError).to.be.a('function'); expect(result.current.tellMoreAboutInsight).to.be.a('function'); - expect(result.current.clearChat).to.be.a('function'); }); }); -describe('AssistantProvider', function () { +describe('CompassAssistantProvider', function () { const mockMessages: AssistantMessage[] = [ { id: '1', @@ -198,16 +249,24 @@ describe('AssistantProvider', function () { }); async function renderOpenAssistantDrawer( - mockChat: Chat + mockChat: Chat, + mockAtlasAiService?: any ): Promise> { - const result = render(, { - preferences: { - enableAIAssistant: true, - enableGenAIFeatures: true, - enableGenAIFeaturesAtlasOrg: true, - cloudFeatureRolloutAccess: { GEN_AI_COMPASS: true }, - }, - }); + const result = render( + , + { + preferences: { + enableAIAssistant: true, + enableGenAIFeatures: true, + enableGenAIFeaturesAtlasOrg: true, + cloudFeatureRolloutAccess: { GEN_AI_COMPASS: true }, + }, + } + ); await waitFor(() => { expect(screen.getByTestId('assistant-chat')).to.exist; @@ -221,6 +280,11 @@ describe('AssistantProvider', function () { await renderOpenAssistantDrawer(mockChat); + // ensureAiFeatureAccess is async + await waitFor(() => { + expect(screen.getByTestId('assistant-message-1')).to.exist; + }); + expect(screen.getByTestId('assistant-message-1')).to.exist; expect(screen.getByTestId('assistant-message-1')).to.have.text( 'Test message' @@ -255,8 +319,8 @@ describe('AssistantProvider', function () { userEvent.type(input, 'Hello assistant'); userEvent.click(sendButton); - expect(sendMessageSpy.calledOnce).to.be.true; await waitFor(() => { + expect(sendMessageSpy.calledOnce).to.be.true; expect(sendMessageSpy.firstCall.args[0]).to.deep.include({ text: 'Hello assistant', }); @@ -292,14 +356,54 @@ describe('AssistantProvider', function () { ); userEvent.click(screen.getByLabelText('Send message')); - expect(sendMessageSpy.calledOnce).to.be.true; - expect(sendMessageSpy.firstCall.args[0]).to.deep.include({ - text: 'Hello assistant!', + await waitFor(() => { + expect(sendMessageSpy.calledOnce).to.be.true; + expect(sendMessageSpy.firstCall.args[0]).to.deep.include({ + text: 'Hello assistant!', + }); + + expect(screen.getByText('Hello assistant!')).to.exist; + }); + }); + + it('will not send new messages if the user does not opt in', async function () { + const mockChat = new Chat({ + messages: [ + { + id: 'assistant', + role: 'assistant', + parts: [{ type: 'text', text: 'Hello user!' }], + }, + ], + transport: { + sendMessages: sinon.stub().returns( + new Promise(() => { + return new ReadableStream({}); + }) + ), + reconnectToStream: sinon.stub(), + }, }); + const mockAtlasAiService = { + ensureAiFeatureAccess: sinon.stub().rejects(), + }; + + const sendMessageSpy = sinon.spy(mockChat, 'sendMessage'); + + await renderOpenAssistantDrawer(mockChat, mockAtlasAiService); + + userEvent.type( + screen.getByPlaceholderText('Ask MongoDB Assistant a question'), + 'Hello assistant!' + ); + userEvent.click(screen.getByLabelText('Send message')); + await waitFor(() => { - expect(screen.getByText('Hello assistant!')).to.exist; + expect(mockAtlasAiService.ensureAiFeatureAccess.calledOnce).to.be.true; + expect(sendMessageSpy.called).to.be.false; }); + expect(screen.queryByText('Hello assistant!')).to.not.exist; }); describe('clear chat button', function () { @@ -366,29 +470,31 @@ describe('AssistantProvider', function () { }); describe('CompassAssistantProvider', function () { - beforeEach(function () { - process.env.COMPASS_ASSISTANT_USE_ATLAS_SERVICE_URL = 'true'; - }); - - afterEach(function () { - delete process.env.COMPASS_ASSISTANT_USE_ATLAS_SERVICE_URL; - }); - - it('uses the Atlas Service assistantApiEndpoint', function () { + it('uses the Atlas Service assistantApiEndpoint', async function () { const mockAtlasService = { assistantApiEndpoint: sinon .stub() .returns('https://example.com/assistant/api/v1'), }; + const mockAtlasAiService = { + ensureAiFeatureAccess: sinon.stub().callsFake(() => { + return Promise.resolve(); + }), + }; + + const mockAtlasAuthService = {}; + const MockedProvider = CompassAssistantProvider.withMockServices({ atlasService: mockAtlasService as unknown as AtlasService, + atlasAiService: mockAtlasAiService as unknown as AtlasAiService, + atlasAuthService: mockAtlasAuthService as unknown as AtlasAuthService, }); render( - + , { preferences: { @@ -400,7 +506,9 @@ describe('AssistantProvider', function () { } ); - expect(mockAtlasService.assistantApiEndpoint.calledOnce).to.be.true; + await waitFor(() => { + expect(mockAtlasService.assistantApiEndpoint.calledOnce).to.be.true; + }); }); }); }); diff --git a/packages/compass-assistant/src/compass-assistant-provider.tsx b/packages/compass-assistant/src/compass-assistant-provider.tsx index 472e8168276..bd9a7058346 100644 --- a/packages/compass-assistant/src/compass-assistant-provider.tsx +++ b/packages/compass-assistant/src/compass-assistant-provider.tsx @@ -6,7 +6,10 @@ import { createServiceLocator, registerCompassPlugin, } from '@mongodb-js/compass-app-registry'; -import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider'; +import { + atlasAuthServiceLocator, + atlasServiceLocator, +} from '@mongodb-js/atlas-service/provider'; import { DocsProviderTransport } from './docs-provider-transport'; import { useDrawerActions } from '@mongodb-js/compass-components'; import { @@ -23,6 +26,8 @@ import { import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import type { ConnectionInfo } from '@mongodb-js/connection-info'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; +import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider'; +import { atlasAiServiceLocator } from '@mongodb-js/compass-generative-ai/provider'; export const ASSISTANT_DRAWER_ID = 'compass-assistant-drawer'; @@ -39,6 +44,9 @@ export const AssistantContext = createContext( null ); +type SendMessage = Parameters['sendMessage']>[0]; +type SendOptions = Parameters['sendMessage']>[1]; + type AssistantActionsContextType = { interpretExplainPlan?: ({ namespace, @@ -56,43 +64,90 @@ type AssistantActionsContextType = { }) => void; clearChat?: () => void; tellMoreAboutInsight?: (context: ProactiveInsightsContext) => void; + ensureOptInAndSend?: ( + message: SendMessage, + options: SendOptions, + callback: () => void + ) => Promise; +}; + +type AssistantActionsType = Omit< + AssistantActionsContextType, + 'ensureOptInAndSend' | 'clearChat' +> & { + getIsAssistantEnabled: () => boolean; }; + export const AssistantActionsContext = createContext({ interpretExplainPlan: () => {}, interpretConnectionError: () => {}, tellMoreAboutInsight: () => {}, clearChat: () => {}, + ensureOptInAndSend: async () => {}, }); -export function useAssistantActions(): AssistantActionsContextType { +export function useAssistantActions(): AssistantActionsType { + const actions = useContext(AssistantActionsContext); const isAIFeatureEnabled = useIsAIFeatureEnabled(); const isAssistantFlagEnabled = usePreference('enableAIAssistant'); - const actions = useContext(AssistantActionsContext); if (!isAIFeatureEnabled || !isAssistantFlagEnabled) { - return {}; + return { + getIsAssistantEnabled: () => false, + }; } - return actions; + const { + interpretExplainPlan, + interpretConnectionError, + tellMoreAboutInsight, + } = actions; + + return { + interpretExplainPlan, + interpretConnectionError, + tellMoreAboutInsight, + getIsAssistantEnabled: () => true, + }; } -export const compassAssistantServiceLocator = createServiceLocator(function () { +export const compassAssistantServiceLocator = createServiceLocator(() => { const actions = useAssistantActions(); - return actions; + const interpretConnectionErrorRef = useRef(actions.interpretConnectionError); + interpretConnectionErrorRef.current = actions.interpretConnectionError; + + const getIsAssistantEnabledRef = useRef(actions.getIsAssistantEnabled); + getIsAssistantEnabledRef.current = actions.getIsAssistantEnabled; + + return { + interpretConnectionError: (options: { + connectionInfo: ConnectionInfo; + error: Error; + }) => interpretConnectionErrorRef.current?.(options), + getIsAssistantEnabled: () => { + return getIsAssistantEnabledRef.current(); + }, + }; }, 'compassAssistantLocator'); -export type CompassAssistantService = ReturnType< - typeof compassAssistantServiceLocator ->; +export type CompassAssistantService = { + interpretConnectionError: (options: { + connectionInfo: ConnectionInfo; + error: Error; + }) => void; + getIsAssistantEnabled: () => boolean; +}; export const AssistantProvider: React.FunctionComponent< PropsWithChildren<{ chat: Chat; + atlasAiService: AtlasAiService; }> -> = ({ chat, children }) => { +> = ({ chat, atlasAiService, children }) => { const { openDrawer } = useDrawerActions(); const track = useTelemetry(); + const createEntryPointHandler = useRef(function ( entryPointName: | 'explain plan' @@ -101,12 +156,22 @@ export const AssistantProvider: React.FunctionComponent< builder: (props: T) => EntryPointMessage ) { return (props: T) => { - openDrawer(ASSISTANT_DRAWER_ID); + if (!assistantActionsContext.current.ensureOptInAndSend) { + return; + } + const { prompt, displayText } = builder(props); - void chat.sendMessage({ text: prompt, metadata: { displayText } }, {}); - track('Assistant Entry Point Used', { - source: entryPointName, - }); + void assistantActionsContext.current.ensureOptInAndSend( + { text: prompt, metadata: { displayText } }, + {}, + () => { + openDrawer(ASSISTANT_DRAWER_ID); + + track('Assistant Entry Point Used', { + source: entryPointName, + }); + } + ); }; }); const assistantActionsContext = useRef({ @@ -125,6 +190,24 @@ export const AssistantProvider: React.FunctionComponent< clearChat: () => { chat.messages = []; }, + ensureOptInAndSend: async ( + message: SendMessage, + options: SendOptions, + callback: () => void + ) => { + try { + await atlasAiService.ensureAiFeatureAccess(); + } catch { + // opt-in failed: just do nothing + return; + } + + // Call the callback to indicate that the opt-in was successful. A good + // place to do tracking. + callback(); + + await chat.sendMessage(message, options); + }, }); return ( @@ -141,37 +224,50 @@ export const CompassAssistantProvider = registerCompassPlugin( name: 'CompassAssistant', component: ({ chat, + atlasAiService, children, }: PropsWithChildren<{ chat?: Chat; + atlasAiService?: AtlasAiService; }>) => { if (!chat) { throw new Error('Chat was not provided by the state'); } - return {children}; + if (!atlasAiService) { + throw new Error('atlasAiService was not provided by the state'); + } + return ( + + {children} + + ); }, - activate: (initialProps, { atlasService, logger }) => { - const chat = new Chat({ - transport: new DocsProviderTransport({ - baseUrl: atlasService.assistantApiEndpoint(), - }), - onError: (err: Error) => { - logger.log.error( - logger.mongoLogId(1_001_000_370), - 'Assistant', - 'Failed to send a message', - { err } - ); - }, - }); + activate: (initialProps, { atlasService, atlasAiService, logger }) => { + const chat = + initialProps.chat ?? + new Chat({ + transport: new DocsProviderTransport({ + baseUrl: atlasService.assistantApiEndpoint(), + }), + onError: (err: Error) => { + logger.log.error( + logger.mongoLogId(1_001_000_370), + 'Assistant', + 'Failed to send a message', + { err } + ); + }, + }); return { - store: { state: { chat } }, + store: { state: { chat, atlasAiService } }, deactivate: () => {}, }; }, }, { atlasService: atlasServiceLocator, + atlasAiService: atlasAiServiceLocator, + atlasAuthService: atlasAuthServiceLocator, logger: createLoggerLocator('COMPASS-ASSISTANT'), } ); diff --git a/packages/compass-connections/src/stores/connections-store-redux.ts b/packages/compass-connections/src/stores/connections-store-redux.ts index fc056078fbe..92da56e5653 100644 --- a/packages/compass-connections/src/stores/connections-store-redux.ts +++ b/packages/compass-connections/src/stores/connections-store-redux.ts @@ -1284,9 +1284,9 @@ const connectionAttemptError = ( } : undefined, onDebugClick: - compassAssistant.interpretConnectionError && connectionInfo + compassAssistant.getIsAssistantEnabled() && connectionInfo ? () => { - compassAssistant.interpretConnectionError?.({ + compassAssistant.interpretConnectionError({ connectionInfo, error: err, }); diff --git a/packages/compass-generative-ai/package.json b/packages/compass-generative-ai/package.json index 2cf393ce179..2535c8e8d13 100644 --- a/packages/compass-generative-ai/package.json +++ b/packages/compass-generative-ai/package.json @@ -55,11 +55,11 @@ "@mongodb-js/atlas-service": "^0.56.0", "@mongodb-js/compass-app-registry": "^9.4.20", "@mongodb-js/compass-components": "^1.49.0", - "@mongodb-js/compass-connections": "^1.71.0", "@mongodb-js/compass-intercom": "^0.35.0", "@mongodb-js/compass-logging": "^1.7.12", "@mongodb-js/compass-telemetry": "^1.14.0", "@mongodb-js/compass-utils": "^0.9.11", + "@mongodb-js/connection-info": "^0.17.2", "bson": "^6.10.4", "compass-preferences-model": "^2.51.0", "mongodb": "^6.19.0", @@ -71,7 +71,6 @@ "zod": "^3.25.76" }, "devDependencies": { - "@mongodb-js/connection-info": "^0.17.2", "@mongodb-js/eslint-config-compass": "^1.4.7", "@mongodb-js/mocha-config-compass": "^1.7.0", "@mongodb-js/prettier-config-compass": "^1.2.8", diff --git a/packages/compass-generative-ai/src/atlas-ai-service.ts b/packages/compass-generative-ai/src/atlas-ai-service.ts index 3dd8d727b47..70f7e49b60b 100644 --- a/packages/compass-generative-ai/src/atlas-ai-service.ts +++ b/packages/compass-generative-ai/src/atlas-ai-service.ts @@ -5,7 +5,7 @@ import { } from 'compass-preferences-model/provider'; import type { AtlasService } from '@mongodb-js/atlas-service/provider'; import { AtlasServiceError } from '@mongodb-js/atlas-service/renderer'; -import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; +import type { ConnectionInfo } from '@mongodb-js/connection-info'; import type { Document } from 'mongodb'; import type { Logger } from '@mongodb-js/compass-logging'; import { EJSON } from 'bson';