diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx index ba18594836792..abcaaef48c5f3 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx @@ -12,8 +12,8 @@ import { HttpSetup } from '@kbn/core-http-browser'; import { FormattedMessage } from '@kbn/i18n-react'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common'; import { noop } from 'lodash/fp'; -import { PromptResponse } from '@kbn/elastic-assistant-common'; -import { Conversation } from '../../../..'; +import type { PromptResponse } from '@kbn/elastic-assistant-common'; +import { Conversation, useAssistantContext } from '../../../..'; import * as i18n from './translations'; import * as i18nModel from '../../../connectorland/models/model_selector/translations'; @@ -52,8 +52,10 @@ export const ConversationSettingsEditor: React.FC { + const { settings } = useAssistantContext(); const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ http, + settings, }); const selectedSystemPrompt = useMemo(() => { return getDefaultSystemPrompt({ allSystemPrompts, conversation: selectedConversation }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts index 26609dea82164..e03081f4aefb7 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.test.ts @@ -10,7 +10,9 @@ import { getOptionalRequestParams, mergeBaseWithPersistedConversations, } from './helpers'; -import { AIConnector } from '../connectorland/connector_selector'; +import type { AIConnector } from '../connectorland/connector_selector'; +import type { SettingsStart } from '@kbn/core-ui-settings-browser'; +import { GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR } from '@kbn/management-settings-ids'; describe('helpers', () => { describe('getDefaultConnector', () => { @@ -29,41 +31,97 @@ describe('helpers', () => { apiUrl: 'https://api.openai.com/v1/chat/completions', }, }; + + const connector2: AIConnector = { + ...defaultConnector, + id: 'c7f91dc0-2197-11ee-aded-897192c5d633', + name: 'OpenAI', + config: { + apiProvider: 'OpenAI 2', + apiUrl: 'https://api.openai.com/v1/chat/completions', + }, + }; + + const clientGet = jest.fn(); + + const settings = { + client: { + get: clientGet, + }, + } as unknown as SettingsStart; + + beforeEach(() => { + jest.clearAllMocks(); + clientGet.mockImplementation((key: string) => { + return undefined; + }); + }); + it('should return undefined if connectors array is undefined', () => { const connectors = undefined; - const result = getDefaultConnector(connectors); + const result = getDefaultConnector(connectors, settings); expect(result).toBeUndefined(); }); it('should return undefined if connectors array is empty', () => { const connectors: AIConnector[] = []; - const result = getDefaultConnector(connectors); + const result = getDefaultConnector(connectors, settings); expect(result).toBeUndefined(); }); - it('should return the connector id if there is only one connector', () => { + it('should return the first connector if there is only one connector available', () => { const connectors: AIConnector[] = [defaultConnector]; - const result = getDefaultConnector(connectors); + const result = getDefaultConnector(connectors, settings); expect(result).toBe(connectors[0]); }); - it('should return the connector id if there are multiple connectors', () => { - const connectors: AIConnector[] = [ - defaultConnector, - { - ...defaultConnector, - id: 'c7f91dc0-2197-11ee-aded-897192c5d633', - name: 'OpenAI', - config: { - apiProvider: 'OpenAI 2', - apiUrl: 'https://api.openai.com/v1/chat/completions', - }, - }, - ]; - const result = getDefaultConnector(connectors); + it('should return the default connector if there are multiple connectors and default connector is defined', () => { + clientGet.mockImplementation((key: string) => { + if (key === GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR) { + return defaultConnector.id; + } + return undefined; + }); + const connectors: AIConnector[] = [defaultConnector, connector2]; + const result = getDefaultConnector(connectors, settings); + expect(result).toBe(defaultConnector); + }); + + it('should return the default connector if there are multiple connectors and default connector is defined but they are in a different order', () => { + clientGet.mockImplementation((key: string) => { + if (key === GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR) { + return defaultConnector.id; + } + return undefined; + }); + const connectors: AIConnector[] = [connector2, defaultConnector]; + const result = getDefaultConnector(connectors, settings); + expect(result).toBe(defaultConnector); + }); + + it('should return the first connector if there are multiple connectors and no default connector is defined', () => { + clientGet.mockImplementation(() => { + return undefined; + }); + + const connectors: AIConnector[] = [connector2, defaultConnector]; + const result = getDefaultConnector(connectors, settings); + expect(result).toBe(connectors[0]); + }); + + it('should return the first connector if there are multiple connectors and a default connector is defined but it does not exist', () => { + clientGet.mockImplementation((key: string) => { + if (key === GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR) { + return 'randomConnectorIdThatDoesNotExist'; + } + return undefined; + }); + + const connectors: AIConnector[] = [connector2, defaultConnector]; + const result = getDefaultConnector(connectors, settings); expect(result).toBe(connectors[0]); }); }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts index 7c4aa0482c052..6e29addf4d1e9 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/helpers.ts @@ -6,10 +6,12 @@ */ import { isEmpty, some } from 'lodash'; -import { AIConnector } from '../connectorland/connector_selector'; -import { FetchConnectorExecuteResponse, FetchConversationsResponse } from './api'; -import { Conversation } from '../..'; +import type { SettingsStart } from '@kbn/core-ui-settings-browser'; +import { GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR } from '@kbn/management-settings-ids'; +import type { AIConnector } from '../connectorland/connector_selector'; +import type { FetchConnectorExecuteResponse, FetchConversationsResponse } from './api'; import type { ClientMessage } from '../assistant_context/types'; +import { Conversation } from '../..'; export const getMessageFromRawResponse = ( rawResponse: FetchConnectorExecuteResponse @@ -59,13 +61,30 @@ export const mergeBaseWithPersistedConversations = ( * @param connectors */ export const getDefaultConnector = ( - connectors: AIConnector[] | undefined + connectors: AIConnector[] | undefined, + settings: SettingsStart ): AIConnector | undefined => { + const defaultAiConnectorId = settings.client.get( + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR, + undefined + ); + const validConnectors = connectors?.filter((connector) => !connector.isMissingSecrets); + const defaultConnector = validConnectors?.find( + (connector) => connector.id === defaultAiConnectorId + ); + + if (defaultConnector) { + // If the user has set a default connector setting, and that connector exists, use it + return defaultConnector; + } + if (validConnectors?.length) { + // In case the default connector is not set or is invalid, return the first valid connector return validConnectors[0]; } + // If no valid connectors are available, return undefined return undefined; }; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx index 76c4aa8cbb226..be95deba8a8be 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx @@ -101,6 +101,7 @@ const AssistantComponent: React.FC = ({ showAnonymizedValues, setContentReferencesVisible, setShowAnonymizedValues, + settings, } = useAssistantContext(); const [selectedPromptContexts, setSelectedPromptContexts] = useState< @@ -131,8 +132,12 @@ const AssistantComponent: React.FC = ({ // Connector details const { data: connectors, isFetchedAfterMount: isFetchedConnectors } = useLoadConnectors({ http, + settings, }); - const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); + const defaultConnector = useMemo( + () => getDefaultConnector(connectors, settings), + [connectors, settings] + ); const { currentConversation, currentSystemPrompt, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx index 1837d40aae2b1..05d9045b76dd2 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx @@ -37,11 +37,27 @@ const setSelectedSettingsTab = jest.fn(); const mockContext = { basePromptContexts: MOCK_QUICK_PROMPTS, setSelectedSettingsTab, - http: {}, + http: { + get: jest.fn(), + }, selectedSettingsTab: 'CONVERSATIONS_TAB', assistantAvailability: { isAssistantEnabled: true, }, + settings: { + client: { + get: jest.fn((key) => { + if (key === 'genAiSettings:defaultAIConnector') { + return 'c5f91dc0-2197-11ee-aded-897192c5d6f5'; + } + if (key === 'genAiSettings:defaultAIConnectorDefaultOnly') { + return false; + } + return undefined; + }), + }, + }, + assistantFeatures: { assistantModelEvaluation: true }, }; const onClose = jest.fn(); const onSave = jest.fn().mockResolvedValue(() => {}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx index 04c5818118728..baf8c559b44ba 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx @@ -75,7 +75,8 @@ export const AssistantSettings: React.FC = React.memo( conversations, conversationsLoaded, }) => { - const { http, toasts, selectedSettingsTab, setSelectedSettingsTab } = useAssistantContext(); + const { http, toasts, selectedSettingsTab, setSelectedSettingsTab, settings } = + useAssistantContext(); useEffect(() => { if (selectedSettingsTab == null) { @@ -89,6 +90,7 @@ export const AssistantSettings: React.FC = React.memo( const { data: connectors } = useLoadConnectors({ http, + settings, }); const { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx index ee75732165423..9ad874693fbf2 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx @@ -27,6 +27,10 @@ import { import { mockSystemPrompts } from '../../mock/system_prompt'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; import { SettingsStart } from '@kbn/core-ui-settings-browser'; +import { + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR, + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY, +} from '@kbn/management-settings-ids'; const mockConversations = { [alertConvo.title]: alertConvo, @@ -50,6 +54,20 @@ const mockContext = { assistantAvailability: { isAssistantEnabled: true, }, + settings: { + client: { + get: jest.fn((key) => { + if (key === GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR) { + return 'c5f91dc0-2197-11ee-aded-897192c5d6f5'; + } + if (key === GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY) { + return false; + } + return undefined; + }), + }, + }, + navigateToApp: jest.fn(), }; const mockDataViews = { @@ -62,7 +80,11 @@ const testProps = { dataViews: mockDataViews, onTabChange, currentTab: CONNECTORS_TAB, - settings: {} as SettingsStart, + settings: { + client: { + get: jest.fn(), + }, + } as unknown as SettingsStart, }; jest.mock('../../assistant_context'); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx index bc4245df88387..e8ef89eac7fa9 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx @@ -60,8 +60,12 @@ export const AssistantSettingsManagement: React.FC = React.memo( } = useAssistantContext(); const { data: connectors } = useLoadConnectors({ http, + settings, }); - const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); + const defaultConnector = useMemo( + () => getDefaultConnector(connectors, settings), + [connectors, settings] + ); const { euiTheme } = useEuiTheme(); const headerIconShadow = useEuiShadow('s'); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx index 5e15dfcd4c8c4..2789a4c055512 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx @@ -49,8 +49,9 @@ const AS_PLAIN_TEXT: EuiComboBoxSingleSelectionShape = { asPlainText: true }; * Evaluation Settings -- development-only feature for evaluating models */ export const EvaluationSettings: React.FC = React.memo(() => { - const { actionTypeRegistry, http, setTraceOptions, toasts, traceOptions } = useAssistantContext(); - const { data: connectors } = useLoadConnectors({ http, inferenceEnabled: true }); + const { actionTypeRegistry, http, setTraceOptions, toasts, traceOptions, settings } = + useAssistantContext(); + const { data: connectors } = useLoadConnectors({ http, inferenceEnabled: true, settings }); const { mutate: performEvaluation, isLoading: isPerformingEvaluation } = usePerformEvaluation({ http, toasts, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.test.tsx index 7a142710d19ec..0f4096494af4c 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.test.tsx @@ -17,6 +17,11 @@ const mockUseAssistantContext = { registerPromptContext: jest.fn(), showAssistantOverlay: jest.fn(), unRegisterPromptContext: jest.fn(), + settings: { + client: { + get: jest.fn(), + }, + }, }; jest.mock('../../assistant_context', () => { const original = jest.requireActual('../../assistant_context'); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx index ec93829ac6727..88ff000199daa 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/use_assistant_overlay/index.tsx @@ -82,13 +82,17 @@ export const useAssistantOverlay = ( */ replacements?: Replacements | null ): UseAssistantOverlay => { - const { http, inferenceEnabled } = useAssistantContext(); + const { http, inferenceEnabled, settings } = useAssistantContext(); const { data: connectors } = useLoadConnectors({ http, inferenceEnabled, + settings, }); - const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); + const defaultConnector = useMemo( + () => getDefaultConnector(connectors, settings), + [connectors, settings] + ); const apiConfig = useMemo(() => getGenAiConfig(defaultConnector), [defaultConnector]); const { createConversation } = useConversation(); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx index 22838f307c491..d351660ba4cf9 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -17,6 +17,7 @@ import { AssistantFeatures, defaultAssistantFeatures } from '@kbn/elastic-assist import { ApplicationStart, ChromeStart, UserProfileService } from '@kbn/core/public'; import type { ProductDocBasePluginStart } from '@kbn/product-doc-base-plugin/public'; import { useQuery } from '@tanstack/react-query'; +import type { SettingsStart } from '@kbn/core-ui-settings-browser'; import { updatePromptContexts } from './helpers'; import type { PromptContext, @@ -83,6 +84,7 @@ export interface AssistantProviderProps { nameSpace?: string; navigateToApp: ApplicationStart['navigateToApp']; title?: string; + settings: SettingsStart; toasts?: IToasts; currentAppId: string; productDocBase: ProductDocBasePluginStart; @@ -124,6 +126,7 @@ export interface UseAssistantContext { selectedSettingsTab: SettingsTabs | null; contentReferencesVisible: boolean; showAnonymizedValues: boolean; + settings: SettingsStart; setShowAnonymizedValues: React.Dispatch>; setContentReferencesVisible: React.Dispatch>; setAssistantStreamingEnabled: React.Dispatch>; @@ -156,6 +159,7 @@ export const AssistantProvider: React.FC = ({ alertsIndexPattern, assistantAvailability, assistantTelemetry, + settings, augmentMessageCodeBlocks, docLinks, basePath, @@ -318,6 +322,7 @@ export const AssistantProvider: React.FC = ({ getComments, getUrlForApp, http, + settings, inferenceEnabled, knowledgeBase: { ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, @@ -331,6 +336,7 @@ export const AssistantProvider: React.FC = ({ selectedSettingsTab, // can be undefined from localStorage, if not defined, default to true assistantStreamingEnabled: localStorageStreaming ?? true, + setAssistantStreamingEnabled: setLocalStorageStreaming, setKnowledgeBase: setLocalStorageKnowledgeBase, contentReferencesVisible: contentReferencesVisible ?? true, @@ -368,6 +374,7 @@ export const AssistantProvider: React.FC = ({ basePromptContexts, currentUserAvatar, docLinks, + settings, getComments, getUrlForApp, http, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_selector/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_selector/index.tsx index a0fd1e070d724..26a0bff4db2ef 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_selector/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_selector/index.tsx @@ -15,6 +15,7 @@ import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/c import { euiThemeVars } from '@kbn/ui-theme'; import { some } from 'lodash'; import type { AttackDiscoveryStats } from '@kbn/elastic-assistant-common'; +import { GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR } from '@kbn/management-settings-ids'; import { AttackDiscoveryStatusIndicator } from './attack_discovery_status_indicator'; import { useLoadConnectors } from '../use_load_connectors'; import * as i18n from '../translations'; @@ -57,7 +58,7 @@ export const ConnectorSelector: React.FC = React.memo( setIsOpen, stats = null, }) => { - const { actionTypeRegistry, http, assistantAvailability, inferenceEnabled } = + const { actionTypeRegistry, http, assistantAvailability, inferenceEnabled, settings } = useAssistantContext(); // Connector Modal State const [isConnectorModalVisible, setIsConnectorModalVisible] = useState(false); @@ -68,6 +69,7 @@ export const ConnectorSelector: React.FC = React.memo( const { data: aiConnectors, refetch: refetchConnectors } = useLoadConnectors({ http, inferenceEnabled, + settings, }); const localIsDisabled = isDisabled || !assistantAvailability.hasConnectorsReadPrivilege; @@ -185,6 +187,11 @@ export const ConnectorSelector: React.FC = React.memo( [cleanupAndCloseModal, onConnectorSelectionChange, refetchConnectors] ); + const defaultAIConnectorId = settings.client.get( + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR, + undefined + ); + return ( <> {!connectorExists && !connectorOptions.length ? ( @@ -208,7 +215,7 @@ export const ConnectorSelector: React.FC = React.memo( isOpen={modalForceOpen} onChange={onChange} options={allConnectorOptions} - valueOfSelected={selectedConnectorId} + valueOfSelected={selectedConnectorId ?? defaultAIConnectorId} placeholder={i18n.INLINE_CONNECTOR_PLACEHOLDER} popoverProps={{ panelMinWidth: 400, anchorPosition: 'downRight' }} /> diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx index bde72752cc903..2e09b872fe8d4 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx @@ -35,9 +35,9 @@ export const ConnectorSetup = ({ ); const { setApiConfig } = useConversation(); // Access all conversations so we can add connector to all on initial setup - const { actionTypeRegistry, http, inferenceEnabled } = useAssistantContext(); + const { actionTypeRegistry, http, inferenceEnabled, settings } = useAssistantContext(); - const { refetch: refetchConnectors } = useLoadConnectors({ http, inferenceEnabled }); + const { refetch: refetchConnectors } = useLoadConnectors({ http, inferenceEnabled, settings }); const { data: actionTypes } = useLoadActionTypes({ http }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.test.tsx index e1c7fa93116c5..3dc937aeaa6fa 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.test.tsx @@ -9,6 +9,10 @@ import { waitFor, renderHook } from '@testing-library/react'; import { useLoadConnectors, Props } from '.'; import { mockConnectors } from '../../mock/connectors'; import { TestProviders } from '../../mock/test_providers/test_providers'; +import { + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR, + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY, +} from '@kbn/management-settings-ids'; const mockConnectorsAndExtras = [ ...mockConnectors, @@ -52,7 +56,19 @@ const http = { const toasts = { addError: jest.fn(), }; -const defaultProps = { http, toasts } as unknown as Props; +const settings = { + client: { + get: jest.fn().mockImplementation((settingKey) => { + if (settingKey === GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR) { + return undefined; + } + if (settingKey === GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY) { + return false; + } + }), + }, +}; +const defaultProps = { http, toasts, settings } as unknown as Props; describe('useLoadConnectors', () => { beforeEach(() => { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.tsx index 078a5c483d3e4..f0d19a4fd9b08 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.tsx @@ -9,11 +9,15 @@ import type { UseQueryResult } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; import type { ServerError } from '@kbn/cases-plugin/public/types'; import { loadAllActions as loadConnectors } from '@kbn/triggers-actions-ui-plugin/public/common/constants'; -import type { IHttpFetchError } from '@kbn/core-http-browser'; -import { HttpSetup } from '@kbn/core-http-browser'; -import { IToasts } from '@kbn/core-notifications-browser'; -import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; -import { AIConnector } from '../connector_selector'; +import type { IHttpFetchError, HttpSetup } from '@kbn/core-http-browser'; +import type { IToasts } from '@kbn/core-notifications-browser'; +import type { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; +import type { SettingsStart } from '@kbn/core-ui-settings-browser'; +import { + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR, + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY, +} from '@kbn/management-settings-ids'; +import type { AIConnector } from '../connector_selector'; import * as i18n from '../translations'; /** @@ -26,6 +30,7 @@ export interface Props { http: HttpSetup; toasts?: IToasts; inferenceEnabled?: boolean; + settings: SettingsStart; } const actionTypes = ['.bedrock', '.gen-ai', '.gemini']; @@ -34,6 +39,7 @@ export const useLoadConnectors = ({ http, toasts, inferenceEnabled = false, + settings, }: Props): UseQueryResult => { useEffect(() => { if (inferenceEnabled && !actionTypes.includes('.inference')) { @@ -41,13 +47,20 @@ export const useLoadConnectors = ({ } }, [inferenceEnabled]); + const defaultAiConnectorId = settings.client.get(GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR); + const defaultAiConnectorOnly = settings.client.get( + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY, + false + ); + return useQuery( QUERY_KEY, async () => { const connectors = await loadConnectors({ http }); - return connectors.reduce((acc: AIConnector[], connector) => { + + const allAiConnectors = connectors.flatMap((connector) => { if (!connector.isMissingSecrets && actionTypes.includes(connector.actionTypeId)) { - acc.push({ + const aiConnector: AIConnector = { ...connector, apiProvider: !connector.isPreconfigured && @@ -55,10 +68,23 @@ export const useLoadConnectors = ({ connector?.config?.apiProvider ? (connector?.config?.apiProvider as OpenAiProviderType) : undefined, - }); + }; + return [aiConnector]; } - return acc; - }, []); + return []; + }); + + const availableConnectors = allAiConnectors.filter((connector) => { + if (defaultAiConnectorOnly) { + return connector.id === defaultAiConnectorId; + } + return true; + }); + + if (availableConnectors.length === 0) { + return allAiConnectors; + } + return availableConnectors; }, { retry: false, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx index 9fbd3677d034d..4a716d1a166ba 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx @@ -17,8 +17,10 @@ import { UserProfileService } from '@kbn/core/public'; import { chromeServiceMock } from '@kbn/core-chrome-browser-mocks'; import { of } from 'rxjs'; import { docLinksServiceMock } from '@kbn/core/public/mocks'; -import { AssistantProvider, AssistantProviderProps } from '../../assistant_context'; -import { AssistantAvailability } from '../../assistant_context/types'; +import type { SettingsStart } from '@kbn/core-ui-settings-browser'; +import type { AssistantProviderProps } from '../../assistant_context'; +import { AssistantProvider } from '../../assistant_context'; +import type { AssistantAvailability } from '../../assistant_context/types'; interface Props { assistantAvailability?: AssistantAvailability; @@ -86,6 +88,13 @@ export const TestProvidersComponent: React.FC = ({ http={mockHttp} baseConversations={{}} navigateToApp={mockNavigateToApp} + settings={ + { + client: { + get: jest.fn(), + }, + } as unknown as SettingsStart + } {...providerContext} currentAppId={'test'} productDocBase={{ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/tour/elastic_llm/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/tour/elastic_llm/index.tsx index bdf42647e7b68..b40bc9ce41864 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/tour/elastic_llm/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/tour/elastic_llm/index.tsx @@ -41,7 +41,7 @@ const ElasticLLMCostAwarenessTourComponent: React.FC = ({ zIndex, wrapper = true, // Whether to wrap the children in a div with padding }) => { - const { http, inferenceEnabled } = useAssistantContext(); + const { http, inferenceEnabled, settings } = useAssistantContext(); const { euiTheme } = useEuiTheme(); const tourStorageKey = useTourStorageKey(storageKey); const [tourState, setTourState] = useLocalStorage( @@ -72,6 +72,7 @@ const ElasticLLMCostAwarenessTourComponent: React.FC = ({ const { data: aiConnectors } = useLoadConnectors({ http, inferenceEnabled, + settings, }); const isElasticLLMConnectorSelected = useMemo( () => diff --git a/x-pack/platform/plugins/shared/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/connector_step.tsx b/x-pack/platform/plugins/shared/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/connector_step.tsx index 3c87e0aab7b36..e2abfe39cbad8 100644 --- a/x-pack/platform/plugins/shared/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/connector_step.tsx +++ b/x-pack/platform/plugins/shared/integration_assistant/public/components/create_integration/create_integration_assistant/steps/connector_step/connector_step.tsx @@ -87,7 +87,7 @@ interface ConnectorStepProps { export const ConnectorStep = React.memo(({ connector }) => { const { euiTheme } = useEuiTheme(); - const { http, notifications, triggersActionsUi } = useKibana().services; + const { http, notifications, triggersActionsUi, settings } = useKibana().services; const { setConnector, completeStep } = useActions(); const [connectors, setConnectors] = useState(); @@ -103,7 +103,7 @@ export const ConnectorStep = React.memo(({ connector }) => { isLoading, data: aiConnectors, refetch: refetchConnectors, - } = useLoadConnectors({ http, toasts: notifications.toasts, inferenceEnabled }); + } = useLoadConnectors({ http, toasts: notifications.toasts, inferenceEnabled, settings }); useEffect(() => { if (aiConnectors != null) { diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx index c40f40288d0b6..077683dc3cd0b 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx @@ -20,7 +20,9 @@ import { I18nProvider } from '@kbn/i18n-react'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { EuiThemeProvider } from '@elastic/eui'; -import { DataQualityProvider, DataQualityProviderProps } from '../../data_quality_context'; +import { SettingsStart } from '@kbn/core-ui-settings-browser'; +import type { DataQualityProviderProps } from '../../data_quality_context'; +import { DataQualityProvider } from '../../data_quality_context'; import { ResultsRollupContext } from '../../contexts/results_rollup_context'; import { IndicesCheckContext } from '../../contexts/indices_check_context'; import { UseIndicesCheckReturnValue } from '../../hooks/use_indices_check/types'; @@ -89,6 +91,13 @@ const TestExternalProvidersComponent: React.FC = ({ productDocBase={{ installation: { getStatus: jest.fn(), install: jest.fn(), uninstall: jest.fn() }, }} + settings={ + { + client: { + get: jest.fn(), + }, + } as unknown as SettingsStart + } currentAppId={'securitySolutionUI'} userProfileService={jest.fn() as unknown as UserProfileService} getUrlForApp={jest.fn()} diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/tsconfig.json b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/tsconfig.json index 8334bc4449955..c85c07f8e33d3 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/tsconfig.json +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/tsconfig.json @@ -24,6 +24,7 @@ "@kbn/core-notifications-browser-mocks", "@kbn/core-chrome-browser-mocks", "@kbn/ai-assistant-icon", - "@kbn/react-kibana-context-render" + "@kbn/react-kibana-context-render", + "@kbn/core-ui-settings-browser", ] } diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx index bd56466db5bdc..b58a7bfdca869 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx @@ -144,6 +144,7 @@ export const AssistantProvider: FC> = ({ children }) docLinks, userProfile, chrome, + settings, productDocBase, } = useKibana().services; @@ -243,6 +244,7 @@ export const AssistantProvider: FC> = ({ children }) currentAppId={currentAppId ?? 'securitySolutionUI'} userProfileService={userProfile} chrome={chrome} + settings={settings} > {children} diff --git a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.tsx index d9fa0b8ae607c..886903896a316 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.tsx @@ -49,12 +49,13 @@ export const ID = 'attackDiscoveryQuery'; const AttackDiscoveryPageComponent: React.FC = () => { const { - services: { uiSettings }, + services: { uiSettings, settings }, } = useKibana(); const { http } = useAssistantContext(); const { data: aiConnectors } = useLoadConnectors({ http, + settings, }); // for showing / hiding anonymized data: diff --git a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/use_attack_discovery/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/use_attack_discovery/index.tsx index 9cc7b7c475c09..5472ea1e3e954 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/use_attack_discovery/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/use_attack_discovery/index.tsx @@ -59,9 +59,12 @@ export const useAttackDiscovery = ({ const { http, notifications: { toasts }, + settings, } = useKibana().services; + const { data: aiConnectors } = useLoadConnectors({ http, + settings, }); // generation can take a long time, so we calculate an approximate future time: diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index 42f785e64d7e9..1413594e4df5f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -14,6 +14,7 @@ import type { UserProfileService } from '@kbn/core/public'; import { chromeServiceMock } from '@kbn/core-chrome-browser-mocks'; import { of } from 'rxjs'; import { docLinksServiceMock } from '@kbn/core/public/mocks'; +import type { SettingsStart } from '@kbn/core-ui-settings-browser'; import { BASE_SECURITY_CONVERSATIONS } from '../../assistant/content/conversations'; interface Props { @@ -65,6 +66,13 @@ export const MockAssistantProviderComponent: React.FC = ({ }} userProfileService={mockUserProfileService} chrome={chrome} + settings={ + { + client: { + get: jest.fn(), + }, + } as unknown as SettingsStart + } > {children} diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_scan.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_scan.tsx index 95beeb5699cd0..7387d794a9af9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_scan.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/insights/workflow_insights_scan.tsx @@ -40,9 +40,10 @@ export const WorkflowInsightsScanSection = ({ const CONNECTOR_ID_LOCAL_STORAGE_KEY = 'connectorId'; const spaceId = useSpaceId() ?? 'default'; - const { http } = useKibana().services; + const { http, settings } = useKibana().services; const { data: aiConnectors } = useLoadConnectors({ http, + settings, }); const { canWriteWorkflowInsights } = useUserPrivileges().endpointPrivileges; diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx index 5b13f9b58b540..e8ba115b7e810 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx @@ -59,16 +59,21 @@ export const AssistantCard: OnboardingCardComponent = ({ const [selectedConnectorId, setSelectedConnectorId] = useStoredAssistantConnectorId(spaceId); - const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); - - const { setApiConfig } = useConversation(); - const { http, assistantAvailability: { isAssistantEnabled }, baseConversations, getLastConversationId, + settings, } = useAssistantContext(); + + const defaultConnector = useMemo( + () => getDefaultConnector(connectors, settings), + [connectors, settings] + ); + + const { setApiConfig } = useConversation(); + const { allSystemPrompts, conversations, diff --git a/x-pack/solutions/security/plugins/security_solution/tsconfig.json b/x-pack/solutions/security/plugins/security_solution/tsconfig.json index 0cc9998fbc14e..3191bec1e33f9 100644 --- a/x-pack/solutions/security/plugins/security_solution/tsconfig.json +++ b/x-pack/solutions/security/plugins/security_solution/tsconfig.json @@ -242,5 +242,6 @@ "@kbn/inference-endpoint-ui-common", "@kbn/core-metrics-server", "@kbn/ai-assistant-default-llm-setting", + "@kbn/core-ui-settings-browser", ] }