mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Security Assistant] Starter prompts (#224981)
This commit is contained in:
parent
e140d226bd
commit
b82ab8acb8
18 changed files with 580 additions and 9 deletions
|
@ -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;
|
||||
}) => {
|
||||
|
|
|
@ -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<SetStateAction<boolean>>;
|
||||
setCurrentSystemPromptId: (promptId: string | undefined) => void;
|
||||
allSystemPrompts: PromptResponse[];
|
||||
setUserPrompt: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
}
|
||||
const starterPromptWrapperClassName = css`
|
||||
max-width: 95%;
|
||||
`;
|
||||
|
||||
export const EmptyConvo: React.FC<Props> = ({
|
||||
allSystemPrompts,
|
||||
connectorId,
|
||||
currentSystemPromptId,
|
||||
isSettingsModalVisible,
|
||||
setCurrentSystemPromptId,
|
||||
setIsSettingsModalVisible,
|
||||
setUserPrompt,
|
||||
}) => {
|
||||
const { assistantAvailability } = useAssistantContext();
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" data-test-subj="emptyConvo">
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
data-test-subj="emptyConvo"
|
||||
direction="column"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
|
@ -64,6 +79,11 @@ export const EmptyConvo: React.FC<Props> = ({
|
|||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
{assistantAvailability.isStarterPromptsEnabled && (
|
||||
<EuiFlexItem grow={false} css={starterPromptWrapperClassName}>
|
||||
<StarterPrompts connectorId={connectorId} setUserPrompt={setUserPrompt} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -43,6 +43,7 @@ interface Props {
|
|||
http: HttpSetup;
|
||||
setCurrentSystemPromptId: (promptId: string | undefined) => void;
|
||||
setIsSettingsModalVisible: Dispatch<SetStateAction<boolean>>;
|
||||
setUserPrompt: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
}
|
||||
|
||||
export const AssistantBody: FunctionComponent<Props> = ({
|
||||
|
@ -58,6 +59,7 @@ export const AssistantBody: FunctionComponent<Props> = ({
|
|||
isSettingsModalVisible,
|
||||
isWelcomeSetup,
|
||||
setIsSettingsModalVisible,
|
||||
setUserPrompt,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
|
@ -109,7 +111,7 @@ export const AssistantBody: FunctionComponent<Props> = ({
|
|||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem>
|
||||
{isLoading ? (
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj="animatedLogo"
|
||||
|
@ -127,6 +129,8 @@ export const AssistantBody: FunctionComponent<Props> = ({
|
|||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setCurrentSystemPromptId={setCurrentSystemPromptId}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
setUserPrompt={setUserPrompt}
|
||||
connectorId={currentConversation?.apiConfig?.connectorId}
|
||||
/>
|
||||
) : (
|
||||
<EuiPanel
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* 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 {
|
||||
formatPromptGroups,
|
||||
getAllPromptIds,
|
||||
promptGroups,
|
||||
StarterPrompts,
|
||||
} from './starter_prompts';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../mock/test_providers/test_providers';
|
||||
import React from 'react';
|
||||
import { useFindPrompts } from '../../..';
|
||||
const mockResponse = [
|
||||
{
|
||||
promptId: 'starterPromptTitle1',
|
||||
prompt: 'starterPromptTitle1 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptDescription1',
|
||||
prompt: 'starterPromptDescription1 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptIcon1',
|
||||
prompt: 'starterPromptIcon1 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptPrompt1',
|
||||
prompt: 'starterPromptPrompt1 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptDescription2',
|
||||
prompt: 'starterPromptDescription2 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptTitle2',
|
||||
prompt: 'starterPromptTitle2 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptIcon2',
|
||||
prompt: 'starterPromptIcon2 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptPrompt2',
|
||||
prompt: 'starterPromptPrompt2 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptDescription3',
|
||||
prompt: 'starterPromptDescription3 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptTitle3',
|
||||
prompt: 'starterPromptTitle3 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptIcon3',
|
||||
prompt: 'starterPromptIcon3 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptPrompt3',
|
||||
prompt: 'starterPromptPrompt3 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptDescription4',
|
||||
prompt: 'starterPromptDescription4 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptTitle4',
|
||||
prompt: 'starterPromptTitle4 from API yall',
|
||||
},
|
||||
{
|
||||
promptId: 'starterPromptPrompt4',
|
||||
prompt: 'starterPromptPrompt4 from API yall',
|
||||
},
|
||||
];
|
||||
|
||||
const testProps = {
|
||||
setUserPrompt: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('../../..', () => {
|
||||
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(
|
||||
<TestProviders>
|
||||
<StarterPrompts {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
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(
|
||||
<TestProviders>
|
||||
<StarterPrompts {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
fireEvent.click(getByTestId('starterPromptPrompt2 from API yall'));
|
||||
expect(testProps.setUserPrompt).toHaveBeenCalledWith('starterPromptPrompt2 from API yall');
|
||||
});
|
||||
});
|
|
@ -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<React.SetStateAction<string | null>>;
|
||||
}
|
||||
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<Props> = ({ 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 (
|
||||
<EuiFlexGroup direction="row" gutterSize="m" wrap>
|
||||
{fetchedPromptGroups.map(({ description, title, icon, prompt }) => (
|
||||
<EuiFlexItem key={prompt} className={starterPromptClassName}>
|
||||
<EuiPanel
|
||||
paddingSize="m"
|
||||
hasShadow={false}
|
||||
hasBorder
|
||||
data-test-subj={prompt}
|
||||
onClick={() => onSelectPrompt(prompt)}
|
||||
className={starterPromptInnerClassName}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiIcon type={icon} size="xl" />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiTitle size="xs">
|
||||
<h2>{title}</h2>
|
||||
</EuiTitle>
|
||||
<EuiText size="s">{description}</EuiText>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const getAllPromptIds = (pGroups: PromptGroup[]) => {
|
||||
return pGroups.map((promptGroup: PromptGroup) => [...Object.values(promptGroup)]).flat();
|
||||
};
|
||||
|
||||
export const formatPromptGroups = (actualPrompts: PromptItemArray): PromptGroup[] =>
|
||||
promptGroups.reduce<PromptGroup[]>((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;
|
||||
}, []);
|
|
@ -541,6 +541,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
isLoading={isInitialLoad}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
isWelcomeSetup={isWelcomeSetup}
|
||||
setUserPrompt={setUserPrompt}
|
||||
setCurrentSystemPromptId={setCurrentSystemPromptId}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ const TestExternalProvidersComponent: React.FC<TestExternalProvidersProps> = ({
|
|||
hasUpdateAIAssistantAnonymization: true,
|
||||
hasManageGlobalKnowledgeBase: true,
|
||||
isAssistantEnabled: true,
|
||||
isStarterPromptsEnabled: true,
|
||||
};
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -47,6 +47,9 @@ describe('useAssistantAvailability', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
featureFlags: {
|
||||
getBooleanValue: jest.fn().mockReturnValue(true),
|
||||
},
|
||||
},
|
||||
} as unknown as ReturnType<typeof useKibana>);
|
||||
|
||||
|
@ -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<typeof useKibana>);
|
||||
|
||||
|
@ -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<typeof useKibana>);
|
||||
|
||||
|
@ -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<typeof useKibana>);
|
||||
|
||||
|
@ -166,6 +181,7 @@ describe('useAssistantAvailability', () => {
|
|||
hasConnectorsAllPrivilege: false,
|
||||
hasConnectorsReadPrivilege: false,
|
||||
isAssistantEnabled: true,
|
||||
isStarterPromptsEnabled: true,
|
||||
hasUpdateAIAssistantAnonymization: false,
|
||||
hasManageGlobalKnowledgeBase: false,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 },
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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' +
|
||||
'<Insert your specific requirement or goal here, e.g., "Identify all failed login attempts from a specific IP address within the last 24 hours.">\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?';
|
||||
|
|
|
@ -40,6 +40,7 @@ export const MockAssistantProviderComponent: React.FC<Props> = ({
|
|||
hasUpdateAIAssistantAnonymization: true,
|
||||
hasManageGlobalKnowledgeBase: true,
|
||||
isAssistantEnabled: true,
|
||||
isStarterPromptsEnabled: true,
|
||||
};
|
||||
const chrome = chromeServiceMock.createStartContract();
|
||||
chrome.getChromeStyle$.mockReturnValue(of('classic'));
|
||||
|
|
|
@ -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('[]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue