mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Security AI Assistant] Persist prompts (#187040)
Moving prompts persistence layer from the local storage to the server side data stream `.kibana-elastic-ai-assistant-prompts` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
aad2239c32
commit
0a0bb1498e
90 changed files with 1629 additions and 623 deletions
|
@ -52,3 +52,6 @@ export * from './knowledge_base/bulk_crud_knowledge_base_route.gen';
|
|||
export * from './knowledge_base/common_attributes.gen';
|
||||
export * from './knowledge_base/crud_knowledge_base_route.gen';
|
||||
export * from './knowledge_base/find_knowledge_base_entries_route.gen';
|
||||
|
||||
export * from './prompts/find_prompts_route.gen';
|
||||
export { PromptResponse, PromptTypeEnum } from './prompts/bulk_crud_prompts_route.gen';
|
||||
|
|
|
@ -34,6 +34,14 @@ export const PromptDetailsInError = z.object({
|
|||
name: z.string().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Prompt type
|
||||
*/
|
||||
export type PromptType = z.infer<typeof PromptType>;
|
||||
export const PromptType = z.enum(['system', 'quick']);
|
||||
export type PromptTypeEnum = typeof PromptType.enum;
|
||||
export const PromptTypeEnum = PromptType.enum;
|
||||
|
||||
export type NormalizedPromptError = z.infer<typeof NormalizedPromptError>;
|
||||
export const NormalizedPromptError = z.object({
|
||||
message: z.string(),
|
||||
|
@ -47,11 +55,13 @@ export const PromptResponse = z.object({
|
|||
id: NonEmptyString,
|
||||
timestamp: NonEmptyString.optional(),
|
||||
name: z.string(),
|
||||
promptType: z.string(),
|
||||
promptType: PromptType,
|
||||
content: z.string(),
|
||||
categories: z.array(z.string()).optional(),
|
||||
color: z.string().optional(),
|
||||
isNewConversationDefault: z.boolean().optional(),
|
||||
isDefault: z.boolean().optional(),
|
||||
isShared: z.boolean().optional(),
|
||||
consumer: z.string().optional(),
|
||||
updatedAt: z.string().optional(),
|
||||
updatedBy: z.string().optional(),
|
||||
createdAt: z.string().optional(),
|
||||
|
@ -107,20 +117,24 @@ export const BulkActionBase = z.object({
|
|||
export type PromptCreateProps = z.infer<typeof PromptCreateProps>;
|
||||
export const PromptCreateProps = z.object({
|
||||
name: z.string(),
|
||||
promptType: z.string(),
|
||||
promptType: PromptType,
|
||||
content: z.string(),
|
||||
color: z.string().optional(),
|
||||
categories: z.array(z.string()).optional(),
|
||||
isNewConversationDefault: z.boolean().optional(),
|
||||
isDefault: z.boolean().optional(),
|
||||
isShared: z.boolean().optional(),
|
||||
consumer: z.string().optional(),
|
||||
});
|
||||
|
||||
export type PromptUpdateProps = z.infer<typeof PromptUpdateProps>;
|
||||
export const PromptUpdateProps = z.object({
|
||||
id: z.string(),
|
||||
content: z.string().optional(),
|
||||
color: z.string().optional(),
|
||||
categories: z.array(z.string()).optional(),
|
||||
isNewConversationDefault: z.boolean().optional(),
|
||||
isDefault: z.boolean().optional(),
|
||||
isShared: z.boolean().optional(),
|
||||
consumer: z.string().optional(),
|
||||
});
|
||||
|
||||
export type PerformBulkActionRequestBody = z.infer<typeof PerformBulkActionRequestBody>;
|
||||
|
|
|
@ -78,6 +78,13 @@ components:
|
|||
required:
|
||||
- id
|
||||
|
||||
PromptType:
|
||||
type: string
|
||||
description: Prompt type
|
||||
enum:
|
||||
- system
|
||||
- quick
|
||||
|
||||
NormalizedPromptError:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -111,15 +118,21 @@ components:
|
|||
name:
|
||||
type: string
|
||||
promptType:
|
||||
type: string
|
||||
$ref: '#/components/schemas/PromptType'
|
||||
content:
|
||||
type: string
|
||||
categories:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
color:
|
||||
type: string
|
||||
isNewConversationDefault:
|
||||
type: boolean
|
||||
isDefault:
|
||||
type: boolean
|
||||
isShared:
|
||||
type: boolean
|
||||
consumer:
|
||||
type: string
|
||||
updatedAt:
|
||||
type: string
|
||||
updatedBy:
|
||||
|
@ -231,15 +244,21 @@ components:
|
|||
name:
|
||||
type: string
|
||||
promptType:
|
||||
type: string
|
||||
$ref: '#/components/schemas/PromptType'
|
||||
content:
|
||||
type: string
|
||||
color:
|
||||
type: string
|
||||
categories:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
isNewConversationDefault:
|
||||
type: boolean
|
||||
isDefault:
|
||||
type: boolean
|
||||
isShared:
|
||||
type: boolean
|
||||
consumer:
|
||||
type: string
|
||||
|
||||
PromptUpdateProps:
|
||||
type: object
|
||||
|
@ -250,9 +269,15 @@ components:
|
|||
type: string
|
||||
content:
|
||||
type: string
|
||||
color:
|
||||
type: string
|
||||
categories:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
isNewConversationDefault:
|
||||
type: boolean
|
||||
isDefault:
|
||||
type: boolean
|
||||
isShared:
|
||||
type: boolean
|
||||
consumer:
|
||||
type: string
|
||||
|
|
|
@ -11,6 +11,7 @@ import { API_ERROR } from '../translations';
|
|||
import { getOptionalRequestParams } from '../helpers';
|
||||
import { TraceOptions } from '../types';
|
||||
export * from './conversations';
|
||||
export * from './prompts';
|
||||
|
||||
export interface FetchConnectorExecuteAction {
|
||||
conversationId: string;
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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 {
|
||||
API_VERSIONS,
|
||||
ELASTIC_AI_ASSISTANT_PROMPTS_URL_BULK_ACTION,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { IToasts } from '@kbn/core-notifications-browser';
|
||||
import { bulkUpdatePrompts } from './bulk_update_prompts';
|
||||
import { PromptTypeEnum } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
|
||||
const prompt1 = {
|
||||
id: 'field1',
|
||||
content: 'Prompt 1',
|
||||
name: 'test',
|
||||
promptType: PromptTypeEnum.system,
|
||||
};
|
||||
const prompt2 = {
|
||||
...prompt1,
|
||||
id: 'field2',
|
||||
content: 'Prompt 2',
|
||||
name: 'test2',
|
||||
promptType: PromptTypeEnum.system,
|
||||
};
|
||||
const toasts = {
|
||||
addError: jest.fn(),
|
||||
};
|
||||
describe('bulkUpdatePrompts', () => {
|
||||
let httpMock: ReturnType<typeof httpServiceMock.createSetupContract>;
|
||||
|
||||
beforeEach(() => {
|
||||
httpMock = httpServiceMock.createSetupContract();
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should send a POST request with the correct parameters and receive a successful response', async () => {
|
||||
const promptsActions = {
|
||||
create: [],
|
||||
update: [],
|
||||
delete: { ids: [] },
|
||||
};
|
||||
|
||||
await bulkUpdatePrompts(httpMock, promptsActions);
|
||||
|
||||
expect(httpMock.fetch).toHaveBeenCalledWith(ELASTIC_AI_ASSISTANT_PROMPTS_URL_BULK_ACTION, {
|
||||
method: 'POST',
|
||||
version: API_VERSIONS.internal.v1,
|
||||
body: JSON.stringify({
|
||||
create: [],
|
||||
update: [],
|
||||
delete: { ids: [] },
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform the prompts dictionary to an array of fields to create', async () => {
|
||||
const promptsActions = {
|
||||
create: [prompt1, prompt2],
|
||||
update: [],
|
||||
delete: { ids: [] },
|
||||
};
|
||||
|
||||
await bulkUpdatePrompts(httpMock, promptsActions);
|
||||
|
||||
expect(httpMock.fetch).toHaveBeenCalledWith(ELASTIC_AI_ASSISTANT_PROMPTS_URL_BULK_ACTION, {
|
||||
method: 'POST',
|
||||
version: API_VERSIONS.internal.v1,
|
||||
body: JSON.stringify({
|
||||
create: [prompt1, prompt2],
|
||||
update: [],
|
||||
delete: { ids: [] },
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform the prompts dictionary to an array of fields to update', async () => {
|
||||
const promptsActions = {
|
||||
update: [prompt1, prompt2],
|
||||
delete: { ids: [] },
|
||||
};
|
||||
|
||||
await bulkUpdatePrompts(httpMock, promptsActions);
|
||||
|
||||
expect(httpMock.fetch).toHaveBeenCalledWith(ELASTIC_AI_ASSISTANT_PROMPTS_URL_BULK_ACTION, {
|
||||
method: 'POST',
|
||||
version: API_VERSIONS.internal.v1,
|
||||
body: JSON.stringify({
|
||||
update: [prompt1, prompt2],
|
||||
delete: { ids: [] },
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error with the correct message when receiving an unsuccessful response', async () => {
|
||||
httpMock.fetch.mockResolvedValue({
|
||||
success: false,
|
||||
attributes: {
|
||||
errors: [
|
||||
{
|
||||
statusCode: 400,
|
||||
message: 'Error updating prompt',
|
||||
prompts: [{ id: prompt1.id, name: prompt1.content }],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const promptsActions = {
|
||||
create: [],
|
||||
update: [prompt1],
|
||||
delete: { ids: [] },
|
||||
};
|
||||
await bulkUpdatePrompts(httpMock, promptsActions, toasts as unknown as IToasts);
|
||||
expect(toasts.addError.mock.calls[0][0]).toEqual(
|
||||
new Error('Error message: Error updating prompt for prompt Prompt 1')
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle cases where result.attributes.errors is undefined', async () => {
|
||||
httpMock.fetch.mockResolvedValue({
|
||||
success: false,
|
||||
attributes: {},
|
||||
});
|
||||
const promptsActions = {
|
||||
create: [],
|
||||
update: [],
|
||||
delete: { ids: [] },
|
||||
};
|
||||
|
||||
await bulkUpdatePrompts(httpMock, promptsActions, toasts as unknown as IToasts);
|
||||
expect(toasts.addError.mock.calls[0][0]).toEqual(new Error(''));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { HttpSetup, IToasts } from '@kbn/core/public';
|
||||
import {
|
||||
ELASTIC_AI_ASSISTANT_PROMPTS_URL_BULK_ACTION,
|
||||
API_VERSIONS,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import {
|
||||
PerformBulkActionRequestBody,
|
||||
PerformBulkActionResponse,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
|
||||
export const bulkUpdatePrompts = async (
|
||||
http: HttpSetup,
|
||||
prompts: PerformBulkActionRequestBody,
|
||||
toasts?: IToasts
|
||||
) => {
|
||||
try {
|
||||
const result = await http.fetch<PerformBulkActionResponse>(
|
||||
ELASTIC_AI_ASSISTANT_PROMPTS_URL_BULK_ACTION,
|
||||
{
|
||||
method: 'POST',
|
||||
version: API_VERSIONS.internal.v1,
|
||||
body: JSON.stringify(prompts),
|
||||
}
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
const serverError = result.attributes.errors
|
||||
?.map(
|
||||
(e) =>
|
||||
`${e.status_code ? `Error code: ${e.status_code}. ` : ''}Error message: ${
|
||||
e.message
|
||||
} for prompt ${e.prompts.map((c) => c.name).join(',')}`
|
||||
)
|
||||
.join(',\n');
|
||||
throw new Error(serverError);
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
toasts?.addError(error.body && error.body.message ? new Error(error.body.message) : error, {
|
||||
title: i18n.translate('xpack.elasticAssistant.prompts.bulkActionspromptsError', {
|
||||
defaultMessage: 'Error updating prompts {error}',
|
||||
values: { error },
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './bulk_update_prompts';
|
||||
export * from './use_fetch_prompts';
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { act, renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import type { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
import { useFetchPrompts } from './use_fetch_prompts';
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
import { API_VERSIONS, defaultAssistantFeatures } from '@kbn/elastic-assistant-common';
|
||||
|
||||
const http = {
|
||||
fetch: jest.fn().mockResolvedValue(defaultAssistantFeatures),
|
||||
} as unknown as HttpSetup;
|
||||
|
||||
jest.mock('../../../assistant_context');
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient();
|
||||
// eslint-disable-next-line react/display-name
|
||||
return ({ children }: { children: ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('useFetchPrompts', () => {
|
||||
(useAssistantContext as jest.Mock).mockReturnValue({
|
||||
http,
|
||||
assistantAvailability: {
|
||||
isAssistantEnabled: true,
|
||||
},
|
||||
});
|
||||
it(`should make http request to fetch prompts`, async () => {
|
||||
renderHook(() => useFetchPrompts(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const { waitForNextUpdate } = renderHook(() => useFetchPrompts());
|
||||
await waitForNextUpdate();
|
||||
expect(http.fetch).toHaveBeenCalledWith('/internal/elastic_assistant/prompts/_find', {
|
||||
method: 'GET',
|
||||
query: {
|
||||
page: 1,
|
||||
per_page: 1000,
|
||||
filter: 'consumer:*',
|
||||
},
|
||||
version: API_VERSIONS.internal.v1,
|
||||
signal: undefined,
|
||||
});
|
||||
|
||||
expect(http.fetch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 { FindPromptsResponse } from '@kbn/elastic-assistant-common/impl/schemas/prompts/find_prompts_route.gen';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { API_VERSIONS, ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND } from '@kbn/elastic-assistant-common';
|
||||
import { HttpSetup, IToasts } from '@kbn/core/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
|
||||
export interface UseFetchPromptsParams {
|
||||
signal?: AbortSignal | undefined;
|
||||
consumer?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* API call for fetching prompts for current spaceId
|
||||
*
|
||||
* @param {Object} options - The options object.
|
||||
* @param {string} options.consumer - prompt consumer
|
||||
* @param {AbortSignal} [options.signal] - AbortSignal
|
||||
*
|
||||
* @returns {useQuery} hook for getting the status of the prompts
|
||||
*/
|
||||
|
||||
export const useFetchPrompts = (payload?: UseFetchPromptsParams) => {
|
||||
const {
|
||||
assistantAvailability: { isAssistantEnabled },
|
||||
http,
|
||||
} = useAssistantContext();
|
||||
|
||||
const QUERY = {
|
||||
page: 1,
|
||||
per_page: 1000, // Continue use in-memory paging till the new design will be ready
|
||||
filter: `consumer:${payload?.consumer ?? '*'}`,
|
||||
};
|
||||
|
||||
const CACHING_KEYS = [
|
||||
ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND,
|
||||
QUERY.page,
|
||||
QUERY.per_page,
|
||||
QUERY.filter,
|
||||
API_VERSIONS.internal.v1,
|
||||
];
|
||||
|
||||
return useQuery<FindPromptsResponse, unknown, FindPromptsResponse>(
|
||||
CACHING_KEYS,
|
||||
async () =>
|
||||
http.fetch(ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND, {
|
||||
method: 'GET',
|
||||
version: API_VERSIONS.internal.v1,
|
||||
query: QUERY,
|
||||
signal: payload?.signal,
|
||||
}),
|
||||
{
|
||||
initialData: {
|
||||
data: [],
|
||||
page: 1,
|
||||
perPage: 5,
|
||||
total: 0,
|
||||
},
|
||||
placeholderData: {
|
||||
data: [],
|
||||
page: 1,
|
||||
perPage: 5,
|
||||
total: 0,
|
||||
},
|
||||
keepPreviousData: true,
|
||||
enabled: isAssistantEnabled,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const getPrompts = async ({
|
||||
http,
|
||||
signal,
|
||||
toasts,
|
||||
}: {
|
||||
http: HttpSetup;
|
||||
toasts: IToasts;
|
||||
signal?: AbortSignal | undefined;
|
||||
}) => {
|
||||
try {
|
||||
return await http.fetch<FindPromptsResponse>(ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND, {
|
||||
method: 'GET',
|
||||
version: API_VERSIONS.internal.v1,
|
||||
signal,
|
||||
});
|
||||
} catch (error) {
|
||||
toasts.addError(error.body && error.body.message ? new Error(error.body.message) : error, {
|
||||
title: i18n.translate('xpack.elasticAssistant.prompts.getPromptsError', {
|
||||
defaultMessage: 'Error fetching prompts',
|
||||
}),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -47,6 +48,9 @@ interface OwnProps {
|
|||
refetchConversationsState: () => Promise<void>;
|
||||
onConversationCreate: () => Promise<void>;
|
||||
isAssistantEnabled: boolean;
|
||||
refetchPrompts?: (
|
||||
options?: RefetchOptions & RefetchQueryFilters<unknown>
|
||||
) => Promise<QueryObserverResult<unknown, unknown>>;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
|
@ -74,6 +78,7 @@ export const AssistantHeaderFlyout: React.FC<Props> = ({
|
|||
refetchConversationsState,
|
||||
onConversationCreate,
|
||||
isAssistantEnabled,
|
||||
refetchPrompts,
|
||||
}) => {
|
||||
const showAnonymizedValuesChecked = useMemo(
|
||||
() =>
|
||||
|
@ -164,6 +169,7 @@ export const AssistantHeaderFlyout: React.FC<Props> = ({
|
|||
conversationsLoaded={conversationsLoaded}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
isFlyoutMode={true}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ const testProps = {
|
|||
refetchConversationsState: jest.fn(),
|
||||
anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] },
|
||||
refetchAnonymizationFieldsResults: jest.fn(),
|
||||
allPrompts: [],
|
||||
};
|
||||
|
||||
jest.mock('../../connectorland/use_load_connectors', () => ({
|
||||
|
|
|
@ -14,9 +14,11 @@ import {
|
|||
EuiSwitch,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
|
||||
import { css } from '@emotion/react';
|
||||
import { DocLinksStart } from '@kbn/core-doc-links-browser';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
import { Conversation } from '../../..';
|
||||
import { AssistantTitle } from '../assistant_title';
|
||||
|
@ -40,6 +42,10 @@ interface OwnProps {
|
|||
conversations: Record<string, Conversation>;
|
||||
conversationsLoaded: boolean;
|
||||
refetchConversationsState: () => Promise<void>;
|
||||
allPrompts: PromptResponse[];
|
||||
refetchPrompts?: (
|
||||
options?: RefetchOptions & RefetchQueryFilters<unknown>
|
||||
) => Promise<QueryObserverResult<unknown, unknown>>;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
|
@ -64,6 +70,8 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
conversations,
|
||||
conversationsLoaded,
|
||||
refetchConversationsState,
|
||||
allPrompts,
|
||||
refetchPrompts,
|
||||
}) => {
|
||||
const showAnonymizedValuesChecked = useMemo(
|
||||
() =>
|
||||
|
@ -122,6 +130,7 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
isDisabled={isDisabled}
|
||||
conversations={conversations}
|
||||
onConversationDeleted={onConversationDeleted}
|
||||
allPrompts={allPrompts}
|
||||
/>
|
||||
|
||||
<>
|
||||
|
@ -156,6 +165,7 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
conversationsLoaded={conversationsLoaded}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
isFlyoutMode={false}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -8,18 +8,18 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Replacements } from '@kbn/elastic-assistant-common';
|
||||
import { PromptResponse, Replacements } from '@kbn/elastic-assistant-common';
|
||||
import type { ClientMessage } from '../../assistant_context/types';
|
||||
import { SelectedPromptContext } from '../prompt_context/types';
|
||||
import { useSendMessage } from '../use_send_message';
|
||||
import { useConversation } from '../use_conversation';
|
||||
import { getCombinedMessage } from '../prompt/helpers';
|
||||
import { Conversation, Prompt, useAssistantContext } from '../../..';
|
||||
import { Conversation, useAssistantContext } from '../../..';
|
||||
import { getMessageFromRawResponse } from '../helpers';
|
||||
import { getDefaultSystemPrompt } from '../use_conversation/helpers';
|
||||
|
||||
export interface UseChatSendProps {
|
||||
allSystemPrompts: Prompt[];
|
||||
allSystemPrompts: PromptResponse[];
|
||||
currentConversation?: Conversation;
|
||||
editingSystemPromptId: string | undefined;
|
||||
http: HttpSetup;
|
||||
|
|
|
@ -50,6 +50,7 @@ const defaultProps = {
|
|||
defaultProvider: OpenAiProviderType.OpenAi,
|
||||
conversations: mockConversations,
|
||||
onConversationDeleted,
|
||||
allPrompts: [],
|
||||
};
|
||||
describe('Conversation selector', () => {
|
||||
beforeAll(() => {
|
||||
|
|
|
@ -18,10 +18,13 @@ import {
|
|||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import {
|
||||
PromptResponse,
|
||||
PromptTypeEnum,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { getGenAiConfig } from '../../../connectorland/helpers';
|
||||
import { AIConnector } from '../../../connectorland/connector_selector';
|
||||
import { Conversation } from '../../../..';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
import * as i18n from './translations';
|
||||
import { DEFAULT_CONVERSATION_TITLE } from '../../use_conversation/translations';
|
||||
import { useConversation } from '../../use_conversation';
|
||||
|
@ -35,6 +38,7 @@ interface Props {
|
|||
shouldDisableKeyboardShortcut?: () => boolean;
|
||||
isDisabled?: boolean;
|
||||
conversations: Record<string, Conversation>;
|
||||
allPrompts: PromptResponse[];
|
||||
}
|
||||
|
||||
const getPreviousConversationId = (conversationIds: string[], selectedConversationId: string) => {
|
||||
|
@ -64,10 +68,13 @@ export const ConversationSelector: React.FC<Props> = React.memo(
|
|||
shouldDisableKeyboardShortcut = () => false,
|
||||
isDisabled = false,
|
||||
conversations,
|
||||
allPrompts,
|
||||
}) => {
|
||||
const { allSystemPrompts } = useAssistantContext();
|
||||
|
||||
const { createConversation } = useConversation();
|
||||
const allSystemPrompts = useMemo(
|
||||
() => allPrompts.filter((p) => p.promptType === PromptTypeEnum.system),
|
||||
[allPrompts]
|
||||
);
|
||||
const conversationIds = useMemo(() => Object.keys(conversations), [conversations]);
|
||||
const conversationOptions = useMemo<ConversationSelectorOption[]>(() => {
|
||||
return Object.values(conversations).map((conversation) => ({
|
||||
|
|
|
@ -18,7 +18,8 @@ import React, { useMemo } from 'react';
|
|||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
|
||||
import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { Conversation, Prompt } from '../../../..';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../..';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import { AIConnector } from '../../../connectorland/connector_selector';
|
||||
|
@ -33,7 +34,7 @@ import { getConversationApiConfig } from '../../use_conversation/helpers';
|
|||
|
||||
export interface ConversationSettingsProps {
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
allSystemPrompts: Prompt[];
|
||||
allSystemPrompts: PromptResponse[];
|
||||
connectors?: AIConnector[];
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
conversationsSettingsBulkActions: ConversationsBulkActions;
|
||||
|
|
|
@ -12,7 +12,8 @@ import { HttpSetup } from '@kbn/core-http-browser';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common';
|
||||
import { noop } from 'lodash/fp';
|
||||
import { Conversation, Prompt } from '../../../..';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../..';
|
||||
import * as i18n from './translations';
|
||||
import * as i18nModel from '../../../connectorland/models/model_selector/translations';
|
||||
|
||||
|
@ -25,7 +26,7 @@ import { ConversationsBulkActions } from '../../api';
|
|||
import { getDefaultSystemPrompt } from '../../use_conversation/helpers';
|
||||
|
||||
export interface ConversationSettingsEditorProps {
|
||||
allSystemPrompts: Prompt[];
|
||||
allSystemPrompts: PromptResponse[];
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
conversationsSettingsBulkActions: ConversationsBulkActions;
|
||||
http: HttpSetup;
|
||||
|
@ -268,7 +269,7 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
|
|||
helpText={i18n.SETTINGS_PROMPT_HELP_TEXT_TITLE}
|
||||
>
|
||||
<SelectSystemPrompt
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
allPrompts={allSystemPrompts}
|
||||
compressed
|
||||
conversation={selectedConversation}
|
||||
isEditing={true}
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
*/
|
||||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Conversation, Prompt } from '../../../..';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../..';
|
||||
import { getDefaultSystemPrompt } from '../../use_conversation/helpers';
|
||||
import { ConversationsBulkActions } from '../../api';
|
||||
import { AIConnector } from '../../../connectorland/connector_selector';
|
||||
|
||||
interface Props {
|
||||
allSystemPrompts: Prompt[];
|
||||
allSystemPrompts: PromptResponse[];
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
conversationsSettingsBulkActions: ConversationsBulkActions;
|
||||
defaultConnector?: AIConnector;
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
import { EuiPanel, EuiSpacer, EuiConfirmModal, EuiInMemoryTable } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../assistant_context/types';
|
||||
import { ConversationTableItem, useConversationsTable } from './use_conversations_table';
|
||||
import { ConversationStreamingSwitch } from '../conversation_settings/conversation_streaming_switch';
|
||||
import { AIConnector } from '../../../connectorland/connector_selector';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import { Prompt } from '../../types';
|
||||
import { ConversationsBulkActions } from '../../api';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
import { useConversationDeleted } from '../conversation_settings/use_conversation_deleted';
|
||||
|
@ -27,7 +27,7 @@ import { CONVERSATION_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_conte
|
|||
import { useSessionPagination } from '../../common/components/assistant_settings_management/pagination/use_session_pagination';
|
||||
import { DEFAULT_PAGE_SIZE } from '../../settings/const';
|
||||
interface Props {
|
||||
allSystemPrompts: Prompt[];
|
||||
allSystemPrompts: PromptResponse[];
|
||||
assistantStreamingEnabled: boolean;
|
||||
connectors: AIConnector[] | undefined;
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
|
|
|
@ -11,10 +11,10 @@ import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/publ
|
|||
import { EuiBadge, EuiBasicTableColumn, EuiLink } from '@elastic/eui';
|
||||
|
||||
import { FormattedDate } from '@kbn/i18n-react';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../assistant_context/types';
|
||||
import { AIConnector } from '../../../connectorland/connector_selector';
|
||||
import { getConnectorTypeTitle } from '../../../connectorland/helpers';
|
||||
import { Prompt } from '../../../..';
|
||||
import {
|
||||
getConversationApiConfig,
|
||||
getInitialDefaultSystemPrompt,
|
||||
|
@ -25,7 +25,7 @@ import { RowActions } from '../../common/components/assistant_settings_managemen
|
|||
const emptyConversations = {};
|
||||
|
||||
export interface GetConversationsListParams {
|
||||
allSystemPrompts: Prompt[];
|
||||
allSystemPrompts: PromptResponse[];
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
connectors: AIConnector[] | undefined;
|
||||
conversations: Record<string, Conversation>;
|
||||
|
@ -126,7 +126,7 @@ export const useConversationsTable = () => {
|
|||
);
|
||||
const connectorTypeTitle = getConnectorTypeTitle(connector, actionTypeRegistry);
|
||||
|
||||
const systemPrompt: Prompt | undefined = allSystemPrompts.find(
|
||||
const systemPrompt: PromptResponse | undefined = allSystemPrompts.find(
|
||||
({ id }) => id === conversation.apiConfig?.defaultSystemPromptId
|
||||
);
|
||||
const defaultSystemPrompt = getInitialDefaultSystemPrompt({
|
||||
|
@ -135,10 +135,10 @@ export const useConversationsTable = () => {
|
|||
});
|
||||
|
||||
const systemPromptTitle =
|
||||
systemPrompt?.label ||
|
||||
systemPrompt?.name ||
|
||||
defaultSystemPrompt?.label ||
|
||||
defaultSystemPrompt?.name;
|
||||
systemPrompt?.id ||
|
||||
defaultSystemPrompt?.name ||
|
||||
defaultSystemPrompt?.id;
|
||||
|
||||
return {
|
||||
...conversation,
|
||||
|
|
|
@ -39,6 +39,7 @@ import deepEqual from 'fast-deep-equal';
|
|||
|
||||
import { find, isEmpty, uniqBy } from 'lodash';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { PromptTypeEnum } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { useChatSend } from './chat_send/use_chat_send';
|
||||
import { ChatSend } from './chat_send';
|
||||
import { BlockBotCallToAction } from './block_bot/cta';
|
||||
|
@ -91,6 +92,7 @@ import { getGenAiConfig } from '../connectorland/helpers';
|
|||
import { AssistantAnimatedIcon } from './assistant_animated_icon';
|
||||
import { useFetchAnonymizationFields } from './api/anonymization_fields/use_fetch_anonymization_fields';
|
||||
import { InstallKnowledgeBaseButton } from '../knowledge_base/install_knowledge_base_button';
|
||||
import { useFetchPrompts } from './api/prompts/use_fetch_prompts';
|
||||
|
||||
export interface Props {
|
||||
conversationTitle?: string;
|
||||
|
@ -135,7 +137,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
setLastConversationId,
|
||||
getLastConversationId,
|
||||
title,
|
||||
allSystemPrompts,
|
||||
baseConversations,
|
||||
} = useAssistantContext();
|
||||
|
||||
|
@ -182,6 +183,19 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
isFetched: isFetchedAnonymizationFields,
|
||||
} = useFetchAnonymizationFields();
|
||||
|
||||
const {
|
||||
data: { data: allPrompts },
|
||||
refetch: refetchPrompts,
|
||||
isLoading: isLoadingPrompts,
|
||||
} = useFetchPrompts();
|
||||
|
||||
const allSystemPrompts = useMemo(() => {
|
||||
if (!isLoadingPrompts) {
|
||||
return allPrompts.filter((p) => p.promptType === PromptTypeEnum.system);
|
||||
}
|
||||
return [];
|
||||
}, [allPrompts, isLoadingPrompts]);
|
||||
|
||||
// Connector details
|
||||
const { data: connectors, isFetchedAfterMount: areConnectorsFetched } = useLoadConnectors({
|
||||
http,
|
||||
|
@ -397,7 +411,11 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
// End Scrolling
|
||||
|
||||
const selectedSystemPrompt = useMemo(
|
||||
() => getDefaultSystemPrompt({ allSystemPrompts, conversation: currentConversation }),
|
||||
() =>
|
||||
getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: currentConversation,
|
||||
}),
|
||||
[allSystemPrompts, currentConversation]
|
||||
);
|
||||
|
||||
|
@ -409,20 +427,21 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
async ({ cId, cTitle }: { cId: string; cTitle: string }) => {
|
||||
const updatedConv = await refetchResults();
|
||||
|
||||
let selectedConversation;
|
||||
if (cId === '') {
|
||||
setCurrentConversationId(cTitle);
|
||||
setEditingSystemPromptId(
|
||||
getDefaultSystemPrompt({ allSystemPrompts, conversation: updatedConv?.data?.[cTitle] })
|
||||
?.id
|
||||
);
|
||||
selectedConversation = updatedConv?.data?.[cTitle];
|
||||
setCurrentConversationId(cTitle);
|
||||
} else {
|
||||
const refetchedConversation = await refetchCurrentConversation({ cId });
|
||||
setEditingSystemPromptId(
|
||||
getDefaultSystemPrompt({ allSystemPrompts, conversation: refetchedConversation })?.id
|
||||
);
|
||||
selectedConversation = await refetchCurrentConversation({ cId });
|
||||
setCurrentConversationId(cId);
|
||||
}
|
||||
setEditingSystemPromptId(
|
||||
getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: selectedConversation,
|
||||
})?.id
|
||||
);
|
||||
},
|
||||
[allSystemPrompts, refetchCurrentConversation, refetchResults]
|
||||
);
|
||||
|
@ -639,18 +658,18 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
/>
|
||||
</ModalPromptEditorWrapper>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
[
|
||||
abortStream,
|
||||
refetchCurrentConversation,
|
||||
currentConversation,
|
||||
editingSystemPromptId,
|
||||
getComments,
|
||||
abortStream,
|
||||
currentConversation,
|
||||
showAnonymizedValues,
|
||||
refetchCurrentConversation,
|
||||
handleRegenerateResponse,
|
||||
isEnabledKnowledgeBase,
|
||||
isEnabledRAGAlerts,
|
||||
|
@ -658,12 +677,14 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
currentUserAvatar,
|
||||
isFlyoutMode,
|
||||
selectedPromptContextsCount,
|
||||
editingSystemPromptId,
|
||||
isNewConversation,
|
||||
isSettingsModalVisible,
|
||||
promptContexts,
|
||||
promptTextPreview,
|
||||
handleOnSystemPromptSelectionChange,
|
||||
selectedPromptContexts,
|
||||
allSystemPrompts,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -859,6 +880,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -882,6 +904,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
</EuiPanel>
|
||||
);
|
||||
}, [
|
||||
allSystemPrompts,
|
||||
comments,
|
||||
connectorPrompt,
|
||||
currentConversation,
|
||||
|
@ -948,6 +971,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
refetchConversationsState={refetchConversationsState}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
|
||||
{/* Create portals for each EuiCodeBlock to add the `Investigate in Timeline` action */}
|
||||
|
@ -1080,6 +1104,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
trackPrompt={trackPrompt}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
allPrompts={allPrompts}
|
||||
/>
|
||||
</EuiPanel>
|
||||
)}
|
||||
|
@ -1116,6 +1141,8 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
conversationsLoaded={conversationsLoaded}
|
||||
onConversationDeleted={handleOnConversationDeleted}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
allPrompts={allPrompts}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -1194,6 +1221,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
trackPrompt={trackPrompt}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
allPrompts={allPrompts}
|
||||
/>
|
||||
)}
|
||||
</EuiModalFooter>
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Replacements, transformRawData } from '@kbn/elastic-assistant-common';
|
||||
import { Replacements, transformRawData, PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import type { ClientMessage } from '../../assistant_context/types';
|
||||
import { getAnonymizedValue as defaultGetAnonymizedValue } from '../get_anonymized_value';
|
||||
import type { SelectedPromptContext } from '../prompt_context/types';
|
||||
import type { Prompt } from '../types';
|
||||
import { SYSTEM_PROMPT_CONTEXT_NON_I18N } from '../../content/prompts/system/translations';
|
||||
|
||||
export const getSystemMessages = ({
|
||||
|
@ -17,7 +16,7 @@ export const getSystemMessages = ({
|
|||
selectedSystemPrompt,
|
||||
}: {
|
||||
isNewChat: boolean;
|
||||
selectedSystemPrompt: Prompt | undefined;
|
||||
selectedSystemPrompt: PromptResponse | undefined;
|
||||
}): ClientMessage[] => {
|
||||
if (!isNewChat || selectedSystemPrompt == null) {
|
||||
return [];
|
||||
|
@ -53,7 +52,7 @@ export function getCombinedMessage({
|
|||
isNewChat: boolean;
|
||||
promptText: string;
|
||||
selectedPromptContexts: Record<string, SelectedPromptContext>;
|
||||
selectedSystemPrompt: Prompt | undefined;
|
||||
selectedSystemPrompt: PromptResponse | undefined;
|
||||
}): ClientMessageWithReplacements {
|
||||
let replacements: Replacements = currentReplacements ?? {};
|
||||
const onNewReplacements = (newReplacements: Replacements) => {
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
import { getPromptById } from './helpers';
|
||||
import { mockSystemPrompt, mockSuperheroSystemPrompt } from '../../mock/system_prompt';
|
||||
import type { Prompt } from '../types';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('getPromptById', () => {
|
||||
const prompts: Prompt[] = [mockSystemPrompt, mockSuperheroSystemPrompt];
|
||||
const prompts: PromptResponse[] = [mockSystemPrompt, mockSuperheroSystemPrompt];
|
||||
|
||||
it('returns the correct prompt by id', () => {
|
||||
const result = getPromptById({ prompts, id: mockSuperheroSystemPrompt.id });
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Prompt } from '../types';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
export const getPromptById = ({
|
||||
prompts,
|
||||
id,
|
||||
}: {
|
||||
prompts: Prompt[];
|
||||
prompts: PromptResponse[];
|
||||
id: string;
|
||||
}): Prompt | undefined => prompts.find((p) => p.id === id);
|
||||
}): PromptResponse | undefined => prompts.find((p) => p.id === id);
|
||||
|
|
|
@ -40,6 +40,7 @@ const defaultProps: Props = {
|
|||
setIsSettingsModalVisible: jest.fn(),
|
||||
setSelectedPromptContexts: jest.fn(),
|
||||
isFlyoutMode: false,
|
||||
allSystemPrompts: [],
|
||||
};
|
||||
|
||||
describe('PromptEditorComponent', () => {
|
||||
|
|
|
@ -10,6 +10,7 @@ import React, { useMemo } from 'react';
|
|||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../..';
|
||||
import type { PromptContext, SelectedPromptContext } from '../prompt_context/types';
|
||||
import { SystemPrompt } from './system_prompt';
|
||||
|
@ -31,6 +32,7 @@ export interface Props {
|
|||
React.SetStateAction<Record<string, SelectedPromptContext>>
|
||||
>;
|
||||
isFlyoutMode: boolean;
|
||||
allSystemPrompts: PromptResponse[];
|
||||
}
|
||||
|
||||
const PreviewText = styled(EuiText)`
|
||||
|
@ -49,12 +51,14 @@ const PromptEditorComponent: React.FC<Props> = ({
|
|||
setIsSettingsModalVisible,
|
||||
setSelectedPromptContexts,
|
||||
isFlyoutMode,
|
||||
allSystemPrompts,
|
||||
}) => {
|
||||
const commentBody = useMemo(
|
||||
() => (
|
||||
<>
|
||||
{isNewConversation && (
|
||||
<SystemPrompt
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
conversation={conversation}
|
||||
editingSystemPromptId={editingSystemPromptId}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
|
@ -79,17 +83,18 @@ const PromptEditorComponent: React.FC<Props> = ({
|
|||
</>
|
||||
),
|
||||
[
|
||||
isNewConversation,
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
editingSystemPromptId,
|
||||
isNewConversation,
|
||||
isSettingsModalVisible,
|
||||
onSystemPromptSelectionChange,
|
||||
promptContexts,
|
||||
promptTextPreview,
|
||||
selectedPromptContexts,
|
||||
isSettingsModalVisible,
|
||||
setIsSettingsModalVisible,
|
||||
setSelectedPromptContexts,
|
||||
isFlyoutMode,
|
||||
promptContexts,
|
||||
selectedPromptContexts,
|
||||
setSelectedPromptContexts,
|
||||
promptTextPreview,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -16,13 +16,13 @@ import { getOptions, getOptionFromPrompt } from './helpers';
|
|||
describe('helpers', () => {
|
||||
describe('getOptionFromPrompt', () => {
|
||||
it('returns an EuiSuperSelectOption with the correct value', () => {
|
||||
const option = getOptionFromPrompt(mockSystemPrompt);
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt, isFlyoutMode: true });
|
||||
|
||||
expect(option.value).toBe(mockSystemPrompt.id);
|
||||
});
|
||||
|
||||
it('returns an EuiSuperSelectOption with the correct inputDisplay', () => {
|
||||
const option = getOptionFromPrompt(mockSystemPrompt);
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt, isFlyoutMode: false });
|
||||
|
||||
render(<>{option.inputDisplay}</>);
|
||||
|
||||
|
@ -30,7 +30,7 @@ describe('helpers', () => {
|
|||
});
|
||||
|
||||
it('shows the expected name in the dropdownDisplay', () => {
|
||||
const option = getOptionFromPrompt(mockSystemPrompt);
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt, isFlyoutMode: true });
|
||||
|
||||
render(<TestProviders>{option.dropdownDisplay}</TestProviders>);
|
||||
|
||||
|
@ -38,7 +38,7 @@ describe('helpers', () => {
|
|||
});
|
||||
|
||||
it('shows the expected prompt content in the dropdownDisplay', () => {
|
||||
const option = getOptionFromPrompt(mockSystemPrompt);
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt, isFlyoutMode: true });
|
||||
|
||||
render(<TestProviders>{option.dropdownDisplay}</TestProviders>);
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import styled from 'styled-components';
|
|||
|
||||
import { css } from '@emotion/react';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import type { Prompt } from '../../types';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { EMPTY_PROMPT } from './translations';
|
||||
|
||||
const Strong = styled.strong`
|
||||
|
@ -26,7 +26,10 @@ export const getOptionFromPrompt = ({
|
|||
name,
|
||||
showTitles = false,
|
||||
isFlyoutMode,
|
||||
}: Prompt & { showTitles?: boolean }): EuiSuperSelectOption<string> => ({
|
||||
}: PromptResponse & {
|
||||
showTitles?: boolean;
|
||||
isFlyoutMode: boolean;
|
||||
}): EuiSuperSelectOption<string> => ({
|
||||
value: id,
|
||||
inputDisplay: isFlyoutMode ? (
|
||||
name
|
||||
|
@ -60,13 +63,13 @@ export const getOptionFromPrompt = ({
|
|||
});
|
||||
|
||||
interface GetOptionsProps {
|
||||
prompts: Prompt[] | undefined;
|
||||
prompts: PromptResponse[] | undefined;
|
||||
showTitles?: boolean;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
export const getOptions = ({
|
||||
prompts,
|
||||
showTitles = false,
|
||||
isFlyoutMode = false,
|
||||
isFlyoutMode,
|
||||
}: GetOptionsProps): Array<EuiSuperSelectOption<string>> =>
|
||||
prompts?.map((p) => getOptionFromPrompt({ ...p, showTitles, isFlyoutMode })) ?? [];
|
||||
|
|
|
@ -13,11 +13,11 @@ import { mockSystemPrompt } from '../../../mock/system_prompt';
|
|||
import { SystemPrompt } from '.';
|
||||
import { Conversation } from '../../../..';
|
||||
import { DEFAULT_CONVERSATION_TITLE } from '../../use_conversation/translations';
|
||||
import { Prompt } from '../../types';
|
||||
import { TestProviders } from '../../../mock/test_providers/test_providers';
|
||||
import { TEST_IDS } from '../../constants';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
import { WELCOME_CONVERSATION } from '../../use_conversation/sample_conversations';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
const BASE_CONVERSATION: Conversation = {
|
||||
...WELCOME_CONVERSATION,
|
||||
|
@ -32,7 +32,7 @@ const mockConversations = {
|
|||
[DEFAULT_CONVERSATION_TITLE]: BASE_CONVERSATION,
|
||||
};
|
||||
|
||||
const mockSystemPrompts: Prompt[] = [mockSystemPrompt];
|
||||
const mockSystemPrompts: PromptResponse[] = [mockSystemPrompt];
|
||||
|
||||
const mockUseAssistantContext = {
|
||||
conversations: mockConversations,
|
||||
|
@ -91,6 +91,7 @@ describe('SystemPrompt', () => {
|
|||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -117,11 +118,12 @@ describe('SystemPrompt', () => {
|
|||
render(
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={BASE_CONVERSATION.id}
|
||||
editingSystemPromptId={mockSystemPrompt.id}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -157,6 +159,7 @@ describe('SystemPrompt', () => {
|
|||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -204,6 +207,7 @@ describe('SystemPrompt', () => {
|
|||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -265,6 +269,7 @@ describe('SystemPrompt', () => {
|
|||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -333,6 +338,7 @@ describe('SystemPrompt', () => {
|
|||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -416,6 +422,7 @@ describe('SystemPrompt', () => {
|
|||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -481,11 +488,12 @@ describe('SystemPrompt', () => {
|
|||
<TestProviders>
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={BASE_CONVERSATION.id}
|
||||
editingSystemPromptId={mockSystemPrompt.id}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -500,11 +508,12 @@ describe('SystemPrompt', () => {
|
|||
<TestProviders>
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={BASE_CONVERSATION.id}
|
||||
editingSystemPromptId={mockSystemPrompt.id}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, { useCallback, useMemo } from 'react';
|
|||
|
||||
import { css } from '@emotion/react';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../..';
|
||||
import * as i18n from './translations';
|
||||
import { SelectSystemPrompt } from './select_system_prompt';
|
||||
|
@ -22,6 +22,7 @@ interface Props {
|
|||
onSystemPromptSelectionChange: (systemPromptId: string | undefined) => void;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isFlyoutMode: boolean;
|
||||
allSystemPrompts: PromptResponse[];
|
||||
}
|
||||
|
||||
const SystemPromptComponent: React.FC<Props> = ({
|
||||
|
@ -31,17 +32,13 @@ const SystemPromptComponent: React.FC<Props> = ({
|
|||
onSystemPromptSelectionChange,
|
||||
setIsSettingsModalVisible,
|
||||
isFlyoutMode,
|
||||
allSystemPrompts,
|
||||
}) => {
|
||||
const { allSystemPrompts } = useAssistantContext();
|
||||
|
||||
const selectedPrompt = useMemo(() => {
|
||||
if (editingSystemPromptId !== undefined) {
|
||||
return (
|
||||
allSystemPrompts?.find((p) => p.id === editingSystemPromptId) ??
|
||||
allSystemPrompts?.find((p) => p.id === conversation?.apiConfig?.defaultSystemPromptId)
|
||||
);
|
||||
return allSystemPrompts.find((p) => p.id === editingSystemPromptId);
|
||||
} else {
|
||||
return undefined;
|
||||
return allSystemPrompts.find((p) => p.id === conversation?.apiConfig?.defaultSystemPromptId);
|
||||
}
|
||||
}, [allSystemPrompts, conversation?.apiConfig?.defaultSystemPromptId, editingSystemPromptId]);
|
||||
|
||||
|
@ -58,7 +55,7 @@ const SystemPromptComponent: React.FC<Props> = ({
|
|||
if (isFlyoutMode) {
|
||||
return (
|
||||
<SelectSystemPrompt
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
allPrompts={allSystemPrompts}
|
||||
clearSelectedSystemPrompt={handleClearSystemPrompt}
|
||||
conversation={conversation}
|
||||
data-test-subj="systemPrompt"
|
||||
|
@ -78,7 +75,7 @@ const SystemPromptComponent: React.FC<Props> = ({
|
|||
<div>
|
||||
{selectedPrompt == null || isEditing ? (
|
||||
<SelectSystemPrompt
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
allPrompts={allSystemPrompts}
|
||||
clearSelectedSystemPrompt={handleClearSystemPrompt}
|
||||
conversation={conversation}
|
||||
data-test-subj="systemPrompt"
|
||||
|
|
|
@ -11,9 +11,32 @@ import userEvent from '@testing-library/user-event';
|
|||
|
||||
import { Props, SelectSystemPrompt } from '.';
|
||||
import { TEST_IDS } from '../../../constants';
|
||||
import { defaultAssistantFeatures } from '@kbn/elastic-assistant-common';
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
import { useFetchPrompts } from '../../../api';
|
||||
import { mockSystemPrompts } from '../../../../mock/system_prompt';
|
||||
import { DefinedUseQueryResult } from '@tanstack/react-query';
|
||||
|
||||
jest.mock('../../../api/prompts/use_fetch_prompts');
|
||||
const http = {
|
||||
fetch: jest.fn().mockResolvedValue(defaultAssistantFeatures),
|
||||
} as unknown as HttpSetup;
|
||||
|
||||
jest.mocked(useFetchPrompts).mockReturnValue({
|
||||
data: { page: 1, perPage: 1000, data: mockSystemPrompts, total: 10 },
|
||||
isLoading: false,
|
||||
refetch: jest.fn().mockResolvedValue({
|
||||
isLoading: false,
|
||||
data: {
|
||||
...mockSystemPrompts,
|
||||
},
|
||||
}),
|
||||
isFetched: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as unknown as DefinedUseQueryResult<any, unknown>);
|
||||
|
||||
const props: Props = {
|
||||
allSystemPrompts: [
|
||||
allPrompts: [
|
||||
{
|
||||
id: 'default-system-prompt',
|
||||
content: 'default',
|
||||
|
@ -31,6 +54,8 @@ const props: Props = {
|
|||
};
|
||||
|
||||
const mockUseAssistantContext = {
|
||||
http,
|
||||
assistantAvailability: { isAssistantEnabled: true },
|
||||
allSystemPrompts: [
|
||||
{
|
||||
id: 'default-system-prompt',
|
||||
|
|
|
@ -18,10 +18,13 @@ import {
|
|||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import {
|
||||
PromptResponse,
|
||||
PromptTypeEnum,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { Conversation } from '../../../../..';
|
||||
import { getOptions } from '../helpers';
|
||||
import * as i18n from '../translations';
|
||||
import type { Prompt } from '../../../types';
|
||||
import { useAssistantContext } from '../../../../assistant_context';
|
||||
import { useConversation } from '../../../use_conversation';
|
||||
import { TEST_IDS } from '../../../constants';
|
||||
|
@ -29,10 +32,10 @@ import { PROMPT_CONTEXT_SELECTOR_PREFIX } from '../../../quick_prompts/prompt_co
|
|||
import { SYSTEM_PROMPTS_TAB } from '../../../settings/const';
|
||||
|
||||
export interface Props {
|
||||
allSystemPrompts: Prompt[];
|
||||
allPrompts: PromptResponse[];
|
||||
compressed?: boolean;
|
||||
conversation?: Conversation;
|
||||
selectedPrompt: Prompt | undefined;
|
||||
selectedPrompt: PromptResponse | undefined;
|
||||
clearSelectedSystemPrompt?: () => void;
|
||||
isClearable?: boolean;
|
||||
isEditing?: boolean;
|
||||
|
@ -49,7 +52,7 @@ export interface Props {
|
|||
const ADD_NEW_SYSTEM_PROMPT = 'ADD_NEW_SYSTEM_PROMPT';
|
||||
|
||||
const SelectSystemPromptComponent: React.FC<Props> = ({
|
||||
allSystemPrompts,
|
||||
allPrompts,
|
||||
compressed = false,
|
||||
conversation,
|
||||
selectedPrompt,
|
||||
|
@ -68,21 +71,24 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
const { setSelectedSettingsTab } = useAssistantContext();
|
||||
const { setApiConfig } = useConversation();
|
||||
|
||||
const [isOpenLocal, setIsOpenLocal] = useState<boolean>(isOpen);
|
||||
const [valueOfSelected, setValueOfSelected] = useState<string | undefined>(
|
||||
selectedPrompt?.id ?? allSystemPrompts?.[0]?.id
|
||||
const allSystemPrompts = useMemo(
|
||||
() => allPrompts.filter((p) => p.promptType === PromptTypeEnum.system),
|
||||
[allPrompts]
|
||||
);
|
||||
|
||||
const [isOpenLocal, setIsOpenLocal] = useState<boolean>(isOpen);
|
||||
const handleOnBlur = useCallback(() => setIsOpenLocal(false), []);
|
||||
const valueOfSelected = useMemo(() => selectedPrompt?.id, [selectedPrompt?.id]);
|
||||
|
||||
// Write the selected system prompt to the conversation config
|
||||
const setSelectedSystemPrompt = useCallback(
|
||||
(prompt: Prompt | undefined) => {
|
||||
(promptId?: string) => {
|
||||
if (conversation && conversation.apiConfig) {
|
||||
setApiConfig({
|
||||
conversation,
|
||||
apiConfig: {
|
||||
...conversation.apiConfig,
|
||||
defaultSystemPromptId: prompt?.id,
|
||||
defaultSystemPromptId: promptId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -126,14 +132,11 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
// Note: if callback is provided, this component does not persist. Extract to separate component
|
||||
if (onSystemPromptSelectionChange != null) {
|
||||
onSystemPromptSelectionChange(selectedSystemPromptId);
|
||||
} else {
|
||||
setSelectedSystemPrompt(allSystemPrompts.find((sp) => sp.id === selectedSystemPromptId));
|
||||
}
|
||||
setValueOfSelected(selectedSystemPromptId);
|
||||
setSelectedSystemPrompt(selectedSystemPromptId);
|
||||
setIsEditing?.(false);
|
||||
},
|
||||
[
|
||||
allSystemPrompts,
|
||||
onSystemPromptSelectionChange,
|
||||
setIsEditing,
|
||||
setIsSettingsModalVisible,
|
||||
|
@ -146,7 +149,6 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
setSelectedSystemPrompt(undefined);
|
||||
setIsEditing?.(false);
|
||||
clearSelectedSystemPrompt?.();
|
||||
setValueOfSelected(undefined);
|
||||
}, [clearSelectedSystemPrompt, setIsEditing, setSelectedSystemPrompt]);
|
||||
|
||||
const onShowSelectSystemPrompt = useCallback(() => {
|
||||
|
|
|
@ -18,9 +18,13 @@ import {
|
|||
import { keyBy } from 'lodash/fp';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
PromptResponse,
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { ApiConfig } from '@kbn/elastic-assistant-common';
|
||||
import { AIConnector } from '../../../../connectorland/connector_selector';
|
||||
import { Conversation, Prompt } from '../../../../..';
|
||||
import { Conversation } from '../../../../..';
|
||||
import * as i18n from './translations';
|
||||
import { ConversationMultiSelector } from './conversation_multi_selector/conversation_multi_selector';
|
||||
import { SystemPromptSelector } from './system_prompt_selector/system_prompt_selector';
|
||||
|
@ -34,16 +38,18 @@ interface Props {
|
|||
connectors: AIConnector[] | undefined;
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
conversationsSettingsBulkActions: ConversationsBulkActions;
|
||||
onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void;
|
||||
selectedSystemPrompt: Prompt | undefined;
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<Prompt[]>>;
|
||||
onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void;
|
||||
selectedSystemPrompt: PromptResponse | undefined;
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
setConversationSettings: React.Dispatch<React.SetStateAction<Record<string, Conversation>>>;
|
||||
systemPromptSettings: Prompt[];
|
||||
systemPromptSettings: PromptResponse[];
|
||||
setConversationsSettingsBulkActions: React.Dispatch<
|
||||
React.SetStateAction<ConversationsBulkActions>
|
||||
>;
|
||||
defaultConnector?: AIConnector;
|
||||
resetSettings?: () => void;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,6 +67,8 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
setConversationsSettingsBulkActions,
|
||||
defaultConnector,
|
||||
resetSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
}) => {
|
||||
// Prompt
|
||||
const promptContent = useMemo(
|
||||
|
@ -72,11 +80,11 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
const handlePromptContentChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
if (selectedSystemPrompt != null) {
|
||||
setUpdatedSystemPromptSettings((prev): Prompt[] => {
|
||||
setUpdatedSystemPromptSettings((prev): PromptResponse[] => {
|
||||
const alreadyExists = prev.some((sp) => sp.id === selectedSystemPrompt.id);
|
||||
|
||||
if (alreadyExists) {
|
||||
return prev.map((sp): Prompt => {
|
||||
return prev.map((sp): PromptResponse => {
|
||||
if (sp.id === selectedSystemPrompt.id) {
|
||||
return {
|
||||
...sp,
|
||||
|
@ -89,9 +97,44 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
|
||||
return prev;
|
||||
});
|
||||
const existingPrompt = systemPromptSettings.find((sp) => sp.id === selectedSystemPrompt.id);
|
||||
if (existingPrompt) {
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
...(selectedSystemPrompt.name !== selectedSystemPrompt.id
|
||||
? {
|
||||
update: [
|
||||
...(promptsBulkActions.update ?? []).filter(
|
||||
(p) => p.id !== selectedSystemPrompt.id
|
||||
),
|
||||
{
|
||||
...selectedSystemPrompt,
|
||||
content: e.target.value,
|
||||
},
|
||||
],
|
||||
}
|
||||
: {
|
||||
create: [
|
||||
...(promptsBulkActions.create ?? []).filter(
|
||||
(p) => p.name !== selectedSystemPrompt.name
|
||||
),
|
||||
{
|
||||
...selectedSystemPrompt,
|
||||
content: e.target.value,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[selectedSystemPrompt, setUpdatedSystemPromptSettings]
|
||||
[
|
||||
promptsBulkActions,
|
||||
selectedSystemPrompt,
|
||||
setPromptsBulkActions,
|
||||
setUpdatedSystemPromptSettings,
|
||||
systemPromptSettings,
|
||||
]
|
||||
);
|
||||
|
||||
const conversationsWithApiConfig = Object.entries(conversationSettings).reduce<
|
||||
|
@ -258,14 +301,47 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
};
|
||||
});
|
||||
});
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
...(selectedSystemPrompt.name !== selectedSystemPrompt.id
|
||||
? {
|
||||
update: [
|
||||
...(promptsBulkActions.update ?? []).filter(
|
||||
(p) => p.id !== selectedSystemPrompt.id
|
||||
),
|
||||
{
|
||||
...selectedSystemPrompt,
|
||||
isNewConversationDefault: isChecked,
|
||||
},
|
||||
],
|
||||
}
|
||||
: {
|
||||
create: [
|
||||
...(promptsBulkActions.create ?? []).filter(
|
||||
(p) => p.name !== selectedSystemPrompt.name
|
||||
),
|
||||
{
|
||||
...selectedSystemPrompt,
|
||||
isNewConversationDefault: isChecked,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
[selectedSystemPrompt, setUpdatedSystemPromptSettings]
|
||||
[
|
||||
promptsBulkActions,
|
||||
selectedSystemPrompt,
|
||||
setPromptsBulkActions,
|
||||
setUpdatedSystemPromptSettings,
|
||||
]
|
||||
);
|
||||
|
||||
const { onSystemPromptSelectionChange, onSystemPromptDeleted } = useSystemPromptEditor({
|
||||
setUpdatedSystemPromptSettings,
|
||||
onSelectedSystemPromptChange,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -35,7 +35,7 @@ describe('SystemPromptSelector', () => {
|
|||
fireEvent.click(getByTestId('comboBoxToggleListButton'));
|
||||
// there is only one delete system prompt because there is only one custom option
|
||||
fireEvent.click(getAllByTestId('delete-prompt')[1]);
|
||||
expect(onSystemPromptDeleted).toHaveBeenCalledWith(mockSystemPrompts[1].name);
|
||||
expect(onSystemPromptDeleted).toHaveBeenCalledWith(mockSystemPrompts[1].id);
|
||||
expect(onSystemPromptSelectionChange).not.toHaveBeenCalled();
|
||||
});
|
||||
it('Deletes a system prompt that is selected', () => {
|
||||
|
@ -43,7 +43,7 @@ describe('SystemPromptSelector', () => {
|
|||
fireEvent.click(getByTestId('comboBoxToggleListButton'));
|
||||
// there is only one delete system prompt because there is only one custom option
|
||||
fireEvent.click(getAllByTestId('delete-prompt')[0]);
|
||||
expect(onSystemPromptDeleted).toHaveBeenCalledWith(mockSystemPrompts[0].name);
|
||||
expect(onSystemPromptDeleted).toHaveBeenCalledWith(mockSystemPrompts[0].id);
|
||||
expect(onSystemPromptSelectionChange).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
it('Selects existing system prompt from the search input', () => {
|
||||
|
|
|
@ -18,8 +18,8 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { TEST_IDS } from '../../../../constants';
|
||||
import { Prompt } from '../../../../../..';
|
||||
import * as i18n from './translations';
|
||||
import { SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION } from '../translations';
|
||||
|
||||
|
@ -28,10 +28,10 @@ export const SYSTEM_PROMPT_SELECTOR_CLASSNAME = 'systemPromptSelector';
|
|||
interface Props {
|
||||
autoFocus?: boolean;
|
||||
onSystemPromptDeleted: (systemPromptTitle: string) => void;
|
||||
onSystemPromptSelectionChange: (systemPrompt?: Prompt | string) => void;
|
||||
onSystemPromptSelectionChange: (systemPrompt?: PromptResponse | string) => void;
|
||||
systemPrompts: PromptResponse[];
|
||||
selectedSystemPrompt?: PromptResponse;
|
||||
resetSettings?: () => void;
|
||||
selectedSystemPrompt?: Prompt;
|
||||
systemPrompts: Prompt[];
|
||||
}
|
||||
|
||||
export type SystemPromptSelectorOption = EuiComboBoxOptionOption<{
|
||||
|
@ -59,6 +59,7 @@ export const SystemPromptSelector: React.FC<Props> = React.memo(
|
|||
isNewConversationDefault: sp.isNewConversationDefault ?? false,
|
||||
},
|
||||
label: sp.name,
|
||||
id: sp.id,
|
||||
'data-test-subj': `${TEST_IDS.SYSTEM_PROMPT_SELECTOR}-${sp.id}`,
|
||||
}))
|
||||
);
|
||||
|
@ -70,6 +71,7 @@ export const SystemPromptSelector: React.FC<Props> = React.memo(
|
|||
isDefault: selectedSystemPrompt.isDefault ?? false,
|
||||
isNewConversationDefault: selectedSystemPrompt.isNewConversationDefault ?? false,
|
||||
},
|
||||
id: selectedSystemPrompt.id,
|
||||
label: selectedSystemPrompt.name,
|
||||
},
|
||||
]
|
||||
|
@ -106,6 +108,7 @@ export const SystemPromptSelector: React.FC<Props> = React.memo(
|
|||
|
||||
const newOption = {
|
||||
value: searchValue,
|
||||
id: searchValue,
|
||||
label: searchValue,
|
||||
};
|
||||
|
||||
|
@ -132,11 +135,12 @@ export const SystemPromptSelector: React.FC<Props> = React.memo(
|
|||
// Callback for when user deletes a quick prompt
|
||||
const onDelete = useCallback(
|
||||
(label: string) => {
|
||||
const deleteId = options.find((o) => o.label === label)?.id;
|
||||
setOptions(options.filter((o) => o.label !== label));
|
||||
if (selectedOptions?.[0]?.label === label) {
|
||||
handleSelectionChange([]);
|
||||
}
|
||||
onSystemPromptDeleted(label);
|
||||
onSystemPromptDeleted(deleteId ?? label);
|
||||
},
|
||||
[handleSelectionChange, onSystemPromptDeleted, options, selectedOptions]
|
||||
);
|
||||
|
|
|
@ -36,6 +36,8 @@ const testProps = {
|
|||
systemPromptSettings: mockSystemPrompts,
|
||||
conversationsSettingsBulkActions: {},
|
||||
setConversationsSettingsBulkActions: jest.fn(),
|
||||
promptsBulkActions: {},
|
||||
setPromptsBulkActions: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('./system_prompt_selector/system_prompt_selector', () => ({
|
||||
|
@ -96,6 +98,7 @@ describe('SystemPromptSettings', () => {
|
|||
);
|
||||
fireEvent.click(getByTestId('change-sp-custom'));
|
||||
const customOption = {
|
||||
consumer: 'test',
|
||||
content: '',
|
||||
id: 'sooper custom prompt',
|
||||
name: 'sooper custom prompt',
|
||||
|
|
|
@ -26,7 +26,9 @@ export const SystemPromptSettings: React.FC<SystemPromptSettingsProps> = React.m
|
|||
systemPromptSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
setConversationsSettingsBulkActions,
|
||||
promptsBulkActions,
|
||||
defaultConnector,
|
||||
setPromptsBulkActions,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
|
@ -48,6 +50,8 @@ export const SystemPromptSettings: React.FC<SystemPromptSettingsProps> = React.m
|
|||
conversationsSettingsBulkActions={conversationsSettingsBulkActions}
|
||||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
defaultConnector={defaultConnector}
|
||||
setPromptsBulkActions={setPromptsBulkActions}
|
||||
promptsBulkActions={promptsBulkActions}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -4,21 +4,27 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
PromptResponse,
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { AIConnector } from '../../../../connectorland/connector_selector';
|
||||
import { Conversation, Prompt } from '../../../../..';
|
||||
import { Conversation } from '../../../../..';
|
||||
import { ConversationsBulkActions } from '../../../api';
|
||||
|
||||
export interface SystemPromptSettingsProps {
|
||||
connectors: AIConnector[] | undefined;
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
conversationsSettingsBulkActions: ConversationsBulkActions;
|
||||
onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void;
|
||||
selectedSystemPrompt: Prompt | undefined;
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<Prompt[]>>;
|
||||
onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void;
|
||||
selectedSystemPrompt: PromptResponse | undefined;
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
setConversationSettings: React.Dispatch<React.SetStateAction<Record<string, Conversation>>>;
|
||||
systemPromptSettings: Prompt[];
|
||||
systemPromptSettings: PromptResponse[];
|
||||
setConversationsSettingsBulkActions: React.Dispatch<
|
||||
React.SetStateAction<ConversationsBulkActions>
|
||||
>;
|
||||
defaultConnector?: AIConnector;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
}
|
||||
|
|
|
@ -6,21 +6,27 @@
|
|||
*/
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { useSystemPromptEditor } from './use_system_prompt_editor';
|
||||
import { Prompt } from '../../../types';
|
||||
import {
|
||||
mockSystemPrompt,
|
||||
mockSuperheroSystemPrompt,
|
||||
mockSystemPrompts,
|
||||
} from '../../../../mock/system_prompt';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { useAssistantContext } from '../../../../assistant_context';
|
||||
|
||||
jest.mock('../../../../assistant_context');
|
||||
// Mock functions for the tests
|
||||
const mockOnSelectedSystemPromptChange = jest.fn();
|
||||
const mockSetUpdatedSystemPromptSettings = jest.fn();
|
||||
const mockSetPromptsBulkActions = jest.fn();
|
||||
const mockPreviousSystemPrompts = [...mockSystemPrompts];
|
||||
|
||||
describe('useSystemPromptEditor', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useAssistantContext as jest.Mock).mockReturnValue({
|
||||
currentAppId: 'securitySolutionUI',
|
||||
});
|
||||
});
|
||||
|
||||
test('should delete a system prompt by id', () => {
|
||||
|
@ -28,6 +34,8 @@ describe('useSystemPromptEditor', () => {
|
|||
useSystemPromptEditor({
|
||||
onSelectedSystemPromptChange: mockOnSelectedSystemPromptChange,
|
||||
setUpdatedSystemPromptSettings: mockSetUpdatedSystemPromptSettings,
|
||||
setPromptsBulkActions: mockSetPromptsBulkActions,
|
||||
promptsBulkActions: {},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -41,11 +49,13 @@ describe('useSystemPromptEditor', () => {
|
|||
});
|
||||
|
||||
test('should handle selection of an existing system prompt', () => {
|
||||
const existingPrompt: Prompt = mockSystemPrompt;
|
||||
const existingPrompt: PromptResponse = mockSystemPrompt;
|
||||
const { result } = renderHook(() =>
|
||||
useSystemPromptEditor({
|
||||
onSelectedSystemPromptChange: mockOnSelectedSystemPromptChange,
|
||||
setUpdatedSystemPromptSettings: mockSetUpdatedSystemPromptSettings,
|
||||
setPromptsBulkActions: mockSetPromptsBulkActions,
|
||||
promptsBulkActions: {},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -65,6 +75,8 @@ describe('useSystemPromptEditor', () => {
|
|||
useSystemPromptEditor({
|
||||
onSelectedSystemPromptChange: mockOnSelectedSystemPromptChange,
|
||||
setUpdatedSystemPromptSettings: mockSetUpdatedSystemPromptSettings,
|
||||
setPromptsBulkActions: mockSetPromptsBulkActions,
|
||||
promptsBulkActions: {},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -72,11 +84,12 @@ describe('useSystemPromptEditor', () => {
|
|||
result.current.onSystemPromptSelectionChange(newPromptId);
|
||||
});
|
||||
|
||||
const newPrompt: Prompt = {
|
||||
const newPrompt: PromptResponse = {
|
||||
id: newPromptId,
|
||||
content: '',
|
||||
name: newPromptId,
|
||||
promptType: 'system',
|
||||
consumer: 'securitySolutionUI',
|
||||
};
|
||||
|
||||
expect(mockOnSelectedSystemPromptChange).toHaveBeenCalledWith(newPrompt);
|
||||
|
@ -90,10 +103,12 @@ describe('useSystemPromptEditor', () => {
|
|||
useSystemPromptEditor({
|
||||
onSelectedSystemPromptChange: mockOnSelectedSystemPromptChange,
|
||||
setUpdatedSystemPromptSettings: mockSetUpdatedSystemPromptSettings,
|
||||
setPromptsBulkActions: mockSetPromptsBulkActions,
|
||||
promptsBulkActions: {},
|
||||
})
|
||||
);
|
||||
|
||||
const expectedPrompt: Prompt = mockSuperheroSystemPrompt;
|
||||
const expectedPrompt: PromptResponse = mockSuperheroSystemPrompt;
|
||||
|
||||
act(() => {
|
||||
result.current.onSystemPromptSelectionChange(expectedPrompt);
|
||||
|
|
|
@ -5,28 +5,38 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
PromptResponse,
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { useCallback } from 'react';
|
||||
import { Prompt } from '../../../types';
|
||||
import { useAssistantContext } from '../../../../..';
|
||||
|
||||
interface Props {
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<Prompt[]>>;
|
||||
onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void;
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
}
|
||||
|
||||
export const useSystemPromptEditor = ({
|
||||
setUpdatedSystemPromptSettings,
|
||||
onSelectedSystemPromptChange,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
}: Props) => {
|
||||
const { currentAppId } = useAssistantContext();
|
||||
// When top level system prompt selection changes
|
||||
const onSystemPromptSelectionChange = useCallback(
|
||||
(systemPrompt?: Prompt | string) => {
|
||||
(systemPrompt?: PromptResponse | string) => {
|
||||
const isNew = typeof systemPrompt === 'string';
|
||||
const newSelectedSystemPrompt: Prompt | undefined = isNew
|
||||
const newSelectedSystemPrompt: PromptResponse | undefined = isNew
|
||||
? {
|
||||
id: systemPrompt ?? '',
|
||||
content: '',
|
||||
name: systemPrompt ?? '',
|
||||
promptType: 'system',
|
||||
consumer: currentAppId,
|
||||
}
|
||||
: systemPrompt;
|
||||
|
||||
|
@ -40,18 +50,42 @@ export const useSystemPromptEditor = ({
|
|||
|
||||
return prev;
|
||||
});
|
||||
|
||||
if (isNew) {
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
create: [
|
||||
...(promptsBulkActions.create ?? []),
|
||||
{
|
||||
...newSelectedSystemPrompt,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onSelectedSystemPromptChange(newSelectedSystemPrompt);
|
||||
},
|
||||
[onSelectedSystemPromptChange, setUpdatedSystemPromptSettings]
|
||||
[
|
||||
currentAppId,
|
||||
onSelectedSystemPromptChange,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
setUpdatedSystemPromptSettings,
|
||||
]
|
||||
);
|
||||
|
||||
const onSystemPromptDeleted = useCallback(
|
||||
(id: string) => {
|
||||
setUpdatedSystemPromptSettings((prev) => prev.filter((sp) => sp.id !== id));
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
delete: {
|
||||
ids: [...(promptsBulkActions.delete?.ids ?? []), id],
|
||||
},
|
||||
});
|
||||
},
|
||||
[setUpdatedSystemPromptSettings]
|
||||
[promptsBulkActions, setPromptsBulkActions, setUpdatedSystemPromptSettings]
|
||||
);
|
||||
|
||||
return { onSystemPromptSelectionChange, onSystemPromptDeleted };
|
||||
|
|
|
@ -16,6 +16,10 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
PromptResponse,
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { Conversation, ConversationsBulkActions, useAssistantContext } from '../../../../..';
|
||||
import { SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../../assistant_context/constants';
|
||||
import { AIConnector } from '../../../../connectorland/connector_selector';
|
||||
|
@ -26,7 +30,6 @@ import {
|
|||
useSessionPagination,
|
||||
} from '../../../common/components/assistant_settings_management/pagination/use_session_pagination';
|
||||
import { CANCEL, DELETE } from '../../../settings/translations';
|
||||
import { Prompt } from '../../../types';
|
||||
import { SystemPromptEditor } from '../system_prompt_modal/system_prompt_editor';
|
||||
import { SETTINGS_TITLE } from '../system_prompt_modal/translations';
|
||||
import { useSystemPromptEditor } from '../system_prompt_modal/use_system_prompt_editor';
|
||||
|
@ -37,11 +40,11 @@ interface Props {
|
|||
connectors: AIConnector[] | undefined;
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
conversationsSettingsBulkActions: ConversationsBulkActions;
|
||||
onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void;
|
||||
selectedSystemPrompt: Prompt | undefined;
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<Prompt[]>>;
|
||||
onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void;
|
||||
selectedSystemPrompt: PromptResponse | undefined;
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
setConversationSettings: React.Dispatch<React.SetStateAction<Record<string, Conversation>>>;
|
||||
systemPromptSettings: Prompt[];
|
||||
systemPromptSettings: PromptResponse[];
|
||||
setConversationsSettingsBulkActions: React.Dispatch<
|
||||
React.SetStateAction<ConversationsBulkActions>
|
||||
>;
|
||||
|
@ -49,6 +52,8 @@ interface Props {
|
|||
handleSave: (shouldRefetchConversation?: boolean) => void;
|
||||
onCancelClick: () => void;
|
||||
resetSettings: () => void;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
}
|
||||
|
||||
const SystemPromptSettingsManagementComponent = ({
|
||||
|
@ -65,6 +70,8 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
handleSave,
|
||||
onCancelClick,
|
||||
resetSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
}: Props) => {
|
||||
const { nameSpace } = useAssistantContext();
|
||||
const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility();
|
||||
|
@ -73,7 +80,7 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
openFlyout: openConfirmModal,
|
||||
closeFlyout: closeConfirmModal,
|
||||
} = useFlyoutModalVisibility();
|
||||
const [deletedPrompt, setDeletedPrompt] = useState<Prompt | null>();
|
||||
const [deletedPrompt, setDeletedPrompt] = useState<PromptResponse | null>();
|
||||
|
||||
const onCreate = useCallback(() => {
|
||||
onSelectedSystemPromptChange({
|
||||
|
@ -88,10 +95,12 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
const { onSystemPromptSelectionChange, onSystemPromptDeleted } = useSystemPromptEditor({
|
||||
setUpdatedSystemPromptSettings,
|
||||
onSelectedSystemPromptChange,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
});
|
||||
|
||||
const onEditActionClicked = useCallback(
|
||||
(prompt: Prompt) => {
|
||||
(prompt: PromptResponse) => {
|
||||
onSystemPromptSelectionChange(prompt);
|
||||
openFlyout();
|
||||
},
|
||||
|
@ -99,7 +108,7 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
);
|
||||
|
||||
const onDeleteActionClicked = useCallback(
|
||||
(prompt: Prompt) => {
|
||||
(prompt: PromptResponse) => {
|
||||
setDeletedPrompt(prompt);
|
||||
onSystemPromptDeleted(prompt.id);
|
||||
openConfirmModal();
|
||||
|
@ -200,6 +209,8 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
defaultConnector={defaultConnector}
|
||||
resetSettings={resetSettings}
|
||||
promptsBulkActions={promptsBulkActions}
|
||||
setPromptsBulkActions={setPromptsBulkActions}
|
||||
/>
|
||||
</Flyout>
|
||||
{deleteConfirmModalVisibility && deletedPrompt?.name && (
|
||||
|
|
|
@ -7,26 +7,25 @@
|
|||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useSystemPromptTable } from './use_system_prompt_table';
|
||||
import { Prompt } from '../../../types';
|
||||
import { Conversation } from '../../../../assistant_context/types';
|
||||
import { AIConnector } from '../../../../connectorland/connector_selector';
|
||||
import { customConvo, welcomeConvo } from '../../../../mock/conversation';
|
||||
import { mockConnectors } from '../../../../mock/connectors';
|
||||
import { ApiConfig } from '@kbn/elastic-assistant-common';
|
||||
import { ApiConfig, PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
// Mock data for tests
|
||||
const mockSystemPrompts: Prompt[] = [
|
||||
const mockSystemPrompts: PromptResponse[] = [
|
||||
{
|
||||
id: 'prompt-1',
|
||||
content: 'Prompt 1',
|
||||
name: 'Prompt 1',
|
||||
promptType: 'user',
|
||||
promptType: 'quick',
|
||||
},
|
||||
{
|
||||
id: 'prompt-2',
|
||||
content: 'Prompt 2',
|
||||
name: 'Prompt 2',
|
||||
promptType: 'user',
|
||||
promptType: 'quick',
|
||||
isNewConversationDefault: true,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
*/
|
||||
import { EuiBasicTableColumn, EuiIcon, EuiLink } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../../assistant_context/types';
|
||||
import { AIConnector } from '../../../../connectorland/connector_selector';
|
||||
import { BadgesColumn } from '../../../common/components/assistant_settings_management/badges';
|
||||
import { RowActions } from '../../../common/components/assistant_settings_management/row_actions';
|
||||
import { Prompt } from '../../../types';
|
||||
import {
|
||||
getConversationApiConfig,
|
||||
getInitialDefaultSystemPrompt,
|
||||
|
@ -21,10 +21,10 @@ import { getSelectedConversations } from './utils';
|
|||
|
||||
type ConversationsWithSystemPrompt = Record<
|
||||
string,
|
||||
Conversation & { systemPrompt: Prompt | undefined }
|
||||
Conversation & { systemPrompt: PromptResponse | undefined }
|
||||
>;
|
||||
|
||||
type SystemPromptTableItem = Prompt & { defaultConversations: string[] };
|
||||
type SystemPromptTableItem = PromptResponse & { defaultConversations: string[] };
|
||||
|
||||
export const useSystemPromptTable = () => {
|
||||
const getColumns = useCallback(
|
||||
|
@ -97,7 +97,7 @@ export const useSystemPromptTable = () => {
|
|||
connectors: AIConnector[] | undefined;
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
defaultConnector: AIConnector | undefined;
|
||||
systemPromptSettings: Prompt[];
|
||||
systemPromptSettings: PromptResponse[];
|
||||
}): SystemPromptTableItem[] => {
|
||||
const conversationsWithApiConfig = Object.entries(
|
||||
conversationSettings
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
import { ProviderEnum } from '@kbn/elastic-assistant-common';
|
||||
import { mockSystemPrompts } from '../../../../mock/system_prompt';
|
||||
import { PromptType } from '../../../types';
|
||||
import { getSelectedConversations } from './utils';
|
||||
import { PromptTypeEnum } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
describe('getSelectedConversations', () => {
|
||||
const allSystemPrompts = [...mockSystemPrompts];
|
||||
const conversationSettings = {
|
||||
|
@ -39,7 +39,7 @@ describe('getSelectedConversations', () => {
|
|||
content:
|
||||
'You are a helpful, expert assistant who answers questions about Elastic Security. Do not answer questions unrelated to Elastic Security.\nProvide the most detailed and relevant answer possible, as if you were relaying this information back to a cyber security expert.\nIf you answer a question related to KQL, EQL, or ES|QL, it should be immediately usable within an Elastic Security timeline; please always format the output correctly with back ticks. Any answer provided for Query DSL should also be usable in a security timeline. This means you should only ever include the "filter" portion of the query. xxx',
|
||||
name: 'Enhanced system prompt',
|
||||
promptType: 'system' as PromptType,
|
||||
promptType: PromptTypeEnum.system,
|
||||
isDefault: true,
|
||||
isNewConversationDefault: true,
|
||||
},
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../../assistant_context/types';
|
||||
import { Prompt } from '../../../types';
|
||||
|
||||
export const getSelectedConversations = (
|
||||
allSystemPrompts: Prompt[],
|
||||
allSystemPrompts: PromptResponse[],
|
||||
conversationSettings: Record<string, Conversation>,
|
||||
systemPromptId: string
|
||||
) => {
|
||||
|
|
|
@ -25,9 +25,9 @@ describe('QuickPromptSelector', () => {
|
|||
});
|
||||
it('Selects an existing quick prompt', () => {
|
||||
const { getByTestId } = render(<QuickPromptSelector {...testProps} />);
|
||||
expect(getByTestId('euiComboBoxPill')).toHaveTextContent(MOCK_QUICK_PROMPTS[0].title);
|
||||
expect(getByTestId('euiComboBoxPill')).toHaveTextContent(MOCK_QUICK_PROMPTS[0].name);
|
||||
fireEvent.click(getByTestId('comboBoxToggleListButton'));
|
||||
fireEvent.click(getByTestId(MOCK_QUICK_PROMPTS[1].title));
|
||||
fireEvent.click(getByTestId(MOCK_QUICK_PROMPTS[1].name));
|
||||
expect(onQuickPromptSelectionChange).toHaveBeenCalledWith(MOCK_QUICK_PROMPTS[1]);
|
||||
});
|
||||
it('Only custom option can be deleted', () => {
|
||||
|
@ -49,8 +49,10 @@ describe('QuickPromptSelector', () => {
|
|||
expect(onQuickPromptSelectionChange).toHaveBeenCalledWith({
|
||||
categories: [],
|
||||
color: '#D36086',
|
||||
prompt: 'quickly prompt please',
|
||||
title: 'A_CUSTOM_OPTION',
|
||||
content: 'quickly prompt please',
|
||||
id: 'A_CUSTOM_OPTION',
|
||||
name: 'A_CUSTOM_OPTION',
|
||||
promptType: 'quick',
|
||||
});
|
||||
});
|
||||
it('Reset settings every time before selecting an system prompt from the input if resetSettings is provided', () => {
|
||||
|
@ -60,7 +62,7 @@ describe('QuickPromptSelector', () => {
|
|||
);
|
||||
// changing the selection
|
||||
fireEvent.change(getByTestId('comboBoxSearchInput'), {
|
||||
target: { value: MOCK_QUICK_PROMPTS[1].title },
|
||||
target: { value: MOCK_QUICK_PROMPTS[1].name },
|
||||
});
|
||||
fireEvent.keyDown(getByTestId('comboBoxSearchInput'), {
|
||||
key: 'Enter',
|
||||
|
|
|
@ -18,16 +18,16 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import * as i18n from './translations';
|
||||
import { QuickPrompt } from '../types';
|
||||
|
||||
interface Props {
|
||||
isDisabled?: boolean;
|
||||
onQuickPromptDeleted: (quickPromptTitle: string) => void;
|
||||
onQuickPromptSelectionChange: (quickPrompt?: QuickPrompt | string) => void;
|
||||
quickPrompts: QuickPrompt[];
|
||||
onQuickPromptSelectionChange: (quickPrompt?: PromptResponse | string) => void;
|
||||
quickPrompts: PromptResponse[];
|
||||
selectedQuickPrompt?: PromptResponse;
|
||||
resetSettings?: () => void;
|
||||
selectedQuickPrompt?: QuickPrompt;
|
||||
}
|
||||
|
||||
export type QuickPromptSelectorOption = EuiComboBoxOptionOption<{ isDefault: boolean }>;
|
||||
|
@ -50,8 +50,9 @@ export const QuickPromptSelector: React.FC<Props> = React.memo(
|
|||
value: {
|
||||
isDefault: qp.isDefault ?? false,
|
||||
},
|
||||
label: qp.title,
|
||||
'data-test-subj': qp.title,
|
||||
label: qp.name,
|
||||
'data-test-subj': qp.name,
|
||||
id: qp.id,
|
||||
color: qp.color,
|
||||
}))
|
||||
);
|
||||
|
@ -62,7 +63,8 @@ export const QuickPromptSelector: React.FC<Props> = React.memo(
|
|||
value: {
|
||||
isDefault: true,
|
||||
},
|
||||
label: selectedQuickPrompt.title,
|
||||
label: selectedQuickPrompt.name,
|
||||
id: selectedQuickPrompt.id,
|
||||
color: selectedQuickPrompt.color,
|
||||
},
|
||||
]
|
||||
|
@ -76,7 +78,7 @@ export const QuickPromptSelector: React.FC<Props> = React.memo(
|
|||
const newQuickPrompt =
|
||||
quickPromptSelectorOption.length === 0
|
||||
? undefined
|
||||
: quickPrompts.find((qp) => qp.title === quickPromptSelectorOption[0]?.label) ??
|
||||
: quickPrompts.find((qp) => qp.name === quickPromptSelectorOption[0]?.label) ??
|
||||
quickPromptSelectorOption[0]?.label;
|
||||
onQuickPromptSelectionChange(newQuickPrompt);
|
||||
},
|
||||
|
@ -100,6 +102,7 @@ export const QuickPromptSelector: React.FC<Props> = React.memo(
|
|||
const newOption = {
|
||||
value: searchValue,
|
||||
label: searchValue,
|
||||
id: searchValue,
|
||||
};
|
||||
|
||||
if (!optionExists) {
|
||||
|
@ -125,11 +128,12 @@ export const QuickPromptSelector: React.FC<Props> = React.memo(
|
|||
// Callback for when user deletes a quick prompt
|
||||
const onDelete = useCallback(
|
||||
(label: string) => {
|
||||
const deleteId = options.find((o) => o.label === label)?.id;
|
||||
setOptions(options.filter((o) => o.label !== label));
|
||||
if (selectedOptions?.[0]?.label === label) {
|
||||
handleSelectionChange([]);
|
||||
}
|
||||
onQuickPromptDeleted(label);
|
||||
onQuickPromptDeleted(deleteId ?? label);
|
||||
},
|
||||
[handleSelectionChange, onQuickPromptDeleted, options, selectedOptions]
|
||||
);
|
||||
|
|
|
@ -10,9 +10,12 @@ import { EuiFormRow, EuiColorPicker, EuiTextArea } from '@elastic/eui';
|
|||
|
||||
import { EuiSetColorMethod } from '@elastic/eui/src/services/color_picker/color_picker';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
PromptResponse,
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { PromptContextTemplate } from '../../../..';
|
||||
import * as i18n from './translations';
|
||||
import { QuickPrompt } from '../types';
|
||||
import { QuickPromptSelector } from '../quick_prompt_selector/quick_prompt_selector';
|
||||
import { PromptContextSelector } from '../prompt_context_selector/prompt_context_selector';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
|
@ -21,11 +24,13 @@ import { useQuickPromptEditor } from './use_quick_prompt_editor';
|
|||
const DEFAULT_COLOR = '#D36086';
|
||||
|
||||
interface Props {
|
||||
onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void;
|
||||
quickPromptSettings: QuickPrompt[];
|
||||
onSelectedQuickPromptChange: (quickPrompt?: PromptResponse) => void;
|
||||
quickPromptSettings: PromptResponse[];
|
||||
resetSettings?: () => void;
|
||||
selectedQuickPrompt: QuickPrompt | undefined;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<QuickPrompt[]>>;
|
||||
selectedQuickPrompt: PromptResponse | undefined;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
}
|
||||
|
||||
const QuickPromptSettingsEditorComponent = ({
|
||||
|
@ -34,28 +39,30 @@ const QuickPromptSettingsEditorComponent = ({
|
|||
resetSettings,
|
||||
selectedQuickPrompt,
|
||||
setUpdatedQuickPromptSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
}: Props) => {
|
||||
const { basePromptContexts } = useAssistantContext();
|
||||
|
||||
// Prompt
|
||||
const prompt = useMemo(
|
||||
const promptContent = useMemo(
|
||||
// Fixing Cursor Jump in text area
|
||||
() => quickPromptSettings.find((p) => p.title === selectedQuickPrompt?.title)?.prompt ?? '',
|
||||
[selectedQuickPrompt?.title, quickPromptSettings]
|
||||
() => quickPromptSettings.find((p) => p.id === selectedQuickPrompt?.id)?.content ?? '',
|
||||
[selectedQuickPrompt?.id, quickPromptSettings]
|
||||
);
|
||||
|
||||
const handlePromptChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
if (selectedQuickPrompt != null) {
|
||||
setUpdatedQuickPromptSettings((prev) => {
|
||||
const alreadyExists = prev.some((qp) => qp.title === selectedQuickPrompt.title);
|
||||
setUpdatedQuickPromptSettings((prev): PromptResponse[] => {
|
||||
const alreadyExists = prev.some((qp) => qp.id === selectedQuickPrompt.id);
|
||||
|
||||
if (alreadyExists) {
|
||||
return prev.map((qp) => {
|
||||
if (qp.title === selectedQuickPrompt.title) {
|
||||
if (qp.id === selectedQuickPrompt.id) {
|
||||
return {
|
||||
...qp,
|
||||
prompt: e.target.value,
|
||||
content: e.target.value,
|
||||
};
|
||||
}
|
||||
return qp;
|
||||
|
@ -64,9 +71,45 @@ const QuickPromptSettingsEditorComponent = ({
|
|||
|
||||
return prev;
|
||||
});
|
||||
|
||||
const existingPrompt = quickPromptSettings.find((sp) => sp.id === selectedQuickPrompt.id);
|
||||
if (existingPrompt) {
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
...(selectedQuickPrompt.name !== selectedQuickPrompt.id
|
||||
? {
|
||||
update: [
|
||||
...(promptsBulkActions.update ?? []).filter(
|
||||
(p) => p.id !== selectedQuickPrompt.id
|
||||
),
|
||||
{
|
||||
...selectedQuickPrompt,
|
||||
content: e.target.value,
|
||||
},
|
||||
],
|
||||
}
|
||||
: {
|
||||
create: [
|
||||
...(promptsBulkActions.create ?? []).filter(
|
||||
(p) => p.name !== selectedQuickPrompt.name
|
||||
),
|
||||
{
|
||||
...selectedQuickPrompt,
|
||||
content: e.target.value,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[selectedQuickPrompt, setUpdatedQuickPromptSettings]
|
||||
[
|
||||
promptsBulkActions,
|
||||
quickPromptSettings,
|
||||
selectedQuickPrompt,
|
||||
setPromptsBulkActions,
|
||||
setUpdatedQuickPromptSettings,
|
||||
]
|
||||
);
|
||||
|
||||
// Color
|
||||
|
@ -79,11 +122,11 @@ const QuickPromptSettingsEditorComponent = ({
|
|||
(color, { hex, isValid }) => {
|
||||
if (selectedQuickPrompt != null) {
|
||||
setUpdatedQuickPromptSettings((prev) => {
|
||||
const alreadyExists = prev.some((qp) => qp.title === selectedQuickPrompt.title);
|
||||
const alreadyExists = prev.some((qp) => qp.name === selectedQuickPrompt.name);
|
||||
|
||||
if (alreadyExists) {
|
||||
return prev.map((qp) => {
|
||||
if (qp.title === selectedQuickPrompt.title) {
|
||||
if (qp.name === selectedQuickPrompt.name) {
|
||||
return {
|
||||
...qp,
|
||||
color,
|
||||
|
@ -94,9 +137,44 @@ const QuickPromptSettingsEditorComponent = ({
|
|||
}
|
||||
return prev;
|
||||
});
|
||||
const existingPrompt = quickPromptSettings.find((sp) => sp.id === selectedQuickPrompt.id);
|
||||
if (existingPrompt) {
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
...(selectedQuickPrompt.name !== selectedQuickPrompt.id
|
||||
? {
|
||||
update: [
|
||||
...(promptsBulkActions.update ?? []).filter(
|
||||
(p) => p.id !== selectedQuickPrompt.id
|
||||
),
|
||||
{
|
||||
...selectedQuickPrompt,
|
||||
color,
|
||||
},
|
||||
],
|
||||
}
|
||||
: {
|
||||
create: [
|
||||
...(promptsBulkActions.create ?? []).filter(
|
||||
(p) => p.name !== selectedQuickPrompt.name
|
||||
),
|
||||
{
|
||||
...selectedQuickPrompt,
|
||||
color,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[selectedQuickPrompt, setUpdatedQuickPromptSettings]
|
||||
[
|
||||
promptsBulkActions,
|
||||
quickPromptSettings,
|
||||
selectedQuickPrompt,
|
||||
setPromptsBulkActions,
|
||||
setUpdatedQuickPromptSettings,
|
||||
]
|
||||
);
|
||||
|
||||
// Prompt Contexts
|
||||
|
@ -112,11 +190,11 @@ const QuickPromptSettingsEditorComponent = ({
|
|||
(pc: PromptContextTemplate[]) => {
|
||||
if (selectedQuickPrompt != null) {
|
||||
setUpdatedQuickPromptSettings((prev) => {
|
||||
const alreadyExists = prev.some((qp) => qp.title === selectedQuickPrompt.title);
|
||||
const alreadyExists = prev.some((qp) => qp.name === selectedQuickPrompt.name);
|
||||
|
||||
if (alreadyExists) {
|
||||
return prev.map((qp) => {
|
||||
if (qp.title === selectedQuickPrompt.title) {
|
||||
if (qp.name === selectedQuickPrompt.name) {
|
||||
return {
|
||||
...qp,
|
||||
categories: pc.map((p) => p.category),
|
||||
|
@ -127,15 +205,53 @@ const QuickPromptSettingsEditorComponent = ({
|
|||
}
|
||||
return prev;
|
||||
});
|
||||
|
||||
const existingPrompt = quickPromptSettings.find((sp) => sp.id === selectedQuickPrompt.id);
|
||||
if (existingPrompt) {
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
...(selectedQuickPrompt.name !== selectedQuickPrompt.id
|
||||
? {
|
||||
update: [
|
||||
...(promptsBulkActions.update ?? []).filter(
|
||||
(p) => p.id !== selectedQuickPrompt.id
|
||||
),
|
||||
{
|
||||
...selectedQuickPrompt,
|
||||
categories: pc.map((p) => p.category),
|
||||
},
|
||||
],
|
||||
}
|
||||
: {
|
||||
create: [
|
||||
...(promptsBulkActions.create ?? []).filter(
|
||||
(p) => p.name !== selectedQuickPrompt.name
|
||||
),
|
||||
{
|
||||
...selectedQuickPrompt,
|
||||
categories: pc.map((p) => p.category),
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[selectedQuickPrompt, setUpdatedQuickPromptSettings]
|
||||
[
|
||||
promptsBulkActions,
|
||||
quickPromptSettings,
|
||||
selectedQuickPrompt,
|
||||
setPromptsBulkActions,
|
||||
setUpdatedQuickPromptSettings,
|
||||
]
|
||||
);
|
||||
|
||||
// When top level quick prompt selection changes
|
||||
const { onQuickPromptDeleted, onQuickPromptSelectionChange } = useQuickPromptEditor({
|
||||
onSelectedQuickPromptChange,
|
||||
setUpdatedQuickPromptSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -158,7 +274,7 @@ const QuickPromptSettingsEditorComponent = ({
|
|||
data-test-subj="quick-prompt-prompt"
|
||||
onChange={handlePromptChange}
|
||||
placeholder={i18n.QUICK_PROMPT_PROMPT_PLACEHOLDER}
|
||||
value={prompt}
|
||||
value={promptContent}
|
||||
css={css`
|
||||
min-height: 150px;
|
||||
`}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { MOCK_QUICK_PROMPTS } from '../../../mock/quick_prompt';
|
|||
import { mockPromptContexts } from '../../../mock/prompt_context';
|
||||
|
||||
const onSelectedQuickPromptChange = jest.fn();
|
||||
const setPromptsBulkActions = jest.fn();
|
||||
const setUpdatedQuickPromptSettings = jest.fn().mockImplementation((fn) => {
|
||||
return fn(MOCK_QUICK_PROMPTS);
|
||||
});
|
||||
|
@ -22,6 +23,8 @@ const testProps = {
|
|||
quickPromptSettings: MOCK_QUICK_PROMPTS,
|
||||
selectedQuickPrompt: MOCK_QUICK_PROMPTS[0],
|
||||
setUpdatedQuickPromptSettings,
|
||||
promptsBulkActions: {},
|
||||
setPromptsBulkActions,
|
||||
};
|
||||
const mockContext = {
|
||||
basePromptContexts: MOCK_QUICK_PROMPTS,
|
||||
|
@ -91,8 +94,11 @@ describe('QuickPromptSettings', () => {
|
|||
const customOption = {
|
||||
categories: [],
|
||||
color: '#D36086',
|
||||
prompt: '',
|
||||
title: 'sooper custom prompt',
|
||||
consumer: undefined,
|
||||
content: '',
|
||||
id: 'sooper custom prompt',
|
||||
name: 'sooper custom prompt',
|
||||
promptType: 'quick',
|
||||
};
|
||||
expect(setUpdatedQuickPromptSettings).toHaveReturnedWith([...MOCK_QUICK_PROMPTS, customOption]);
|
||||
expect(onSelectedQuickPromptChange).toHaveBeenCalledWith(customOption);
|
||||
|
@ -130,7 +136,7 @@ describe('QuickPromptSettings', () => {
|
|||
const previousFirstElementOfTheArray = mutatableQuickPrompts.shift();
|
||||
|
||||
expect(setUpdatedQuickPromptSettings).toHaveReturnedWith([
|
||||
{ ...previousFirstElementOfTheArray, prompt: 'what does this do' },
|
||||
{ ...previousFirstElementOfTheArray, content: 'what does this do' },
|
||||
...mutatableQuickPrompts,
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -8,15 +8,20 @@
|
|||
import React from 'react';
|
||||
import { EuiTitle, EuiText, EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import {
|
||||
PromptResponse,
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import * as i18n from './translations';
|
||||
import { QuickPrompt } from '../types';
|
||||
import { QuickPromptSettingsEditor } from './quick_prompt_editor';
|
||||
|
||||
interface Props {
|
||||
onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void;
|
||||
quickPromptSettings: QuickPrompt[];
|
||||
selectedQuickPrompt: QuickPrompt | undefined;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<QuickPrompt[]>>;
|
||||
onSelectedQuickPromptChange: (quickPrompt?: PromptResponse) => void;
|
||||
quickPromptSettings: PromptResponse[];
|
||||
selectedQuickPrompt: PromptResponse | undefined;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,6 +33,8 @@ export const QuickPromptSettings: React.FC<Props> = React.memo<Props>(
|
|||
quickPromptSettings,
|
||||
selectedQuickPrompt,
|
||||
setUpdatedQuickPromptSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
|
@ -43,6 +50,8 @@ export const QuickPromptSettings: React.FC<Props> = React.memo<Props>(
|
|||
quickPromptSettings={quickPromptSettings}
|
||||
selectedQuickPrompt={selectedQuickPrompt}
|
||||
setUpdatedQuickPromptSettings={setUpdatedQuickPromptSettings}
|
||||
promptsBulkActions={promptsBulkActions}
|
||||
setPromptsBulkActions={setPromptsBulkActions}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -7,18 +7,24 @@
|
|||
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { useQuickPromptEditor, DEFAULT_COLOR } from './use_quick_prompt_editor';
|
||||
import { QuickPrompt } from '../types';
|
||||
import { mockAlertPromptContext } from '../../../mock/prompt_context';
|
||||
import { MOCK_QUICK_PROMPTS } from '../../../mock/quick_prompt';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
|
||||
jest.mock('../../../assistant_context');
|
||||
// Mock functions for the tests
|
||||
const mockOnSelectedQuickPromptChange = jest.fn();
|
||||
const mockSetUpdatedQuickPromptSettings = jest.fn();
|
||||
const mockPreviousQuickPrompts = [...MOCK_QUICK_PROMPTS];
|
||||
const setPromptsBulkActions = jest.fn();
|
||||
|
||||
describe('useQuickPromptEditor', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useAssistantContext as jest.Mock).mockReturnValue({
|
||||
currentAppId: 'securitySolutionUI',
|
||||
});
|
||||
});
|
||||
|
||||
test('should delete a quick prompt by title', () => {
|
||||
|
@ -26,6 +32,8 @@ describe('useQuickPromptEditor', () => {
|
|||
useQuickPromptEditor({
|
||||
onSelectedQuickPromptChange: mockOnSelectedQuickPromptChange,
|
||||
setUpdatedQuickPromptSettings: mockSetUpdatedQuickPromptSettings,
|
||||
setPromptsBulkActions,
|
||||
promptsBulkActions: {},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -34,7 +42,7 @@ describe('useQuickPromptEditor', () => {
|
|||
});
|
||||
|
||||
expect(mockSetUpdatedQuickPromptSettings.mock.calls[0][0]?.(mockPreviousQuickPrompts)).toEqual(
|
||||
MOCK_QUICK_PROMPTS.filter((qp) => qp.title !== 'ALERT_SUMMARIZATION_TITLE')
|
||||
MOCK_QUICK_PROMPTS.filter((qp) => qp.name !== 'ALERT_SUMMARIZATION_TITLE')
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -44,6 +52,8 @@ describe('useQuickPromptEditor', () => {
|
|||
useQuickPromptEditor({
|
||||
onSelectedQuickPromptChange: mockOnSelectedQuickPromptChange,
|
||||
setUpdatedQuickPromptSettings: mockSetUpdatedQuickPromptSettings,
|
||||
setPromptsBulkActions,
|
||||
promptsBulkActions: {},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -51,11 +61,14 @@ describe('useQuickPromptEditor', () => {
|
|||
result.current.onQuickPromptSelectionChange(newPromptTitle);
|
||||
});
|
||||
|
||||
const newPrompt: QuickPrompt = {
|
||||
title: newPromptTitle,
|
||||
prompt: '',
|
||||
const newPrompt: PromptResponse = {
|
||||
name: newPromptTitle,
|
||||
content: '',
|
||||
color: DEFAULT_COLOR,
|
||||
categories: [],
|
||||
id: newPromptTitle,
|
||||
promptType: 'quick',
|
||||
consumer: 'securitySolutionUI',
|
||||
};
|
||||
|
||||
expect(mockOnSelectedQuickPromptChange).toHaveBeenCalledWith(newPrompt);
|
||||
|
@ -70,17 +83,21 @@ describe('useQuickPromptEditor', () => {
|
|||
useQuickPromptEditor({
|
||||
onSelectedQuickPromptChange: mockOnSelectedQuickPromptChange,
|
||||
setUpdatedQuickPromptSettings: mockSetUpdatedQuickPromptSettings,
|
||||
setPromptsBulkActions,
|
||||
promptsBulkActions: {},
|
||||
})
|
||||
);
|
||||
|
||||
const alertData = await mockAlertPromptContext.getPromptContext();
|
||||
|
||||
const expectedPrompt: QuickPrompt = {
|
||||
title: mockAlertPromptContext.description,
|
||||
prompt: alertData,
|
||||
const expectedPrompt: PromptResponse = {
|
||||
name: mockAlertPromptContext.description,
|
||||
content: JSON.stringify(alertData ?? {}),
|
||||
color: DEFAULT_COLOR,
|
||||
categories: [mockAlertPromptContext.category],
|
||||
} as QuickPrompt;
|
||||
id: '',
|
||||
promptType: 'quick',
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.onQuickPromptSelectionChange(expectedPrompt);
|
||||
|
|
|
@ -5,41 +5,60 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
PromptResponse,
|
||||
PromptTypeEnum,
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { useCallback } from 'react';
|
||||
import { QuickPrompt } from '../types';
|
||||
import { useAssistantContext } from '../../../..';
|
||||
|
||||
export const DEFAULT_COLOR = '#D36086';
|
||||
|
||||
export const useQuickPromptEditor = ({
|
||||
onSelectedQuickPromptChange,
|
||||
setUpdatedQuickPromptSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
}: {
|
||||
onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<QuickPrompt[]>>;
|
||||
onSelectedQuickPromptChange: (quickPrompt?: PromptResponse) => void;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
}) => {
|
||||
const { currentAppId } = useAssistantContext();
|
||||
const onQuickPromptDeleted = useCallback(
|
||||
(title: string) => {
|
||||
setUpdatedQuickPromptSettings((prev) => prev.filter((qp) => qp.title !== title));
|
||||
(id: string) => {
|
||||
setUpdatedQuickPromptSettings((prev) => prev.filter((qp) => qp.id !== id));
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
delete: {
|
||||
ids: [...(promptsBulkActions.delete?.ids ?? []), id],
|
||||
},
|
||||
});
|
||||
},
|
||||
[setUpdatedQuickPromptSettings]
|
||||
[promptsBulkActions, setPromptsBulkActions, setUpdatedQuickPromptSettings]
|
||||
);
|
||||
|
||||
// When top level quick prompt selection changes
|
||||
const onQuickPromptSelectionChange = useCallback(
|
||||
(quickPrompt?: QuickPrompt | string) => {
|
||||
(quickPrompt?: PromptResponse | string) => {
|
||||
const isNew = typeof quickPrompt === 'string';
|
||||
const newSelectedQuickPrompt: QuickPrompt | undefined = isNew
|
||||
const newSelectedQuickPrompt: PromptResponse | undefined = isNew
|
||||
? {
|
||||
title: quickPrompt ?? '',
|
||||
prompt: '',
|
||||
name: quickPrompt,
|
||||
id: quickPrompt,
|
||||
content: '',
|
||||
color: DEFAULT_COLOR,
|
||||
categories: [],
|
||||
promptType: PromptTypeEnum.quick,
|
||||
consumer: currentAppId,
|
||||
}
|
||||
: quickPrompt;
|
||||
|
||||
if (newSelectedQuickPrompt != null) {
|
||||
setUpdatedQuickPromptSettings((prev) => {
|
||||
const alreadyExists = prev.some((qp) => qp.title === newSelectedQuickPrompt.title);
|
||||
const alreadyExists = prev.some((qp) => qp.name === newSelectedQuickPrompt.name);
|
||||
|
||||
if (!alreadyExists) {
|
||||
return [...prev, newSelectedQuickPrompt];
|
||||
|
@ -47,11 +66,29 @@ export const useQuickPromptEditor = ({
|
|||
|
||||
return prev;
|
||||
});
|
||||
|
||||
if (isNew) {
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
create: [
|
||||
...(promptsBulkActions.create ?? []),
|
||||
{
|
||||
...newSelectedQuickPrompt,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onSelectedQuickPromptChange(newSelectedQuickPrompt);
|
||||
},
|
||||
[onSelectedQuickPromptChange, setUpdatedQuickPromptSettings]
|
||||
[
|
||||
currentAppId,
|
||||
onSelectedQuickPromptChange,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
setUpdatedQuickPromptSettings,
|
||||
]
|
||||
);
|
||||
|
||||
return { onQuickPromptDeleted, onQuickPromptSelectionChange };
|
||||
|
|
|
@ -14,7 +14,10 @@ import {
|
|||
EuiPanel,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { QuickPrompt } from '../types';
|
||||
import {
|
||||
PromptResponse,
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { QuickPromptSettingsEditor } from '../quick_prompt_settings/quick_prompt_editor';
|
||||
import * as i18n from './translations';
|
||||
import { useFlyoutModalVisibility } from '../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility';
|
||||
|
@ -32,11 +35,13 @@ import { useAssistantContext } from '../../../assistant_context';
|
|||
interface Props {
|
||||
handleSave: (shouldRefetchConversation?: boolean) => void;
|
||||
onCancelClick: () => void;
|
||||
onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void;
|
||||
quickPromptSettings: QuickPrompt[];
|
||||
onSelectedQuickPromptChange: (quickPrompt?: PromptResponse) => void;
|
||||
quickPromptSettings: PromptResponse[];
|
||||
resetSettings?: () => void;
|
||||
selectedQuickPrompt: QuickPrompt | undefined;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<QuickPrompt[]>>;
|
||||
selectedQuickPrompt: PromptResponse | undefined;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
}
|
||||
|
||||
const QuickPromptSettingsManagementComponent = ({
|
||||
|
@ -47,11 +52,13 @@ const QuickPromptSettingsManagementComponent = ({
|
|||
resetSettings,
|
||||
selectedQuickPrompt,
|
||||
setUpdatedQuickPromptSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
}: Props) => {
|
||||
const { nameSpace, basePromptContexts } = useAssistantContext();
|
||||
|
||||
const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility();
|
||||
const [deletedQuickPrompt, setDeletedQuickPrompt] = useState<QuickPrompt | null>();
|
||||
const [deletedQuickPrompt, setDeletedQuickPrompt] = useState<PromptResponse | null>();
|
||||
const {
|
||||
isFlyoutOpen: deleteConfirmModalVisibility,
|
||||
openFlyout: openConfirmModal,
|
||||
|
@ -61,10 +68,12 @@ const QuickPromptSettingsManagementComponent = ({
|
|||
const { onQuickPromptDeleted, onQuickPromptSelectionChange } = useQuickPromptEditor({
|
||||
onSelectedQuickPromptChange,
|
||||
setUpdatedQuickPromptSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
});
|
||||
|
||||
const onEditActionClicked = useCallback(
|
||||
(prompt: QuickPrompt) => {
|
||||
(prompt: PromptResponse) => {
|
||||
onQuickPromptSelectionChange(prompt);
|
||||
openFlyout();
|
||||
},
|
||||
|
@ -72,9 +81,9 @@ const QuickPromptSettingsManagementComponent = ({
|
|||
);
|
||||
|
||||
const onDeleteActionClicked = useCallback(
|
||||
(prompt: QuickPrompt) => {
|
||||
(prompt: PromptResponse) => {
|
||||
setDeletedQuickPrompt(prompt);
|
||||
onQuickPromptDeleted(prompt.title);
|
||||
onQuickPromptDeleted(prompt.id);
|
||||
openConfirmModal();
|
||||
},
|
||||
[onQuickPromptDeleted, openConfirmModal]
|
||||
|
@ -123,10 +132,10 @@ const QuickPromptSettingsManagementComponent = ({
|
|||
|
||||
const confirmationTitle = useMemo(
|
||||
() =>
|
||||
deletedQuickPrompt?.title
|
||||
? i18n.DELETE_QUICK_PROMPT_MODAL_TITLE(deletedQuickPrompt.title)
|
||||
deletedQuickPrompt?.name
|
||||
? i18n.DELETE_QUICK_PROMPT_MODAL_TITLE(deletedQuickPrompt.name)
|
||||
: i18n.DELETE_QUICK_PROMPT_MODAL_DEFAULT_TITLE,
|
||||
[deletedQuickPrompt?.title]
|
||||
[deletedQuickPrompt?.name]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -161,6 +170,8 @@ const QuickPromptSettingsManagementComponent = ({
|
|||
resetSettings={resetSettings}
|
||||
selectedQuickPrompt={selectedQuickPrompt}
|
||||
setUpdatedQuickPromptSettings={setUpdatedQuickPromptSettings}
|
||||
promptsBulkActions={promptsBulkActions}
|
||||
setPromptsBulkActions={setPromptsBulkActions}
|
||||
/>
|
||||
</Flyout>
|
||||
{deleteConfirmModalVisibility && deletedQuickPrompt && (
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useQuickPromptTable } from './use_quick_prompt_table';
|
||||
import { EuiTableComputedColumnType } from '@elastic/eui';
|
||||
import { QuickPrompt } from '../types';
|
||||
import { MOCK_QUICK_PROMPTS } from '../../../mock/quick_prompt';
|
||||
import { mockPromptContexts } from '../../../mock/prompt_context';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
const mockOnEditActionClicked = jest.fn();
|
||||
const mockOnDeleteActionClicked = jest.fn();
|
||||
|
@ -43,7 +43,7 @@ describe('useQuickPromptTable', () => {
|
|||
});
|
||||
|
||||
const mockQuickPrompt = { ...MOCK_QUICK_PROMPTS[0], categories: ['alert'] };
|
||||
const mockBadgesColumn = (columns[1] as EuiTableComputedColumnType<QuickPrompt>).render(
|
||||
const mockBadgesColumn = (columns[1] as EuiTableComputedColumnType<PromptResponse>).render(
|
||||
mockQuickPrompt
|
||||
);
|
||||
const selectedPromptContexts = mockPromptContexts
|
||||
|
@ -51,7 +51,7 @@ describe('useQuickPromptTable', () => {
|
|||
.map((bpc) => bpc.description);
|
||||
expect(mockBadgesColumn).toHaveProperty('props', {
|
||||
items: selectedPromptContexts,
|
||||
prefix: MOCK_QUICK_PROMPTS[0].title,
|
||||
prefix: MOCK_QUICK_PROMPTS[0].name,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -62,7 +62,7 @@ describe('useQuickPromptTable', () => {
|
|||
onDeleteActionClicked: mockOnDeleteActionClicked,
|
||||
});
|
||||
|
||||
const mockRowActions = (columns[2] as EuiTableComputedColumnType<QuickPrompt>).render(
|
||||
const mockRowActions = (columns[2] as EuiTableComputedColumnType<PromptResponse>).render(
|
||||
MOCK_QUICK_PROMPTS[0]
|
||||
);
|
||||
|
||||
|
@ -83,7 +83,7 @@ describe('useQuickPromptTable', () => {
|
|||
|
||||
const nonDefaultPrompt = MOCK_QUICK_PROMPTS.find((qp) => !qp.isDefault);
|
||||
if (nonDefaultPrompt) {
|
||||
const mockRowActions = (columns[2] as EuiTableComputedColumnType<QuickPrompt>).render(
|
||||
const mockRowActions = (columns[2] as EuiTableComputedColumnType<PromptResponse>).render(
|
||||
nonDefaultPrompt
|
||||
);
|
||||
expect(mockRowActions).toHaveProperty('props', {
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import { EuiBasicTableColumn, EuiLink } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { BadgesColumn } from '../../common/components/assistant_settings_management/badges';
|
||||
import { RowActions } from '../../common/components/assistant_settings_management/row_actions';
|
||||
import { PromptContextTemplate } from '../../prompt_context/types';
|
||||
import { QuickPrompt } from '../types';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const useQuickPromptTable = () => {
|
||||
|
@ -21,29 +21,29 @@ export const useQuickPromptTable = () => {
|
|||
onDeleteActionClicked,
|
||||
}: {
|
||||
basePromptContexts: PromptContextTemplate[];
|
||||
onEditActionClicked: (prompt: QuickPrompt) => void;
|
||||
onDeleteActionClicked: (prompt: QuickPrompt) => void;
|
||||
}): Array<EuiBasicTableColumn<QuickPrompt>> => [
|
||||
onEditActionClicked: (prompt: PromptResponse) => void;
|
||||
onDeleteActionClicked: (prompt: PromptResponse) => void;
|
||||
}): Array<EuiBasicTableColumn<PromptResponse>> => [
|
||||
{
|
||||
align: 'left',
|
||||
name: i18n.QUICK_PROMPTS_TABLE_COLUMN_NAME,
|
||||
render: (prompt: QuickPrompt) =>
|
||||
prompt?.title ? (
|
||||
<EuiLink onClick={() => onEditActionClicked(prompt)}>{prompt?.title}</EuiLink>
|
||||
render: (prompt: PromptResponse) =>
|
||||
prompt?.name ? (
|
||||
<EuiLink onClick={() => onEditActionClicked(prompt)}>{prompt?.name}</EuiLink>
|
||||
) : null,
|
||||
sortable: ({ title }: QuickPrompt) => title,
|
||||
sortable: ({ name }: PromptResponse) => name,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CONTEXTS,
|
||||
render: (prompt: QuickPrompt) => {
|
||||
render: (prompt: PromptResponse) => {
|
||||
const selectedPromptContexts = (
|
||||
basePromptContexts.filter((bpc) =>
|
||||
prompt?.categories?.some((cat) => bpc?.category === cat)
|
||||
) ?? []
|
||||
).map((bpc) => bpc?.description);
|
||||
return selectedPromptContexts ? (
|
||||
<BadgesColumn items={selectedPromptContexts} prefix={prompt.title} />
|
||||
<BadgesColumn items={selectedPromptContexts} prefix={prompt.name} />
|
||||
) : null;
|
||||
},
|
||||
},
|
||||
|
@ -58,13 +58,13 @@ export const useQuickPromptTable = () => {
|
|||
align: 'center',
|
||||
name: i18n.QUICK_PROMPTS_TABLE_COLUMN_ACTIONS,
|
||||
width: '120px',
|
||||
render: (prompt: QuickPrompt) => {
|
||||
render: (prompt: PromptResponse) => {
|
||||
if (!prompt) {
|
||||
return null;
|
||||
}
|
||||
const isDeletable = !prompt.isDefault;
|
||||
return (
|
||||
<RowActions<QuickPrompt>
|
||||
<RowActions<PromptResponse>
|
||||
rowItem={prompt}
|
||||
onDelete={isDeletable ? onDeleteActionClicked : undefined}
|
||||
onEdit={onEditActionClicked}
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import React from 'react';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { QuickPrompts } from './quick_prompts';
|
||||
import { TestProviders } from '../../mock/test_providers/test_providers';
|
||||
import { MOCK_QUICK_PROMPTS } from '../../mock/quick_prompt';
|
||||
import { QUICK_PROMPTS_TAB } from '../settings/const';
|
||||
import { QuickPrompts } from './quick_prompts';
|
||||
|
||||
const setInput = jest.fn();
|
||||
const setIsSettingsModalVisible = jest.fn();
|
||||
|
@ -20,6 +20,7 @@ const testProps = {
|
|||
setIsSettingsModalVisible,
|
||||
trackPrompt,
|
||||
isFlyoutMode: false,
|
||||
allPrompts: MOCK_QUICK_PROMPTS,
|
||||
};
|
||||
const setSelectedSettingsTab = jest.fn();
|
||||
const mockUseAssistantContext = {
|
||||
|
|
|
@ -17,7 +17,10 @@ import {
|
|||
import { useMeasure } from 'react-use';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { QuickPrompt } from '../../..';
|
||||
import {
|
||||
PromptResponse,
|
||||
PromptTypeEnum,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import * as i18n from './translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { QUICK_PROMPTS_TAB } from '../settings/const';
|
||||
|
@ -30,6 +33,7 @@ interface QuickPromptsProps {
|
|||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
trackPrompt: (prompt: string) => void;
|
||||
isFlyoutMode: boolean;
|
||||
allPrompts: PromptResponse[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,11 +42,10 @@ interface QuickPromptsProps {
|
|||
* and localstorage for storing new and edited prompts.
|
||||
*/
|
||||
export const QuickPrompts: React.FC<QuickPromptsProps> = React.memo(
|
||||
({ setInput, setIsSettingsModalVisible, trackPrompt, isFlyoutMode }) => {
|
||||
({ setInput, setIsSettingsModalVisible, trackPrompt, isFlyoutMode, allPrompts }) => {
|
||||
const [quickPromptsContainerRef, { width }] = useMeasure();
|
||||
|
||||
const { allQuickPrompts, knowledgeBase, promptContexts, setSelectedSettingsTab } =
|
||||
useAssistantContext();
|
||||
const { knowledgeBase, promptContexts, setSelectedSettingsTab } = useAssistantContext();
|
||||
|
||||
const contextFilteredQuickPrompts = useMemo(() => {
|
||||
const registeredPromptContextTitles = Object.values(promptContexts).map((pc) => pc.category);
|
||||
|
@ -50,17 +53,21 @@ export const QuickPrompts: React.FC<QuickPromptsProps> = React.memo(
|
|||
if (knowledgeBase.isEnabledKnowledgeBase) {
|
||||
registeredPromptContextTitles.push(KNOWLEDGE_BASE_CATEGORY);
|
||||
}
|
||||
return allQuickPrompts.filter((quickPrompt) => {
|
||||
return allPrompts.filter((prompt) => {
|
||||
// only quick prompts
|
||||
if (prompt.promptType !== PromptTypeEnum.quick) {
|
||||
return false;
|
||||
}
|
||||
// Return quick prompt as match if it has no categories, otherwise ensure category exists in registered prompt contexts
|
||||
if (quickPrompt.categories == null || quickPrompt.categories.length === 0) {
|
||||
if (!prompt.categories || prompt.categories.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return quickPrompt.categories.some((category) => {
|
||||
return prompt.categories?.some((category) => {
|
||||
return registeredPromptContextTitles.includes(category);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [allQuickPrompts, knowledgeBase.isEnabledKnowledgeBase, promptContexts]);
|
||||
}, [allPrompts, knowledgeBase.isEnabledKnowledgeBase, promptContexts]);
|
||||
|
||||
// Overflow state
|
||||
const [isOverflowPopoverOpen, setIsOverflowPopoverOpen] = useState(false);
|
||||
|
@ -71,10 +78,10 @@ export const QuickPrompts: React.FC<QuickPromptsProps> = React.memo(
|
|||
const closeOverflowPopover = useCallback(() => setIsOverflowPopoverOpen(false), []);
|
||||
|
||||
const onClickAddQuickPrompt = useCallback(
|
||||
(badge: QuickPrompt) => {
|
||||
setInput(badge.prompt);
|
||||
(badge: PromptResponse) => {
|
||||
setInput(badge.content);
|
||||
if (badge.isDefault) {
|
||||
trackPrompt(badge.title);
|
||||
trackPrompt(badge.name);
|
||||
} else {
|
||||
trackPrompt('Custom');
|
||||
}
|
||||
|
@ -83,7 +90,7 @@ export const QuickPrompts: React.FC<QuickPromptsProps> = React.memo(
|
|||
);
|
||||
|
||||
const onClickOverflowQuickPrompt = useCallback(
|
||||
(badge: QuickPrompt) => {
|
||||
(badge: PromptResponse) => {
|
||||
onClickAddQuickPrompt(badge);
|
||||
closeOverflowPopover();
|
||||
},
|
||||
|
@ -137,9 +144,9 @@ export const QuickPrompts: React.FC<QuickPromptsProps> = React.memo(
|
|||
<EuiBadge
|
||||
color={badge.color}
|
||||
onClick={() => onClickAddQuickPrompt(badge)}
|
||||
onClickAriaLabel={badge.title}
|
||||
onClickAriaLabel={badge.name}
|
||||
>
|
||||
{badge.title}
|
||||
{badge.name}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
|
@ -172,9 +179,9 @@ export const QuickPrompts: React.FC<QuickPromptsProps> = React.memo(
|
|||
<EuiBadge
|
||||
color={badge.color}
|
||||
onClick={() => onClickOverflowQuickPrompt(badge)}
|
||||
onClickAriaLabel={badge.title}
|
||||
onClickAriaLabel={badge.name}
|
||||
>
|
||||
{badge.title}
|
||||
{badge.name}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* 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 { PromptContext } from '../../..';
|
||||
|
||||
/**
|
||||
* A QuickPrompt is a badge that is displayed below the Assistant's input field. They provide
|
||||
* a quick way for users to insert prompts as templates into the Assistant's input field. If no
|
||||
* categories are provided they will always display with the assistant, however categories can be
|
||||
* supplied to only display the QuickPrompt when the Assistant is registered with corresponding
|
||||
* PromptContext's containing the same category.
|
||||
*
|
||||
* isDefault: If true, this QuickPrompt cannot be deleted by the user
|
||||
*/
|
||||
export interface QuickPrompt {
|
||||
title: string;
|
||||
prompt: string;
|
||||
color: string;
|
||||
categories?: Array<PromptContext['category']>;
|
||||
isDefault?: boolean;
|
||||
}
|
|
@ -23,8 +23,9 @@ import {
|
|||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import styled from 'styled-components';
|
||||
import { css } from '@emotion/react';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
import { Conversation, Prompt, QuickPrompt, useLoadConnectors } from '../../..';
|
||||
import { Conversation, useLoadConnectors } from '../../..';
|
||||
import * as i18n from './translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { TEST_IDS } from '../constants';
|
||||
|
@ -46,6 +47,7 @@ import {
|
|||
QUICK_PROMPTS_TAB,
|
||||
SYSTEM_PROMPTS_TAB,
|
||||
} from './const';
|
||||
import { useFetchPrompts } from '../api/prompts/use_fetch_prompts';
|
||||
|
||||
const StyledEuiModal = styled(EuiModal)`
|
||||
width: 800px;
|
||||
|
@ -97,6 +99,7 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
|
||||
const { data: anonymizationFields, refetch: refetchAnonymizationFieldsResults } =
|
||||
useFetchAnonymizationFields();
|
||||
const { data: allPrompts } = useFetchPrompts();
|
||||
|
||||
const { data: connectors } = useLoadConnectors({
|
||||
http,
|
||||
|
@ -112,7 +115,7 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
setUpdatedAssistantStreamingEnabled,
|
||||
setUpdatedKnowledgeBaseSettings,
|
||||
setUpdatedQuickPromptSettings,
|
||||
setUpdatedSystemPromptSettings,
|
||||
promptsBulkActions,
|
||||
saveSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
updatedAnonymizationData,
|
||||
|
@ -120,7 +123,9 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
anonymizationFieldsBulkActions,
|
||||
setAnonymizationFieldsBulkActions,
|
||||
setUpdatedAnonymizationData,
|
||||
} = useSettingsUpdater(conversations, conversationsLoaded, anonymizationFields);
|
||||
setPromptsBulkActions,
|
||||
setUpdatedSystemPromptSettings,
|
||||
} = useSettingsUpdater(conversations, allPrompts, conversationsLoaded, anonymizationFields);
|
||||
|
||||
// Local state for saving previously selected items so tab switching is friendlier
|
||||
// Conversation Selection State
|
||||
|
@ -137,21 +142,21 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
);
|
||||
|
||||
// Quick Prompt Selection State
|
||||
const [selectedQuickPrompt, setSelectedQuickPrompt] = useState<QuickPrompt | undefined>();
|
||||
const onHandleSelectedQuickPromptChange = useCallback((quickPrompt?: QuickPrompt) => {
|
||||
const [selectedQuickPrompt, setSelectedQuickPrompt] = useState<PromptResponse | undefined>();
|
||||
const onHandleSelectedQuickPromptChange = useCallback((quickPrompt?: PromptResponse) => {
|
||||
setSelectedQuickPrompt(quickPrompt);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (selectedQuickPrompt != null) {
|
||||
setSelectedQuickPrompt(
|
||||
quickPromptSettings.find((q) => q.title === selectedQuickPrompt.title)
|
||||
quickPromptSettings.find((q) => q.name === selectedQuickPrompt.name)
|
||||
);
|
||||
}
|
||||
}, [quickPromptSettings, selectedQuickPrompt]);
|
||||
|
||||
// System Prompt Selection State
|
||||
const [selectedSystemPrompt, setSelectedSystemPrompt] = useState<Prompt | undefined>();
|
||||
const onHandleSelectedSystemPromptChange = useCallback((systemPrompt?: Prompt) => {
|
||||
const [selectedSystemPrompt, setSelectedSystemPrompt] = useState<PromptResponse | undefined>();
|
||||
const onHandleSelectedSystemPromptChange = useCallback((systemPrompt?: PromptResponse) => {
|
||||
setSelectedSystemPrompt(systemPrompt);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
|
@ -342,6 +347,8 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
onSelectedQuickPromptChange={onHandleSelectedQuickPromptChange}
|
||||
selectedQuickPrompt={selectedQuickPrompt}
|
||||
setUpdatedQuickPromptSettings={setUpdatedQuickPromptSettings}
|
||||
setPromptsBulkActions={setPromptsBulkActions}
|
||||
promptsBulkActions={promptsBulkActions}
|
||||
/>
|
||||
)}
|
||||
{selectedSettingsTab === SYSTEM_PROMPTS_TAB && (
|
||||
|
@ -356,6 +363,8 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
conversationsSettingsBulkActions={conversationsSettingsBulkActions}
|
||||
setUpdatedSystemPromptSettings={setUpdatedSystemPromptSettings}
|
||||
setPromptsBulkActions={setPromptsBulkActions}
|
||||
promptsBulkActions={promptsBulkActions}
|
||||
/>
|
||||
)}
|
||||
{selectedSettingsTab === ANONYMIZATION_TAB && (
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
import { Conversation } from '../../..';
|
||||
import { AssistantSettings } from './assistant_settings';
|
||||
|
@ -26,6 +27,9 @@ interface Props {
|
|||
conversations: Record<string, Conversation>;
|
||||
conversationsLoaded: boolean;
|
||||
refetchConversationsState: () => Promise<void>;
|
||||
refetchPrompts?: (
|
||||
options?: RefetchOptions & RefetchQueryFilters<unknown>
|
||||
) => Promise<QueryObserverResult<unknown, unknown>>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,6 +47,7 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo(
|
|||
conversations,
|
||||
conversationsLoaded,
|
||||
refetchConversationsState,
|
||||
refetchPrompts,
|
||||
}) => {
|
||||
const { toasts, setSelectedSettingsTab } = useAssistantContext();
|
||||
|
||||
|
@ -59,6 +64,9 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo(
|
|||
async (success: boolean) => {
|
||||
cleanupAndCloseModal();
|
||||
await refetchConversationsState();
|
||||
if (refetchPrompts) {
|
||||
await refetchPrompts();
|
||||
}
|
||||
if (success) {
|
||||
toasts?.addSuccess({
|
||||
iconType: 'check',
|
||||
|
@ -66,7 +74,7 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo(
|
|||
});
|
||||
}
|
||||
},
|
||||
[cleanupAndCloseModal, refetchConversationsState, toasts]
|
||||
[cleanupAndCloseModal, refetchConversationsState, refetchPrompts, toasts]
|
||||
);
|
||||
|
||||
const handleShowConversationSettings = useCallback(() => {
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
QUICK_PROMPTS_TAB,
|
||||
SYSTEM_PROMPTS_TAB,
|
||||
} from './const';
|
||||
import { mockSystemPrompts } from '../../mock/system_prompt';
|
||||
|
||||
const mockConversations = {
|
||||
[alertConvo.title]: alertConvo,
|
||||
|
@ -33,6 +34,8 @@ const saveSettings = jest.fn();
|
|||
const mockValues = {
|
||||
conversationSettings: mockConversations,
|
||||
saveSettings,
|
||||
systemPromptSettings: mockSystemPrompts,
|
||||
quickPromptSettings: [],
|
||||
};
|
||||
|
||||
const setSelectedSettingsTab = jest.fn();
|
||||
|
|
|
@ -19,7 +19,8 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { Conversation, Prompt, QuickPrompt } from '../../..';
|
||||
import { PromptResponse, PromptTypeEnum } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../..';
|
||||
import * as i18n from './translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { useSettingsUpdater } from './use_settings_updater/use_settings_updater';
|
||||
|
@ -42,6 +43,7 @@ import {
|
|||
QUICK_PROMPTS_TAB,
|
||||
SYSTEM_PROMPTS_TAB,
|
||||
} from './const';
|
||||
import { useFetchPrompts } from '../api/prompts/use_fetch_prompts';
|
||||
|
||||
interface Props {
|
||||
conversations: Record<string, Conversation>;
|
||||
|
@ -73,6 +75,9 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
|
||||
const { data: anonymizationFields } = useFetchAnonymizationFields();
|
||||
|
||||
const { data: allPrompts } = useFetchPrompts();
|
||||
|
||||
// Connector details
|
||||
const { data: connectors } = useLoadConnectors({
|
||||
http,
|
||||
});
|
||||
|
@ -92,7 +97,7 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
setUpdatedAssistantStreamingEnabled,
|
||||
setUpdatedKnowledgeBaseSettings,
|
||||
setUpdatedQuickPromptSettings,
|
||||
setUpdatedSystemPromptSettings,
|
||||
setPromptsBulkActions,
|
||||
saveSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
updatedAnonymizationData,
|
||||
|
@ -100,13 +105,32 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
anonymizationFieldsBulkActions,
|
||||
setAnonymizationFieldsBulkActions,
|
||||
setUpdatedAnonymizationData,
|
||||
setUpdatedSystemPromptSettings,
|
||||
promptsBulkActions,
|
||||
resetSettings,
|
||||
} = useSettingsUpdater(
|
||||
conversations,
|
||||
allPrompts,
|
||||
conversationsLoaded,
|
||||
anonymizationFields ?? { page: 0, perPage: 0, total: 0, data: [] }
|
||||
);
|
||||
|
||||
const quickPrompts = useMemo(
|
||||
() =>
|
||||
quickPromptSettings.length === 0
|
||||
? allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.quick)
|
||||
: quickPromptSettings,
|
||||
[allPrompts.data, quickPromptSettings]
|
||||
);
|
||||
|
||||
const systemPrompts = useMemo(
|
||||
() =>
|
||||
systemPromptSettings.length === 0
|
||||
? allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system)
|
||||
: systemPromptSettings,
|
||||
[allPrompts.data, systemPromptSettings]
|
||||
);
|
||||
|
||||
// Local state for saving previously selected items so tab switching is friendlier
|
||||
// Conversation Selection State
|
||||
const [selectedConversation, setSelectedConversation] = useState<Conversation | undefined>(
|
||||
|
@ -136,21 +160,21 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
}, [selectedSettingsTab, setSelectedSettingsTab]);
|
||||
|
||||
// Quick Prompt Selection State
|
||||
const [selectedQuickPrompt, setSelectedQuickPrompt] = useState<QuickPrompt | undefined>();
|
||||
const onHandleSelectedQuickPromptChange = useCallback((quickPrompt?: QuickPrompt) => {
|
||||
const [selectedQuickPrompt, setSelectedQuickPrompt] = useState<PromptResponse | undefined>();
|
||||
const onHandleSelectedQuickPromptChange = useCallback((quickPrompt?: PromptResponse) => {
|
||||
setSelectedQuickPrompt(quickPrompt);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (selectedQuickPrompt != null) {
|
||||
setSelectedQuickPrompt(
|
||||
quickPromptSettings.find((q) => q.title === selectedQuickPrompt.title)
|
||||
quickPromptSettings.find((q) => q.name === selectedQuickPrompt.name)
|
||||
);
|
||||
}
|
||||
}, [quickPromptSettings, selectedQuickPrompt]);
|
||||
|
||||
// System Prompt Selection State
|
||||
const [selectedSystemPrompt, setSelectedSystemPrompt] = useState<Prompt | undefined>();
|
||||
const onHandleSelectedSystemPromptChange = useCallback((systemPrompt?: Prompt) => {
|
||||
const [selectedSystemPrompt, setSelectedSystemPrompt] = useState<PromptResponse | undefined>();
|
||||
const onHandleSelectedSystemPromptChange = useCallback((systemPrompt?: PromptResponse) => {
|
||||
setSelectedSystemPrompt(systemPrompt);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
|
@ -303,7 +327,9 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
setConversationSettings={setConversationSettings}
|
||||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
setUpdatedSystemPromptSettings={setUpdatedSystemPromptSettings}
|
||||
systemPromptSettings={systemPromptSettings}
|
||||
systemPromptSettings={systemPrompts}
|
||||
promptsBulkActions={promptsBulkActions}
|
||||
setPromptsBulkActions={setPromptsBulkActions}
|
||||
/>
|
||||
)}
|
||||
{selectedSettingsTab === QUICK_PROMPTS_TAB && (
|
||||
|
@ -311,10 +337,12 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
handleSave={handleSave}
|
||||
onCancelClick={onCancelClick}
|
||||
onSelectedQuickPromptChange={onHandleSelectedQuickPromptChange}
|
||||
quickPromptSettings={quickPromptSettings}
|
||||
quickPromptSettings={quickPrompts}
|
||||
resetSettings={resetSettings}
|
||||
selectedQuickPrompt={selectedQuickPrompt}
|
||||
setUpdatedQuickPromptSettings={setUpdatedQuickPromptSettings}
|
||||
promptsBulkActions={promptsBulkActions}
|
||||
setPromptsBulkActions={setPromptsBulkActions}
|
||||
/>
|
||||
)}
|
||||
{selectedSettingsTab === ANONYMIZATION_TAB && (
|
||||
|
|
|
@ -9,13 +9,9 @@ import { act, renderHook } from '@testing-library/react-hooks';
|
|||
import { DEFAULT_LATEST_ALERTS } from '../../../assistant_context/constants';
|
||||
import { alertConvo, welcomeConvo } from '../../../mock/conversation';
|
||||
import { useSettingsUpdater } from './use_settings_updater';
|
||||
import { Prompt } from '../../../..';
|
||||
import {
|
||||
defaultSystemPrompt,
|
||||
mockSuperheroSystemPrompt,
|
||||
mockSystemPrompt,
|
||||
} from '../../../mock/system_prompt';
|
||||
import { defaultQuickPrompt, mockSystemPrompt } from '../../../mock/system_prompt';
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
const mockConversations = {
|
||||
[alertConvo.title]: alertConvo,
|
||||
|
@ -27,8 +23,8 @@ const mockHttp = {
|
|||
fetch: jest.fn(),
|
||||
} as unknown as HttpSetup;
|
||||
|
||||
const mockSystemPrompts: Prompt[] = [mockSystemPrompt];
|
||||
const mockQuickPrompts: Prompt[] = [defaultSystemPrompt];
|
||||
const mockSystemPrompts: PromptResponse[] = [mockSystemPrompt];
|
||||
const mockQuickPrompts: PromptResponse[] = [defaultQuickPrompt];
|
||||
|
||||
const anonymizationFields = {
|
||||
total: 2,
|
||||
|
@ -40,8 +36,6 @@ const anonymizationFields = {
|
|||
],
|
||||
};
|
||||
|
||||
const setAllQuickPromptsMock = jest.fn();
|
||||
const setAllSystemPromptsMock = jest.fn();
|
||||
const setAssistantStreamingEnabled = jest.fn();
|
||||
const setKnowledgeBaseMock = jest.fn();
|
||||
const reportAssistantSettingToggled = jest.fn();
|
||||
|
@ -58,8 +52,6 @@ const mockValues = {
|
|||
latestAlerts: DEFAULT_LATEST_ALERTS,
|
||||
},
|
||||
baseConversations: {},
|
||||
setAllQuickPrompts: setAllQuickPromptsMock,
|
||||
setAllSystemPrompts: setAllSystemPromptsMock,
|
||||
setKnowledgeBase: setKnowledgeBaseMock,
|
||||
http: mockHttp,
|
||||
anonymizationFieldsBulkActions: {},
|
||||
|
@ -67,8 +59,18 @@ const mockValues = {
|
|||
|
||||
const updatedValues = {
|
||||
conversations: { ...mockConversations },
|
||||
allSystemPrompts: [mockSuperheroSystemPrompt],
|
||||
allQuickPrompts: [{ title: 'Prompt 2', prompt: 'Prompt 2', color: 'red' }],
|
||||
allSystemPrompts: [mockSystemPrompt],
|
||||
allQuickPrompts: [
|
||||
{
|
||||
consumer: 'securitySolutionUI',
|
||||
content:
|
||||
'You are a helpful, expert assistant who answers questions about Elastic Security. Do not answer questions unrelated to Elastic Security.\nIf you answer a question related to KQL or EQL, it should be immediately usable within an Elastic Security timeline; please always format the output correctly with back ticks. Any answer provided for Query DSL should also be usable in a security timeline. This means you should only ever include the "filter" portion of the query.\nUse the following context to answer questions:',
|
||||
id: 'default-system-prompt',
|
||||
name: 'Default system prompt',
|
||||
promptType: 'quick',
|
||||
color: 'red',
|
||||
},
|
||||
],
|
||||
updatedAnonymizationData: {
|
||||
total: 2,
|
||||
page: 1,
|
||||
|
@ -101,23 +103,31 @@ describe('useSettingsUpdater', () => {
|
|||
it('should set all state variables to their initial values when resetSettings is called', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useSettingsUpdater(mockConversations, conversationsLoaded, anonymizationFields)
|
||||
useSettingsUpdater(
|
||||
mockConversations,
|
||||
{
|
||||
data: [...mockSystemPrompts, ...mockQuickPrompts],
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
total: 10,
|
||||
},
|
||||
conversationsLoaded,
|
||||
anonymizationFields
|
||||
)
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
const {
|
||||
setConversationSettings,
|
||||
setConversationsSettingsBulkActions,
|
||||
setUpdatedQuickPromptSettings,
|
||||
setUpdatedSystemPromptSettings,
|
||||
setUpdatedKnowledgeBaseSettings,
|
||||
setUpdatedAssistantStreamingEnabled,
|
||||
resetSettings,
|
||||
setPromptsBulkActions,
|
||||
} = result.current;
|
||||
|
||||
setConversationSettings(updatedValues.conversations);
|
||||
setConversationsSettingsBulkActions({});
|
||||
setUpdatedQuickPromptSettings(updatedValues.allQuickPrompts);
|
||||
setUpdatedSystemPromptSettings(updatedValues.allSystemPrompts);
|
||||
setPromptsBulkActions({});
|
||||
setUpdatedAnonymizationData(updatedValues.updatedAnonymizationData);
|
||||
setUpdatedKnowledgeBaseSettings(updatedValues.knowledgeBase);
|
||||
setUpdatedAssistantStreamingEnabled(updatedValues.assistantStreamingEnabled);
|
||||
|
@ -149,23 +159,31 @@ describe('useSettingsUpdater', () => {
|
|||
it('should update all state variables to their updated values when saveSettings is called', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useSettingsUpdater(mockConversations, conversationsLoaded, anonymizationFields)
|
||||
useSettingsUpdater(
|
||||
mockConversations,
|
||||
{
|
||||
data: mockSystemPrompts,
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
total: 10,
|
||||
},
|
||||
conversationsLoaded,
|
||||
anonymizationFields
|
||||
)
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
const {
|
||||
setConversationSettings,
|
||||
setConversationsSettingsBulkActions,
|
||||
setUpdatedQuickPromptSettings,
|
||||
setUpdatedSystemPromptSettings,
|
||||
setAnonymizationFieldsBulkActions,
|
||||
setUpdatedKnowledgeBaseSettings,
|
||||
setPromptsBulkActions,
|
||||
} = result.current;
|
||||
|
||||
setConversationSettings(updatedValues.conversations);
|
||||
setConversationsSettingsBulkActions({ delete: { ids: ['1'] } });
|
||||
setAnonymizationFieldsBulkActions({ delete: { ids: ['1'] } });
|
||||
setUpdatedQuickPromptSettings(updatedValues.allQuickPrompts);
|
||||
setUpdatedSystemPromptSettings(updatedValues.allSystemPrompts);
|
||||
setPromptsBulkActions({});
|
||||
setUpdatedAnonymizationData(updatedValues.updatedAnonymizationData);
|
||||
setUpdatedKnowledgeBaseSettings(updatedValues.knowledgeBase);
|
||||
|
||||
|
@ -179,8 +197,6 @@ describe('useSettingsUpdater', () => {
|
|||
body: '{"delete":{"ids":["1"]}}',
|
||||
}
|
||||
);
|
||||
expect(setAllQuickPromptsMock).toHaveBeenCalledWith(updatedValues.allQuickPrompts);
|
||||
expect(setAllSystemPromptsMock).toHaveBeenCalledWith(updatedValues.allSystemPrompts);
|
||||
expect(setUpdatedAnonymizationData).toHaveBeenCalledWith(
|
||||
updatedValues.updatedAnonymizationData
|
||||
);
|
||||
|
@ -190,7 +206,17 @@ describe('useSettingsUpdater', () => {
|
|||
it('should track which toggles have been updated when saveSettings is called', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useSettingsUpdater(mockConversations, conversationsLoaded, anonymizationFields)
|
||||
useSettingsUpdater(
|
||||
mockConversations,
|
||||
{
|
||||
data: mockSystemPrompts,
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
total: 10,
|
||||
},
|
||||
conversationsLoaded,
|
||||
anonymizationFields
|
||||
)
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
const { setUpdatedKnowledgeBaseSettings } = result.current;
|
||||
|
@ -207,7 +233,17 @@ describe('useSettingsUpdater', () => {
|
|||
it('should track only toggles that updated', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useSettingsUpdater(mockConversations, conversationsLoaded, anonymizationFields)
|
||||
useSettingsUpdater(
|
||||
mockConversations,
|
||||
{
|
||||
data: mockSystemPrompts,
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
total: 10,
|
||||
},
|
||||
conversationsLoaded,
|
||||
anonymizationFields
|
||||
)
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
const { setUpdatedKnowledgeBaseSettings } = result.current;
|
||||
|
@ -225,7 +261,17 @@ describe('useSettingsUpdater', () => {
|
|||
it('if no toggles update, do not track anything', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useSettingsUpdater(mockConversations, conversationsLoaded, anonymizationFields)
|
||||
useSettingsUpdater(
|
||||
mockConversations,
|
||||
{
|
||||
data: mockSystemPrompts,
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
total: 10,
|
||||
},
|
||||
conversationsLoaded,
|
||||
anonymizationFields
|
||||
)
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
const { setUpdatedKnowledgeBaseSettings } = result.current;
|
||||
|
|
|
@ -8,7 +8,13 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { FindAnonymizationFieldsResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.gen';
|
||||
import { PerformBulkActionRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen';
|
||||
import { Conversation, Prompt, QuickPrompt } from '../../../..';
|
||||
import {
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
PromptResponse,
|
||||
PromptTypeEnum,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { FindPromptsResponse } from '@kbn/elastic-assistant-common/impl/schemas/prompts/find_prompts_route.gen';
|
||||
import { Conversation } from '../../../..';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
import type { KnowledgeBaseConfig } from '../../types';
|
||||
import {
|
||||
|
@ -16,6 +22,7 @@ import {
|
|||
bulkUpdateConversations,
|
||||
} from '../../api/conversations/bulk_update_actions_conversations';
|
||||
import { bulkUpdateAnonymizationFields } from '../../api/anonymization_fields/bulk_update_anonymization_fields';
|
||||
import { bulkUpdatePrompts } from '../../api/prompts/bulk_update_prompts';
|
||||
|
||||
interface UseSettingsUpdater {
|
||||
assistantStreamingEnabled: boolean;
|
||||
|
@ -23,9 +30,9 @@ interface UseSettingsUpdater {
|
|||
conversationsSettingsBulkActions: ConversationsBulkActions;
|
||||
updatedAnonymizationData: FindAnonymizationFieldsResponse;
|
||||
knowledgeBase: KnowledgeBaseConfig;
|
||||
quickPromptSettings: QuickPrompt[];
|
||||
quickPromptSettings: PromptResponse[];
|
||||
resetSettings: () => void;
|
||||
systemPromptSettings: Prompt[];
|
||||
systemPromptSettings: PromptResponse[];
|
||||
setUpdatedAnonymizationData: React.Dispatch<
|
||||
React.SetStateAction<FindAnonymizationFieldsResponse>
|
||||
>;
|
||||
|
@ -37,26 +44,25 @@ interface UseSettingsUpdater {
|
|||
setAnonymizationFieldsBulkActions: React.Dispatch<
|
||||
React.SetStateAction<PerformBulkActionRequestBody>
|
||||
>;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
setUpdatedKnowledgeBaseSettings: React.Dispatch<React.SetStateAction<KnowledgeBaseConfig>>;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<QuickPrompt[]>>;
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<Prompt[]>>;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
setUpdatedAssistantStreamingEnabled: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
saveSettings: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export const useSettingsUpdater = (
|
||||
conversations: Record<string, Conversation>,
|
||||
allPrompts: FindPromptsResponse,
|
||||
conversationsLoaded: boolean,
|
||||
anonymizationFields: FindAnonymizationFieldsResponse
|
||||
): UseSettingsUpdater => {
|
||||
// Initial state from assistant context
|
||||
const {
|
||||
allQuickPrompts,
|
||||
allSystemPrompts,
|
||||
assistantTelemetry,
|
||||
knowledgeBase,
|
||||
setAllQuickPrompts,
|
||||
setAllSystemPrompts,
|
||||
assistantStreamingEnabled,
|
||||
setAssistantStreamingEnabled,
|
||||
setKnowledgeBase,
|
||||
|
@ -73,14 +79,20 @@ export const useSettingsUpdater = (
|
|||
const [conversationsSettingsBulkActions, setConversationsSettingsBulkActions] =
|
||||
useState<ConversationsBulkActions>({});
|
||||
// Quick Prompts
|
||||
const [updatedQuickPromptSettings, setUpdatedQuickPromptSettings] =
|
||||
useState<QuickPrompt[]>(allQuickPrompts);
|
||||
const [quickPromptSettings, setUpdatedQuickPromptSettings] = useState<PromptResponse[]>(
|
||||
allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.quick)
|
||||
);
|
||||
// System Prompts
|
||||
const [updatedSystemPromptSettings, setUpdatedSystemPromptSettings] =
|
||||
useState<Prompt[]>(allSystemPrompts);
|
||||
const [systemPromptSettings, setUpdatedSystemPromptSettings] = useState<PromptResponse[]>(
|
||||
allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system)
|
||||
);
|
||||
// Anonymization
|
||||
const [anonymizationFieldsBulkActions, setAnonymizationFieldsBulkActions] =
|
||||
useState<PerformBulkActionRequestBody>({});
|
||||
// Prompts
|
||||
const [promptsBulkActions, setPromptsBulkActions] = useState<PromptsPerformBulkActionRequestBody>(
|
||||
{}
|
||||
);
|
||||
const [updatedAnonymizationData, setUpdatedAnonymizationData] =
|
||||
useState<FindAnonymizationFieldsResponse>(anonymizationFields);
|
||||
const [updatedAssistantStreamingEnabled, setUpdatedAssistantStreamingEnabled] =
|
||||
|
@ -95,31 +107,57 @@ export const useSettingsUpdater = (
|
|||
const resetSettings = useCallback((): void => {
|
||||
setConversationSettings(conversations);
|
||||
setConversationsSettingsBulkActions({});
|
||||
setUpdatedQuickPromptSettings(allQuickPrompts);
|
||||
setUpdatedQuickPromptSettings(
|
||||
allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.quick)
|
||||
);
|
||||
setUpdatedKnowledgeBaseSettings(knowledgeBase);
|
||||
setUpdatedAssistantStreamingEnabled(assistantStreamingEnabled);
|
||||
setUpdatedSystemPromptSettings(allSystemPrompts);
|
||||
setUpdatedSystemPromptSettings(
|
||||
allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system)
|
||||
);
|
||||
setUpdatedAnonymizationData(anonymizationFields);
|
||||
}, [
|
||||
allQuickPrompts,
|
||||
allSystemPrompts,
|
||||
anonymizationFields,
|
||||
assistantStreamingEnabled,
|
||||
conversations,
|
||||
knowledgeBase,
|
||||
]);
|
||||
}, [allPrompts, anonymizationFields, assistantStreamingEnabled, conversations, knowledgeBase]);
|
||||
|
||||
const hasBulkConversations =
|
||||
conversationsSettingsBulkActions.create ||
|
||||
conversationsSettingsBulkActions.update ||
|
||||
conversationsSettingsBulkActions.delete;
|
||||
|
||||
const hasBulkAnonymizationFields =
|
||||
anonymizationFieldsBulkActions.create ||
|
||||
anonymizationFieldsBulkActions.update ||
|
||||
anonymizationFieldsBulkActions.delete;
|
||||
|
||||
const hasBulkPrompts =
|
||||
promptsBulkActions.create || promptsBulkActions.update || promptsBulkActions.delete;
|
||||
/**
|
||||
* Save all pending settings
|
||||
*/
|
||||
const saveSettings = useCallback(async (): Promise<boolean> => {
|
||||
setAllQuickPrompts(updatedQuickPromptSettings);
|
||||
setAllSystemPrompts(updatedSystemPromptSettings);
|
||||
const bulkPromptsResult = hasBulkPrompts
|
||||
? await bulkUpdatePrompts(http, promptsBulkActions, toasts)
|
||||
: undefined;
|
||||
|
||||
// replace conversation references for created
|
||||
if (bulkPromptsResult) {
|
||||
bulkPromptsResult.attributes.results.created.forEach((p) => {
|
||||
if (conversationsSettingsBulkActions.create) {
|
||||
Object.values(conversationsSettingsBulkActions.create).forEach((c) => {
|
||||
if (c.apiConfig?.defaultSystemPromptId === p.name) {
|
||||
c.apiConfig.defaultSystemPromptId = p.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (conversationsSettingsBulkActions.update) {
|
||||
Object.values(conversationsSettingsBulkActions.update).forEach((c) => {
|
||||
if (c.apiConfig?.defaultSystemPromptId === p.name) {
|
||||
c.apiConfig.defaultSystemPromptId = p.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const hasBulkConversations =
|
||||
conversationsSettingsBulkActions.create ||
|
||||
conversationsSettingsBulkActions.update ||
|
||||
conversationsSettingsBulkActions.delete;
|
||||
const bulkResult = hasBulkConversations
|
||||
? await bulkUpdateConversations(http, conversationsSettingsBulkActions, toasts)
|
||||
: undefined;
|
||||
|
@ -145,21 +183,20 @@ export const useSettingsUpdater = (
|
|||
}
|
||||
setAssistantStreamingEnabled(updatedAssistantStreamingEnabled);
|
||||
setKnowledgeBase(updatedKnowledgeBaseSettings);
|
||||
const hasBulkAnonymizationFields =
|
||||
anonymizationFieldsBulkActions.create ||
|
||||
anonymizationFieldsBulkActions.update ||
|
||||
anonymizationFieldsBulkActions.delete;
|
||||
|
||||
const bulkAnonymizationFieldsResult = hasBulkAnonymizationFields
|
||||
? await bulkUpdateAnonymizationFields(http, anonymizationFieldsBulkActions, toasts)
|
||||
: undefined;
|
||||
return (bulkResult?.success ?? true) && (bulkAnonymizationFieldsResult?.success ?? true);
|
||||
|
||||
return (
|
||||
(bulkResult?.success ?? true) &&
|
||||
(bulkAnonymizationFieldsResult?.success ?? true) &&
|
||||
(bulkPromptsResult?.success ?? true)
|
||||
);
|
||||
}, [
|
||||
setAllQuickPrompts,
|
||||
updatedQuickPromptSettings,
|
||||
setAllSystemPrompts,
|
||||
updatedSystemPromptSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
hasBulkConversations,
|
||||
http,
|
||||
conversationsSettingsBulkActions,
|
||||
toasts,
|
||||
knowledgeBase.isEnabledKnowledgeBase,
|
||||
knowledgeBase.isEnabledRAGAlerts,
|
||||
|
@ -168,7 +205,10 @@ export const useSettingsUpdater = (
|
|||
updatedAssistantStreamingEnabled,
|
||||
setAssistantStreamingEnabled,
|
||||
setKnowledgeBase,
|
||||
hasBulkAnonymizationFields,
|
||||
anonymizationFieldsBulkActions,
|
||||
hasBulkPrompts,
|
||||
promptsBulkActions,
|
||||
assistantTelemetry,
|
||||
]);
|
||||
|
||||
|
@ -200,9 +240,9 @@ export const useSettingsUpdater = (
|
|||
conversationsSettingsBulkActions,
|
||||
knowledgeBase: updatedKnowledgeBaseSettings,
|
||||
assistantStreamingEnabled: updatedAssistantStreamingEnabled,
|
||||
quickPromptSettings: updatedQuickPromptSettings,
|
||||
quickPromptSettings,
|
||||
resetSettings,
|
||||
systemPromptSettings: updatedSystemPromptSettings,
|
||||
systemPromptSettings,
|
||||
saveSettings,
|
||||
updatedAnonymizationData,
|
||||
setUpdatedAnonymizationData,
|
||||
|
@ -214,5 +254,7 @@ export const useSettingsUpdater = (
|
|||
setUpdatedSystemPromptSettings,
|
||||
setConversationSettings,
|
||||
setConversationsSettingsBulkActions,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,19 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export type PromptType = 'system' | 'user';
|
||||
|
||||
export interface Prompt {
|
||||
id: string;
|
||||
content: string;
|
||||
name: string;
|
||||
promptType: PromptType;
|
||||
isDefault?: boolean; // TODO: Should be renamed to isImmutable as this flag is used to prevent users from deleting prompts
|
||||
isNewConversationDefault?: boolean;
|
||||
isFlyoutMode?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface KnowledgeBaseConfig {
|
||||
isEnabledRAGAlerts: boolean;
|
||||
isEnabledKnowledgeBase: boolean;
|
||||
|
|
|
@ -13,7 +13,8 @@ import {
|
|||
getDefaultSystemPrompt,
|
||||
} from './helpers';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
import { Conversation, Prompt } from '../../..';
|
||||
import { Conversation } from '../../..';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
const tilde = '`';
|
||||
const codeDelimiter = '```';
|
||||
|
@ -61,28 +62,28 @@ ${codeDelimiter}
|
|||
This query will filter the events based on the condition that the ${tilde}user.name${tilde} field should exactly match the value \"9dcc9960-78cf-4ef6-9a2e-dbd5816daa60\".`;
|
||||
|
||||
describe('useConversation helpers', () => {
|
||||
const allSystemPrompts: Prompt[] = [
|
||||
const allSystemPrompts: PromptResponse[] = [
|
||||
{
|
||||
id: '1',
|
||||
content: 'Prompt 1',
|
||||
name: 'Prompt 1',
|
||||
promptType: 'user',
|
||||
promptType: 'quick',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
content: 'Prompt 2',
|
||||
name: 'Prompt 2',
|
||||
promptType: 'user',
|
||||
promptType: 'quick',
|
||||
isNewConversationDefault: true,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
content: 'Prompt 3',
|
||||
name: 'Prompt 3',
|
||||
promptType: 'user',
|
||||
promptType: 'quick',
|
||||
},
|
||||
];
|
||||
const allSystemPromptsNoDefault: Prompt[] = allSystemPrompts.filter(
|
||||
const allSystemPromptsNoDefault: PromptResponse[] = allSystemPrompts.filter(
|
||||
({ isNewConversationDefault }) => isNewConversationDefault !== true
|
||||
);
|
||||
|
||||
|
@ -237,25 +238,25 @@ describe('useConversation helpers', () => {
|
|||
});
|
||||
|
||||
describe('getConversationApiConfig', () => {
|
||||
const allSystemPrompts: Prompt[] = [
|
||||
const allSystemPrompts: PromptResponse[] = [
|
||||
{
|
||||
id: '1',
|
||||
content: 'Prompt 1',
|
||||
name: 'Prompt 1',
|
||||
promptType: 'user',
|
||||
promptType: 'quick',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
content: 'Prompt 2',
|
||||
name: 'Prompt 2',
|
||||
promptType: 'user',
|
||||
promptType: 'quick',
|
||||
isNewConversationDefault: true,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
content: 'Prompt 3',
|
||||
name: 'Prompt 3',
|
||||
promptType: 'user',
|
||||
promptType: 'quick',
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -390,7 +391,7 @@ describe('getConversationApiConfig', () => {
|
|||
});
|
||||
|
||||
test('should return the first system prompt if both conversation system prompt and default new system prompt do not exist', () => {
|
||||
const allSystemPromptsNoDefault: Prompt[] = allSystemPrompts.filter(
|
||||
const allSystemPromptsNoDefault: PromptResponse[] = allSystemPrompts.filter(
|
||||
({ isNewConversationDefault }) => isNewConversationDefault !== true
|
||||
);
|
||||
|
||||
|
@ -418,7 +419,7 @@ describe('getConversationApiConfig', () => {
|
|||
});
|
||||
|
||||
test('should return the first system prompt if conversation system prompt does not exist within all system prompts', () => {
|
||||
const allSystemPromptsNoDefault: Prompt[] = allSystemPrompts.filter(
|
||||
const allSystemPromptsNoDefault: PromptResponse[] = allSystemPrompts.filter(
|
||||
({ isNewConversationDefault }) => isNewConversationDefault !== true
|
||||
);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Prompt } from '../types';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../assistant_context/types';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
import { getGenAiConfig } from '../../connectorland/helpers';
|
||||
|
@ -75,7 +75,7 @@ export const analyzeMarkdown = (markdown: string): CodeBlockDetails[] => {
|
|||
*
|
||||
* @param allSystemPrompts All available System Prompts
|
||||
*/
|
||||
export const getDefaultNewSystemPrompt = (allSystemPrompts: Prompt[]) =>
|
||||
export const getDefaultNewSystemPrompt = (allSystemPrompts: PromptResponse[]) =>
|
||||
allSystemPrompts.find((prompt) => prompt.isNewConversationDefault) ?? allSystemPrompts?.[0];
|
||||
|
||||
/**
|
||||
|
@ -88,15 +88,15 @@ export const getDefaultSystemPrompt = ({
|
|||
allSystemPrompts,
|
||||
conversation,
|
||||
}: {
|
||||
allSystemPrompts: Prompt[];
|
||||
allSystemPrompts: PromptResponse[];
|
||||
conversation: Conversation | undefined;
|
||||
}): Prompt | undefined => {
|
||||
}): PromptResponse | undefined => {
|
||||
const conversationSystemPrompt = allSystemPrompts.find(
|
||||
(prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId
|
||||
);
|
||||
const defaultNewSystemPrompt = getDefaultNewSystemPrompt(allSystemPrompts);
|
||||
|
||||
return conversationSystemPrompt ?? defaultNewSystemPrompt;
|
||||
return conversationSystemPrompt?.id ? conversationSystemPrompt : defaultNewSystemPrompt;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -109,9 +109,9 @@ export const getInitialDefaultSystemPrompt = ({
|
|||
allSystemPrompts,
|
||||
conversation,
|
||||
}: {
|
||||
allSystemPrompts: Prompt[];
|
||||
allSystemPrompts: PromptResponse[];
|
||||
conversation: Conversation | undefined;
|
||||
}): Prompt | undefined => {
|
||||
}): PromptResponse | undefined => {
|
||||
const conversationSystemPrompt = allSystemPrompts.find(
|
||||
(prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId
|
||||
);
|
||||
|
@ -133,7 +133,7 @@ export const getConversationApiConfig = ({
|
|||
connectors,
|
||||
defaultConnector,
|
||||
}: {
|
||||
allSystemPrompts: Prompt[];
|
||||
allSystemPrompts: PromptResponse[];
|
||||
conversation: Conversation;
|
||||
connectors?: AIConnector[];
|
||||
defaultConnector?: AIConnector;
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
updateConversation,
|
||||
} from '../api/conversations';
|
||||
import { WELCOME_CONVERSATION } from './sample_conversations';
|
||||
import { useFetchPrompts } from '../api/prompts/use_fetch_prompts';
|
||||
|
||||
export const DEFAULT_CONVERSATION_STATE: Conversation = {
|
||||
id: '',
|
||||
|
@ -63,7 +64,10 @@ interface UseConversation {
|
|||
}
|
||||
|
||||
export const useConversation = (): UseConversation => {
|
||||
const { allSystemPrompts, http, toasts } = useAssistantContext();
|
||||
const { http, toasts } = useAssistantContext();
|
||||
const {
|
||||
data: { data: allPrompts },
|
||||
} = useFetchPrompts();
|
||||
|
||||
const getConversation = useCallback(
|
||||
async (conversationId: string, silent?: boolean) => {
|
||||
|
@ -101,7 +105,7 @@ export const useConversation = (): UseConversation => {
|
|||
async (conversation: Conversation) => {
|
||||
if (conversation.apiConfig) {
|
||||
const defaultSystemPromptId = getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
allSystemPrompts: allPrompts,
|
||||
conversation,
|
||||
})?.id;
|
||||
|
||||
|
@ -115,7 +119,7 @@ export const useConversation = (): UseConversation => {
|
|||
});
|
||||
}
|
||||
},
|
||||
[allSystemPrompts, http, toasts]
|
||||
[allPrompts, http, toasts]
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,17 +25,13 @@ import type { Conversation } from './types';
|
|||
import { DEFAULT_ASSISTANT_TITLE } from '../assistant/translations';
|
||||
import { CodeBlockDetails } from '../assistant/use_conversation/helpers';
|
||||
import { PromptContextTemplate } from '../assistant/prompt_context/types';
|
||||
import { QuickPrompt } from '../assistant/quick_prompts/types';
|
||||
import { KnowledgeBaseConfig, Prompt, TraceOptions } from '../assistant/types';
|
||||
import { BASE_SYSTEM_PROMPTS } from '../content/prompts/system';
|
||||
import { KnowledgeBaseConfig, TraceOptions } from '../assistant/types';
|
||||
import {
|
||||
DEFAULT_ASSISTANT_NAMESPACE,
|
||||
DEFAULT_KNOWLEDGE_BASE_SETTINGS,
|
||||
KNOWLEDGE_BASE_LOCAL_STORAGE_KEY,
|
||||
LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY,
|
||||
QUICK_PROMPT_LOCAL_STORAGE_KEY,
|
||||
STREAMING_LOCAL_STORAGE_KEY,
|
||||
SYSTEM_PROMPT_LOCAL_STORAGE_KEY,
|
||||
TRACE_OPTIONS_SESSION_STORAGE_KEY,
|
||||
} from './constants';
|
||||
import { AssistantAvailability, AssistantTelemetry } from './types';
|
||||
|
@ -65,8 +61,6 @@ export interface AssistantProviderProps {
|
|||
) => CodeBlockDetails[][];
|
||||
basePath: string;
|
||||
basePromptContexts?: PromptContextTemplate[];
|
||||
baseQuickPrompts?: QuickPrompt[];
|
||||
baseSystemPrompts?: Prompt[];
|
||||
docLinks: Omit<DocLinksStart, 'links'>;
|
||||
children: React.ReactNode;
|
||||
getComments: (commentArgs: {
|
||||
|
@ -87,6 +81,7 @@ export interface AssistantProviderProps {
|
|||
navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise<void>;
|
||||
title?: string;
|
||||
toasts?: IToasts;
|
||||
currentAppId: string;
|
||||
}
|
||||
|
||||
export interface UserAvatar {
|
||||
|
@ -106,13 +101,8 @@ export interface UseAssistantContext {
|
|||
currentConversation: Conversation,
|
||||
showAnonymizedValues: boolean
|
||||
) => CodeBlockDetails[][];
|
||||
allQuickPrompts: QuickPrompt[];
|
||||
allSystemPrompts: Prompt[];
|
||||
docLinks: Omit<DocLinksStart, 'links'>;
|
||||
basePath: string;
|
||||
basePromptContexts: PromptContextTemplate[];
|
||||
baseQuickPrompts: QuickPrompt[];
|
||||
baseSystemPrompts: Prompt[];
|
||||
baseConversations: Record<string, Conversation>;
|
||||
getComments: (commentArgs: {
|
||||
abortStream: () => void;
|
||||
|
@ -134,8 +124,6 @@ export interface UseAssistantContext {
|
|||
nameSpace: string;
|
||||
registerPromptContext: RegisterPromptContext;
|
||||
selectedSettingsTab: SettingsTabs | null;
|
||||
setAllQuickPrompts: React.Dispatch<React.SetStateAction<QuickPrompt[] | undefined>>;
|
||||
setAllSystemPrompts: React.Dispatch<React.SetStateAction<Prompt[] | undefined>>;
|
||||
setAssistantStreamingEnabled: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
||||
setKnowledgeBase: React.Dispatch<React.SetStateAction<KnowledgeBaseConfig | undefined>>;
|
||||
setLastConversationId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
|
@ -150,7 +138,9 @@ export interface UseAssistantContext {
|
|||
title: string;
|
||||
toasts: IToasts | undefined;
|
||||
traceOptions: TraceOptions;
|
||||
basePromptContexts: PromptContextTemplate[];
|
||||
unRegisterPromptContext: UnRegisterPromptContext;
|
||||
currentAppId: string;
|
||||
}
|
||||
|
||||
const AssistantContext = React.createContext<UseAssistantContext | undefined>(undefined);
|
||||
|
@ -164,8 +154,6 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
docLinks,
|
||||
basePath,
|
||||
basePromptContexts = [],
|
||||
baseQuickPrompts = [],
|
||||
baseSystemPrompts = BASE_SYSTEM_PROMPTS,
|
||||
children,
|
||||
getComments,
|
||||
http,
|
||||
|
@ -174,6 +162,7 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
nameSpace = DEFAULT_ASSISTANT_NAMESPACE,
|
||||
title = DEFAULT_ASSISTANT_TITLE,
|
||||
toasts,
|
||||
currentAppId,
|
||||
}) => {
|
||||
/**
|
||||
* Session storage for traceOptions, including APM URL and LangSmith Project/API Key
|
||||
|
@ -189,22 +178,6 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
defaultTraceOptions
|
||||
);
|
||||
|
||||
/**
|
||||
* Local storage for all quick prompts, prefixed by assistant nameSpace
|
||||
*/
|
||||
const [localStorageQuickPrompts, setLocalStorageQuickPrompts] = useLocalStorage(
|
||||
`${nameSpace}.${QUICK_PROMPT_LOCAL_STORAGE_KEY}`,
|
||||
baseQuickPrompts
|
||||
);
|
||||
|
||||
/**
|
||||
* Local storage for all system prompts, prefixed by assistant nameSpace
|
||||
*/
|
||||
const [localStorageSystemPrompts, setLocalStorageSystemPrompts] = useLocalStorage(
|
||||
`${nameSpace}.${SYSTEM_PROMPT_LOCAL_STORAGE_KEY}`,
|
||||
baseSystemPrompts
|
||||
);
|
||||
|
||||
const [localStorageLastConversationId, setLocalStorageLastConversationId] =
|
||||
useLocalStorage<string>(`${nameSpace}.${LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY}`);
|
||||
|
||||
|
@ -290,12 +263,8 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
assistantFeatures: assistantFeatures ?? defaultAssistantFeatures,
|
||||
assistantTelemetry,
|
||||
augmentMessageCodeBlocks,
|
||||
allQuickPrompts: localStorageQuickPrompts ?? [],
|
||||
allSystemPrompts: localStorageSystemPrompts ?? [],
|
||||
basePath,
|
||||
basePromptContexts,
|
||||
baseQuickPrompts,
|
||||
baseSystemPrompts,
|
||||
docLinks,
|
||||
getComments,
|
||||
http,
|
||||
|
@ -308,8 +277,6 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
// can be undefined from localStorage, if not defined, default to true
|
||||
assistantStreamingEnabled: localStorageStreaming ?? true,
|
||||
setAssistantStreamingEnabled: setLocalStorageStreaming,
|
||||
setAllQuickPrompts: setLocalStorageQuickPrompts,
|
||||
setAllSystemPrompts: setLocalStorageSystemPrompts,
|
||||
setKnowledgeBase: setLocalStorageKnowledgeBase,
|
||||
setSelectedSettingsTab,
|
||||
setShowAssistantOverlay,
|
||||
|
@ -322,6 +289,7 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
getLastConversationId,
|
||||
setLastConversationId: setLocalStorageLastConversationId,
|
||||
baseConversations,
|
||||
currentAppId,
|
||||
}),
|
||||
[
|
||||
actionTypeRegistry,
|
||||
|
@ -330,12 +298,8 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
assistantFeatures,
|
||||
assistantTelemetry,
|
||||
augmentMessageCodeBlocks,
|
||||
localStorageQuickPrompts,
|
||||
localStorageSystemPrompts,
|
||||
basePath,
|
||||
basePromptContexts,
|
||||
baseQuickPrompts,
|
||||
baseSystemPrompts,
|
||||
docLinks,
|
||||
getComments,
|
||||
http,
|
||||
|
@ -347,8 +311,6 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
selectedSettingsTab,
|
||||
localStorageStreaming,
|
||||
setLocalStorageStreaming,
|
||||
setLocalStorageQuickPrompts,
|
||||
setLocalStorageSystemPrompts,
|
||||
setLocalStorageKnowledgeBase,
|
||||
setSessionStorageTraceOptions,
|
||||
showAssistantOverlay,
|
||||
|
@ -359,6 +321,7 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
getLastConversationId,
|
||||
setLocalStorageLastConversationId,
|
||||
baseConversations,
|
||||
currentAppId,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* 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 { Prompt } from '../../../..';
|
||||
import {
|
||||
DEFAULT_SYSTEM_PROMPT_LABEL,
|
||||
DEFAULT_SYSTEM_PROMPT_NAME,
|
||||
DEFAULT_SYSTEM_PROMPT_NON_I18N,
|
||||
SUPERHERO_SYSTEM_PROMPT_LABEL,
|
||||
SUPERHERO_SYSTEM_PROMPT_NAME,
|
||||
SUPERHERO_SYSTEM_PROMPT_NON_I18N,
|
||||
} from './translations';
|
||||
|
||||
/**
|
||||
* Base System Prompts for Elastic AI Assistant (if not overridden on initialization).
|
||||
*/
|
||||
export const BASE_SYSTEM_PROMPTS: Prompt[] = [
|
||||
{
|
||||
id: 'default-system-prompt',
|
||||
content: DEFAULT_SYSTEM_PROMPT_NON_I18N,
|
||||
name: DEFAULT_SYSTEM_PROMPT_NAME,
|
||||
promptType: 'system',
|
||||
label: DEFAULT_SYSTEM_PROMPT_LABEL,
|
||||
},
|
||||
{
|
||||
id: 'CB9FA555-B59F-4F71-AFF9-8A891AC5BC28',
|
||||
content: SUPERHERO_SYSTEM_PROMPT_NON_I18N,
|
||||
name: SUPERHERO_SYSTEM_PROMPT_NAME,
|
||||
promptType: 'system',
|
||||
label: SUPERHERO_SYSTEM_PROMPT_LABEL,
|
||||
},
|
||||
];
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const THEN_SUMMARIZE_SUGGESTED_KQL_AND_EQL_QUERIES = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries',
|
||||
{
|
||||
defaultMessage:
|
||||
'Evaluate the event from the context above and format your output neatly in markdown syntax for my Elastic Security case.',
|
||||
}
|
||||
);
|
||||
|
||||
export const FINALLY_SUGGEST_INVESTIGATION_GUIDE_AND_FORMAT_AS_MARKDOWN = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown',
|
||||
{
|
||||
defaultMessage: `Add your description, recommended actions and bulleted triage steps. Use the MITRE ATT&CK data provided to add more context and recommendations from MITRE, and hyperlink to the relevant pages on MITRE\'s website. Be sure to include the user and host risk score data from the context. Your response should include steps that point to Elastic Security specific features, including endpoint response actions, the Elastic Agent OSQuery manager integration (with example osquery queries), timelines and entity analytics and link to all the relevant Elastic Security documentation.`,
|
||||
}
|
||||
);
|
||||
|
||||
export const EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N = `${THEN_SUMMARIZE_SUGGESTED_KQL_AND_EQL_QUERIES}
|
||||
${FINALLY_SUGGEST_INVESTIGATION_GUIDE_AND_FORMAT_AS_MARKDOWN}`;
|
|
@ -5,52 +5,69 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { QuickPrompt } from '../..';
|
||||
import {
|
||||
PromptResponse,
|
||||
PromptTypeEnum,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
|
||||
export const MOCK_QUICK_PROMPTS: QuickPrompt[] = [
|
||||
export const MOCK_QUICK_PROMPTS: PromptResponse[] = [
|
||||
{
|
||||
title: 'ALERT_SUMMARIZATION_TITLE',
|
||||
prompt: 'ALERT_SUMMARIZATION_PROMPT',
|
||||
name: 'ALERT_SUMMARIZATION_TITLE',
|
||||
content: 'ALERT_SUMMARIZATION_PROMPT',
|
||||
color: '#F68FBE',
|
||||
categories: ['PROMPT_CONTEXT_ALERT_CATEGORY'],
|
||||
isDefault: true,
|
||||
id: 'ALERT_SUMMARIZATION_TITLE',
|
||||
promptType: PromptTypeEnum.quick,
|
||||
},
|
||||
{
|
||||
title: 'RULE_CREATION_TITLE',
|
||||
prompt: 'RULE_CREATION_PROMPT',
|
||||
name: 'RULE_CREATION_TITLE',
|
||||
content: 'RULE_CREATION_PROMPT',
|
||||
categories: ['PROMPT_CONTEXT_DETECTION_RULES_CATEGORY'],
|
||||
color: '#7DDED8',
|
||||
isDefault: true,
|
||||
id: 'RULE_CREATION_TITLE',
|
||||
promptType: PromptTypeEnum.quick,
|
||||
},
|
||||
{
|
||||
title: 'WORKFLOW_ANALYSIS_TITLE',
|
||||
prompt: 'WORKFLOW_ANALYSIS_PROMPT',
|
||||
name: 'WORKFLOW_ANALYSIS_TITLE',
|
||||
content: 'WORKFLOW_ANALYSIS_PROMPT',
|
||||
color: '#36A2EF',
|
||||
isDefault: true,
|
||||
id: 'WORKFLOW_ANALYSIS_TITLE',
|
||||
promptType: PromptTypeEnum.quick,
|
||||
},
|
||||
{
|
||||
title: 'THREAT_INVESTIGATION_GUIDES_TITLE',
|
||||
prompt: 'THREAT_INVESTIGATION_GUIDES_PROMPT',
|
||||
name: 'THREAT_INVESTIGATION_GUIDES_TITLE',
|
||||
content: 'THREAT_INVESTIGATION_GUIDES_PROMPT',
|
||||
categories: ['PROMPT_CONTEXT_EVENT_CATEGORY'],
|
||||
color: '#F3D371',
|
||||
isDefault: true,
|
||||
id: 'THREAT_INVESTIGATION_GUIDES_TITLE',
|
||||
promptType: PromptTypeEnum.quick,
|
||||
},
|
||||
{
|
||||
title: 'SPL_QUERY_CONVERSION_TITLE',
|
||||
prompt: 'SPL_QUERY_CONVERSION_PROMPT',
|
||||
name: 'SPL_QUERY_CONVERSION_TITLE',
|
||||
content: 'SPL_QUERY_CONVERSION_PROMPT',
|
||||
color: '#BADA55',
|
||||
isDefault: true,
|
||||
id: 'SPL_QUERY_CONVERSION_TITLE',
|
||||
promptType: PromptTypeEnum.quick,
|
||||
},
|
||||
{
|
||||
title: 'AUTOMATION_TITLE',
|
||||
prompt: 'AUTOMATION_PROMPT',
|
||||
name: 'AUTOMATION_TITLE',
|
||||
content: 'AUTOMATION_PROMPT',
|
||||
color: '#FFA500',
|
||||
isDefault: true,
|
||||
id: 'AUTOMATION_TITLE',
|
||||
promptType: PromptTypeEnum.quick,
|
||||
},
|
||||
{
|
||||
title: 'A_CUSTOM_OPTION',
|
||||
prompt: 'quickly prompt please',
|
||||
name: 'A_CUSTOM_OPTION',
|
||||
content: 'quickly prompt please',
|
||||
color: '#D36086',
|
||||
categories: [],
|
||||
id: 'A_CUSTOM_OPTION',
|
||||
promptType: PromptTypeEnum.quick,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -5,35 +5,47 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Prompt } from '../../assistant/types';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
export const mockSystemPrompt: Prompt = {
|
||||
export const mockSystemPrompt: PromptResponse = {
|
||||
id: 'mock-system-prompt-1',
|
||||
content: 'You are a helpful, expert assistant who answers questions about Elastic Security.',
|
||||
name: 'Mock system prompt',
|
||||
consumer: 'securitySolutionUI',
|
||||
promptType: 'system',
|
||||
isFlyoutMode: false,
|
||||
};
|
||||
|
||||
export const mockSuperheroSystemPrompt: Prompt = {
|
||||
export const mockSuperheroSystemPrompt: PromptResponse = {
|
||||
id: 'mock-superhero-system-prompt-1',
|
||||
content: `You are a helpful, expert assistant who answers questions about Elastic Security.
|
||||
You have the personality of a mutant superhero who says "bub" a lot.`,
|
||||
name: 'Mock superhero system prompt',
|
||||
consumer: 'securitySolutionUI',
|
||||
promptType: 'system',
|
||||
};
|
||||
|
||||
export const defaultSystemPrompt: Prompt = {
|
||||
export const defaultSystemPrompt: PromptResponse = {
|
||||
id: 'default-system-prompt',
|
||||
content:
|
||||
'You are a helpful, expert assistant who answers questions about Elastic Security. Do not answer questions unrelated to Elastic Security.\nIf you answer a question related to KQL or EQL, it should be immediately usable within an Elastic Security timeline; please always format the output correctly with back ticks. Any answer provided for Query DSL should also be usable in a security timeline. This means you should only ever include the "filter" portion of the query.\nUse the following context to answer questions:',
|
||||
name: 'Default system prompt',
|
||||
promptType: 'system',
|
||||
consumer: 'securitySolutionUI',
|
||||
isDefault: true,
|
||||
isNewConversationDefault: true,
|
||||
};
|
||||
|
||||
export const mockSystemPrompts: Prompt[] = [
|
||||
export const defaultQuickPrompt: PromptResponse = {
|
||||
id: 'default-system-prompt',
|
||||
content:
|
||||
'You are a helpful, expert assistant who answers questions about Elastic Security. Do not answer questions unrelated to Elastic Security.\nIf you answer a question related to KQL or EQL, it should be immediately usable within an Elastic Security timeline; please always format the output correctly with back ticks. Any answer provided for Query DSL should also be usable in a security timeline. This means you should only ever include the "filter" portion of the query.\nUse the following context to answer questions:',
|
||||
name: 'Default system prompt',
|
||||
promptType: 'quick',
|
||||
consumer: 'securitySolutionUI',
|
||||
color: 'red',
|
||||
};
|
||||
|
||||
export const mockSystemPrompts: PromptResponse[] = [
|
||||
mockSystemPrompt,
|
||||
mockSuperheroSystemPrompt,
|
||||
defaultSystemPrompt,
|
||||
|
|
|
@ -81,6 +81,7 @@ export const TestProvidersComponent: React.FC<Props> = ({
|
|||
baseConversations={{}}
|
||||
navigateToApp={mockNavigateToApp}
|
||||
{...providerContext}
|
||||
currentAppId={'test'}
|
||||
>
|
||||
{children}
|
||||
</AssistantProvider>
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Prompt } from '../../assistant/types';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
export const mockUserPrompt: Prompt = {
|
||||
export const mockUserPrompt: PromptResponse = {
|
||||
id: 'mock-user-prompt-1',
|
||||
content: `Explain the meaning from the context above, then summarize a list of suggested Elasticsearch KQL and EQL queries.
|
||||
Finally, suggest an investigation guide, and format it as markdown.`,
|
||||
name: 'Mock user prompt',
|
||||
promptType: 'user',
|
||||
promptType: 'quick',
|
||||
};
|
||||
|
|
|
@ -90,12 +90,6 @@ export {
|
|||
WELCOME_CONVERSATION_TITLE,
|
||||
} from './impl/assistant/use_conversation/translations';
|
||||
|
||||
/** i18n translations of system prompts */
|
||||
export * as SYSTEM_PROMPTS from './impl/content/prompts/system/translations';
|
||||
|
||||
/** i18n translations of user prompts */
|
||||
export * as USER_PROMPTS from './impl/content/prompts/user/translations';
|
||||
|
||||
export type {
|
||||
/** for rendering results in a code block */
|
||||
CodeBlockDetails,
|
||||
|
@ -114,9 +108,6 @@ export type {
|
|||
ClientMessage,
|
||||
} from './impl/assistant_context/types';
|
||||
|
||||
/** Interface for defining system/user prompts */
|
||||
export type { Prompt } from './impl/assistant/types';
|
||||
|
||||
/**
|
||||
* This interface is used to pass context to the assistant,
|
||||
* for the purpose of building prompts. Examples of context include:
|
||||
|
@ -139,12 +130,6 @@ export type { PromptContext } from './impl/assistant/prompt_context/types';
|
|||
*/
|
||||
export type { PromptContextTemplate } from './impl/assistant/prompt_context/types';
|
||||
|
||||
/**
|
||||
* This interface is used to pass a default or base set of Quick Prompts to the Elastic Assistant that
|
||||
* can be displayed when corresponding PromptContext's are registered.
|
||||
*/
|
||||
export type { QuickPrompt } from './impl/assistant/quick_prompts/types';
|
||||
|
||||
export { useFetchCurrentUserConversations } from './impl/assistant/api/conversations/use_fetch_current_user_conversations';
|
||||
export * from './impl/assistant/api/conversations/bulk_update_actions_conversations';
|
||||
export { getConversationById } from './impl/assistant/api/conversations/conversations';
|
||||
|
@ -152,4 +137,4 @@ export { getConversationById } from './impl/assistant/api/conversations/conversa
|
|||
export { mergeBaseWithPersistedConversations } from './impl/assistant/helpers';
|
||||
|
||||
export { UpgradeButtons } from './impl/upgrade/upgrade_buttons';
|
||||
export { getUserConversations } from './impl/assistant/api';
|
||||
export { getUserConversations, getPrompts, bulkUpdatePrompts } from './impl/assistant/api';
|
||||
|
|
|
@ -73,6 +73,7 @@ export const TestProvidersComponent: React.FC<Props> = ({ children, isILMAvailab
|
|||
http={mockHttp}
|
||||
baseConversations={{}}
|
||||
navigateToApp={mockNavigateToApp}
|
||||
currentAppId={'securitySolutionUI'}
|
||||
>
|
||||
<DataQualityProvider
|
||||
httpFetch={http.fetch}
|
||||
|
|
|
@ -42,8 +42,10 @@ export const getPromptsSearchEsMock = () => {
|
|||
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
|
||||
content: 'test content',
|
||||
name: 'test',
|
||||
prompt_type: 'quickPrompt',
|
||||
is_shared: false,
|
||||
prompt_type: 'quick',
|
||||
consumer: 'securitySolutionUI',
|
||||
categories: [],
|
||||
color: 'red',
|
||||
created_by: 'elastic',
|
||||
users: [
|
||||
{
|
||||
|
@ -62,15 +64,19 @@ export const getCreatePromptSchemaMock = (): PromptCreateProps => ({
|
|||
name: 'test',
|
||||
content: 'test content',
|
||||
isNewConversationDefault: false,
|
||||
isShared: true,
|
||||
consumer: 'securitySolutionUI',
|
||||
categories: [],
|
||||
color: 'red',
|
||||
isDefault: false,
|
||||
promptType: 'quickPrompt',
|
||||
promptType: 'quick',
|
||||
});
|
||||
|
||||
export const getUpdatePromptSchemaMock = (promptId = 'prompt-1'): PromptUpdateProps => ({
|
||||
content: 'test content',
|
||||
isNewConversationDefault: false,
|
||||
isShared: true,
|
||||
consumer: 'securitySolutionUI',
|
||||
categories: [],
|
||||
color: 'red',
|
||||
isDefault: false,
|
||||
id: promptId,
|
||||
});
|
||||
|
@ -79,7 +85,7 @@ export const getPromptMock = (params: PromptCreateProps | PromptUpdateProps): Pr
|
|||
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
|
||||
content: 'test content',
|
||||
name: 'test',
|
||||
promptType: 'quickPrompt',
|
||||
promptType: 'quick',
|
||||
isDefault: false,
|
||||
...params,
|
||||
createdAt: '2019-12-13T16:40:33.400Z',
|
||||
|
@ -97,19 +103,23 @@ export const getQueryPromptParams = (isUpdate?: boolean): PromptCreateProps | Pr
|
|||
? {
|
||||
content: 'test 2',
|
||||
name: 'test',
|
||||
promptType: 'quickPrompt',
|
||||
promptType: 'quick',
|
||||
isDefault: false,
|
||||
isNewConversationDefault: true,
|
||||
isShared: true,
|
||||
consumer: 'securitySolutionUI',
|
||||
categories: [],
|
||||
color: 'red',
|
||||
id: '1',
|
||||
}
|
||||
: {
|
||||
content: 'test 2',
|
||||
name: 'test',
|
||||
promptType: 'quickPrompt',
|
||||
promptType: 'quick',
|
||||
isDefault: false,
|
||||
isNewConversationDefault: true,
|
||||
isShared: true,
|
||||
consumer: 'securitySolutionUI',
|
||||
categories: [],
|
||||
color: 'red',
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -23,11 +23,21 @@ export const assistantPromptsFieldMap: FieldMap = {
|
|||
array: false,
|
||||
required: false,
|
||||
},
|
||||
is_shared: {
|
||||
type: 'boolean',
|
||||
consumer: {
|
||||
type: 'text',
|
||||
array: false,
|
||||
required: false,
|
||||
},
|
||||
color: {
|
||||
type: 'keyword',
|
||||
array: false,
|
||||
required: false,
|
||||
},
|
||||
categories: {
|
||||
type: 'keyword',
|
||||
array: true,
|
||||
required: false,
|
||||
},
|
||||
is_new_conversation_default: {
|
||||
type: 'boolean',
|
||||
array: false,
|
||||
|
|
|
@ -9,6 +9,7 @@ import { estypes } from '@elastic/elasticsearch';
|
|||
import {
|
||||
PromptCreateProps,
|
||||
PromptResponse,
|
||||
PromptType,
|
||||
PromptUpdateProps,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { AuthenticatedUser } from '@kbn/core-security-common';
|
||||
|
@ -31,8 +32,10 @@ export const transformESToPrompts = (response: EsPromptsSchema[]): PromptRespons
|
|||
namespace: promptSchema.namespace,
|
||||
id: promptSchema.id,
|
||||
name: promptSchema.name,
|
||||
promptType: promptSchema.prompt_type,
|
||||
isShared: promptSchema.is_shared,
|
||||
promptType: promptSchema.prompt_type as unknown as PromptType,
|
||||
color: promptSchema.color,
|
||||
categories: promptSchema.categories,
|
||||
consumer: promptSchema.consumer,
|
||||
createdBy: promptSchema.created_by,
|
||||
updatedBy: promptSchema.updated_by,
|
||||
};
|
||||
|
@ -65,8 +68,10 @@ export const transformESSearchToPrompts = (
|
|||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
id: hit._id!,
|
||||
name: promptSchema.name,
|
||||
promptType: promptSchema.prompt_type,
|
||||
isShared: promptSchema.is_shared,
|
||||
promptType: promptSchema.prompt_type as unknown as PromptType,
|
||||
color: promptSchema.color,
|
||||
categories: promptSchema.categories,
|
||||
consumer: promptSchema.consumer,
|
||||
createdBy: promptSchema.created_by,
|
||||
updatedBy: promptSchema.updated_by,
|
||||
};
|
||||
|
@ -78,14 +83,15 @@ export const transformESSearchToPrompts = (
|
|||
export const transformToUpdateScheme = (
|
||||
user: AuthenticatedUser,
|
||||
updatedAt: string,
|
||||
{ content, isNewConversationDefault, isShared, id }: PromptUpdateProps
|
||||
{ content, isNewConversationDefault, categories, color, id }: PromptUpdateProps
|
||||
): UpdatePromptSchema => {
|
||||
return {
|
||||
id,
|
||||
updated_at: updatedAt,
|
||||
content: content ?? '',
|
||||
is_new_conversation_default: isNewConversationDefault,
|
||||
is_shared: isShared,
|
||||
categories,
|
||||
color,
|
||||
users: [
|
||||
{
|
||||
id: user.profile_uid,
|
||||
|
@ -98,13 +104,25 @@ export const transformToUpdateScheme = (
|
|||
export const transformToCreateScheme = (
|
||||
user: AuthenticatedUser,
|
||||
updatedAt: string,
|
||||
{ content, isDefault, isNewConversationDefault, isShared, name, promptType }: PromptCreateProps
|
||||
{
|
||||
content,
|
||||
isDefault,
|
||||
isNewConversationDefault,
|
||||
categories,
|
||||
color,
|
||||
consumer,
|
||||
name,
|
||||
promptType,
|
||||
}: PromptCreateProps
|
||||
): CreatePromptSchema => {
|
||||
return {
|
||||
'@timestamp': updatedAt,
|
||||
updated_at: updatedAt,
|
||||
content: content ?? '',
|
||||
is_new_conversation_default: isNewConversationDefault,
|
||||
is_shared: isShared,
|
||||
color,
|
||||
consumer,
|
||||
categories,
|
||||
name,
|
||||
is_default: isDefault,
|
||||
prompt_type: promptType,
|
||||
|
@ -132,8 +150,11 @@ export const getUpdateScript = ({
|
|||
if (params.assignEmpty == true || params.containsKey('is_new_conversation_default')) {
|
||||
ctx._source.is_new_conversation_default = params.is_new_conversation_default;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('is_shared')) {
|
||||
ctx._source.is_shared = params.is_shared;
|
||||
if (params.assignEmpty == true || params.containsKey('color')) {
|
||||
ctx._source.color = params.color;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('categories')) {
|
||||
ctx._source.categories = params.categories;
|
||||
}
|
||||
ctx._source.updated_at = params.updated_at;
|
||||
`,
|
||||
|
|
|
@ -12,7 +12,9 @@ export interface EsPromptsSchema {
|
|||
created_by: string;
|
||||
content: string;
|
||||
is_default?: boolean;
|
||||
is_shared?: boolean;
|
||||
consumer?: string;
|
||||
color?: string;
|
||||
categories?: string[];
|
||||
is_new_conversation_default?: boolean;
|
||||
name: string;
|
||||
prompt_type: string;
|
||||
|
@ -28,7 +30,8 @@ export interface EsPromptsSchema {
|
|||
export interface UpdatePromptSchema {
|
||||
id: string;
|
||||
'@timestamp'?: string;
|
||||
is_shared?: boolean;
|
||||
color?: string;
|
||||
categories?: string[];
|
||||
is_new_conversation_default?: boolean;
|
||||
content?: string;
|
||||
updated_at?: string;
|
||||
|
@ -42,7 +45,9 @@ export interface UpdatePromptSchema {
|
|||
|
||||
export interface CreatePromptSchema {
|
||||
'@timestamp'?: string;
|
||||
is_shared?: boolean;
|
||||
consumer?: string;
|
||||
color?: string;
|
||||
categories?: string[];
|
||||
is_new_conversation_default?: boolean;
|
||||
is_default?: boolean;
|
||||
name: string;
|
||||
|
|
|
@ -59,7 +59,7 @@ export const findPromptsRoute = (router: ElasticAssistantPluginRouter, logger: L
|
|||
page: query.page,
|
||||
sortField: query.sort_field,
|
||||
sortOrder: query.sort_order,
|
||||
filter: query.filter,
|
||||
filter: query.filter ? decodeURIComponent(query.filter) : undefined,
|
||||
fields: query.fields,
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Prompt } from '@kbn/elastic-assistant';
|
||||
import {
|
||||
PromptTypeEnum,
|
||||
type PromptResponse,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { APP_UI_ID } from '../../../../../common';
|
||||
import {
|
||||
DEFAULT_SYSTEM_PROMPT_NAME,
|
||||
DEFAULT_SYSTEM_PROMPT_NON_I18N,
|
||||
|
@ -16,20 +20,22 @@ import {
|
|||
/**
|
||||
* Base System Prompts for Security Solution.
|
||||
*/
|
||||
export const BASE_SECURITY_SYSTEM_PROMPTS: Prompt[] = [
|
||||
export const BASE_SECURITY_SYSTEM_PROMPTS: PromptResponse[] = [
|
||||
{
|
||||
id: 'default-system-prompt',
|
||||
content: DEFAULT_SYSTEM_PROMPT_NON_I18N,
|
||||
name: DEFAULT_SYSTEM_PROMPT_NAME,
|
||||
promptType: 'system',
|
||||
promptType: PromptTypeEnum.system,
|
||||
isDefault: true,
|
||||
isNewConversationDefault: true,
|
||||
consumer: APP_UI_ID,
|
||||
},
|
||||
{
|
||||
id: 'CB9FA555-B59F-4F71-AFF9-8A891AC5BC28',
|
||||
content: SUPERHERO_SYSTEM_PROMPT_NON_I18N,
|
||||
name: SUPERHERO_SYSTEM_PROMPT_NAME,
|
||||
promptType: 'system',
|
||||
promptType: PromptTypeEnum.system,
|
||||
consumer: APP_UI_ID,
|
||||
isDefault: true,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { QuickPrompt } from '@kbn/elastic-assistant';
|
||||
import {
|
||||
PromptTypeEnum,
|
||||
type PromptResponse,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { APP_UI_ID } from '../../../../common';
|
||||
import * as i18n from './translations';
|
||||
import {
|
||||
KNOWLEDGE_BASE_CATEGORY,
|
||||
|
@ -19,51 +23,72 @@ import {
|
|||
* Useful if wanting to see all available QuickPrompts in one place, or if needing
|
||||
* to reference when constructing a new chat window to include a QuickPrompt.
|
||||
*/
|
||||
export const BASE_SECURITY_QUICK_PROMPTS: QuickPrompt[] = [
|
||||
export const BASE_SECURITY_QUICK_PROMPTS: PromptResponse[] = [
|
||||
{
|
||||
title: i18n.ALERT_SUMMARIZATION_TITLE,
|
||||
prompt: i18n.ALERT_SUMMARIZATION_PROMPT,
|
||||
name: i18n.ALERT_SUMMARIZATION_TITLE,
|
||||
content: i18n.ALERT_SUMMARIZATION_PROMPT,
|
||||
color: '#F68FBE',
|
||||
categories: [PROMPT_CONTEXT_ALERT_CATEGORY],
|
||||
isDefault: true,
|
||||
id: i18n.ALERT_SUMMARIZATION_TITLE,
|
||||
promptType: PromptTypeEnum.quick,
|
||||
consumer: APP_UI_ID,
|
||||
},
|
||||
{
|
||||
title: i18n.ESQL_QUERY_GENERATION_TITLE,
|
||||
prompt: i18n.ESQL_QUERY_GENERATION_PROMPT,
|
||||
name: i18n.ESQL_QUERY_GENERATION_TITLE,
|
||||
content: i18n.ESQL_QUERY_GENERATION_PROMPT,
|
||||
color: '#9170B8',
|
||||
categories: [KNOWLEDGE_BASE_CATEGORY],
|
||||
isDefault: true,
|
||||
id: i18n.ESQL_QUERY_GENERATION_TITLE,
|
||||
promptType: PromptTypeEnum.quick,
|
||||
consumer: APP_UI_ID,
|
||||
},
|
||||
{
|
||||
title: i18n.RULE_CREATION_TITLE,
|
||||
prompt: i18n.RULE_CREATION_PROMPT,
|
||||
name: i18n.RULE_CREATION_TITLE,
|
||||
content: i18n.RULE_CREATION_PROMPT,
|
||||
categories: [PROMPT_CONTEXT_DETECTION_RULES_CATEGORY],
|
||||
color: '#7DDED8',
|
||||
isDefault: true,
|
||||
id: i18n.RULE_CREATION_TITLE,
|
||||
promptType: PromptTypeEnum.quick,
|
||||
consumer: APP_UI_ID,
|
||||
},
|
||||
{
|
||||
title: i18n.WORKFLOW_ANALYSIS_TITLE,
|
||||
prompt: i18n.WORKFLOW_ANALYSIS_PROMPT,
|
||||
name: i18n.WORKFLOW_ANALYSIS_TITLE,
|
||||
content: i18n.WORKFLOW_ANALYSIS_PROMPT,
|
||||
color: '#36A2EF',
|
||||
isDefault: true,
|
||||
id: i18n.WORKFLOW_ANALYSIS_TITLE,
|
||||
promptType: PromptTypeEnum.quick,
|
||||
consumer: APP_UI_ID,
|
||||
},
|
||||
{
|
||||
title: i18n.THREAT_INVESTIGATION_GUIDES_TITLE,
|
||||
prompt: i18n.THREAT_INVESTIGATION_GUIDES_PROMPT,
|
||||
name: i18n.THREAT_INVESTIGATION_GUIDES_TITLE,
|
||||
content: i18n.THREAT_INVESTIGATION_GUIDES_PROMPT,
|
||||
categories: [PROMPT_CONTEXT_EVENT_CATEGORY],
|
||||
color: '#F3D371',
|
||||
isDefault: true,
|
||||
id: i18n.THREAT_INVESTIGATION_GUIDES_TITLE,
|
||||
promptType: PromptTypeEnum.quick,
|
||||
consumer: APP_UI_ID,
|
||||
},
|
||||
{
|
||||
title: i18n.SPL_QUERY_CONVERSION_TITLE,
|
||||
prompt: i18n.SPL_QUERY_CONVERSION_PROMPT,
|
||||
name: i18n.SPL_QUERY_CONVERSION_TITLE,
|
||||
content: i18n.SPL_QUERY_CONVERSION_PROMPT,
|
||||
color: '#BADA55',
|
||||
isDefault: true,
|
||||
id: i18n.SPL_QUERY_CONVERSION_TITLE,
|
||||
promptType: PromptTypeEnum.quick,
|
||||
consumer: APP_UI_ID,
|
||||
},
|
||||
{
|
||||
title: i18n.AUTOMATION_TITLE,
|
||||
prompt: i18n.AUTOMATION_PROMPT,
|
||||
name: i18n.AUTOMATION_TITLE,
|
||||
content: i18n.AUTOMATION_PROMPT,
|
||||
color: '#FFA500',
|
||||
isDefault: true,
|
||||
id: i18n.AUTOMATION_TITLE,
|
||||
promptType: PromptTypeEnum.quick,
|
||||
consumer: APP_UI_ID,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -15,24 +15,28 @@ import {
|
|||
AssistantProvider as ElasticAssistantProvider,
|
||||
bulkUpdateConversations,
|
||||
getUserConversations,
|
||||
getPrompts,
|
||||
bulkUpdatePrompts,
|
||||
} from '@kbn/elastic-assistant';
|
||||
|
||||
import { once } from 'lodash/fp';
|
||||
import type { HttpSetup } from '@kbn/core-http-browser';
|
||||
import type { Message } from '@kbn/elastic-assistant-common';
|
||||
import { loadAllActions as loadConnectors } from '@kbn/triggers-actions-ui-plugin/public/common/constants';
|
||||
import { useObservable } from 'react-use';
|
||||
import { APP_ID } from '../../common';
|
||||
import { useBasePath, useKibana } from '../common/lib/kibana';
|
||||
import { useAssistantTelemetry } from './use_assistant_telemetry';
|
||||
import { getComments } from './get_comments';
|
||||
import { LOCAL_STORAGE_KEY, augmentMessageCodeBlocks } from './helpers';
|
||||
import { useBaseConversations } from './use_conversation_store';
|
||||
import { PROMPT_CONTEXTS } from './content/prompt_contexts';
|
||||
import { BASE_SECURITY_QUICK_PROMPTS } from './content/quick_prompts';
|
||||
import { BASE_SECURITY_SYSTEM_PROMPTS } from './content/prompts/system';
|
||||
import { useBaseConversations } from './use_conversation_store';
|
||||
import { PROMPT_CONTEXTS } from './content/prompt_contexts';
|
||||
import { useAssistantAvailability } from './use_assistant_availability';
|
||||
import { useAppToasts } from '../common/hooks/use_app_toasts';
|
||||
import { useSignalIndex } from '../detections/containers/detection_engine/alerts/use_signal_index';
|
||||
import { licenseService } from '../common/hooks/use_license';
|
||||
|
||||
const ASSISTANT_TITLE = i18n.translate('xpack.securitySolution.assistant.title', {
|
||||
defaultMessage: 'Elastic AI Assistant',
|
||||
|
@ -112,12 +116,28 @@ export const createConversations = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const createBasePrompts = async (notifications: NotificationsStart, http: HttpSetup) => {
|
||||
const promptsToCreate = [...BASE_SECURITY_QUICK_PROMPTS, ...BASE_SECURITY_SYSTEM_PROMPTS];
|
||||
|
||||
// post bulk create
|
||||
const bulkResult = await bulkUpdatePrompts(
|
||||
http,
|
||||
{
|
||||
create: promptsToCreate,
|
||||
},
|
||||
notifications.toasts
|
||||
);
|
||||
if (bulkResult && bulkResult.success) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This component configures the Elastic AI Assistant context provider for the Security Solution app.
|
||||
*/
|
||||
export const AssistantProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const {
|
||||
application: { navigateToApp },
|
||||
application: { navigateToApp, currentAppId$ },
|
||||
http,
|
||||
notifications,
|
||||
storage,
|
||||
|
@ -129,29 +149,59 @@ export const AssistantProvider: FC<PropsWithChildren<unknown>> = ({ children })
|
|||
const baseConversations = useBaseConversations();
|
||||
const assistantAvailability = useAssistantAvailability();
|
||||
const assistantTelemetry = useAssistantTelemetry();
|
||||
|
||||
const currentAppId = useObservable(currentAppId$, '');
|
||||
const hasEnterpriseLicence = licenseService.isEnterprise();
|
||||
useEffect(() => {
|
||||
const migrateConversationsFromLocalStorage = once(async () => {
|
||||
const res = await getUserConversations({
|
||||
http,
|
||||
});
|
||||
if (
|
||||
hasEnterpriseLicence &&
|
||||
assistantAvailability.isAssistantEnabled &&
|
||||
assistantAvailability.hasAssistantPrivilege &&
|
||||
res.total === 0
|
||||
assistantAvailability.hasAssistantPrivilege
|
||||
) {
|
||||
await createConversations(notifications, http, storage);
|
||||
const res = await getUserConversations({
|
||||
http,
|
||||
});
|
||||
if (res.total === 0) {
|
||||
await createConversations(notifications, http, storage);
|
||||
}
|
||||
}
|
||||
});
|
||||
migrateConversationsFromLocalStorage();
|
||||
}, [
|
||||
assistantAvailability.hasAssistantPrivilege,
|
||||
assistantAvailability.isAssistantEnabled,
|
||||
hasEnterpriseLicence,
|
||||
http,
|
||||
notifications,
|
||||
storage,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const createSecurityPrompts = once(async () => {
|
||||
if (
|
||||
hasEnterpriseLicence &&
|
||||
assistantAvailability.isAssistantEnabled &&
|
||||
assistantAvailability.hasAssistantPrivilege
|
||||
) {
|
||||
const res = await getPrompts({
|
||||
http,
|
||||
toasts: notifications.toasts,
|
||||
});
|
||||
|
||||
if (res.total === 0) {
|
||||
await createBasePrompts(notifications, http);
|
||||
}
|
||||
}
|
||||
});
|
||||
createSecurityPrompts();
|
||||
}, [
|
||||
assistantAvailability.hasAssistantPrivilege,
|
||||
assistantAvailability.isAssistantEnabled,
|
||||
hasEnterpriseLicence,
|
||||
http,
|
||||
notifications,
|
||||
]);
|
||||
|
||||
const { signalIndexName } = useSignalIndex();
|
||||
const alertsIndexPattern = signalIndexName ?? undefined;
|
||||
const toasts = useAppToasts() as unknown as IToasts; // useAppToasts is the current, non-deprecated method of getting the toasts service in the Security Solution, but it doesn't return the IToasts interface (defined by core)
|
||||
|
@ -166,14 +216,13 @@ export const AssistantProvider: FC<PropsWithChildren<unknown>> = ({ children })
|
|||
docLinks={{ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }}
|
||||
basePath={basePath}
|
||||
basePromptContexts={Object.values(PROMPT_CONTEXTS)}
|
||||
baseQuickPrompts={BASE_SECURITY_QUICK_PROMPTS} // to server and plugin start
|
||||
baseSystemPrompts={BASE_SECURITY_SYSTEM_PROMPTS} // to server and plugin start
|
||||
baseConversations={baseConversations}
|
||||
getComments={getComments}
|
||||
http={http}
|
||||
navigateToApp={navigateToApp}
|
||||
title={ASSISTANT_TITLE}
|
||||
toasts={toasts}
|
||||
currentAppId={currentAppId ?? 'securitySolutionUI'}
|
||||
>
|
||||
{children}
|
||||
</ElasticAssistantProvider>
|
||||
|
|
|
@ -50,6 +50,7 @@ export const MockAssistantProviderComponent: React.FC<Props> = ({
|
|||
http={mockHttp}
|
||||
navigateToApp={mockNavigateToApp}
|
||||
baseConversations={BASE_SECURITY_CONVERSATIONS}
|
||||
currentAppId={'test'}
|
||||
>
|
||||
{children}
|
||||
</AssistantProvider>
|
||||
|
|
|
@ -64,6 +64,7 @@ const ContextWrapper: FC<PropsWithChildren<unknown>> = ({ children }) => (
|
|||
http={mockHttp}
|
||||
navigateToApp={mockNavigationToApp}
|
||||
baseConversations={BASE_SECURITY_CONVERSATIONS}
|
||||
currentAppId={'security'}
|
||||
>
|
||||
{children}
|
||||
</AssistantProvider>
|
||||
|
|
|
@ -13341,8 +13341,6 @@
|
|||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroPersonality": "Donnez la réponse la plus pertinente et détaillée possible, comme si vous deviez communiquer ces informations à un expert en cybersécurité.",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptName": "Invite système améliorée",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.youAreAHelpfulExpertAssistant": "Vous êtes un assistant expert et serviable qui répond à des questions au sujet d’Elastic Security.",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown": "Ajoutez votre description, les actions que vous recommandez ainsi que les étapes de triage à puces. Utilisez les données \"MITRE ATT&CK\" fournies pour ajouter du contexte et des recommandations de MITRE ainsi que des liens hypertexte vers les pages pertinentes sur le site web de MITRE. Assurez-vous d’inclure les scores de risque de l’utilisateur et de l’hôte du contexte. Votre réponse doit inclure des étapes qui pointent vers les fonctionnalités spécifiques d’Elastic Security, y compris les actions de réponse du terminal, l’intégration OSQuery Manager d’Elastic Agent (avec des exemples de requêtes OSQuery), des analyses de timeline et d’entités, ainsi qu’un lien pour toute la documentation Elastic Security pertinente.",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries": "Évaluer l’événement depuis le contexte ci-dessus et formater soigneusement la sortie en syntaxe Markdown pour mon cas Elastic Security.",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.connectorTitle": "Connecteur",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptHelpTextTitle": "Contexte fournit dans le cadre de chaque conversation",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptTitle": "Invite système",
|
||||
|
|
|
@ -13320,8 +13320,6 @@
|
|||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroPersonality": "サイバーセキュリティの専門家に情報を伝えるつもりで、できるだけ詳細で関連性のある回答を入力してください。",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptName": "拡張システムプロンプト",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.youAreAHelpfulExpertAssistant": "あなたはElasticセキュリティに関する質問に答える、親切で専門的なアシスタントです。",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown": "説明、推奨されるアクション、箇条書きのトリアージステップを追加します。提供された MITRE ATT&CKデータを使用して、MITREからのコンテキストや推奨事項を追加し、MITREのWebサイトの関連ページにハイパーリンクを貼ります。コンテキストのユーザーとホストのリスクスコアデータを必ず含めてください。回答には、エンドポイント対応アクション、ElasticエージェントOSQueryマネージャー統合(osqueryクエリの例を付けて)、タイムライン、エンティティ分析など、Elasticセキュリティ固有の機能を指す手順を含め、関連するElasticセキュリティのドキュメントすべてにリンクしてください。",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries": "上記のコンテキストからイベントを評価し、Elasticセキュリティのケース用に、出力をマークダウン構文で正しく書式設定してください。",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.connectorTitle": "コネクター",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptHelpTextTitle": "すべての会話の一部として提供されたコンテキスト",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptTitle": "システムプロンプト",
|
||||
|
|
|
@ -13346,8 +13346,6 @@
|
|||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroPersonality": "提供可能的最详细、最相关的答案,就好像您正将此信息转发给网络安全专家一样。",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptName": "已增强系统提示",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.youAreAHelpfulExpertAssistant": "您是一位可帮助回答 Elastic Security 相关问题的专家助手。",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown": "添加描述、建议操作和带项目符号的分类步骤。使用提供的 MITRE ATT&CK 数据以从 MITRE 添加更多上下文和建议,以及指向 MITRE 网站上的相关页面的超链接。确保包括上下文中的用户和主机风险分数数据。您的响应应包含指向 Elastic Security 特定功能的步骤,包括终端响应操作、Elastic 代理 OSQuery 管理器集成(带示例 osquery 查询)、时间线和实体分析,以及所有相关 Elastic Security 文档的链接。",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries": "评估来自上述上下文的事件,并以用于我的 Elastic Security 案例的 Markdown 语法对您的输出进行全面格式化。",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.connectorTitle": "连接器",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptHelpTextTitle": "已作为每个对话的一部分提供上下文",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptTitle": "系统提示",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue