diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts index edf6debd4e298..2bba137a37cb3 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts @@ -27,10 +27,10 @@ interface UseChatComplete { // useChatComplete uses the same api as useSendMessage (post_actions_connector_execute) but without requiring conversationId/apiConfig // it is meant to be used for one-off messages that don't require a conversation export const useChatComplete = ({ connectorId }: { connectorId: string }): UseChatComplete => { - const { alertsIndexPattern, http, traceOptions } = useAssistantContext(); + const { alertsIndexPattern, http, traceOptions, settings } = useAssistantContext(); const [isLoading, setIsLoading] = useState(false); const abortController = useRef(new AbortController()); - const { data: connectors } = useLoadConnectors({ http, inferenceEnabled: true }); + const { data: connectors } = useLoadConnectors({ http, inferenceEnabled: true, settings }); const actionTypeId = useMemo( () => connectors?.find(({ id }) => id === connectorId)?.actionTypeId ?? '.gen-ai', [connectors, connectorId] 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 c6db8517c3838..4b14192a65b2c 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 @@ -17,7 +17,7 @@ import { ConversationSharedState, } from '@kbn/elastic-assistant-common'; import { ShareSelect } from '../../share_conversation/share_select'; -import type { Conversation } from '../../../..'; +import { useAssistantContext, type Conversation } from '../../../..'; import * as i18n from './translations'; import * as i18nModel from '../../../connectorland/models/model_selector/translations'; @@ -57,8 +57,10 @@ export const ConversationSettingsEditor: React.FC { + const { settings } = useAssistantContext(); const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ http, + settings, }); const [conversationUpdates, setConversationUpdates] = useState(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 7140995f50c1b..7339f29bdefb7 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 @@ -7,6 +7,8 @@ import { getDefaultConnector, getOptionalRequestParams } from './helpers'; 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', () => { @@ -25,41 +27,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 8c1fb7c3630e0..e11c19c96dce8 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 @@ -5,6 +5,8 @@ * 2.0. */ +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 } from './api'; import type { ClientMessage } from '../assistant_context/types'; @@ -40,13 +42,30 @@ export const getMessageFromRawResponse = ( * @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 8fc24d70a8b54..79450793a1d5e 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 @@ -91,6 +91,7 @@ const AssistantComponent: React.FC = ({ showAnonymizedValues, setContentReferencesVisible, setShowAnonymizedValues, + settings, } = useAssistantContext(); const [selectedPromptContexts, setSelectedPromptContexts] = useState< @@ -123,8 +124,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 spaceId = useAssistantSpaceId(); const { getLastConversation, setLastConversation } = useAssistantLastConversation({ spaceId }); const lastConversationFromLocalStorage = useMemo( 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 5517f96d9cc19..bd46b34a10254 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 @@ -69,6 +69,7 @@ export const AssistantSettings: React.FC = React.memo( selectedSettingsTab, setSelectedSettingsTab, toasts, + settings, } = useAssistantContext(); useEffect(() => { @@ -81,6 +82,7 @@ export const AssistantSettings: React.FC = React.memo( const { data: connectors } = useLoadConnectors({ http, + settings, }); const { conversationsSettingsBulkActions, 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 123399fa577f8..b33987d060fab 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 @@ -24,6 +24,10 @@ import { SYSTEM_PROMPTS_TAB, } from './const'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR, + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY, +} from '@kbn/management-settings-ids'; const mockSetSelectedSettingsTab = jest.fn(); @@ -38,6 +42,19 @@ const mockContext = { isAssistantManagementEnabled: true, hasConnectorsAllPrivilege: 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; + }), + }, + }, selectedSettingsTab: null, setSelectedSettingsTab: mockSetSelectedSettingsTab, navigateToApp: jest.fn(), 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 9a9a40b594830..72564d5cb83bd 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 @@ -56,6 +56,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( setSelectedSettingsTab, navigateToApp, assistantAvailability: { isAssistantManagementEnabled, hasConnectorsAllPrivilege }, + settings, } = useAssistantContext(); useEffect(() => { @@ -68,8 +69,12 @@ export const AssistantSettingsManagement: React.FC = React.memo( 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 e34c129bffe0e..abfd31135e5d1 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/settings/search_ai_lake_configurations_settings_management.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/search_ai_lake_configurations_settings_management.test.tsx index a49bd120fadf7..b28953d0ee143 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/search_ai_lake_configurations_settings_management.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/search_ai_lake_configurations_settings_management.test.tsx @@ -24,6 +24,10 @@ import { SYSTEM_PROMPTS_TAB, } from './const'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR, + GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY, +} from '@kbn/management-settings-ids'; const mockContext = { basePromptContexts: MOCK_QUICK_PROMPTS, @@ -34,6 +38,19 @@ 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; + }), + }, + }, }; const mockDataViews = { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/search_ai_lake_configurations_settings_management.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/search_ai_lake_configurations_settings_management.tsx index 5c0222525aa7f..e8fc6611f5c13 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/search_ai_lake_configurations_settings_management.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/search_ai_lake_configurations_settings_management.tsx @@ -55,6 +55,7 @@ export const SearchAILakeConfigurationsSettingsManagement: React.FC = Rea http, selectedSettingsTab, setSelectedSettingsTab, + settings, } = useAssistantContext(); useEffect(() => { @@ -66,8 +67,12 @@ export const SearchAILakeConfigurationsSettingsManagement: React.FC = Rea const { data: connectors } = useLoadConnectors({ http, + settings, }); - const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); + const defaultConnector = useMemo( + () => getDefaultConnector(connectors, settings), + [connectors, settings] + ); const { euiTheme } = useEuiTheme(); 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 a22502d70df7f..faab3dd4d588e 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 @@ -23,6 +23,7 @@ import type { } 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, @@ -88,6 +89,7 @@ export interface AssistantProviderProps { nameSpace?: string; navigateToApp: ApplicationStart['navigateToApp']; title?: string; + settings: SettingsStart; toasts?: IToasts; currentAppId: string; productDocBase: ProductDocBasePluginStart; @@ -129,6 +131,7 @@ export interface UseAssistantContext { selectedSettingsTab: ModalSettingsTabs | null; contentReferencesVisible: boolean; showAnonymizedValues: boolean; + settings: SettingsStart; setShowAnonymizedValues: React.Dispatch>; setContentReferencesVisible: React.Dispatch>; setAssistantStreamingEnabled: React.Dispatch>; @@ -179,6 +182,7 @@ export const useAssistantContextValue = (props: AssistantProviderProps): UseAssi getUrlForApp, http, inferenceEnabled = false, + settings, navigateToApp, nameSpace = DEFAULT_ASSISTANT_NAMESPACE, productDocBase, @@ -318,6 +322,7 @@ export const useAssistantContextValue = (props: AssistantProviderProps): UseAssi getComments, getUrlForApp, http, + settings, inferenceEnabled, knowledgeBase: { ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, @@ -331,6 +336,7 @@ export const useAssistantContextValue = (props: AssistantProviderProps): UseAssi selectedSettingsTab, // can be undefined from localStorage, if not defined, default to true assistantStreamingEnabled: localStorageStreaming ?? true, + setAssistantStreamingEnabled: setLocalStorageStreaming, setKnowledgeBase: setLocalStorageKnowledgeBase, contentReferencesVisible: contentReferencesVisible ?? true, @@ -365,6 +371,7 @@ export const useAssistantContextValue = (props: AssistantProviderProps): UseAssi basePromptContexts, currentUser, 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 5f25d1e574903..50e7d7af2e633 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 @@ -21,6 +21,7 @@ import type { ActionConnector, ActionType } from '@kbn/triggers-actions-ui-plugi import type { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; 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'; @@ -59,7 +60,7 @@ export const ConnectorSelector: React.FC = React.memo( stats = null, }) => { const { euiTheme } = useEuiTheme(); - const { actionTypeRegistry, http, assistantAvailability, inferenceEnabled } = + const { actionTypeRegistry, http, assistantAvailability, inferenceEnabled, settings } = useAssistantContext(); // Connector Modal State const [isConnectorModalVisible, setIsConnectorModalVisible] = useState(false); @@ -70,6 +71,7 @@ export const ConnectorSelector: React.FC = React.memo( const { data: aiConnectors, refetch: refetchConnectors } = useLoadConnectors({ http, inferenceEnabled, + settings, }); const localIsDisabled = isDisabled || !assistantAvailability.hasConnectorsReadPrivilege; @@ -187,6 +189,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 ? ( @@ -216,7 +223,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 1d612f27b2f3d..af1067f93148f 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 297dae585e7d2..b042cd55f2e3e 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 @@ -10,6 +10,10 @@ import type { Props } from '.'; import { useLoadConnectors } 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, @@ -53,7 +57,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 edd6d83f15a9b..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 @@ -12,6 +12,11 @@ import { loadAllActions as loadConnectors } from '@kbn/triggers-actions-ui-plugi 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'; @@ -25,6 +30,7 @@ export interface Props { http: HttpSetup; toasts?: IToasts; inferenceEnabled?: boolean; + settings: SettingsStart; } const actionTypes = ['.bedrock', '.gen-ai', '.gemini']; @@ -33,6 +39,7 @@ export const useLoadConnectors = ({ http, toasts, inferenceEnabled = false, + settings, }: Props): UseQueryResult => { useEffect(() => { if (inferenceEnabled && !actionTypes.includes('.inference')) { @@ -40,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 && @@ -54,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 0efb4ebec3273..6f13b25ef63dd 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,6 +17,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 type { AssistantProviderProps } from '../../assistant_context'; import { AssistantProvider, useAssistantContextValue } from '../../assistant_context'; import type { AssistantAvailability } from '../../assistant_context/types'; @@ -97,7 +98,12 @@ export const TestProvidersComponent: React.FC = ({ }, userProfileService: jest.fn() as unknown as UserProfileService, chrome, - }; + settings: { + client: { + get: jest.fn(), + }, + } as unknown as SettingsStart, + } as AssistantProviderProps; return ( 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 a9c099535017a..d3edd42c9c727 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 @@ -36,7 +36,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( @@ -67,6 +67,7 @@ const ElasticLLMCostAwarenessTourComponent: React.FC = ({ const { data: aiConnectors } = useLoadConnectors({ http, inferenceEnabled, + settings, }); const isElasticLLMConnectorSelected = useMemo( () => diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/tsconfig.json b/x-pack/platform/packages/shared/kbn-elastic-assistant/tsconfig.json index be43cf052ad5d..26392425880ca 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/tsconfig.json +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/tsconfig.json @@ -47,5 +47,7 @@ "@kbn/inference-common", "@kbn/core-doc-links-browser-mocks", "@kbn/core-user-profile-common", + "@kbn/management-settings-ids", + "@kbn/core-ui-settings-browser", ] } diff --git a/x-pack/platform/plugins/private/gen_ai_settings/public/components/default_ai_connector/default_ai_connector.tsx b/x-pack/platform/plugins/private/gen_ai_settings/public/components/default_ai_connector/default_ai_connector.tsx index d36084e7b3c77..f617fe13abe27 100644 --- a/x-pack/platform/plugins/private/gen_ai_settings/public/components/default_ai_connector/default_ai_connector.tsx +++ b/x-pack/platform/plugins/private/gen_ai_settings/public/components/default_ai_connector/default_ai_connector.tsx @@ -172,7 +172,10 @@ export const DefaultAIConnector: React.FC = ({ connectors }) => { onChange={onChangeDefaultLlm} isDisabled={fields[GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR]?.isReadOnly} isLoading={connectors.loading} - isInvalid={selectedOptions.length === 0 && !connectors.loading} + isInvalid={ + (selectedOptions.length === 0 && !connectors.loading) || + (defaultLlmOnlyValue && selectedOptions[0]?.value === NO_DEFAULT_CONNECTOR) + } /> diff --git a/x-pack/platform/plugins/private/gen_ai_settings/public/contexts/settings_context.tsx b/x-pack/platform/plugins/private/gen_ai_settings/public/contexts/settings_context.tsx index 66562d3468734..d8a5edef221ec 100644 --- a/x-pack/platform/plugins/private/gen_ai_settings/public/contexts/settings_context.tsx +++ b/x-pack/platform/plugins/private/gen_ai_settings/public/contexts/settings_context.tsx @@ -129,8 +129,6 @@ const Settings = ({ settingsKeys }: { settingsKeys: string[] }) => { if (updateErrors.length > 0) { throw combineErrors(updateErrors); } - } catch (e) { - throw e; } finally { if (subscription) { subscription.unsubscribe(); diff --git a/x-pack/platform/plugins/shared/automatic_import/public/components/create_integration/create_automatic_import/steps/connector_step/connector_step.tsx b/x-pack/platform/plugins/shared/automatic_import/public/components/create_integration/create_automatic_import/steps/connector_step/connector_step.tsx index e281061f09bd2..bd211f85c94aa 100644 --- a/x-pack/platform/plugins/shared/automatic_import/public/components/create_integration/create_automatic_import/steps/connector_step/connector_step.tsx +++ b/x-pack/platform/plugins/shared/automatic_import/public/components/create_integration/create_automatic_import/steps/connector_step/connector_step.tsx @@ -94,7 +94,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(); @@ -110,7 +110,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 c2bbb8865fce8..d1e7c49c25ae3 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 @@ -23,6 +23,7 @@ import { EuiThemeProvider } from '@elastic/eui'; import type { AssistantProviderProps } from '@kbn/elastic-assistant/impl/assistant_context'; import { useAssistantContextValue } from '@kbn/elastic-assistant/impl/assistant_context'; +import type { 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'; @@ -98,6 +99,11 @@ const TestExternalProvidersComponent: React.FC = ({ userProfileService: jest.fn() as unknown as UserProfileService, getUrlForApp: jest.fn(), chrome, + settings: { + client: { + get: jest.fn(), + }, + } as unknown as SettingsStart, }; return ( 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..c9574559abb45 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/elastic_assistant/public/src/context/assistant_context/assistant_provider.tsx b/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.tsx index 568abd9bb2dc7..809e16d9721f1 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.tsx +++ b/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.tsx @@ -39,6 +39,7 @@ export function AssistantProvider({ children }: { children: React.ReactElement } chrome, productDocBase, elasticAssistantSharedState, + settings, } = useKibana().services; const inferenceEnabled = useInferenceEnabled(); @@ -104,6 +105,7 @@ export function AssistantProvider({ children }: { children: React.ReactElement } userProfileService: userProfile, chrome, getUrlForApp, + settings, }); useEffect(() => { 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 ccef12c2c0fb9..630a283f42cc3 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 @@ -51,13 +51,14 @@ export const ID = 'attackDiscoveryQuery'; const AttackDiscoveryPageComponent: React.FC = () => { const { - services: { uiSettings }, + services: { uiSettings, settings }, } = useKibana(); const { http, inferenceEnabled } = useAssistantContext(); const { data: aiConnectors } = useLoadConnectors({ http, inferenceEnabled, + settings, }); // for showing / hiding anonymized data: diff --git a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/settings_flyout/schedule/create_flyout/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/settings_flyout/schedule/create_flyout/index.tsx index b1b9bf6d57608..438f8cc6d3500 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/settings_flyout/schedule/create_flyout/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/settings_flyout/schedule/create_flyout/index.tsx @@ -58,9 +58,10 @@ export const CreateFlyout: React.FC = React.memo(({ onClose }) => { services: { uiSettings }, } = useKibana(); - const { alertsIndexPattern, http } = useAssistantContext(); + const { alertsIndexPattern, http, settings } = useAssistantContext(); const { data: aiConnectors, isLoading: isLoadingConnectors } = useLoadConnectors({ http, + settings, }); const { sourcererDataView } = useSourcererDataView(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/settings_flyout/schedule/details_flyout/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/settings_flyout/schedule/details_flyout/index.tsx index 8142953f364ed..867ea72ad9d4b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/settings_flyout/schedule/details_flyout/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/settings_flyout/schedule/details_flyout/index.tsx @@ -74,9 +74,10 @@ export const DetailsFlyout: React.FC = React.memo(({ scheduleId, onClose } = useKibana(); const { euiTheme } = useEuiTheme(); - const { alertsIndexPattern, http } = useAssistantContext(); + const { alertsIndexPattern, http, settings } = useAssistantContext(); const { data: aiConnectors, isLoading: isLoadingConnectors } = useLoadConnectors({ http, + settings, }); const { data: { schedule } = { schedule: undefined }, isLoading: isLoadingSchedule } = useGetAttackDiscoverySchedule({ 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 c586fd601a48d..24ccecdbcea29 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 @@ -55,9 +55,12 @@ export const useAttackDiscovery = ({ const { http, notifications: { toasts }, + settings, } = useKibana().services; + const { data: aiConnectors } = useLoadConnectors({ http, + settings, }); // loading boilerplate: 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 33e3312f52e51..c34773c1e3213 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 @@ -15,6 +15,7 @@ import { chromeServiceMock } from '@kbn/core-chrome-browser-mocks'; import { of } from 'rxjs'; import { useAssistantContextValue } from '@kbn/elastic-assistant/impl/assistant_context'; import { docLinksServiceMock } from '@kbn/core/public/mocks'; +import type { SettingsStart } from '@kbn/core-ui-settings-browser'; interface Props { assistantAvailability?: AssistantAvailability; @@ -69,6 +70,11 @@ export const MockAssistantProviderComponent: React.FC = ({ userProfileService: mockUserProfileService, chrome, getUrlForApp: jest.fn(), + settings: { + client: { + get: jest.fn(), + }, + } as unknown as SettingsStart, }); return {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 989ecbd48ef4c..acd79ae7f207c 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 @@ -44,9 +44,10 @@ export const WorkflowInsightsScanSection = ({ const CONNECTOR_ID_LOCAL_STORAGE_KEY = 'connectorId'; const spaceId = useSpaceId(); - const { http } = useKibana().services; + const { http, settings } = useKibana().services; const { data: aiConnectors } = useLoadConnectors({ http, + settings, }); const { canWriteWorkflowInsights } = useUserPrivileges().endpointPrivileges; const { inferenceEnabled } = useAssistantContext(); 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 7591cfa52a672..663b3dd1c1c62 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 @@ -46,14 +46,19 @@ export const AssistantCard: OnboardingCardComponent = ({ const [selectedConnectorId, setSelectedConnectorId] = useStoredAssistantConnectorId(spaceId); - const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); - - const { setApiConfig } = useConversation(); - const { http, assistantAvailability: { isAssistantEnabled }, + settings, } = useAssistantContext(); + + const defaultConnector = useMemo( + () => getDefaultConnector(connectors, settings), + [connectors, settings] + ); + + const { setApiConfig } = useConversation(); + const { getLastConversation, setLastConversation } = useAssistantLastConversation({ spaceId }); const { allSystemPrompts,