diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts index 01c9cbf773592..7faac2cd63542 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts @@ -19,7 +19,7 @@ export interface UseFindPromptsParams { context: { isAssistantEnabled: boolean; httpFetch: HttpHandler; - toasts: IToasts; + toasts?: IToasts; }; signal?: AbortSignal | undefined; params: FindSecurityAIPromptsRequestQuery; @@ -82,7 +82,7 @@ const getPrompts = async ({ query, }: { httpFetch: HttpHandler; - toasts: IToasts; + toasts?: IToasts; signal?: AbortSignal | undefined; query: FindSecurityAIPromptsRequestQuery; }) => { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx index 86bd31318349c..ef2b75e86d64f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx @@ -10,27 +10,42 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; import { css } from '@emotion/react'; import { PromptResponse } from '@kbn/elastic-assistant-common'; import { AssistantBeacon } from '@kbn/ai-assistant-icon'; +import { useAssistantContext } from '../../..'; +import { StarterPrompts } from './starter_prompts'; import { SystemPrompt } from '../prompt_editor/system_prompt'; import { SetupKnowledgeBaseButton } from '../../knowledge_base/setup_knowledge_base_button'; import * as i18n from '../translations'; interface Props { + connectorId?: string; currentSystemPromptId: string | undefined; isSettingsModalVisible: boolean; setIsSettingsModalVisible: Dispatch>; setCurrentSystemPromptId: (promptId: string | undefined) => void; allSystemPrompts: PromptResponse[]; + setUserPrompt: React.Dispatch>; } +const starterPromptWrapperClassName = css` + max-width: 95%; +`; export const EmptyConvo: React.FC = ({ allSystemPrompts, + connectorId, currentSystemPromptId, isSettingsModalVisible, setCurrentSystemPromptId, setIsSettingsModalVisible, + setUserPrompt, }) => { + const { assistantAvailability } = useAssistantContext(); return ( - + = ({ + {assistantAvailability.isStarterPromptsEnabled && ( + + + + )} ); }; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx index f93b62bbe67eb..77d1cea588aeb 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx @@ -43,6 +43,7 @@ interface Props { http: HttpSetup; setCurrentSystemPromptId: (promptId: string | undefined) => void; setIsSettingsModalVisible: Dispatch>; + setUserPrompt: React.Dispatch>; } export const AssistantBody: FunctionComponent = ({ @@ -58,6 +59,7 @@ export const AssistantBody: FunctionComponent = ({ isSettingsModalVisible, isWelcomeSetup, setIsSettingsModalVisible, + setUserPrompt, }) => { const { euiTheme } = useEuiTheme(); @@ -109,7 +111,7 @@ export const AssistantBody: FunctionComponent = ({ return ( - + {isLoading ? ( = ({ isSettingsModalVisible={isSettingsModalVisible} setCurrentSystemPromptId={setCurrentSystemPromptId} setIsSettingsModalVisible={setIsSettingsModalVisible} + setUserPrompt={setUserPrompt} + connectorId={currentConversation?.apiConfig?.connectorId} /> ) : ( { + return { + useFindPrompts: jest.fn(), + useAssistantContext: () => ({ + assistantAvailability: { + isAssistantEnabled: true, + }, + http: { fetch: {} }, + }), + }; +}); + +describe('StarterPrompts', () => { + it('should return an empty array if no prompts are provided', () => { + expect(getAllPromptIds(promptGroups)).toEqual([ + 'starterPromptTitle1', + 'starterPromptDescription1', + 'starterPromptIcon1', + 'starterPromptPrompt1', + 'starterPromptDescription2', + 'starterPromptTitle2', + 'starterPromptIcon2', + 'starterPromptPrompt2', + 'starterPromptDescription3', + 'starterPromptTitle3', + 'starterPromptIcon3', + 'starterPromptPrompt3', + 'starterPromptDescription4', + 'starterPromptTitle4', + 'starterPromptIcon4', + 'starterPromptPrompt4', + ]); + }); + it('should return the correct prompt groups with fetched prompts', () => { + const response = formatPromptGroups(mockResponse); + expect(response).toEqual([ + { + description: 'starterPromptDescription1 from API yall', + icon: 'starterPromptIcon1 from API yall', + prompt: 'starterPromptPrompt1 from API yall', + title: 'starterPromptTitle1 from API yall', + }, + { + description: 'starterPromptDescription2 from API yall', + icon: 'starterPromptIcon2 from API yall', + prompt: 'starterPromptPrompt2 from API yall', + title: 'starterPromptTitle2 from API yall', + }, + { + description: 'starterPromptDescription3 from API yall', + icon: 'starterPromptIcon3 from API yall', + prompt: 'starterPromptPrompt3 from API yall', + title: 'starterPromptTitle3 from API yall', + }, + // starterPrompt Group4 should not exist because starterPromptIcon4 is not in the mockResponse + ]); + }); + it('the component renders correctly with valid props', () => { + (useFindPrompts as jest.Mock).mockReturnValue({ data: { prompts: mockResponse } }); + const { getByTestId } = render( + + + + ); + expect(getByTestId('starterPromptPrompt2 from API yall')).toBeInTheDocument(); + }); + it('calls setUserPrompt when a prompt is selected', () => { + (useFindPrompts as jest.Mock).mockReturnValue({ data: { prompts: mockResponse } }); + const { getByTestId } = render( + + + + ); + fireEvent.click(getByTestId('starterPromptPrompt2 from API yall')); + expect(testProps.setUserPrompt).toHaveBeenCalledWith('starterPromptPrompt2 from API yall'); + }); +}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx new file mode 100644 index 0000000000000..3b914b8555ae4 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo, useCallback } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { css } from '@emotion/css'; +import { PromptItemArray } from '@kbn/elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen'; +import { useAssistantContext, useFindPrompts } from '../../..'; + +interface Props { + connectorId?: string; + setUserPrompt: React.Dispatch>; +} +const starterPromptClassName = css` + max-width: 50%; + min-width: calc(50% - 8px); +`; + +const starterPromptInnerClassName = css` + text-align: center !important; +`; +interface PromptGroup { + description: string; + title: string; + icon: string; + prompt: string; +} +// these are the promptIds (Security AI Prompts integration) for each of the starter prompts fields +export const promptGroups = [ + { + title: 'starterPromptTitle1', + description: 'starterPromptDescription1', + icon: 'starterPromptIcon1', + prompt: 'starterPromptPrompt1', + }, + { + description: 'starterPromptDescription2', + title: 'starterPromptTitle2', + icon: 'starterPromptIcon2', + prompt: 'starterPromptPrompt2', + }, + { + description: 'starterPromptDescription3', + title: 'starterPromptTitle3', + icon: 'starterPromptIcon3', + prompt: 'starterPromptPrompt3', + }, + { + description: 'starterPromptDescription4', + title: 'starterPromptTitle4', + icon: 'starterPromptIcon4', + prompt: 'starterPromptPrompt4', + }, +]; + +export const StarterPrompts: React.FC = ({ connectorId, setUserPrompt }) => { + const { + assistantAvailability: { isAssistantEnabled }, + http, + toasts, + } = useAssistantContext(); + const { + data: { prompts: actualPrompts }, + } = useFindPrompts({ + context: { + isAssistantEnabled, + httpFetch: http.fetch, + toasts, + }, + params: { + connector_id: connectorId, + prompt_group_id: 'aiAssistant', + prompt_ids: getAllPromptIds(promptGroups), + }, + }); + + const fetchedPromptGroups = useMemo(() => { + if (!actualPrompts.length) { + return []; + } + return formatPromptGroups(actualPrompts); + }, [actualPrompts]); + + const onSelectPrompt = useCallback( + (prompt: string) => { + setUserPrompt(prompt); + }, + [setUserPrompt] + ); + + return ( + + {fetchedPromptGroups.map(({ description, title, icon, prompt }) => ( + + onSelectPrompt(prompt)} + className={starterPromptInnerClassName} + > + + + + +

{title}

+
+ {description} +
+
+ ))} +
+ ); +}; + +export const getAllPromptIds = (pGroups: PromptGroup[]) => { + return pGroups.map((promptGroup: PromptGroup) => [...Object.values(promptGroup)]).flat(); +}; + +export const formatPromptGroups = (actualPrompts: PromptItemArray): PromptGroup[] => + promptGroups.reduce((acc, promptGroup) => { + const foundPrompt = (field: keyof PromptGroup) => + actualPrompts.find((p) => p.promptId === promptGroup[field])?.prompt; + const toBePrompt = { + prompt: foundPrompt('prompt'), + icon: foundPrompt('icon'), + title: foundPrompt('title'), + description: foundPrompt('description'), + }; + if (toBePrompt.prompt && toBePrompt.icon && toBePrompt.title && toBePrompt.description) { + acc.push(toBePrompt as PromptGroup); + } + return acc; + }, []); 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 e9c494e0d7c08..e144ef4466d7f 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 @@ -541,6 +541,7 @@ const AssistantComponent: React.FC = ({ isLoading={isInitialLoad} isSettingsModalVisible={isSettingsModalVisible} isWelcomeSetup={isWelcomeSetup} + setUserPrompt={setUserPrompt} setCurrentSystemPromptId={setCurrentSystemPromptId} setIsSettingsModalVisible={setIsSettingsModalVisible} /> diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx index 3c8ecbb2a70dc..ff4e306844ee3 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx @@ -74,6 +74,7 @@ export interface AssistantAvailability { hasUpdateAIAssistantAnonymization: boolean; // When true, user has `Edit` privilege for `Global Knowledge Base` hasManageGlobalKnowledgeBase: boolean; + isStarterPromptsEnabled: boolean; } export type GetAssistantMessages = (commentArgs: { 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 8cf59f167c021..04ea24b3ffc5f 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 @@ -42,6 +42,7 @@ export const mockAssistantAvailability: AssistantAvailability = { hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, }; /** A utility for wrapping children in the providers required to run tests */ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts index 569ed723f2995..540c506a98cdf 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts @@ -201,4 +201,6 @@ export interface UseAssistantAvailability { hasUpdateAIAssistantAnonymization: boolean; // When true, user has `Edit` privilege for `Global Knowledge Base` hasManageGlobalKnowledgeBase: boolean; + // remove once product has signed off on prompt text + isStarterPromptsEnabled: boolean; } 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 b4a635ebc4bcf..e9d5c049f68ad 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 @@ -59,6 +59,7 @@ const TestExternalProvidersComponent: React.FC = ({ hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, }; const queryClient = new QueryClient({ defaultOptions: { diff --git a/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.test.tsx b/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.test.tsx index be2f7854093da..2bed09bd68558 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.test.tsx +++ b/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.test.tsx @@ -40,6 +40,9 @@ describe('AssistantProvider', () => { http: mockHttp, notifications, elasticAssistantSharedState, + featureFlags: { + getBooleanValue: jest.fn().mockReturnValue(false), + }, }} > {children} @@ -63,6 +66,7 @@ describe('AssistantProvider', () => { hasSearchAILakeConfigurations: expect.any(Boolean), hasUpdateAIAssistantAnonymization: expect.any(Boolean), isAssistantEnabled: expect.any(Boolean), + isStarterPromptsEnabled: expect.any(Boolean), }), assistantFeatures: expect.objectContaining({ advancedEsqlGeneration: expect.any(Boolean), diff --git a/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.test.ts index 07e66144da26b..2b7c8765d9479 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.test.ts @@ -47,6 +47,9 @@ describe('useAssistantAvailability', () => { }, }, }, + featureFlags: { + getBooleanValue: jest.fn().mockReturnValue(true), + }, }, } as unknown as ReturnType); @@ -58,6 +61,7 @@ describe('useAssistantAvailability', () => { hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, }); @@ -88,6 +92,9 @@ describe('useAssistantAvailability', () => { }, }, }, + featureFlags: { + getBooleanValue: jest.fn().mockReturnValue(false), + }, }, } as unknown as ReturnType); @@ -99,6 +106,7 @@ describe('useAssistantAvailability', () => { hasConnectorsAllPrivilege: false, hasConnectorsReadPrivilege: false, isAssistantEnabled: false, + isStarterPromptsEnabled: false, hasUpdateAIAssistantAnonymization: false, hasManageGlobalKnowledgeBase: false, }); @@ -129,6 +137,9 @@ describe('useAssistantAvailability', () => { }, }, }, + featureFlags: { + getBooleanValue: jest.fn().mockReturnValue(true), + }, }, } as unknown as ReturnType); @@ -140,6 +151,7 @@ describe('useAssistantAvailability', () => { hasConnectorsAllPrivilege: false, hasConnectorsReadPrivilege: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, hasUpdateAIAssistantAnonymization: false, hasManageGlobalKnowledgeBase: false, }); @@ -155,6 +167,9 @@ describe('useAssistantAvailability', () => { application: { capabilities: {}, }, + featureFlags: { + getBooleanValue: jest.fn().mockReturnValue(true), + }, }, } as unknown as ReturnType); @@ -166,6 +181,7 @@ describe('useAssistantAvailability', () => { hasConnectorsAllPrivilege: false, hasConnectorsReadPrivilege: false, isAssistantEnabled: true, + isStarterPromptsEnabled: true, hasUpdateAIAssistantAnonymization: false, hasManageGlobalKnowledgeBase: false, }); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.ts b/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.ts index 54a99d76f5902..9c78ca410ef8c 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.ts @@ -11,9 +11,13 @@ import { useKibana } from '../../context/typed_kibana_context/typed_kibana_conte import { useLicense } from '../licence/use_licence'; +export const STARTER_PROMPTS_FEATURE_FLAG = 'elasticAssistant.starterPromptsEnabled' as const; export const useAssistantAvailability = (): UseAssistantAvailability => { const isEnterprise = useLicense().isEnterprise(); - const capabilities = useKibana().services.application.capabilities; + const { + application: { capabilities }, + featureFlags, + } = useKibana().services; const hasAssistantPrivilege = capabilities[ASSISTANT_FEATURE_ID]?.['ai-assistant'] === true; const hasUpdateAIAssistantAnonymization = @@ -32,11 +36,14 @@ export const useAssistantAvailability = (): UseAssistantAvailability => { capabilities.actions?.delete === true && capabilities.actions?.save === true; + const isStarterPromptsEnabled = featureFlags.getBooleanValue(STARTER_PROMPTS_FEATURE_FLAG, false); + return { hasSearchAILakeConfigurations, hasAssistantPrivilege, hasConnectorsAllPrivilege, hasConnectorsReadPrivilege, + isStarterPromptsEnabled, isAssistantEnabled: isEnterprise, hasUpdateAIAssistantAnonymization, hasManageGlobalKnowledgeBase, diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts index 703e29c8f30a4..4e5e86ea73368 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts @@ -29,6 +29,22 @@ import { RULE_ANALYSIS, DATA_QUALITY_ANALYSIS, ALERT_EVALUATION, + starterPromptTitle1, + starterPromptDescription1, + starterPromptIcon1, + starterPromptPrompt1, + starterPromptDescription2, + starterPromptTitle2, + starterPromptIcon2, + starterPromptPrompt2, + starterPromptDescription3, + starterPromptTitle3, + starterPromptIcon3, + starterPromptPrompt3, + starterPromptDescription4, + starterPromptTitle4, + starterPromptIcon4, + starterPromptPrompt4, } from './prompts'; export const promptGroupId = { @@ -41,9 +57,6 @@ export const promptGroupId = { }; export const promptDictionary = { - alertEvaluation: `alertEvaluation`, - dataQualityAnalysis: 'dataQualityAnalysis', - ruleAnalysis: 'ruleAnalysis', alertSummary: `alertSummary`, alertSummarySystemPrompt: `alertSummarySystemPrompt`, systemPrompt: `systemPrompt`, @@ -68,6 +81,26 @@ export const promptDictionary = { 'defendInsights-incompatibleAntivirusEventsEndpointId', defendInsightsIncompatibleAntivirusEventsValue: 'defendInsights-incompatibleAntivirusEventsValue', // context prompts + alertEvaluation: `alertEvaluation`, + dataQualityAnalysis: 'dataQualityAnalysis', + ruleAnalysis: 'ruleAnalysis', + // starter prompts + starterPromptDescription1: 'starterPromptDescription1', + starterPromptTitle1: 'starterPromptTitle1', + starterPromptIcon1: 'starterPromptIcon1', + starterPromptPrompt1: 'starterPromptPrompt1', + starterPromptDescription2: 'starterPromptDescription2', + starterPromptTitle2: 'starterPromptTitle2', + starterPromptIcon2: 'starterPromptIcon2', + starterPromptPrompt2: 'starterPromptPrompt2', + starterPromptDescription3: 'starterPromptDescription3', + starterPromptTitle3: 'starterPromptTitle3', + starterPromptIcon3: 'starterPromptIcon3', + starterPromptPrompt3: 'starterPromptPrompt3', + starterPromptDescription4: 'starterPromptDescription4', + starterPromptTitle4: 'starterPromptTitle4', + starterPromptIcon4: 'starterPromptIcon4', + starterPromptPrompt4: 'starterPromptPrompt4', }; export const localPrompts: Prompt[] = [ @@ -287,4 +320,84 @@ export const localPrompts: Prompt[] = [ default: RULE_ANALYSIS, }, }, + { + promptId: promptDictionary.starterPromptDescription1, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptDescription1 }, + }, + { + promptId: promptDictionary.starterPromptTitle1, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptTitle1 }, + }, + { + promptId: promptDictionary.starterPromptIcon1, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptIcon1 }, + }, + { + promptId: promptDictionary.starterPromptPrompt1, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptPrompt1 }, + }, + { + promptId: promptDictionary.starterPromptDescription2, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptDescription2 }, + }, + { + promptId: promptDictionary.starterPromptTitle2, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptTitle2 }, + }, + { + promptId: promptDictionary.starterPromptIcon2, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptIcon2 }, + }, + { + promptId: promptDictionary.starterPromptPrompt2, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptPrompt2 }, + }, + { + promptId: promptDictionary.starterPromptDescription3, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptDescription3 }, + }, + { + promptId: promptDictionary.starterPromptTitle3, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptTitle3 }, + }, + { + promptId: promptDictionary.starterPromptIcon3, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptIcon3 }, + }, + { + promptId: promptDictionary.starterPromptPrompt3, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptPrompt3 }, + }, + { + promptId: promptDictionary.starterPromptDescription4, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptDescription4 }, + }, + { + promptId: promptDictionary.starterPromptTitle4, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptTitle4 }, + }, + { + promptId: promptDictionary.starterPromptIcon4, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptIcon4 }, + }, + { + promptId: promptDictionary.starterPromptPrompt4, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: starterPromptPrompt4 }, + }, ]; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts index 94ac4c2af1579..bf8b30a6b884a 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts @@ -255,3 +255,67 @@ Formatting Requirements: - Use concise, actionable language. - Include relevant emojis in section headers for visual clarity (e.g., 📝, 🛡️, 🔍, 📚). `; + +export const starterPromptTitle1 = 'Alerts'; +export const starterPromptDescription1 = 'Most important alerts from the last 24 hrs'; +export const starterPromptIcon1 = 'bell'; +export const starterPromptPrompt1 = `🔍 Identify and Prioritize Today's Most Critical Alerts +Provide a structured summary of today's most significant alerts, including: +🛡️ Critical Alerts Overview +Highlight the most impactful alerts based on risk scores, severity, and affected entities. +Summarize key details such as alert name, risk score, severity, and associated users or hosts. +📊 Risk Context +Include user and host risk scores for each alert to provide additional context. +Reference relevant MITRE ATT&CK techniques, with hyperlinks to the official MITRE pages. +🚨 Why These Alerts Matter +Explain why these alerts are critical, focusing on potential business impact, lateral movement risks, or sensitive data exposure. +🔧 Recommended Next Steps +Provide actionable triage steps for each alert, such as: +Investigating the alert in Elastic Security. +Reviewing related events in Timelines. +Analyzing user and host behavior using Entity Analytics. +Suggest Elastic Defend endpoint response actions (e.g., isolate host, kill process, retrieve/delete file), with links to Elastic documentation. +📚 Documentation and References +Include direct links to Elastic Security documentation and relevant MITRE ATT&CK pages for further guidance. +Use markdown headers, tables, and code blocks for clarity. Include relevant emojis for visual distinction and ensure the response is concise, actionable, and tailored to Elastic Security workflows.`; +export const starterPromptDescription2 = 'Latest Elastic Security Labs research'; +export const starterPromptTitle2 = 'Research'; +export const starterPromptIcon2 = 'launch'; +export const starterPromptPrompt2 = `Retrieve and summarize the latest Elastic Security Labs articles one by one sorted by latest at the top. Ensure the response includes: +Article Summaries +Title and Link: Provide the title of each article with a hyperlink to the original content. +Publication Date: Include the date the article was published. +Key Insights: Summarize the main points or findings of each article in concise bullet points. +Relevant Threats or Techniques: Highlight any specific malware, attack techniques, or adversary behaviors discussed, with references to MITRE ATT&CK techniques (include hyperlinks to the official MITRE pages). +Practical Applications +Detection and Response Guidance: Provide actionable steps or recommendations based on the article's content, tailored for Elastic Security workflows. +Elastic Security Features: Highlight any Elastic Security features, detection rules, or tools mentioned in the articles, with links to relevant documentation. +Example Queries: If applicable, include example ES|QL or OSQuery Manager queries inspired by the article's findings, formatted as code blocks. +Documentation and Resources +Elastic Security Labs: Include a link to the Elastic Security Labs homepage. +Additional References: Provide links to any related Elastic documentation or external resources mentioned in the articles. +Formatting Requirements +Use markdown headers, tables, and code blocks for clarity. +Organize the response into visually distinct sections. +Use concise, actionable language.`; +export const starterPromptDescription3 = 'Generate ES|QL Queries'; +export const starterPromptTitle3 = 'Query'; +export const starterPromptIcon3 = 'esqlVis'; +export const starterPromptPrompt3 = + 'I need an Elastic ES|QL query to achieve the following goal:\n' + + 'Goal/Requirement:\n' + + '\n' + + 'Please:\n' + + 'Generate the ES|QL Query: Provide a complete ES|QL query tailored to the stated goal.\n' + + 'Explain the Query: Offer a brief explanation of each part of the query, including filters, fields, and logic used.\n' + + 'Optimize for Elastic Security: Suggest additional filters, aggregations, or enhancements to make the query more efficient and actionable within Elastic Security workflows.\n' + + 'Provide Documentation Links: Include links to relevant Elastic Security documentation for deeper understanding.\n' + + 'Formatting Requirements:\n' + + 'Use code blocks for the ES|QL query.\n' + + 'Include concise explanations in bullet points for clarity.\n' + + 'Highlight any advanced ES|QL features used in the query.\n'; +export const starterPromptDescription4 = 'Discover the types of questions you can ask'; +export const starterPromptTitle4 = 'Suggest'; +export const starterPromptIcon4 = 'sparkles'; +export const starterPromptPrompt4 = + 'Can you provide examples of questions I can ask about Elastic Security, such as investigating alerts, running ES|QL queries, incident response, or threat intelligence?'; 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 3b72222a2475f..0b19233080c46 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 @@ -40,6 +40,7 @@ export const MockAssistantProviderComponent: React.FC = ({ hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, }; const chrome = chromeServiceMock.createStartContract(); chrome.getChromeStyle$.mockReturnValue(of('classic')); diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts index 3c49c6563b89a..040d755e26117 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts @@ -15,7 +15,8 @@ import type { import { newContentReferencesStoreMock } from '@kbn/elastic-assistant-common/impl/content_references/content_references_store/__mocks__/content_references_store.mock'; import type { AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; import { Document } from 'langchain/document'; - +import { getIsKnowledgeBaseInstalled } from '@kbn/elastic-assistant-plugin/server/routes/helpers'; +jest.mock('@kbn/elastic-assistant-plugin/server/routes/helpers'); describe('SecurityLabsTool', () => { const contentReferencesStore = newContentReferencesStoreMock(); const getKnowledgeBaseDocumentEntries = jest.fn(); @@ -100,5 +101,23 @@ In previous publications,`, expect(result).toContain('Citation: {reference(exampleContentReferenceId)}'); }); + it('Responds with The "AI Assistant knowledge base" needs to be installed... when no docs and no kb install', async () => { + getKnowledgeBaseDocumentEntries.mockResolvedValue([]); + (getIsKnowledgeBaseInstalled as jest.Mock).mockResolvedValue(false); + const tool = SECURITY_LABS_KNOWLEDGE_BASE_TOOL.getTool(defaultArgs) as DynamicStructuredTool; + + const result = await tool.func({ query: 'What is Kibana Security?', product: 'kibana' }); + + expect(result).toContain('The "AI Assistant knowledge base" needs to be installed'); + }); + it('Responds with empty response when no docs and kb is installed', async () => { + getKnowledgeBaseDocumentEntries.mockResolvedValue([]); + (getIsKnowledgeBaseInstalled as jest.Mock).mockResolvedValue(true); + const tool = SECURITY_LABS_KNOWLEDGE_BASE_TOOL.getTool(defaultArgs) as DynamicStructuredTool; + + const result = await tool.func({ query: 'What is Kibana Security?', product: 'kibana' }); + + expect(result).toEqual('[]'); + }); }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts index d9f4f75cd7716..c37d1e4e73690 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts @@ -18,6 +18,7 @@ import { knowledgeBaseReference, } from '@kbn/elastic-assistant-common/impl/content_references/references'; import { Document } from 'langchain/document'; +import { getIsKnowledgeBaseInstalled } from '@kbn/elastic-assistant-plugin/server/routes/helpers'; import { APP_UI_ID } from '../../../../common'; const toolDetails = { @@ -52,6 +53,14 @@ export const SECURITY_LABS_KNOWLEDGE_BASE_TOOL: AssistantTool = { query: input.question, }); + if (docs.length === 0) { + const isKnowledgeBaseInstalled = await getIsKnowledgeBaseInstalled(kbDataClient); + if (!isKnowledgeBaseInstalled) { + // prompt to help user install knowledge base + return 'The "AI Assistant knowledge base" needs to be installed, containing the Security Labs content. Navigate to the Knowledge Base page in the AI Assistant Settings to install it.'; + } + } + const citedDocs = docs.map((doc) => { let reference: ContentReference | undefined; try {