diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts index eb5d0738f378b..1cbe8bbf8e792 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts @@ -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'; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen.ts index d0bd99e063d0c..2d7a4d762eecc 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen.ts @@ -34,6 +34,14 @@ export const PromptDetailsInError = z.object({ name: z.string().optional(), }); +/** + * Prompt type + */ +export type PromptType = z.infer; +export const PromptType = z.enum(['system', 'quick']); +export type PromptTypeEnum = typeof PromptType.enum; +export const PromptTypeEnum = PromptType.enum; + export type NormalizedPromptError = z.infer; 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; 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; 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; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.schema.yaml index ede0136ba710a..5be6bde140b85 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.schema.yaml @@ -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 diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.tsx index b8c42a787621b..8b6bba33f680a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.tsx @@ -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; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/bulk_update_prompts.test.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/bulk_update_prompts.test.ts new file mode 100644 index 0000000000000..d6ba2e726a76f --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/bulk_update_prompts.test.ts @@ -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; + + 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('')); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/bulk_update_prompts.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/bulk_update_prompts.ts new file mode 100644 index 0000000000000..2d7b053d7acbd --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/bulk_update_prompts.ts @@ -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( + 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 }, + }), + }); + } +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/index.tsx new file mode 100644 index 0000000000000..5a2f6bb80992b --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/index.tsx @@ -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'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx new file mode 100644 index 0000000000000..3ec1586d6cb44 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx @@ -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 }) => ( + {children} + ); +}; + +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(); + }); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.ts new file mode 100644 index 0000000000000..18a229e524dc7 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.ts @@ -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( + 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(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; + } +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx index bd75e80aef0ca..5725d983eff33 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx @@ -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; onConversationCreate: () => Promise; isAssistantEnabled: boolean; + refetchPrompts?: ( + options?: RefetchOptions & RefetchQueryFilters + ) => Promise>; } type Props = OwnProps; @@ -74,6 +78,7 @@ export const AssistantHeaderFlyout: React.FC = ({ refetchConversationsState, onConversationCreate, isAssistantEnabled, + refetchPrompts, }) => { const showAnonymizedValuesChecked = useMemo( () => @@ -164,6 +169,7 @@ export const AssistantHeaderFlyout: React.FC = ({ conversationsLoaded={conversationsLoaded} refetchConversationsState={refetchConversationsState} isFlyoutMode={true} + refetchPrompts={refetchPrompts} /> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx index 2301078d57ba1..f806f5d1ef7c6 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx @@ -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', () => ({ diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx index ac7ca235b36ee..7507c14648614 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx @@ -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; conversationsLoaded: boolean; refetchConversationsState: () => Promise; + allPrompts: PromptResponse[]; + refetchPrompts?: ( + options?: RefetchOptions & RefetchQueryFilters + ) => Promise>; } type Props = OwnProps; @@ -64,6 +70,8 @@ export const AssistantHeader: React.FC = ({ conversations, conversationsLoaded, refetchConversationsState, + allPrompts, + refetchPrompts, }) => { const showAnonymizedValuesChecked = useMemo( () => @@ -122,6 +130,7 @@ export const AssistantHeader: React.FC = ({ isDisabled={isDisabled} conversations={conversations} onConversationDeleted={onConversationDeleted} + allPrompts={allPrompts} /> <> @@ -156,6 +165,7 @@ export const AssistantHeader: React.FC = ({ conversationsLoaded={conversationsLoaded} refetchConversationsState={refetchConversationsState} isFlyoutMode={false} + refetchPrompts={refetchPrompts} /> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx index f42fe17242d86..9d5e822fcdf55 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx @@ -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; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector/index.test.tsx index 6c2ae3b6af211..613163db196ae 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector/index.test.tsx @@ -50,6 +50,7 @@ const defaultProps = { defaultProvider: OpenAiProviderType.OpenAi, conversations: mockConversations, onConversationDeleted, + allPrompts: [], }; describe('Conversation selector', () => { beforeAll(() => { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector/index.tsx index c55540b55b6c0..fd9cddc39dbbe 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector/index.tsx @@ -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; + allPrompts: PromptResponse[]; } const getPreviousConversationId = (conversationIds: string[], selectedConversationId: string) => { @@ -64,10 +68,13 @@ export const ConversationSelector: React.FC = 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(() => { return Object.values(conversations).map((conversation) => ({ diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.tsx index adf2e012c0b8f..cba17030e1577 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.tsx @@ -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; conversationsSettingsBulkActions: ConversationsBulkActions; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx index a0ddd33b34d05..41da376d21b73 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx @@ -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; conversationsSettingsBulkActions: ConversationsBulkActions; http: HttpSetup; @@ -268,7 +269,7 @@ export const ConversationSettingsEditor: React.FC ; conversationsSettingsBulkActions: ConversationsBulkActions; defaultConnector?: AIConnector; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index c4b2f834ecec5..485f89358f57a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -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; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.tsx index fb705db6bb33c..446fb33ebb9e9 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.tsx @@ -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; @@ -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, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx index 907e4d70accd5..6892fdcaf48bd 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx @@ -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 = ({ setLastConversationId, getLastConversationId, title, - allSystemPrompts, baseConversations, } = useAssistantContext(); @@ -182,6 +183,19 @@ const AssistantComponent: React.FC = ({ 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 = ({ // End Scrolling const selectedSystemPrompt = useMemo( - () => getDefaultSystemPrompt({ allSystemPrompts, conversation: currentConversation }), + () => + getDefaultSystemPrompt({ + allSystemPrompts, + conversation: currentConversation, + }), [allSystemPrompts, currentConversation] ); @@ -409,20 +427,21 @@ const AssistantComponent: React.FC = ({ 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 = ({ setIsSettingsModalVisible={setIsSettingsModalVisible} setSelectedPromptContexts={setSelectedPromptContexts} isFlyoutMode={isFlyoutMode} + allSystemPrompts={allSystemPrompts} /> )} ), [ + getComments, abortStream, - refetchCurrentConversation, currentConversation, - editingSystemPromptId, - getComments, showAnonymizedValues, + refetchCurrentConversation, handleRegenerateResponse, isEnabledKnowledgeBase, isEnabledRAGAlerts, @@ -658,12 +677,14 @@ const AssistantComponent: React.FC = ({ currentUserAvatar, isFlyoutMode, selectedPromptContextsCount, + editingSystemPromptId, isNewConversation, isSettingsModalVisible, promptContexts, promptTextPreview, handleOnSystemPromptSelectionChange, selectedPromptContexts, + allSystemPrompts, ] ); @@ -859,6 +880,7 @@ const AssistantComponent: React.FC = ({ isSettingsModalVisible={isSettingsModalVisible} setIsSettingsModalVisible={setIsSettingsModalVisible} isFlyoutMode + allSystemPrompts={allSystemPrompts} /> @@ -882,6 +904,7 @@ const AssistantComponent: React.FC = ({ ); }, [ + allSystemPrompts, comments, connectorPrompt, currentConversation, @@ -948,6 +971,7 @@ const AssistantComponent: React.FC = ({ 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 = ({ setIsSettingsModalVisible={setIsSettingsModalVisible} trackPrompt={trackPrompt} isFlyoutMode={isFlyoutMode} + allPrompts={allPrompts} /> )} @@ -1116,6 +1141,8 @@ const AssistantComponent: React.FC = ({ conversationsLoaded={conversationsLoaded} onConversationDeleted={handleOnConversationDeleted} refetchConversationsState={refetchConversationsState} + allPrompts={allPrompts} + refetchPrompts={refetchPrompts} /> )} @@ -1194,6 +1221,7 @@ const AssistantComponent: React.FC = ({ setIsSettingsModalVisible={setIsSettingsModalVisible} trackPrompt={trackPrompt} isFlyoutMode={isFlyoutMode} + allPrompts={allPrompts} /> )} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt/helpers.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt/helpers.ts index bd00058b4bbd3..4868eff04b4e7 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt/helpers.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt/helpers.ts @@ -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; - selectedSystemPrompt: Prompt | undefined; + selectedSystemPrompt: PromptResponse | undefined; }): ClientMessageWithReplacements { let replacements: Replacements = currentReplacements ?? {}; const onNewReplacements = (newReplacements: Replacements) => { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/helpers.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/helpers.test.tsx index c055d9bd6bb95..2171b13273a28 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/helpers.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/helpers.test.tsx @@ -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 }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/helpers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/helpers.tsx index 7b3e91147635f..f11d2e0af641a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/helpers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/helpers.tsx @@ -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); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/index.test.tsx index 6a18b9794c597..6d421b649a380 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/index.test.tsx @@ -40,6 +40,7 @@ const defaultProps: Props = { setIsSettingsModalVisible: jest.fn(), setSelectedPromptContexts: jest.fn(), isFlyoutMode: false, + allSystemPrompts: [], }; describe('PromptEditorComponent', () => { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/index.tsx index b2f5c1a3a2e33..1528435764acd 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/index.tsx @@ -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> >; isFlyoutMode: boolean; + allSystemPrompts: PromptResponse[]; } const PreviewText = styled(EuiText)` @@ -49,12 +51,14 @@ const PromptEditorComponent: React.FC = ({ setIsSettingsModalVisible, setSelectedPromptContexts, isFlyoutMode, + allSystemPrompts, }) => { const commentBody = useMemo( () => ( <> {isNewConversation && ( = ({ ), [ + isNewConversation, + allSystemPrompts, conversation, editingSystemPromptId, - isNewConversation, - isSettingsModalVisible, onSystemPromptSelectionChange, + isSettingsModalVisible, + setIsSettingsModalVisible, + isFlyoutMode, promptContexts, - promptTextPreview, selectedPromptContexts, - setIsSettingsModalVisible, setSelectedPromptContexts, - isFlyoutMode, + promptTextPreview, ] ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.test.tsx index 4a26b22b0b2d3..82b04c60a569c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.test.tsx @@ -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({option.dropdownDisplay}); @@ -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({option.dropdownDisplay}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx index a52ed303d4a63..bd217bb54e9f6 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx @@ -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 => ({ +}: PromptResponse & { + showTitles?: boolean; + isFlyoutMode: boolean; +}): EuiSuperSelectOption => ({ 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> => prompts?.map((p) => getOptionFromPrompt({ ...p, showTitles, isFlyoutMode })) ?? []; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.test.tsx index e0fed34795dfc..34d40852ba505 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.test.tsx @@ -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( ); }); @@ -157,6 +159,7 @@ describe('SystemPrompt', () => { onSystemPromptSelectionChange={onSystemPromptSelectionChange} setIsSettingsModalVisible={setIsSettingsModalVisible} isFlyoutMode={false} + allSystemPrompts={mockSystemPrompts} /> ); @@ -204,6 +207,7 @@ describe('SystemPrompt', () => { onSystemPromptSelectionChange={onSystemPromptSelectionChange} setIsSettingsModalVisible={setIsSettingsModalVisible} isFlyoutMode={false} + allSystemPrompts={mockSystemPrompts} /> ); @@ -265,6 +269,7 @@ describe('SystemPrompt', () => { onSystemPromptSelectionChange={onSystemPromptSelectionChange} setIsSettingsModalVisible={setIsSettingsModalVisible} isFlyoutMode={false} + allSystemPrompts={mockSystemPrompts} /> ); @@ -333,6 +338,7 @@ describe('SystemPrompt', () => { onSystemPromptSelectionChange={onSystemPromptSelectionChange} setIsSettingsModalVisible={setIsSettingsModalVisible} isFlyoutMode={false} + allSystemPrompts={mockSystemPrompts} /> ); @@ -416,6 +422,7 @@ describe('SystemPrompt', () => { onSystemPromptSelectionChange={onSystemPromptSelectionChange} setIsSettingsModalVisible={setIsSettingsModalVisible} isFlyoutMode={false} + allSystemPrompts={mockSystemPrompts} /> ); @@ -481,11 +488,12 @@ describe('SystemPrompt', () => { ); @@ -500,11 +508,12 @@ describe('SystemPrompt', () => { ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx index d6a507b6deeed..f2808c3e204f1 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx @@ -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>; isFlyoutMode: boolean; + allSystemPrompts: PromptResponse[]; } const SystemPromptComponent: React.FC = ({ @@ -31,17 +32,13 @@ const SystemPromptComponent: React.FC = ({ 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 = ({ if (isFlyoutMode) { return ( = ({
{selectedPrompt == null || isEditing ? ( ); 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', diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/select_system_prompt/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/select_system_prompt/index.tsx index 2cbbdf68b307c..0296fa3e636ca 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/select_system_prompt/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/select_system_prompt/index.tsx @@ -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 = ({ - allSystemPrompts, + allPrompts, compressed = false, conversation, selectedPrompt, @@ -68,21 +71,24 @@ const SelectSystemPromptComponent: React.FC = ({ const { setSelectedSettingsTab } = useAssistantContext(); const { setApiConfig } = useConversation(); - const [isOpenLocal, setIsOpenLocal] = useState(isOpen); - const [valueOfSelected, setValueOfSelected] = useState( - selectedPrompt?.id ?? allSystemPrompts?.[0]?.id + const allSystemPrompts = useMemo( + () => allPrompts.filter((p) => p.promptType === PromptTypeEnum.system), + [allPrompts] ); + + const [isOpenLocal, setIsOpenLocal] = useState(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 = ({ // 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 = ({ setSelectedSystemPrompt(undefined); setIsEditing?.(false); clearSelectedSystemPrompt?.(); - setValueOfSelected(undefined); }, [clearSelectedSystemPrompt, setIsEditing, setSelectedSystemPrompt]); const onShowSelectSystemPrompt = useCallback(() => { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx index 3fd7dfeb00e73..fecb2ed401a4b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx @@ -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; conversationsSettingsBulkActions: ConversationsBulkActions; - onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; - selectedSystemPrompt: Prompt | undefined; - setUpdatedSystemPromptSettings: React.Dispatch>; + onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void; + selectedSystemPrompt: PromptResponse | undefined; + setUpdatedSystemPromptSettings: React.Dispatch>; setConversationSettings: React.Dispatch>>; - systemPromptSettings: Prompt[]; + systemPromptSettings: PromptResponse[]; setConversationsSettingsBulkActions: React.Dispatch< React.SetStateAction >; defaultConnector?: AIConnector; resetSettings?: () => void; + promptsBulkActions: PromptsPerformBulkActionRequestBody; + setPromptsBulkActions: React.Dispatch>; } /** @@ -61,6 +67,8 @@ export const SystemPromptEditorComponent: React.FC = ({ setConversationsSettingsBulkActions, defaultConnector, resetSettings, + promptsBulkActions, + setPromptsBulkActions, }) => { // Prompt const promptContent = useMemo( @@ -72,11 +80,11 @@ export const SystemPromptEditorComponent: React.FC = ({ const handlePromptContentChange = useCallback( (e: React.ChangeEvent) => { 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 = ({ 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 = ({ }; }); }); + 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 ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.test.tsx index 45f320528ec64..cbf5efe79213f 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.test.tsx @@ -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', () => { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx index 53b6414d05b53..2c4826940a7ca 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.tsx @@ -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 = 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 = 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 = React.memo( const newOption = { value: searchValue, + id: searchValue, label: searchValue, }; @@ -132,11 +135,12 @@ export const SystemPromptSelector: React.FC = 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] ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.test.tsx index be9e33f615e4b..5116da2a56207 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.test.tsx @@ -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', diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx index b7f66acba85c7..7b8e451449884 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx @@ -26,7 +26,9 @@ export const SystemPromptSettings: React.FC = React.m systemPromptSettings, conversationsSettingsBulkActions, setConversationsSettingsBulkActions, + promptsBulkActions, defaultConnector, + setPromptsBulkActions, }) => { return ( <> @@ -48,6 +50,8 @@ export const SystemPromptSettings: React.FC = React.m conversationsSettingsBulkActions={conversationsSettingsBulkActions} setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} defaultConnector={defaultConnector} + setPromptsBulkActions={setPromptsBulkActions} + promptsBulkActions={promptsBulkActions} /> ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/types.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/types.ts index 63025566c9400..e92961cb1763f 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/types.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/types.ts @@ -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; conversationsSettingsBulkActions: ConversationsBulkActions; - onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; - selectedSystemPrompt: Prompt | undefined; - setUpdatedSystemPromptSettings: React.Dispatch>; + onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void; + selectedSystemPrompt: PromptResponse | undefined; + setUpdatedSystemPromptSettings: React.Dispatch>; setConversationSettings: React.Dispatch>>; - systemPromptSettings: Prompt[]; + systemPromptSettings: PromptResponse[]; setConversationsSettingsBulkActions: React.Dispatch< React.SetStateAction >; defaultConnector?: AIConnector; + promptsBulkActions: PromptsPerformBulkActionRequestBody; + setPromptsBulkActions: React.Dispatch>; } diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.test.tsx index 85efe99979650..009ee6c5a83cd 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.test.tsx @@ -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); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.tsx index 87e284d6dcf25..ec77de113b5d9 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.tsx @@ -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>; - onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; + setUpdatedSystemPromptSettings: React.Dispatch>; + onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void; + promptsBulkActions: PromptsPerformBulkActionRequestBody; + setPromptsBulkActions: React.Dispatch>; } 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 }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx index e0f27f3fa8c7d..14b6ecb868ead 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -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; conversationsSettingsBulkActions: ConversationsBulkActions; - onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; - selectedSystemPrompt: Prompt | undefined; - setUpdatedSystemPromptSettings: React.Dispatch>; + onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void; + selectedSystemPrompt: PromptResponse | undefined; + setUpdatedSystemPromptSettings: React.Dispatch>; setConversationSettings: React.Dispatch>>; - systemPromptSettings: Prompt[]; + systemPromptSettings: PromptResponse[]; setConversationsSettingsBulkActions: React.Dispatch< React.SetStateAction >; @@ -49,6 +52,8 @@ interface Props { handleSave: (shouldRefetchConversation?: boolean) => void; onCancelClick: () => void; resetSettings: () => void; + promptsBulkActions: PromptsPerformBulkActionRequestBody; + setPromptsBulkActions: React.Dispatch>; } 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(); + const [deletedPrompt, setDeletedPrompt] = useState(); 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} /> {deleteConfirmModalVisibility && deletedPrompt?.name && ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.test.tsx index 90cea2319714d..48d3232f0ae38 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.test.tsx @@ -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, }, ]; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.tsx index 7cf907bb7adf5..46e082b86f2c0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.tsx @@ -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; defaultConnector: AIConnector | undefined; - systemPromptSettings: Prompt[]; + systemPromptSettings: PromptResponse[]; }): SystemPromptTableItem[] => { const conversationsWithApiConfig = Object.entries( conversationSettings diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.test.tsx index 5f10e3bb59c65..9fbfb3a8782e0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.test.tsx @@ -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, }, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.tsx index 5fde200db9b17..fd01b8eb318a6 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.tsx @@ -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, systemPromptId: string ) => { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.test.tsx index 04ccd478e3bc5..941b442ce4d48 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.test.tsx @@ -25,9 +25,9 @@ describe('QuickPromptSelector', () => { }); it('Selects an existing quick prompt', () => { const { getByTestId } = render(); - 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', diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.tsx index 3fb0ba17cf4bf..d29887e8c4f6a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.tsx @@ -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 = 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 = 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 = 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 = React.memo( const newOption = { value: searchValue, label: searchValue, + id: searchValue, }; if (!optionExists) { @@ -125,11 +128,12 @@ export const QuickPromptSelector: React.FC = 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] ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx index 4300e53525b33..01ffe00d11100 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx @@ -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>; + selectedQuickPrompt: PromptResponse | undefined; + setUpdatedQuickPromptSettings: React.Dispatch>; + promptsBulkActions: PromptsPerformBulkActionRequestBody; + setPromptsBulkActions: React.Dispatch>; } 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) => { 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; `} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.test.tsx index 6aa939934d585..eb8cc2cb2569c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.test.tsx @@ -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, ]); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.tsx index 4b8b6a8f8039d..61496c64fd73a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.tsx @@ -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>; + onSelectedQuickPromptChange: (quickPrompt?: PromptResponse) => void; + quickPromptSettings: PromptResponse[]; + selectedQuickPrompt: PromptResponse | undefined; + setUpdatedQuickPromptSettings: React.Dispatch>; + promptsBulkActions: PromptsPerformBulkActionRequestBody; + setPromptsBulkActions: React.Dispatch>; } /** @@ -28,6 +33,8 @@ export const QuickPromptSettings: React.FC = React.memo( quickPromptSettings, selectedQuickPrompt, setUpdatedQuickPromptSettings, + promptsBulkActions, + setPromptsBulkActions, }) => { return ( <> @@ -43,6 +50,8 @@ export const QuickPromptSettings: React.FC = React.memo( quickPromptSettings={quickPromptSettings} selectedQuickPrompt={selectedQuickPrompt} setUpdatedQuickPromptSettings={setUpdatedQuickPromptSettings} + promptsBulkActions={promptsBulkActions} + setPromptsBulkActions={setPromptsBulkActions} /> ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.test.tsx index ec3a0256716ae..509db5991455f 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.test.tsx @@ -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); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.tsx index 716298afb21da..d96c4fca716d1 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.tsx @@ -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>; + onSelectedQuickPromptChange: (quickPrompt?: PromptResponse) => void; + setUpdatedQuickPromptSettings: React.Dispatch>; + promptsBulkActions: PromptsPerformBulkActionRequestBody; + setPromptsBulkActions: React.Dispatch>; }) => { + 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 }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx index e8362db441719..ac93161d35c17 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx @@ -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>; + selectedQuickPrompt: PromptResponse | undefined; + setUpdatedQuickPromptSettings: React.Dispatch>; + promptsBulkActions: PromptsPerformBulkActionRequestBody; + setPromptsBulkActions: React.Dispatch>; } 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(); + const [deletedQuickPrompt, setDeletedQuickPrompt] = useState(); 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} /> {deleteConfirmModalVisibility && deletedQuickPrompt && ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.test.tsx index 316b43f6cfb3d..ca647dc530265 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.test.tsx @@ -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).render( + const mockBadgesColumn = (columns[1] as EuiTableComputedColumnType).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).render( + const mockRowActions = (columns[2] as EuiTableComputedColumnType).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).render( + const mockRowActions = (columns[2] as EuiTableComputedColumnType).render( nonDefaultPrompt ); expect(mockRowActions).toHaveProperty('props', { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.tsx index 9ec334f817340..1899905db0ea1 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.tsx @@ -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> => [ + onEditActionClicked: (prompt: PromptResponse) => void; + onDeleteActionClicked: (prompt: PromptResponse) => void; + }): Array> => [ { align: 'left', name: i18n.QUICK_PROMPTS_TABLE_COLUMN_NAME, - render: (prompt: QuickPrompt) => - prompt?.title ? ( - onEditActionClicked(prompt)}>{prompt?.title} + render: (prompt: PromptResponse) => + prompt?.name ? ( + onEditActionClicked(prompt)}>{prompt?.name} ) : 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 ? ( - + ) : 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 ( - + rowItem={prompt} onDelete={isDeletable ? onDeleteActionClicked : undefined} onEdit={onEditActionClicked} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx index 7fb2c9760fc7b..6e5172dc0c2ad 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx @@ -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 = { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx index 7d08d20f432b9..c578a58be728d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx @@ -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>; 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 = 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 = 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 = 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 = React.memo( ); const onClickOverflowQuickPrompt = useCallback( - (badge: QuickPrompt) => { + (badge: PromptResponse) => { onClickAddQuickPrompt(badge); closeOverflowPopover(); }, @@ -137,9 +144,9 @@ export const QuickPrompts: React.FC = React.memo( onClickAddQuickPrompt(badge)} - onClickAriaLabel={badge.title} + onClickAriaLabel={badge.name} > - {badge.title} + {badge.name} ))} @@ -172,9 +179,9 @@ export const QuickPrompts: React.FC = React.memo( onClickOverflowQuickPrompt(badge)} - onClickAriaLabel={badge.title} + onClickAriaLabel={badge.name} > - {badge.title} + {badge.name} ))} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/types.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/types.tsx deleted file mode 100644 index c0688f432e7dd..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/types.tsx +++ /dev/null @@ -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; - isDefault?: boolean; -} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx index 68a8049b825b3..d5bbefe304208 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx @@ -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 = 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 = React.memo( setUpdatedAssistantStreamingEnabled, setUpdatedKnowledgeBaseSettings, setUpdatedQuickPromptSettings, - setUpdatedSystemPromptSettings, + promptsBulkActions, saveSettings, conversationsSettingsBulkActions, updatedAnonymizationData, @@ -120,7 +123,9 @@ export const AssistantSettings: React.FC = 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 = React.memo( ); // Quick Prompt Selection State - const [selectedQuickPrompt, setSelectedQuickPrompt] = useState(); - const onHandleSelectedQuickPromptChange = useCallback((quickPrompt?: QuickPrompt) => { + const [selectedQuickPrompt, setSelectedQuickPrompt] = useState(); + 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(); - const onHandleSelectedSystemPromptChange = useCallback((systemPrompt?: Prompt) => { + const [selectedSystemPrompt, setSelectedSystemPrompt] = useState(); + const onHandleSelectedSystemPromptChange = useCallback((systemPrompt?: PromptResponse) => { setSelectedSystemPrompt(systemPrompt); }, []); useEffect(() => { @@ -342,6 +347,8 @@ export const AssistantSettings: React.FC = 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 = React.memo( setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} conversationsSettingsBulkActions={conversationsSettingsBulkActions} setUpdatedSystemPromptSettings={setUpdatedSystemPromptSettings} + setPromptsBulkActions={setPromptsBulkActions} + promptsBulkActions={promptsBulkActions} /> )} {selectedSettingsTab === ANONYMIZATION_TAB && ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx index 432730194d1a3..30f141f219476 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx @@ -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; conversationsLoaded: boolean; refetchConversationsState: () => Promise; + refetchPrompts?: ( + options?: RefetchOptions & RefetchQueryFilters + ) => Promise>; } /** @@ -43,6 +47,7 @@ export const AssistantSettingsButton: React.FC = React.memo( conversations, conversationsLoaded, refetchConversationsState, + refetchPrompts, }) => { const { toasts, setSelectedSettingsTab } = useAssistantContext(); @@ -59,6 +64,9 @@ export const AssistantSettingsButton: React.FC = 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 = React.memo( }); } }, - [cleanupAndCloseModal, refetchConversationsState, toasts] + [cleanupAndCloseModal, refetchConversationsState, refetchPrompts, toasts] ); const handleShowConversationSettings = useCallback(() => { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx index 3b34b3467aa84..15fb05ca1c807 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx @@ -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(); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx index 4e89bb3bba4fc..3f9be4972fe7e 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx @@ -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; @@ -73,6 +75,9 @@ export const AssistantSettingsManagement: React.FC = 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 = React.memo( setUpdatedAssistantStreamingEnabled, setUpdatedKnowledgeBaseSettings, setUpdatedQuickPromptSettings, - setUpdatedSystemPromptSettings, + setPromptsBulkActions, saveSettings, conversationsSettingsBulkActions, updatedAnonymizationData, @@ -100,13 +105,32 @@ export const AssistantSettingsManagement: React.FC = 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( @@ -136,21 +160,21 @@ export const AssistantSettingsManagement: React.FC = React.memo( }, [selectedSettingsTab, setSelectedSettingsTab]); // Quick Prompt Selection State - const [selectedQuickPrompt, setSelectedQuickPrompt] = useState(); - const onHandleSelectedQuickPromptChange = useCallback((quickPrompt?: QuickPrompt) => { + const [selectedQuickPrompt, setSelectedQuickPrompt] = useState(); + 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(); - const onHandleSelectedSystemPromptChange = useCallback((systemPrompt?: Prompt) => { + const [selectedSystemPrompt, setSelectedSystemPrompt] = useState(); + const onHandleSelectedSystemPromptChange = useCallback((systemPrompt?: PromptResponse) => { setSelectedSystemPrompt(systemPrompt); }, []); useEffect(() => { @@ -303,7 +327,9 @@ export const AssistantSettingsManagement: React.FC = 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 = 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 && ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.test.tsx index 20e5c86ddd251..0a2c72ba80ac4 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.test.tsx @@ -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; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx index c6cf81c4bf949..1ae1c9e5b1b73 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_settings_updater/use_settings_updater.tsx @@ -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 >; @@ -37,26 +44,25 @@ interface UseSettingsUpdater { setAnonymizationFieldsBulkActions: React.Dispatch< React.SetStateAction >; + promptsBulkActions: PromptsPerformBulkActionRequestBody; + setPromptsBulkActions: React.Dispatch>; setUpdatedKnowledgeBaseSettings: React.Dispatch>; - setUpdatedQuickPromptSettings: React.Dispatch>; - setUpdatedSystemPromptSettings: React.Dispatch>; + setUpdatedQuickPromptSettings: React.Dispatch>; + setUpdatedSystemPromptSettings: React.Dispatch>; setUpdatedAssistantStreamingEnabled: React.Dispatch>; saveSettings: () => Promise; } export const useSettingsUpdater = ( conversations: Record, + 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({}); // Quick Prompts - const [updatedQuickPromptSettings, setUpdatedQuickPromptSettings] = - useState(allQuickPrompts); + const [quickPromptSettings, setUpdatedQuickPromptSettings] = useState( + allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.quick) + ); // System Prompts - const [updatedSystemPromptSettings, setUpdatedSystemPromptSettings] = - useState(allSystemPrompts); + const [systemPromptSettings, setUpdatedSystemPromptSettings] = useState( + allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system) + ); // Anonymization const [anonymizationFieldsBulkActions, setAnonymizationFieldsBulkActions] = useState({}); + // Prompts + const [promptsBulkActions, setPromptsBulkActions] = useState( + {} + ); const [updatedAnonymizationData, setUpdatedAnonymizationData] = useState(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 => { - 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, }; }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts index 91ee3468a12d9..587be76910c3e 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts @@ -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; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/helpers.test.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/helpers.test.ts index c8c8ab5ff7727..3e997fef5d573 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/helpers.test.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/helpers.test.ts @@ -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 ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/helpers.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/helpers.ts index 2d6c4075fba0e..fde1c1d3d943c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/helpers.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/helpers.ts @@ -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; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx index 84fa21417ae70..a276aea3ff4ab 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_conversation/index.tsx @@ -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] ); /** diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx index ffafb4f704a17..78336f8a8b03d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -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; children: React.ReactNode; getComments: (commentArgs: { @@ -87,6 +81,7 @@ export interface AssistantProviderProps { navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise; 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; basePath: string; - basePromptContexts: PromptContextTemplate[]; - baseQuickPrompts: QuickPrompt[]; - baseSystemPrompts: Prompt[]; baseConversations: Record; getComments: (commentArgs: { abortStream: () => void; @@ -134,8 +124,6 @@ export interface UseAssistantContext { nameSpace: string; registerPromptContext: RegisterPromptContext; selectedSettingsTab: SettingsTabs | null; - setAllQuickPrompts: React.Dispatch>; - setAllSystemPrompts: React.Dispatch>; setAssistantStreamingEnabled: React.Dispatch>; setKnowledgeBase: React.Dispatch>; setLastConversationId: React.Dispatch>; @@ -150,7 +138,9 @@ export interface UseAssistantContext { title: string; toasts: IToasts | undefined; traceOptions: TraceOptions; + basePromptContexts: PromptContextTemplate[]; unRegisterPromptContext: UnRegisterPromptContext; + currentAppId: string; } const AssistantContext = React.createContext(undefined); @@ -164,8 +154,6 @@ export const AssistantProvider: React.FC = ({ docLinks, basePath, basePromptContexts = [], - baseQuickPrompts = [], - baseSystemPrompts = BASE_SYSTEM_PROMPTS, children, getComments, http, @@ -174,6 +162,7 @@ export const AssistantProvider: React.FC = ({ 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 = ({ 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(`${nameSpace}.${LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY}`); @@ -290,12 +263,8 @@ export const AssistantProvider: React.FC = ({ 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 = ({ // 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 = ({ getLastConversationId, setLastConversationId: setLocalStorageLastConversationId, baseConversations, + currentAppId, }), [ actionTypeRegistry, @@ -330,12 +298,8 @@ export const AssistantProvider: React.FC = ({ assistantFeatures, assistantTelemetry, augmentMessageCodeBlocks, - localStorageQuickPrompts, - localStorageSystemPrompts, basePath, basePromptContexts, - baseQuickPrompts, - baseSystemPrompts, docLinks, getComments, http, @@ -347,8 +311,6 @@ export const AssistantProvider: React.FC = ({ selectedSettingsTab, localStorageStreaming, setLocalStorageStreaming, - setLocalStorageQuickPrompts, - setLocalStorageSystemPrompts, setLocalStorageKnowledgeBase, setSessionStorageTraceOptions, showAssistantOverlay, @@ -359,6 +321,7 @@ export const AssistantProvider: React.FC = ({ getLastConversationId, setLocalStorageLastConversationId, baseConversations, + currentAppId, ] ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/index.tsx deleted file mode 100644 index a73fbf4854ef1..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/index.tsx +++ /dev/null @@ -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, - }, -]; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/user/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/user/translations.ts deleted file mode 100644 index 28cda1f9414a8..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/user/translations.ts +++ /dev/null @@ -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}`; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/mock/quick_prompt.ts b/x-pack/packages/kbn-elastic-assistant/impl/mock/quick_prompt.ts index 14318f4b1b534..31fa9bb6508b8 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/mock/quick_prompt.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/mock/quick_prompt.ts @@ -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, }, ]; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/mock/system_prompt/index.ts b/x-pack/packages/kbn-elastic-assistant/impl/mock/system_prompt/index.ts index de23052d15564..04b027cbfe578 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/mock/system_prompt/index.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/mock/system_prompt/index.ts @@ -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, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx index 17e977fdbf80f..13e543a02b3b2 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx @@ -81,6 +81,7 @@ export const TestProvidersComponent: React.FC = ({ baseConversations={{}} navigateToApp={mockNavigateToApp} {...providerContext} + currentAppId={'test'} > {children} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/mock/user_prompt/index.ts b/x-pack/packages/kbn-elastic-assistant/impl/mock/user_prompt/index.ts index 5bc23b0d680e3..1f7c96126bc10 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/mock/user_prompt/index.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/mock/user_prompt/index.ts @@ -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', }; diff --git a/x-pack/packages/kbn-elastic-assistant/index.ts b/x-pack/packages/kbn-elastic-assistant/index.ts index df0ba1e8db0f9..7cd882cd633b8 100644 --- a/x-pack/packages/kbn-elastic-assistant/index.ts +++ b/x-pack/packages/kbn-elastic-assistant/index.ts @@ -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'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx index a4e6d39e720ec..b4579dd4bd50c 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx @@ -73,6 +73,7 @@ export const TestProvidersComponent: React.FC = ({ children, isILMAvailab http={mockHttp} baseConversations={{}} navigateToApp={mockNavigateToApp} + currentAppId={'securitySolutionUI'} > { 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', }; }; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/field_maps_configuration.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/field_maps_configuration.ts index 50df573d01872..5a916793332b7 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/field_maps_configuration.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/field_maps_configuration.ts @@ -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, diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/helpers.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/helpers.ts index 83d7713c23f1f..a4534972c8478 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/helpers.ts @@ -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; `, diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/types.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/types.ts index 91f52fb3a0829..0d936cc852aca 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/types.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/prompts/types.ts @@ -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; diff --git a/x-pack/plugins/elastic_assistant/server/routes/prompts/find_route.ts b/x-pack/plugins/elastic_assistant/server/routes/prompts/find_route.ts index df2ec323bc356..8838db3c44943 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/prompts/find_route.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/prompts/find_route.ts @@ -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, }); diff --git a/x-pack/plugins/security_solution/public/assistant/content/prompts/system/index.tsx b/x-pack/plugins/security_solution/public/assistant/content/prompts/system/index.tsx index 6b1976cd5207d..ee9d4018365c5 100644 --- a/x-pack/plugins/security_solution/public/assistant/content/prompts/system/index.tsx +++ b/x-pack/plugins/security_solution/public/assistant/content/prompts/system/index.tsx @@ -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, }, ]; diff --git a/x-pack/plugins/security_solution/public/assistant/content/quick_prompts/index.tsx b/x-pack/plugins/security_solution/public/assistant/content/quick_prompts/index.tsx index 799087f202e98..adb952d661214 100644 --- a/x-pack/plugins/security_solution/public/assistant/content/quick_prompts/index.tsx +++ b/x-pack/plugins/security_solution/public/assistant/content/quick_prompts/index.tsx @@ -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, }, ]; diff --git a/x-pack/plugins/security_solution/public/assistant/provider.tsx b/x-pack/plugins/security_solution/public/assistant/provider.tsx index 2b5daf73fbf4b..134bfb25c15ac 100644 --- a/x-pack/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/plugins/security_solution/public/assistant/provider.tsx @@ -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> = ({ children }) => { const { - application: { navigateToApp }, + application: { navigateToApp, currentAppId$ }, http, notifications, storage, @@ -129,29 +149,59 @@ export const AssistantProvider: FC> = ({ 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> = ({ 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} diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index b9a83fd280b10..04860ba9c6c71 100644 --- a/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -50,6 +50,7 @@ export const MockAssistantProviderComponent: React.FC = ({ http={mockHttp} navigateToApp={mockNavigateToApp} baseConversations={BASE_SECURITY_CONVERSATIONS} + currentAppId={'test'} > {children} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx index b5e7737be38f7..23c2d2e7b9f6b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx @@ -64,6 +64,7 @@ const ContextWrapper: FC> = ({ children }) => ( http={mockHttp} navigateToApp={mockNavigationToApp} baseConversations={BASE_SECURITY_CONVERSATIONS} + currentAppId={'security'} > {children} diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 04bc9871cb0af..00eb8d11923b2 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -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", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2fed66584a779..7778a86693d86 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -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": "システムプロンプト", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8735ee2e0257d..0257b4d2830a4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -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": "系统提示",