diff --git a/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx b/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx index 24dc00cae84f6..3b50e26d4de08 100644 --- a/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx +++ b/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx @@ -20,6 +20,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useAppContext } from '../../app_context'; export function AiAssistantSelectionPage() { @@ -86,21 +87,25 @@ export function AiAssistantSelectionPage() { ) : null}

- {i18n.translate( - 'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkDescription', - { defaultMessage: 'For more info, see our' } - )}{' '} - - {i18n.translate( - 'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkLabel', - { defaultMessage: 'documentation' } - )} - + + {i18n.translate( + 'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkLabel', + { defaultMessage: 'documentation' } + )} + + ), + }} + />

) : null}

- {i18n.translate( - 'aiAssistantManagementSelection.aiAssistantSelectionPage.securityAssistant.documentationLinkDescription', - { defaultMessage: 'For more info, see our' } - )}{' '} - - {i18n.translate( - 'aiAssistantManagementSelection.aiAssistantSettingsPage.securityAssistant.documentationLinkLabel', - { defaultMessage: 'documentation' } - )} - + + {i18n.translate( + 'aiAssistantManagementSelection.aiAssistantSettingsPage.securityAssistant.documentationLinkLabel', + { defaultMessage: 'documentation' } + )} + + ), + }} + />

{ - const defaultSystemPromptId = getDefaultSystemPrompt({ - allSystemPrompts, - conversation: currentConversation, - })?.id; + const defaultSystemPromptId = + getDefaultSystemPrompt({ + allSystemPrompts, + conversation: currentConversation, + })?.id ?? getDefaultNewSystemPrompt(allSystemPrompts)?.id; setUserPrompt(''); setSelectedPromptContexts({}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/index.tsx index e749fb483d504..f36591e5dbb0e 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/index.tsx @@ -27,6 +27,7 @@ interface Props { onClose: () => void; onSaveCancelled: () => void; onSaveConfirmed: () => void; + saveButtonDisabled?: boolean; } const FlyoutComponent: React.FC = ({ @@ -36,6 +37,7 @@ const FlyoutComponent: React.FC = ({ onClose, onSaveCancelled, onSaveConfirmed, + saveButtonDisabled = false, }) => { return flyoutVisible ? ( = ({ data-test-subj="save-button" onClick={onSaveConfirmed} iconType="check" + disabled={saveButtonDisabled} fill > {i18n.FLYOUT_SAVE_BUTTON_TITLE} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx new file mode 100644 index 0000000000000..6e955dd554d39 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/index.tsx @@ -0,0 +1,60 @@ +/* + * 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 { EuiTableActionsColumnType } from '@elastic/eui'; +import { useCallback } from 'react'; +import * as i18n from './translations'; + +interface Props { + disabled?: boolean; + onDelete?: (rowItem: T) => void; + onEdit?: (rowItem: T) => void; +} + +export const useInlineActions = () => { + const getInlineActions = useCallback(({ disabled = false, onDelete, onEdit }: Props) => { + const handleEdit = (rowItem: T) => { + onEdit?.(rowItem); + }; + + const handleDelete = (rowItem: T) => { + onDelete?.(rowItem); + }; + + const actions: EuiTableActionsColumnType = { + name: i18n.ACTIONS_BUTTON, + actions: [ + { + name: i18n.EDIT_BUTTON, + description: i18n.EDIT_BUTTON, + icon: 'pencil', + type: 'icon', + onClick: (rowItem: T) => { + handleEdit(rowItem); + }, + enabled: () => !disabled, + available: () => onEdit != null, + }, + { + name: i18n.DELETE_BUTTON, + description: i18n.DELETE_BUTTON, + icon: 'trash', + type: 'icon', + onClick: (rowItem: T) => { + handleDelete(rowItem); + }, + enabled: ({ isDefault }: { isDefault?: boolean }) => !isDefault && !disabled, + available: () => onDelete != null, + color: 'danger', + }, + ], + }; + return actions; + }, []); + + return getInlineActions; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/translations.ts similarity index 79% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/translations.ts rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/translations.ts index 1a2430cb6428c..9503c97962bff 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/inline_actions/translations.ts @@ -20,3 +20,10 @@ export const DELETE_BUTTON = i18n.translate( defaultMessage: 'Delete', } ); + +export const ACTIONS_BUTTON = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.actionsButtonTitle', + { + defaultMessage: 'Actions', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/index.tsx deleted file mode 100644 index b2d31ec80433f..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/index.tsx +++ /dev/null @@ -1,89 +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 { EuiButtonEmpty, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; -import * as i18n from './translations'; - -interface Props { - isDeletable?: boolean; - isEditable?: boolean; - onDelete?: (rowItem: T) => void; - onEdit?: (rowItem: T) => void; - rowItem: T; -} - -type RowActionsComponentType = (props: Props) => JSX.Element; - -const RowActionsComponent = ({ - isDeletable = true, - isEditable = true, - onDelete, - onEdit, - rowItem, -}: Props) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - - const closePopover = useCallback(() => setIsPopoverOpen(false), []); - const handleEdit = useCallback(() => { - closePopover(); - onEdit?.(rowItem); - }, [closePopover, onEdit, rowItem]); - - const handleDelete = useCallback(() => { - closePopover(); - onDelete?.(rowItem); - }, [closePopover, onDelete, rowItem]); - - const onButtonClick = useCallback(() => setIsPopoverOpen((prevState) => !prevState), []); - return onEdit || onDelete ? ( - - } - isOpen={isPopoverOpen} - closePopover={closePopover} - anchorPosition="downLeft" - > - - {onEdit != null && ( - - - {i18n.EDIT_BUTTON} - - - )} - {onDelete != null && ( - - - {i18n.DELETE_BUTTON} - - - )} - - - ) : null; -}; - -// casting to correctly infer the param of onEdit and onDelete when reusing this component -export const RowActions = React.memo(RowActionsComponent) as RowActionsComponentType; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/index.tsx index f1edb5a9dc2a9..9e7e3ae362d84 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/index.tsx @@ -93,7 +93,6 @@ export const ConversationSelectorSettings: React.FC = React.memo( (conversation) => conversation.title === conversationSelectorSettingsOption[0]?.label ) ?? conversationSelectorSettingsOption[0]?.label; - onConversationSelectionChange(newConversation); }, [onConversationSelectionChange, conversations] diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.test.tsx index 104b55987bc42..9897fc9dc3d97 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings.test.tsx @@ -11,8 +11,8 @@ import { ConversationSettings, ConversationSettingsProps } from './conversation_ import { TestProviders } from '../../../mock/test_providers/test_providers'; import { alertConvo, customConvo, welcomeConvo } from '../../../mock/conversation'; import { mockSystemPrompts } from '../../../mock/system_prompt'; -import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; import { mockConnectors } from '../../../mock/connectors'; +import { HttpSetup } from '@kbn/core/public'; const mockConvos = { '1234': { ...welcomeConvo, id: '1234' }, @@ -24,18 +24,19 @@ const onSelectedConversationChange = jest.fn(); const setConversationSettings = jest.fn(); const setConversationsSettingsBulkActions = jest.fn(); -const testProps = { +const testProps: ConversationSettingsProps = { allSystemPrompts: mockSystemPrompts, + assistantStreamingEnabled: false, + connectors: mockConnectors, conversationSettings: mockConvos, - defaultConnectorId: '123', - defaultProvider: OpenAiProviderType.OpenAi, - http: { basePath: { get: jest.fn() } }, + conversationsSettingsBulkActions: {}, + http: { basePath: { get: jest.fn() } } as unknown as HttpSetup, onSelectedConversationChange, selectedConversation: mockConvos['1234'], + setAssistantStreamingEnabled: jest.fn(), setConversationSettings, - conversationsSettingsBulkActions: {}, setConversationsSettingsBulkActions, -} as unknown as ConversationSettingsProps; +}; jest.mock('../../../connectorland/use_load_connectors', () => ({ useLoadConnectors: () => ({ @@ -113,7 +114,6 @@ jest.mock('../../../connectorland/connector_selector', () => ({ /> ), })); - describe('ConversationSettings', () => { beforeEach(() => { jest.clearAllMocks(); 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 1584a46ee687a..d929c132baf43 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 @@ -17,7 +17,6 @@ import React, { useMemo } from 'react'; import { HttpSetup } from '@kbn/core-http-browser'; -import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; import { PromptResponse } from '@kbn/elastic-assistant-common'; import { Conversation } from '../../../..'; import * as i18n from './translations'; @@ -33,7 +32,6 @@ import { useConversationChanged } from './use_conversation_changed'; import { getConversationApiConfig } from '../../use_conversation/helpers'; export interface ConversationSettingsProps { - actionTypeRegistry: ActionTypeRegistryContract; allSystemPrompts: PromptResponse[]; connectors?: AIConnector[]; conversationSettings: Record; @@ -128,6 +126,7 @@ export const ConversationSettings: React.FC = React.m selectedConversation={selectedConversationWithApiConfig} setConversationSettings={setConversationSettings} setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} + onSelectedConversationChange={onSelectedConversationChange} /> 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 cf8275203090b..85b6360b409be 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 @@ -36,6 +36,7 @@ export interface ConversationSettingsEditorProps { setConversationsSettingsBulkActions: React.Dispatch< React.SetStateAction >; + onSelectedConversationChange: (conversation?: Conversation) => void; } /** @@ -51,11 +52,11 @@ export const ConversationSettingsEditor: React.FC { const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ http, }); - const selectedSystemPrompt = useMemo(() => { return getDefaultSystemPrompt({ allSystemPrompts, conversation: selectedConversation }); }, [allSystemPrompts, selectedConversation]); @@ -95,13 +96,14 @@ export const ConversationSettingsEditor: React.FC @@ -290,7 +297,7 @@ export const ConversationSettingsEditor: React.FC } diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/translations.ts index 300176480ba1e..55852fc2a1bad 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/translations.ts @@ -52,14 +52,14 @@ export const SETTINGS_PROMPT_TITLE = i18n.translate( export const SETTINGS_PROMPT_HELP_TEXT_TITLE = i18n.translate( 'xpack.elasticAssistant.assistant.conversations.settings.promptHelpTextTitle', { - defaultMessage: 'Context provided as part of every conversation', + defaultMessage: 'Context provided as part of every conversation.', } ); export const STREAMING_TITLE = i18n.translate( 'xpack.elasticAssistant.assistant.conversations.settings.streamingTitle', { - defaultMessage: 'Streaming', + defaultMessage: 'STREAMING', } ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_changed.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_changed.tsx index 78209d95c75fa..f0bc2fbc44ac2 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_changed.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_changed.tsx @@ -84,7 +84,6 @@ export const useConversationChanged = ({ }; }); } - onSelectedConversationChange({ ...newSelectedConversation, id: newSelectedConversation.id || newSelectedConversation.title, 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 10608502e70d3..0f4cba3a29957 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 @@ -5,45 +5,45 @@ * 2.0. */ -import { EuiPanel, EuiSpacer, EuiConfirmModal, EuiInMemoryTable } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; +import { + EuiPanel, + EuiSpacer, + EuiConfirmModal, + EuiInMemoryTable, + EuiTitle, + EuiText, +} from '@elastic/eui'; +import React, { useCallback, useEffect, 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 { ConversationsBulkActions } from '../../api'; +import { + FetchConversationsResponse, + useFetchCurrentUserConversations, + useFetchPrompts, +} from '../../api'; import { useAssistantContext } from '../../../assistant_context'; import { useConversationDeleted } from '../conversation_settings/use_conversation_deleted'; import { useFlyoutModalVisibility } from '../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; import { Flyout } from '../../common/components/assistant_settings_management/flyout'; -import { CANCEL, DELETE } from '../../settings/translations'; +import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../settings/translations'; import { ConversationSettingsEditor } from '../conversation_settings/conversation_settings_editor'; import { useConversationChanged } from '../conversation_settings/use_conversation_changed'; import { CONVERSATION_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_context/constants'; import { useSessionPagination } from '../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { DEFAULT_PAGE_SIZE } from '../../settings/const'; +import { useSettingsUpdater } from '../../settings/use_settings_updater/use_settings_updater'; +import { mergeBaseWithPersistedConversations } from '../../helpers'; +import { AssistantSettingsBottomBar } from '../../settings/assistant_settings_bottom_bar'; interface Props { - allSystemPrompts: PromptResponse[]; - assistantStreamingEnabled: boolean; connectors: AIConnector[] | undefined; - conversationSettings: Record; - conversationsSettingsBulkActions: ConversationsBulkActions; - conversationsLoaded: boolean; defaultConnector?: AIConnector; - handleSave: (shouldRefetchConversation?: boolean) => void; + defaultSelectedConversation: Conversation; isDisabled?: boolean; - onCancelClick: () => void; - setAssistantStreamingEnabled: React.Dispatch>; - setConversationSettings: React.Dispatch>>; - setConversationsSettingsBulkActions: React.Dispatch< - React.SetStateAction - >; - selectedConversation: Conversation | undefined; - onSelectedConversationChange: (conversation?: Conversation) => void; } export const DEFAULT_TABLE_OPTIONS = { @@ -52,23 +52,112 @@ export const DEFAULT_TABLE_OPTIONS = { }; const ConversationSettingsManagementComponent: React.FC = ({ - allSystemPrompts, - assistantStreamingEnabled, connectors, defaultConnector, - conversationSettings, - conversationsSettingsBulkActions, - conversationsLoaded, - handleSave, + defaultSelectedConversation, isDisabled, - onSelectedConversationChange, - onCancelClick, - selectedConversation, - setAssistantStreamingEnabled, - setConversationSettings, - setConversationsSettingsBulkActions, }) => { - const { http, nameSpace, actionTypeRegistry } = useAssistantContext(); + const { + actionTypeRegistry, + assistantAvailability: { isAssistantEnabled }, + baseConversations, + http, + nameSpace, + toasts, + } = useAssistantContext(); + + const onFetchedConversations = useCallback( + (conversationsData: FetchConversationsResponse): Record => + mergeBaseWithPersistedConversations(baseConversations, conversationsData), + [baseConversations] + ); + + const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts(); + + const { + data: conversations, + isFetched: conversationsLoaded, + refetch: refetchConversations, + } = useFetchCurrentUserConversations({ + http, + onFetch: onFetchedConversations, + isAssistantEnabled, + }); + + const refetchAll = useCallback(() => { + refetchPrompts(); + refetchConversations(); + }, [refetchPrompts, refetchConversations]); + + const { + systemPromptSettings: allSystemPrompts, + assistantStreamingEnabled, + conversationSettings, + conversationsSettingsBulkActions, + resetSettings, + saveSettings, + setConversationSettings, + setConversationsSettingsBulkActions, + setUpdatedAssistantStreamingEnabled, + } = useSettingsUpdater(conversations, allPrompts, conversationsLoaded, promptsLoaded); + + const [hasPendingChanges, setHasPendingChanges] = useState(false); + + const handleSave = useCallback( + async (param?: { callback?: () => void }) => { + const isSuccess = await saveSettings(); + if (isSuccess) { + toasts?.addSuccess({ + iconType: 'check', + title: SETTINGS_UPDATED_TOAST_TITLE, + }); + setHasPendingChanges(false); + param?.callback?.(); + } else { + resetSettings(); + } + }, + [resetSettings, saveSettings, toasts] + ); + + const setAssistantStreamingEnabled = useCallback( + (value) => { + setHasPendingChanges(true); + setUpdatedAssistantStreamingEnabled(value); + }, + [setUpdatedAssistantStreamingEnabled] + ); + + const onSaveButtonClicked = useCallback(() => { + handleSave({ callback: refetchAll }); + }, [handleSave, refetchAll]); + + const onCancelClick = useCallback(() => { + resetSettings(); + setHasPendingChanges(false); + }, [resetSettings]); + + // Local state for saving previously selected items so tab switching is friendlier + // Conversation Selection State + const [selectedConversation, setSelectedConversation] = useState(() => { + return conversationSettings[defaultSelectedConversation.title]; + }); + + const onSelectedConversationChange = useCallback((conversation?: Conversation) => { + setSelectedConversation(conversation); + }, []); + + useEffect(() => { + if (selectedConversation != null) { + const newConversation = + conversationSettings[selectedConversation.id] || + conversationSettings[selectedConversation.title]; + setSelectedConversation( + // conversationSettings has title as key, sometime has id as key + newConversation + ); + } + }, [conversationSettings, selectedConversation]); const { isFlyoutOpen: editFlyoutVisible, @@ -124,12 +213,13 @@ const ConversationSettingsManagementComponent: React.FC = ({ return; } closeConfirmModal(); - handleSave(true); + handleSave({ callback: refetchAll }); setConversationsSettingsBulkActions({}); }, [ closeConfirmModal, conversationsSettingsBulkActions, handleSave, + refetchAll, setConversationsSettingsBulkActions, ]); @@ -162,9 +252,9 @@ const ConversationSettingsManagementComponent: React.FC = ({ const onSaveConfirmed = useCallback(() => { closeEditFlyout(); - handleSave(true); + handleSave({ callback: refetchAll }); setConversationsSettingsBulkActions({}); - }, [closeEditFlyout, handleSave, setConversationsSettingsBulkActions]); + }, [closeEditFlyout, handleSave, refetchAll, setConversationsSettingsBulkActions]); const columns = useMemo( () => @@ -191,12 +281,21 @@ const ConversationSettingsManagementComponent: React.FC = ({ return ( <> + +

{i18n.CONVERSATIONS_SETTINGS_TITLE}

+
- + + +

{i18n.CONVERSATIONS_LIST_TITLE}

+
+ + {i18n.CONVERSATIONS_LIST_DESCRIPTION} + = ({ onSaveConfirmed={onSaveConfirmed} onSaveCancelled={onSaveCancelled} title={selectedConversation?.title ?? i18n.CONVERSATIONS_FLYOUT_DEFAULT_TITLE} + saveButtonDisabled={ + selectedConversation?.title == null || selectedConversation?.title === '' + } > = ({ selectedConversation={selectedConversation} setConversationSettings={setConversationSettings} setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} + onSelectedConversationChange={onSelectedConversationChange} /> )} @@ -240,6 +343,11 @@ const ConversationSettingsManagementComponent: React.FC = ({

)} + ); }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/translations.ts index 5761ba8e6e99f..260f095f9a128 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/translations.ts @@ -7,10 +7,31 @@ import { i18n } from '@kbn/i18n'; -export const CONVERSATIONS_TABLE_COLUMN_NAME = i18n.translate( - 'xpack.elasticAssistant.assistant.conversationSettings.column.name', +export const CONVERSATIONS_SETTINGS_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.title', { - defaultMessage: 'Name', + defaultMessage: 'Settings', + } +); + +export const CONVERSATIONS_LIST_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.list.title', + { + defaultMessage: 'Conversation list', + } +); + +export const CONVERSATIONS_LIST_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.list.description', + { + defaultMessage: 'Create and manage conversations with the Elastic AI Assistant.', + } +); + +export const CONVERSATIONS_TABLE_COLUMN_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.column.Title', + { + defaultMessage: 'Title', } ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.test.tsx index 465ffa792fb0b..1c293cf061c29 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.test.tsx @@ -14,8 +14,6 @@ import { alertConvo, welcomeConvo, customConvo } from '../../../mock/conversatio import { mockActionTypes, mockConnectors } from '../../../mock/connectors'; import { mockSystemPrompts } from '../../../mock/system_prompt'; import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; -import * as i18n from './translations'; -import { EuiTableFieldDataColumnType } from '@elastic/eui'; const mockActionTypeRegistry: ActionTypeRegistryContract = { has: jest @@ -42,17 +40,11 @@ describe('useConversationsTable', () => { expect(columns).toHaveLength(5); - expect(columns[0].name).toBe(i18n.CONVERSATIONS_TABLE_COLUMN_NAME); - expect((columns[1] as EuiTableFieldDataColumnType).field).toBe( - 'systemPromptTitle' - ); - expect((columns[2] as EuiTableFieldDataColumnType).field).toBe( - 'connectorTypeTitle' - ); - expect((columns[3] as EuiTableFieldDataColumnType).field).toBe( - 'updatedAt' - ); - expect(columns[4].name).toBe(i18n.CONVERSATIONS_TABLE_COLUMN_ACTIONS); + expect(columns[0].name).toBe('Title'); + expect(columns[1].name).toBe('System prompt'); + expect(columns[2].name).toBe('Connector'); + expect(columns[3].name).toBe('Date updated'); + expect(columns[4].name).toBe('Actions'); }); it('should return a list of conversations', () => { @@ -78,14 +70,14 @@ describe('useConversationsTable', () => { expect(conversationsList[0].title).toBe(alertConvo.title); expect(conversationsList[0].connectorTypeTitle).toBe('OpenAI'); - expect(conversationsList[0].systemPromptTitle).toBe('Mock system prompt'); + expect(conversationsList[0].systemPromptTitle).toBeUndefined(); expect(conversationsList[1].title).toBe(welcomeConvo.title); expect(conversationsList[1].connectorTypeTitle).toBe('OpenAI'); - expect(conversationsList[1].systemPromptTitle).toBe('Mock system prompt'); + expect(conversationsList[1].systemPromptTitle).toBeUndefined(); expect(conversationsList[2].title).toBe(customConvo.title); expect(conversationsList[2].connectorTypeTitle).toBe('OpenAI'); - expect(conversationsList[2].systemPromptTitle).toBe('Mock system prompt'); + expect(conversationsList[2].systemPromptTitle).toBeUndefined(); }); }); 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 446fb33ebb9e9..0d253be5d2651 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 @@ -15,12 +15,9 @@ 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 { - getConversationApiConfig, - getInitialDefaultSystemPrompt, -} from '../../use_conversation/helpers'; +import { getConversationApiConfig } from '../../use_conversation/helpers'; import * as i18n from './translations'; -import { RowActions } from '../../common/components/assistant_settings_management/row_actions'; +import { useInlineActions } from '../../common/components/assistant_settings_management/inline_actions'; const emptyConversations = {}; @@ -38,6 +35,7 @@ export type ConversationTableItem = Conversation & { }; export const useConversationsTable = () => { + const getActions = useInlineActions(); const getColumns = useCallback( ({ onDeleteActionClicked, @@ -45,7 +43,7 @@ export const useConversationsTable = () => { }): Array> => { return [ { - name: i18n.CONVERSATIONS_TABLE_COLUMN_NAME, + name: i18n.CONVERSATIONS_TABLE_COLUMN_TITLE, render: (conversation: ConversationTableItem) => ( onEditActionClicked(conversation)}> {conversation.title} @@ -87,24 +85,16 @@ export const useConversationsTable = () => { sortable: true, }, { - name: i18n.CONVERSATIONS_TABLE_COLUMN_ACTIONS, width: '120px', align: 'center', - render: (conversation: ConversationTableItem) => { - const isDeletable = !conversation.isDefault; - return ( - - rowItem={conversation} - onDelete={isDeletable ? onDeleteActionClicked : undefined} - onEdit={onEditActionClicked} - isDeletable={isDeletable} - /> - ); - }, + ...getActions({ + onDelete: onDeleteActionClicked, + onEdit: onEditActionClicked, + }), }, ]; }, - [] + [getActions] ); const getConversationsList = useCallback( ({ @@ -129,16 +119,8 @@ export const useConversationsTable = () => { const systemPrompt: PromptResponse | undefined = allSystemPrompts.find( ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId ); - const defaultSystemPrompt = getInitialDefaultSystemPrompt({ - allSystemPrompts, - conversation, - }); - const systemPromptTitle = - systemPrompt?.name || - systemPrompt?.id || - defaultSystemPrompt?.name || - defaultSystemPrompt?.id; + const systemPromptTitle = systemPrompt?.name || systemPrompt?.id; return { ...conversation, 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 0f10cf6d3063f..fbe0f40320c4c 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 @@ -43,6 +43,9 @@ export interface Props { isSettingsModalVisible: boolean; setIsSettingsModalVisible: React.Dispatch>; onSystemPromptSelectionChange?: (promptId: string | undefined) => void; + onSelectedConversationChange?: (result: Conversation) => void; + setConversationSettings?: React.Dispatch>>; + setConversationsSettingsBulkActions?: React.Dispatch>; } const ADD_NEW_SYSTEM_PROMPT = 'ADD_NEW_SYSTEM_PROMPT'; @@ -59,6 +62,9 @@ const SelectSystemPromptComponent: React.FC = ({ isSettingsModalVisible, onSystemPromptSelectionChange, setIsSettingsModalVisible, + onSelectedConversationChange, + setConversationSettings, + setConversationsSettingsBulkActions, }) => { const { setSelectedSettingsTab } = useAssistantContext(); const { setApiConfig } = useConversation(); @@ -74,15 +80,16 @@ const SelectSystemPromptComponent: React.FC = ({ // Write the selected system prompt to the conversation config const setSelectedSystemPrompt = useCallback( - (promptId?: string) => { + async (promptId?: string) => { if (conversation && conversation.apiConfig) { - setApiConfig({ + const result = await setApiConfig({ conversation, apiConfig: { ...conversation.apiConfig, defaultSystemPromptId: promptId, }, }); + return result; } }, [conversation, setApiConfig] @@ -112,7 +119,7 @@ const SelectSystemPromptComponent: React.FC = ({ const options = useMemo(() => getOptions({ prompts: allSystemPrompts }), [allSystemPrompts]); const onChange = useCallback( - (selectedSystemPromptId) => { + async (selectedSystemPromptId) => { if (selectedSystemPromptId === ADD_NEW_SYSTEM_PROMPT) { setIsSettingsModalVisible(true); setSelectedSettingsTab(SYSTEM_PROMPTS_TAB); @@ -122,10 +129,30 @@ const SelectSystemPromptComponent: React.FC = ({ if (onSystemPromptSelectionChange != null) { onSystemPromptSelectionChange(selectedSystemPromptId); } - setSelectedSystemPrompt(selectedSystemPromptId); + const result = await setSelectedSystemPrompt(selectedSystemPromptId); + if (result) { + setConversationSettings?.((prev: Record) => { + const newConversationsSettings = Object.entries(prev).reduce< + Record + >((acc, [key, convo]) => { + if (result.title === convo.title) { + acc[result.id] = result; + } else { + acc[key] = convo; + } + return acc; + }, {}); + return newConversationsSettings; + }); + onSelectedConversationChange?.(result); + setConversationsSettingsBulkActions?.({}); + } }, [ + onSelectedConversationChange, onSystemPromptSelectionChange, + setConversationSettings, + setConversationsSettingsBulkActions, setIsSettingsModalVisible, setSelectedSettingsTab, setSelectedSystemPrompt, 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 fecb2ed401a4b..be072790bf7fc 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 @@ -32,7 +32,10 @@ import { TEST_IDS } from '../../../constants'; import { ConversationsBulkActions } from '../../../api'; import { getSelectedConversations } from '../system_prompt_settings_management/utils'; import { useSystemPromptEditor } from './use_system_prompt_editor'; -import { getConversationApiConfig } from '../../../use_conversation/helpers'; +import { + getConversationApiConfig, + getFallbackDefaultSystemPrompt, +} from '../../../use_conversation/helpers'; interface Props { connectors: AIConnector[] | undefined; @@ -99,7 +102,7 @@ export const SystemPromptEditorComponent: React.FC = ({ }); const existingPrompt = systemPromptSettings.find((sp) => sp.id === selectedSystemPrompt.id); if (existingPrompt) { - setPromptsBulkActions({ + const newBulkActions = { ...promptsBulkActions, ...(selectedSystemPrompt.name !== selectedSystemPrompt.id ? { @@ -124,7 +127,8 @@ export const SystemPromptEditorComponent: React.FC = ({ }, ], }), - }); + }; + setPromptsBulkActions(newBulkActions); } } }, @@ -159,20 +163,15 @@ export const SystemPromptEditorComponent: React.FC = ({ const selectedConversations = useMemo(() => { return selectedSystemPrompt != null - ? getSelectedConversations( - systemPromptSettings, - conversationsWithApiConfig, - selectedSystemPrompt.id - ) + ? getSelectedConversations(conversationsWithApiConfig, selectedSystemPrompt.id) : []; - }, [conversationsWithApiConfig, selectedSystemPrompt, systemPromptSettings]); + }, [conversationsWithApiConfig, selectedSystemPrompt]); const handleConversationSelectionChange = useCallback( (currentPromptConversations: Conversation[]) => { const currentPromptConversationTitles = currentPromptConversations.map( (convo) => convo.title ); - const getDefaultSystemPromptId = (convo: Conversation) => currentPromptConversationTitles.includes(convo.title) ? selectedSystemPrompt?.id @@ -180,7 +179,7 @@ export const SystemPromptEditorComponent: React.FC = ({ ? // remove the default System Prompt if it is assigned to a conversation // but that conversation is not in the currentPromptConversationList // This means conversation was removed in the current transaction - systemPromptSettings?.[0].id + undefined : // leave it as it is .. if that conversation was neither added nor removed. convo.apiConfig?.defaultSystemPromptId; @@ -194,23 +193,26 @@ export const SystemPromptEditorComponent: React.FC = ({ * through each conversation adds/removed the selected prompt on each conversation. * * */ - Object.values(prev).map((convo) => ({ - ...convo, - ...(convo.apiConfig - ? { - apiConfig: { - ...convo.apiConfig, - defaultSystemPromptId: getDefaultSystemPromptId(convo), - }, - } - : { - apiConfig: { - defaultSystemPromptId: getDefaultSystemPromptId(convo), - connectorId: defaultConnector?.id ?? '', - actionTypeId: defaultConnector?.actionTypeId ?? '', - }, - }), - })) + Object.values(prev).map((convo) => { + const newConversationSetting = { + ...convo, + ...(convo.apiConfig + ? { + apiConfig: { + ...convo.apiConfig, + defaultSystemPromptId: getDefaultSystemPromptId(convo), + }, + } + : { + apiConfig: { + defaultSystemPromptId: getDefaultSystemPromptId(convo), + connectorId: defaultConnector?.id ?? '', + actionTypeId: defaultConnector?.actionTypeId ?? '', + }, + }), + }; + return newConversationSetting; + }) ) ); @@ -226,7 +228,9 @@ export const SystemPromptEditorComponent: React.FC = ({ conversation: convo, defaultConnector, }).apiConfig, - defaultSystemPromptId: getDefaultSystemPromptId(convo), + defaultSystemPromptId: + getDefaultSystemPromptId(convo) ?? + getFallbackDefaultSystemPrompt({ allSystemPrompts: systemPromptSettings })?.id, }, }; } @@ -266,7 +270,6 @@ export const SystemPromptEditorComponent: React.FC = ({ ...updateOperation, }; }); - setConversationsSettingsBulkActions(updatedConversationsSettingsBulkActions); } }, @@ -291,6 +294,21 @@ export const SystemPromptEditorComponent: React.FC = ({ const handleNewConversationDefaultChange = useCallback( (e) => { const isChecked = e.target.checked; + const defaultNewSystemPrompts = systemPromptSettings.filter( + (p) => p.isNewConversationDefault + ); + + const shouldCreateNewDefaultSystemPrompts = (sp?: { name: string; id: string }) => + sp?.name === sp?.id; // Prompts before preserving have the SAME name and id + + const shouldUpdateNewDefaultSystemPrompts = (sp?: { name: string; id: string }) => + sp?.name !== sp?.id; // Prompts after preserving have different name and id + + const shouldCreateSelectedSystemPrompt = + selectedSystemPrompt?.name === selectedSystemPrompt?.id; + + const shouldUpdateSelectedSystemPrompt = + selectedSystemPrompt?.name !== selectedSystemPrompt?.id; if (selectedSystemPrompt != null) { setUpdatedSystemPromptSettings((prev) => { @@ -301,39 +319,60 @@ 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, - }, - ], - }), + // Update and Create prompts can happen at the same time, as we have to unchecked the previous default prompt + // Each prompt can be updated or created + setPromptsBulkActions(() => { + const newBulkActions = { + update: [ + ...defaultNewSystemPrompts + .filter( + (p) => p.id !== selectedSystemPrompt.id && shouldUpdateNewDefaultSystemPrompts(p) + ) + .map((p) => ({ + ...p, + isNewConversationDefault: false, + })), + + ...(shouldUpdateSelectedSystemPrompt + ? [ + { + ...selectedSystemPrompt, + isNewConversationDefault: isChecked, + }, + ] + : []), + ], + create: [ + ...defaultNewSystemPrompts + .filter( + (p) => + p.name !== selectedSystemPrompt.name && shouldCreateNewDefaultSystemPrompts(p) + ) + .map((p) => ({ + ...p, + isNewConversationDefault: false, + })), + + ...(shouldCreateSelectedSystemPrompt + ? [ + { + ...selectedSystemPrompt, + isNewConversationDefault: isChecked, + }, + ] + : []), + ], + }; + + return newBulkActions; }); } }, [ - promptsBulkActions, selectedSystemPrompt, setPromptsBulkActions, setUpdatedSystemPromptSettings, + systemPromptSettings, ] ); 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 ae5fce935cfe3..4e97ca36f1198 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 @@ -87,6 +87,7 @@ export const SystemPromptSelector: React.FC = React.memo( ? undefined : systemPrompts.find((sp) => sp.name === systemPromptSelectorOption[0]?.label) ?? systemPromptSelectorOption[0]?.label; + onSystemPromptSelectionChange(newSystemPrompt); }, [onSystemPromptSelectionChange, resetSettings, systemPrompts] diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/translations.ts index 06544b98a68bd..92837d970864c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/translations.ts @@ -65,7 +65,7 @@ export const SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION = i18n.translate( export const SYSTEM_PROMPT_DEFAULT_CONVERSATIONS_HELP_TEXT = i18n.translate( 'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultConversationsHelpText', { - defaultMessage: 'Conversations that should use this System Prompt by default', + defaultMessage: 'Conversations that should use this System Prompt by default.', } ); 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 ec77de113b5d9..72e193142e998 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 @@ -52,14 +52,17 @@ export const useSystemPromptEditor = ({ }); if (isNew) { - setPromptsBulkActions({ - ...promptsBulkActions, - create: [ - ...(promptsBulkActions.create ?? []), - { - ...newSelectedSystemPrompt, - }, - ], + setPromptsBulkActions((prev) => { + const newBulkActions = { + ...prev, + create: [ + ...(promptsBulkActions.create ?? []), + { + ...newSelectedSystemPrompt, + }, + ], + }; + return newBulkActions; }); } } 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 14b6ecb868ead..69ec81547912e 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 @@ -13,23 +13,28 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, + EuiText, } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { PromptResponse } from '@kbn/elastic-assistant-common'; import { - PromptResponse, - PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody, -} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen'; -import { Conversation, ConversationsBulkActions, useAssistantContext } from '../../../../..'; + Conversation, + mergeBaseWithPersistedConversations, + useAssistantContext, + useFetchCurrentUserConversations, +} from '../../../../..'; import { SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../../assistant_context/constants'; import { AIConnector } from '../../../../connectorland/connector_selector'; +import { FetchConversationsResponse, useFetchPrompts } from '../../../api'; import { Flyout } from '../../../common/components/assistant_settings_management/flyout'; import { useFlyoutModalVisibility } from '../../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; import { DEFAULT_TABLE_OPTIONS, useSessionPagination, } from '../../../common/components/assistant_settings_management/pagination/use_session_pagination'; -import { CANCEL, DELETE } from '../../../settings/translations'; +import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../../settings/translations'; +import { useSettingsUpdater } from '../../../settings/use_settings_updater/use_settings_updater'; 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'; @@ -38,42 +43,42 @@ import { useSystemPromptTable } from './use_system_prompt_table'; interface Props { connectors: AIConnector[] | undefined; - conversationSettings: Record; - conversationsSettingsBulkActions: ConversationsBulkActions; - onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void; - selectedSystemPrompt: PromptResponse | undefined; - setUpdatedSystemPromptSettings: React.Dispatch>; - setConversationSettings: React.Dispatch>>; - systemPromptSettings: PromptResponse[]; - setConversationsSettingsBulkActions: React.Dispatch< - React.SetStateAction - >; defaultConnector?: AIConnector; - handleSave: (shouldRefetchConversation?: boolean) => void; - onCancelClick: () => void; - resetSettings: () => void; - promptsBulkActions: PromptsPerformBulkActionRequestBody; - setPromptsBulkActions: React.Dispatch>; } -const SystemPromptSettingsManagementComponent = ({ - connectors, - conversationSettings, - onSelectedSystemPromptChange, - setUpdatedSystemPromptSettings, - setConversationSettings, - selectedSystemPrompt, - systemPromptSettings, - conversationsSettingsBulkActions, - setConversationsSettingsBulkActions, - defaultConnector, - handleSave, - onCancelClick, - resetSettings, - promptsBulkActions, - setPromptsBulkActions, -}: Props) => { - const { nameSpace } = useAssistantContext(); +const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector }: Props) => { + const { + nameSpace, + http, + assistantAvailability: { isAssistantEnabled }, + baseConversations, + toasts, + } = useAssistantContext(); + + const onFetchedConversations = useCallback( + (conversationsData: FetchConversationsResponse): Record => + mergeBaseWithPersistedConversations(baseConversations, conversationsData), + [baseConversations] + ); + + const { data: allPrompts, refetch: refetchPrompts, isFetched: promptsLoaded } = useFetchPrompts(); + + const { + data: conversations, + isFetched: conversationsLoaded, + refetch: refetchConversations, + } = useFetchCurrentUserConversations({ + http, + onFetch: onFetchedConversations, + isAssistantEnabled, + }); + + const refetchAll = useCallback(() => { + refetchPrompts(); + refetchConversations(); + }, [refetchPrompts, refetchConversations]); + + const isTableLoading = !conversationsLoaded || !promptsLoaded; const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); const { isFlyoutOpen: deleteConfirmModalVisibility, @@ -82,6 +87,48 @@ const SystemPromptSettingsManagementComponent = ({ } = useFlyoutModalVisibility(); const [deletedPrompt, setDeletedPrompt] = useState(); + const { + conversationSettings, + setConversationSettings, + systemPromptSettings, + setUpdatedSystemPromptSettings, + conversationsSettingsBulkActions, + setConversationsSettingsBulkActions, + resetSettings, + saveSettings, + promptsBulkActions, + setPromptsBulkActions, + } = useSettingsUpdater(conversations, allPrompts, conversationsLoaded, promptsLoaded); + + // System Prompt Selection State + const [selectedSystemPrompt, setSelectedSystemPrompt] = useState(); + + const onSelectedSystemPromptChange = useCallback((systemPrompt?: PromptResponse) => { + setSelectedSystemPrompt(systemPrompt); + }, []); + + useEffect(() => { + if (selectedSystemPrompt != null) { + setSelectedSystemPrompt(systemPromptSettings.find((p) => p.id === selectedSystemPrompt.id)); + } + }, [selectedSystemPrompt, systemPromptSettings]); + + const handleSave = useCallback( + async (param?: { callback?: () => void }) => { + await saveSettings(); + toasts?.addSuccess({ + iconType: 'check', + title: SETTINGS_UPDATED_TOAST_TITLE, + }); + param?.callback?.(); + }, + [saveSettings, toasts] + ); + + const onCancelClick = useCallback(() => { + resetSettings(); + }, [resetSettings]); + const onCreate = useCallback(() => { onSelectedSystemPromptChange({ id: '', @@ -124,9 +171,9 @@ const SystemPromptSettingsManagementComponent = ({ const onDeleteConfirmed = useCallback(() => { closeConfirmModal(); - handleSave(true); + handleSave({ callback: refetchAll }); setConversationsSettingsBulkActions({}); - }, [closeConfirmModal, handleSave, setConversationsSettingsBulkActions]); + }, [closeConfirmModal, handleSave, refetchAll, setConversationsSettingsBulkActions]); const onSaveCancelled = useCallback(() => { closeFlyout(); @@ -135,9 +182,9 @@ const SystemPromptSettingsManagementComponent = ({ const onSaveConfirmed = useCallback(() => { closeFlyout(); - handleSave(true); + handleSave({ callback: refetchAll }); setConversationsSettingsBulkActions({}); - }, [closeFlyout, handleSave, setConversationsSettingsBulkActions]); + }, [closeFlyout, handleSave, refetchAll, setConversationsSettingsBulkActions]); const confirmationTitle = useMemo( () => @@ -156,8 +203,9 @@ const SystemPromptSettingsManagementComponent = ({ }); const columns = useMemo( - () => getColumns({ onEditActionClicked, onDeleteActionClicked }), - [getColumns, onEditActionClicked, onDeleteActionClicked] + () => + getColumns({ isActionsDisabled: isTableLoading, onEditActionClicked, onDeleteActionClicked }), + [getColumns, isTableLoading, onEditActionClicked, onDeleteActionClicked] ); const systemPromptListItems = useMemo( () => @@ -173,10 +221,13 @@ const SystemPromptSettingsManagementComponent = ({ return ( <> - + + + {i18n.SYSTEM_PROMPTS_TABLE_SETTINGS_DESCRIPTION} + - - {SETTINGS_TITLE} + + {i18n.CREATE_SYSTEM_PROMPT_LABEL} @@ -196,6 +247,7 @@ const SystemPromptSettingsManagementComponent = ({ onClose={onSaveCancelled} onSaveCancelled={onSaveCancelled} onSaveConfirmed={onSaveConfirmed} + saveButtonDisabled={selectedSystemPrompt?.name == null || selectedSystemPrompt?.name === ''} > i18n.translate( 'xpack.elasticAssistant.assistant.promptEditor.modal.deleteSystemPromptConfirmationTitle', @@ -56,3 +64,10 @@ export const DELETE_SYSTEM_PROMPT_MODAL_DESCRIPTION = i18n.translate( defaultMessage: 'You cannot recover the prompt once deleted', } ); + +export const CREATE_SYSTEM_PROMPT_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.promptEditor.createSystemPromptLabel', + { + defaultMessage: 'System Prompt', + } +); 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 48d3232f0ae38..e677001e2d38b 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 @@ -65,14 +65,16 @@ describe('useSystemPromptTable', () => { const onEditActionClicked = jest.fn(); const onDeleteActionClicked = jest.fn(); const columns = result.current.getColumns({ + isActionsDisabled: false, onEditActionClicked, onDeleteActionClicked, }); - expect(columns).toHaveLength(3); + expect(columns).toHaveLength(4); expect(columns[0].name).toBe('Name'); expect(columns[1].name).toBe('Default conversations'); - expect(columns[2].name).toBe('Actions'); + expect(columns[2].name).toBe('Date updated'); + expect(columns[3].name).toBe('Actions'); }); }); 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 46e082b86f2c0..d2b63e9abdbd5 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 @@ -4,34 +4,31 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiBasicTableColumn, EuiIcon, EuiLink } from '@elastic/eui'; +import { EuiBadge, EuiBasicTableColumn, EuiIcon, EuiLink } from '@elastic/eui'; import React, { useCallback } from 'react'; +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 { BadgesColumn } from '../../../common/components/assistant_settings_management/badges'; -import { RowActions } from '../../../common/components/assistant_settings_management/row_actions'; -import { - getConversationApiConfig, - getInitialDefaultSystemPrompt, -} from '../../../use_conversation/helpers'; +import { useInlineActions } from '../../../common/components/assistant_settings_management/inline_actions'; +import { getConversationApiConfig } from '../../../use_conversation/helpers'; import { SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION } from '../system_prompt_modal/translations'; import * as i18n from './translations'; import { getSelectedConversations } from './utils'; -type ConversationsWithSystemPrompt = Record< - string, - Conversation & { systemPrompt: PromptResponse | undefined } ->; - type SystemPromptTableItem = PromptResponse & { defaultConversations: string[] }; export const useSystemPromptTable = () => { + const getActions = useInlineActions(); const getColumns = useCallback( ({ + isActionsDisabled, onEditActionClicked, onDeleteActionClicked, }: { + isActionsDisabled: boolean; onEditActionClicked: (prompt: SystemPromptTableItem) => void; onDeleteActionClicked: (prompt: SystemPromptTableItem) => void; }): Array> => [ @@ -41,7 +38,7 @@ export const useSystemPromptTable = () => { truncateText: { lines: 3 }, render: (prompt: SystemPromptTableItem) => prompt?.name ? ( - onEditActionClicked(prompt)}> + onEditActionClicked(prompt)} disabled={isActionsDisabled}> {prompt?.name} {prompt.isNewConversationDefault && ( { ), }, - /* TODO: enable when createdAt is added { align: 'left', - field: 'createdAt', - name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_CREATED_ON, + field: 'updatedAt', + name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DATE_UPDATED, + render: (updatedAt: SystemPromptTableItem['updatedAt']) => + updatedAt ? ( + + + + ) : null, + sortable: true, }, - */ { align: 'center', - name: 'Actions', width: '120px', - render: (prompt: SystemPromptTableItem) => { - const isDeletable = !prompt.isDefault; - return ( - - rowItem={prompt} - onEdit={onEditActionClicked} - onDelete={isDeletable ? onDeleteActionClicked : undefined} - isDeletable={isDeletable} - /> - ); - }, + ...getActions({ + onDelete: onDeleteActionClicked, + onEdit: onEditActionClicked, + }), }, ], - [] + [getActions] ); const getSystemPromptsList = ({ @@ -99,14 +98,9 @@ export const useSystemPromptTable = () => { defaultConnector: AIConnector | undefined; systemPromptSettings: PromptResponse[]; }): SystemPromptTableItem[] => { - const conversationsWithApiConfig = Object.entries( - conversationSettings - ).reduce((acc, [key, conversation]) => { - const defaultSystemPrompt = getInitialDefaultSystemPrompt({ - allSystemPrompts: systemPromptSettings, - conversation, - }); - + const conversationsWithApiConfig = Object.entries(conversationSettings).reduce< + Record + >((acc, [key, conversation]) => { acc[key] = { ...conversation, ...getConversationApiConfig({ @@ -115,18 +109,19 @@ export const useSystemPromptTable = () => { conversation, defaultConnector, }), - systemPrompt: defaultSystemPrompt, }; + return acc; }, {}); return systemPromptSettings.map((systemPrompt) => { + const defaultConversations = getSelectedConversations( + conversationsWithApiConfig, + systemPrompt?.id + ).map(({ title }) => title); + return { ...systemPrompt, - defaultConversations: getSelectedConversations( - systemPromptSettings, - conversationsWithApiConfig, - systemPrompt?.id - ).map(({ title }) => title), + defaultConversations, }; }); }; 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 9fbfb3a8782e0..4ea3943598501 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 @@ -5,11 +5,9 @@ * 2.0. */ import { ProviderEnum } from '@kbn/elastic-assistant-common'; -import { mockSystemPrompts } from '../../../../mock/system_prompt'; 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 = { '8f1e3218-0b02-480a-8791-78c1ed5f3708': { timestamp: '2024-06-25T12:33:26.779Z', @@ -48,22 +46,14 @@ describe('getSelectedConversations', () => { test('should return selected conversations', () => { const systemPromptId = 'mock-system-prompt-1'; - const conversations = getSelectedConversations( - allSystemPrompts, - conversationSettings, - systemPromptId - ); + const conversations = getSelectedConversations(conversationSettings, systemPromptId); expect(conversations).toEqual(Object.values(conversationSettings)); }); test('should return empty array if no conversations are selected', () => { const systemPromptId = 'ooo'; - const conversations = getSelectedConversations( - allSystemPrompts, - conversationSettings, - systemPromptId - ); + const conversations = getSelectedConversations(conversationSettings, systemPromptId); expect(conversations).toEqual([]); }); 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 fd01b8eb318a6..895e175814d91 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,18 +5,13 @@ * 2.0. */ -import { PromptResponse } from '@kbn/elastic-assistant-common'; import { Conversation } from '../../../../assistant_context/types'; export const getSelectedConversations = ( - allSystemPrompts: PromptResponse[], conversationSettings: Record, systemPromptId: string ) => { - return Object.values(conversationSettings).filter((conversation) => { - const conversationSystemPrompt = allSystemPrompts.find( - (prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId - ); - return conversationSystemPrompt?.id === systemPromptId; - }); + return Object.values(conversationSettings).filter( + (conversation) => conversation?.apiConfig?.defaultSystemPromptId === systemPromptId + ); }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/translations.ts index dd134dc034574..50646560bf489 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/translations.ts @@ -67,7 +67,7 @@ export const QUICK_PROMPT_CONTEXTS_HELP_TEXT = i18n.translate( 'xpack.elasticAssistant.assistant.quickPrompts.settings.contextsHelpText', { defaultMessage: - 'Select the Prompt Contexts that this Quick Prompt will be available for. Selecting none will make this Quick Prompt available at all times.', + 'Select where this Quick Prompt will appear. Selecting none will make this prompt appear everywhere.', } ); 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 ac93161d35c17..0f7b6df8d1893 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 @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiButton, EuiConfirmModal, @@ -13,16 +13,14 @@ import { EuiInMemoryTable, EuiPanel, EuiSpacer, + EuiText, } from '@elastic/eui'; -import { - PromptResponse, - PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody, -} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen'; +import { PromptResponse } 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'; import { Flyout } from '../../common/components/assistant_settings_management/flyout'; -import { CANCEL, DELETE } from '../../settings/translations'; +import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../settings/translations'; import { useQuickPromptEditor } from '../quick_prompt_settings/use_quick_prompt_editor'; import { useQuickPromptTable } from './use_quick_prompt_table'; import { @@ -31,31 +29,58 @@ import { } from '../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { QUICK_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_context/constants'; import { useAssistantContext } from '../../../assistant_context'; +import { + DEFAULT_CONVERSATIONS, + useSettingsUpdater, +} from '../../settings/use_settings_updater/use_settings_updater'; +import { useFetchPrompts } from '../../api'; -interface Props { - handleSave: (shouldRefetchConversation?: boolean) => void; - onCancelClick: () => void; - onSelectedQuickPromptChange: (quickPrompt?: PromptResponse) => void; - quickPromptSettings: PromptResponse[]; - resetSettings?: () => void; - selectedQuickPrompt: PromptResponse | undefined; - setUpdatedQuickPromptSettings: React.Dispatch>; - promptsBulkActions: PromptsPerformBulkActionRequestBody; - setPromptsBulkActions: React.Dispatch>; -} - -const QuickPromptSettingsManagementComponent = ({ - handleSave, - onCancelClick, - onSelectedQuickPromptChange, - quickPromptSettings, - resetSettings, - selectedQuickPrompt, - setUpdatedQuickPromptSettings, - promptsBulkActions, - setPromptsBulkActions, -}: Props) => { - const { nameSpace, basePromptContexts } = useAssistantContext(); +const QuickPromptSettingsManagementComponent = () => { + const { nameSpace, basePromptContexts, toasts } = useAssistantContext(); + + const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts(); + + const { + promptsBulkActions, + quickPromptSettings, + resetSettings, + saveSettings, + setPromptsBulkActions, + setUpdatedQuickPromptSettings, + } = useSettingsUpdater( + DEFAULT_CONVERSATIONS, // Quick Prompt settings do not require conversations + allPrompts, + false, // Quick Prompt settings do not require conversations + promptsLoaded + ); + + // Quick Prompt Selection State + const [selectedQuickPrompt, setSelectedQuickPrompt] = useState(); + const onSelectedQuickPromptChange = useCallback((quickPrompt?: PromptResponse) => { + setSelectedQuickPrompt(quickPrompt); + }, []); + + useEffect(() => { + if (selectedQuickPrompt != null) { + setSelectedQuickPrompt(quickPromptSettings.find((q) => q.name === selectedQuickPrompt.name)); + } + }, [quickPromptSettings, selectedQuickPrompt]); + + const handleSave = useCallback( + async (param?: { callback?: () => void }) => { + await saveSettings(); + toasts?.addSuccess({ + iconType: 'check', + title: SETTINGS_UPDATED_TOAST_TITLE, + }); + param?.callback?.(); + }, + [saveSettings, toasts] + ); + + const onCancelClick = useCallback(() => { + resetSettings(); + }, [resetSettings]); const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); const [deletedQuickPrompt, setDeletedQuickPrompt] = useState(); @@ -96,9 +121,9 @@ const QuickPromptSettingsManagementComponent = ({ }, [closeConfirmModal, onCancelClick]); const onDeleteConfirmed = useCallback(() => { - handleSave(); + handleSave({ callback: refetchPrompts }); closeConfirmModal(); - }, [closeConfirmModal, handleSave]); + }, [closeConfirmModal, handleSave, refetchPrompts]); const onCreate = useCallback(() => { onSelectedQuickPromptChange(); @@ -112,13 +137,14 @@ const QuickPromptSettingsManagementComponent = ({ }, [closeFlyout, onSelectedQuickPromptChange, onCancelClick]); const onSaveConfirmed = useCallback(() => { - handleSave(); + handleSave({ callback: refetchPrompts }); onSelectedQuickPromptChange(); closeFlyout(); - }, [closeFlyout, handleSave, onSelectedQuickPromptChange]); + }, [closeFlyout, handleSave, onSelectedQuickPromptChange, refetchPrompts]); const { getColumns } = useQuickPromptTable(); const columns = getColumns({ + isActionsDisabled: !promptsLoaded, basePromptContexts, onEditActionClicked, onDeleteActionClicked, @@ -141,9 +167,12 @@ const QuickPromptSettingsManagementComponent = ({ return ( <> - + + + {i18n.QUICK_PROMPTS_DESCRIPTION} + - + {i18n.QUICK_PROMPTS_TABLE_CREATE_BUTTON_TITLE} @@ -163,6 +192,7 @@ const QuickPromptSettingsManagementComponent = ({ onClose={onSaveCancelled} onSaveCancelled={onSaveCancelled} onSaveConfirmed={onSaveConfirmed} + saveButtonDisabled={selectedQuickPrompt?.name == null || selectedQuickPrompt?.name === ''} > { const { result } = renderHook(() => useQuickPromptTable()); + const props = { + isActionsDisabled: false, + basePromptContexts: mockPromptContexts, + onEditActionClicked: mockOnEditActionClicked, + onDeleteActionClicked: mockOnDeleteActionClicked, + }; describe('getColumns', () => { beforeEach(() => { jest.clearAllMocks(); }); it('should return columns with correct render functions', () => { - const columns = result.current.getColumns({ - basePromptContexts: mockPromptContexts, - onEditActionClicked: mockOnEditActionClicked, - onDeleteActionClicked: mockOnDeleteActionClicked, - }); + const columns = result.current.getColumns(props); - expect(columns).toHaveLength(3); + expect(columns).toHaveLength(4); expect(columns[0].name).toBe('Name'); expect(columns[1].name).toBe('Contexts'); - expect(columns[2].name).toBe('Actions'); + expect(columns[2].name).toBe('Date updated'); + expect(columns[3].name).toBe('Actions'); }); it('should render contexts column correctly', () => { - const columns = result.current.getColumns({ - basePromptContexts: mockPromptContexts, - onEditActionClicked: mockOnEditActionClicked, - onDeleteActionClicked: mockOnDeleteActionClicked, - }); + const columns = result.current.getColumns(props); const mockQuickPrompt = { ...MOCK_QUICK_PROMPTS[0], categories: ['alert'] }; const mockBadgesColumn = (columns[1] as EuiTableComputedColumnType).render( @@ -56,42 +55,22 @@ describe('useQuickPromptTable', () => { }); it('should not render delete action for non-deletable prompt', () => { - const columns = result.current.getColumns({ - basePromptContexts: mockPromptContexts, - onEditActionClicked: mockOnEditActionClicked, - onDeleteActionClicked: mockOnDeleteActionClicked, - }); - - const mockRowActions = (columns[2] as EuiTableComputedColumnType).render( - MOCK_QUICK_PROMPTS[0] - ); + const columns = result.current.getColumns(props); - expect(mockRowActions).toHaveProperty('props', { - rowItem: MOCK_QUICK_PROMPTS[0], - onDelete: undefined, - onEdit: mockOnEditActionClicked, - isDeletable: false, - }); + const defaultPrompt = MOCK_QUICK_PROMPTS.find((qp) => qp.isDefault); + if (defaultPrompt) { + const mockRowActions = (columns[3] as EuiTableActionsColumnType).actions[1]; + expect(mockRowActions?.enabled?.(defaultPrompt)).toEqual(false); + } }); it('should render delete actions correctly for deletable prompt', () => { - const columns = result.current.getColumns({ - basePromptContexts: mockPromptContexts, - onEditActionClicked: mockOnEditActionClicked, - onDeleteActionClicked: mockOnDeleteActionClicked, - }); + const columns = result.current.getColumns(props); const nonDefaultPrompt = MOCK_QUICK_PROMPTS.find((qp) => !qp.isDefault); if (nonDefaultPrompt) { - const mockRowActions = (columns[2] as EuiTableComputedColumnType).render( - nonDefaultPrompt - ); - expect(mockRowActions).toHaveProperty('props', { - rowItem: nonDefaultPrompt, - onDelete: mockOnDeleteActionClicked, - onEdit: mockOnEditActionClicked, - isDeletable: true, - }); + const mockRowActions = (columns[3] as EuiTableActionsColumnType).actions[1]; + expect(mockRowActions?.enabled?.(nonDefaultPrompt)).toEqual(true); } }); }); 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 1899905db0ea1..7c6a250224184 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 @@ -5,21 +5,25 @@ * 2.0. */ -import { EuiBasicTableColumn, EuiLink } from '@elastic/eui'; +import { EuiBadge, EuiBasicTableColumn, EuiLink } from '@elastic/eui'; import React, { useCallback } from 'react'; import { PromptResponse } from '@kbn/elastic-assistant-common'; +import { FormattedDate } from '@kbn/i18n-react'; 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 * as i18n from './translations'; +import { useInlineActions } from '../../common/components/assistant_settings_management/inline_actions'; export const useQuickPromptTable = () => { + const getActions = useInlineActions(); const getColumns = useCallback( ({ + isActionsDisabled, basePromptContexts, onEditActionClicked, onDeleteActionClicked, }: { + isActionsDisabled: boolean; basePromptContexts: PromptContextTemplate[]; onEditActionClicked: (prompt: PromptResponse) => void; onDeleteActionClicked: (prompt: PromptResponse) => void; @@ -29,7 +33,9 @@ export const useQuickPromptTable = () => { name: i18n.QUICK_PROMPTS_TABLE_COLUMN_NAME, render: (prompt: PromptResponse) => prompt?.name ? ( - onEditActionClicked(prompt)}>{prompt?.name} + onEditActionClicked(prompt)} disabled={isActionsDisabled}> + {prompt?.name} + ) : null, sortable: ({ name }: PromptResponse) => name, }, @@ -47,34 +53,33 @@ export const useQuickPromptTable = () => { ) : null; }, }, - /* TODO: enable when createdAt is added { align: 'left', - field: 'createdAt', - name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CREATED_AT, + field: 'updatedAt', + name: i18n.QUICK_PROMPTS_TABLE_COLUMN_DATE_UPDATED, + render: (updatedAt: PromptResponse['updatedAt']) => + updatedAt ? ( + + + + ) : null, + sortable: true, }, - */ { align: 'center', - name: i18n.QUICK_PROMPTS_TABLE_COLUMN_ACTIONS, width: '120px', - render: (prompt: PromptResponse) => { - if (!prompt) { - return null; - } - const isDeletable = !prompt.isDefault; - return ( - - rowItem={prompt} - onDelete={isDeletable ? onDeleteActionClicked : undefined} - onEdit={onEditActionClicked} - isDeletable={isDeletable} - /> - ); - }, + ...getActions({ + onDelete: onDeleteActionClicked, + onEdit: onEditActionClicked, + }), }, ], - [] + [getActions] ); return { getColumns }; 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 4b46d2b75d0a9..692bf9cc7c62d 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 @@ -81,7 +81,6 @@ export const AssistantSettings: React.FC = React.memo( conversationsLoaded, }) => { const { - actionTypeRegistry, assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled }, http, toasts, @@ -97,7 +96,7 @@ export const AssistantSettings: React.FC = React.memo( const { data: anonymizationFields, refetch: refetchAnonymizationFieldsResults } = useFetchAnonymizationFields(); - const { data: allPrompts } = useFetchPrompts(); + const { data: allPrompts, isFetched: promptsLoaded } = useFetchPrompts(); const { data: connectors } = useLoadConnectors({ http, @@ -123,7 +122,13 @@ export const AssistantSettings: React.FC = React.memo( setUpdatedAnonymizationData, setPromptsBulkActions, setUpdatedSystemPromptSettings, - } = useSettingsUpdater(conversations, allPrompts, conversationsLoaded, anonymizationFields); + } = useSettingsUpdater( + conversations, + allPrompts, + conversationsLoaded, + promptsLoaded, + anonymizationFields + ); // Local state for saving previously selected items so tab switching is friendlier // Conversation Selection State @@ -315,14 +320,13 @@ export const AssistantSettings: React.FC = React.memo( className="eui-scrollBar" grow={true} css={css` - max-height: 550px; + max-height: 519px; overflow-y: scroll; `} > {!selectedSettingsTab || (selectedSettingsTab === CONVERSATIONS_TAB && ( void; + onSaveButtonClicked: () => void; +}> = React.memo(({ hasPendingChanges, onCancelClick, onSaveButtonClicked }) => + hasPendingChanges ? ( + + + + + {CANCEL} + + + + + {SAVE} + + + + + ) : null +); +AssistantSettingsBottomBar.displayName = 'AssistantSettingsBottomBar'; 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 7d70ee5ede730..d8e207cbb23cd 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 @@ -10,10 +10,11 @@ import { useAssistantContext } from '../../assistant_context'; import { fireEvent, render } from '@testing-library/react'; import React from 'react'; -import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; +import { I18nProvider } from '@kbn/i18n-react'; import { MOCK_QUICK_PROMPTS } from '../../mock/quick_prompt'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AssistantSettingsManagement } from './assistant_settings_management'; + import { ANONYMIZATION_TAB, CONNECTORS_TAB, @@ -51,22 +52,9 @@ const mockContext = { isAssistantEnabled: true, }, }; -const onClose = jest.fn(); -const onSave = jest.fn().mockResolvedValue(() => {}); -const onConversationSelected = jest.fn(); const testProps = { - conversationsLoaded: true, - defaultConnectorId: '123', - defaultProvider: OpenAiProviderType.OpenAi, selectedConversation: welcomeConvo, - onClose, - onSave, - onConversationSelected, - conversations: {}, - anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] }, - refetchAnonymizationFieldsResults: jest.fn(), - refetchConversations: jest.fn(), }; jest.mock('../../assistant_context'); @@ -86,6 +74,10 @@ jest.mock('../prompt_editor/system_prompt/system_prompt_settings_management', () SystemPromptSettingsManagement: () => , })); +jest.mock('../../knowledge_base/knowledge_base_settings_management', () => ({ + KnowledgeBaseSettingsManagement: () => , +})); + jest.mock('../../data_anonymization/settings/anonymization_settings_management', () => ({ AnonymizationSettingsManagement: () => , })); @@ -93,7 +85,6 @@ jest.mock('../../data_anonymization/settings/anonymization_settings_management', jest.mock('.', () => { return { EvaluationSettings: () => , - KnowledgeBaseSettings: () => , }; }); @@ -105,10 +96,16 @@ jest.mock('./use_settings_updater/use_settings_updater', () => { }; }); +jest.mock('../../connectorland/use_load_connectors', () => ({ + useLoadConnectors: jest.fn().mockReturnValue({ data: [] }), +})); + const queryClient = new QueryClient(); const wrapper = (props: { children: React.ReactNode }) => ( - {props.children} + + {props.children} + ); describe('AssistantSettingsManagement', () => { 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 be6370d36e841..89c00fbf88773 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 @@ -5,29 +5,15 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { - EuiAvatar, - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiPageTemplate, - EuiTitle, - useEuiShadow, - useEuiTheme, -} from '@elastic/eui'; +import React, { useEffect, useMemo } from 'react'; +import { EuiAvatar, EuiPageTemplate, EuiTitle, useEuiShadow, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; -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'; -import { KnowledgeBaseSettings, EvaluationSettings } from '.'; import { useLoadConnectors } from '../../connectorland/use_load_connectors'; import { getDefaultConnector } from '../helpers'; -import { useFetchAnonymizationFields } from '../api/anonymization_fields/use_fetch_anonymization_fields'; import { ConnectorsSettingsManagement } from '../../connectorland/connector_settings_management'; import { ConversationSettingsManagement } from '../conversations/conversation_settings_management'; import { QuickPromptSettingsManagement } from '../quick_prompts/quick_prompt_settings_management'; @@ -43,13 +29,11 @@ import { QUICK_PROMPTS_TAB, SYSTEM_PROMPTS_TAB, } from './const'; -import { useFetchPrompts } from '../api/prompts/use_fetch_prompts'; +import { KnowledgeBaseSettingsManagement } from '../../knowledge_base/knowledge_base_settings_management'; +import { EvaluationSettings } from '.'; interface Props { - conversations: Record; - conversationsLoaded: boolean; selectedConversation: Conversation; - refetchConversations: () => void; } /** @@ -57,149 +41,28 @@ interface Props { * anonymization, knowledge base, and evaluation via the `isModelEvaluationEnabled` feature flag. */ export const AssistantSettingsManagement: React.FC = React.memo( - ({ - conversations, - conversationsLoaded, - refetchConversations, - selectedConversation: defaultSelectedConversation, - }) => { + ({ selectedConversation: defaultSelectedConversation }) => { const { assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled }, http, selectedSettingsTab, setSelectedSettingsTab, - toasts, } = useAssistantContext(); - const { data: anonymizationFields } = useFetchAnonymizationFields(); - - const { data: allPrompts } = useFetchPrompts(); - - // Connector details const { data: connectors } = useLoadConnectors({ http, }); const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); - const [hasPendingChanges, setHasPendingChanges] = useState(false); const { euiTheme } = useEuiTheme(); const headerIconShadow = useEuiShadow('s'); - const { - conversationSettings, - setConversationSettings, - knowledgeBase, - quickPromptSettings, - systemPromptSettings, - assistantStreamingEnabled, - setUpdatedAssistantStreamingEnabled, - setUpdatedKnowledgeBaseSettings, - setUpdatedQuickPromptSettings, - setPromptsBulkActions, - saveSettings, - conversationsSettingsBulkActions, - updatedAnonymizationData, - setConversationsSettingsBulkActions, - 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( - () => { - return conversationSettings[defaultSelectedConversation.title]; - } - ); - - const onHandleSelectedConversationChange = useCallback((conversation?: Conversation) => { - setSelectedConversation(conversation); - }, []); - - useEffect(() => { - if (selectedConversation != null) { - setSelectedConversation( - // conversationSettings has title as key, sometime has id as key - conversationSettings[selectedConversation.id] || - conversationSettings[selectedConversation.title] - ); - } - }, [conversationSettings, selectedConversation]); - useEffect(() => { if (selectedSettingsTab == null) { setSelectedSettingsTab(CONNECTORS_TAB); } }, [selectedSettingsTab, setSelectedSettingsTab]); - // Quick Prompt Selection State - const [selectedQuickPrompt, setSelectedQuickPrompt] = useState(); - const onHandleSelectedQuickPromptChange = useCallback((quickPrompt?: PromptResponse) => { - setSelectedQuickPrompt(quickPrompt); - }, []); - useEffect(() => { - if (selectedQuickPrompt != null) { - setSelectedQuickPrompt( - quickPromptSettings.find((q) => q.name === selectedQuickPrompt.name) - ); - } - }, [quickPromptSettings, selectedQuickPrompt]); - - // System Prompt Selection State - const [selectedSystemPrompt, setSelectedSystemPrompt] = useState(); - const onHandleSelectedSystemPromptChange = useCallback((systemPrompt?: PromptResponse) => { - setSelectedSystemPrompt(systemPrompt); - }, []); - useEffect(() => { - if (selectedSystemPrompt != null) { - setSelectedSystemPrompt(systemPromptSettings.find((p) => p.id === selectedSystemPrompt.id)); - } - }, [selectedSystemPrompt, systemPromptSettings]); - - const handleSave = useCallback( - async (shouldRefetchConversation?: boolean) => { - await saveSettings(); - toasts?.addSuccess({ - iconType: 'check', - title: i18n.SETTINGS_UPDATED_TOAST_TITLE, - }); - setHasPendingChanges(false); - if (shouldRefetchConversation) { - refetchConversations(); - } - }, - [refetchConversations, saveSettings, toasts] - ); - - const onSaveButtonClicked = useCallback(() => { - handleSave(true); - }, [handleSave]); - const tabsConfig = useMemo( () => [ { @@ -247,18 +110,6 @@ export const AssistantSettingsManagement: React.FC = React.memo( })); }, [setSelectedSettingsTab, selectedSettingsTab, tabsConfig]); - const handleChange = useCallback( - (callback) => (value: unknown) => { - setHasPendingChanges(true); - callback(value); - }, - [] - ); - - const onCancelClick = useCallback(() => { - resetSettings(); - setHasPendingChanges(false); - }, [resetSettings]); return ( <> = React.memo( `} /> -

{i18n.SECURITY_AI_SETTINGS}

+ {i18n.SECURITY_AI_SETTINGS} } @@ -294,100 +145,22 @@ export const AssistantSettingsManagement: React.FC = React.memo( {selectedSettingsTab === CONNECTORS_TAB && } {selectedSettingsTab === CONVERSATIONS_TAB && ( )} {selectedSettingsTab === SYSTEM_PROMPTS_TAB && ( - )} - {selectedSettingsTab === QUICK_PROMPTS_TAB && ( - - )} - {selectedSettingsTab === ANONYMIZATION_TAB && ( - - )} - {selectedSettingsTab === KNOWLEDGE_BASE_TAB && ( - )} + {selectedSettingsTab === QUICK_PROMPTS_TAB && } + {selectedSettingsTab === ANONYMIZATION_TAB && } + {selectedSettingsTab === KNOWLEDGE_BASE_TAB && } {selectedSettingsTab === EVALUATION_TAB && } - {hasPendingChanges && ( - - - - - {i18n.CANCEL} - - - - - {i18n.SAVE} - - - - - )} ); } diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx index 71bbab7636a4a..e6ce69f65abab 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_settings.tsx @@ -23,6 +23,7 @@ import { EuiFlexItem, EuiFlexGroup, EuiLink, + EuiPanel, } from '@elastic/eui'; import { css } from '@emotion/react'; @@ -348,12 +349,8 @@ export const EvaluationSettings: React.FC = React.memo(() => { `; return ( - <> - -

{i18n.SETTINGS_TITLE}

-
- - {i18n.SETTINGS_DESCRIPTION} + + {i18n.SETTINGS_DESCRIPTION} {/* Run Details*/} { { - + ); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts index 517f4456b49e8..e875b4c35b203 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/translations.ts @@ -31,7 +31,7 @@ export const RUN_DETAILS_TITLE = i18n.translate( export const RUN_DETAILS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runDetailsDescription', { - defaultMessage: 'Configure test run details like project, run name, dataset, and output index', + defaultMessage: 'Configure test run details like project, run name, dataset, and output index.', } ); @@ -46,7 +46,7 @@ export const PREDICTION_DETAILS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.predictionDetailsDescription', { defaultMessage: - 'Choose models (connectors) and corresponding agents the dataset should run against', + 'Choose models (connectors) and corresponding agents the dataset should run against.', } ); @@ -61,7 +61,7 @@ export const EVALUATION_DETAILS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluationDetailsDescription', { defaultMessage: - 'Evaluate prediction results using a specific model (connector) and evaluation criterion', + 'Evaluate prediction results using a specific model (connector) and evaluation criterion.', } ); @@ -75,7 +75,7 @@ export const PROJECT_LABEL = i18n.translate( export const PROJECT_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectDescription', { - defaultMessage: 'LangSmith project to write results to', + defaultMessage: 'LangSmith project to write results to.', } ); @@ -96,7 +96,7 @@ export const RUN_NAME_LABEL = i18n.translate( export const RUN_NAME_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNameDescription', { - defaultMessage: 'Name for this specific test run', + defaultMessage: 'Name for this specific test run.', } ); @@ -117,7 +117,7 @@ export const CONNECTORS_LABEL = i18n.translate( export const CONNECTORS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsDescription', { - defaultMessage: 'Select whichever models you want to evaluate the dataset against', + defaultMessage: 'Select models to evaluate the dataset against.', } ); @@ -131,7 +131,7 @@ export const AGENTS_LABEL = i18n.translate( export const AGENTS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.agentsDescription', { - defaultMessage: 'Select the agents (i.e. RAG algos) to evaluate the dataset against', + defaultMessage: 'Select the agents (RAG algorithms) to evaluate the dataset against.', } ); @@ -145,7 +145,7 @@ export const EVALUATOR_MODEL_LABEL = i18n.translate( export const EVALUATOR_MODEL_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorModelDescription', { - defaultMessage: 'Model to perform the final evaluation with', + defaultMessage: 'Model that performs the final evaluation.', } ); @@ -160,7 +160,7 @@ export const EVALUATION_TYPE_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluationTypeDescription', { defaultMessage: - 'Type of evaluation to perform, e.g. "correctness" "esql-validator", or "custom" and provide your own evaluation prompt', + 'Type of evaluation to perform, e.g. "correctness" "esql-validator", or "custom".', } ); @@ -175,7 +175,7 @@ export const EVALUATION_PROMPT_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluationPromptDescription', { defaultMessage: - 'Prompt template given `input`, `reference` and `prediction` template variables', + 'Prompt template given `input`, `reference` and `prediction` template variables.', } ); export const EVALUATOR_OUTPUT_INDEX_LABEL = i18n.translate( @@ -189,7 +189,7 @@ export const EVALUATOR_OUTPUT_INDEX_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorOutputIndexDescription', { defaultMessage: - 'Index to write results to. Must be prefixed with ".kibana-elastic-ai-assistant-"', + 'Index to write results to. Must be prefixed with ".kibana-elastic-ai-assistant-".', } ); @@ -211,7 +211,7 @@ export const APM_URL_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.apmUrlDescription', { defaultMessage: - 'URL for the Kibana APM app. Used to link to APM traces for evaluation results. Defaults to "{defaultUrlPath}"', + 'URL for the Kibana APM app. Used to link to APM traces for evaluation results. Defaults to "{defaultUrlPath}".', values: { defaultUrlPath: '${basePath}/app/apm', }, @@ -228,7 +228,7 @@ export const LANGSMITH_PROJECT_LABEL = i18n.translate( export const LANGSMITH_PROJECT_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithProjectDescription', { - defaultMessage: 'LangSmith Project to write traces to', + defaultMessage: 'LangSmith Project to write traces to.', } ); @@ -264,7 +264,7 @@ export const LANGSMITH_DATASET_LABEL = i18n.translate( export const LANGSMITH_DATASET_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetDescription', { - defaultMessage: 'Name of dataset hosted on LangSmith to evaluate', + defaultMessage: 'Name of dataset hosted on LangSmith to evaluate.', } ); @@ -286,7 +286,7 @@ export const CUSTOM_DATASET_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.customDatasetDescription', { defaultMessage: - 'Custom dataset to evaluate. Array of objects with "input" and "references" properties', + 'Custom dataset to evaluate. Array of objects with "input" and "references" properties.', } ); 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 0a2c72ba80ac4..c7efa1f8e5922 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 @@ -17,7 +17,8 @@ const mockConversations = { [alertConvo.title]: alertConvo, [welcomeConvo.title]: welcomeConvo, }; -const conversationsLoaded = true; +const conversationsLoaded = false; +const promptsLoaded = false; const mockHttp = { fetch: jest.fn(), @@ -112,6 +113,7 @@ describe('useSettingsUpdater', () => { total: 10, }, conversationsLoaded, + promptsLoaded, anonymizationFields ) ); @@ -168,6 +170,7 @@ describe('useSettingsUpdater', () => { total: 10, }, conversationsLoaded, + promptsLoaded, anonymizationFields ) ); @@ -215,6 +218,7 @@ describe('useSettingsUpdater', () => { total: 10, }, conversationsLoaded, + promptsLoaded, anonymizationFields ) ); @@ -242,6 +246,7 @@ describe('useSettingsUpdater', () => { total: 10, }, conversationsLoaded, + promptsLoaded, anonymizationFields ) ); @@ -270,6 +275,7 @@ describe('useSettingsUpdater', () => { total: 10, }, conversationsLoaded, + promptsLoaded, anonymizationFields ) ); 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 1ae1c9e5b1b73..f08a6f9fe6866 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 @@ -24,6 +24,16 @@ import { import { bulkUpdateAnonymizationFields } from '../../api/anonymization_fields/bulk_update_anonymization_fields'; import { bulkUpdatePrompts } from '../../api/prompts/bulk_update_prompts'; +export const DEFAULT_ANONYMIZATION_FIELDS = { + page: 0, + perPage: 0, + total: 0, + data: [], +}; + +export const DEFAULT_CONVERSATIONS: Record = {}; + +export const DEFAULT_PROMPTS: FindPromptsResponse = { page: 0, perPage: 0, total: 0, data: [] }; interface UseSettingsUpdater { assistantStreamingEnabled: boolean; conversationSettings: Record; @@ -57,7 +67,8 @@ export const useSettingsUpdater = ( conversations: Record, allPrompts: FindPromptsResponse, conversationsLoaded: boolean, - anonymizationFields: FindAnonymizationFieldsResponse + promptsLoaded: boolean, + anonymizationFields: FindAnonymizationFieldsResponse = DEFAULT_ANONYMIZATION_FIELDS // Put default as a constant to avoid re-creating it on every render ): UseSettingsUpdater => { // Initial state from assistant context const { @@ -100,7 +111,6 @@ export const useSettingsUpdater = ( // Knowledge Base const [updatedKnowledgeBaseSettings, setUpdatedKnowledgeBaseSettings] = useState(knowledgeBase); - /** * Reset all pending settings */ @@ -115,6 +125,7 @@ export const useSettingsUpdater = ( setUpdatedSystemPromptSettings( allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system) ); + setPromptsBulkActions({}); setUpdatedAnonymizationData(anonymizationFields); }, [allPrompts, anonymizationFields, assistantStreamingEnabled, conversations, knowledgeBase]); @@ -188,6 +199,8 @@ export const useSettingsUpdater = ( ? await bulkUpdateAnonymizationFields(http, anonymizationFieldsBulkActions, toasts) : undefined; + setPromptsBulkActions({}); + setConversationsSettingsBulkActions({}); return ( (bulkResult?.success ?? true) && (bulkAnonymizationFieldsResult?.success ?? true) && @@ -235,6 +248,18 @@ export const useSettingsUpdater = ( } }, [conversations, conversationsLoaded]); + useEffect(() => { + // Update quick prompts settings when prompts are loaded + if (promptsLoaded) { + setUpdatedQuickPromptSettings( + allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.quick) + ); + setUpdatedSystemPromptSettings( + allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system) + ); + } + }, [allPrompts.data, promptsLoaded]); + return { conversationSettings, conversationsSettingsBulkActions, 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 3e997fef5d573..a5ae389a40353 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 @@ -72,7 +72,7 @@ describe('useConversation helpers', () => { { id: '2', content: 'Prompt 2', - name: 'Prompt 2', + name: 'Default system prompt', promptType: 'quick', isNewConversationDefault: true, }, @@ -99,16 +99,31 @@ describe('useConversation helpers', () => { }); describe('getDefaultNewSystemPrompt', () => { + const systemPrompts: PromptResponse[] = [ + { + id: '1', + content: 'Prompt 1', + name: 'Default system prompt', + promptType: 'system', + }, + { + id: '2', + content: 'Prompt 2', + name: 'Prompt 2', + promptType: 'system', + isNewConversationDefault: true, + }, + ]; test('should return the default (starred) isNewConversationDefault system prompt', () => { - const result = getDefaultNewSystemPrompt(allSystemPrompts); + const result = getDefaultNewSystemPrompt(systemPrompts); - expect(result).toEqual(allSystemPrompts[1]); + expect(result).toEqual(systemPrompts[1]); }); - test('should return the first prompt if default new system prompt do not exist', () => { - const result = getDefaultNewSystemPrompt(allSystemPromptsNoDefault); + test('should return the fallback prompt if default new system prompt do not exist', () => { + const result = getDefaultNewSystemPrompt([systemPrompts[0]]); - expect(result).toEqual(allSystemPromptsNoDefault[0]); + expect(result).toEqual(systemPrompts[0]); }); test('should return undefined if default (starred) isNewConversationDefault system prompt does not exist and there are no system prompts', () => { @@ -131,53 +146,25 @@ describe('useConversation helpers', () => { replacements: {}, title: '1', }; - test('should return the conversation system prompt if it exists', () => { + test('should return the conversation system prompt', () => { const result = getDefaultSystemPrompt({ allSystemPrompts, conversation }); expect(result).toEqual(allSystemPrompts[2]); }); - test('should return the default (starred) isNewConversationDefault system prompt if conversation system prompt does not exist', () => { - const conversationWithoutSystemPrompt: Conversation = { - ...conversation, - apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' }, - }; + test('should return undefined if the conversation system prompt does not exist', () => { const result = getDefaultSystemPrompt({ allSystemPrompts, - conversation: conversationWithoutSystemPrompt, - }); - - expect(result).toEqual(allSystemPrompts[1]); - }); - - test('should return the default (starred) isNewConversationDefault system prompt if conversation system prompt does not exist within all system prompts', () => { - const conversationWithoutSystemPrompt: Conversation = { - apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' }, - replacements: {}, - category: 'assistant', - id: '4', // this id does not exist within allSystemPrompts - messages: [], - title: '4', - }; - const result = getDefaultSystemPrompt({ - allSystemPrompts, - conversation: conversationWithoutSystemPrompt, - }); - - expect(result).toEqual(allSystemPrompts[1]); - }); - - test('should return the first prompt if both conversation system prompt and default new system prompt do not exist', () => { - const conversationWithoutSystemPrompt: Conversation = { - ...conversation, - apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' }, - }; - const result = getDefaultSystemPrompt({ - allSystemPrompts: allSystemPromptsNoDefault, - conversation: conversationWithoutSystemPrompt, + conversation: { + ...conversation, + apiConfig: { + ...conversation.apiConfig, + defaultSystemPromptId: undefined, + }, + } as Conversation, }); - expect(result).toEqual(allSystemPromptsNoDefault[0]); + expect(result).toBeUndefined(); }); test('should return undefined if conversation system prompt does not exist and there are no system prompts', () => { @@ -190,40 +177,21 @@ describe('useConversation helpers', () => { conversation: conversationWithoutSystemPrompt, }); - expect(result).toEqual(undefined); + expect(result).toBeUndefined(); }); test('should return undefined if conversation system prompt does not exist within all system prompts', () => { const conversationWithoutSystemPrompt: Conversation = { ...conversation, - apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' }, - replacements: {}, - id: '4', // this id does not exist within allSystemPrompts + apiConfig: { connectorId: '123', actionTypeId: '.gen-ai', defaultSystemPromptId: 'xxx' }, + id: '4', }; const result = getDefaultSystemPrompt({ allSystemPrompts: allSystemPromptsNoDefault, conversation: conversationWithoutSystemPrompt, }); - expect(result).toEqual(allSystemPromptsNoDefault[0]); - }); - - test('should return (starred) isNewConversationDefault system prompt if conversation is undefined', () => { - const result = getDefaultSystemPrompt({ - allSystemPrompts, - conversation: undefined, - }); - - expect(result).toEqual(allSystemPrompts[1]); - }); - - test('should return the first system prompt if the conversation is undefined and isNewConversationDefault is not present in system prompts', () => { - const result = getDefaultSystemPrompt({ - allSystemPrompts: allSystemPromptsNoDefault, - conversation: undefined, - }); - - expect(result).toEqual(allSystemPromptsNoDefault[0]); + expect(result).toBeUndefined(); }); test('should return undefined if conversation is undefined and no system prompts are provided', () => { @@ -235,242 +203,142 @@ describe('useConversation helpers', () => { expect(result).toEqual(undefined); }); }); -}); - -describe('getConversationApiConfig', () => { - const allSystemPrompts: PromptResponse[] = [ - { - id: '1', - content: 'Prompt 1', - name: 'Prompt 1', - promptType: 'quick', - }, - { - id: '2', - content: 'Prompt 2', - name: 'Prompt 2', - promptType: 'quick', - isNewConversationDefault: true, - }, - { - id: '3', - content: 'Prompt 3', - name: 'Prompt 3', - promptType: 'quick', - }, - ]; - const conversation: Conversation = { - apiConfig: { - connectorId: '123', - actionTypeId: '.gen-ai', - defaultSystemPromptId: '2', - model: 'gpt-3', - }, - category: 'assistant', - id: '1', - messages: [], - replacements: {}, - title: 'Test Conversation', - }; - - const connectors: AIConnector[] = [ - { - id: '123', - actionTypeId: '.gen-ai', - apiProvider: OpenAiProviderType.OpenAi, - }, - { - id: '456', - actionTypeId: '.gen-ai', - apiProvider: OpenAiProviderType.AzureAi, - }, - ] as AIConnector[]; - - const defaultConnector: AIConnector = { - id: '456', - actionTypeId: '.gen-ai', - apiProvider: OpenAiProviderType.AzureAi, - } as AIConnector; - - test('should return the correct API config when connector and system prompt are found', () => { - const result = getConversationApiConfig({ - allSystemPrompts, - conversation, - connectors, - defaultConnector, - }); - - expect(result).toEqual({ + describe('getConversationApiConfig', () => { + const conversation: Conversation = { apiConfig: { connectorId: '123', actionTypeId: '.gen-ai', - provider: OpenAiProviderType.OpenAi, defaultSystemPromptId: '2', model: 'gpt-3', }, - }); - }); - - test('should return the default connector when specific connector is not found', () => { - const conversationWithMissingConnector: Conversation = { - ...conversation, - apiConfig: { ...conversation.apiConfig, connectorId: '999' } as Conversation['apiConfig'], + category: 'assistant', + id: '1', + messages: [], + replacements: {}, + title: 'Test Conversation', }; - const result = getConversationApiConfig({ - allSystemPrompts, - conversation: conversationWithMissingConnector, - connectors, - defaultConnector, - }); - - expect(result).toEqual({ - apiConfig: { - connectorId: '456', + const connectors: AIConnector[] = [ + { + id: '123', actionTypeId: '.gen-ai', - provider: OpenAiProviderType.AzureAi, - defaultSystemPromptId: '2', - model: 'gpt-3', + apiProvider: OpenAiProviderType.OpenAi, + config: { + provider: OpenAiProviderType.OpenAi, + }, }, - }); - }); - - test('should return an empty object when no connectors are provided and default connector is missing', () => { - const result = getConversationApiConfig({ - allSystemPrompts, - conversation, - }); - - expect(result).toEqual({}); - }); - - test('should return the default system prompt if conversation system prompt is not found', () => { - const conversationWithMissingSystemPrompt: Conversation = { - ...conversation, - apiConfig: { - ...conversation.apiConfig, - defaultSystemPromptId: '999', - } as Conversation['apiConfig'], - }; - - const result = getConversationApiConfig({ - allSystemPrompts, - conversation: conversationWithMissingSystemPrompt, - connectors, - defaultConnector, - }); - - expect(result).toEqual({ - apiConfig: { - connectorId: '123', + { + id: '456', actionTypeId: '.gen-ai', - provider: OpenAiProviderType.OpenAi, - defaultSystemPromptId: '2', // Returns the default system prompt for new conversations - model: 'gpt-3', + apiProvider: OpenAiProviderType.AzureAi, }, - }); - }); + ] as AIConnector[]; - test('should return the correct config when connectors are not provided', () => { - const result = getConversationApiConfig({ - allSystemPrompts, - conversation, - defaultConnector, - }); + const defaultConnector: AIConnector = { + id: '456', + actionTypeId: '.gen-ai', + apiProvider: OpenAiProviderType.AzureAi, + } as AIConnector; - expect(result).toEqual({ - apiConfig: { - connectorId: '456', - actionTypeId: '.gen-ai', - provider: OpenAiProviderType.AzureAi, - defaultSystemPromptId: '2', - model: 'gpt-3', - }, - }); - }); + test('should return the correct API config when connector and system prompt are found', () => { + const result = getConversationApiConfig({ + allSystemPrompts, + conversation, + connectors, + defaultConnector, + }); - test('should return the first system prompt if both conversation system prompt and default new system prompt do not exist', () => { - const allSystemPromptsNoDefault: PromptResponse[] = allSystemPrompts.filter( - ({ isNewConversationDefault }) => isNewConversationDefault !== true - ); + expect(result).toEqual({ + apiConfig: { + connectorId: '123', + actionTypeId: '.gen-ai', + provider: OpenAiProviderType.OpenAi, + defaultSystemPromptId: '2', + model: 'gpt-3', + }, + }); + }); - const conversationWithoutSystemPrompt: Conversation = { - ...conversation, - apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' }, - }; + test('should return the default connector when specific connector is not found', () => { + const conversationWithMissingConnector: Conversation = { + ...conversation, + apiConfig: { ...conversation.apiConfig, connectorId: '999' } as Conversation['apiConfig'], + }; - const result = getConversationApiConfig({ - allSystemPrompts: allSystemPromptsNoDefault, - conversation: conversationWithoutSystemPrompt, - connectors, - defaultConnector, - }); + const result = getConversationApiConfig({ + allSystemPrompts, + conversation: conversationWithMissingConnector, + connectors, + defaultConnector, + }); - expect(result).toEqual({ - apiConfig: { - connectorId: '123', - actionTypeId: '.gen-ai', - provider: OpenAiProviderType.OpenAi, - defaultSystemPromptId: '1', // Uses the first prompt in the list - model: undefined, // default connector's model - }, + expect(result).toEqual({ + apiConfig: { + connectorId: '456', + actionTypeId: '.gen-ai', + provider: OpenAiProviderType.AzureAi, + defaultSystemPromptId: '2', + model: 'gpt-3', + }, + }); }); - }); - - test('should return the first system prompt if conversation system prompt does not exist within all system prompts', () => { - const allSystemPromptsNoDefault: PromptResponse[] = allSystemPrompts.filter( - ({ isNewConversationDefault }) => isNewConversationDefault !== true - ); - const conversationWithoutSystemPrompt: Conversation = { - ...conversation, - apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' }, - id: '4', // this id does not exist within allSystemPrompts - }; + test('should return an empty object when no connectors are provided and default connector is missing', () => { + const result = getConversationApiConfig({ + allSystemPrompts, + conversation, + }); - const result = getConversationApiConfig({ - allSystemPrompts: allSystemPromptsNoDefault, - conversation: conversationWithoutSystemPrompt, - connectors, - defaultConnector, + expect(result).toEqual({ + apiConfig: { + defaultSystemPromptId: '2', + }, + }); }); - expect(result).toEqual({ - apiConfig: { - connectorId: '123', - actionTypeId: '.gen-ai', - provider: OpenAiProviderType.OpenAi, - defaultSystemPromptId: '1', // Uses the first prompt in the list - model: undefined, // default connector's model - }, - }); - }); + test('should set default system prompt as undefined if conversation system prompt is not found', () => { + const conversationWithMissingSystemPrompt: Conversation = { + ...conversation, + apiConfig: { + ...conversation.apiConfig, + defaultSystemPromptId: '999', + } as Conversation['apiConfig'], + }; - test('should return the new default system prompt if defaultSystemPromptId is undefined', () => { - const conversationWithUndefinedPrompt: Conversation = { - ...conversation, - apiConfig: { - ...conversation.apiConfig, - defaultSystemPromptId: undefined, - } as Conversation['apiConfig'], - }; + const result = getConversationApiConfig({ + allSystemPrompts, + conversation: conversationWithMissingSystemPrompt, + connectors, + defaultConnector, + }); - const result = getConversationApiConfig({ - allSystemPrompts, - conversation: conversationWithUndefinedPrompt, - connectors, - defaultConnector, + expect(result).toEqual({ + apiConfig: { + connectorId: '123', + actionTypeId: '.gen-ai', + provider: OpenAiProviderType.OpenAi, + model: 'gpt-3', + }, + }); }); - expect(result).toEqual({ - apiConfig: { - connectorId: '123', - actionTypeId: '.gen-ai', - provider: OpenAiProviderType.OpenAi, - defaultSystemPromptId: '1', - model: 'gpt-3', - }, + test('should return the correct config when connectors are not provided', () => { + const result = getConversationApiConfig({ + allSystemPrompts, + conversation, + defaultConnector, + }); + + expect(result).toEqual({ + apiConfig: { + connectorId: '456', + actionTypeId: '.gen-ai', + provider: OpenAiProviderType.AzureAi, + defaultSystemPromptId: '2', + model: 'gpt-3', + }, + }); }); }); }); 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 fde1c1d3d943c..dce5a1ab11388 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,10 +6,11 @@ */ import React from 'react'; -import { PromptResponse } from '@kbn/elastic-assistant-common'; +import { ApiConfig, PromptResponse } from '@kbn/elastic-assistant-common'; import { Conversation } from '../../assistant_context/types'; import { AIConnector } from '../../connectorland/connector_selector'; import { getGenAiConfig } from '../../connectorland/helpers'; +import { DEFAULT_SYSTEM_PROMPT_NAME } from '../../content/prompts/system/translations'; export interface CodeBlockDetails { type: QueryType; @@ -71,15 +72,19 @@ export const analyzeMarkdown = (markdown: string): CodeBlockDetails[] => { }; /** - * Returns the default system prompt + * Returns the new default system prompt, fallback to the default system prompt if not found * * @param allSystemPrompts All available System Prompts */ -export const getDefaultNewSystemPrompt = (allSystemPrompts: PromptResponse[]) => - allSystemPrompts.find((prompt) => prompt.isNewConversationDefault) ?? allSystemPrompts?.[0]; +export const getDefaultNewSystemPrompt = (allSystemPrompts: PromptResponse[]) => { + const fallbackSystemPrompt = allSystemPrompts.find( + (prompt) => prompt.name === DEFAULT_SYSTEM_PROMPT_NAME + ); + return allSystemPrompts.find((prompt) => prompt.isNewConversationDefault) ?? fallbackSystemPrompt; +}; /** - * Returns the default system prompt for a given (New Custom) conversation + * Returns the default system prompt for a given conversation * * @param allSystemPrompts All available System Prompts * @param conversation Conversation to get the default system prompt for @@ -94,29 +99,26 @@ export const getDefaultSystemPrompt = ({ const conversationSystemPrompt = allSystemPrompts.find( (prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId ); - const defaultNewSystemPrompt = getDefaultNewSystemPrompt(allSystemPrompts); - return conversationSystemPrompt?.id ? conversationSystemPrompt : defaultNewSystemPrompt; + return conversationSystemPrompt; }; /** - * Returns the default system prompt for an existing conversation that has never been given a system prompt + * Returns the default system prompt * * @param allSystemPrompts All available System Prompts * @param conversation Conversation to get the default system prompt for */ -export const getInitialDefaultSystemPrompt = ({ +export const getFallbackDefaultSystemPrompt = ({ allSystemPrompts, - conversation, }: { allSystemPrompts: PromptResponse[]; - conversation: Conversation | undefined; }): PromptResponse | undefined => { - const conversationSystemPrompt = allSystemPrompts.find( - (prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId + const fallbackSystemPrompt = allSystemPrompts.find( + (prompt) => prompt.name === DEFAULT_SYSTEM_PROMPT_NAME ); - return conversationSystemPrompt ?? allSystemPrompts?.[0]; + return fallbackSystemPrompt; }; /** @@ -140,27 +142,29 @@ export const getConversationApiConfig = ({ }) => { const connector: AIConnector | undefined = connectors?.find((c) => c.id === conversation.apiConfig?.connectorId) ?? defaultConnector; - const connectorModel = getGenAiConfig(connector)?.defaultModel; - const defaultSystemPrompt = - conversation.apiConfig?.defaultSystemPromptId == null - ? getInitialDefaultSystemPrompt({ - allSystemPrompts, - conversation, - }) - : getDefaultSystemPrompt({ - allSystemPrompts, - conversation, - }); + + const { apiProvider: connectorApiProvider, defaultModel: connectorModel } = + getGenAiConfig(connector) ?? {}; + + const defaultSystemPrompt = getDefaultSystemPrompt({ + allSystemPrompts, + conversation, + }); return connector ? { apiConfig: { connectorId: connector.id, actionTypeId: connector.actionTypeId, - provider: connector.apiProvider, + provider: connector.apiProvider ?? connectorApiProvider, defaultSystemPromptId: defaultSystemPrompt?.id, model: conversation?.apiConfig?.model ?? connectorModel, }, } - : {}; + : ({ + // Scenario when no connectors is configured + apiConfig: { + defaultSystemPromptId: defaultSystemPrompt?.id, + }, + } as unknown as { apiConfig: ApiConfig }); }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/index.tsx index f144253ea4cce..afa8bd48567a2 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/index.tsx @@ -14,6 +14,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; +import { css } from '@emotion/react'; import React, { useCallback } from 'react'; import { useAssistantContext } from '../../assistant_context'; @@ -32,13 +33,17 @@ const ConnectorsSettingsManagementComponent: React.FC = () => { return ( - +

{i18n.CONNECTOR_SETTINGS_MANAGEMENT_TITLE}

- - {i18n.CONNECTOR_SETTINGS_MANAGEMENT_DESCRIPTION} + + {i18n.CONNECTOR_SETTINGS_MANAGEMENT_DESCRIPTION} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/translations.ts index 941337761fadc..e7f7ac69a7d1c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/translations.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export const CONNECTOR_SETTINGS_MANAGEMENT_TITLE = i18n.translate( 'xpack.elasticAssistant.connectors.connectorSettingsManagement.title', { - defaultMessage: 'Connector Settings', + defaultMessage: 'Settings', } ); @@ -18,7 +18,7 @@ export const CONNECTOR_SETTINGS_MANAGEMENT_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.connectors.connectorSettingsManagement.description', { defaultMessage: - 'Using the Elastic AI Assistant requires setting up a connector with API access to OpenAI or Bedrock large language models. ', + 'To use Elastic AI Assistant, you must set up a connector to an external large language model.', } ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/translations.ts index 82d0b7ec47701..0d4ea885aeffc 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/translations.ts @@ -38,6 +38,6 @@ export const SETTINGS_DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.settingsDescription', { defaultMessage: - "When adding Prompt Context throughout the Security App that may contain sensitive information, you can choose which fields are sent, and whether to enable anonymization for these fields. This will replace the field's value with a random string before sending the conversation. Helpful defaults are provided below.", + 'Define privacy settings for event data sent to third-party LLM providers. You can choose which fields to include, and which to anonymize by replacing their values with random strings. Helpful defaults are provided below.', } ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/use_anonymization_list_update.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/use_anonymization_list_update.tsx index f9ee4875fad23..643a2ca8d7c39 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/use_anonymization_list_update.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/use_anonymization_list_update.tsx @@ -31,7 +31,6 @@ export const useAnonymizationListUpdate = ({ const onListUpdated = useCallback( async (updates: BatchUpdateListItem[]) => { const updatedFieldsKeys = updates.map((u) => u.field); - const updatedFields = updates.map((u) => ({ ...(anonymizationFields.data.find((f) => f.field === u.field) ?? { id: '', field: '' }), ...(u.update === 'allow' || u.update === 'defaultAllow' diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx index 3e3812510f076..420f570834cce 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx @@ -5,71 +5,125 @@ * 2.0. */ -import { EuiFlexGroup, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import React from 'react'; +import { EuiFlexGroup, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import React, { useCallback, 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 { euiThemeVars } from '@kbn/ui-theme'; import { Stats } from '../../../data_anonymization_editor/stats'; import { ContextEditor } from '../../../data_anonymization_editor/context_editor'; import * as i18n from '../anonymization_settings/translations'; import { useAnonymizationListUpdate } from '../anonymization_settings/use_anonymization_list_update'; +import { + DEFAULT_ANONYMIZATION_FIELDS, + DEFAULT_CONVERSATIONS, + DEFAULT_PROMPTS, + useSettingsUpdater, +} from '../../../assistant/settings/use_settings_updater/use_settings_updater'; +import { useFetchAnonymizationFields } from '../../../assistant/api/anonymization_fields/use_fetch_anonymization_fields'; +import { AssistantSettingsBottomBar } from '../../../assistant/settings/assistant_settings_bottom_bar'; +import { useAssistantContext } from '../../../assistant_context'; +import { SETTINGS_UPDATED_TOAST_TITLE } from '../../../assistant/settings/translations'; export interface Props { defaultPageSize?: number; - anonymizationFields: FindAnonymizationFieldsResponse; - anonymizationFieldsBulkActions: PerformBulkActionRequestBody; - setAnonymizationFieldsBulkActions: React.Dispatch< - React.SetStateAction - >; - setUpdatedAnonymizationData: React.Dispatch< - React.SetStateAction - >; } -const AnonymizationSettingsManagementComponent: React.FC = ({ - defaultPageSize, - anonymizationFields, - anonymizationFieldsBulkActions, - setAnonymizationFieldsBulkActions, - setUpdatedAnonymizationData, -}) => { - const onListUpdated = useAnonymizationListUpdate({ - anonymizationFields, +const AnonymizationSettingsManagementComponent: React.FC = ({ defaultPageSize = 5 }) => { + const { toasts } = useAssistantContext(); + const { data: anonymizationFields } = useFetchAnonymizationFields(); + const [hasPendingChanges, setHasPendingChanges] = useState(false); + + const { anonymizationFieldsBulkActions, setAnonymizationFieldsBulkActions, setUpdatedAnonymizationData, + resetSettings, + saveSettings, + updatedAnonymizationData, + } = useSettingsUpdater( + DEFAULT_CONVERSATIONS, // Anonymization settings do not require conversations + DEFAULT_PROMPTS, // Anonymization settings do not require prompts + false, // Anonymization settings do not require conversations + false, // Anonymization settings do not require prompts + anonymizationFields ?? DEFAULT_ANONYMIZATION_FIELDS + ); + + const onCancelClick = useCallback(() => { + resetSettings(); + setHasPendingChanges(false); + }, [resetSettings]); + + const handleSave = useCallback( + async (param?: { callback?: () => void }) => { + await saveSettings(); + toasts?.addSuccess({ + iconType: 'check', + title: SETTINGS_UPDATED_TOAST_TITLE, + }); + setHasPendingChanges(false); + param?.callback?.(); + }, + [saveSettings, toasts] + ); + + const onSaveButtonClicked = useCallback(() => { + handleSave(); + }, [handleSave]); + + const handleAnonymizationFieldsBulkActions = useCallback( + (value) => { + setHasPendingChanges(true); + setAnonymizationFieldsBulkActions(value); + }, + [setAnonymizationFieldsBulkActions] + ); + + const handleUpdatedAnonymizationData = useCallback( + (value) => { + setHasPendingChanges(true); + setUpdatedAnonymizationData(value); + }, + [setUpdatedAnonymizationData] + ); + + const onListUpdated = useAnonymizationListUpdate({ + anonymizationFields: updatedAnonymizationData, + anonymizationFieldsBulkActions, + setAnonymizationFieldsBulkActions: handleAnonymizationFieldsBulkActions, + setUpdatedAnonymizationData: handleUpdatedAnonymizationData, }); return ( - - -

{i18n.SETTINGS_TITLE}

-
- - {i18n.SETTINGS_DESCRIPTION} - - - - - - + <> + + {i18n.SETTINGS_DESCRIPTION} + + - + + + - + + + + -
+ ); }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/get_columns/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/get_columns/index.tsx index 66d3a89a65df8..cc08b0a68ab89 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/get_columns/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/get_columns/index.tsx @@ -19,13 +19,15 @@ const AnonymizedButton = styled(EuiButtonEmpty)` `; export const getColumns = ({ + compressed = true, + hasUpdateAIAssistantAnonymization, onListUpdated, rawData, - hasUpdateAIAssistantAnonymization, }: { + compressed?: boolean; + hasUpdateAIAssistantAnonymization: boolean; onListUpdated: (updates: BatchUpdateListItem[]) => void; rawData: Record | null; - hasUpdateAIAssistantAnonymization: boolean; }): Array> => { const actionsColumn: EuiBasicTableColumn = { field: FIELDS.ACTIONS, @@ -68,6 +70,7 @@ export const getColumns = ({ disabled={!hasUpdateAIAssistantAnonymization} label="" showLabel={false} + compressed={compressed} onChange={() => { onListUpdated([ { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/index.tsx index 13ed9bd866513..6138a907f285b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/context_editor/index.tsx @@ -98,8 +98,8 @@ const ContextEditorComponent: React.FC = ({ ); const columns = useMemo( - () => getColumns({ onListUpdated, rawData, hasUpdateAIAssistantAnonymization }), - [hasUpdateAIAssistantAnonymization, onListUpdated, rawData] + () => getColumns({ onListUpdated, rawData, hasUpdateAIAssistantAnonymization, compressed }), + [hasUpdateAIAssistantAnonymization, onListUpdated, rawData, compressed] ); const rows = useMemo( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx index fc152d1e5a3da..335f654890a62 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx @@ -227,7 +227,23 @@ export const KnowledgeBaseSettings: React.FC = React.memo(
- {i18n.SETTINGS_DESCRIPTION} + + + {i18n.KNOWLEDGE_BASE_DOCUMENTATION} + + ), + }} + /> + { + const { + assistantFeatures: { assistantKnowledgeBaseByDefault: enableKnowledgeBaseByDefault }, + http, + toasts, + } = useAssistantContext(); + const [hasPendingChanges, setHasPendingChanges] = useState(false); + + const { knowledgeBase, setUpdatedKnowledgeBaseSettings, resetSettings, saveSettings } = + useSettingsUpdater( + DEFAULT_CONVERSATIONS, // Knowledge Base settings do not require conversations + DEFAULT_PROMPTS, // Knowledge Base settings do not require prompts + false, // Knowledge Base settings do not require prompts + false // Knowledge Base settings do not require conversations + ); + + const handleSave = useCallback( + async (param?: { callback?: () => void }) => { + await saveSettings(); + toasts?.addSuccess({ + iconType: 'check', + title: SETTINGS_UPDATED_TOAST_TITLE, + }); + setHasPendingChanges(false); + param?.callback?.(); + }, + [saveSettings, toasts] + ); + + const handleUpdateKnowledgeBaseSettings = useCallback( + (updatedKnowledgebase) => { + setHasPendingChanges(true); + setUpdatedKnowledgeBaseSettings(updatedKnowledgebase); + }, + [setUpdatedKnowledgeBaseSettings] + ); + + const onCancelClick = useCallback(() => { + resetSettings(); + setHasPendingChanges(false); + }, [resetSettings]); + + const onSaveButtonClicked = useCallback(() => { + handleSave(); + }, [handleSave]); + + const { + data: kbStatus, + isLoading, + isFetching, + } = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE }); + const { mutate: setupKB, isLoading: isSettingUpKB } = useSetupKnowledgeBase({ http }); + const { mutate: deleteKB, isLoading: isDeletingUpKB } = useDeleteKnowledgeBase({ http }); + + // Resource enabled state + const isElserEnabled = kbStatus?.elser_exists ?? false; + const isKnowledgeBaseEnabled = (kbStatus?.index_exists && kbStatus?.pipeline_exists) ?? false; + const isESQLEnabled = kbStatus?.esql_exists ?? false; + const isSetupInProgress = kbStatus?.is_setup_in_progress ?? false; + + // Resource availability state + const isLoadingKb = + isLoading || isFetching || isSettingUpKB || isDeletingUpKB || isSetupInProgress; + const isKnowledgeBaseAvailable = knowledgeBase.isEnabledKnowledgeBase && kbStatus?.elser_exists; + const isESQLAvailable = + knowledgeBase.isEnabledKnowledgeBase && isKnowledgeBaseAvailable && isKnowledgeBaseEnabled; + // Prevent enabling if elser doesn't exist, but always allow to disable + const isSwitchDisabled = enableKnowledgeBaseByDefault + ? false + : !kbStatus?.elser_exists && !knowledgeBase.isEnabledKnowledgeBase; + + // Calculated health state for EuiHealth component + const elserHealth = isElserEnabled ? 'success' : 'subdued'; + const knowledgeBaseHealth = isKnowledgeBaseEnabled ? 'success' : 'subdued'; + const esqlHealth = isESQLEnabled ? 'success' : 'subdued'; + + ////////////////////////////////////////////////////////////////////////////////////////// + // Main `Knowledge Base` switch, which toggles the `isEnabledKnowledgeBase` UI feature toggle + // setting that is saved to localstorage + const onEnableAssistantLangChainChange = useCallback( + (event: EuiSwitchEvent) => { + handleUpdateKnowledgeBaseSettings({ + ...knowledgeBase, + isEnabledKnowledgeBase: event.target.checked, + }); + + // If enabling and ELSER exists or automatic KB setup FF is enabled, try to set up automatically + if (event.target.checked && (enableKnowledgeBaseByDefault || kbStatus?.elser_exists)) { + setupKB(ESQL_RESOURCE); + } + }, + [ + enableKnowledgeBaseByDefault, + handleUpdateKnowledgeBaseSettings, + kbStatus?.elser_exists, + knowledgeBase, + setupKB, + ] + ); + + const isEnabledKnowledgeBaseSwitch = useMemo(() => { + return isLoadingKb ? ( + + ) : ( + + + + ); + }, [ + isLoadingKb, + isSwitchDisabled, + knowledgeBase.isEnabledKnowledgeBase, + onEnableAssistantLangChainChange, + ]); + + ////////////////////////////////////////////////////////////////////////////////////////// + // Knowledge Base Resource + const onEnableKB = useCallback( + (enabled: boolean) => { + if (enabled) { + setupKB(); + } else { + deleteKB(); + } + }, + [deleteKB, setupKB] + ); + + const knowledgeBaseActionButton = useMemo(() => { + return isLoadingKb || !isKnowledgeBaseAvailable ? ( + <> + ) : ( + onEnableKB(!isKnowledgeBaseEnabled)} + size="xs" + > + {isKnowledgeBaseEnabled + ? i18n.KNOWLEDGE_BASE_DELETE_BUTTON + : i18n.KNOWLEDGE_BASE_INIT_BUTTON} + + ); + }, [isKnowledgeBaseAvailable, isKnowledgeBaseEnabled, isLoadingKb, onEnableKB]); + + const knowledgeBaseDescription = useMemo(() => { + return isKnowledgeBaseEnabled ? ( + + {i18n.KNOWLEDGE_BASE_DESCRIPTION_INSTALLED( + enableKnowledgeBaseByDefault + ? KNOWLEDGE_BASE_INDEX_PATTERN + : KNOWLEDGE_BASE_INDEX_PATTERN_OLD + )}{' '} + {knowledgeBaseActionButton} + + ) : ( + + {i18n.KNOWLEDGE_BASE_DESCRIPTION} {knowledgeBaseActionButton} + + ); + }, [enableKnowledgeBaseByDefault, isKnowledgeBaseEnabled, knowledgeBaseActionButton]); + + ////////////////////////////////////////////////////////////////////////////////////////// + // ESQL Resource + const onEnableESQL = useCallback( + (enabled: boolean) => { + if (enabled) { + setupKB(ESQL_RESOURCE); + } else { + deleteKB(ESQL_RESOURCE); + } + }, + [deleteKB, setupKB] + ); + + const esqlActionButton = useMemo(() => { + return isLoadingKb || !isESQLAvailable ? ( + <> + ) : ( + onEnableESQL(!isESQLEnabled)} + size="xs" + > + {isESQLEnabled ? i18n.KNOWLEDGE_BASE_DELETE_BUTTON : i18n.KNOWLEDGE_BASE_INIT_BUTTON} + + ); + }, [isLoadingKb, isESQLAvailable, isESQLEnabled, onEnableESQL]); + + const esqlDescription = useMemo(() => { + return isESQLEnabled ? ( + + {i18n.ESQL_DESCRIPTION_INSTALLED} {esqlActionButton} + + ) : ( + + {i18n.ESQL_DESCRIPTION} {esqlActionButton} + + ); + }, [esqlActionButton, isESQLEnabled]); + + return ( + + + + {i18n.KNOWLEDGE_BASE_DOCUMENTATION} + + ), + }} + /> + + + + + {isEnabledKnowledgeBaseSwitch} + + + + + +
+ {i18n.KNOWLEDGE_BASE_ELSER_LABEL} + + + {i18n.KNOWLEDGE_BASE_ELSER_MACHINE_LEARNING} + + ), + seeDocs: ( + + {i18n.KNOWLEDGE_BASE_ELSER_SEE_DOCS} + + ), + }} + /> + +
+
+ +
+ {i18n.KNOWLEDGE_BASE_LABEL} + + {knowledgeBaseDescription} + +
+
+ + + {i18n.ESQL_LABEL} + + {esqlDescription} + + + +
+ + + + + + +
+ ); +}); + +KnowledgeBaseSettingsManagement.displayName = 'KnowledgeBaseSettingsManagement'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/translations.ts index e1b176e9dcaa7..28cd2092e4cfa 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/translations.ts @@ -66,11 +66,10 @@ export const SETTINGS_BADGE = i18n.translate( } ); -export const SETTINGS_DESCRIPTION = i18n.translate( +export const KNOWLEDGE_BASE_DOCUMENTATION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsDescription', { - defaultMessage: - 'Powered by ELSER, the Knowledge Base enables the ability to recall documents and other relevant context within your conversation.', + defaultMessage: 'documentation', } ); diff --git a/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx b/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx index 31f3910fdab49..2855c6b6115d3 100644 --- a/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx +++ b/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx @@ -31,11 +31,7 @@ export const ManagementSettings = React.memo(() => { mergeBaseWithPersistedConversations(baseConversations, conversationsData), [baseConversations] ); - const { - data: conversations, - isFetched: conversationsLoaded, - refetch: refetchConversations, - } = useFetchCurrentUserConversations({ + const { data: conversations } = useFetchCurrentUserConversations({ http, onFetch: onFetchedConversations, isAssistantEnabled, @@ -51,14 +47,7 @@ export const ManagementSettings = React.memo(() => { ); if (conversations) { - return ( - - ); + return ; } return <>;