From c316c0aa39ed55c7176cd4c1be25cdc3c1a73005 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 3 Jun 2024 18:46:33 +0100 Subject: [PATCH 01/37] connectors tab --- .../ai_assistant_selection_page.tsx | 85 +++++--- .../assistant/settings/assistant_settings.tsx | 14 +- .../assistant_settings_management.tsx | 33 ++-- .../impl/assistant/settings/translations.ts | 14 ++ .../impl/assistant_context/index.tsx | 20 +- .../connector_row_actions.tsx | 58 ++++++ .../connector_settings/index.tsx | 182 ++++++++++++++++++ .../connectorland/connector_setup/index.tsx | 7 +- .../impl/connectorland/translations.ts | 35 ++++ .../public/assistant/provider.tsx | 4 +- 10 files changed, 402 insertions(+), 50 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx 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 62687625db86d..5a494bdb3b257 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 @@ -9,6 +9,7 @@ import React, { useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { + EuiButton, EuiCallOut, EuiCard, EuiFlexGrid, @@ -86,32 +87,48 @@ export function AiAssistantSelectionPage() { ) : null} - + {i18n.translate( + 'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkDescription', + { defaultMessage: 'For more info, see our' } + )}{' '} + + {i18n.translate( + 'aiAssistantManagementSelection.aiAssistantSettingsPage.obsAssistant.documentationLinkLabel', + { defaultMessage: 'Documentation' } + )} + +

+ + navigateToApp('management', { + path: 'kibana/observabilityAiAssistantManagement', + }) + } > {i18n.translate( - 'aiAssistantManagementSelection.aiAssistantSettingsPage.obsAssistant.documentationLinkLabel', - { defaultMessage: 'Documentation' } + 'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.manageSettingsButtonLabel', + { defaultMessage: 'Manage Settings' } )} -
+ } display="plain" hasBorder - icon={} + icon={} isDisabled={!observabilityAIAssistantEnabled} - layout="horizontal" title={i18n.translate( 'aiAssistantManagementSelection.aiAssistantSelectionPage.observabilityLabel', { defaultMessage: 'Elastic AI Assistant for Observability' } )} titleSize="xs" - onClick={() => - navigateToApp('management', { path: 'kibana/observabilityAiAssistantManagement' }) - } /> @@ -135,32 +152,46 @@ export function AiAssistantSelectionPage() { ) : null} - + {i18n.translate( + 'aiAssistantManagementSelection.aiAssistantSelectionPage.securityAssistant.documentationLinkDescription', + { defaultMessage: 'For more info, see our' } + )}{' '} + + {i18n.translate( + 'aiAssistantManagementSelection.aiAssistantSettingsPage.securityAssistant.documentationLinkLabel', + { defaultMessage: 'Documentation' } + )} + +

+ + navigateToApp('management', { path: 'kibana/securityAiAssistantManagement' }) + } > {i18n.translate( - 'aiAssistantManagementSelection.aiAssistantSettingsPage.securityAssistant.documentationLinkLabel', - { defaultMessage: 'Documentation' } + 'aiAssistantManagementSelection.aiAssistantSelectionPage.securityAssistant.manageSettingsButtonLabel', + { defaultMessage: 'Manage Settings' } )} -
+ } display="plain" hasBorder - icon={} + icon={} isDisabled={!securityAIAssistantEnabled} - layout="horizontal" title={i18n.translate( 'aiAssistantManagementSelection.aiAssistantSelectionPage.securityLabel', - { defaultMessage: 'Elastic AI Assistant for Security' } + { defaultMessage: 'Elastic AI for Security' } )} titleSize="xs" - onClick={() => - navigateToApp('management', { path: 'kibana/securityAiAssistantManagement' }) - } />
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 f83e6c0d72ee6..999d511d570ec 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 @@ -44,6 +44,7 @@ const StyledEuiModal = styled(EuiModal)` height: 575px; `; +export const CONNECTORS_TAB = 'CONNECTORS_TAB' as const; export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const; export const QUICK_PROMPTS_TAB = 'QUICK_PROMPTS_TAB' as const; export const SYSTEM_PROMPTS_TAB = 'SYSTEM_PROMPTS_TAB' as const; @@ -51,13 +52,18 @@ export const ANONYMIZATION_TAB = 'ANONYMIZATION_TAB' as const; export const KNOWLEDGE_BASE_TAB = 'KNOWLEDGE_BASE_TAB' as const; export const EVALUATION_TAB = 'EVALUATION_TAB' as const; -export type SettingsTabs = +export type BaseSettingsTabs = | typeof CONVERSATIONS_TAB | typeof QUICK_PROMPTS_TAB | typeof SYSTEM_PROMPTS_TAB | typeof ANONYMIZATION_TAB | typeof KNOWLEDGE_BASE_TAB | typeof EVALUATION_TAB; + +export type AdditionalSettingsTabs = typeof CONNECTORS_TAB; + +export type SettingsTabs = BaseSettingsTabs | AdditionalSettingsTabs; + interface Props { defaultConnector?: AIConnector; onClose: ( @@ -93,6 +99,12 @@ export const AssistantSettings: React.FC = React.memo( setSelectedSettingsTab, } = useAssistantContext(); + useEffect(() => { + if (selectedSettingsTab == null) { + setSelectedSettingsTab(CONVERSATIONS_TAB); + } + }, [selectedSettingsTab, setSelectedSettingsTab]); + const { data: anonymizationFields, refetch: refetchAnonymizationFieldsResults } = useFetchAnonymizationFields(); 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 79a050b11bb27..f9b335b84b03f 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 @@ -9,7 +9,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiButton, EuiButtonEmpty, - EuiIcon, EuiFlexItem, EuiPageTemplate, EuiFlexGroup, @@ -31,7 +30,9 @@ import { import { useLoadConnectors } from '../../connectorland/use_load_connectors'; import { getDefaultConnector } from '../helpers'; import { useFetchAnonymizationFields } from '../api/anonymization_fields/use_fetch_anonymization_fields'; +import { ConnectorsSettings } from '../../connectorland/connector_settings'; +export const CONNECTORS_TAB = 'CONNECTORS_TAB' as const; export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const; export const QUICK_PROMPTS_TAB = 'QUICK_PROMPTS_TAB' as const; export const SYSTEM_PROMPTS_TAB = 'SYSTEM_PROMPTS_TAB' as const; @@ -39,13 +40,6 @@ export const ANONYMIZATION_TAB = 'ANONYMIZATION_TAB' as const; export const KNOWLEDGE_BASE_TAB = 'KNOWLEDGE_BASE_TAB' as const; export const EVALUATION_TAB = 'EVALUATION_TAB' as const; -export type SettingsTabs = - | typeof CONVERSATIONS_TAB - | typeof QUICK_PROMPTS_TAB - | typeof SYSTEM_PROMPTS_TAB - | typeof ANONYMIZATION_TAB - | typeof KNOWLEDGE_BASE_TAB - | typeof EVALUATION_TAB; interface Props { conversations: Record; selectedConversation: Conversation; @@ -125,6 +119,12 @@ export const AssistantSettingsManagement: React.FC = React.memo( } }, [conversationSettings, selectedConversation]); + useEffect(() => { + if (selectedSettingsTab == null) { + setSelectedSettingsTab(CONNECTORS_TAB); + } + }, [selectedSettingsTab, setSelectedSettingsTab]); + // Quick Prompt Selection State const [selectedQuickPrompt, setSelectedQuickPrompt] = useState(); const onHandleSelectedQuickPromptChange = useCallback((quickPrompt?: QuickPrompt) => { @@ -173,37 +173,35 @@ export const AssistantSettingsManagement: React.FC = React.memo( const tabsConfig = useMemo( () => [ + { + id: CONNECTORS_TAB, + label: i18n.CONNECTORS_MENU_ITEM, + }, { id: CONVERSATIONS_TAB, label: i18n.CONVERSATIONS_MENU_ITEM, - prepend: , }, { id: QUICK_PROMPTS_TAB, label: i18n.QUICK_PROMPTS_MENU_ITEM, - prepend: , }, { id: SYSTEM_PROMPTS_TAB, label: i18n.SYSTEM_PROMPTS_MENU_ITEM, - prepend: , }, { id: ANONYMIZATION_TAB, label: i18n.ANONYMIZATION_MENU_ITEM, - prepend: , }, { id: KNOWLEDGE_BASE_TAB, label: i18n.KNOWLEDGE_BASE_MENU_ITEM, - prepend: , }, ...(modelEvaluatorEnabled ? [ { id: EVALUATION_TAB, label: i18n.EVALUATION_MENU_ITEM, - prepend: , }, ] : []), @@ -235,7 +233,11 @@ export const AssistantSettingsManagement: React.FC = React.memo( return ( <> - + = React.memo( padding-right: 0; `} > + {selectedSettingsTab === CONNECTORS_TAB && } {selectedSettingsTab === CONVERSATIONS_TAB && ( ; children: React.ReactNode; + getAddConnectorFlyout: TriggersAndActionsUIPublicPluginStart['getAddConnectorFlyout']; + getEditConnectorFlyout: TriggersAndActionsUIPublicPluginStart['getEditConnectorFlyout']; getComments: (commentArgs: { abortStream: () => void; currentConversation?: Conversation; @@ -112,6 +117,7 @@ export interface UseAssistantContext { baseQuickPrompts: QuickPrompt[]; baseSystemPrompts: Prompt[]; baseConversations: Record; + getEditConnectorFlyout: TriggersAndActionsUIPublicPluginStart['getEditConnectorFlyout']; getComments: (commentArgs: { abortStream: () => void; currentConversation?: Conversation; @@ -130,7 +136,7 @@ export interface UseAssistantContext { promptContexts: Record; nameSpace: string; registerPromptContext: RegisterPromptContext; - selectedSettingsTab: SettingsTabs; + selectedSettingsTab: SettingsTabs | null; setAllQuickPrompts: React.Dispatch>; setAllSystemPrompts: React.Dispatch>; setAssistantStreamingEnabled: React.Dispatch>; @@ -164,6 +170,8 @@ export const AssistantProvider: React.FC = ({ baseQuickPrompts = [], baseSystemPrompts = BASE_SYSTEM_PROMPTS, children, + getAddConnectorFlyout, + getEditConnectorFlyout, getComments, http, baseConversations, @@ -264,7 +272,7 @@ export const AssistantProvider: React.FC = ({ /** * Settings State */ - const [selectedSettingsTab, setSelectedSettingsTab] = useState(CONVERSATIONS_TAB); + const [selectedSettingsTab, setSelectedSettingsTab] = useState(null); const getLastConversationId = useCallback( // if a conversationId has been provided, use that @@ -293,6 +301,8 @@ export const AssistantProvider: React.FC = ({ baseQuickPrompts, baseSystemPrompts, docLinks, + getAddConnectorFlyout, + getEditConnectorFlyout, getComments, http, knowledgeBase: { ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, ...localStorageKnowledgeBase }, @@ -332,6 +342,8 @@ export const AssistantProvider: React.FC = ({ baseQuickPrompts, baseSystemPrompts, docLinks, + getAddConnectorFlyout, + getEditConnectorFlyout, getComments, http, localStorageKnowledgeBase, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx new file mode 100644 index 0000000000000..c6f73840ac09f --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx @@ -0,0 +1,58 @@ +/* + * 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 { AIConnector } from '../connector_selector'; + +import { DELETE_CONNECTOR_BUTTON, EDIT_CONNECTOR_BUTTON } from '../translations'; + +interface Props { + connector: AIConnector; + onClickEditConnector: (connector: AIConnector) => void; +} + +const ConnectorRowActionsComponent: React.FC = ({ connector, onClickEditConnector }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const closePopover = useCallback(() => setIsPopoverOpen(false), []); + const handleEditConnector = useCallback(() => { + closePopover(); + onClickEditConnector(connector); + }, [closePopover, onClickEditConnector, connector]); + + const onButtonClick = useCallback(() => setIsPopoverOpen((prevState) => !prevState), []); + return ( + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + anchorPosition="downLeft" + > + + + + {EDIT_CONNECTOR_BUTTON} + + + + {DELETE_CONNECTOR_BUTTON} + + + + ); +}; + +export const ConnectorRowActions = React.memo(ConnectorRowActionsComponent); +ConnectorRowActions.displayName = 'ConnectorRowActions'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx new file mode 100644 index 0000000000000..fb2d36911c4c2 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx @@ -0,0 +1,182 @@ +/* + * 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 { + EuiBadge, + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiInMemoryTable, + EuiSearchBarProps, + EuiSkeletonText, +} from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import { useAssistantContext } from '../../assistant_context'; +import { AIConnector } from '../connector_selector'; +import { useLoadConnectors } from '../use_load_connectors'; +import { + CREATE_CONNECTOR_BUTTON, + MISSING_READ_CONNECTORS_CALLOUT_TITLE, + REFRESH_CONNECTORS_BUTTON, +} from '../translations'; +import { ConnectorRowActions } from './connector_row_actions'; + +export interface Props {} + +const emptyConnectors = [] as AIConnector[]; + +const ConnectorsSettingsComponent: React.FC = () => { + const { http, assistantAvailability, getEditConnectorFlyout } = useAssistantContext(); + const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); + + const onCloseEditFlyout = useCallback(() => { + setEditFlyoutVisibility(false); + }, []); + const [editedConnectorItem, setEditedConnectorItem] = useState(null); + + const { + data: aiConnectors, + refetch: refetchConnectors, + isFetchedAfterMount: areConnectorsFetched, + } = useLoadConnectors({ http }); + + const [query, setQuery] = useState(''); + + const handleOnChange: EuiSearchBarProps['onChange'] = ({ queryText, error }) => { + if (!error) { + setQuery(queryText); + } + }; + + const handleRefetchConnectors = useCallback(() => { + refetchConnectors(); + }, [refetchConnectors]); + + const onClickEditConnector = useCallback((connector: AIConnector) => { + setEditedConnectorItem(connector); + setEditFlyoutVisibility(true); + }, []); + + const columns = [ + { + name: 'Connector name', + truncateText: false, + mobileOptions: { + show: true, + }, + render: (connector: AIConnector) => ( + onClickEditConnector(connector)}> + {connector.name} + + ), + }, + { + field: 'apiProvider', + name: 'Type', + truncateText: false, + mobileOptions: { + show: true, + }, + render: (apiProvider: AIConnector['apiProvider']) => ( + {apiProvider} + ), + }, + { + name: 'Actions', + actions: [ + { + name: 'Action', + icon: 'boxesHorizontal', + render: (connector: AIConnector) => { + return ( + + ); + }, + }, + ], + }, + ]; + + const search: EuiSearchBarProps = { + query, + onChange: handleOnChange, + box: { + schema: true, + placeholder: 'Search for connectors', + }, + toolsRight: [ + + {REFRESH_CONNECTORS_BUTTON} + , + + {CREATE_CONNECTOR_BUTTON} + , + ], + }; + + const onConnectorUpdated = useCallback( + async (updatedConnector) => { + setEditedConnectorItem(updatedConnector); + refetchConnectors(); + }, + [refetchConnectors, setEditedConnectorItem] + ); + + const ConnectorEditFlyout = useMemo( + () => + editedConnectorItem && editFlyoutVisible + ? getEditConnectorFlyout({ + connector: editedConnectorItem, + onClose: onCloseEditFlyout, + onConnectorUpdated, + }) + : null, + [ + editFlyoutVisible, + editedConnectorItem, + getEditConnectorFlyout, + onCloseEditFlyout, + onConnectorUpdated, + ] + ); + + if (!assistantAvailability.hasConnectorsReadPrivilege) { + return ( + + ); + } + + return areConnectorsFetched ? ( + <> + + {ConnectorEditFlyout} + + ) : ( + + ); +}; + +export const ConnectorsSettings = React.memo(ConnectorsSettingsComponent); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx index ab552656fc57f..81166bbf90fa1 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx @@ -35,7 +35,7 @@ export interface ConnectorSetupProps { conversation?: Conversation; isFlyoutMode?: boolean; onSetupComplete?: () => void; - onConversationUpdate: ({ cId, cTitle }: { cId: string; cTitle: string }) => Promise; + onConversationUpdate?: ({ cId, cTitle }: { cId: string; cTitle: string }) => Promise; updateConversationsOnSaveConnector?: boolean; } @@ -198,7 +198,10 @@ export const useConnectorSetup = ({ }); if (updatedConversation) { - onConversationUpdate({ cId: updatedConversation.id, cTitle: updatedConversation.title }); + onConversationUpdate?.({ + cId: updatedConversation.id, + cTitle: updatedConversation.title, + }); refetchConnectors?.(); setIsConnectorModalVisible(false); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts index 4381da8486497..424c7ce893726 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts @@ -149,3 +149,38 @@ export const MISSING_CONNECTOR_CONVERSATION_SETTINGS_LINK = i18n.translate( defaultMessage: 'Conversation Settings', } ); + +export const CREATE_CONNECTOR_BUTTON = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.createConnectorButton', + { + defaultMessage: 'Connector', + } +); + +export const REFRESH_CONNECTORS_BUTTON = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.refreshConnectorsButton', + { + defaultMessage: 'Refresh', + } +); + +export const EDIT_CONNECTOR_BUTTON = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.editConnectorButton', + { + defaultMessage: 'Edit', + } +); + +export const DELETE_CONNECTOR_BUTTON = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.deleteConnectorButton', + { + defaultMessage: 'Delete', + } +); + +export const MISSING_READ_CONNECTORS_CALLOUT_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.connectorMissingCallout.missingReadConnectorsCalloutTitle', + { + defaultMessage: 'Missing Read Connectors Privileges', + } +); diff --git a/x-pack/plugins/security_solution/public/assistant/provider.tsx b/x-pack/plugins/security_solution/public/assistant/provider.tsx index 8516ec455aac0..3a4a9004c81f0 100644 --- a/x-pack/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/plugins/security_solution/public/assistant/provider.tsx @@ -120,7 +120,7 @@ export const AssistantProvider: FC> = ({ children }) http, notifications, storage, - triggersActionsUi: { actionTypeRegistry }, + triggersActionsUi: { actionTypeRegistry, getAddConnectorFlyout, getEditConnectorFlyout }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, } = useKibana().services; const basePath = useBasePath(); @@ -168,6 +168,8 @@ export const AssistantProvider: FC> = ({ children }) baseQuickPrompts={BASE_SECURITY_QUICK_PROMPTS} // to server and plugin start baseSystemPrompts={BASE_SECURITY_SYSTEM_PROMPTS} // to server and plugin start baseConversations={baseConversations} + getAddConnectorFlyout={getAddConnectorFlyout} + getEditConnectorFlyout={getEditConnectorFlyout} getComments={getComments} http={http} title={ASSISTANT_TITLE} From f6a81d43e00a49545d76291a60ecfb3580801737 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 4 Jun 2024 17:42:51 +0100 Subject: [PATCH 02/37] connectors tab --- .../impl/assistant_context/index.tsx | 5 + .../connector_row_actions.tsx | 22 +- .../delete_confirmation_modal.tsx | 8 + .../connector_settings/index.tsx | 193 +++++++++++++++--- .../connectorland/connector_settings/types.ts | 13 ++ .../impl/connectorland/helpers.tsx | 26 +++ .../impl/connectorland/translations.ts | 42 ++++ .../public/assistant/provider.tsx | 8 +- .../components/delete_modal_confirmation.tsx | 30 +-- .../common/get_delete_modal_confirmation.tsx | 29 +++ .../triggers_actions_ui/public/mocks.ts | 7 + .../triggers_actions_ui/public/plugin.ts | 13 ++ 12 files changed, 348 insertions(+), 48 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/delete_confirmation_modal.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/types.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/common/get_delete_modal_confirmation.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx index d55651689b5b1..96800a1001cfa 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -72,6 +72,7 @@ export interface AssistantProviderProps { docLinks: Omit; children: React.ReactNode; getAddConnectorFlyout: TriggersAndActionsUIPublicPluginStart['getAddConnectorFlyout']; + getDeleteConnectorModalConfirmation: TriggersAndActionsUIPublicPluginStart['getDeleteConnectorModalConfirmation']; getEditConnectorFlyout: TriggersAndActionsUIPublicPluginStart['getEditConnectorFlyout']; getComments: (commentArgs: { abortStream: () => void; @@ -130,6 +131,7 @@ export interface UseAssistantContext { setIsStreaming: (isStreaming: boolean) => void; isFlyoutMode: boolean; }) => EuiCommentProps[]; + getDeleteConnectorModalConfirmation: TriggersAndActionsUIPublicPluginStart['getDeleteConnectorModalConfirmation']; http: HttpSetup; knowledgeBase: KnowledgeBaseConfig; getLastConversationId: (conversationTitle?: string) => string; @@ -171,6 +173,7 @@ export const AssistantProvider: React.FC = ({ baseSystemPrompts = BASE_SYSTEM_PROMPTS, children, getAddConnectorFlyout, + getDeleteConnectorModalConfirmation, getEditConnectorFlyout, getComments, http, @@ -303,6 +306,7 @@ export const AssistantProvider: React.FC = ({ docLinks, getAddConnectorFlyout, getEditConnectorFlyout, + getDeleteConnectorModalConfirmation, getComments, http, knowledgeBase: { ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, ...localStorageKnowledgeBase }, @@ -344,6 +348,7 @@ export const AssistantProvider: React.FC = ({ docLinks, getAddConnectorFlyout, getEditConnectorFlyout, + getDeleteConnectorModalConfirmation, getComments, http, localStorageKnowledgeBase, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx index c6f73840ac09f..4b5acec308dea 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx @@ -7,16 +7,21 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui'; import React, { useCallback, useState } from 'react'; -import { AIConnector } from '../connector_selector'; +import { ActionConnectorTableItem } from './types'; import { DELETE_CONNECTOR_BUTTON, EDIT_CONNECTOR_BUTTON } from '../translations'; interface Props { - connector: AIConnector; - onClickEditConnector: (connector: AIConnector) => void; + connector: ActionConnectorTableItem; + onClickEditConnector: (connector: ActionConnectorTableItem) => void; + onClickDeleteConnector: (connector: ActionConnectorTableItem) => void; } -const ConnectorRowActionsComponent: React.FC = ({ connector, onClickEditConnector }) => { +const ConnectorRowActionsComponent: React.FC = ({ + connector, + onClickEditConnector, + onClickDeleteConnector, +}) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const closePopover = useCallback(() => setIsPopoverOpen(false), []); @@ -25,6 +30,11 @@ const ConnectorRowActionsComponent: React.FC = ({ connector, onClickEditC onClickEditConnector(connector); }, [closePopover, onClickEditConnector, connector]); + const handleDeleteConnector = useCallback(() => { + closePopover(); + onClickDeleteConnector(connector); + }, [closePopover, onClickDeleteConnector, connector]); + const onButtonClick = useCallback(() => setIsPopoverOpen((prevState) => !prevState), []); return ( = ({ connector, onClickEditC - {DELETE_CONNECTOR_BUTTON} + + {DELETE_CONNECTOR_BUTTON} + diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/delete_confirmation_modal.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/delete_confirmation_modal.tsx new file mode 100644 index 0000000000000..9b1a3ffb5175a --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/delete_confirmation_modal.tsx @@ -0,0 +1,8 @@ +/* + * 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. + */ + + diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx index fb2d36911c4c2..ebf843171d04c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx @@ -10,40 +10,106 @@ import { EuiButton, EuiButtonEmpty, EuiCallOut, + EuiFlexGroup, + EuiFlexItem, EuiInMemoryTable, EuiSearchBarProps, EuiSkeletonText, } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { Suspense, useCallback, useMemo, useState } from 'react'; +import { ActionConnector, ActionType } from '@kbn/triggers-actions-ui-plugin/public'; +import { getConnectorCompatibility } from '@kbn/actions-plugin/common'; import { useAssistantContext } from '../../assistant_context'; import { AIConnector } from '../connector_selector'; + import { useLoadConnectors } from '../use_load_connectors'; import { + CONNECTORS_TABLE_COLUMN_ACTIONS, + CONNECTORS_TABLE_COLUMN_ACTION_TYPE, + CONNECTORS_TABLE_COLUMN_COMPATIBILITY, + CONNECTORS_TABLE_COLUMN_NAME, CREATE_CONNECTOR_BUTTON, + DELETE_CONNECTOR_CONFIRMATION_MULTIPLE_TITLE, + DELETE_CONNECTOR_CONFIRMATION_SINGLE_TITLE, MISSING_READ_CONNECTORS_CALLOUT_TITLE, REFRESH_CONNECTORS_BUTTON, } from '../translations'; import { ConnectorRowActions } from './connector_row_actions'; +import { useLoadActionTypes } from '../use_load_action_types'; +import { AddConnectorModal } from '../add_connector_modal'; +import { ActionConnectorTableItem } from './types'; +import { deleteActions } from '../helpers'; -export interface Props {} +const emptyConnectors = [] as ActionConnectorTableItem[]; -const emptyConnectors = [] as AIConnector[]; +const ConnectorsSettingsComponent: React.FC = () => { + const { + actionTypeRegistry, + http, + assistantAvailability, + getEditConnectorFlyout, + getDeleteConnectorModalConfirmation, + } = useAssistantContext(); -const ConnectorsSettingsComponent: React.FC = () => { - const { http, assistantAvailability, getEditConnectorFlyout } = useAssistantContext(); + const { + data: aiConnectors, + refetch: refetchConnectors, + isFetchedAfterMount: areConnectorsFetched, + } = useLoadConnectors({ http }); + const { data: actionTypes } = useLoadActionTypes({ http }); + + const aiConnectorTableItems: ActionConnectorTableItem[] | undefined = areConnectorsFetched + ? (aiConnectors ?? []).map((action) => { + const currentActionType = actionTypes?.find( + (actionType) => actionType.id === action.actionTypeId + ); + return { + ...action, + actionType: currentActionType?.name ?? action.actionTypeId, + compatibility: currentActionType + ? getConnectorCompatibility(currentActionType.supportedFeatureIds) + : [], + }; + }) + : undefined; + + // Edit Connector const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); - const onCloseEditFlyout = useCallback(() => { + const handleCloseEditFlyout = useCallback(() => { setEditFlyoutVisibility(false); }, []); const [editedConnectorItem, setEditedConnectorItem] = useState(null); - const { - data: aiConnectors, - refetch: refetchConnectors, - isFetchedAfterMount: areConnectorsFetched, - } = useLoadConnectors({ http }); + // Add Connector + const [isAddConnectorModalVisible, setIsAddConnectorModalVisible] = useState(false); + + const [selectedActionType, setSelectedActionType] = useState(null); + + const onSaveConnector = useCallback( + (connector: ActionConnector) => { + // onConnectorSelectionChange({ + // ...connector, + // }); + refetchConnectors?.(); + // setAddModalVisibility(false); + }, + [refetchConnectors] + ); + + const handleAddConnector = useCallback(() => { + handleCloseEditFlyout(); + setIsAddConnectorModalVisible(true); + }, [handleCloseEditFlyout]); + + const onClickEditConnector = useCallback((connector: ActionConnectorTableItem) => { + setIsAddConnectorModalVisible(false); + setEditedConnectorItem(connector); + setEditFlyoutVisibility(true); + }, []); + + // search const [query, setQuery] = useState(''); const handleOnChange: EuiSearchBarProps['onChange'] = ({ queryText, error }) => { @@ -52,50 +118,99 @@ const ConnectorsSettingsComponent: React.FC = () => { } }; + // refetch + const handleRefetchConnectors = useCallback(() => { refetchConnectors(); }, [refetchConnectors]); - const onClickEditConnector = useCallback((connector: AIConnector) => { - setEditedConnectorItem(connector); - setEditFlyoutVisibility(true); + // delete + + const [connectorsToDelete, setConnectorsToDelete] = useState([]); + + const onClickDeleteConnector = useCallback((connector: ActionConnectorTableItem) => { + const itemIds = [connector.id]; + setConnectorsToDelete(itemIds); }, []); + const DeleteConnectorModalConfirmation = getDeleteConnectorModalConfirmation({ + idsToDelete: connectorsToDelete, + apiDeleteCall: deleteActions, + onDeleted: (deleted: string[]) => { + refetchConnectors(); + }, + onCancel: () => { + setConnectorsToDelete([]); + }, + onErrors: () => { + setConnectorsToDelete([]); + }, + singleTitle: DELETE_CONNECTOR_CONFIRMATION_SINGLE_TITLE, + multipleTitle: DELETE_CONNECTOR_CONFIRMATION_MULTIPLE_TITLE, + setIsLoadingState: () => {}, + }); + const columns = [ { - name: 'Connector name', + name: CONNECTORS_TABLE_COLUMN_NAME, truncateText: false, mobileOptions: { show: true, }, - render: (connector: AIConnector) => ( + render: (connector: ActionConnectorTableItem) => ( onClickEditConnector(connector)}> {connector.name} ), }, { - field: 'apiProvider', - name: 'Type', + field: 'actionType', + name: CONNECTORS_TABLE_COLUMN_ACTION_TYPE, truncateText: false, mobileOptions: { show: true, }, - render: (apiProvider: AIConnector['apiProvider']) => ( - {apiProvider} - ), + render: (actionType: ActionConnectorTableItem['actionType']) => + actionType ? {actionType} : null, + }, + { + name: CONNECTORS_TABLE_COLUMN_COMPATIBILITY, + sortable: false, + truncateText: true, + mobileOptions: { + show: true, + }, + render: (tableItem: ActionConnectorTableItem) => { + return ( + + {tableItem?.compatibility?.map((compatibilityItem: string) => ( + + + {compatibilityItem} + + + ))} + + ); + }, }, { - name: 'Actions', + name: CONNECTORS_TABLE_COLUMN_ACTIONS, actions: [ { - name: 'Action', + name: CONNECTORS_TABLE_COLUMN_ACTIONS, icon: 'boxesHorizontal', - render: (connector: AIConnector) => { + render: (connector: ActionConnectorTableItem) => { return ( ); }, @@ -120,7 +235,12 @@ const ConnectorsSettingsComponent: React.FC = () => { > {REFRESH_CONNECTORS_BUTTON} , - + {CREATE_CONNECTOR_BUTTON} , ], @@ -130,8 +250,9 @@ const ConnectorsSettingsComponent: React.FC = () => { async (updatedConnector) => { setEditedConnectorItem(updatedConnector); refetchConnectors(); + handleCloseEditFlyout(); }, - [refetchConnectors, setEditedConnectorItem] + [handleCloseEditFlyout, refetchConnectors] ); const ConnectorEditFlyout = useMemo( @@ -139,7 +260,7 @@ const ConnectorsSettingsComponent: React.FC = () => { editedConnectorItem && editFlyoutVisible ? getEditConnectorFlyout({ connector: editedConnectorItem, - onClose: onCloseEditFlyout, + onClose: handleCloseEditFlyout, onConnectorUpdated, }) : null, @@ -147,7 +268,7 @@ const ConnectorsSettingsComponent: React.FC = () => { editFlyoutVisible, editedConnectorItem, getEditConnectorFlyout, - onCloseEditFlyout, + handleCloseEditFlyout, onConnectorUpdated, ] ); @@ -166,13 +287,27 @@ const ConnectorsSettingsComponent: React.FC = () => { return areConnectorsFetched ? ( <> + {DeleteConnectorModalConfirmation} {ConnectorEditFlyout} + {isAddConnectorModalVisible && ( + // Crashing management app otherwise + + setIsAddConnectorModalVisible(false)} + onSaveConnector={onSaveConnector} + onSelectActionType={(actionType: ActionType) => setSelectedActionType(actionType)} + selectedActionType={selectedActionType} + /> + + )} ) : ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/types.ts b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/types.ts new file mode 100644 index 0000000000000..ba613134b90ec --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/types.ts @@ -0,0 +1,13 @@ +/* + * 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 { AIConnector } from '../connector_selector'; + +export type ActionConnectorTableItem = AIConnector & { + actionType: string; + compatibility: string[]; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx index c8b17de9906a3..e401db2f07a20 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx @@ -8,6 +8,8 @@ import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types'; import { ActionTypeModel } from '@kbn/triggers-actions-ui-plugin/public'; +import { HttpSetup } from '@kbn/core/public'; +import { BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; // aligns with OpenAiProviderType from '@kbn/stack-connectors-plugin/common/openai/types' enum OpenAiProviderType { @@ -54,3 +56,27 @@ const getAzureApiVersionParameter = (url: string): string | undefined => { const urlSearchParams = new URLSearchParams(new URL(url).search); return urlSearchParams.get('api-version') ?? undefined; }; + +export async function deleteActions({ + ids, + http, +}: { + ids: string[]; + http: HttpSetup; +}): Promise<{ successes: string[]; errors: string[] }> { + const successes: string[] = []; + const errors: string[] = []; + await Promise.all( + ids.map((id) => + http.delete(`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(id)}`) + ) + ).then( + function (fulfilled) { + successes.push(...fulfilled); + }, + function (rejected) { + errors.push(...rejected); + } + ); + return { successes, errors }; +} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts index 424c7ce893726..1c53d9a015881 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts @@ -184,3 +184,45 @@ export const MISSING_READ_CONNECTORS_CALLOUT_TITLE = i18n.translate( defaultMessage: 'Missing Read Connectors Privileges', } ); + +export const CONNECTORS_TABLE_COLUMN_NAME = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.table.column.Name', + { + defaultMessage: 'Connector name', + } +); + +export const CONNECTORS_TABLE_COLUMN_ACTION_TYPE = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.table.column.type', + { + defaultMessage: 'Type', + } +); + +export const CONNECTORS_TABLE_COLUMN_COMPATIBILITY = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.table.column.compatibility', + { + defaultMessage: 'Compatibility', + } +); + +export const CONNECTORS_TABLE_COLUMN_ACTIONS = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.table.column.actions', + { + defaultMessage: 'Actions', + } +); + +export const DELETE_CONNECTOR_CONFIRMATION_SINGLE_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.deleteConnectorConfirmation.singleTitle', + { + defaultMessage: 'connector', + } +); + +export const DELETE_CONNECTOR_CONFIRMATION_MULTIPLE_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.deleteConnectorConfirmation.multipleTitle', + { + defaultMessage: 'connectors', + } +); diff --git a/x-pack/plugins/security_solution/public/assistant/provider.tsx b/x-pack/plugins/security_solution/public/assistant/provider.tsx index 3a4a9004c81f0..16d9df5847a2a 100644 --- a/x-pack/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/plugins/security_solution/public/assistant/provider.tsx @@ -120,7 +120,12 @@ export const AssistantProvider: FC> = ({ children }) http, notifications, storage, - triggersActionsUi: { actionTypeRegistry, getAddConnectorFlyout, getEditConnectorFlyout }, + triggersActionsUi: { + actionTypeRegistry, + getAddConnectorFlyout, + getDeleteConnectorModalConfirmation, + getEditConnectorFlyout, + }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, } = useKibana().services; const basePath = useBasePath(); @@ -169,6 +174,7 @@ export const AssistantProvider: FC> = ({ children }) baseSystemPrompts={BASE_SECURITY_SYSTEM_PROMPTS} // to server and plugin start baseConversations={baseConversations} getAddConnectorFlyout={getAddConnectorFlyout} + getDeleteConnectorModalConfirmation={getDeleteConnectorModalConfirmation} getEditConnectorFlyout={getEditConnectorFlyout} getComments={getComments} http={http} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx index a0334c6ff7694..f8a777cda589a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx @@ -17,18 +17,7 @@ import { CANCEL_BUTTON_TEXT, } from '../sections/rules_list/translations'; -export const DeleteModalConfirmation = ({ - idsToDelete, - apiDeleteCall, - onDeleted, - onCancel, - onErrors, - singleTitle, - multipleTitle, - showWarningText, - warningText, - setIsLoadingState, -}: { +export interface DeleteModalConfirmationProps { idsToDelete: string[]; apiDeleteCall: ({ ids, @@ -45,7 +34,20 @@ export const DeleteModalConfirmation = ({ setIsLoadingState: (isLoading: boolean) => void; showWarningText?: boolean; warningText?: string; -}) => { +} + +export const DeleteModalConfirmation = ({ + idsToDelete, + apiDeleteCall, + onDeleted, + onCancel, + onErrors, + singleTitle, + multipleTitle, + showWarningText, + warningText, + setIsLoadingState, +}: DeleteModalConfirmationProps) => { const [deleteModalFlyoutVisible, setDeleteModalVisibility] = useState(false); useEffect(() => { @@ -102,3 +104,5 @@ export const DeleteModalConfirmation = ({ ); }; +// eslint-disable-next-line import/no-default-export +export { DeleteModalConfirmation as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_delete_modal_confirmation.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_delete_modal_confirmation.tsx new file mode 100644 index 0000000000000..2e78b09db431b --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_delete_modal_confirmation.tsx @@ -0,0 +1,29 @@ +/* + * 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 { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; +import { + DeleteModalConfirmation, + DeleteModalConfirmationProps, +} from '../application/components/delete_modal_confirmation'; +import { ConnectorProvider } from '../application/context/connector_context'; +import { ConnectorServices } from '../types'; + +const queryClient = new QueryClient(); + +export const getDeleteConnectorModalConfirmationLazy = ( + props: DeleteModalConfirmationProps & { connectorServices: ConnectorServices } +) => { + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index 5224a2e0d505f..a4d8451b5994d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -52,6 +52,8 @@ import { getRulesSettingsLinkLazy } from './common/get_rules_settings_link'; import { AlertTableConfigRegistry } from './application/alert_table_config_registry'; import { AlertActionsProps } from './types'; import { AlertSummaryWidgetDependencies } from './application/sections/alert_summary_widget/types'; +import { getDeleteConnectorModalConfirmationLazy } from './common/get_delete_modal_confirmation'; +import { DeleteModalConfirmationProps } from './application/components/delete_modal_confirmation'; function createStartMock(): TriggersAndActionsUIPublicPluginStart { const actionTypeRegistry = new TypeRegistry(); @@ -85,6 +87,11 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart { connectorServices, }); }, + getDeleteConnectorModalConfirmation: ( + props: Omit + ) => { + return getDeleteConnectorModalConfirmationLazy({ ...props, connectorServices }); + }, getAddRuleFlyout: (props) => { return getAddRuleFlyoutLazy({ ...props, diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 829e0bf364562..51ab5f7423a93 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -98,6 +98,8 @@ import { getRulesSettingsLinkLazy } from './common/get_rules_settings_link'; import { getGlobalRuleEventLogListLazy } from './common/get_global_rule_event_log_list'; import { AlertTableConfigRegistry } from './application/alert_table_config_registry'; import { AlertSummaryWidgetDependencies } from './application/sections/alert_summary_widget/types'; +import { DeleteModalConfirmationProps } from './application/components/delete_modal_confirmation'; +import { getDeleteConnectorModalConfirmationLazy } from './common/get_delete_modal_confirmation'; export interface TriggersAndActionsUIPublicPluginSetup { actionTypeRegistry: TypeRegistry; @@ -120,6 +122,9 @@ export interface TriggersAndActionsUIPublicPluginStart { getEditConnectorFlyout: ( props: Omit ) => ReactElement; + getDeleteConnectorModalConfirmation: ( + props: Omit + ) => ReactElement; getAddRuleFlyout: < Params extends RuleTypeParams = RuleTypeParams, MetaData extends RuleTypeMetaData = RuleTypeMetaData @@ -489,6 +494,14 @@ export class Plugin connectorServices: this.connectorServices!, }); }, + getDeleteConnectorModalConfirmation: ( + props: Omit + ) => { + return getDeleteConnectorModalConfirmationLazy({ + ...props, + connectorServices: this.connectorServices!, + }); + }, getAddRuleFlyout: (props) => { return getAddRuleFlyoutLazy({ ...props, From 0f053ba6cfb63456c690eaef2ddaa80d530152fb Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 6 Jun 2024 21:31:15 +0100 Subject: [PATCH 03/37] conversation tab --- .../common/components/formatted_date.tsx | 19 ++ .../common/components/row_actions.tsx} | 36 +- .../assistant/common/hooks/use_settings.ts | 29 ++ .../common/hooks/use_table_search.ts} | 2 - .../conversation_selector_settings/index.tsx | 6 +- .../conversation_selector_settings/types.ts | 12 + .../use_conversation_selector_settings.tsx | 55 +++ .../conversation_settings.tsx | 174 ++-------- .../conversation_streaming_switch.tsx | 59 ++++ .../use_connector_selector.ts | 100 ++++++ .../use_conversation_deleted.tsx | 50 +++ .../use_select_system_prompt.tsx | 93 ++++++ .../index.tsx | 313 ++++++++++++++++++ .../translations.ts | 43 +++ .../index.tsx | 28 ++ .../assistant_settings_management.tsx | 44 +-- .../assistant/settings/use_handle_save.tsx | 53 +++ .../impl/assistant/types.ts | 1 + .../impl/assistant_context/index.tsx | 6 + .../connector_settings/index.tsx | 238 ++++--------- .../use_connector_table.tsx | 187 +++++++++++ .../impl/connectorland/helpers.tsx | 26 +- .../impl/connectorland/translations.ts | 7 + .../impl/content/prompts/system/index.tsx | 4 + .../content/prompts/system/translations.ts | 14 + .../public/assistant/provider.tsx | 2 + .../stack_management/management_settings.tsx | 2 - 27 files changed, 1222 insertions(+), 381 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/formatted_date.tsx rename x-pack/packages/kbn-elastic-assistant/impl/{connectorland/connector_settings/connector_row_actions.tsx => assistant/common/components/row_actions.tsx} (65%) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_settings.ts rename x-pack/packages/kbn-elastic-assistant/impl/{connectorland/connector_settings/delete_confirmation_modal.tsx => assistant/common/hooks/use_table_search.ts} (99%) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/types.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_streaming_switch.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_connector_selector.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deleted.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_select_system_prompt.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_handle_save.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/use_connector_table.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/formatted_date.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/formatted_date.tsx new file mode 100644 index 0000000000000..c641290bc3895 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/formatted_date.tsx @@ -0,0 +1,19 @@ +/* + * 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 moment from 'moment'; +import React from 'react'; +import { useDateFormat, useTimeZone } from '../hooks/use_settings'; + +export const FormattedDate = React.memo<{ dateFormat?: string; value: Date }>( + /* eslint-disable-next-line react-hooks/rules-of-hooks */ + ({ value, dateFormat = useDateFormat() }) => ( + <>{moment.tz(value, useTimeZone()).format(dateFormat)} + ) +); + +FormattedDate.displayName = 'FormattedDate'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/row_actions.tsx similarity index 65% rename from x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/row_actions.tsx index 4b5acec308dea..97360feaebc3c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/connector_row_actions.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/row_actions.tsx @@ -7,33 +7,33 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui'; import React, { useCallback, useState } from 'react'; -import { ActionConnectorTableItem } from './types'; -import { DELETE_CONNECTOR_BUTTON, EDIT_CONNECTOR_BUTTON } from '../translations'; +import { + DELETE_CONNECTOR_BUTTON, + EDIT_CONNECTOR_BUTTON, +} from '../../../connectorland/translations'; -interface Props { - connector: ActionConnectorTableItem; - onClickEditConnector: (connector: ActionConnectorTableItem) => void; - onClickDeleteConnector: (connector: ActionConnectorTableItem) => void; +interface Props { + rowItem: T; + onEdit: (rowItem: T) => void; + onDelete: (rowItem: T) => void; } -const ConnectorRowActionsComponent: React.FC = ({ - connector, - onClickEditConnector, - onClickDeleteConnector, -}) => { +type RowActionsComponentType = (props: Props) => JSX.Element; + +const RowActionsComponent = ({ rowItem, onEdit, onDelete }: Props) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const closePopover = useCallback(() => setIsPopoverOpen(false), []); const handleEditConnector = useCallback(() => { closePopover(); - onClickEditConnector(connector); - }, [closePopover, onClickEditConnector, connector]); + onEdit(rowItem); + }, [closePopover, onEdit, rowItem]); const handleDeleteConnector = useCallback(() => { closePopover(); - onClickDeleteConnector(connector); - }, [closePopover, onClickDeleteConnector, connector]); + onDelete(rowItem); + }, [closePopover, onDelete, rowItem]); const onButtonClick = useCallback(() => setIsPopoverOpen((prevState) => !prevState), []); return ( @@ -42,7 +42,7 @@ const ConnectorRowActionsComponent: React.FC = ({ } @@ -66,5 +66,5 @@ const ConnectorRowActionsComponent: React.FC = ({ ); }; -export const ConnectorRowActions = React.memo(ConnectorRowActionsComponent); -ConnectorRowActions.displayName = 'ConnectorRowActions'; +// 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/common/hooks/use_settings.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_settings.ts new file mode 100644 index 0000000000000..aa9b70963580f --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_settings.ts @@ -0,0 +1,29 @@ +/* + * 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 moment from 'moment'; +import { useAssistantContext } from '../../../assistant_context'; + +export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz' as const; +export const DEFAULT_DATE_FORMAT = 'dateFormat' as const; + +export const useUiSetting = (key: string, defaultValue?: T): T => { + const { settings } = useAssistantContext(); + + if (!settings) { + throw new TypeError('uiSettings service not available in kibana-react context.'); + } + + return settings.client.get(key, defaultValue); +}; + +export const useDateFormat = (): string => useUiSetting(DEFAULT_DATE_FORMAT); + +export const useTimeZone = (): string => { + const timeZone = useUiSetting(DEFAULT_DATE_FORMAT_TZ); + return timeZone === 'Browser' ? moment.tz.guess() : timeZone; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/delete_confirmation_modal.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_table_search.ts similarity index 99% rename from x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/delete_confirmation_modal.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_table_search.ts index 9b1a3ffb5175a..1fec1c76430eb 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/delete_confirmation_modal.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_table_search.ts @@ -4,5 +4,3 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - - 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 286be81f531f3..f4b8f9a79412f 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 @@ -8,7 +8,6 @@ import { EuiButtonIcon, EuiComboBox, - EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -21,6 +20,7 @@ import { css } from '@emotion/react'; import { Conversation } from '../../../..'; import * as i18n from '../conversation_selector/translations'; import { SystemPromptSelectorOption } from '../../prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector'; +import { ConversationSelectorSettingsOption } from './types'; interface Props { conversations: Record; @@ -49,10 +49,6 @@ const getNextConversationTitle = ( : conversationTitles[conversationTitles.indexOf(selectedConversationTitle) + 1]; }; -export type ConversationSelectorSettingsOption = EuiComboBoxOptionOption<{ - isDefault: boolean; -}>; - /** * A disconnected variant of the ConversationSelector component that allows for * modifiable settings without persistence. Also changes some styling and removes diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/types.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/types.ts new file mode 100644 index 0000000000000..548149ffe0c7b --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/types.ts @@ -0,0 +1,12 @@ +/* + * 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 { EuiComboBoxOptionOption } from '@elastic/eui'; + +export type ConversationSelectorSettingsOption = EuiComboBoxOptionOption<{ + isDefault: boolean; +}>; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx new file mode 100644 index 0000000000000..98f47e4e8ddcd --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx @@ -0,0 +1,55 @@ +/* + * 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 { useMemo } from 'react'; +import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; +import { Conversation } from '../../../assistant_context/types'; +import { AIConnector } from '../../../connectorland/connector_selector'; +import { getConnectorTypeTitle } from '../../../connectorland/helpers'; +import { Prompt } from '../../../..'; + +const emptyConversations = {}; + +export interface UseConversationSelectorSettingsProps { + allSystemPrompts: Prompt[]; + actionTypeRegistry: ActionTypeRegistryContract; + connectors: AIConnector[] | undefined; + conversations: Record; +} + +export type ConversationTableItem = Conversation & { + actionType?: string | null; + systemPrompt?: string; +}; + +export const useConversationSelectorSettings = ({ + allSystemPrompts, + actionTypeRegistry, + connectors, + conversations = emptyConversations, +}: UseConversationSelectorSettingsProps) => { + const conversationOptions = useMemo(() => { + return Object.values(conversations).map((conversation) => { + const connector: AIConnector | undefined = connectors?.find( + (c) => c.id === conversation.apiConfig?.connectorId + ); + + const actionType = getConnectorTypeTitle(connector, actionTypeRegistry); + const systemPrompt = allSystemPrompts.find( + ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId + ); + + return { + ...conversation, + actionType, + systemPrompt: systemPrompt?.label ?? systemPrompt?.name, + }; + }); + }, [allSystemPrompts, actionTypeRegistry, connectors, conversations]); + + return conversationOptions; +}; 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 179ff7524bd88..8ea90eaf41d66 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 @@ -33,6 +33,9 @@ import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; import { getGenAiConfig } from '../../../connectorland/helpers'; import { ConversationsBulkActions } from '../../api'; +import { useSelectSystemPrompt } from './use_select_system_prompt'; +import { useConnectorSelector } from './use_connector_selector'; +import { useConversationDeleted } from './use_conversation_deleted'; export interface ConversationSettingsProps { actionTypeRegistry: ActionTypeRegistryContract; @@ -76,22 +79,10 @@ export const ConversationSettings: React.FC = React.m return getDefaultSystemPrompt({ allSystemPrompts, conversation: undefined }); }, [allSystemPrompts]); - const selectedSystemPrompt = useMemo(() => { - return getDefaultSystemPrompt({ allSystemPrompts, conversation: selectedConversation }); - }, [allSystemPrompts, selectedConversation]); - const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ http, }); - const selectedConversationId = useMemo( - () => - selectedConversation?.id === '' - ? selectedConversation.title - : (selectedConversation?.id as string), - [selectedConversation] - ); - // Conversation callbacks // When top level conversation selection changes const onConversationSelectionChange = useCallback( @@ -152,83 +143,21 @@ export const ConversationSettings: React.FC = React.m ] ); - const onConversationDeleted = useCallback( - (conversationTitle: string) => { - const conversationId = - Object.values(conversationSettings).find((c) => c.title === conversationTitle)?.id ?? ''; - const updatedConversationSettings = { ...conversationSettings }; - delete updatedConversationSettings[conversationId]; - setConversationSettings(updatedConversationSettings); - - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - delete: { - ids: [...(conversationsSettingsBulkActions.delete?.ids ?? []), conversationId], - }, - }); - }, - [ - conversationSettings, - conversationsSettingsBulkActions, - setConversationSettings, - setConversationsSettingsBulkActions, - ] - ); + const onConversationDeleted = useConversationDeleted({ + conversationSettings, + conversationsSettingsBulkActions, + setConversationSettings, + setConversationsSettingsBulkActions, + }); - const handleOnSystemPromptSelectionChange = useCallback( - (systemPromptId?: string | undefined) => { - if (selectedConversation != null && selectedConversation.apiConfig) { - const updatedConversation = { - ...selectedConversation, - apiConfig: { - ...selectedConversation.apiConfig, - defaultSystemPromptId: systemPromptId, - }, - }; - setConversationSettings({ - ...conversationSettings, - [updatedConversation.id]: updatedConversation, - }); - if (selectedConversation.id !== '') { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - update: { - ...(conversationsSettingsBulkActions.update ?? {}), - [updatedConversation.id]: { - ...updatedConversation, - ...(conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {}), - apiConfig: { - ...updatedConversation.apiConfig, - ...((conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {} - ).apiConfig ?? {}), - defaultSystemPromptId: systemPromptId, - }, - }, - }, - }); - } else { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - create: { - ...(conversationsSettingsBulkActions.create ?? {}), - [updatedConversation.id]: updatedConversation, - }, - }); - } - } - }, - [ - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - setConversationSettings, - setConversationsSettingsBulkActions, - ] - ); + const { selectedSystemPrompt, handleOnSystemPromptSelectionChange } = useSelectSystemPrompt({ + allSystemPrompts, + conversationSettings, + conversationsSettingsBulkActions, + selectedConversation, + setConversationSettings, + setConversationsSettingsBulkActions, + }); const selectedConnector = useMemo(() => { const selectedConnectorId = selectedConversation?.apiConfig?.connectorId; @@ -243,68 +172,13 @@ export const ConversationSettings: React.FC = React.m [selectedConversation?.apiConfig?.provider] ); - const handleOnConnectorSelectionChange = useCallback( - (connector) => { - if (selectedConversation != null) { - const config = getGenAiConfig(connector); - const updatedConversation = { - ...selectedConversation, - apiConfig: { - ...selectedConversation.apiConfig, - connectorId: connector.id, - actionTypeId: connector.actionTypeId, - provider: config?.apiProvider, - model: config?.defaultModel, - }, - }; - setConversationSettings({ - ...conversationSettings, - [selectedConversationId]: updatedConversation, - }); - if (selectedConversation.id !== '') { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - update: { - ...(conversationsSettingsBulkActions.update ?? {}), - [updatedConversation.id]: { - ...updatedConversation, - ...(conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {}), - apiConfig: { - ...updatedConversation.apiConfig, - ...((conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {} - ).apiConfig ?? {}), - connectorId: connector?.id, - actionTypeId: connector?.actionTypeId, - provider: config?.apiProvider, - model: config?.defaultModel, - }, - }, - }, - }); - } else { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - create: { - ...(conversationsSettingsBulkActions.create ?? {}), - [updatedConversation.id]: updatedConversation, - }, - }); - } - } - }, - [ - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - selectedConversationId, - setConversationSettings, - setConversationsSettingsBulkActions, - ] - ); + const handleOnConnectorSelectionChange = useConnectorSelector({ + selectedConversation, + setConversationSettings, + conversationSettings, + setConversationsSettingsBulkActions, + conversationsSettingsBulkActions, + }); const selectedModel = useMemo(() => { const connectorModel = getGenAiConfig(selectedConnector)?.defaultModel; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_streaming_switch.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_streaming_switch.tsx new file mode 100644 index 0000000000000..c302716595b9d --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_streaming_switch.tsx @@ -0,0 +1,59 @@ +/* + * 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 { EuiFormRow, EuiSwitch, EuiText, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React from 'react'; +import { STREAMING_TITLE, STREAMING_HELP_TEXT_TITLE } from './translations'; + +interface Props { + assistantStreamingEnabled: boolean; + setAssistantStreamingEnabled: React.Dispatch>; + compressed?: boolean; +} + +const ConversationStreamingSwitchComponent: React.FC = ({ + assistantStreamingEnabled, + compressed, + setAssistantStreamingEnabled, +}) => { + const { euiTheme } = useEuiTheme(); + + return ( + + {STREAMING_TITLE} + + } + > + {STREAMING_HELP_TEXT_TITLE}} + checked={assistantStreamingEnabled} + onChange={(e) => setAssistantStreamingEnabled(e.target.checked)} + compressed={compressed} + /> + + ); +}; + +export const ConversationStreamingSwitch = React.memo(ConversationStreamingSwitchComponent); + +ConversationStreamingSwitch.displayName = 'ConversationStreamingSwitch'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_connector_selector.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_connector_selector.ts new file mode 100644 index 0000000000000..e295452ceaa6a --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_connector_selector.ts @@ -0,0 +1,100 @@ +/* + * 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 { useCallback, useMemo } from 'react'; +import { Conversation, ConversationsBulkActions } from '../../../..'; +import { getGenAiConfig } from '../../../connectorland/helpers'; + +interface Props { + selectedConversation?: Conversation; + setConversationSettings: React.Dispatch>>; + conversationSettings: Record; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; + conversationsSettingsBulkActions: ConversationsBulkActions; +} + +export const useConnectorSelector = ({ + selectedConversation, + setConversationSettings, + conversationSettings, + setConversationsSettingsBulkActions, + conversationsSettingsBulkActions, +}: Props) => { + const selectedConversationId = useMemo( + () => + selectedConversation?.id === '' + ? selectedConversation.title + : (selectedConversation?.id as string), + [selectedConversation] + ); + const handleOnConnectorSelectionChange = useCallback( + (connector) => { + if (selectedConversation != null) { + const config = getGenAiConfig(connector); + const updatedConversation = { + ...selectedConversation, + apiConfig: { + ...selectedConversation.apiConfig, + connectorId: connector.id, + actionTypeId: connector.actionTypeId, + provider: config?.apiProvider, + model: config?.defaultModel, + }, + }; + setConversationSettings({ + ...conversationSettings, + [selectedConversationId]: updatedConversation, + }); + if (selectedConversation.id !== '') { + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + update: { + ...(conversationsSettingsBulkActions.update ?? {}), + [updatedConversation.id]: { + ...updatedConversation, + ...(conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {}), + apiConfig: { + ...updatedConversation.apiConfig, + ...((conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {} + ).apiConfig ?? {}), + connectorId: connector?.id, + actionTypeId: connector?.actionTypeId, + provider: config?.apiProvider, + model: config?.defaultModel, + }, + }, + }, + }); + } else { + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + create: { + ...(conversationsSettingsBulkActions.create ?? {}), + [updatedConversation.id]: updatedConversation, + }, + }); + } + } + }, + [ + conversationSettings, + conversationsSettingsBulkActions, + selectedConversation, + selectedConversationId, + setConversationSettings, + setConversationsSettingsBulkActions, + ] + ); + + return handleOnConnectorSelectionChange; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deleted.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deleted.tsx new file mode 100644 index 0000000000000..855f08d588e48 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deleted.tsx @@ -0,0 +1,50 @@ +/* + * 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 { useCallback } from 'react'; +import { Conversation, ConversationsBulkActions } from '../../../..'; + +interface Props { + conversationSettings: Record; + conversationsSettingsBulkActions: ConversationsBulkActions; + setConversationSettings: React.Dispatch>>; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; +} + +export const useConversationDeleted = ({ + conversationSettings, + conversationsSettingsBulkActions, + setConversationSettings, + setConversationsSettingsBulkActions, +}: Props) => { + const onConversationDeleted = useCallback( + (conversationTitle: string) => { + const conversationId = + Object.values(conversationSettings).find((c) => c.title === conversationTitle)?.id ?? ''; + const updatedConversationSettings = { ...conversationSettings }; + delete updatedConversationSettings[conversationId]; + setConversationSettings(updatedConversationSettings); + + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + delete: { + ids: [...(conversationsSettingsBulkActions.delete?.ids ?? []), conversationId], + }, + }); + }, + [ + conversationSettings, + conversationsSettingsBulkActions, + setConversationSettings, + setConversationsSettingsBulkActions, + ] + ); + + return onConversationDeleted; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_select_system_prompt.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_select_system_prompt.tsx new file mode 100644 index 0000000000000..10dd08656d4c1 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_select_system_prompt.tsx @@ -0,0 +1,93 @@ +/* + * 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 { useCallback, useMemo } from 'react'; +import { Conversation, Prompt } from '../../../..'; +import { ConversationsBulkActions } from '../../api'; +import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; + +interface Props { + allSystemPrompts: Prompt[]; + conversationSettings: Record; + conversationsSettingsBulkActions: ConversationsBulkActions; + selectedConversation?: Conversation; + setConversationSettings: React.Dispatch>>; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; +} +export const useSelectSystemPrompt = ({ + allSystemPrompts, + conversationSettings, + conversationsSettingsBulkActions, + selectedConversation, + setConversationSettings, + setConversationsSettingsBulkActions, +}: Props) => { + const selectedSystemPrompt = useMemo(() => { + return getDefaultSystemPrompt({ allSystemPrompts, conversation: selectedConversation }); + }, [allSystemPrompts, selectedConversation]); + const handleOnSystemPromptSelectionChange = useCallback( + (systemPromptId?: string | undefined) => { + if (selectedConversation != null && selectedConversation.apiConfig) { + const updatedConversation = { + ...selectedConversation, + apiConfig: { + ...selectedConversation.apiConfig, + defaultSystemPromptId: systemPromptId, + }, + }; + setConversationSettings({ + ...conversationSettings, + [updatedConversation.id]: updatedConversation, + }); + if (selectedConversation.id !== '') { + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + update: { + ...(conversationsSettingsBulkActions.update ?? {}), + [updatedConversation.id]: { + ...updatedConversation, + ...(conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {}), + apiConfig: { + ...updatedConversation.apiConfig, + ...((conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {} + ).apiConfig ?? {}), + defaultSystemPromptId: systemPromptId, + }, + }, + }, + }); + } else { + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + create: { + ...(conversationsSettingsBulkActions.create ?? {}), + [updatedConversation.id]: updatedConversation, + }, + }); + } + } + }, + [ + conversationSettings, + conversationsSettingsBulkActions, + selectedConversation, + setConversationSettings, + setConversationsSettingsBulkActions, + ] + ); + + return { + selectedSystemPrompt, + handleOnSystemPromptSelectionChange, + }; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx new file mode 100644 index 0000000000000..001f1c26cf790 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx @@ -0,0 +1,313 @@ +/* + * 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 { + EuiInMemoryTable, + EuiPanel, + EuiSpacer, + EuiBadge, + EuiLink, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiFormRow, + EuiBasicTableColumn, + EuiTitle, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, +} from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; + +import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { noop } from 'lodash/fp'; +import { Conversation } from '../../../assistant_context/types'; +import { + ConversationTableItem, + useConversationSelectorSettings, +} from '../conversation_selector_settings/use_conversation_selector_settings'; +import { ConversationStreamingSwitch } from '../conversation_settings/conversation_streaming_switch'; +import { AIConnector, ConnectorSelector } from '../../../connectorland/connector_selector'; +import { FormattedDate } from '../../common/components/formatted_date'; +import { RowActions } from '../../common/components/row_actions'; +import { + CONVERSATIONS_TABLE_COLUMN_ACTIONS, + CONVERSATIONS_TABLE_COLUMN_CONNECTOR, + CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT, + CONVERSATIONS_TABLE_COLUMN_TYPE, + CONVERSATIONS_TABLE_COLUMN_UPDATED_AT, +} from './translations'; +import { SelectSystemPrompt } from '../../prompt_editor/system_prompt/select_system_prompt'; +import { + CONNECTOR_TITLE, + SETTINGS_PROMPT_HELP_TEXT_TITLE, + SETTINGS_PROMPT_TITLE, +} from '../conversation_settings/translations'; +import { Prompt } from '../../types'; +import { useSelectSystemPrompt } from '../conversation_settings/use_select_system_prompt'; +import { useConnectorSelector } from '../conversation_settings/use_connector_selector'; +import { ConversationsBulkActions } from '../../api'; +import { useAssistantContext } from '../../../assistant_context'; +import { CANCEL, SAVE } from '../../settings/translations'; +import { useConversationDeleted } from '../conversation_settings/use_conversation_deleted'; + +interface Props { + actionTypeRegistry: ActionTypeRegistryContract; + allSystemPrompts: Prompt[]; + areConnectorsFetched: boolean; + assistantStreamingEnabled: boolean; + connectors: AIConnector[] | undefined; + conversations: Record; + conversationSettings: Record; + conversationsSettingsBulkActions: ConversationsBulkActions; + handleSave: () => void; + isDisabled?: boolean; + isFlyoutMode: boolean; + resetSettings: () => void; + setAssistantStreamingEnabled: React.Dispatch>; + setConversationSettings: React.Dispatch>>; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; +} + +const ConversationSettingsManagementComponent: React.FC = ({ + actionTypeRegistry, + allSystemPrompts, + areConnectorsFetched, + assistantStreamingEnabled, + connectors, + conversations, + conversationSettings, + conversationsSettingsBulkActions, + handleSave, + isDisabled, + isFlyoutMode, + resetSettings, + setAssistantStreamingEnabled, + setConversationSettings, + setConversationsSettingsBulkActions, +}) => { + const { http } = useAssistantContext(); + const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); + const [selectedConversation, setSelectedConversation] = useState< + ConversationTableItem | undefined + >(undefined); + + const selectedConnector = useMemo(() => { + const selectedConnectorId = selectedConversation?.apiConfig?.connectorId; + if (areConnectorsFetched) { + return connectors?.find((c) => c.id === selectedConnectorId); + } + return undefined; + }, [areConnectorsFetched, connectors, selectedConversation?.apiConfig?.connectorId]); + + const selectedConversationTitle = selectedConversation?.title ?? ''; + + const handleConversationEdited = useCallback((rowItem: ConversationTableItem) => { + setEditFlyoutVisibility(true); + setSelectedConversation(rowItem); + }, []); + + const onConversationDeleted = useConversationDeleted({ + conversationSettings, + conversationsSettingsBulkActions, + setConversationSettings, + setConversationsSettingsBulkActions, + }); + + const handleConversationDeleted = useCallback(() => { + onConversationDeleted(selectedConversationTitle); + }, [onConversationDeleted, selectedConversationTitle]); + + const conversationOptions = useConversationSelectorSettings({ + allSystemPrompts, + actionTypeRegistry, + connectors, + conversations, + }); + + const { selectedSystemPrompt, handleOnSystemPromptSelectionChange } = useSelectSystemPrompt({ + allSystemPrompts, + conversationSettings, + conversationsSettingsBulkActions, + selectedConversation, + setConversationSettings, + setConversationsSettingsBulkActions, + }); + + const handleOnConnectorSelectionChange = useConnectorSelector({ + selectedConversation, + setConversationSettings, + conversationSettings, + setConversationsSettingsBulkActions, + conversationsSettingsBulkActions, + }); + + const columns: Array> = [ + { + name: CONVERSATIONS_TABLE_COLUMN_TYPE, + render: (conversation: ConversationTableItem) => ( + handleConversationEdited(conversation)}> + {conversation.title} + + ), + }, + { + field: 'systemPrompt', + name: CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT, + render: (systemPrompt: ConversationTableItem['systemPrompt']) => + systemPrompt ? {systemPrompt} : null, + }, + { + field: 'actionType', + name: CONVERSATIONS_TABLE_COLUMN_CONNECTOR, + render: (actionType: ConversationTableItem['actionType']) => + actionType ? {actionType} : null, + }, + { + field: 'updatedAt', + name: CONVERSATIONS_TABLE_COLUMN_UPDATED_AT, + render: (updatedAt: ConversationTableItem['updatedAt']) => + updatedAt ? ( + + + + ) : null, + }, + { + name: CONVERSATIONS_TABLE_COLUMN_ACTIONS, + actions: [ + { + name: CONVERSATIONS_TABLE_COLUMN_ACTIONS, + render: (conversation: ConversationTableItem) => { + return ( + + rowItem={conversation} + onEdit={handleConversationEdited} + onDelete={handleConversationDeleted} + /> + ); + }, + }, + ], + }, + ]; + + const onCancelClick = useCallback(() => { + setEditFlyoutVisibility(false); + resetSettings(); + }, [resetSettings]); + + const onSaveClick = useCallback(() => { + handleSave(); + setEditFlyoutVisibility(false); + }, [handleSave]); + return ( + <> + + + + + + {editFlyoutVisible && ( + setEditFlyoutVisibility(false)}> + + +

{selectedConversationTitle}

+
+
+ + + + + + + + } + > + + + + + + + + {CANCEL} + + + + + {SAVE} + + + + +
+ )} + + ); +}; + +export const ConversationSettingsManagement = React.memo(ConversationSettingsManagementComponent); + +ConversationSettingsManagement.displayName = 'ConversationSettingsManagement'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts new file mode 100644 index 0000000000000..d28f1705de5ab --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const CONVERSATIONS_TABLE_COLUMN_TYPE = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.column.type', + { + defaultMessage: 'Conversation Type', + } +); + +export const CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.column.systemPrompt', + { + defaultMessage: 'Title', + } +); + +export const CONVERSATIONS_TABLE_COLUMN_CONNECTOR = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.column.connector', + { + defaultMessage: 'Connector', + } +); + +export const CONVERSATIONS_TABLE_COLUMN_UPDATED_AT = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.column.updatedAt', + { + defaultMessage: 'Date updated', + } +); + +export const CONVERSATIONS_TABLE_COLUMN_ACTIONS = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.column.actions', + { + defaultMessage: 'Actions', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx new file mode 100644 index 0000000000000..9ddd43d8d90df --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiInMemoryTable } from '@elastic/eui'; +import { QuickPrompt } from '../types'; + +interface Props { + quickPrompts: QuickPrompt[]; +} +const QuickPromptSettingsManagementComponent = ({quickPrompts}: Props) => { + cosnt columns = [ + { + field: 'title', + name: 'Title', + } + ]; + return ( +
+ +
+ ); +}; + +export const QuickPromptSettingsManagement = QuickPromptSettingsManagementComponent; 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 f9b335b84b03f..0a526b18aeb87 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 @@ -21,7 +21,6 @@ import { useAssistantContext } from '../../assistant_context'; import { useSettingsUpdater } from './use_settings_updater/use_settings_updater'; import { AnonymizationSettings, - ConversationSettings, EvaluationSettings, KnowledgeBaseSettings, QuickPromptSettings, @@ -31,6 +30,7 @@ import { useLoadConnectors } from '../../connectorland/use_load_connectors'; import { getDefaultConnector } from '../helpers'; import { useFetchAnonymizationFields } from '../api/anonymization_fields/use_fetch_anonymization_fields'; import { ConnectorsSettings } from '../../connectorland/connector_settings'; +import { ConversationSettingsManagement } from '../conversations/converstaion_settings_management'; export const CONNECTORS_TAB = 'CONNECTORS_TAB' as const; export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const; @@ -70,7 +70,11 @@ export const AssistantSettingsManagement: React.FC = React.memo( const { data: anonymizationFields } = useFetchAnonymizationFields(); // Connector details - const { data: connectors } = useLoadConnectors({ + const { + data: connectors, + refetch: refetchConnectors, + isFetchedAfterMount: areConnectorsFetched, + } = useLoadConnectors({ http, }); const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); @@ -109,10 +113,6 @@ export const AssistantSettingsManagement: React.FC = React.memo( } ); - const onHandleSelectedConversationChange = useCallback((conversation?: Conversation) => { - setSelectedConversation(conversation); - }, []); - useEffect(() => { if (selectedConversation != null) { setSelectedConversation(conversationSettings[selectedConversation.title]); @@ -245,25 +245,31 @@ export const AssistantSettingsManagement: React.FC = React.memo( padding-right: 0; `} > - {selectedSettingsTab === CONNECTORS_TAB && } + {selectedSettingsTab === CONNECTORS_TAB && ( + + )} {selectedSettingsTab === CONVERSATIONS_TAB && ( - )} {selectedSettingsTab === QUICK_PROMPTS_TAB && ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_handle_save.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_handle_save.tsx new file mode 100644 index 0000000000000..63f937f2fdf7a --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/use_handle_save.tsx @@ -0,0 +1,53 @@ +/* + * 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 { useCallback } from 'react'; +import { IToasts } from '@kbn/core/public'; +import { Conversation } from '../../..'; +import { SETTINGS_UPDATED_TOAST_TITLE } from './translations'; + +interface Props { + conversationSettings: Record; + defaultSelectedConversation: Conversation; + setSelectedConversationId: React.Dispatch>; + saveSettings: () => void; + setHasPendingChanges: React.Dispatch>; + toasts: IToasts | undefined; +} +export const useHandleSave = ({ + conversationSettings, + defaultSelectedConversation, + setSelectedConversationId, + saveSettings, + setHasPendingChanges, + toasts, +}: Props) => { + const handleSave = useCallback(() => { + // If the selected conversation is deleted, we need to select a new conversation to prevent a crash creating a conversation that already exists + const isSelectedConversationDeleted = + conversationSettings[defaultSelectedConversation.title] == null; + const newSelectedConversationId: string | undefined = Object.keys(conversationSettings)[0]; + if (isSelectedConversationDeleted && newSelectedConversationId != null) { + setSelectedConversationId(conversationSettings[newSelectedConversationId].title); + } + saveSettings(); + toasts?.addSuccess({ + iconType: 'check', + title: SETTINGS_UPDATED_TOAST_TITLE, + }); + setHasPendingChanges(false); + }, [ + conversationSettings, + defaultSelectedConversation.title, + saveSettings, + setHasPendingChanges, + setSelectedConversationId, + toasts, + ]); + + return handleSave; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts index 8b80b87584d35..91ee3468a12d9 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts @@ -15,6 +15,7 @@ export interface Prompt { isDefault?: boolean; // TODO: Should be renamed to isImmutable as this flag is used to prevent users from deleting prompts isNewConversationDefault?: boolean; isFlyoutMode?: boolean; + label?: string; } export interface KnowledgeBaseConfig { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx index 96800a1001cfa..a9a949073a4f2 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -17,6 +17,7 @@ import { import { useLocalStorage, useSessionStorage } from 'react-use'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; import { AssistantFeatures, defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; +import { SettingsStart } from '@kbn/core-ui-settings-browser'; import { updatePromptContexts } from './helpers'; import type { PromptContext, @@ -89,6 +90,7 @@ export interface AssistantProviderProps { http: HttpSetup; baseConversations: Record; nameSpace?: string; + settings: SettingsStart; title?: string; toasts?: IToasts; } @@ -147,6 +149,7 @@ export interface UseAssistantContext { setSelectedSettingsTab: React.Dispatch>; setShowAssistantOverlay: (showAssistantOverlay: ShowAssistantOverlay) => void; showAssistantOverlay: ShowAssistantOverlay; + settings: SettingsStart; setTraceOptions: (traceOptions: { apmUrl: string; langSmithProject: string; @@ -179,6 +182,7 @@ export const AssistantProvider: React.FC = ({ http, baseConversations, nameSpace = DEFAULT_ASSISTANT_NAMESPACE, + settings, title = DEFAULT_ASSISTANT_TITLE, toasts, }) => { @@ -322,6 +326,7 @@ export const AssistantProvider: React.FC = ({ setKnowledgeBase: setLocalStorageKnowledgeBase, setSelectedSettingsTab, setShowAssistantOverlay, + settings, setTraceOptions: setSessionStorageTraceOptions, showAssistantOverlay, title, @@ -361,6 +366,7 @@ export const AssistantProvider: React.FC = ({ setLocalStorageQuickPrompts, setLocalStorageSystemPrompts, setLocalStorageKnowledgeBase, + settings, setSessionStorageTraceOptions, showAssistantOverlay, title, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx index ebf843171d04c..9a2b4824a3ba0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx @@ -5,44 +5,40 @@ * 2.0. */ -import { - EuiBadge, - EuiButton, - EuiButtonEmpty, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiInMemoryTable, - EuiSearchBarProps, - EuiSkeletonText, -} from '@elastic/eui'; +import { EuiCallOut, EuiInMemoryTable, EuiPanel, EuiSkeletonText } from '@elastic/eui'; import React, { Suspense, useCallback, useMemo, useState } from 'react'; -import { ActionConnector, ActionType } from '@kbn/triggers-actions-ui-plugin/public'; -import { getConnectorCompatibility } from '@kbn/actions-plugin/common'; +import { ActionType } from '@kbn/triggers-actions-ui-plugin/public'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +import { UseQueryResult } from '@tanstack/react-query'; import { useAssistantContext } from '../../assistant_context'; import { AIConnector } from '../connector_selector'; -import { useLoadConnectors } from '../use_load_connectors'; import { - CONNECTORS_TABLE_COLUMN_ACTIONS, - CONNECTORS_TABLE_COLUMN_ACTION_TYPE, - CONNECTORS_TABLE_COLUMN_COMPATIBILITY, - CONNECTORS_TABLE_COLUMN_NAME, - CREATE_CONNECTOR_BUTTON, DELETE_CONNECTOR_CONFIRMATION_MULTIPLE_TITLE, DELETE_CONNECTOR_CONFIRMATION_SINGLE_TITLE, MISSING_READ_CONNECTORS_CALLOUT_TITLE, - REFRESH_CONNECTORS_BUTTON, } from '../translations'; -import { ConnectorRowActions } from './connector_row_actions'; + import { useLoadActionTypes } from '../use_load_action_types'; import { AddConnectorModal } from '../add_connector_modal'; import { ActionConnectorTableItem } from './types'; import { deleteActions } from '../helpers'; +import { useConnectorTable } from './use_connector_table'; + +interface Props { + connectors: AIConnector[] | undefined; + refetchConnectors: UseQueryResult['refetch']; + areConnectorsFetched: boolean; +} const emptyConnectors = [] as ActionConnectorTableItem[]; -const ConnectorsSettingsComponent: React.FC = () => { +const ConnectorsSettingsComponent: React.FC = ({ + connectors: aiConnectors, + refetchConnectors, + areConnectorsFetched, +}) => { const { actionTypeRegistry, http, @@ -51,28 +47,8 @@ const ConnectorsSettingsComponent: React.FC = () => { getDeleteConnectorModalConfirmation, } = useAssistantContext(); - const { - data: aiConnectors, - refetch: refetchConnectors, - isFetchedAfterMount: areConnectorsFetched, - } = useLoadConnectors({ http }); const { data: actionTypes } = useLoadActionTypes({ http }); - const aiConnectorTableItems: ActionConnectorTableItem[] | undefined = areConnectorsFetched - ? (aiConnectors ?? []).map((action) => { - const currentActionType = actionTypes?.find( - (actionType) => actionType.id === action.actionTypeId - ); - return { - ...action, - actionType: currentActionType?.name ?? action.actionTypeId, - compatibility: currentActionType - ? getConnectorCompatibility(currentActionType.supportedFeatureIds) - : [], - }; - }) - : undefined; - // Edit Connector const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); @@ -87,164 +63,50 @@ const ConnectorsSettingsComponent: React.FC = () => { const [selectedActionType, setSelectedActionType] = useState(null); - const onSaveConnector = useCallback( - (connector: ActionConnector) => { - // onConnectorSelectionChange({ - // ...connector, - // }); - refetchConnectors?.(); - // setAddModalVisibility(false); - }, - [refetchConnectors] - ); + const onSaveConnector = useCallback(() => { + refetchConnectors?.(); + }, [refetchConnectors]); const handleAddConnector = useCallback(() => { handleCloseEditFlyout(); setIsAddConnectorModalVisible(true); }, [handleCloseEditFlyout]); - const onClickEditConnector = useCallback((connector: ActionConnectorTableItem) => { + const onEditConnector = useCallback((connector: ActionConnectorTableItem) => { setIsAddConnectorModalVisible(false); setEditedConnectorItem(connector); setEditFlyoutVisibility(true); }, []); - // search - const [query, setQuery] = useState(''); - - const handleOnChange: EuiSearchBarProps['onChange'] = ({ queryText, error }) => { - if (!error) { - setQuery(queryText); - } - }; - - // refetch - - const handleRefetchConnectors = useCallback(() => { - refetchConnectors(); - }, [refetchConnectors]); - // delete const [connectorsToDelete, setConnectorsToDelete] = useState([]); - const onClickDeleteConnector = useCallback((connector: ActionConnectorTableItem) => { + const onDeleteConnector = useCallback((connector: ActionConnectorTableItem) => { const itemIds = [connector.id]; setConnectorsToDelete(itemIds); }, []); - const DeleteConnectorModalConfirmation = getDeleteConnectorModalConfirmation({ - idsToDelete: connectorsToDelete, - apiDeleteCall: deleteActions, - onDeleted: (deleted: string[]) => { - refetchConnectors(); - }, - onCancel: () => { - setConnectorsToDelete([]); - }, - onErrors: () => { - setConnectorsToDelete([]); - }, - singleTitle: DELETE_CONNECTOR_CONFIRMATION_SINGLE_TITLE, - multipleTitle: DELETE_CONNECTOR_CONFIRMATION_MULTIPLE_TITLE, - setIsLoadingState: () => {}, - }); - - const columns = [ - { - name: CONNECTORS_TABLE_COLUMN_NAME, - truncateText: false, - mobileOptions: { - show: true, - }, - render: (connector: ActionConnectorTableItem) => ( - onClickEditConnector(connector)}> - {connector.name} - - ), - }, - { - field: 'actionType', - name: CONNECTORS_TABLE_COLUMN_ACTION_TYPE, - truncateText: false, - mobileOptions: { - show: true, - }, - render: (actionType: ActionConnectorTableItem['actionType']) => - actionType ? {actionType} : null, - }, - { - name: CONNECTORS_TABLE_COLUMN_COMPATIBILITY, - sortable: false, - truncateText: true, - mobileOptions: { - show: true, - }, - render: (tableItem: ActionConnectorTableItem) => { - return ( - - {tableItem?.compatibility?.map((compatibilityItem: string) => ( - - - {compatibilityItem} - - - ))} - - ); - }, - }, - { - name: CONNECTORS_TABLE_COLUMN_ACTIONS, - actions: [ - { - name: CONNECTORS_TABLE_COLUMN_ACTIONS, - icon: 'boxesHorizontal', - render: (connector: ActionConnectorTableItem) => { - return ( - - ); - }, + const DeleteConnectorModalConfirmation = useMemo( + () => + getDeleteConnectorModalConfirmation({ + idsToDelete: connectorsToDelete, + apiDeleteCall: deleteActions, + onDeleted: (deleted: string[]) => { + refetchConnectors(); }, - ], - }, - ]; - - const search: EuiSearchBarProps = { - query, - onChange: handleOnChange, - box: { - schema: true, - placeholder: 'Search for connectors', - }, - toolsRight: [ - - {REFRESH_CONNECTORS_BUTTON} - , - - {CREATE_CONNECTOR_BUTTON} - , - ], - }; + onCancel: () => { + setConnectorsToDelete([]); + }, + onErrors: () => { + setConnectorsToDelete([]); + }, + singleTitle: DELETE_CONNECTOR_CONFIRMATION_SINGLE_TITLE, + multipleTitle: DELETE_CONNECTOR_CONFIRMATION_MULTIPLE_TITLE, + setIsLoadingState: () => {}, + }), + [connectorsToDelete, getDeleteConnectorModalConfirmation, refetchConnectors] + ); const onConnectorUpdated = useCallback( async (updatedConnector) => { @@ -273,6 +135,18 @@ const ConnectorsSettingsComponent: React.FC = () => { ] ); + const { search, columns, aiConnectorTableItems } = useConnectorTable({ + actionTypes, + actionTypeRegistry, + aiConnectors, + areConnectorsFetched, + hasConnectorsAllPrivilege: assistantAvailability.hasConnectorsAllPrivilege, + refetchConnectors, + handleAddConnector, + onEditConnector, + onDeleteConnector, + }); + if (!assistantAvailability.hasConnectorsReadPrivilege) { return ( { } return areConnectorsFetched ? ( - <> + {DeleteConnectorModalConfirmation} { /> )} - + ) : ( ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/use_connector_table.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/use_connector_table.tsx new file mode 100644 index 0000000000000..9ea7412e598da --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/use_connector_table.tsx @@ -0,0 +1,187 @@ +/* + * 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 { + EuiBadge, + EuiBasicTableColumn, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSearchBarProps, +} from '@elastic/eui'; +import React, { useState, useMemo } from 'react'; +import { ActionType, ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; +import { getConnectorCompatibility } from '@kbn/actions-plugin/common'; +import { RowActions } from '../../assistant/common/components/row_actions'; +import { AIConnector } from '../connector_selector'; +import { getActionTypeTitle, getGenAiConfig } from '../helpers'; +import { + CONNECTORS_TABLE_COLUMN_ACTIONS, + CONNECTORS_TABLE_COLUMN_ACTION_TYPE, + CONNECTORS_TABLE_COLUMN_COMPATIBILITY, + CONNECTORS_TABLE_COLUMN_NAME, + CREATE_CONNECTOR_BUTTON, + PRECONFIGURED_CONNECTOR, + REFRESH_CONNECTORS_BUTTON, + SEARCH_CONNECTOR_PLACEHOLDER, +} from '../translations'; +import { ActionConnectorTableItem } from './types'; + +interface Props { + actionTypes: ActionType[] | undefined; + actionTypeRegistry: ActionTypeRegistryContract; + aiConnectors: AIConnector[] | undefined; + areConnectorsFetched: boolean; + hasConnectorsAllPrivilege: boolean; + refetchConnectors: () => void; + handleAddConnector: () => void; + onEditConnector: (connector: ActionConnectorTableItem) => void; + onDeleteConnector: (connector: ActionConnectorTableItem) => void; +} +export const useConnectorTable = ({ + actionTypes, + actionTypeRegistry, + aiConnectors, + areConnectorsFetched, + hasConnectorsAllPrivilege, + refetchConnectors, + handleAddConnector, + onEditConnector, + onDeleteConnector, +}: Props) => { + const [query, setQuery] = useState(''); + + const handleOnChange: EuiSearchBarProps['onChange'] = ({ queryText, error }) => { + if (!error) { + setQuery(queryText); + } + }; + + const search: EuiSearchBarProps = useMemo( + () => ({ + query, + onChange: handleOnChange, + box: { + schema: true, + placeholder: SEARCH_CONNECTOR_PLACEHOLDER, + }, + toolsRight: [ + + {REFRESH_CONNECTORS_BUTTON} + , + + {CREATE_CONNECTOR_BUTTON} + , + ], + }), + [areConnectorsFetched, hasConnectorsAllPrivilege, handleAddConnector, refetchConnectors, query] + ); + + const columns: Array> = useMemo( + () => [ + { + name: CONNECTORS_TABLE_COLUMN_NAME, + truncateText: false, + mobileOptions: { + show: true, + }, + render: (connector: ActionConnectorTableItem) => ( + onEditConnector(connector)}>{connector.name} + ), + }, + { + field: 'actionType', + name: CONNECTORS_TABLE_COLUMN_ACTION_TYPE, + truncateText: false, + mobileOptions: { + show: true, + }, + render: (actionType: ActionConnectorTableItem['actionType']) => + actionType ? {actionType} : null, + }, + { + name: CONNECTORS_TABLE_COLUMN_COMPATIBILITY, + truncateText: true, + mobileOptions: { + show: true, + }, + render: (tableItem: ActionConnectorTableItem) => { + return ( + + {tableItem?.compatibility?.map((compatibilityItem: string) => ( + + + {compatibilityItem} + + + ))} + + ); + }, + }, + { + name: CONNECTORS_TABLE_COLUMN_ACTIONS, + actions: [ + { + name: CONNECTORS_TABLE_COLUMN_ACTIONS, + render: (connector: ActionConnectorTableItem) => { + return ( + + rowItem={connector} + onEdit={onEditConnector} + onDelete={onDeleteConnector} + /> + ); + }, + }, + ], + }, + ], + [onDeleteConnector, onEditConnector] + ); + + const aiConnectorTableItems: ActionConnectorTableItem[] | undefined = areConnectorsFetched + ? (aiConnectors ?? []).map((connector) => { + const currentActionType = actionTypes?.find( + (actionType) => actionType.id === connector.actionTypeId + ); + + const connectorTypeTitle = + getGenAiConfig(connector)?.apiProvider ?? + getActionTypeTitle(actionTypeRegistry.get(connector.actionTypeId)); + const actionType = connector.isPreconfigured ? PRECONFIGURED_CONNECTOR : connectorTypeTitle; + return { + ...connector, + actionType, + compatibility: currentActionType + ? getConnectorCompatibility(currentActionType.supportedFeatureIds) + : [], + }; + }) + : undefined; + + return { search, columns, aiConnectorTableItems }; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx index e401db2f07a20..d1e2b01007f67 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx @@ -5,11 +5,15 @@ * 2.0. */ -import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; -import { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types'; -import { ActionTypeModel } from '@kbn/triggers-actions-ui-plugin/public'; +import type { + ActionConnector, + ActionTypeModel, + ActionTypeRegistryContract, +} from '@kbn/triggers-actions-ui-plugin/public'; import { HttpSetup } from '@kbn/core/public'; import { BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; +import { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { PRECONFIGURED_CONNECTOR } from './translations'; // aligns with OpenAiProviderType from '@kbn/stack-connectors-plugin/common/openai/types' enum OpenAiProviderType { @@ -57,6 +61,22 @@ const getAzureApiVersionParameter = (url: string): string | undefined => { return urlSearchParams.get('api-version') ?? undefined; }; +export const getConnectorTypeTitle = ( + connector: ActionConnector | undefined, + actionTypeRegistry: ActionTypeRegistryContract +) => { + if (!connector) { + return null; + } + const connectorTypeTitle = + getGenAiConfig(connector)?.apiProvider ?? + getActionTypeTitle(actionTypeRegistry.get(connector.actionTypeId)); + + const actionType = connector.isPreconfigured ? PRECONFIGURED_CONNECTOR : connectorTypeTitle; + + return actionType; +}; + export async function deleteActions({ ids, http, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts index 1c53d9a015881..a12ebd61129e3 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts @@ -226,3 +226,10 @@ export const DELETE_CONNECTOR_CONFIRMATION_MULTIPLE_TITLE = i18n.translate( defaultMessage: 'connectors', } ); + +export const SEARCH_CONNECTOR_PLACEHOLDER = i18n.translate( + 'xpack.elasticAssistant.assistant.connectors.searchConnectorPlaceholder', + { + defaultMessage: 'Search for connectors', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/index.tsx index e2034cc62c33a..a73fbf4854ef1 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/index.tsx @@ -7,8 +7,10 @@ import { Prompt } from '../../../..'; import { + DEFAULT_SYSTEM_PROMPT_LABEL, DEFAULT_SYSTEM_PROMPT_NAME, DEFAULT_SYSTEM_PROMPT_NON_I18N, + SUPERHERO_SYSTEM_PROMPT_LABEL, SUPERHERO_SYSTEM_PROMPT_NAME, SUPERHERO_SYSTEM_PROMPT_NON_I18N, } from './translations'; @@ -22,11 +24,13 @@ export const BASE_SYSTEM_PROMPTS: Prompt[] = [ content: DEFAULT_SYSTEM_PROMPT_NON_I18N, name: DEFAULT_SYSTEM_PROMPT_NAME, promptType: 'system', + label: DEFAULT_SYSTEM_PROMPT_LABEL, }, { id: 'CB9FA555-B59F-4F71-AFF9-8A891AC5BC28', content: SUPERHERO_SYSTEM_PROMPT_NON_I18N, name: SUPERHERO_SYSTEM_PROMPT_NAME, promptType: 'system', + label: SUPERHERO_SYSTEM_PROMPT_LABEL, }, ]; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/translations.ts index eecc3b6dea246..8ce92919de1cb 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/content/prompts/system/translations.ts @@ -39,6 +39,13 @@ export const DEFAULT_SYSTEM_PROMPT_NAME = i18n.translate( } ); +export const DEFAULT_SYSTEM_PROMPT_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.content.prompts.system.defaultSystemPromptLabel', + { + defaultMessage: 'Default', + } +); + export const SUPERHERO_SYSTEM_PROMPT_NON_I18N = `${YOU_ARE_A_HELPFUL_EXPERT_ASSISTANT} ${IF_YOU_DONT_KNOW_THE_ANSWER} ${SUPERHERO_PERSONALITY}`; @@ -49,6 +56,13 @@ export const SUPERHERO_SYSTEM_PROMPT_NAME = i18n.translate( } ); +export const SUPERHERO_SYSTEM_PROMPT_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptLabel', + { + defaultMessage: 'Enhanced', + } +); + export const SYSTEM_PROMPT_CONTEXT_NON_I18N = (context: string) => { return `CONTEXT:\n"""\n${context}\n"""`; }; diff --git a/x-pack/plugins/security_solution/public/assistant/provider.tsx b/x-pack/plugins/security_solution/public/assistant/provider.tsx index 16d9df5847a2a..106078f792d08 100644 --- a/x-pack/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/plugins/security_solution/public/assistant/provider.tsx @@ -119,6 +119,7 @@ export const AssistantProvider: FC> = ({ children }) const { http, notifications, + settings, storage, triggersActionsUi: { actionTypeRegistry, @@ -178,6 +179,7 @@ export const AssistantProvider: FC> = ({ children }) getEditConnectorFlyout={getEditConnectorFlyout} getComments={getComments} http={http} + settings={settings} title={ASSISTANT_TITLE} toasts={toasts} > 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 7282e5e85e437..d7d4c180442ba 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 @@ -37,11 +37,9 @@ export const ManagementSettings = React.memo(() => { onFetch: onFetchedConversations, isAssistantEnabled, }); - const [selectedConversationId, setSelectedConversationId] = useState( WELCOME_CONVERSATION_TITLE ); - const { getDefaultConversation } = useConversation(); const currentConversation = useMemo( From 889ef91b487bc8afe6a8bc3afb2928c4205811ec Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 17 Jun 2024 14:37:28 +0100 Subject: [PATCH 04/37] add prompts --- .../use_fetch_current_user_conversations.ts | 1 + .../flyout/index.tsx | 78 ++++ .../flyout/translations.ts | 22 + .../flyout/use_flyout_modal_visibility.ts | 26 ++ .../row_actions.tsx | 41 +- .../common/components/formatted_date.tsx | 19 - .../assistant/common/hooks/use_settings.ts | 29 -- .../common/hooks/use_table_search.ts | 6 - .../use_conversation_selector_settings.tsx | 15 +- .../conversation_settings.tsx | 184 +-------- .../conversation_settings_editor.tsx | 267 ++++++++++++ .../use_select_system_prompt.tsx | 93 ----- .../index.tsx | 382 ++++++++---------- .../translations.ts | 9 +- .../system_prompt_editor.tsx | 338 ++++++++++++++++ .../system_prompt_settings.tsx | 328 +-------------- .../system_prompt_modal/translations.ts | 14 - .../system_prompt_modal/types.ts | 23 ++ .../index.tsx | 221 ++++++++++ .../translations.ts | 58 +++ .../quick_prompt_editor.tsx | 217 ++++++++++ .../quick_prompt_settings.tsx | 203 +--------- .../helpers.ts | 31 ++ .../index.tsx | 174 +++++++- .../translations.ts | 65 +++ .../assistant_settings_management.tsx | 76 ++-- .../impl/assistant/settings/translations.ts | 7 + .../impl/assistant_context/index.tsx | 31 +- .../connector_settings/index.tsx | 191 --------- .../connectorland/connector_settings/types.ts | 13 - .../use_connector_table.tsx | 187 --------- .../connector_settings_management/index.tsx | 57 +++ .../translations.ts | 30 ++ .../impl/connectorland/helpers.tsx | 27 +- .../impl/connectorland/translations.ts | 56 --- .../index.tsx | 78 ++++ .../settings/anonymization_settings/index.tsx | 44 +- .../use_anonymization_list_update.tsx | 66 +++ .../context_editor/index.tsx | 4 +- .../stats/allowed_stat/index.tsx | 14 +- .../stats/anonymized_stat/index.tsx | 14 +- .../stats/available_stat/index.tsx | 13 +- .../stats/constants.ts | 1 + .../data_anonymization_editor/stats/index.tsx | 16 +- .../public/assistant/provider.tsx | 14 +- .../stack_management/management_settings.tsx | 3 +- 46 files changed, 2110 insertions(+), 1676 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/index.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/translations.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility.ts rename x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/{ => assisttant_settings_management}/row_actions.tsx (66%) delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/formatted_date.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_settings.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_table_search.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_select_system_prompt.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/types.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/translations.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/helpers.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/translations.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/types.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/use_connector_table.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/index.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/translations.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonomization_settings_management/index.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/use_anonymization_list_update.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts index 58be08317d40c..08cbbeaef20a4 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts @@ -26,6 +26,7 @@ export interface UseFetchCurrentUserConversationsParams { signal?: AbortSignal | undefined; refetchOnWindowFocus?: boolean; isAssistantEnabled: boolean; + query?: { page: number; perPage: number }; } /** diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/index.tsx new file mode 100644 index 0000000000000..d82dc77f7a505 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/index.tsx @@ -0,0 +1,78 @@ +/* + * 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 { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiTitle, +} from '@elastic/eui'; +import React from 'react'; +import * as i18n from './translations'; + +interface Props { + children: React.ReactNode; + title: string; + flyoutVisible: boolean; + onClose: () => void; + onSaveCancelled: () => void; + onSaveConfirmed: () => void; +} + +const FlyoutComponent: React.FC = ({ + title, + flyoutVisible, + children, + onClose, + onSaveCancelled, + onSaveConfirmed, +}) => { + return flyoutVisible ? ( + + + +

{title}

+
+
+ {children} + + + + + {i18n.FLYOUT_CANCEL_BUTTON_TITLE} + + + + + {i18n.FLYOUT_SAVE_BUTTON_TITLE} + + + + +
+ ) : null; +}; + +export const Flyout = React.memo(FlyoutComponent); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/translations.ts new file mode 100644 index 0000000000000..f25348cd8b5d6 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const FLYOUT_SAVE_BUTTON_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.flyout.saveButtonTitle', + { + defaultMessage: 'Save', + } +); + +export const FLYOUT_CANCEL_BUTTON_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.flyout.cancelButtonTitle', + { + defaultMessage: 'Cancel', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility.ts new file mode 100644 index 0000000000000..01291b6f77252 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility.ts @@ -0,0 +1,26 @@ +/* + * 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 { useState } from 'react'; + +export const useFlyoutModalVisibility = () => { + const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); + + const openFlyout = () => { + setIsFlyoutOpen(true); + }; + + const closeFlyout = () => { + setIsFlyoutOpen(false); + }; + + return { + isFlyoutOpen, + openFlyout, + closeFlyout, + }; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/row_actions.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/row_actions.tsx similarity index 66% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/row_actions.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/row_actions.tsx index 97360feaebc3c..7eeaf07f930d2 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/row_actions.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/row_actions.tsx @@ -11,32 +11,33 @@ import React, { useCallback, useState } from 'react'; import { DELETE_CONNECTOR_BUTTON, EDIT_CONNECTOR_BUTTON, -} from '../../../connectorland/translations'; +} from '../../../../connectorland/translations'; interface Props { rowItem: T; - onEdit: (rowItem: T) => void; - onDelete: (rowItem: T) => void; + onEdit?: (rowItem: T) => void; + onDelete?: (rowItem: T) => void; + disabled?: boolean; } type RowActionsComponentType = (props: Props) => JSX.Element; -const RowActionsComponent = ({ rowItem, onEdit, onDelete }: Props) => { +const RowActionsComponent = ({ disabled, rowItem, onEdit, onDelete }: Props) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const closePopover = useCallback(() => setIsPopoverOpen(false), []); const handleEditConnector = useCallback(() => { closePopover(); - onEdit(rowItem); + onEdit?.(rowItem); }, [closePopover, onEdit, rowItem]); const handleDeleteConnector = useCallback(() => { closePopover(); - onDelete(rowItem); + onDelete?.(rowItem); }, [closePopover, onDelete, rowItem]); const onButtonClick = useCallback(() => setIsPopoverOpen((prevState) => !prevState), []); - return ( + return onEdit || onDelete ? ( ({ rowItem, onEdit, onDelete }: Props) => { anchorPosition="downLeft" > - - - {EDIT_CONNECTOR_BUTTON} - - - - - {DELETE_CONNECTOR_BUTTON} - - + {onEdit != null && ( + + + {EDIT_CONNECTOR_BUTTON} + + + )} + {onDelete != null && ( + + + {DELETE_CONNECTOR_BUTTON} + + + )} - ); + ) : null; }; // casting to correctly infer the param of onEdit and onDelete when reusing this component diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/formatted_date.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/formatted_date.tsx deleted file mode 100644 index c641290bc3895..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/formatted_date.tsx +++ /dev/null @@ -1,19 +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 moment from 'moment'; -import React from 'react'; -import { useDateFormat, useTimeZone } from '../hooks/use_settings'; - -export const FormattedDate = React.memo<{ dateFormat?: string; value: Date }>( - /* eslint-disable-next-line react-hooks/rules-of-hooks */ - ({ value, dateFormat = useDateFormat() }) => ( - <>{moment.tz(value, useTimeZone()).format(dateFormat)} - ) -); - -FormattedDate.displayName = 'FormattedDate'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_settings.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_settings.ts deleted file mode 100644 index aa9b70963580f..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_settings.ts +++ /dev/null @@ -1,29 +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 moment from 'moment'; -import { useAssistantContext } from '../../../assistant_context'; - -export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz' as const; -export const DEFAULT_DATE_FORMAT = 'dateFormat' as const; - -export const useUiSetting = (key: string, defaultValue?: T): T => { - const { settings } = useAssistantContext(); - - if (!settings) { - throw new TypeError('uiSettings service not available in kibana-react context.'); - } - - return settings.client.get(key, defaultValue); -}; - -export const useDateFormat = (): string => useUiSetting(DEFAULT_DATE_FORMAT); - -export const useTimeZone = (): string => { - const timeZone = useUiSetting(DEFAULT_DATE_FORMAT_TZ); - return timeZone === 'Browser' ? moment.tz.guess() : timeZone; -}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_table_search.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_table_search.ts deleted file mode 100644 index 1fec1c76430eb..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/hooks/use_table_search.ts +++ /dev/null @@ -1,6 +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. - */ diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx index 98f47e4e8ddcd..9a60f19209200 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx @@ -11,6 +11,7 @@ import { Conversation } from '../../../assistant_context/types'; import { AIConnector } from '../../../connectorland/connector_selector'; import { getConnectorTypeTitle } from '../../../connectorland/helpers'; import { Prompt } from '../../../..'; +import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; const emptyConversations = {}; @@ -26,7 +27,7 @@ export type ConversationTableItem = Conversation & { systemPrompt?: string; }; -export const useConversationSelectorSettings = ({ +export const useConversationsList = ({ allSystemPrompts, actionTypeRegistry, connectors, @@ -39,14 +40,22 @@ export const useConversationSelectorSettings = ({ ); const actionType = getConnectorTypeTitle(connector, actionTypeRegistry); - const systemPrompt = allSystemPrompts.find( + const systemPrompt: Prompt | undefined = allSystemPrompts.find( ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId ); + const defaultSystemPrompt = getDefaultSystemPrompt({ + allSystemPrompts, + conversation, + }); return { ...conversation, actionType, - systemPrompt: systemPrompt?.label ?? systemPrompt?.name, + systemPrompt: + systemPrompt?.label ?? + systemPrompt?.name ?? + defaultSystemPrompt?.label ?? + defaultSystemPrompt?.name, }; }); }, [allSystemPrompts, actionTypeRegistry, connectors, conversations]); 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 8ea90eaf41d66..9c89fc27302bc 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 @@ -6,36 +6,29 @@ */ import { - EuiFormRow, - EuiLink, EuiTitle, EuiText, EuiHorizontalRule, EuiSpacer, + EuiFormRow, EuiSwitch, } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { HttpSetup } from '@kbn/core-http-browser'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common'; -import { noop } from 'lodash/fp'; + import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; import { Conversation, Prompt } from '../../../..'; import * as i18n from './translations'; -import * as i18nModel from '../../../connectorland/models/model_selector/translations'; -import { AIConnector, ConnectorSelector } from '../../../connectorland/connector_selector'; -import { SelectSystemPrompt } from '../../prompt_editor/system_prompt/select_system_prompt'; -import { ModelSelector } from '../../../connectorland/models/model_selector/model_selector'; +import { AIConnector } from '../../../connectorland/connector_selector'; + import { ConversationSelectorSettings } from '../conversation_selector_settings'; import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; -import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; -import { getGenAiConfig } from '../../../connectorland/helpers'; + import { ConversationsBulkActions } from '../../api'; -import { useSelectSystemPrompt } from './use_select_system_prompt'; -import { useConnectorSelector } from './use_connector_selector'; import { useConversationDeleted } from './use_conversation_deleted'; +import { ConversationSettingsEditor } from './conversation_settings_editor'; export interface ConversationSettingsProps { actionTypeRegistry: ActionTypeRegistryContract; @@ -79,10 +72,6 @@ export const ConversationSettings: React.FC = React.m return getDefaultSystemPrompt({ allSystemPrompts, conversation: undefined }); }, [allSystemPrompts]); - const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ - http, - }); - // Conversation callbacks // When top level conversation selection changes const onConversationSelectionChange = useCallback( @@ -150,96 +139,6 @@ export const ConversationSettings: React.FC = React.m setConversationsSettingsBulkActions, }); - const { selectedSystemPrompt, handleOnSystemPromptSelectionChange } = useSelectSystemPrompt({ - allSystemPrompts, - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - setConversationSettings, - setConversationsSettingsBulkActions, - }); - - const selectedConnector = useMemo(() => { - const selectedConnectorId = selectedConversation?.apiConfig?.connectorId; - if (areConnectorsFetched) { - return connectors?.find((c) => c.id === selectedConnectorId); - } - return undefined; - }, [areConnectorsFetched, connectors, selectedConversation?.apiConfig?.connectorId]); - - const selectedProvider = useMemo( - () => selectedConversation?.apiConfig?.provider, - [selectedConversation?.apiConfig?.provider] - ); - - const handleOnConnectorSelectionChange = useConnectorSelector({ - selectedConversation, - setConversationSettings, - conversationSettings, - setConversationsSettingsBulkActions, - conversationsSettingsBulkActions, - }); - - const selectedModel = useMemo(() => { - const connectorModel = getGenAiConfig(selectedConnector)?.defaultModel; - // Prefer conversation configuration over connector default - return selectedConversation?.apiConfig?.model ?? connectorModel; - }, [selectedConnector, selectedConversation?.apiConfig?.model]); - - const handleOnModelSelectionChange = useCallback( - (model?: string) => { - if (selectedConversation != null && selectedConversation.apiConfig) { - const updatedConversation = { - ...selectedConversation, - apiConfig: { - ...selectedConversation.apiConfig, - model, - }, - }; - setConversationSettings({ - ...conversationSettings, - [updatedConversation.id]: updatedConversation, - }); - if (selectedConversation.id !== '') { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - update: { - ...(conversationsSettingsBulkActions.update ?? {}), - [updatedConversation.id]: { - ...updatedConversation, - ...(conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {}), - apiConfig: { - ...updatedConversation.apiConfig, - ...((conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {} - ).apiConfig ?? {}), - model, - }, - }, - }, - }); - } else { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - create: { - ...(conversationsSettingsBulkActions.create ?? {}), - [updatedConversation.id]: updatedConversation, - }, - }); - } - } - }, - [ - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - setConversationSettings, - setConversationsSettingsBulkActions, - ] - ); return ( <> @@ -256,67 +155,18 @@ export const ConversationSettings: React.FC = React.m onConversationSelectionChange={onConversationSelectionChange} /> - - - - - - - - } - > - - + - {selectedConnector?.isPreconfigured === false && - selectedProvider === OpenAiProviderType.OpenAi && ( - - - - )}

{i18n.SETTINGS_ALL_TITLE}

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 new file mode 100644 index 0000000000000..691ad911be3a5 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx @@ -0,0 +1,267 @@ +/* + * 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 { EuiFormRow, EuiLink } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; + +import { HttpSetup } from '@kbn/core-http-browser'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common'; +import { noop } from 'lodash/fp'; +import { Conversation, Prompt } from '../../../..'; +import * as i18n from './translations'; +import * as i18nModel from '../../../connectorland/models/model_selector/translations'; + +import { ConnectorSelector } from '../../../connectorland/connector_selector'; +import { SelectSystemPrompt } from '../../prompt_editor/system_prompt/select_system_prompt'; +import { ModelSelector } from '../../../connectorland/models/model_selector/model_selector'; +import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; +import { getGenAiConfig } from '../../../connectorland/helpers'; +import { ConversationsBulkActions } from '../../api'; +import { useConnectorSelector } from './use_connector_selector'; +import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; + +export interface ConversationSettingsEditorProps { + allSystemPrompts: Prompt[]; + conversationSettings: Record; + conversationsSettingsBulkActions: ConversationsBulkActions; + http: HttpSetup; + isDisabled?: boolean; + isFlyoutMode: boolean; + selectedConversation?: Conversation; + setConversationSettings: React.Dispatch>>; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; +} + +/** + * Settings for adding/removing conversation and configuring default system prompt and connector. + */ +export const ConversationSettingsEditor: React.FC = React.memo( + ({ + allSystemPrompts, + selectedConversation, + conversationSettings, + http, + isDisabled = false, + isFlyoutMode, + setConversationSettings, + conversationsSettingsBulkActions, + setConversationsSettingsBulkActions, + }) => { + const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ + http, + }); + + const selectedSystemPrompt = useMemo(() => { + return getDefaultSystemPrompt({ allSystemPrompts, conversation: selectedConversation }); + }, [allSystemPrompts, selectedConversation]); + const handleOnSystemPromptSelectionChange = useCallback( + (systemPromptId?: string | undefined) => { + if (selectedConversation != null && selectedConversation.apiConfig) { + const updatedConversation = { + ...selectedConversation, + apiConfig: { + ...selectedConversation.apiConfig, + defaultSystemPromptId: systemPromptId, + }, + }; + setConversationSettings({ + ...conversationSettings, + [updatedConversation.id]: updatedConversation, + }); + if (selectedConversation.id !== '') { + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + update: { + ...(conversationsSettingsBulkActions.update ?? {}), + [updatedConversation.id]: { + ...updatedConversation, + ...(conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {}), + apiConfig: { + ...updatedConversation.apiConfig, + ...((conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {} + ).apiConfig ?? {}), + defaultSystemPromptId: systemPromptId, + }, + }, + }, + }); + } else { + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + create: { + ...(conversationsSettingsBulkActions.create ?? {}), + [updatedConversation.id]: updatedConversation, + }, + }); + } + } + }, + [ + conversationSettings, + conversationsSettingsBulkActions, + selectedConversation, + setConversationSettings, + setConversationsSettingsBulkActions, + ] + ); + + const selectedConnector = useMemo(() => { + const selectedConnectorId = selectedConversation?.apiConfig?.connectorId; + if (areConnectorsFetched) { + return connectors?.find((c) => c.id === selectedConnectorId); + } + return undefined; + }, [areConnectorsFetched, connectors, selectedConversation?.apiConfig?.connectorId]); + + const selectedProvider = useMemo( + () => selectedConversation?.apiConfig?.provider, + [selectedConversation?.apiConfig?.provider] + ); + + const handleOnConnectorSelectionChange = useConnectorSelector({ + selectedConversation, + setConversationSettings, + conversationSettings, + setConversationsSettingsBulkActions, + conversationsSettingsBulkActions, + }); + + const selectedModel = useMemo(() => { + const connectorModel = getGenAiConfig(selectedConnector)?.defaultModel; + // Prefer conversation configuration over connector default + return selectedConversation?.apiConfig?.model ?? connectorModel; + }, [selectedConnector, selectedConversation?.apiConfig?.model]); + + const handleOnModelSelectionChange = useCallback( + (model?: string) => { + if (selectedConversation != null && selectedConversation.apiConfig) { + const updatedConversation = { + ...selectedConversation, + apiConfig: { + ...selectedConversation.apiConfig, + model, + }, + }; + setConversationSettings({ + ...conversationSettings, + [updatedConversation.id]: updatedConversation, + }); + if (selectedConversation.id !== '') { + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + update: { + ...(conversationsSettingsBulkActions.update ?? {}), + [updatedConversation.id]: { + ...updatedConversation, + ...(conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {}), + apiConfig: { + ...updatedConversation.apiConfig, + ...((conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {} + ).apiConfig ?? {}), + model, + }, + }, + }, + }); + } else { + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + create: { + ...(conversationsSettingsBulkActions.create ?? {}), + [updatedConversation.id]: updatedConversation, + }, + }); + } + } + }, + [ + conversationSettings, + conversationsSettingsBulkActions, + selectedConversation, + setConversationSettings, + setConversationsSettingsBulkActions, + ] + ); + return ( + <> + + + + + + + + } + > + + + + {selectedConnector?.isPreconfigured === false && + selectedProvider === OpenAiProviderType.OpenAi && ( + + + + )} + + ); + } +); +ConversationSettingsEditor.displayName = 'ConversationSettingsEditor'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_select_system_prompt.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_select_system_prompt.tsx deleted file mode 100644 index 10dd08656d4c1..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_select_system_prompt.tsx +++ /dev/null @@ -1,93 +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 { useCallback, useMemo } from 'react'; -import { Conversation, Prompt } from '../../../..'; -import { ConversationsBulkActions } from '../../api'; -import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; - -interface Props { - allSystemPrompts: Prompt[]; - conversationSettings: Record; - conversationsSettingsBulkActions: ConversationsBulkActions; - selectedConversation?: Conversation; - setConversationSettings: React.Dispatch>>; - setConversationsSettingsBulkActions: React.Dispatch< - React.SetStateAction - >; -} -export const useSelectSystemPrompt = ({ - allSystemPrompts, - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - setConversationSettings, - setConversationsSettingsBulkActions, -}: Props) => { - const selectedSystemPrompt = useMemo(() => { - return getDefaultSystemPrompt({ allSystemPrompts, conversation: selectedConversation }); - }, [allSystemPrompts, selectedConversation]); - const handleOnSystemPromptSelectionChange = useCallback( - (systemPromptId?: string | undefined) => { - if (selectedConversation != null && selectedConversation.apiConfig) { - const updatedConversation = { - ...selectedConversation, - apiConfig: { - ...selectedConversation.apiConfig, - defaultSystemPromptId: systemPromptId, - }, - }; - setConversationSettings({ - ...conversationSettings, - [updatedConversation.id]: updatedConversation, - }); - if (selectedConversation.id !== '') { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - update: { - ...(conversationsSettingsBulkActions.update ?? {}), - [updatedConversation.id]: { - ...updatedConversation, - ...(conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {}), - apiConfig: { - ...updatedConversation.apiConfig, - ...((conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {} - ).apiConfig ?? {}), - defaultSystemPromptId: systemPromptId, - }, - }, - }, - }); - } else { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - create: { - ...(conversationsSettingsBulkActions.create ?? {}), - [updatedConversation.id]: updatedConversation, - }, - }); - } - } - }, - [ - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - setConversationSettings, - setConversationsSettingsBulkActions, - ] - ); - - return { - selectedSystemPrompt, - handleOnSystemPromptSelectionChange, - }; -}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx index 001f1c26cf790..fb3b618ea9c8b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx @@ -6,210 +6,212 @@ */ import { - EuiInMemoryTable, EuiPanel, EuiSpacer, EuiBadge, EuiLink, - EuiFlyout, - EuiFlyoutHeader, - EuiFlyoutBody, - EuiFormRow, EuiBasicTableColumn, - EuiTitle, - EuiFlyoutFooter, - EuiFlexGroup, - EuiFlexItem, - EuiButtonEmpty, - EuiButton, + EuiConfirmModal, + EuiInMemoryTable, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { noop } from 'lodash/fp'; +import { FormattedDate } from '@kbn/i18n-react'; import { Conversation } from '../../../assistant_context/types'; import { ConversationTableItem, - useConversationSelectorSettings, + useConversationsList, } from '../conversation_selector_settings/use_conversation_selector_settings'; import { ConversationStreamingSwitch } from '../conversation_settings/conversation_streaming_switch'; -import { AIConnector, ConnectorSelector } from '../../../connectorland/connector_selector'; -import { FormattedDate } from '../../common/components/formatted_date'; -import { RowActions } from '../../common/components/row_actions'; -import { - CONVERSATIONS_TABLE_COLUMN_ACTIONS, - CONVERSATIONS_TABLE_COLUMN_CONNECTOR, - CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT, - CONVERSATIONS_TABLE_COLUMN_TYPE, - CONVERSATIONS_TABLE_COLUMN_UPDATED_AT, -} from './translations'; -import { SelectSystemPrompt } from '../../prompt_editor/system_prompt/select_system_prompt'; -import { - CONNECTOR_TITLE, - SETTINGS_PROMPT_HELP_TEXT_TITLE, - SETTINGS_PROMPT_TITLE, -} from '../conversation_settings/translations'; +import { AIConnector } from '../../../connectorland/connector_selector'; +import { RowActions } from '../../common/components/assisttant_settings_management/row_actions'; +import * as i18n from './translations'; + import { Prompt } from '../../types'; -import { useSelectSystemPrompt } from '../conversation_settings/use_select_system_prompt'; -import { useConnectorSelector } from '../conversation_settings/use_connector_selector'; import { ConversationsBulkActions } from '../../api'; import { useAssistantContext } from '../../../assistant_context'; -import { CANCEL, SAVE } from '../../settings/translations'; import { useConversationDeleted } from '../conversation_settings/use_conversation_deleted'; +import { useFlyoutModalVisibility } from '../../common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility'; +import { Flyout } from '../../common/components/assisttant_settings_management/flyout'; +import { CANCEL, DELETE } from '../../settings/translations'; +import { ConversationSettingsEditor } from '../conversation_settings/conversation_settings_editor'; interface Props { actionTypeRegistry: ActionTypeRegistryContract; allSystemPrompts: Prompt[]; - areConnectorsFetched: boolean; assistantStreamingEnabled: boolean; connectors: AIConnector[] | undefined; conversations: Record; - conversationSettings: Record; conversationsSettingsBulkActions: ConversationsBulkActions; handleSave: () => void; isDisabled?: boolean; isFlyoutMode: boolean; + refetchConversations: () => void; resetSettings: () => void; setAssistantStreamingEnabled: React.Dispatch>; setConversationSettings: React.Dispatch>>; setConversationsSettingsBulkActions: React.Dispatch< React.SetStateAction >; + selectedConversation: Conversation | undefined; + onSelectedConversationChange: (conversation?: Conversation) => void; } const ConversationSettingsManagementComponent: React.FC = ({ actionTypeRegistry, allSystemPrompts, - areConnectorsFetched, assistantStreamingEnabled, connectors, conversations, - conversationSettings, conversationsSettingsBulkActions, handleSave, isDisabled, isFlyoutMode, + onSelectedConversationChange, + refetchConversations, resetSettings, + selectedConversation, setAssistantStreamingEnabled, setConversationSettings, setConversationsSettingsBulkActions, }) => { const { http } = useAssistantContext(); - const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); - const [selectedConversation, setSelectedConversation] = useState< - ConversationTableItem | undefined - >(undefined); + const { + isFlyoutOpen: editFlyoutVisible, + openFlyout: openEditFlyout, + closeFlyout: closeEditFlyout, + } = useFlyoutModalVisibility(); + const [deleteConversation, setDeleteConversation] = useState(); - const selectedConnector = useMemo(() => { - const selectedConnectorId = selectedConversation?.apiConfig?.connectorId; - if (areConnectorsFetched) { - return connectors?.find((c) => c.id === selectedConnectorId); - } - return undefined; - }, [areConnectorsFetched, connectors, selectedConversation?.apiConfig?.connectorId]); + const { + isFlyoutOpen: deleteConfirmModalVisibility, + openFlyout: openConfirmModal, + closeFlyout: closeConfirmModal, + } = useFlyoutModalVisibility(); - const selectedConversationTitle = selectedConversation?.title ?? ''; - - const handleConversationEdited = useCallback((rowItem: ConversationTableItem) => { - setEditFlyoutVisibility(true); - setSelectedConversation(rowItem); - }, []); + const onEditActionClicked = useCallback( + (rowItem: ConversationTableItem) => { + openEditFlyout(); + onSelectedConversationChange(rowItem); + }, + [onSelectedConversationChange, openEditFlyout] + ); const onConversationDeleted = useConversationDeleted({ - conversationSettings, + conversationSettings: conversations, conversationsSettingsBulkActions, setConversationSettings, setConversationsSettingsBulkActions, }); - const handleConversationDeleted = useCallback(() => { - onConversationDeleted(selectedConversationTitle); - }, [onConversationDeleted, selectedConversationTitle]); + const onDeleteActionClicked = useCallback( + (rowItem: ConversationTableItem) => { + setDeleteConversation(rowItem); + closeEditFlyout(); + openConfirmModal(); + }, + [closeEditFlyout, openConfirmModal] + ); - const conversationOptions = useConversationSelectorSettings({ + const onDeleteConfirmed = useCallback(() => { + if (!deleteConversation) return; + onConversationDeleted(deleteConversation.title); + closeConfirmModal(); + refetchConversations(); + }, [deleteConversation, onConversationDeleted, closeConfirmModal, refetchConversations]); + + const onDeleteCancelled = useCallback(() => { + setDeleteConversation(null); + closeConfirmModal(); + resetSettings(); + }, [closeConfirmModal, resetSettings]); + + const conversationOptions = useConversationsList({ allSystemPrompts, actionTypeRegistry, connectors, conversations, }); - const { selectedSystemPrompt, handleOnSystemPromptSelectionChange } = useSelectSystemPrompt({ - allSystemPrompts, - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - setConversationSettings, - setConversationsSettingsBulkActions, - }); + const onEditFlyoutClosed = useCallback(() => { + closeEditFlyout(); + resetSettings(); + }, [closeEditFlyout, resetSettings]); - const handleOnConnectorSelectionChange = useConnectorSelector({ - selectedConversation, - setConversationSettings, - conversationSettings, - setConversationsSettingsBulkActions, - conversationsSettingsBulkActions, - }); + const onEditFlyoutSaved = useCallback(() => { + handleSave(); + closeEditFlyout(); + refetchConversations(); + }, [closeEditFlyout, handleSave, refetchConversations]); - const columns: Array> = [ - { - name: CONVERSATIONS_TABLE_COLUMN_TYPE, - render: (conversation: ConversationTableItem) => ( - handleConversationEdited(conversation)}> - {conversation.title} - - ), - }, - { - field: 'systemPrompt', - name: CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT, - render: (systemPrompt: ConversationTableItem['systemPrompt']) => - systemPrompt ? {systemPrompt} : null, - }, - { - field: 'actionType', - name: CONVERSATIONS_TABLE_COLUMN_CONNECTOR, - render: (actionType: ConversationTableItem['actionType']) => - actionType ? {actionType} : null, - }, - { - field: 'updatedAt', - name: CONVERSATIONS_TABLE_COLUMN_UPDATED_AT, - render: (updatedAt: ConversationTableItem['updatedAt']) => - updatedAt ? ( - - - - ) : null, - }, - { - name: CONVERSATIONS_TABLE_COLUMN_ACTIONS, - actions: [ - { - name: CONVERSATIONS_TABLE_COLUMN_ACTIONS, - render: (conversation: ConversationTableItem) => { - return ( - - rowItem={conversation} - onEdit={handleConversationEdited} - onDelete={handleConversationDeleted} + const columns: Array> = useMemo( + () => [ + { + name: i18n.CONVERSATIONS_TABLE_COLUMN_TYPE, + render: (conversation: ConversationTableItem) => ( + onEditActionClicked(conversation)}>{conversation.title} + ), + }, + { + field: 'systemPrompt', + name: i18n.CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT, + render: (systemPrompt: ConversationTableItem['systemPrompt']) => + systemPrompt ? {systemPrompt} : null, + }, + { + field: 'actionType', + name: i18n.CONVERSATIONS_TABLE_COLUMN_CONNECTOR, + render: (actionType: ConversationTableItem['actionType']) => + actionType ? {actionType} : null, + }, + { + field: 'updatedAt', + name: i18n.CONVERSATIONS_TABLE_COLUMN_UPDATED_AT, + render: (updatedAt: ConversationTableItem['updatedAt']) => + updatedAt ? ( + + - ); + + ) : null, + }, + { + name: i18n.CONVERSATIONS_TABLE_COLUMN_ACTIONS, + actions: [ + { + name: i18n.CONVERSATIONS_TABLE_COLUMN_ACTIONS, + render: (conversation: ConversationTableItem) => { + return ( + + rowItem={conversation} + onEdit={onEditActionClicked} + onDelete={onDeleteActionClicked} + /> + ); + }, }, - }, - ], - }, - ]; + ], + }, + ], + [onDeleteActionClicked, onEditActionClicked] + ); - const onCancelClick = useCallback(() => { - setEditFlyoutVisibility(false); - resetSettings(); - }, [resetSettings]); + const sorting = useMemo( + () => ({ + sort: { + field: 'updatedAt', + direction: 'desc' as const, + }, + }), + [] + ); - const onSaveClick = useCallback(() => { - handleSave(); - setEditFlyoutVisibility(false); - }, [handleSave]); return ( <> @@ -219,90 +221,48 @@ const ConversationSettingsManagementComponent: React.FC = ({ compressed={false} /> - + {editFlyoutVisible && ( - setEditFlyoutVisibility(false)}> - - -

{selectedConversationTitle}

-
-
- - - - - - - - } - > - - - - - - - - {CANCEL} - - - - - {SAVE} - - - - -
+ + + + )} + {deleteConfirmModalVisibility && deleteConversation?.title && ( + +

+ )} ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts index d28f1705de5ab..783e641640b9c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts @@ -17,7 +17,7 @@ export const CONVERSATIONS_TABLE_COLUMN_TYPE = i18n.translate( export const CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT = i18n.translate( 'xpack.elasticAssistant.assistant.conversationSettings.column.systemPrompt', { - defaultMessage: 'Title', + defaultMessage: 'System prompt', } ); @@ -41,3 +41,10 @@ export const CONVERSATIONS_TABLE_COLUMN_ACTIONS = i18n.translate( defaultMessage: 'Actions', } ); + +export const DELETE_CONVERSATION_CONFIRMATION_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.deleteConfirmation.title', + { + defaultMessage: 'conversation', + } +); 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 new file mode 100644 index 0000000000000..5f19726b1497a --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_editor.tsx @@ -0,0 +1,338 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo } from 'react'; +import { + EuiFormRow, + EuiTextArea, + EuiCheckbox, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; + +import { keyBy } from 'lodash/fp'; + +import { css } from '@emotion/react'; +import { ApiConfig } from '@kbn/elastic-assistant-common'; +import { AIConnector } from '../../../../connectorland/connector_selector'; +import { Conversation, Prompt } from '../../../../..'; +import * as i18n from './translations'; +import { ConversationMultiSelector } from './conversation_multi_selector/conversation_multi_selector'; +import { SystemPromptSelector } from './system_prompt_selector/system_prompt_selector'; +import { TEST_IDS } from '../../../constants'; +import { ConversationsBulkActions } from '../../../api'; +import { getSelectedConversations } from '../../../quick_prompts/quick_prompt_settings_management.tsx/helpers'; + +interface Props { + conversationSettings: Record; + conversationsSettingsBulkActions: ConversationsBulkActions; + onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; + selectedSystemPrompt: Prompt | undefined; + setUpdatedSystemPromptSettings: React.Dispatch>; + setConversationSettings: React.Dispatch>>; + systemPromptSettings: Prompt[]; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; + defaultConnector?: AIConnector; +} + +/** + * Settings for adding/removing system prompts. Configure name, prompt and default conversations. + */ +export const SystemPromptEditorComponent: React.FC = ({ + conversationSettings, + onSelectedSystemPromptChange, + selectedSystemPrompt, + setUpdatedSystemPromptSettings, + setConversationSettings, + systemPromptSettings, + conversationsSettingsBulkActions, + setConversationsSettingsBulkActions, + defaultConnector, +}) => { + // Prompt + const promptContent = useMemo( + () => selectedSystemPrompt?.content ?? '', + [selectedSystemPrompt?.content] + ); + + const handlePromptContentChange = useCallback( + (e: React.ChangeEvent) => { + if (selectedSystemPrompt != null) { + setUpdatedSystemPromptSettings((prev): Prompt[] => { + const alreadyExists = prev.some((sp) => sp.id === selectedSystemPrompt.id); + + if (alreadyExists) { + return prev.map((sp): Prompt => { + if (sp.id === selectedSystemPrompt.id) { + return { + ...sp, + content: e.target.value, + }; + } + return sp; + }); + } + + return prev; + }); + } + }, + [selectedSystemPrompt, setUpdatedSystemPromptSettings] + ); + + // Conversations this system prompt should be a default for + const conversationOptions = useMemo( + () => Object.values(conversationSettings), + [conversationSettings] + ); + const selectedConversations = useMemo(() => { + return selectedSystemPrompt != null + ? getSelectedConversations(conversationSettings, selectedSystemPrompt.id) + : []; + }, [conversationSettings, selectedSystemPrompt]); + + const handleConversationSelectionChange = useCallback( + (currentPromptConversations: Conversation[]) => { + const currentPromptConversationTitles = currentPromptConversations.map( + (convo) => convo.title + ); + const getDefaultSystemPromptId = (convo: Conversation) => + currentPromptConversationTitles.includes(convo.title) + ? selectedSystemPrompt?.id + : convo.apiConfig && convo.apiConfig.defaultSystemPromptId === selectedSystemPrompt?.id + ? // 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 + undefined + : // leave it as it is .. if that conversation was neither added nor removed. + convo.apiConfig?.defaultSystemPromptId; + + if (selectedSystemPrompt != null) { + setConversationSettings((prev) => + keyBy( + 'title', + /* + * updatedConversationWithPrompts calculates the present of prompt for + * each conversation. Based on the values of selected conversation, it goes + * 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 ?? '', + }, + }), + })) + ) + ); + + let updatedConversationsSettingsBulkActions = { ...conversationsSettingsBulkActions }; + Object.values(conversationSettings).forEach((convo) => { + const getApiConfig = (): ApiConfig | {} => { + if (convo.apiConfig) { + return { + apiConfig: { + ...convo.apiConfig, + defaultSystemPromptId: getDefaultSystemPromptId(convo), + }, + }; + } + return {}; + }; + const createOperation = + convo.id === '' + ? { + create: { + ...(updatedConversationsSettingsBulkActions.create ?? {}), + [convo.id]: { + ...convo, + ...(convo.apiConfig + ? { + apiConfig: { + ...convo.apiConfig, + defaultSystemPromptId: getDefaultSystemPromptId(convo), + }, + } + : {}), + }, + }, + } + : {}; + + const updateOperation = + convo.id !== '' + ? { + update: { + ...(updatedConversationsSettingsBulkActions.update ?? {}), + [convo.id]: { + ...(updatedConversationsSettingsBulkActions.update + ? updatedConversationsSettingsBulkActions.update[convo.id] ?? {} + : {}), + ...getApiConfig(), + }, + }, + } + : {}; + + updatedConversationsSettingsBulkActions = { + ...updatedConversationsSettingsBulkActions, + ...createOperation, + ...updateOperation, + }; + }); + setConversationsSettingsBulkActions(updatedConversationsSettingsBulkActions); + } + }, + [ + conversationSettings, + conversationsSettingsBulkActions, + defaultConnector?.actionTypeId, + defaultConnector?.id, + selectedSystemPrompt, + setConversationSettings, + setConversationsSettingsBulkActions, + ] + ); + + // Whether this system prompt should be the default for new conversations + const isNewConversationDefault = useMemo( + () => selectedSystemPrompt?.isNewConversationDefault ?? false, + [selectedSystemPrompt?.isNewConversationDefault] + ); + + const handleNewConversationDefaultChange = useCallback( + (e) => { + const isChecked = e.target.checked; + + if (selectedSystemPrompt != null) { + setUpdatedSystemPromptSettings((prev) => { + return prev.map((pp) => { + return { + ...pp, + isNewConversationDefault: selectedSystemPrompt.id === pp.id && isChecked, + }; + }); + }); + } + }, + [selectedSystemPrompt, setUpdatedSystemPromptSettings] + ); + + // When top level system prompt selection changes + const onSystemPromptSelectionChange = useCallback( + (systemPrompt?: Prompt | string) => { + const isNew = typeof systemPrompt === 'string'; + const newSelectedSystemPrompt: Prompt | undefined = isNew + ? { + id: systemPrompt ?? '', + content: '', + name: systemPrompt ?? '', + promptType: 'system', + } + : systemPrompt; + + if (newSelectedSystemPrompt != null) { + setUpdatedSystemPromptSettings((prev) => { + const alreadyExists = prev.some((sp) => sp.id === newSelectedSystemPrompt.id); + + if (!alreadyExists) { + return [...prev, newSelectedSystemPrompt]; + } + + return prev; + }); + } + + onSelectedSystemPromptChange(newSelectedSystemPrompt); + }, + [onSelectedSystemPromptChange, setUpdatedSystemPromptSettings] + ); + + const onSystemPromptDeleted = useCallback( + (id: string) => { + setUpdatedSystemPromptSettings((prev) => prev.filter((sp) => sp.id !== id)); + }, + [setUpdatedSystemPromptSettings] + ); + + return ( + <> + + + + + + + + + + + + + {i18n.SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION} + + + + + } + checked={isNewConversationDefault} + onChange={handleNewConversationDefaultChange} + compressed + /> + + + ); +}; + +export const SystemPromptEditor = React.memo(SystemPromptEditorComponent); + +SystemPromptEditor.displayName = 'SystemPromptEditor'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx index defe0d90e8c8c..c228cc7df49c6 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx @@ -5,50 +5,17 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; -import { - EuiFormRow, - EuiTextArea, - EuiCheckbox, - EuiIcon, - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiText, - EuiHorizontalRule, - EuiSpacer, -} from '@elastic/eui'; +import React from 'react'; +import { EuiTitle, EuiText, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; -import { keyBy } from 'lodash/fp'; - -import { css } from '@emotion/react'; -import { ApiConfig } from '@kbn/elastic-assistant-common'; -import { AIConnector } from '../../../../connectorland/connector_selector'; -import { Conversation, Prompt } from '../../../../..'; import * as i18n from './translations'; -import { ConversationMultiSelector } from './conversation_multi_selector/conversation_multi_selector'; -import { SystemPromptSelector } from './system_prompt_selector/system_prompt_selector'; -import { TEST_IDS } from '../../../constants'; -import { ConversationsBulkActions } from '../../../api'; - -interface Props { - conversationSettings: Record; - conversationsSettingsBulkActions: ConversationsBulkActions; - onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; - selectedSystemPrompt: Prompt | undefined; - setUpdatedSystemPromptSettings: React.Dispatch>; - setConversationSettings: React.Dispatch>>; - systemPromptSettings: Prompt[]; - setConversationsSettingsBulkActions: React.Dispatch< - React.SetStateAction - >; - defaultConnector?: AIConnector; -} +import { SystemPromptEditor } from './system_prompt_editor'; +import { SystemPromptSettingsProps } from './types'; /** * Settings for adding/removing system prompts. Configure name, prompt and default conversations. */ -export const SystemPromptSettings: React.FC = React.memo( +export const SystemPromptSettings: React.FC = React.memo( ({ conversationSettings, onSelectedSystemPromptChange, @@ -60,226 +27,6 @@ export const SystemPromptSettings: React.FC = React.memo( setConversationsSettingsBulkActions, defaultConnector, }) => { - // Prompt - const promptContent = useMemo( - () => selectedSystemPrompt?.content ?? '', - [selectedSystemPrompt?.content] - ); - - const handlePromptContentChange = useCallback( - (e: React.ChangeEvent) => { - if (selectedSystemPrompt != null) { - setUpdatedSystemPromptSettings((prev): Prompt[] => { - const alreadyExists = prev.some((sp) => sp.id === selectedSystemPrompt.id); - - if (alreadyExists) { - return prev.map((sp): Prompt => { - if (sp.id === selectedSystemPrompt.id) { - return { - ...sp, - content: e.target.value, - }; - } - return sp; - }); - } - - return prev; - }); - } - }, - [selectedSystemPrompt, setUpdatedSystemPromptSettings] - ); - - // Conversations this system prompt should be a default for - const conversationOptions = useMemo( - () => Object.values(conversationSettings), - [conversationSettings] - ); - const selectedConversations = useMemo(() => { - return selectedSystemPrompt != null - ? Object.values(conversationSettings).filter( - (conversation) => - conversation.apiConfig?.defaultSystemPromptId === selectedSystemPrompt.id - ) - : []; - }, [conversationSettings, selectedSystemPrompt]); - - const handleConversationSelectionChange = useCallback( - (currentPromptConversations: Conversation[]) => { - const currentPromptConversationTitles = currentPromptConversations.map( - (convo) => convo.title - ); - const getDefaultSystemPromptId = (convo: Conversation) => - currentPromptConversationTitles.includes(convo.title) - ? selectedSystemPrompt?.id - : convo.apiConfig && convo.apiConfig.defaultSystemPromptId === selectedSystemPrompt?.id - ? // 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 - undefined - : // leave it as it is .. if that conversation was neither added nor removed. - convo.apiConfig?.defaultSystemPromptId; - - if (selectedSystemPrompt != null) { - setConversationSettings((prev) => - keyBy( - 'title', - /* - * updatedConversationWithPrompts calculates the present of prompt for - * each conversation. Based on the values of selected conversation, it goes - * 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 ?? '', - }, - }), - })) - ) - ); - - let updatedConversationsSettingsBulkActions = { ...conversationsSettingsBulkActions }; - Object.values(conversationSettings).forEach((convo) => { - const getApiConfig = (): ApiConfig | {} => { - if (convo.apiConfig) { - return { - apiConfig: { - ...convo.apiConfig, - defaultSystemPromptId: getDefaultSystemPromptId(convo), - }, - }; - } - return {}; - }; - const createOperation = - convo.id === '' - ? { - create: { - ...(updatedConversationsSettingsBulkActions.create ?? {}), - [convo.id]: { - ...convo, - ...(convo.apiConfig - ? { - apiConfig: { - ...convo.apiConfig, - defaultSystemPromptId: getDefaultSystemPromptId(convo), - }, - } - : {}), - }, - }, - } - : {}; - - const updateOperation = - convo.id !== '' - ? { - update: { - ...(updatedConversationsSettingsBulkActions.update ?? {}), - [convo.id]: { - ...(updatedConversationsSettingsBulkActions.update - ? updatedConversationsSettingsBulkActions.update[convo.id] ?? {} - : {}), - ...getApiConfig(), - }, - }, - } - : {}; - - updatedConversationsSettingsBulkActions = { - ...updatedConversationsSettingsBulkActions, - ...createOperation, - ...updateOperation, - }; - }); - setConversationsSettingsBulkActions(updatedConversationsSettingsBulkActions); - } - }, - [ - conversationSettings, - conversationsSettingsBulkActions, - defaultConnector?.actionTypeId, - defaultConnector?.id, - selectedSystemPrompt, - setConversationSettings, - setConversationsSettingsBulkActions, - ] - ); - - // Whether this system prompt should be the default for new conversations - const isNewConversationDefault = useMemo( - () => selectedSystemPrompt?.isNewConversationDefault ?? false, - [selectedSystemPrompt?.isNewConversationDefault] - ); - - const handleNewConversationDefaultChange = useCallback( - (e) => { - const isChecked = e.target.checked; - - if (selectedSystemPrompt != null) { - setUpdatedSystemPromptSettings((prev) => { - return prev.map((pp) => { - return { - ...pp, - isNewConversationDefault: selectedSystemPrompt.id === pp.id && isChecked, - }; - }); - }); - } - }, - [selectedSystemPrompt, setUpdatedSystemPromptSettings] - ); - - // When top level system prompt selection changes - const onSystemPromptSelectionChange = useCallback( - (systemPrompt?: Prompt | string) => { - const isNew = typeof systemPrompt === 'string'; - const newSelectedSystemPrompt: Prompt | undefined = isNew - ? { - id: systemPrompt ?? '', - content: '', - name: systemPrompt ?? '', - promptType: 'system', - } - : systemPrompt; - - if (newSelectedSystemPrompt != null) { - setUpdatedSystemPromptSettings((prev) => { - const alreadyExists = prev.some((sp) => sp.id === newSelectedSystemPrompt.id); - - if (!alreadyExists) { - return [...prev, newSelectedSystemPrompt]; - } - - return prev; - }); - } - - onSelectedSystemPromptChange(newSelectedSystemPrompt); - }, - [onSelectedSystemPromptChange, setUpdatedSystemPromptSettings] - ); - - const onSystemPromptDeleted = useCallback( - (id: string) => { - setUpdatedSystemPromptSettings((prev) => prev.filter((sp) => sp.id !== id)); - }, - [setUpdatedSystemPromptSettings] - ); - return ( <> @@ -289,60 +36,17 @@ export const SystemPromptSettings: React.FC = React.memo( {i18n.SETTINGS_DESCRIPTION} - - - - - - - - - - - - - {i18n.SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION} - - - - - } - checked={isNewConversationDefault} - onChange={handleNewConversationDefaultChange} - compressed - /> - + ); } 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..c9cd7bde54797 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 @@ -68,17 +68,3 @@ export const SYSTEM_PROMPT_DEFAULT_CONVERSATIONS_HELP_TEXT = i18n.translate( defaultMessage: 'Conversations that should use this System Prompt by default', } ); - -export const CANCEL = i18n.translate( - 'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.slCancelButtonTitle', - { - defaultMessage: 'Cancel', - } -); - -export const SAVE = i18n.translate( - 'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.slSaveButtonTitle', - { - defaultMessage: 'Save', - } -); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/types.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/types.ts new file mode 100644 index 0000000000000..4fa30c39436e9 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/types.ts @@ -0,0 +1,23 @@ +/* + * 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 { AIConnector } from '../../../../connectorland/connector_selector'; +import { Conversation, Prompt } from '../../../../..'; +import { ConversationsBulkActions } from '../../../api'; + +export interface SystemPromptSettingsProps { + conversationSettings: Record; + conversationsSettingsBulkActions: ConversationsBulkActions; + onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; + selectedSystemPrompt: Prompt | undefined; + setUpdatedSystemPromptSettings: React.Dispatch>; + setConversationSettings: React.Dispatch>>; + systemPromptSettings: Prompt[]; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; + defaultConnector?: AIConnector; +} 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 new file mode 100644 index 0000000000000..28cf547b15e7e --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -0,0 +1,221 @@ +/* + * 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 { + EuiInMemoryTable, + EuiPanel, + EuiConfirmModal, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiBadge, +} from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import { Conversation, ConversationsBulkActions } from '../../../../..'; +import { AIConnector } from '../../../../connectorland/connector_selector'; +import { Flyout } from '../../../common/components/assisttant_settings_management/flyout'; +import { useFlyoutModalVisibility } from '../../../common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility'; +import { RowActions } from '../../../common/components/assisttant_settings_management/row_actions'; +import { getSystemPromptsList } from '../../../quick_prompts/quick_prompt_settings_management.tsx/helpers'; +import { CANCEL, DELETE } from '../../../settings/translations'; +import { Prompt } from '../../../types'; +import { SystemPromptEditor } from '../system_prompt_modal/system_prompt_editor'; +import { SETTINGS_TITLE } from '../system_prompt_modal/translations'; +import * as i18n from './translations'; + +interface Props { + conversations: Record; + conversationSettings: Record; + conversationsSettingsBulkActions: ConversationsBulkActions; + onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; + selectedSystemPrompt: Prompt | undefined; + setUpdatedSystemPromptSettings: React.Dispatch>; + setConversationSettings: React.Dispatch>>; + systemPromptSettings: Prompt[]; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; + defaultConnector?: AIConnector; + handleSave: () => void; + resetSettings: () => void; +} + +const SystemPromptSettingsManagementComponent = ({ + conversations, + conversationSettings, + onSelectedSystemPromptChange, + setUpdatedSystemPromptSettings, + setConversationSettings, + selectedSystemPrompt, + systemPromptSettings, + conversationsSettingsBulkActions, + setConversationsSettingsBulkActions, + defaultConnector, + handleSave, + resetSettings, +}: Props) => { + const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); + const { + isFlyoutOpen: deleteConfirmModalVisibility, + openFlyout: openConfirmModal, + closeFlyout: closeConfirmModal, + } = useFlyoutModalVisibility(); + const [deletedPrompt, setDeletedPrompt] = useState(); + + const onCreate = useCallback(() => { + onSelectedSystemPromptChange({ + id: '', + content: '', + name: '', + promptType: 'system', + }); + openFlyout(); + }, [onSelectedSystemPromptChange, openFlyout]); + + const onEditActionClicked = useCallback( + (prompt: Prompt) => { + onSelectedSystemPromptChange(prompt); + openFlyout(); + }, + [onSelectedSystemPromptChange, openFlyout] + ); + + const onDeleteActionClicked = useCallback( + (prompt: Prompt) => { + setDeletedPrompt(prompt); + openConfirmModal(); + }, + [openConfirmModal] + ); + + const onDeleteCancelled = useCallback(() => { + setDeletedPrompt(null); + closeConfirmModal(); + }, [closeConfirmModal]); + + const onDeleteConfirmed = useCallback(() => { + setUpdatedSystemPromptSettings((prev) => prev.filter((sp) => sp.id !== deletedPrompt?.id)); + handleSave(); + closeConfirmModal(); + }, [closeConfirmModal, deletedPrompt?.id, handleSave, setUpdatedSystemPromptSettings]); + + const onSaveCancelled = useCallback(() => { + onSelectedSystemPromptChange(); + closeFlyout(); + resetSettings(); + }, [onSelectedSystemPromptChange, closeFlyout, resetSettings]); + + const onSaveConfirmed = useCallback(() => { + handleSave(); + onSelectedSystemPromptChange(); + closeFlyout(); + }, [closeFlyout, handleSave, onSelectedSystemPromptChange]); + + const columns = useMemo( + () => [ + { + name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_NAME, + render: (prompt: Prompt) => + prompt?.name ? ( + onEditActionClicked(prompt)}>{prompt?.name} + ) : null, + }, + { + field: 'defaultConversations', + name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, + render: (defaultConversations: string[]) => + defaultConversations.map((c, idx) => ( + + {c} + + )), + }, + { + field: 'createdAt', + name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_CREATED_ON, + }, + { + name: 'Actions', + render: (prompt: Prompt) => { + return ( + + rowItem={prompt} + onEdit={onEditActionClicked} + onDelete={onDeleteActionClicked} + /> + ); + }, + }, + ], + [onEditActionClicked, onDeleteActionClicked] + ); + + const confirmationTitle = useMemo( + () => + deletedPrompt?.name + ? i18n.DELETE_SYSTEM_PROMPT_MODAL_TITLE(deletedPrompt?.name) + : i18n.DELETE_SYSTEM_PROMPT_MODAL_DEFAULT_TITLE, + [deletedPrompt?.name] + ); + + const systemPromptListItems = useMemo( + () => getSystemPromptsList(systemPromptSettings, conversations), + [systemPromptSettings, conversations] + ); + return ( + <> + + + + + {SETTINGS_TITLE} + + + + + + + + + + {deleteConfirmModalVisibility && deletedPrompt?.name && ( + +

{i18n.DELETE_SYSTEM_PROMPT_MODAL_DESCRIPTION}

+
+ )} + + ); +}; + +export const SystemPromptSettingsManagement = React.memo(SystemPromptSettingsManagementComponent); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/translations.ts new file mode 100644 index 0000000000000..b1ff676486954 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/translations.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; + +export const SYSTEM_PROMPTS_TABLE_COLUMN_NAME = i18n.translate( + 'xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnName', + { + defaultMessage: 'Name', + } +); + +export const SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS = i18n.translate( + 'xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnDefaultConversations', + { + defaultMessage: 'Default conversations', + } +); + +export const SYSTEM_PROMPTS_TABLE_COLUMN_CREATED_ON = i18n.translate( + 'xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnCreatedOn', + { + defaultMessage: 'Created on', + } +); + +export const SYSTEM_PROMPTS_TABLE_COLUMN_ACTIONS = i18n.translate( + 'xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnActions', + { + defaultMessage: 'Actions', + } +); + +export const DELETE_SYSTEM_PROMPT_MODAL_TITLE = (prompt: string) => + i18n.translate( + 'xpack.elasticAssistant.assistant.promptEditor.modal.deleteSystemPromptConfirmationTitle', + { + values: { prompt }, + defaultMessage: 'Delete "{prompt}"?', + } + ); + +export const DELETE_SYSTEM_PROMPT_MODAL_DEFAULT_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.promptEditor.modal.deleteSystemPromptConfirmationDefaultTitle', + { + defaultMessage: 'Delete system prompt?', + } +); + +export const DELETE_SYSTEM_PROMPT_MODAL_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.promptEditor.modal.deleteSystemPromptConfirmationMessage', + { + defaultMessage: 'You cannot recover the prompt once deleted', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx new file mode 100644 index 0000000000000..8a7e90a0f890b --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx @@ -0,0 +1,217 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, useCallback, useMemo } from 'react'; +import { EuiFormRow, EuiColorPicker, EuiTextArea } from '@elastic/eui'; + +import { EuiSetColorMethod } from '@elastic/eui/src/services/color_picker/color_picker'; +import { css } from '@emotion/react'; +import { PromptContextTemplate } from '../../../..'; +import * as i18n from './translations'; +import { QuickPrompt } from '../types'; +import { QuickPromptSelector } from '../quick_prompt_selector/quick_prompt_selector'; +import { PromptContextSelector } from '../prompt_context_selector/prompt_context_selector'; +import { useAssistantContext } from '../../../assistant_context'; + +const DEFAULT_COLOR = '#D36086'; + +interface Props { + onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; + quickPromptSettings: QuickPrompt[]; + selectedQuickPrompt: QuickPrompt | undefined; + setUpdatedQuickPromptSettings: React.Dispatch>; +} + +const QuickPromptSettingsEditorComponent = ({ + onSelectedQuickPromptChange, + quickPromptSettings, + selectedQuickPrompt, + setUpdatedQuickPromptSettings, +}: Props) => { + const { basePromptContexts } = useAssistantContext(); + + // Prompt + const prompt = useMemo(() => selectedQuickPrompt?.prompt ?? '', [selectedQuickPrompt?.prompt]); + + const handlePromptChange = useCallback( + (e: React.ChangeEvent) => { + if (selectedQuickPrompt != null) { + setUpdatedQuickPromptSettings((prev) => { + const alreadyExists = prev.some((qp) => qp.title === selectedQuickPrompt.title); + + if (alreadyExists) { + return prev.map((qp) => { + if (qp.title === selectedQuickPrompt.title) { + return { + ...qp, + prompt: e.target.value, + }; + } + return qp; + }); + } + + return prev; + }); + } + }, + [selectedQuickPrompt, setUpdatedQuickPromptSettings] + ); + + // Color + const selectedColor = useMemo( + () => selectedQuickPrompt?.color ?? DEFAULT_COLOR, + [selectedQuickPrompt?.color] + ); + + const handleColorChange = useCallback( + (color, { hex, isValid }) => { + if (selectedQuickPrompt != null) { + setUpdatedQuickPromptSettings((prev) => { + const alreadyExists = prev.some((qp) => qp.title === selectedQuickPrompt.title); + + if (alreadyExists) { + return prev.map((qp) => { + if (qp.title === selectedQuickPrompt.title) { + return { + ...qp, + color, + }; + } + return qp; + }); + } + return prev; + }); + } + }, + [selectedQuickPrompt, setUpdatedQuickPromptSettings] + ); + + // Prompt Contexts + const selectedPromptContexts = useMemo( + () => + basePromptContexts.filter((bpc) => + selectedQuickPrompt?.categories?.some((cat) => bpc?.category === cat) + ) ?? [], + [basePromptContexts, selectedQuickPrompt?.categories] + ); + + const onPromptContextSelectionChange = useCallback( + (pc: PromptContextTemplate[]) => { + if (selectedQuickPrompt != null) { + setUpdatedQuickPromptSettings((prev) => { + const alreadyExists = prev.some((qp) => qp.title === selectedQuickPrompt.title); + + if (alreadyExists) { + return prev.map((qp) => { + if (qp.title === selectedQuickPrompt.title) { + return { + ...qp, + categories: pc.map((p) => p.category), + }; + } + return qp; + }); + } + return prev; + }); + } + }, + [selectedQuickPrompt, setUpdatedQuickPromptSettings] + ); + + // When top level quick prompt selection changes + const onQuickPromptSelectionChange = useCallback( + (quickPrompt?: QuickPrompt | string) => { + const isNew = typeof quickPrompt === 'string'; + const newSelectedQuickPrompt: QuickPrompt | undefined = isNew + ? { + title: quickPrompt ?? '', + prompt: '', + color: DEFAULT_COLOR, + categories: [], + } + : quickPrompt; + + if (newSelectedQuickPrompt != null) { + setUpdatedQuickPromptSettings((prev) => { + const alreadyExists = prev.some((qp) => qp.title === newSelectedQuickPrompt.title); + + if (!alreadyExists) { + return [...prev, newSelectedQuickPrompt]; + } + + return prev; + }); + } + + onSelectedQuickPromptChange(newSelectedQuickPrompt); + }, + [onSelectedQuickPromptChange, setUpdatedQuickPromptSettings] + ); + + const onQuickPromptDeleted = useCallback( + (title: string) => { + setUpdatedQuickPromptSettings((prev) => prev.filter((qp) => qp.title !== title)); + }, + [setUpdatedQuickPromptSettings] + ); + return ( + <> + + + + + + + + + + + + + + + + + ); +}; +export const QuickPromptSettingsEditor = memo(QuickPromptSettingsEditorComponent); + +QuickPromptSettingsEditor.displayName = 'QuickPromptSettingsEditor'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.tsx index 38bbb0e6899be..4b8b6a8f8039d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_settings.tsx @@ -5,27 +5,12 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; -import { - EuiFormRow, - EuiColorPicker, - EuiTextArea, - EuiTitle, - EuiText, - EuiHorizontalRule, - EuiSpacer, -} from '@elastic/eui'; +import React from 'react'; +import { EuiTitle, EuiText, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; -import { EuiSetColorMethod } from '@elastic/eui/src/services/color_picker/color_picker'; -import { css } from '@emotion/react'; -import { PromptContextTemplate } from '../../../..'; import * as i18n from './translations'; import { QuickPrompt } from '../types'; -import { QuickPromptSelector } from '../quick_prompt_selector/quick_prompt_selector'; -import { PromptContextSelector } from '../prompt_context_selector/prompt_context_selector'; -import { useAssistantContext } from '../../../assistant_context'; - -const DEFAULT_COLOR = '#D36086'; +import { QuickPromptSettingsEditor } from './quick_prompt_editor'; interface Props { onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; @@ -44,136 +29,6 @@ export const QuickPromptSettings: React.FC = React.memo( selectedQuickPrompt, setUpdatedQuickPromptSettings, }) => { - const { basePromptContexts } = useAssistantContext(); - - // Prompt - const prompt = useMemo(() => selectedQuickPrompt?.prompt ?? '', [selectedQuickPrompt?.prompt]); - - const handlePromptChange = useCallback( - (e: React.ChangeEvent) => { - if (selectedQuickPrompt != null) { - setUpdatedQuickPromptSettings((prev) => { - const alreadyExists = prev.some((qp) => qp.title === selectedQuickPrompt.title); - - if (alreadyExists) { - return prev.map((qp) => { - if (qp.title === selectedQuickPrompt.title) { - return { - ...qp, - prompt: e.target.value, - }; - } - return qp; - }); - } - - return prev; - }); - } - }, - [selectedQuickPrompt, setUpdatedQuickPromptSettings] - ); - - // Color - const selectedColor = useMemo( - () => selectedQuickPrompt?.color ?? DEFAULT_COLOR, - [selectedQuickPrompt?.color] - ); - - const handleColorChange = useCallback( - (color, { hex, isValid }) => { - if (selectedQuickPrompt != null) { - setUpdatedQuickPromptSettings((prev) => { - const alreadyExists = prev.some((qp) => qp.title === selectedQuickPrompt.title); - - if (alreadyExists) { - return prev.map((qp) => { - if (qp.title === selectedQuickPrompt.title) { - return { - ...qp, - color, - }; - } - return qp; - }); - } - return prev; - }); - } - }, - [selectedQuickPrompt, setUpdatedQuickPromptSettings] - ); - - // Prompt Contexts - const selectedPromptContexts = useMemo( - () => - basePromptContexts.filter((bpc) => - selectedQuickPrompt?.categories?.some((cat) => bpc?.category === cat) - ) ?? [], - [basePromptContexts, selectedQuickPrompt?.categories] - ); - - const onPromptContextSelectionChange = useCallback( - (pc: PromptContextTemplate[]) => { - if (selectedQuickPrompt != null) { - setUpdatedQuickPromptSettings((prev) => { - const alreadyExists = prev.some((qp) => qp.title === selectedQuickPrompt.title); - - if (alreadyExists) { - return prev.map((qp) => { - if (qp.title === selectedQuickPrompt.title) { - return { - ...qp, - categories: pc.map((p) => p.category), - }; - } - return qp; - }); - } - return prev; - }); - } - }, - [selectedQuickPrompt, setUpdatedQuickPromptSettings] - ); - - // When top level quick prompt selection changes - const onQuickPromptSelectionChange = useCallback( - (quickPrompt?: QuickPrompt | string) => { - const isNew = typeof quickPrompt === 'string'; - const newSelectedQuickPrompt: QuickPrompt | undefined = isNew - ? { - title: quickPrompt ?? '', - prompt: '', - color: DEFAULT_COLOR, - categories: [], - } - : quickPrompt; - - if (newSelectedQuickPrompt != null) { - setUpdatedQuickPromptSettings((prev) => { - const alreadyExists = prev.some((qp) => qp.title === newSelectedQuickPrompt.title); - - if (!alreadyExists) { - return [...prev, newSelectedQuickPrompt]; - } - - return prev; - }); - } - - onSelectedQuickPromptChange(newSelectedQuickPrompt); - }, - [onSelectedQuickPromptChange, setUpdatedQuickPromptSettings] - ); - - const onQuickPromptDeleted = useCallback( - (title: string) => { - setUpdatedQuickPromptSettings((prev) => prev.filter((qp) => qp.title !== title)); - }, - [setUpdatedQuickPromptSettings] - ); - return ( <> @@ -183,52 +38,12 @@ export const QuickPromptSettings: React.FC = React.memo( {i18n.SETTINGS_DESCRIPTION} - - - - - - - - - - - - - - - + ); } diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/helpers.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/helpers.ts new file mode 100644 index 0000000000000..294527b5d8191 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/helpers.ts @@ -0,0 +1,31 @@ +/* + * 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 { Conversation } from '../../../..'; +import { Prompt } from '../../types'; + +export const getSelectedConversations = ( + conversationSettings: Record, + systemPromptId: string +) => + Object.values(conversationSettings).filter( + (conversation) => conversation.apiConfig?.defaultSystemPromptId === systemPromptId + ); + +export const getSystemPromptsList = ( + systemPromptSettings: Prompt[], + conversationSettings: Record +): Array => { + return systemPromptSettings.map((systemPrompt) => { + return { + ...systemPrompt, + defaultConversations: getSelectedConversations(conversationSettings, systemPrompt.id).map( + ({ title }) => title + ), + }; + }); +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx index 9ddd43d8d90df..01bdf5fd749b0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx @@ -4,24 +4,172 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; -import { EuiInMemoryTable } from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import { + EuiBasicTableColumn, + EuiButton, + EuiConfirmModal, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiLink, + EuiPanel, +} from '@elastic/eui'; import { QuickPrompt } from '../types'; +import { RowActions } from '../../common/components/assisttant_settings_management/row_actions'; +import { QuickPromptSettingsEditor } from '../quick_prompt_settings/quick_prompt_editor'; +import * as i18n from './translations'; +import { useFlyoutModalVisibility } from '../../common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility'; +import { Flyout } from '../../common/components/assisttant_settings_management/flyout'; +import { CANCEL, DELETE } from '../../settings/translations'; interface Props { - quickPrompts: QuickPrompt[]; + onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; + quickPromptSettings: QuickPrompt[]; + selectedQuickPrompt: QuickPrompt | undefined; + setUpdatedQuickPromptSettings: React.Dispatch>; + handleSave: () => void; + resetSettings: () => void; } -const QuickPromptSettingsManagementComponent = ({quickPrompts}: Props) => { - cosnt columns = [ - { - field: 'title', - name: 'Title', - } - ]; +const QuickPromptSettingsManagementComponent = ({ + onSelectedQuickPromptChange, + quickPromptSettings, + selectedQuickPrompt, + setUpdatedQuickPromptSettings, + handleSave, + resetSettings, +}: Props) => { + const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); + const [deletedQuickPrompt, setDeletedQuickPrompt] = useState(); + const { + isFlyoutOpen: deleteConfirmModalVisibility, + openFlyout: openConfirmModal, + closeFlyout: closeConfirmModal, + } = useFlyoutModalVisibility(); + + const onEditActionClicked = useCallback( + (prompt: QuickPrompt) => { + onSelectedQuickPromptChange(prompt); + openFlyout(); + }, + [onSelectedQuickPromptChange, openFlyout] + ); + + const onDeleteActionClicked = useCallback( + (prompt: QuickPrompt) => { + setDeletedQuickPrompt(prompt); + openConfirmModal(); + }, + [openConfirmModal] + ); + + const onDeleteCancelled = useCallback(() => { + setDeletedQuickPrompt(null); + closeConfirmModal(); + }, [closeConfirmModal]); + + const onDeleteConfirmed = useCallback(() => { + setUpdatedQuickPromptSettings((prev) => + prev.filter((p) => p.title !== deletedQuickPrompt?.title) + ); + handleSave(); + closeConfirmModal(); + }, [closeConfirmModal, deletedQuickPrompt?.title, handleSave, setUpdatedQuickPromptSettings]); + + const onCreate = useCallback(() => { + onSelectedQuickPromptChange(); + openFlyout(); + }, [onSelectedQuickPromptChange, openFlyout]); + + const onCancel = useCallback(() => { + onSelectedQuickPromptChange(); + closeFlyout(); + resetSettings(); + }, [closeFlyout, onSelectedQuickPromptChange, resetSettings]); + + const onSave = useCallback(() => { + handleSave(); + onSelectedQuickPromptChange(); + closeFlyout(); + }, [closeFlyout, handleSave, onSelectedQuickPromptChange]); + + const columns: Array> = useMemo( + () => [ + { + name: i18n.QUICK_PROMPTS_TABLE_COLUMN_NAME, + render: (prompt: QuickPrompt) => + prompt?.title ? ( + onEditActionClicked(prompt)}>{prompt?.title} + ) : null, + }, + { + field: 'createdAt', + name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CREATED_AT, + }, + { + name: i18n.QUICK_PROMPTS_TABLE_COLUMN_ACTIONS, + render: (prompt: QuickPrompt) => { + return ( + + rowItem={prompt} + onDelete={onDeleteActionClicked} + onEdit={onEditActionClicked} + /> + ); + }, + }, + ], + [onDeleteActionClicked, onEditActionClicked] + ); + + const confirmationTitle = useMemo( + () => + deletedQuickPrompt?.title + ? i18n.DELETE_QUICK_PROMPT_MODAL_TITLE(deletedQuickPrompt.title) + : i18n.DELETE_QUICK_PROMPT_MODAL_DEFAULT_TITLE, + [deletedQuickPrompt?.title] + ); return ( -
- -
+ <> + + + + + {i18n.QUICK_PROMPTS_TABLE_CREATE_BUTTON_TITLE} + + + + + + + + + {deleteConfirmModalVisibility && deletedQuickPrompt && ( + +

{i18n.DELETE_QUICK_PROMPT_MODAL_DESCRIPTION}

+
+ )} + ); }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/translations.ts new file mode 100644 index 0000000000000..be24863d2aec1 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/translations.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; + +export const QUICK_PROMPTS_TABLE_COLUMN_NAME = i18n.translate( + 'xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnName', + { + defaultMessage: 'Name', + } +); + +export const QUICK_PROMPTS_TABLE_COLUMN_CREATED_AT = i18n.translate( + 'xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnCreatedAt', + { + defaultMessage: 'Created at', + } +); + +export const QUICK_PROMPTS_TABLE_COLUMN_ACTIONS = i18n.translate( + 'xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnActions', + { + defaultMessage: 'Actions', + } +); + +export const QUICK_PROMPTS_TABLE_CREATE_BUTTON_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.quickPromptsTable.createButtonTitle', + { + defaultMessage: 'Quick Prompt', + } +); + +export const QUICK_PROMPT_EDIT_FLYOUT_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.quickPromptEditFlyout.title', + { + defaultMessage: 'Quick Prompt', + } +); + +export const DELETE_QUICK_PROMPT_MODAL_TITLE = (prompt: string) => + i18n.translate( + 'xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationTitle', + { + values: { prompt }, + defaultMessage: 'Delete "{prompt}"?', + } + ); + +export const DELETE_QUICK_PROMPT_MODAL_DEFAULT_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationDefaultTitle', + { + defaultMessage: 'Delete quick prompt?', + } +); + +export const DELETE_QUICK_PROMPT_MODAL_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationMessage', + { + defaultMessage: 'You cannot recover the prompt once deleted', + } +); 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 0a526b18aeb87..2664f01805bf7 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 @@ -6,31 +6,22 @@ */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlexItem, - EuiPageTemplate, - EuiFlexGroup, -} from '@elastic/eui'; +import { EuiPageTemplate } from '@elastic/eui'; import { css } from '@emotion/react'; import { Conversation, Prompt, QuickPrompt } from '../../..'; import * as i18n from './translations'; import { useAssistantContext } from '../../assistant_context'; import { useSettingsUpdater } from './use_settings_updater/use_settings_updater'; -import { - AnonymizationSettings, - EvaluationSettings, - KnowledgeBaseSettings, - QuickPromptSettings, - SystemPromptSettings, -} from '.'; +import { EvaluationSettings, KnowledgeBaseSettings } from '.'; import { useLoadConnectors } from '../../connectorland/use_load_connectors'; import { getDefaultConnector } from '../helpers'; import { useFetchAnonymizationFields } from '../api/anonymization_fields/use_fetch_anonymization_fields'; -import { ConnectorsSettings } from '../../connectorland/connector_settings'; +import { ConnectorsSettingsManagement } from '../../connectorland/connector_settings_management'; import { ConversationSettingsManagement } from '../conversations/converstaion_settings_management'; +import { QuickPromptSettingsManagement } from '../quick_prompts/quick_prompt_settings_management.tsx'; +import { SystemPromptSettingsManagement } from '../prompt_editor/system_prompt/system_prompt_settings_management'; +import { AnonymizationSettingsManagement } from '../../data_anonymization/settings/anonomization_settings_management'; export const CONNECTORS_TAB = 'CONNECTORS_TAB' as const; export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const; @@ -45,6 +36,7 @@ interface Props { selectedConversation: Conversation; setSelectedConversationId: React.Dispatch>; isFlyoutMode: boolean; + refetchConversations: () => void; } /** @@ -57,11 +49,13 @@ export const AssistantSettingsManagement: React.FC = React.memo( setSelectedConversationId, conversations, isFlyoutMode, + refetchConversations, }) => { const { actionTypeRegistry, assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled }, http, + navigateToApp, selectedSettingsTab, setSelectedSettingsTab, toasts, @@ -70,11 +64,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( const { data: anonymizationFields } = useFetchAnonymizationFields(); // Connector details - const { - data: connectors, - refetch: refetchConnectors, - isFetchedAfterMount: areConnectorsFetched, - } = useLoadConnectors({ + const { data: connectors } = useLoadConnectors({ http, }); const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); @@ -113,6 +103,10 @@ export const AssistantSettingsManagement: React.FC = React.memo( } ); + const onHandleSelectedConversationChange = useCallback((conversation?: Conversation) => { + setSelectedConversation(conversation); + }, []); + useEffect(() => { if (selectedConversation != null) { setSelectedConversation(conversationSettings[selectedConversation.title]); @@ -246,57 +240,59 @@ export const AssistantSettingsManagement: React.FC = React.memo( `} > {selectedSettingsTab === CONNECTORS_TAB && ( - + )} {selectedSettingsTab === CONVERSATIONS_TAB && ( - )} - {selectedSettingsTab === QUICK_PROMPTS_TAB && ( - )} {selectedSettingsTab === SYSTEM_PROMPTS_TAB && ( - + )} + {selectedSettingsTab === QUICK_PROMPTS_TAB && ( + )} {selectedSettingsTab === ANONYMIZATION_TAB && ( - = React.memo( )} {selectedSettingsTab === EVALUATION_TAB && }
- {hasPendingChanges && ( + {/* {hasPendingChanges && ( @@ -340,7 +336,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( - )} + )} */} ); } diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/translations.ts index 6acd07beb7772..a8c2c85941176 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/translations.ts @@ -104,3 +104,10 @@ export const SAVE = i18n.translate( defaultMessage: 'Save', } ); + +export const DELETE = i18n.translate( + 'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.slDeleteButtonTitle', + { + defaultMessage: 'Delete', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx index a9a949073a4f2..68a1e426be7a0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -10,14 +10,11 @@ import type { HttpSetup } from '@kbn/core-http-browser'; import { omit } from 'lodash/fp'; import React, { useCallback, useMemo, useState } from 'react'; import type { IToasts } from '@kbn/core-notifications-browser'; -import { - ActionTypeRegistryContract, - TriggersAndActionsUIPublicPluginStart, -} from '@kbn/triggers-actions-ui-plugin/public'; +import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; import { useLocalStorage, useSessionStorage } from 'react-use'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; import { AssistantFeatures, defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; -import { SettingsStart } from '@kbn/core-ui-settings-browser'; +import { NavigateToAppOptions } from '@kbn/core/public'; import { updatePromptContexts } from './helpers'; import type { PromptContext, @@ -72,9 +69,6 @@ export interface AssistantProviderProps { baseSystemPrompts?: Prompt[]; docLinks: Omit; children: React.ReactNode; - getAddConnectorFlyout: TriggersAndActionsUIPublicPluginStart['getAddConnectorFlyout']; - getDeleteConnectorModalConfirmation: TriggersAndActionsUIPublicPluginStart['getDeleteConnectorModalConfirmation']; - getEditConnectorFlyout: TriggersAndActionsUIPublicPluginStart['getEditConnectorFlyout']; getComments: (commentArgs: { abortStream: () => void; currentConversation?: Conversation; @@ -90,7 +84,7 @@ export interface AssistantProviderProps { http: HttpSetup; baseConversations: Record; nameSpace?: string; - settings: SettingsStart; + navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise; title?: string; toasts?: IToasts; } @@ -120,7 +114,6 @@ export interface UseAssistantContext { baseQuickPrompts: QuickPrompt[]; baseSystemPrompts: Prompt[]; baseConversations: Record; - getEditConnectorFlyout: TriggersAndActionsUIPublicPluginStart['getEditConnectorFlyout']; getComments: (commentArgs: { abortStream: () => void; currentConversation?: Conversation; @@ -133,11 +126,11 @@ export interface UseAssistantContext { setIsStreaming: (isStreaming: boolean) => void; isFlyoutMode: boolean; }) => EuiCommentProps[]; - getDeleteConnectorModalConfirmation: TriggersAndActionsUIPublicPluginStart['getDeleteConnectorModalConfirmation']; http: HttpSetup; knowledgeBase: KnowledgeBaseConfig; getLastConversationId: (conversationTitle?: string) => string; promptContexts: Record; + navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise; nameSpace: string; registerPromptContext: RegisterPromptContext; selectedSettingsTab: SettingsTabs | null; @@ -149,7 +142,6 @@ export interface UseAssistantContext { setSelectedSettingsTab: React.Dispatch>; setShowAssistantOverlay: (showAssistantOverlay: ShowAssistantOverlay) => void; showAssistantOverlay: ShowAssistantOverlay; - settings: SettingsStart; setTraceOptions: (traceOptions: { apmUrl: string; langSmithProject: string; @@ -175,14 +167,11 @@ export const AssistantProvider: React.FC = ({ baseQuickPrompts = [], baseSystemPrompts = BASE_SYSTEM_PROMPTS, children, - getAddConnectorFlyout, - getDeleteConnectorModalConfirmation, - getEditConnectorFlyout, getComments, http, baseConversations, + navigateToApp, nameSpace = DEFAULT_ASSISTANT_NAMESPACE, - settings, title = DEFAULT_ASSISTANT_TITLE, toasts, }) => { @@ -308,13 +297,11 @@ export const AssistantProvider: React.FC = ({ baseQuickPrompts, baseSystemPrompts, docLinks, - getAddConnectorFlyout, - getEditConnectorFlyout, - getDeleteConnectorModalConfirmation, getComments, http, knowledgeBase: { ...DEFAULT_KNOWLEDGE_BASE_SETTINGS, ...localStorageKnowledgeBase }, promptContexts, + navigateToApp, nameSpace, registerPromptContext, selectedSettingsTab, @@ -326,7 +313,6 @@ export const AssistantProvider: React.FC = ({ setKnowledgeBase: setLocalStorageKnowledgeBase, setSelectedSettingsTab, setShowAssistantOverlay, - settings, setTraceOptions: setSessionStorageTraceOptions, showAssistantOverlay, title, @@ -351,13 +337,11 @@ export const AssistantProvider: React.FC = ({ baseQuickPrompts, baseSystemPrompts, docLinks, - getAddConnectorFlyout, - getEditConnectorFlyout, - getDeleteConnectorModalConfirmation, getComments, http, localStorageKnowledgeBase, promptContexts, + navigateToApp, nameSpace, registerPromptContext, selectedSettingsTab, @@ -366,7 +350,6 @@ export const AssistantProvider: React.FC = ({ setLocalStorageQuickPrompts, setLocalStorageSystemPrompts, setLocalStorageKnowledgeBase, - settings, setSessionStorageTraceOptions, showAssistantOverlay, title, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx deleted file mode 100644 index 9a2b4824a3ba0..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/index.tsx +++ /dev/null @@ -1,191 +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 { EuiCallOut, EuiInMemoryTable, EuiPanel, EuiSkeletonText } from '@elastic/eui'; -import React, { Suspense, useCallback, useMemo, useState } from 'react'; -import { ActionType } from '@kbn/triggers-actions-ui-plugin/public'; -import type { IHttpFetchError } from '@kbn/core-http-browser'; - -import { UseQueryResult } from '@tanstack/react-query'; -import { useAssistantContext } from '../../assistant_context'; -import { AIConnector } from '../connector_selector'; - -import { - DELETE_CONNECTOR_CONFIRMATION_MULTIPLE_TITLE, - DELETE_CONNECTOR_CONFIRMATION_SINGLE_TITLE, - MISSING_READ_CONNECTORS_CALLOUT_TITLE, -} from '../translations'; - -import { useLoadActionTypes } from '../use_load_action_types'; -import { AddConnectorModal } from '../add_connector_modal'; -import { ActionConnectorTableItem } from './types'; -import { deleteActions } from '../helpers'; -import { useConnectorTable } from './use_connector_table'; - -interface Props { - connectors: AIConnector[] | undefined; - refetchConnectors: UseQueryResult['refetch']; - areConnectorsFetched: boolean; -} - -const emptyConnectors = [] as ActionConnectorTableItem[]; - -const ConnectorsSettingsComponent: React.FC = ({ - connectors: aiConnectors, - refetchConnectors, - areConnectorsFetched, -}) => { - const { - actionTypeRegistry, - http, - assistantAvailability, - getEditConnectorFlyout, - getDeleteConnectorModalConfirmation, - } = useAssistantContext(); - - const { data: actionTypes } = useLoadActionTypes({ http }); - - // Edit Connector - const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); - - const handleCloseEditFlyout = useCallback(() => { - setEditFlyoutVisibility(false); - }, []); - const [editedConnectorItem, setEditedConnectorItem] = useState(null); - - // Add Connector - - const [isAddConnectorModalVisible, setIsAddConnectorModalVisible] = useState(false); - - const [selectedActionType, setSelectedActionType] = useState(null); - - const onSaveConnector = useCallback(() => { - refetchConnectors?.(); - }, [refetchConnectors]); - - const handleAddConnector = useCallback(() => { - handleCloseEditFlyout(); - setIsAddConnectorModalVisible(true); - }, [handleCloseEditFlyout]); - - const onEditConnector = useCallback((connector: ActionConnectorTableItem) => { - setIsAddConnectorModalVisible(false); - setEditedConnectorItem(connector); - setEditFlyoutVisibility(true); - }, []); - - // delete - - const [connectorsToDelete, setConnectorsToDelete] = useState([]); - - const onDeleteConnector = useCallback((connector: ActionConnectorTableItem) => { - const itemIds = [connector.id]; - setConnectorsToDelete(itemIds); - }, []); - - const DeleteConnectorModalConfirmation = useMemo( - () => - getDeleteConnectorModalConfirmation({ - idsToDelete: connectorsToDelete, - apiDeleteCall: deleteActions, - onDeleted: (deleted: string[]) => { - refetchConnectors(); - }, - onCancel: () => { - setConnectorsToDelete([]); - }, - onErrors: () => { - setConnectorsToDelete([]); - }, - singleTitle: DELETE_CONNECTOR_CONFIRMATION_SINGLE_TITLE, - multipleTitle: DELETE_CONNECTOR_CONFIRMATION_MULTIPLE_TITLE, - setIsLoadingState: () => {}, - }), - [connectorsToDelete, getDeleteConnectorModalConfirmation, refetchConnectors] - ); - - const onConnectorUpdated = useCallback( - async (updatedConnector) => { - setEditedConnectorItem(updatedConnector); - refetchConnectors(); - handleCloseEditFlyout(); - }, - [handleCloseEditFlyout, refetchConnectors] - ); - - const ConnectorEditFlyout = useMemo( - () => - editedConnectorItem && editFlyoutVisible - ? getEditConnectorFlyout({ - connector: editedConnectorItem, - onClose: handleCloseEditFlyout, - onConnectorUpdated, - }) - : null, - [ - editFlyoutVisible, - editedConnectorItem, - getEditConnectorFlyout, - handleCloseEditFlyout, - onConnectorUpdated, - ] - ); - - const { search, columns, aiConnectorTableItems } = useConnectorTable({ - actionTypes, - actionTypeRegistry, - aiConnectors, - areConnectorsFetched, - hasConnectorsAllPrivilege: assistantAvailability.hasConnectorsAllPrivilege, - refetchConnectors, - handleAddConnector, - onEditConnector, - onDeleteConnector, - }); - - if (!assistantAvailability.hasConnectorsReadPrivilege) { - return ( - - ); - } - - return areConnectorsFetched ? ( - - {DeleteConnectorModalConfirmation} - - {ConnectorEditFlyout} - {isAddConnectorModalVisible && ( - // Crashing management app otherwise - - setIsAddConnectorModalVisible(false)} - onSaveConnector={onSaveConnector} - onSelectActionType={(actionType: ActionType) => setSelectedActionType(actionType)} - selectedActionType={selectedActionType} - /> - - )} - - ) : ( - - ); -}; - -export const ConnectorsSettings = React.memo(ConnectorsSettingsComponent); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/types.ts b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/types.ts deleted file mode 100644 index ba613134b90ec..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/types.ts +++ /dev/null @@ -1,13 +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 { AIConnector } from '../connector_selector'; - -export type ActionConnectorTableItem = AIConnector & { - actionType: string; - compatibility: string[]; -}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/use_connector_table.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/use_connector_table.tsx deleted file mode 100644 index 9ea7412e598da..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings/use_connector_table.tsx +++ /dev/null @@ -1,187 +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 { - EuiBadge, - EuiBasicTableColumn, - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSearchBarProps, -} from '@elastic/eui'; -import React, { useState, useMemo } from 'react'; -import { ActionType, ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; -import { getConnectorCompatibility } from '@kbn/actions-plugin/common'; -import { RowActions } from '../../assistant/common/components/row_actions'; -import { AIConnector } from '../connector_selector'; -import { getActionTypeTitle, getGenAiConfig } from '../helpers'; -import { - CONNECTORS_TABLE_COLUMN_ACTIONS, - CONNECTORS_TABLE_COLUMN_ACTION_TYPE, - CONNECTORS_TABLE_COLUMN_COMPATIBILITY, - CONNECTORS_TABLE_COLUMN_NAME, - CREATE_CONNECTOR_BUTTON, - PRECONFIGURED_CONNECTOR, - REFRESH_CONNECTORS_BUTTON, - SEARCH_CONNECTOR_PLACEHOLDER, -} from '../translations'; -import { ActionConnectorTableItem } from './types'; - -interface Props { - actionTypes: ActionType[] | undefined; - actionTypeRegistry: ActionTypeRegistryContract; - aiConnectors: AIConnector[] | undefined; - areConnectorsFetched: boolean; - hasConnectorsAllPrivilege: boolean; - refetchConnectors: () => void; - handleAddConnector: () => void; - onEditConnector: (connector: ActionConnectorTableItem) => void; - onDeleteConnector: (connector: ActionConnectorTableItem) => void; -} -export const useConnectorTable = ({ - actionTypes, - actionTypeRegistry, - aiConnectors, - areConnectorsFetched, - hasConnectorsAllPrivilege, - refetchConnectors, - handleAddConnector, - onEditConnector, - onDeleteConnector, -}: Props) => { - const [query, setQuery] = useState(''); - - const handleOnChange: EuiSearchBarProps['onChange'] = ({ queryText, error }) => { - if (!error) { - setQuery(queryText); - } - }; - - const search: EuiSearchBarProps = useMemo( - () => ({ - query, - onChange: handleOnChange, - box: { - schema: true, - placeholder: SEARCH_CONNECTOR_PLACEHOLDER, - }, - toolsRight: [ - - {REFRESH_CONNECTORS_BUTTON} - , - - {CREATE_CONNECTOR_BUTTON} - , - ], - }), - [areConnectorsFetched, hasConnectorsAllPrivilege, handleAddConnector, refetchConnectors, query] - ); - - const columns: Array> = useMemo( - () => [ - { - name: CONNECTORS_TABLE_COLUMN_NAME, - truncateText: false, - mobileOptions: { - show: true, - }, - render: (connector: ActionConnectorTableItem) => ( - onEditConnector(connector)}>{connector.name} - ), - }, - { - field: 'actionType', - name: CONNECTORS_TABLE_COLUMN_ACTION_TYPE, - truncateText: false, - mobileOptions: { - show: true, - }, - render: (actionType: ActionConnectorTableItem['actionType']) => - actionType ? {actionType} : null, - }, - { - name: CONNECTORS_TABLE_COLUMN_COMPATIBILITY, - truncateText: true, - mobileOptions: { - show: true, - }, - render: (tableItem: ActionConnectorTableItem) => { - return ( - - {tableItem?.compatibility?.map((compatibilityItem: string) => ( - - - {compatibilityItem} - - - ))} - - ); - }, - }, - { - name: CONNECTORS_TABLE_COLUMN_ACTIONS, - actions: [ - { - name: CONNECTORS_TABLE_COLUMN_ACTIONS, - render: (connector: ActionConnectorTableItem) => { - return ( - - rowItem={connector} - onEdit={onEditConnector} - onDelete={onDeleteConnector} - /> - ); - }, - }, - ], - }, - ], - [onDeleteConnector, onEditConnector] - ); - - const aiConnectorTableItems: ActionConnectorTableItem[] | undefined = areConnectorsFetched - ? (aiConnectors ?? []).map((connector) => { - const currentActionType = actionTypes?.find( - (actionType) => actionType.id === connector.actionTypeId - ); - - const connectorTypeTitle = - getGenAiConfig(connector)?.apiProvider ?? - getActionTypeTitle(actionTypeRegistry.get(connector.actionTypeId)); - const actionType = connector.isPreconfigured ? PRECONFIGURED_CONNECTOR : connectorTypeTitle; - return { - ...connector, - actionType, - compatibility: currentActionType - ? getConnectorCompatibility(currentActionType.supportedFeatureIds) - : [], - }; - }) - : undefined; - - return { search, columns, aiConnectorTableItems }; -}; 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 new file mode 100644 index 0000000000000..34dd24e80c99f --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/index.tsx @@ -0,0 +1,57 @@ +/* + * 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 { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { useAssistantContext } from '../../assistant_context'; + +import * as i18n from './translations'; + +interface Props { + navigateToApp: (appId: string, options?: Record) => Promise; +} + +const ConnectorsSettingsManagementComponent: React.FC = () => { + const { navigateToApp } = useAssistantContext(); + + const onClick = useCallback( + () => + navigateToApp('management', { + path: 'insightsAndAlerting/triggersActionsConnectors/connectors', + }), + [navigateToApp] + ); + + return ( + + +

{i18n.CONNECTOR_SETTINGS_MANAGEMENT_TITLE}

+
+ + + + {i18n.CONNECTOR_SETTINGS_MANAGEMENT_DESCRIPTION} + + + + {i18n.CONNECTOR_MANAGEMENT_BUTTON_TITLE} + + +
+ ); +}; + +export const ConnectorsSettingsManagement = React.memo(ConnectorsSettingsManagementComponent); +ConnectorsSettingsManagementComponent.displayName = 'ConnectorsSettingsManagementComponent'; 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 new file mode 100644 index 0000000000000..c564338df0913 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_settings_management/translations.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const CONNECTOR_SETTINGS_MANAGEMENT_TITLE = i18n.translate( + 'xpack.elasticAssistant.connectors.connectorSettingsManagement.title', + { + defaultMessage: 'Connector Settings', + } +); + +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 OpenIA or Bedrock large language models. ', + } +); + +export const CONNECTOR_MANAGEMENT_BUTTON_TITLE = i18n.translate( + 'xpack.elasticAssistant.connectors.connectorSettingsManagement.buttonTitle', + { + defaultMessage: 'Manage Connectors', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx index d1e2b01007f67..74d6ef1bc169b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx @@ -10,8 +10,7 @@ import type { ActionTypeModel, ActionTypeRegistryContract, } from '@kbn/triggers-actions-ui-plugin/public'; -import { HttpSetup } from '@kbn/core/public'; -import { BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; + import { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types'; import { PRECONFIGURED_CONNECTOR } from './translations'; @@ -76,27 +75,3 @@ export const getConnectorTypeTitle = ( return actionType; }; - -export async function deleteActions({ - ids, - http, -}: { - ids: string[]; - http: HttpSetup; -}): Promise<{ successes: string[]; errors: string[] }> { - const successes: string[] = []; - const errors: string[] = []; - await Promise.all( - ids.map((id) => - http.delete(`${BASE_ACTION_API_PATH}/connector/${encodeURIComponent(id)}`) - ) - ).then( - function (fulfilled) { - successes.push(...fulfilled); - }, - function (rejected) { - errors.push(...rejected); - } - ); - return { successes, errors }; -} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts index a12ebd61129e3..86c4bebeb320b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts @@ -177,59 +177,3 @@ export const DELETE_CONNECTOR_BUTTON = i18n.translate( defaultMessage: 'Delete', } ); - -export const MISSING_READ_CONNECTORS_CALLOUT_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.connectors.connectorMissingCallout.missingReadConnectorsCalloutTitle', - { - defaultMessage: 'Missing Read Connectors Privileges', - } -); - -export const CONNECTORS_TABLE_COLUMN_NAME = i18n.translate( - 'xpack.elasticAssistant.assistant.connectors.table.column.Name', - { - defaultMessage: 'Connector name', - } -); - -export const CONNECTORS_TABLE_COLUMN_ACTION_TYPE = i18n.translate( - 'xpack.elasticAssistant.assistant.connectors.table.column.type', - { - defaultMessage: 'Type', - } -); - -export const CONNECTORS_TABLE_COLUMN_COMPATIBILITY = i18n.translate( - 'xpack.elasticAssistant.assistant.connectors.table.column.compatibility', - { - defaultMessage: 'Compatibility', - } -); - -export const CONNECTORS_TABLE_COLUMN_ACTIONS = i18n.translate( - 'xpack.elasticAssistant.assistant.connectors.table.column.actions', - { - defaultMessage: 'Actions', - } -); - -export const DELETE_CONNECTOR_CONFIRMATION_SINGLE_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.connectors.deleteConnectorConfirmation.singleTitle', - { - defaultMessage: 'connector', - } -); - -export const DELETE_CONNECTOR_CONFIRMATION_MULTIPLE_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.connectors.deleteConnectorConfirmation.multipleTitle', - { - defaultMessage: 'connectors', - } -); - -export const SEARCH_CONNECTOR_PLACEHOLDER = i18n.translate( - 'xpack.elasticAssistant.assistant.connectors.searchConnectorPlaceholder', - { - defaultMessage: 'Search for connectors', - } -); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonomization_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonomization_settings_management/index.tsx new file mode 100644 index 0000000000000..3e3812510f076 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonomization_settings_management/index.tsx @@ -0,0 +1,78 @@ +/* + * 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 { EuiFlexGroup, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import React 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'; + +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, + anonymizationFieldsBulkActions, + setAnonymizationFieldsBulkActions, + setUpdatedAnonymizationData, + }); + return ( + + +

{i18n.SETTINGS_TITLE}

+
+ + {i18n.SETTINGS_DESCRIPTION} + + + + + + + + + + +
+ ); +}; + +AnonymizationSettingsManagementComponent.displayName = 'AnonymizationSettingsManagementComponent'; + +export const AnonymizationSettingsManagement = React.memo(AnonymizationSettingsManagementComponent); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/index.tsx index e03a965f6c98c..c371c9498dc2f 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/index.tsx @@ -6,14 +6,14 @@ */ import { EuiFlexGroup, EuiHorizontalRule, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import React, { useCallback } from 'react'; +import React 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 { Stats } from '../../../data_anonymization_editor/stats'; import { ContextEditor } from '../../../data_anonymization_editor/context_editor'; -import type { BatchUpdateListItem } from '../../../data_anonymization_editor/context_editor/types'; import * as i18n from './translations'; +import { useAnonymizationListUpdate } from './use_anonymization_list_update'; export interface Props { defaultPageSize?: number; @@ -34,39 +34,12 @@ const AnonymizationSettingsComponent: React.FC = ({ setAnonymizationFieldsBulkActions, setUpdatedAnonymizationData, }) => { - 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' - ? { allowed: u.operation === 'add' } - : {}), - ...(u.update === 'allowReplacement' || u.update === 'defaultAllowReplacement' - ? { anonymized: u.operation === 'add' } - : {}), - })); - setAnonymizationFieldsBulkActions({ - ...anonymizationFieldsBulkActions, - // Only update makes sense now, as long as we don't have an add new field design/UX - update: [...(anonymizationFieldsBulkActions?.update ?? []), ...updatedFields], - }); - setUpdatedAnonymizationData({ - ...anonymizationFields, - data: [ - ...anonymizationFields.data.filter((f) => !updatedFieldsKeys.includes(f.field)), - ...updatedFields, - ], - }); - }, - [ - anonymizationFields, - anonymizationFieldsBulkActions, - setAnonymizationFieldsBulkActions, - setUpdatedAnonymizationData, - ] - ); + const onListUpdated = useAnonymizationListUpdate({ + anonymizationFields, + anonymizationFieldsBulkActions, + setAnonymizationFieldsBulkActions, + setUpdatedAnonymizationData, + }); return ( <> @@ -88,6 +61,7 @@ const AnonymizationSettingsComponent: React.FC = ({ onListUpdated={onListUpdated} rawData={null} pageSize={defaultPageSize} + compressed={true} /> ); 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 new file mode 100644 index 0000000000000..f9ee4875fad23 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings/use_anonymization_list_update.tsx @@ -0,0 +1,66 @@ +/* + * 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 { useCallback } 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 { BatchUpdateListItem } from '../../../data_anonymization_editor/context_editor/types'; + +interface Props { + anonymizationFields: FindAnonymizationFieldsResponse; + anonymizationFieldsBulkActions: PerformBulkActionRequestBody; + setAnonymizationFieldsBulkActions: React.Dispatch< + React.SetStateAction + >; + setUpdatedAnonymizationData: React.Dispatch< + React.SetStateAction + >; +} + +export const useAnonymizationListUpdate = ({ + anonymizationFields, + anonymizationFieldsBulkActions, + setAnonymizationFieldsBulkActions, + setUpdatedAnonymizationData, +}: Props) => { + 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' + ? { allowed: u.operation === 'add' } + : {}), + ...(u.update === 'allowReplacement' || u.update === 'defaultAllowReplacement' + ? { anonymized: u.operation === 'add' } + : {}), + })); + setAnonymizationFieldsBulkActions({ + ...anonymizationFieldsBulkActions, + // Only update makes sense now, as long as we don't have an add new field design/UX + update: [...(anonymizationFieldsBulkActions?.update ?? []), ...updatedFields], + }); + setUpdatedAnonymizationData({ + ...anonymizationFields, + data: [ + ...anonymizationFields.data.filter((f) => !updatedFieldsKeys.includes(f.field)), + ...updatedFields, + ], + }); + }, + [ + anonymizationFields, + anonymizationFieldsBulkActions, + setAnonymizationFieldsBulkActions, + setUpdatedAnonymizationData, + ] + ); + + return 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 6eeb80f2bb911..e215c8270746e 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 @@ -35,6 +35,7 @@ const defaultSort: SortConfig = { export interface Props { anonymizationFields: FindAnonymizationFieldsResponse; + compressed?: boolean; onListUpdated: (updates: BatchUpdateListItem[]) => void; rawData: Record | null; pageSize?: number; @@ -60,6 +61,7 @@ const search: EuiSearchBarProps = { const ContextEditorComponent: React.FC = ({ anonymizationFields, + compressed = true, onListUpdated, rawData, pageSize = DEFAULT_PAGE_SIZE, @@ -131,7 +133,7 @@ const ContextEditorComponent: React.FC = ({ allowNeutralSort={false} childrenBetween={hasUpdateAIAssistantAnonymization ? toolbar : undefined} columns={columns} - compressed={true} + compressed={compressed} data-test-subj="contextEditor" itemId={FIELDS.FIELD} items={rows} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/allowed_stat/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/allowed_stat/index.tsx index 2febee604e4b5..9d893cdaa65a2 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/allowed_stat/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/allowed_stat/index.tsx @@ -15,11 +15,19 @@ import * as i18n from './translations'; interface Props { allowed: number; + titleSize?: 'xs' | 's' | 'xxxs' | 'xxs' | 'm' | 'l' | undefined; + gap?: string; total: number; inline?: boolean; } -const AllowedStatComponent: React.FC = ({ allowed, total, inline }) => { +const AllowedStatComponent: React.FC = ({ + allowed, + total, + inline, + titleSize = TITLE_SIZE, + gap = euiThemeVars.euiSizeXS, +}) => { const tooltipContent = useMemo(() => i18n.ALLOWED_TOOLTIP({ allowed, total }), [allowed, total]); return ( @@ -30,7 +38,7 @@ const AllowedStatComponent: React.FC = ({ allowed, total, inline }) => { ? css` display: flex; align-items: center; - gap: ${euiThemeVars.euiSizeXS}; + gap: ${gap}; ` : null } @@ -38,7 +46,7 @@ const AllowedStatComponent: React.FC = ({ allowed, total, inline }) => { description={i18n.ALLOWED} reverse title={allowed} - titleSize={TITLE_SIZE} + titleSize={titleSize} /> ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/anonymized_stat/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/anonymized_stat/index.tsx index 210b65c6d6a70..74b3e631b7117 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/anonymized_stat/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/anonymized_stat/index.tsx @@ -16,11 +16,19 @@ import * as i18n from './translations'; interface Props { anonymized: number; + titleSize?: 'xs' | 's' | 'xxxs' | 'xxs' | 'm' | 'l' | undefined; + gap?: string; isDataAnonymizable: boolean; inline?: boolean; } -const AnonymizedStatComponent: React.FC = ({ anonymized, isDataAnonymizable, inline }) => { +const AnonymizedStatComponent: React.FC = ({ + anonymized, + isDataAnonymizable, + inline, + titleSize = TITLE_SIZE, + gap = euiThemeVars.euiSizeXS, +}) => { const color = useMemo(() => getColor(isDataAnonymizable), [isDataAnonymizable]); const tooltipContent = useMemo( @@ -45,7 +53,7 @@ const AnonymizedStatComponent: React.FC = ({ anonymized, isDataAnonymizab ? css` display: flex; align-items: center; - gap: ${euiThemeVars.euiSizeXS}; + gap: ${gap}; ` : null } @@ -54,7 +62,7 @@ const AnonymizedStatComponent: React.FC = ({ anonymized, isDataAnonymizab reverse titleColor={color} title={anonymized} - titleSize={TITLE_SIZE} + titleSize={titleSize} /> ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/available_stat/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/available_stat/index.tsx index 6e5853c871a6f..d29bfab02b897 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/available_stat/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/available_stat/index.tsx @@ -15,10 +15,17 @@ import * as i18n from './translations'; interface Props { total: number; + titleSize?: 'xs' | 's' | 'xxxs' | 'xxs' | 'm' | 'l' | undefined; + gap?: string; inline?: boolean; } -const AvailableStatComponent: React.FC = ({ total, inline }) => { +const AvailableStatComponent: React.FC = ({ + total, + inline, + titleSize = TITLE_SIZE, + gap = euiThemeVars.euiSizeXS, +}) => { const tooltipContent = useMemo(() => i18n.AVAILABLE_TOOLTIP(total), [total]); return ( @@ -29,7 +36,7 @@ const AvailableStatComponent: React.FC = ({ total, inline }) => { ? css` display: flex; align-items: center; - gap: ${euiThemeVars.euiSizeXS}; + gap: ${gap}; ` : null } @@ -37,7 +44,7 @@ const AvailableStatComponent: React.FC = ({ total, inline }) => { description={i18n.AVAILABLE} reverse title={total} - titleSize={TITLE_SIZE} + titleSize={titleSize} /> ); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/constants.ts b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/constants.ts index ba1e02e569bc2..ed5c08a0c64f9 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/constants.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/constants.ts @@ -6,3 +6,4 @@ */ export const TITLE_SIZE = 'xs'; +export const STAT_TITLE_SIZE = 'm'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/index.tsx index 2068bc517025d..b628119032d1a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization_editor/stats/index.tsx @@ -27,6 +27,8 @@ interface Props { rawData?: string | Record; inline?: boolean; replacements?: Replacements; + titleSize?: 's' | 'l' | 'xs' | 'm' | 'xxxs' | 'xxs' | undefined; + gap?: string; } const StatsComponent: React.FC = ({ @@ -35,6 +37,8 @@ const StatsComponent: React.FC = ({ rawData, inline, replacements, + titleSize, + gap, }) => { const { allowed, anonymized, total } = useMemo( () => @@ -50,7 +54,13 @@ const StatsComponent: React.FC = ({ {isDataAnonymizable && ( - + )} @@ -59,12 +69,14 @@ const StatsComponent: React.FC = ({ anonymized={anonymized} isDataAnonymizable={isDataAnonymizable || anonymized > 0} inline={inline} + titleSize={titleSize} + gap={gap} /> {isDataAnonymizable && ( - + )} diff --git a/x-pack/plugins/security_solution/public/assistant/provider.tsx b/x-pack/plugins/security_solution/public/assistant/provider.tsx index 106078f792d08..2b5daf73fbf4b 100644 --- a/x-pack/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/plugins/security_solution/public/assistant/provider.tsx @@ -117,16 +117,11 @@ export const createConversations = async ( */ export const AssistantProvider: FC> = ({ children }) => { const { + application: { navigateToApp }, http, notifications, - settings, storage, - triggersActionsUi: { - actionTypeRegistry, - getAddConnectorFlyout, - getDeleteConnectorModalConfirmation, - getEditConnectorFlyout, - }, + triggersActionsUi: { actionTypeRegistry }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, } = useKibana().services; const basePath = useBasePath(); @@ -174,12 +169,9 @@ export const AssistantProvider: FC> = ({ children }) baseQuickPrompts={BASE_SECURITY_QUICK_PROMPTS} // to server and plugin start baseSystemPrompts={BASE_SECURITY_SYSTEM_PROMPTS} // to server and plugin start baseConversations={baseConversations} - getAddConnectorFlyout={getAddConnectorFlyout} - getDeleteConnectorModalConfirmation={getDeleteConnectorModalConfirmation} - getEditConnectorFlyout={getEditConnectorFlyout} getComments={getComments} http={http} - settings={settings} + navigateToApp={navigateToApp} title={ASSISTANT_TITLE} toasts={toasts} > 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 d7d4c180442ba..0a098b4900c77 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 @@ -32,7 +32,7 @@ export const ManagementSettings = React.memo(() => { mergeBaseWithPersistedConversations(baseConversations, conversationsData), [baseConversations] ); - const { data: conversations } = useFetchCurrentUserConversations({ + const { data: conversations, refetch: refetchConversations } = useFetchCurrentUserConversations({ http, onFetch: onFetchedConversations, isAssistantEnabled, @@ -56,6 +56,7 @@ export const ManagementSettings = React.memo(() => { setSelectedConversationId={setSelectedConversationId} conversations={conversations} isFlyoutMode={isFlyoutMode} + refetchConversations={refetchConversations} /> ); } From 84f05a490e780173e458e5f161fc3de1416b1247 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 17 Jun 2024 14:59:11 +0100 Subject: [PATCH 05/37] revert changes --- .../use_fetch_current_user_conversations.ts | 1 - .../conversation_settings_editor.tsx | 76 +++++++++++-- .../use_connector_selector.ts | 100 ------------------ .../system_prompt_modal/translations.ts | 14 +++ 4 files changed, 83 insertions(+), 108 deletions(-) delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_connector_selector.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts index 08cbbeaef20a4..58be08317d40c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/conversations/use_fetch_current_user_conversations.ts @@ -26,7 +26,6 @@ export interface UseFetchCurrentUserConversationsParams { signal?: AbortSignal | undefined; refetchOnWindowFocus?: boolean; isAssistantEnabled: boolean; - query?: { page: number; perPage: number }; } /** 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 691ad911be3a5..91de744ef75e8 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 @@ -129,13 +129,75 @@ export const ConversationSettingsEditor: React.FC + selectedConversation?.id === '' + ? selectedConversation.title + : (selectedConversation?.id as string), + [selectedConversation] + ); + const handleOnConnectorSelectionChange = useCallback( + (connector) => { + if (selectedConversation != null) { + const config = getGenAiConfig(connector); + const updatedConversation = { + ...selectedConversation, + apiConfig: { + ...selectedConversation.apiConfig, + connectorId: connector.id, + actionTypeId: connector.actionTypeId, + provider: config?.apiProvider, + model: config?.defaultModel, + }, + }; + setConversationSettings({ + ...conversationSettings, + [selectedConversationId]: updatedConversation, + }); + if (selectedConversation.id !== '') { + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + update: { + ...(conversationsSettingsBulkActions.update ?? {}), + [updatedConversation.id]: { + ...updatedConversation, + ...(conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {}), + apiConfig: { + ...updatedConversation.apiConfig, + ...((conversationsSettingsBulkActions.update + ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} + : {} + ).apiConfig ?? {}), + connectorId: connector?.id, + actionTypeId: connector?.actionTypeId, + provider: config?.apiProvider, + model: config?.defaultModel, + }, + }, + }, + }); + } else { + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + create: { + ...(conversationsSettingsBulkActions.create ?? {}), + [updatedConversation.id]: updatedConversation, + }, + }); + } + } + }, + [ + conversationSettings, + conversationsSettingsBulkActions, + selectedConversation, + selectedConversationId, + setConversationSettings, + setConversationsSettingsBulkActions, + ] + ); const selectedModel = useMemo(() => { const connectorModel = getGenAiConfig(selectedConnector)?.defaultModel; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_connector_selector.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_connector_selector.ts deleted file mode 100644 index e295452ceaa6a..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_connector_selector.ts +++ /dev/null @@ -1,100 +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 { useCallback, useMemo } from 'react'; -import { Conversation, ConversationsBulkActions } from '../../../..'; -import { getGenAiConfig } from '../../../connectorland/helpers'; - -interface Props { - selectedConversation?: Conversation; - setConversationSettings: React.Dispatch>>; - conversationSettings: Record; - setConversationsSettingsBulkActions: React.Dispatch< - React.SetStateAction - >; - conversationsSettingsBulkActions: ConversationsBulkActions; -} - -export const useConnectorSelector = ({ - selectedConversation, - setConversationSettings, - conversationSettings, - setConversationsSettingsBulkActions, - conversationsSettingsBulkActions, -}: Props) => { - const selectedConversationId = useMemo( - () => - selectedConversation?.id === '' - ? selectedConversation.title - : (selectedConversation?.id as string), - [selectedConversation] - ); - const handleOnConnectorSelectionChange = useCallback( - (connector) => { - if (selectedConversation != null) { - const config = getGenAiConfig(connector); - const updatedConversation = { - ...selectedConversation, - apiConfig: { - ...selectedConversation.apiConfig, - connectorId: connector.id, - actionTypeId: connector.actionTypeId, - provider: config?.apiProvider, - model: config?.defaultModel, - }, - }; - setConversationSettings({ - ...conversationSettings, - [selectedConversationId]: updatedConversation, - }); - if (selectedConversation.id !== '') { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - update: { - ...(conversationsSettingsBulkActions.update ?? {}), - [updatedConversation.id]: { - ...updatedConversation, - ...(conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {}), - apiConfig: { - ...updatedConversation.apiConfig, - ...((conversationsSettingsBulkActions.update - ? conversationsSettingsBulkActions.update[updatedConversation.id] ?? {} - : {} - ).apiConfig ?? {}), - connectorId: connector?.id, - actionTypeId: connector?.actionTypeId, - provider: config?.apiProvider, - model: config?.defaultModel, - }, - }, - }, - }); - } else { - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - create: { - ...(conversationsSettingsBulkActions.create ?? {}), - [updatedConversation.id]: updatedConversation, - }, - }); - } - } - }, - [ - conversationSettings, - conversationsSettingsBulkActions, - selectedConversation, - selectedConversationId, - setConversationSettings, - setConversationsSettingsBulkActions, - ] - ); - - return handleOnConnectorSelectionChange; -}; 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 c9cd7bde54797..06544b98a68bd 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 @@ -68,3 +68,17 @@ export const SYSTEM_PROMPT_DEFAULT_CONVERSATIONS_HELP_TEXT = i18n.translate( defaultMessage: 'Conversations that should use this System Prompt by default', } ); + +export const CANCEL = i18n.translate( + 'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.slCancelButtonTitle', + { + defaultMessage: 'Cancel', + } +); + +export const SAVE = i18n.translate( + 'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.slSaveButtonTitle', + { + defaultMessage: 'Save', + } +); From fd873ae409a0e511afde0ddc9d4ab1ae9f480c1e Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 18 Jun 2024 21:24:27 +0100 Subject: [PATCH 06/37] evaluation tab --- .../assistant_header_flyout.tsx | 3 + .../impl/assistant/assistant_header/index.tsx | 3 + .../use_conversation_selector_settings.tsx | 25 +- .../conversation_settings.tsx | 76 +-- .../conversation_settings_editor.tsx | 3 +- .../use_conversation_changed.tsx | 100 ++++ .../index.tsx | 94 ++-- .../impl/assistant/index.tsx | 2 + .../index.tsx | 6 + .../index.tsx | 6 + .../assistant/settings/assistant_settings.tsx | 4 +- .../settings/assistant_settings_button.tsx | 3 + .../assistant_settings_management.tsx | 47 +- .../use_evaluation_details.tsx | 87 ++++ .../evaluation_settings.tsx | 479 +++--------------- .../prediction_details/index.tsx | 60 +++ .../prediction_details/translations.ts | 35 ++ .../use_predictions_details.tsx | 108 ++++ .../run_details/data_set_toggle_button.tsx | 44 ++ .../evaluation_settings/run_details/index.tsx | 213 ++++++++ .../run_details/translations.ts | 158 ++++++ .../run_details/use_dataset.tsx | 64 +++ .../run_details/use_run_details.tsx | 55 ++ .../run_details/use_trace_options.tsx | 54 ++ .../evaluation_settings/translations.ts | 178 ------- .../evaluation_settings_management/index.tsx | 291 +++++++++++ .../translations.ts | 28 + .../use_settings_updater.tsx | 7 + .../stack_management/management_settings.tsx | 7 +- 29 files changed, 1511 insertions(+), 729 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_changed.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_details/use_evaluation_details.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/index.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/translations.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/use_predictions_details.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/data_set_toggle_button.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/index.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/translations.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_dataset.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_run_details.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_trace_options.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/index.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/translations.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx index be97f716b2740..d9ba27b96655f 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx @@ -43,6 +43,7 @@ interface OwnProps { setChatHistoryVisible?: React.Dispatch>; onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void; conversations: Record; + conversationsLoaded: boolean; refetchConversationsState: () => Promise; onConversationCreate: () => Promise; isAssistantEnabled: boolean; @@ -69,6 +70,7 @@ export const AssistantHeaderFlyout: React.FC = ({ onCloseFlyout, onConversationSelected, conversations, + conversationsLoaded, refetchConversationsState, onConversationCreate, isAssistantEnabled, @@ -158,6 +160,7 @@ export const AssistantHeaderFlyout: React.FC = ({ setIsSettingsModalVisible={setIsSettingsModalVisible} onConversationSelected={onConversationSelected} conversations={conversations} + conversationsLoaded={conversationsLoaded} refetchConversationsState={refetchConversationsState} isFlyoutMode={true} /> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx index bb2e72e2936d3..ac7ca235b36ee 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx @@ -38,6 +38,7 @@ interface OwnProps { showAnonymizedValues: boolean; title: string; conversations: Record; + conversationsLoaded: boolean; refetchConversationsState: () => Promise; } @@ -61,6 +62,7 @@ export const AssistantHeader: React.FC = ({ showAnonymizedValues, title, conversations, + conversationsLoaded, refetchConversationsState, }) => { const showAnonymizedValuesChecked = useMemo( @@ -151,6 +153,7 @@ export const AssistantHeader: React.FC = ({ setIsSettingsModalVisible={setIsSettingsModalVisible} onConversationSelected={onConversationSelected} conversations={conversations} + conversationsLoaded={conversationsLoaded} refetchConversationsState={refetchConversationsState} isFlyoutMode={false} /> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx index 9a60f19209200..282f2e9f6b2f5 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx @@ -20,11 +20,11 @@ export interface UseConversationSelectorSettingsProps { actionTypeRegistry: ActionTypeRegistryContract; connectors: AIConnector[] | undefined; conversations: Record; + defaultConnector?: AIConnector; } export type ConversationTableItem = Conversation & { actionType?: string | null; - systemPrompt?: string; }; export const useConversationsList = ({ @@ -32,17 +32,15 @@ export const useConversationsList = ({ actionTypeRegistry, connectors, conversations = emptyConversations, + defaultConnector, }: UseConversationSelectorSettingsProps) => { const conversationOptions = useMemo(() => { return Object.values(conversations).map((conversation) => { const connector: AIConnector | undefined = connectors?.find( - (c) => c.id === conversation.apiConfig?.connectorId + (c) => c.id === conversation.apiConfig?.connectorId || defaultConnector?.id ); const actionType = getConnectorTypeTitle(connector, actionTypeRegistry); - const systemPrompt: Prompt | undefined = allSystemPrompts.find( - ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId - ); const defaultSystemPrompt = getDefaultSystemPrompt({ allSystemPrompts, @@ -51,14 +49,19 @@ export const useConversationsList = ({ return { ...conversation, actionType, - systemPrompt: - systemPrompt?.label ?? - systemPrompt?.name ?? - defaultSystemPrompt?.label ?? - defaultSystemPrompt?.name, + ...(defaultConnector + ? { + apiConfig: { + connectorId: defaultConnector.id, + actionTypeId: defaultConnector.actionTypeId, + provider: defaultConnector.apiProvider, + defaultSystemPromptId: defaultSystemPrompt?.id, + }, + } + : {}), }; }); - }, [allSystemPrompts, actionTypeRegistry, connectors, conversations]); + }, [conversations, connectors, actionTypeRegistry, allSystemPrompts, defaultConnector]); return conversationOptions; }; 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 9c89fc27302bc..c52af6eb878ab 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 @@ -13,7 +13,7 @@ import { EuiFormRow, EuiSwitch, } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; +import React from 'react'; import { HttpSetup } from '@kbn/core-http-browser'; @@ -24,11 +24,11 @@ import * as i18n from './translations'; import { AIConnector } from '../../../connectorland/connector_selector'; import { ConversationSelectorSettings } from '../conversation_selector_settings'; -import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; import { ConversationsBulkActions } from '../../api'; import { useConversationDeleted } from './use_conversation_deleted'; import { ConversationSettingsEditor } from './conversation_settings_editor'; +import { useConversationChanged } from './use_conversation_changed'; export interface ConversationSettingsProps { actionTypeRegistry: ActionTypeRegistryContract; @@ -68,69 +68,15 @@ export const ConversationSettings: React.FC = React.m conversationsSettingsBulkActions, setConversationsSettingsBulkActions, }) => { - const defaultSystemPrompt = useMemo(() => { - return getDefaultSystemPrompt({ allSystemPrompts, conversation: undefined }); - }, [allSystemPrompts]); - - // Conversation callbacks - // When top level conversation selection changes - const onConversationSelectionChange = useCallback( - (c?: Conversation | string) => { - const isNew = typeof c === 'string'; - - const newSelectedConversation: Conversation | undefined = isNew - ? { - id: '', - title: c ?? '', - category: 'assistant', - messages: [], - replacements: {}, - ...(defaultConnector - ? { - apiConfig: { - connectorId: defaultConnector.id, - actionTypeId: defaultConnector.actionTypeId, - provider: defaultConnector.apiProvider, - defaultSystemPromptId: defaultSystemPrompt?.id, - }, - } - : {}), - } - : c; - - if (newSelectedConversation && (isNew || newSelectedConversation.id === '')) { - setConversationSettings({ - ...conversationSettings, - [isNew ? c : newSelectedConversation.title]: newSelectedConversation, - }); - setConversationsSettingsBulkActions({ - ...conversationsSettingsBulkActions, - create: { - ...(conversationsSettingsBulkActions.create ?? {}), - [newSelectedConversation.title]: newSelectedConversation, - }, - }); - } else if (newSelectedConversation != null) { - setConversationSettings((prev) => { - return { - ...prev, - [newSelectedConversation.id]: newSelectedConversation, - }; - }); - } - - onSelectedConversationChange(newSelectedConversation); - }, - [ - conversationSettings, - conversationsSettingsBulkActions, - defaultConnector, - defaultSystemPrompt?.id, - onSelectedConversationChange, - setConversationSettings, - setConversationsSettingsBulkActions, - ] - ); + const onConversationSelectionChange = useConversationChanged({ + allSystemPrompts, + conversationSettings, + conversationsSettingsBulkActions, + defaultConnector, + setConversationSettings, + setConversationsSettingsBulkActions, + onSelectedConversationChange, + }); const onConversationDeleted = useConversationDeleted({ conversationSettings, 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 91de744ef75e8..12f13a4c9d6d0 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 @@ -22,7 +22,6 @@ import { ModelSelector } from '../../../connectorland/models/model_selector/mode import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; import { getGenAiConfig } from '../../../connectorland/helpers'; import { ConversationsBulkActions } from '../../api'; -import { useConnectorSelector } from './use_connector_selector'; import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; export interface ConversationSettingsEditorProps { @@ -73,7 +72,7 @@ export const ConversationSettingsEditor: React.FC; + conversationsSettingsBulkActions: ConversationsBulkActions; + defaultConnector?: AIConnector; + setConversationSettings: React.Dispatch>>; + setConversationsSettingsBulkActions: React.Dispatch< + React.SetStateAction + >; + onSelectedConversationChange: (conversation?: Conversation) => void; +} + +export const useConversationChanged = ({ + allSystemPrompts, + conversationSettings, + conversationsSettingsBulkActions, + defaultConnector, + setConversationSettings, + setConversationsSettingsBulkActions, + onSelectedConversationChange, +}: Props) => { + const defaultSystemPrompt = useMemo(() => { + return getDefaultSystemPrompt({ allSystemPrompts, conversation: undefined }); + }, [allSystemPrompts]); + + // Conversation callbacks + // When top level conversation selection changes + const onConversationSelectionChange = useCallback( + (c?: Conversation | string) => { + const isNew = typeof c === 'string'; + + const newSelectedConversation: Conversation | undefined = isNew + ? { + id: '', + title: c ?? '', + category: 'assistant', + messages: [], + replacements: {}, + ...(defaultConnector + ? { + apiConfig: { + connectorId: defaultConnector.id, + actionTypeId: defaultConnector.actionTypeId, + provider: defaultConnector.apiProvider, + defaultSystemPromptId: defaultSystemPrompt?.id, + }, + } + : {}), + } + : c; + + if (newSelectedConversation && (isNew || newSelectedConversation.id === '')) { + setConversationSettings({ + ...conversationSettings, + [isNew ? c : newSelectedConversation.title]: newSelectedConversation, + }); + setConversationsSettingsBulkActions({ + ...conversationsSettingsBulkActions, + create: { + ...(conversationsSettingsBulkActions.create ?? {}), + [newSelectedConversation.title]: newSelectedConversation, + }, + }); + } else if (newSelectedConversation != null) { + setConversationSettings((prev) => { + return { + ...prev, + [newSelectedConversation.id]: newSelectedConversation, + }; + }); + } + + onSelectedConversationChange(newSelectedConversation); + }, + [ + conversationSettings, + conversationsSettingsBulkActions, + defaultConnector, + defaultSystemPrompt?.id, + onSelectedConversationChange, + setConversationSettings, + setConversationsSettingsBulkActions, + ] + ); + + return onConversationSelectionChange; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx index fb3b618ea9c8b..f607933623961 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx @@ -36,14 +36,18 @@ import { useFlyoutModalVisibility } from '../../common/components/assisttant_set import { Flyout } from '../../common/components/assisttant_settings_management/flyout'; import { CANCEL, DELETE } from '../../settings/translations'; import { ConversationSettingsEditor } from '../conversation_settings/conversation_settings_editor'; +import { useConversationChanged } from '../conversation_settings/use_conversation_changed'; +import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; interface Props { actionTypeRegistry: ActionTypeRegistryContract; allSystemPrompts: Prompt[]; assistantStreamingEnabled: boolean; connectors: AIConnector[] | undefined; - conversations: Record; + conversationSettings: Record; conversationsSettingsBulkActions: ConversationsBulkActions; + conversationsLoaded: boolean; + defaultConnector?: AIConnector; handleSave: () => void; isDisabled?: boolean; isFlyoutMode: boolean; @@ -63,8 +67,10 @@ const ConversationSettingsManagementComponent: React.FC = ({ allSystemPrompts, assistantStreamingEnabled, connectors, - conversations, + defaultConnector, + conversationSettings, conversationsSettingsBulkActions, + conversationsLoaded, handleSave, isDisabled, isFlyoutMode, @@ -76,6 +82,8 @@ const ConversationSettingsManagementComponent: React.FC = ({ setConversationSettings, setConversationsSettingsBulkActions, }) => { + console.log('conversationSettings----', conversationSettings); + console.log('selectedConversation---', selectedConversation); const { http } = useAssistantContext(); const { isFlyoutOpen: editFlyoutVisible, @@ -90,16 +98,26 @@ const ConversationSettingsManagementComponent: React.FC = ({ closeFlyout: closeConfirmModal, } = useFlyoutModalVisibility(); + const onConversationSelectionChange = useConversationChanged({ + allSystemPrompts, + conversationSettings, + conversationsSettingsBulkActions, + defaultConnector, + setConversationSettings, + setConversationsSettingsBulkActions, + onSelectedConversationChange, + }); + const onEditActionClicked = useCallback( (rowItem: ConversationTableItem) => { openEditFlyout(); - onSelectedConversationChange(rowItem); + onConversationSelectionChange(rowItem); }, - [onSelectedConversationChange, openEditFlyout] + [onConversationSelectionChange, openEditFlyout] ); const onConversationDeleted = useConversationDeleted({ - conversationSettings: conversations, + conversationSettings, conversationsSettingsBulkActions, setConversationSettings, setConversationsSettingsBulkActions, @@ -118,8 +136,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ if (!deleteConversation) return; onConversationDeleted(deleteConversation.title); closeConfirmModal(); - refetchConversations(); - }, [deleteConversation, onConversationDeleted, closeConfirmModal, refetchConversations]); + }, [deleteConversation, onConversationDeleted, closeConfirmModal]); const onDeleteCancelled = useCallback(() => { setDeleteConversation(null); @@ -131,7 +148,8 @@ const ConversationSettingsManagementComponent: React.FC = ({ allSystemPrompts, actionTypeRegistry, connectors, - conversations, + conversations: conversationSettings, + defaultConnector, }); const onEditFlyoutClosed = useCallback(() => { @@ -142,8 +160,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ const onEditFlyoutSaved = useCallback(() => { handleSave(); closeEditFlyout(); - refetchConversations(); - }, [closeEditFlyout, handleSave, refetchConversations]); + }, [closeEditFlyout, handleSave]); const columns: Array> = useMemo( () => [ @@ -154,20 +171,38 @@ const ConversationSettingsManagementComponent: React.FC = ({ ), }, { - field: 'systemPrompt', name: i18n.CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT, - render: (systemPrompt: ConversationTableItem['systemPrompt']) => - systemPrompt ? {systemPrompt} : null, + align: 'center', + render: (conversation: ConversationTableItem) => { + const systemPrompt: Prompt | undefined = allSystemPrompts.find( + ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId + ); + + const defaultSystemPrompt = getDefaultSystemPrompt({ + allSystemPrompts, + conversation, + }); + + const systemPromptTitle = + systemPrompt?.label || + systemPrompt?.name || + defaultSystemPrompt?.label || + defaultSystemPrompt?.name; + + return systemPromptTitle ? {systemPromptTitle} : null; + }, }, { field: 'actionType', name: i18n.CONVERSATIONS_TABLE_COLUMN_CONNECTOR, + align: 'center', render: (actionType: ConversationTableItem['actionType']) => actionType ? {actionType} : null, }, { field: 'updatedAt', name: i18n.CONVERSATIONS_TABLE_COLUMN_UPDATED_AT, + align: 'center', render: (updatedAt: ConversationTableItem['updatedAt']) => updatedAt ? ( @@ -183,23 +218,20 @@ const ConversationSettingsManagementComponent: React.FC = ({ }, { name: i18n.CONVERSATIONS_TABLE_COLUMN_ACTIONS, - actions: [ - { - name: i18n.CONVERSATIONS_TABLE_COLUMN_ACTIONS, - render: (conversation: ConversationTableItem) => { - return ( - - rowItem={conversation} - onEdit={onEditActionClicked} - onDelete={onDeleteActionClicked} - /> - ); - }, - }, - ], + width: '120px', + align: 'center', + render: (prompt: ConversationTableItem) => { + return ( + + rowItem={prompt} + onDelete={onDeleteActionClicked} + onEdit={onEditActionClicked} + /> + ); + }, }, ], - [onDeleteActionClicked, onEditActionClicked] + [allSystemPrompts, onDeleteActionClicked, onEditActionClicked] ); const sorting = useMemo( @@ -212,6 +244,10 @@ const ConversationSettingsManagementComponent: React.FC = ({ [] ); + if (!conversationsLoaded) { + return null; + } + return ( <> @@ -238,7 +274,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ > = ({ setChatHistoryVisible={setChatHistoryVisible} onConversationSelected={handleOnConversationSelected} conversations={conversations} + conversationsLoaded={conversationsLoaded} refetchConversationsState={refetchConversationsState} onConversationCreate={handleCreateConversation} isAssistantEnabled={isAssistantEnabled} @@ -1114,6 +1115,7 @@ const AssistantComponent: React.FC = ({ showAnonymizedValues={showAnonymizedValues} title={title} conversations={conversations} + conversationsLoaded={conversationsLoaded} onConversationDeleted={handleOnConversationDeleted} refetchConversationsState={refetchConversationsState} /> 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 28cf547b15e7e..fcaf88be2b3e9 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 @@ -14,6 +14,7 @@ import { EuiFlexItem, EuiLink, EuiBadge, + EuiSpacer, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import { Conversation, ConversationsBulkActions } from '../../../../..'; @@ -135,12 +136,16 @@ const SystemPromptSettingsManagementComponent = ({ )), }, + /* TODO: enable when createdAt is added { field: 'createdAt', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_CREATED_ON, }, + */ { name: 'Actions', + align: 'center', + width: '120px', render: (prompt: Prompt) => { return ( @@ -178,6 +183,7 @@ const SystemPromptSettingsManagementComponent = ({ + onEditActionClicked(prompt)}>{prompt?.title} ) : null, }, + /* TODO: enable when createdAt is added { field: 'createdAt', name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CREATED_AT, }, + */ { name: i18n.QUICK_PROMPTS_TABLE_COLUMN_ACTIONS, + width: '120px', + align: 'center', render: (prompt: QuickPrompt) => { return ( @@ -139,6 +144,7 @@ const QuickPromptSettingsManagementComponent = ({ + void; conversations: Record; + conversationsLoaded: boolean; } /** @@ -88,6 +89,7 @@ export const AssistantSettings: React.FC = React.memo( selectedConversationId: defaultSelectedConversationId, onConversationSelected, conversations, + conversationsLoaded, isFlyoutMode, }) => { const { @@ -126,7 +128,7 @@ export const AssistantSettings: React.FC = React.memo( anonymizationFieldsBulkActions, setAnonymizationFieldsBulkActions, setUpdatedAnonymizationData, - } = useSettingsUpdater(conversations, anonymizationFields); + } = useSettingsUpdater(conversations, conversationsLoaded, anonymizationFields); // Local state for saving previously selected items so tab switching is friendlier // Conversation Selection State diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx index 47ca1d78de75d..b9b58dc0764a6 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.tsx @@ -23,6 +23,7 @@ interface Props { isDisabled?: boolean; isFlyoutMode: boolean; conversations: Record; + conversationsLoaded: boolean; refetchConversationsState: () => Promise; } @@ -39,6 +40,7 @@ export const AssistantSettingsButton: React.FC = React.memo( isFlyoutMode, onConversationSelected, conversations, + conversationsLoaded, refetchConversationsState, }) => { const { toasts, setSelectedSettingsTab } = useAssistantContext(); @@ -94,6 +96,7 @@ export const AssistantSettingsButton: React.FC = React.memo( onSave={handleSave} isFlyoutMode={isFlyoutMode} conversations={conversations} + conversationsLoaded={conversationsLoaded} /> )} 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 2664f01805bf7..ef505a3b4fa1d 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 @@ -13,7 +13,7 @@ import { Conversation, Prompt, QuickPrompt } from '../../..'; import * as i18n from './translations'; import { useAssistantContext } from '../../assistant_context'; import { useSettingsUpdater } from './use_settings_updater/use_settings_updater'; -import { EvaluationSettings, KnowledgeBaseSettings } from '.'; +import { KnowledgeBaseSettings } from '.'; import { useLoadConnectors } from '../../connectorland/use_load_connectors'; import { getDefaultConnector } from '../helpers'; import { useFetchAnonymizationFields } from '../api/anonymization_fields/use_fetch_anonymization_fields'; @@ -22,6 +22,7 @@ import { ConversationSettingsManagement } from '../conversations/converstaion_se import { QuickPromptSettingsManagement } from '../quick_prompts/quick_prompt_settings_management.tsx'; import { SystemPromptSettingsManagement } from '../prompt_editor/system_prompt/system_prompt_settings_management'; import { AnonymizationSettingsManagement } from '../../data_anonymization/settings/anonomization_settings_management'; +import { EvaluationSettingsManagement } from './evaluation_settings_management'; export const CONNECTORS_TAB = 'CONNECTORS_TAB' as const; export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const; @@ -33,6 +34,7 @@ export const EVALUATION_TAB = 'EVALUATION_TAB' as const; interface Props { conversations: Record; + conversationsLoaded: boolean; selectedConversation: Conversation; setSelectedConversationId: React.Dispatch>; isFlyoutMode: boolean; @@ -48,6 +50,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( selectedConversation: defaultSelectedConversation, setSelectedConversationId, conversations, + conversationsLoaded, isFlyoutMode, refetchConversations, }) => { @@ -92,6 +95,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( resetSettings, } = useSettingsUpdater( conversations, + conversationsLoaded, anonymizationFields ?? { page: 0, perPage: 0, total: 0, data: [] } ); @@ -109,7 +113,11 @@ export const AssistantSettingsManagement: React.FC = React.memo( useEffect(() => { if (selectedConversation != null) { - setSelectedConversation(conversationSettings[selectedConversation.title]); + setSelectedConversation( + // conversationSettings has title as key, sometime has id as key + conversationSettings[selectedConversation.id] || + conversationSettings[selectedConversation.title] + ); } }, [conversationSettings, selectedConversation]); @@ -248,8 +256,10 @@ export const AssistantSettingsManagement: React.FC = React.memo( allSystemPrompts={systemPromptSettings} assistantStreamingEnabled={assistantStreamingEnabled} connectors={connectors} - conversations={conversations} + conversationSettings={conversationSettings} conversationsSettingsBulkActions={conversationsSettingsBulkActions} + conversationsLoaded={conversationsLoaded} + defaultConnector={defaultConnector} handleSave={handleSave} isFlyoutMode={isFlyoutMode} refetchConversations={refetchConversations} @@ -306,37 +316,8 @@ export const AssistantSettingsManagement: React.FC = React.memo( setUpdatedKnowledgeBaseSettings={handleChange(setUpdatedKnowledgeBaseSettings)} /> )} - {selectedSettingsTab === EVALUATION_TAB && } + {selectedSettingsTab === EVALUATION_TAB && } - {/* {hasPendingChanges && ( - - - - - {i18n.CANCEL} - - - - - {i18n.SAVE} - - - - - )} */} ); } diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_details/use_evaluation_details.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_details/use_evaluation_details.tsx new file mode 100644 index 0000000000000..6f877deae07ff --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_details/use_evaluation_details.tsx @@ -0,0 +1,87 @@ +/* + * 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 { EuiComboBoxOptionOption } from '@elastic/eui'; +import { useCallback, useMemo, useState } from 'react'; + +const DEFAULT_EVAL_TYPES_OPTIONS = [ + { label: 'correctness' }, + { label: 'esql-validator', disabled: true }, + { label: 'custom', disabled: true }, +]; + +export interface EvaluationSettings { + selectedEvaluationType: Array>; + onEvaluationTypeChange: (evaluationType: Array>) => void; + onEvaluationTypeOptionsCreate: (searchValue: string) => void; + evaluationTypeOptions: Array>; + selectedEvaluatorModelOptions: Array>; + onEvaluatorModelOptionsChange: (selectedOptions: Array>) => void; + evalPrompt: string; + onEvalPromptChange: (e: React.ChangeEvent) => void; +} + +export const useEvaluationDetails = (): EvaluationSettings => { + // Evaluation + // Evaluation Type + const [selectedEvaluationType, setSelectedEvaluationType] = useState< + Array> + >([]); + const onEvaluationTypeChange = useCallback( + (evaluationType: Array>) => { + setSelectedEvaluationType(evaluationType); + }, + [setSelectedEvaluationType] + ); + const onEvaluationTypeOptionsCreate = useCallback( + (searchValue: string) => { + const normalizedSearchValue = searchValue.trim(); + + if (!normalizedSearchValue) { + return; + } + + setSelectedEvaluationType([{ label: normalizedSearchValue }]); + }, + [setSelectedEvaluationType] + ); + const evaluationTypeOptions = useMemo(() => { + return DEFAULT_EVAL_TYPES_OPTIONS; + }, []); + + // Eval Model + const [selectedEvaluatorModelOptions, setSelectedEvaluatorModelOptions] = useState< + Array> + >([]); + const onEvaluatorModelOptionsChange = useCallback( + (selectedOptions: Array>) => { + setSelectedEvaluatorModelOptions(selectedOptions); + }, + [setSelectedEvaluatorModelOptions] + ); + + // Eval Prompt + const sampleEvalPrompt: string = `For the below input: \n\n{{input}} \n\na prediction: \n\n{{prediction}} \n\nwas made. How's it stack up against this reference: \n\n{{reference}} \n\nReturn output in a succinct sentence ranking on a simple grading rubric focused on correctness.`; + const [evalPrompt, setEvalPrompt] = useState(sampleEvalPrompt); + const onEvalPromptChange = useCallback( + (e) => { + setEvalPrompt(e.target.value); + }, + [setEvalPrompt] + ); + + return { + selectedEvaluationType, + onEvaluationTypeChange, + onEvaluationTypeOptionsCreate, + evaluationTypeOptions, + selectedEvaluatorModelOptions, + onEvaluatorModelOptionsChange, + evalPrompt, + onEvalPromptChange, + }; +}; 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 fe4d75be04004..cb0aa0ebe789d 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 @@ -5,10 +5,9 @@ * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EuiAccordion, - euiPaletteComplementary, EuiFormRow, EuiTitle, EuiText, @@ -16,10 +15,8 @@ import { EuiSpacer, EuiComboBox, EuiButton, - EuiComboBoxOptionOption, EuiTextArea, EuiTextColor, - EuiFieldText, EuiFlexItem, EuiFlexGroup, EuiLink, @@ -27,31 +24,43 @@ import { import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { GetEvaluateResponse, PostEvaluateResponse } from '@kbn/elastic-assistant-common'; +import type { PostEvaluateResponse } from '@kbn/elastic-assistant-common'; import * as i18n from './translations'; import { useAssistantContext } from '../../../assistant_context'; import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; -import { getActionTypeTitle, getGenAiConfig } from '../../../connectorland/helpers'; -import { PRECONFIGURED_CONNECTOR } from '../../../connectorland/translations'; + import { usePerformEvaluation } from '../../api/evaluate/use_perform_evaluation'; import { getApmLink, getDiscoverLink } from './utils'; -import { useEvaluationData } from '../../api/evaluate/use_evaluation_data'; +import { useRunDetails } from './run_details/use_run_details'; +import { useDataset } from './run_details/use_dataset'; +import { useTraceOptions } from './run_details/use_trace_options'; +import { RunDetailsEditor } from './run_details'; +import { PredictionDetails } from './prediction_details'; +import { usePredictionsDetails } from './prediction_details/use_predictions_details'; +import { useEvaluationDetails } from './evaluation_details/use_evaluation_details'; -const DEFAULT_EVAL_TYPES_OPTIONS = [ - { label: 'correctness' }, - { label: 'esql-validator', disabled: true }, - { label: 'custom', disabled: true }, -]; -const DEFAULT_OUTPUT_INDEX = '.kibana-elastic-ai-assistant-evaluation-results'; +const getSection = (title: string, description: string) => ( +
+ + + +

{title}

+
+
+
-interface Props { - onEvaluationSettingsChange?: () => void; -} + +

+ {description} +

+
+
+); /** * Evaluation Settings -- development-only feature for evaluating models */ -export const EvaluationSettings: React.FC = React.memo(({ onEvaluationSettingsChange }) => { +export const EvaluationSettings: React.FC = React.memo(() => { const { actionTypeRegistry, basePath, http, setTraceOptions, traceOptions } = useAssistantContext(); const { data: connectors } = useLoadConnectors({ http }); @@ -62,246 +71,66 @@ export const EvaluationSettings: React.FC = React.memo(({ onEvaluationSet } = usePerformEvaluation({ http, }); - const { data: evalData } = useEvaluationData({ http }); - const defaultAgents = useMemo( - () => (evalData as GetEvaluateResponse)?.agentExecutors ?? [], - [evalData] - ); // Run Details - // Project Name - const [projectName, setProjectName] = useState(); - const onProjectNameChange = useCallback( - (e) => { - setProjectName(e.target.value); - }, - [setProjectName] - ); - // Run Name - const [runName, setRunName] = useState(); - const onRunNameChange = useCallback( - (e) => { - setRunName(e.target.value); - }, - [setRunName] - ); - // Local Output Index - const [outputIndex, setOutputIndex] = useState(DEFAULT_OUTPUT_INDEX); - const onOutputIndexChange = useCallback( - (e) => { - setOutputIndex(e.target.value); - }, - [setOutputIndex] - ); + const runDetailsSettings = useRunDetails(); /** Trace Options **/ - const [showTraceOptions, setShowTraceOptions] = useState(false); - const onApmUrlChange = useCallback( - (e) => { - setTraceOptions({ ...traceOptions, apmUrl: e.target.value }); - }, - [setTraceOptions, traceOptions] - ); - const onLangSmithProjectChange = useCallback( - (e) => { - setTraceOptions({ ...traceOptions, langSmithProject: e.target.value }); - }, - [setTraceOptions, traceOptions] - ); - const onLangSmithApiKeyChange = useCallback( - (e) => { - setTraceOptions({ ...traceOptions, langSmithApiKey: e.target.value }); - }, - [setTraceOptions, traceOptions] - ); + const tracedOptionsSettings = useTraceOptions({ setTraceOptions, traceOptions }); /** Dataset **/ - const [useLangSmithDataset, setUseLangSmithDataset] = useState(true); - const datasetToggleButton = useMemo(() => { - return ( - - {i18n.EVALUATOR_DATASET_LABEL} - {' ('} - setUseLangSmithDataset(true)} - > - {i18n.LANGSMITH_DATASET_LABEL} - - {' / '} - setUseLangSmithDataset(false)} - > - {i18n.CUSTOM_DATASET_LABEL} - - {')'} - - ); - }, [useLangSmithDataset]); - const [datasetName, setDatasetName] = useState(); - const onDatasetNameChange = useCallback( - (e) => { - setDatasetName(e.target.value); - }, - [setDatasetName] - ); - const sampleDataset = [ - { - input: - 'As an expert user of Elastic Security, please generate an accurate and valid ESQL query to detect the use case below. Your response should be formatted to be able to use immediately in an Elastic Security timeline or detection rule. Take your time with the answer, and really make sure you check your knowledge really well on all the functions I am asking for. check it multiple times if you need to. I cannot afford for queries to be inaccurate. Assume I am using the Elastic Common Schema. Ensure the answers are formatted in a way which is easily copyable.\n\n' + - 'Write an ESQL query for detecting cryptomining activity on an AWS EC2 instance.', - reference: - 'FROM metrics-apm*\n| WHERE metricset.name == ""transaction"" AND metricset.interval == ""1m""\n| EVAL bucket = AUTO_BUCKET(transaction.duration.histogram, 50, , )\n| STATS avg_duration = AVG(transaction.duration.histogram) BY bucket', - }, - ]; - const [datasetText, setDatasetText] = useState(JSON.stringify(sampleDataset, null, 2)); - const onDatasetTextChange = useCallback( - (e) => { - setDatasetText(e.target.value); - }, - [setDatasetText] - ); + const datasetSettings = useDataset(); // Predictions - // Connectors / Models - const [selectedModelOptions, setSelectedModelOptions] = useState< - Array> - >([]); - const onModelOptionsChange = useCallback( - (selectedOptions: Array>) => { - setSelectedModelOptions(selectedOptions); - }, - [setSelectedModelOptions] - ); - const visColorsBehindText = euiPaletteComplementary(connectors?.length ?? 0); - const modelOptions = useMemo(() => { - return ( - connectors?.map((c, index) => { - const apiProvider = getGenAiConfig(c)?.apiProvider; - const connectorTypeTitle = - apiProvider ?? getActionTypeTitle(actionTypeRegistry.get(c.actionTypeId)); - const connectorDetails = c.isPreconfigured ? PRECONFIGURED_CONNECTOR : connectorTypeTitle; - return { - key: c.id, - label: `${c.name} (${connectorDetails})`, - color: visColorsBehindText[index], - }; - }) ?? [] - ); - }, [actionTypeRegistry, connectors, visColorsBehindText]); - - // Agents - const [selectedAgentOptions, setSelectedAgentOptions] = useState< - Array> - >([]); - const onAgentOptionsChange = useCallback( - (agentOptions: Array>) => { - setSelectedAgentOptions(agentOptions); - }, - [setSelectedAgentOptions] - ); - const onAgentOptionsCreate = useCallback( - (searchValue: string) => { - const normalizedSearchValue = searchValue.trim(); - - if (!normalizedSearchValue) { - return; - } - - setSelectedAgentOptions([...selectedAgentOptions, { label: normalizedSearchValue }]); - }, - [selectedAgentOptions] - ); - const agentOptions = useMemo(() => { - return defaultAgents.map((label) => ({ label })); - }, [defaultAgents]); - - // Evaluation - // Evaluation Type - const [selectedEvaluationType, setSelectedEvaluationType] = useState< - Array> - >([]); - const onEvaluationTypeChange = useCallback( - (evaluationType: Array>) => { - setSelectedEvaluationType(evaluationType); - }, - [setSelectedEvaluationType] - ); - const onEvaluationTypeOptionsCreate = useCallback( - (searchValue: string) => { - const normalizedSearchValue = searchValue.trim(); - - if (!normalizedSearchValue) { - return; - } - - setSelectedEvaluationType([{ label: normalizedSearchValue }]); - }, - [setSelectedEvaluationType] - ); - const evaluationTypeOptions = useMemo(() => { - return DEFAULT_EVAL_TYPES_OPTIONS; - }, []); - - // Eval Model - const [selectedEvaluatorModelOptions, setSelectedEvaluatorModelOptions] = useState< - Array> - >([]); - const onEvaluatorModelOptionsChange = useCallback( - (selectedOptions: Array>) => { - setSelectedEvaluatorModelOptions(selectedOptions); - }, - [setSelectedEvaluatorModelOptions] - ); + const predictionsSettings = usePredictionsDetails({ + http, + connectors, + actionTypeRegistry, + }); - // Eval Prompt - const sampleEvalPrompt: string = `For the below input: \n\n{{input}} \n\na prediction: \n\n{{prediction}} \n\nwas made. How's it stack up against this reference: \n\n{{reference}} \n\nReturn output in a succinct sentence ranking on a simple grading rubric focused on correctness.`; - const [evalPrompt, setEvalPrompt] = useState(sampleEvalPrompt); - const onEvalPromptChange = useCallback( - (e) => { - setEvalPrompt(e.target.value); - }, - [setEvalPrompt] - ); + const { + selectedEvaluationType, + onEvaluationTypeChange, + onEvaluationTypeOptionsCreate, + evaluationTypeOptions, + selectedEvaluatorModelOptions, + onEvaluatorModelOptionsChange, + evalPrompt, + onEvalPromptChange, + } = useEvaluationDetails(); // Required fields by eval API const isPerformEvaluationDisabled = - selectedModelOptions.length === 0 || - selectedAgentOptions.length === 0 || - outputIndex.length === 0; + predictionsSettings.selectedModelOptions.length === 0 || + predictionsSettings.selectedAgentOptions.length === 0 || + runDetailsSettings.outputIndex.length === 0; // Perform Evaluation Button const handlePerformEvaluation = useCallback(async () => { const evalParams = { - models: selectedModelOptions.flatMap((option) => option.key ?? []), - agents: selectedAgentOptions.map((option) => option.label), - dataset: useLangSmithDataset ? undefined : datasetText, - datasetName: useLangSmithDataset ? datasetName : undefined, + models: predictionsSettings.selectedModelOptions.flatMap((option) => option.key ?? []), + agents: predictionsSettings.selectedAgentOptions.map((option) => option.label), + dataset: datasetSettings.useLangSmithDataset ? undefined : datasetSettings.datasetText, + datasetName: datasetSettings.useLangSmithDataset ? datasetSettings.datasetName : undefined, evalModel: selectedEvaluatorModelOptions.flatMap((option) => option.key ?? []), evalPrompt, evaluationType: selectedEvaluationType.map((option) => option.label), - outputIndex, - projectName, - runName, + outputIndex: runDetailsSettings.outputIndex, + projectName: runDetailsSettings.projectName, + runName: runDetailsSettings.runName, }; performEvaluation(evalParams); }, [ - datasetName, - datasetText, + datasetSettings.datasetName, + datasetSettings.datasetText, + datasetSettings.useLangSmithDataset, evalPrompt, - outputIndex, performEvaluation, - projectName, - runName, - selectedAgentOptions, + predictionsSettings.selectedAgentOptions, + predictionsSettings.selectedModelOptions, + runDetailsSettings.outputIndex, + runDetailsSettings.projectName, + runDetailsSettings.runName, selectedEvaluationType, selectedEvaluatorModelOptions, - selectedModelOptions, - useLangSmithDataset, ]); const discoverLink = useMemo( @@ -314,24 +143,6 @@ export const EvaluationSettings: React.FC = React.memo(({ onEvaluationSet [basePath, evalResponse] ); - const getSection = (title: string, description: string) => ( -
- - - -

{title}

-
-
-
- - -

- {description} -

-
-
- ); - const runDetailsSection = useMemo( () => getSection(i18n.RUN_DETAILS_TITLE, i18n.RUN_DETAILS_DESCRIPTION), [] @@ -369,134 +180,11 @@ export const EvaluationSettings: React.FC = React.memo(({ onEvaluationSet initialIsOpen={true} paddingSize="s" > - - - - - - - - - - - - - - {useLangSmithDataset ? ( - - ) : ( - - )} - - - - - - setShowTraceOptions(!showTraceOptions)}> - {i18n.SHOW_TRACE_OPTIONS} - - - {showTraceOptions && ( - <> - - - - - - - - - - - )} + {/* Prediction Details*/} @@ -509,34 +197,7 @@ export const EvaluationSettings: React.FC = React.memo(({ onEvaluationSet initialIsOpen={true} paddingSize="s" > - - - - - - - + {/* Evaluation Details*/} @@ -556,7 +217,7 @@ export const EvaluationSettings: React.FC = React.memo(({ onEvaluationSet = React.memo(({ predictionsSettings }) => { + const { + modelOptions, + selectedModelOptions, + onModelOptionsChange, + agentOptions, + selectedAgentOptions, + onAgentOptionsChange, + onAgentOptionsCreate, + } = predictionsSettings; + return ( + <> + + + + + + + + + ); +}); + +PredictionDetails.displayName = 'PredictionDetails'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/translations.ts new file mode 100644 index 0000000000000..bf0f07714ec3c --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/translations.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; + +export const CONNECTORS_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsLabel', + { + defaultMessage: 'Connectors / Models', + } +); + +export const CONNECTORS_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsDescription', + { + defaultMessage: 'Select whichever models you want to evaluate the dataset against', + } +); + +export const AGENTS_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.agentsLabel', + { + defaultMessage: 'Agents', + } +); + +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', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/use_predictions_details.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/use_predictions_details.tsx new file mode 100644 index 0000000000000..4b41bb38a75a4 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/use_predictions_details.tsx @@ -0,0 +1,108 @@ +/* + * 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 { EuiComboBoxOptionOption, euiPaletteComplementary } from '@elastic/eui'; +import { useCallback, useState, useMemo } from 'react'; +import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; +import { HttpSetup } from '@kbn/core/public'; +import type { GetEvaluateResponse } from '@kbn/elastic-assistant-common'; +import { AIConnector } from '../../../../connectorland/connector_selector'; +import { getActionTypeTitle, getGenAiConfig } from '../../../../connectorland/helpers'; +import { PRECONFIGURED_CONNECTOR } from '../../../../connectorland/translations'; +import { useEvaluationData } from '../../../api/evaluate/use_evaluation_data'; + +interface Props { + actionTypeRegistry: ActionTypeRegistryContract; + connectors: AIConnector[] | undefined; + http: HttpSetup; +} + +export interface PredictionsSettings { + modelOptions: Array>; + selectedModelOptions: Array>; + onModelOptionsChange: (selectedOptions: Array>) => void; + agentOptions: Array>; + selectedAgentOptions: Array>; + onAgentOptionsChange: (agentOptions: Array>) => void; + onAgentOptionsCreate: (searchValue: string) => void; +} + +export const usePredictionsDetails = ({ + http, + connectors, + actionTypeRegistry, +}: Props): PredictionsSettings => { + const { data: evalData } = useEvaluationData({ http }); + const defaultAgents = useMemo( + () => (evalData as GetEvaluateResponse)?.agentExecutors ?? [], + [evalData] + ); + + // Predictions + // Connectors / Models + const [selectedModelOptions, setSelectedModelOptions] = useState< + Array> + >([]); + const onModelOptionsChange = useCallback( + (selectedOptions: Array>) => { + setSelectedModelOptions(selectedOptions); + }, + [setSelectedModelOptions] + ); + const visColorsBehindText = euiPaletteComplementary(connectors?.length ?? 0); + const modelOptions = useMemo(() => { + return ( + connectors?.map((c, index) => { + const apiProvider = getGenAiConfig(c)?.apiProvider; + const connectorTypeTitle = + apiProvider ?? getActionTypeTitle(actionTypeRegistry.get(c.actionTypeId)); + const connectorDetails = c.isPreconfigured ? PRECONFIGURED_CONNECTOR : connectorTypeTitle; + return { + key: c.id, + label: `${c.name} (${connectorDetails})`, + color: visColorsBehindText[index], + }; + }) ?? [] + ); + }, [actionTypeRegistry, connectors, visColorsBehindText]); + + // Agents + const [selectedAgentOptions, setSelectedAgentOptions] = useState< + Array> + >([]); + const onAgentOptionsChange = useCallback( + (agentOptions: Array>) => { + setSelectedAgentOptions(agentOptions); + }, + [setSelectedAgentOptions] + ); + const onAgentOptionsCreate = useCallback( + (searchValue: string) => { + const normalizedSearchValue = searchValue.trim(); + + if (!normalizedSearchValue) { + return; + } + + setSelectedAgentOptions([...selectedAgentOptions, { label: normalizedSearchValue }]); + }, + [selectedAgentOptions] + ); + const agentOptions = useMemo(() => { + return defaultAgents.map((label) => ({ label })); + }, [defaultAgents]); + + return { + modelOptions, + selectedModelOptions, + onModelOptionsChange, + agentOptions, + selectedAgentOptions, + onAgentOptionsChange, + onAgentOptionsCreate, + }; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/data_set_toggle_button.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/data_set_toggle_button.tsx new file mode 100644 index 0000000000000..b877fcd201a29 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/data_set_toggle_button.tsx @@ -0,0 +1,44 @@ +/* + * 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 { EuiLink, EuiText } from '@elastic/eui'; +import React from 'react'; +import { css } from '@emotion/react'; + +import * as i18n from './translations'; + +interface Props { + useLangSmithDataset: boolean; + onUseLangSmith: () => void; + onUseCustom: () => void; +} + +export const DatasetToggleButton: React.FC = React.memo( + ({ useLangSmithDataset, onUseLangSmith, onUseCustom }) => { + return ( + + {i18n.EVALUATOR_DATASET_LABEL} + {' ('} + onUseLangSmith()}> + {i18n.LANGSMITH_DATASET_LABEL} + + {' / '} + onUseCustom()}> + {i18n.CUSTOM_DATASET_LABEL} + + {')'} + + ); + } +); + +DatasetToggleButton.displayName = 'DatasetToggleButton'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/index.tsx new file mode 100644 index 0000000000000..2962cb4fcc8ca --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/index.tsx @@ -0,0 +1,213 @@ +/* + * 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. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiFormRow, + EuiText, + EuiTextArea, + EuiFieldText, + EuiFlexItem, + EuiFlexGroup, + EuiLink, +} from '@elastic/eui'; + +import { css } from '@emotion/react'; + +import * as i18n from './translations'; +import { DatasetToggleButton } from './data_set_toggle_button'; +import { RunDetailsSettings } from './use_run_details'; +import { DataSetSettings } from './use_dataset'; +import { TraceOptionsSettings } from './use_trace_options'; + +/** + * Evaluation Settings -- development-only feature for evaluating models + */ +interface Props { + runDetailsSettings: RunDetailsSettings; + datasetSettings: DataSetSettings; + traceOptionsSettings: TraceOptionsSettings; +} + +export const RunDetailsEditor: React.FC = React.memo( + ({ runDetailsSettings, datasetSettings, traceOptionsSettings }) => { + const { + projectName, + onProjectNameChange, + runName, + onRunNameChange, + outputIndex, + onOutputIndexChange, + } = runDetailsSettings; + + const { + onUseLangSmith, + onUseCustom, + useLangSmithDataset, + datasetName, + onDatasetNameChange, + datasetText, + onDatasetTextChange, + } = datasetSettings; + + const { + showTraceOptions, + setShowTraceOptions, + traceOptions, + onApmUrlChange, + onLangSmithProjectChange, + onLangSmithApiKeyChange, + } = traceOptionsSettings; + + return ( + <> + + + + + + + + + + + + + + } + fullWidth + helpText={ + useLangSmithDataset + ? i18n.LANGSMITH_DATASET_DESCRIPTION + : i18n.CUSTOM_DATASET_DESCRIPTION + } + > + {useLangSmithDataset ? ( + + ) : ( + + )} + + + + + + setShowTraceOptions(!showTraceOptions)}> + {i18n.SHOW_TRACE_OPTIONS} + + + {showTraceOptions && ( + <> + + + + + + + + + + + )} + + ); + } +); + +RunDetailsEditor.displayName = 'RunDetailsEditor'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/translations.ts new file mode 100644 index 0000000000000..3f62bd40a520e --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/translations.ts @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; + +export const SHOW_TRACE_OPTIONS = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.showTraceOptionsLabel', + { + defaultMessage: 'Show Trace Options (for internal use only)', + } +); + +export const APM_URL_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.apmUrlLabel', + { + defaultMessage: 'APM URL', + } +); + +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 "$\\{basePath\\}/app/apm"', + } +); + +export const EVALUATOR_DATASET_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorDatasetLabel', + { + defaultMessage: 'Dataset', + } +); + +export const LANGSMITH_DATASET_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetLabel', + { + defaultMessage: 'LangSmith', + } +); + +export const LANGSMITH_DATASET_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetDescription', + { + defaultMessage: 'Name of dataset hosted on LangSmith to evaluate', + } +); + +export const LANGSMITH_DATASET_PLACEHOLDER = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetPlaceholder', + { + defaultMessage: 'ESQL Query Generation', + } +); + +export const LANGSMITH_PROJECT_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithProjectLabel', + { + defaultMessage: 'LangSmith Project', + } +); + +export const LANGSMITH_PROJECT_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithProjectDescription', + { + defaultMessage: 'LangSmith Project to write traces to', + } +); + +export const LANGSMITH_API_KEY_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithApiKeyLabel', + { + defaultMessage: 'LangSmith API Key', + } +); + +export const LANGSMITH_API_KEY_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithApiKeyDescription', + { + defaultMessage: + 'API Key for writing traces to LangSmith. Stored in Session Storage. Close tab to clear session.', + } +); + +export const CUSTOM_DATASET_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.customDatasetLabel', + { + defaultMessage: 'Custom', + } +); + +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', + } +); + +export const EVALUATOR_OUTPUT_INDEX_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorOutputIndexLabel', + { + defaultMessage: 'Output index', + } +); + +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-"', + } +); + +export const PROJECT_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectLabel', + { + defaultMessage: 'Project', + } +); + +export const PROJECT_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectDescription', + { + defaultMessage: 'LangSmith project to write results to', + } +); + +export const PROJECT_PLACEHOLDER = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectPlaceholder', + { + defaultMessage: '8.12 Testing', + } +); + +export const RUN_NAME_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNameLabel', + { + defaultMessage: 'Run name', + } +); + +export const RUN_NAME_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNameDescription', + { + defaultMessage: 'Name for this specific test run', + } +); + +export const RUN_NAME_PLACEHOLDER = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNamePlaceholder', + { + defaultMessage: '8.12 ESQL Query Generation', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_dataset.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_dataset.tsx new file mode 100644 index 0000000000000..0959096d92c9a --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_dataset.tsx @@ -0,0 +1,64 @@ +/* + * 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 { useCallback, useState } from 'react'; + +export interface DataSetSettings { + onUseLangSmith: () => void; + onUseCustom: () => void; + useLangSmithDataset: boolean; + datasetName: string | undefined; + onDatasetNameChange: (e: React.ChangeEvent) => void; + datasetText: string; + onDatasetTextChange: (e: React.ChangeEvent) => void; +} + +export const useDataset = (): DataSetSettings => { + /** Dataset **/ + const [useLangSmithDataset, setUseLangSmithDataset] = useState(true); + + const [datasetName, setDatasetName] = useState(); + const onDatasetNameChange = useCallback( + (e) => { + setDatasetName(e.target.value); + }, + [setDatasetName] + ); + const sampleDataset = [ + { + input: + 'As an expert user of Elastic Security, please generate an accurate and valid ESQL query to detect the use case below. Your response should be formatted to be able to use immediately in an Elastic Security timeline or detection rule. Take your time with the answer, and really make sure you check your knowledge really well on all the functions I am asking for. check it multiple times if you need to. I cannot afford for queries to be inaccurate. Assume I am using the Elastic Common Schema. Ensure the answers are formatted in a way which is easily copyable.\n\n' + + 'Write an ESQL query for detecting cryptomining activity on an AWS EC2 instance.', + reference: + 'FROM metrics-apm*\n| WHERE metricset.name == ""transaction"" AND metricset.interval == ""1m""\n| EVAL bucket = AUTO_BUCKET(transaction.duration.histogram, 50, , )\n| STATS avg_duration = AVG(transaction.duration.histogram) BY bucket', + }, + ]; + const [datasetText, setDatasetText] = useState(JSON.stringify(sampleDataset, null, 2)); + const onDatasetTextChange = useCallback( + (e) => { + setDatasetText(e.target.value); + }, + [setDatasetText] + ); + + const onUseLangSmith = useCallback(() => { + setUseLangSmithDataset(true); + }, []); + + const onUseCustom = useCallback(() => { + setUseLangSmithDataset(false); + }, []); + + return { + onUseLangSmith, + onUseCustom, + useLangSmithDataset, + datasetName, + onDatasetNameChange, + datasetText, + onDatasetTextChange, + }; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_run_details.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_run_details.tsx new file mode 100644 index 0000000000000..7b594ac55af33 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_run_details.tsx @@ -0,0 +1,55 @@ +/* + * 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 { useCallback, useState } from 'react'; + +const DEFAULT_OUTPUT_INDEX = '.kibana-elastic-ai-assistant-evaluation-results'; + +export interface RunDetailsSettings { + projectName: string | undefined; + onProjectNameChange: (e: React.ChangeEvent) => void; + runName: string | undefined; + onRunNameChange: (e: React.ChangeEvent) => void; + outputIndex: string; + onOutputIndexChange: (e: React.ChangeEvent) => void; +} + +export const useRunDetails = (): RunDetailsSettings => { + // Run Details + // Project Name + const [projectName, setProjectName] = useState(); + const onProjectNameChange = useCallback( + (e) => { + setProjectName(e.target.value); + }, + [setProjectName] + ); + // Run Name + const [runName, setRunName] = useState(); + const onRunNameChange = useCallback( + (e) => { + setRunName(e.target.value); + }, + [setRunName] + ); + // Local Output Index + const [outputIndex, setOutputIndex] = useState(DEFAULT_OUTPUT_INDEX); + const onOutputIndexChange = useCallback( + (e) => { + setOutputIndex(e.target.value); + }, + [setOutputIndex] + ); + + return { + projectName, + onProjectNameChange, + runName, + onRunNameChange, + outputIndex, + onOutputIndexChange, + }; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_trace_options.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_trace_options.tsx new file mode 100644 index 0000000000000..be25925d71776 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_trace_options.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useCallback, useState } from 'react'; +import { UseAssistantContext } from '../../../../assistant_context'; + +interface Props { + setTraceOptions: UseAssistantContext['setTraceOptions']; + traceOptions: UseAssistantContext['traceOptions']; +} + +export interface TraceOptionsSettings { + showTraceOptions: boolean; + setShowTraceOptions: (value: boolean) => void; + traceOptions: UseAssistantContext['traceOptions']; + onApmUrlChange: (e: React.ChangeEvent) => void; + onLangSmithProjectChange: (e: React.ChangeEvent) => void; + onLangSmithApiKeyChange: (e: React.ChangeEvent) => void; +} + +export const useTraceOptions = ({ setTraceOptions, traceOptions }: Props): TraceOptionsSettings => { + /** Trace Options **/ + const [showTraceOptions, setShowTraceOptions] = useState(false); + const onApmUrlChange = useCallback( + (e) => { + setTraceOptions({ ...traceOptions, apmUrl: e.target.value }); + }, + [setTraceOptions, traceOptions] + ); + const onLangSmithProjectChange = useCallback( + (e) => { + setTraceOptions({ ...traceOptions, langSmithProject: e.target.value }); + }, + [setTraceOptions, traceOptions] + ); + const onLangSmithApiKeyChange = useCallback( + (e) => { + setTraceOptions({ ...traceOptions, langSmithApiKey: e.target.value }); + }, + [setTraceOptions, traceOptions] + ); + + return { + showTraceOptions, + setShowTraceOptions, + traceOptions, + onApmUrlChange, + onLangSmithProjectChange, + onLangSmithApiKeyChange, + }; +}; 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 1b670384696c3..be33991b0178d 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 @@ -65,76 +65,6 @@ export const EVALUATION_DETAILS_DESCRIPTION = i18n.translate( } ); -export const PROJECT_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectLabel', - { - defaultMessage: 'Project', - } -); - -export const PROJECT_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectDescription', - { - defaultMessage: 'LangSmith project to write results to', - } -); - -export const PROJECT_PLACEHOLDER = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectPlaceholder', - { - defaultMessage: '8.12 Testing', - } -); - -export const RUN_NAME_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNameLabel', - { - defaultMessage: 'Run name', - } -); - -export const RUN_NAME_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNameDescription', - { - defaultMessage: 'Name for this specific test run', - } -); - -export const RUN_NAME_PLACEHOLDER = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNamePlaceholder', - { - defaultMessage: '8.12 ESQL Query Generation', - } -); - -export const CONNECTORS_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsLabel', - { - defaultMessage: 'Connectors / Models', - } -); - -export const CONNECTORS_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsDescription', - { - defaultMessage: 'Select whichever models you want to evaluate the dataset against', - } -); - -export const AGENTS_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.agentsLabel', - { - defaultMessage: 'Agents', - } -); - -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', - } -); - export const EVALUATOR_MODEL_LABEL = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorModelLabel', { @@ -178,114 +108,6 @@ export const EVALUATION_PROMPT_DESCRIPTION = i18n.translate( 'Prompt template given `input`, `reference` and `prediction` template variables', } ); -export const EVALUATOR_OUTPUT_INDEX_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorOutputIndexLabel', - { - defaultMessage: 'Output index', - } -); - -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-"', - } -); - -export const SHOW_TRACE_OPTIONS = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.showTraceOptionsLabel', - { - defaultMessage: 'Show Trace Options (for internal use only)', - } -); - -export const APM_URL_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.apmUrlLabel', - { - defaultMessage: 'APM URL', - } -); - -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 "$\\{basePath\\}/app/apm"', - } -); - -export const LANGSMITH_PROJECT_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithProjectLabel', - { - defaultMessage: 'LangSmith Project', - } -); - -export const LANGSMITH_PROJECT_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithProjectDescription', - { - defaultMessage: 'LangSmith Project to write traces to', - } -); - -export const LANGSMITH_API_KEY_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithApiKeyLabel', - { - defaultMessage: 'LangSmith API Key', - } -); - -export const LANGSMITH_API_KEY_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithApiKeyDescription', - { - defaultMessage: - 'API Key for writing traces to LangSmith. Stored in Session Storage. Close tab to clear session.', - } -); - -export const EVALUATOR_DATASET_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorDatasetLabel', - { - defaultMessage: 'Dataset', - } -); - -export const LANGSMITH_DATASET_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetLabel', - { - defaultMessage: 'LangSmith', - } -); - -export const LANGSMITH_DATASET_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetDescription', - { - defaultMessage: 'Name of dataset hosted on LangSmith to evaluate', - } -); - -export const LANGSMITH_DATASET_PLACEHOLDER = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetPlaceholder', - { - defaultMessage: 'ESQL Query Generation', - } -); - -export const CUSTOM_DATASET_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.customDatasetLabel', - { - defaultMessage: 'Custom', - } -); - -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', - } -); export const PERFORM_EVALUATION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.performEvaluationTitle', diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/index.tsx new file mode 100644 index 0000000000000..cb8c8ea8983f2 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/index.tsx @@ -0,0 +1,291 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo } from 'react'; +import { + EuiAccordion, + EuiFormRow, + EuiTitle, + EuiText, + EuiHorizontalRule, + EuiSpacer, + EuiComboBox, + EuiButton, + EuiTextArea, + EuiFlexItem, + EuiFlexGroup, + EuiLink, +} from '@elastic/eui'; + +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { PostEvaluateResponse } from '@kbn/elastic-assistant-common'; +import { useAssistantContext } from '../../../assistant_context'; +import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; + +import { usePerformEvaluation } from '../../api/evaluate/use_perform_evaluation'; +import { getApmLink, getDiscoverLink } from '../evaluation_settings/utils'; +import { useEvaluationDetails } from '../evaluation_settings/evaluation_details/use_evaluation_details'; +import { useRunDetails } from '../evaluation_settings/run_details/use_run_details'; +import { useTraceOptions } from '../evaluation_settings/run_details/use_trace_options'; +import { useDataset } from '../evaluation_settings/run_details/use_dataset'; +import { usePredictionsDetails } from '../evaluation_settings/prediction_details/use_predictions_details'; +import * as i18n from '../evaluation_settings/translations'; +import { RunDetailsEditor } from '../evaluation_settings/run_details'; +import { PredictionDetails } from '../evaluation_settings/prediction_details'; +import { + EVALUATION_DETAILS_TITLE, + PREDICTION_DETAILS_TITLE, + RUN_DETAILS_TITLE, +} from './translations'; + +const getSection = (title: string, description?: string) => ( + <> + +

{title}

+
+ + {description && {description}} + + +); + +export const EvaluationSettingsManagement: React.FC = React.memo(() => { + const { actionTypeRegistry, basePath, http, setTraceOptions, traceOptions } = + useAssistantContext(); + const { data: connectors } = useLoadConnectors({ http }); + const { + data: evalResponse, + mutate: performEvaluation, + isLoading: isPerformingEvaluation, + } = usePerformEvaluation({ + http, + }); + + // Run Details + const runDetailsSettings = useRunDetails(); + /** Trace Options **/ + const tracedOptionsSettings = useTraceOptions({ setTraceOptions, traceOptions }); + /** Dataset **/ + const datasetSettings = useDataset(); + // Predictions + const predictionsSettings = usePredictionsDetails({ + http, + connectors, + actionTypeRegistry, + }); + + const { + selectedEvaluationType, + onEvaluationTypeChange, + onEvaluationTypeOptionsCreate, + evaluationTypeOptions, + selectedEvaluatorModelOptions, + onEvaluatorModelOptionsChange, + evalPrompt, + onEvalPromptChange, + } = useEvaluationDetails(); + + // Required fields by eval API + const isPerformEvaluationDisabled = + predictionsSettings.selectedModelOptions.length === 0 || + predictionsSettings.selectedAgentOptions.length === 0 || + runDetailsSettings.outputIndex.length === 0; + + // Perform Evaluation Button + const handlePerformEvaluation = useCallback(async () => { + const evalParams = { + models: predictionsSettings.selectedModelOptions.flatMap((option) => option.key ?? []), + agents: predictionsSettings.selectedAgentOptions.map((option) => option.label), + dataset: datasetSettings.useLangSmithDataset ? undefined : datasetSettings.datasetText, + datasetName: datasetSettings.useLangSmithDataset ? datasetSettings.datasetName : undefined, + evalModel: selectedEvaluatorModelOptions.flatMap((option) => option.key ?? []), + evalPrompt, + evaluationType: selectedEvaluationType.map((option) => option.label), + outputIndex: runDetailsSettings.outputIndex, + projectName: runDetailsSettings.projectName, + runName: runDetailsSettings.runName, + }; + performEvaluation(evalParams); + }, [ + datasetSettings.datasetName, + datasetSettings.datasetText, + datasetSettings.useLangSmithDataset, + evalPrompt, + performEvaluation, + predictionsSettings.selectedAgentOptions, + predictionsSettings.selectedModelOptions, + runDetailsSettings.outputIndex, + runDetailsSettings.projectName, + runDetailsSettings.runName, + selectedEvaluationType, + selectedEvaluatorModelOptions, + ]); + + const discoverLink = useMemo( + () => getDiscoverLink(basePath, (evalResponse as PostEvaluateResponse)?.evaluationId ?? ''), + [basePath, evalResponse] + ); + + const apmLink = useMemo( + () => getApmLink(basePath, (evalResponse as PostEvaluateResponse)?.evaluationId ?? ''), + [basePath, evalResponse] + ); + + const runDetailsSection = useMemo( + () => getSection(RUN_DETAILS_TITLE, i18n.RUN_DETAILS_DESCRIPTION), + [] + ); + const predictionDetailsSection = useMemo( + () => getSection(PREDICTION_DETAILS_TITLE, i18n.PREDICTION_DETAILS_DESCRIPTION), + [] + ); + const evalDetailsSection = useMemo(() => getSection(EVALUATION_DETAILS_TITLE), []); + + const buttonCss = css` + &:hover { + text-decoration: none; + } + `; + + return ( + <> + +

{i18n.SETTINGS_TITLE}

+
+ + {i18n.SETTINGS_DESCRIPTION} + + {/* Run Details*/} + + + + + {/* Prediction Details*/} + + + + + {/* Evaluation Details*/} + + + + + + + + + + {i18n.PERFORM_EVALUATION} + + + + + + {i18n.EVALUATOR_FUN_FACT_DISCOVER_LINK} + + ), + apm: ( + + {i18n.EVALUATOR_FUN_FACT_APM_LINK} + + ), + }} + /> + + + + + + + + + + + + + + + + ); +}); +EvaluationSettingsManagement.displayName = 'EvaluationSettingsManagement'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/translations.ts new file mode 100644 index 0000000000000..ebec3c45c4bdc --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/translations.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; + +export const RUN_DETAILS_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettingsManagement.runDetailsTitle', + { + defaultMessage: 'Run Details', + } +); + +export const PREDICTION_DETAILS_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettingsManagement.predictionDetailsTitle', + { + defaultMessage: 'Predictions', + } +); + +export const EVALUATION_DETAILS_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettingsManagement.evaluationDetailsTitle', + { + defaultMessage: 'Evaluation (Optional)', + } +); 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 1e1c3b0b026d6..a4c0959b57ff9 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 @@ -46,6 +46,7 @@ interface UseSettingsUpdater { export const useSettingsUpdater = ( conversations: Record, + conversationsLoaded: boolean, anonymizationFields: FindAnonymizationFieldsResponse ): UseSettingsUpdater => { // Initial state from assistant context @@ -188,6 +189,12 @@ export const useSettingsUpdater = ( anonymizationFieldsBulkActions.update?.length, ]); + useEffect(() => { + if (conversationsLoaded) { + setConversationSettings(conversations); + } + }, [conversations, conversationsLoaded]); + return { conversationSettings, conversationsSettingsBulkActions, 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 0a098b4900c77..c648f8f6a50c6 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 @@ -32,7 +32,11 @@ export const ManagementSettings = React.memo(() => { mergeBaseWithPersistedConversations(baseConversations, conversationsData), [baseConversations] ); - const { data: conversations, refetch: refetchConversations } = useFetchCurrentUserConversations({ + const { + data: conversations, + refetch: refetchConversations, + isFetched: conversationsLoaded, + } = useFetchCurrentUserConversations({ http, onFetch: onFetchedConversations, isAssistantEnabled, @@ -55,6 +59,7 @@ export const ManagementSettings = React.memo(() => { selectedConversation={currentConversation} setSelectedConversationId={setSelectedConversationId} conversations={conversations} + conversationsLoaded={conversationsLoaded} isFlyoutMode={isFlyoutMode} refetchConversations={refetchConversations} /> From d5892f1b253354be97440db6cd5aabb925269945 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 20 Jun 2024 14:15:49 +0100 Subject: [PATCH 07/37] system prompt --- .../flyout/index.tsx | 0 .../flyout/translations.ts | 0 .../flyout/use_flyout_modal_visibility.ts | 0 .../row_actions/index.tsx} | 40 +++-- .../row_actions/translations.ts | 22 +++ .../use_conversation_selector_settings.tsx | 67 ------- .../use_conversation_changed.tsx | 11 +- .../index.tsx | 164 ++++++------------ .../system_prompt_column.tsx | 38 ++++ .../translations.ts | 12 +- .../use_conversation_selector_settings.tsx | 134 ++++++++++++++ .../system_prompt_editor.tsx | 107 ++++++------ .../system_prompt_settings.tsx | 2 + .../system_prompt_modal/types.ts | 1 + .../use_system_prompt_editor.tsx | 58 +++++++ .../default_conversations_column.tsx | 50 ++++++ .../index.tsx | 98 ++++------- .../use_system_prompt_table.tsx | 116 +++++++++++++ .../utils.tsx | 24 +++ .../helpers.ts | 31 ---- .../index.tsx | 10 +- .../assistant_settings_management.tsx | 24 +-- .../use_settings_updater.tsx | 1 + .../assistant/use_conversation/helpers.ts | 38 +++- .../impl/connectorland/translations.ts | 14 -- 25 files changed, 681 insertions(+), 381 deletions(-) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/{assisttant_settings_management => assistant_settings_management}/flyout/index.tsx (100%) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/{assisttant_settings_management => assistant_settings_management}/flyout/translations.ts (100%) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/{assisttant_settings_management => assistant_settings_management}/flyout/use_flyout_modal_visibility.ts (100%) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/{assisttant_settings_management/row_actions.tsx => assistant_settings_management/row_actions/index.tsx} (73%) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/translations.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/system_prompt_column.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/use_conversation_selector_settings.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/default_conversations_column.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/helpers.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/index.tsx similarity index 100% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/index.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/index.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/translations.ts similarity index 100% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/translations.ts rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/translations.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/use_flyout_modal_visibility.ts similarity index 100% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility.ts rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/use_flyout_modal_visibility.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/row_actions.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/index.tsx similarity index 73% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/row_actions.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/index.tsx index 7eeaf07f930d2..558d17cd66086 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assisttant_settings_management/row_actions.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/index.tsx @@ -7,22 +7,25 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui'; import React, { useCallback, useState } from 'react'; - -import { - DELETE_CONNECTOR_BUTTON, - EDIT_CONNECTOR_BUTTON, -} from '../../../../connectorland/translations'; +import * as i18n from './translations'; interface Props { - rowItem: T; - onEdit?: (rowItem: T) => void; + isDeletable?: boolean; + isEditable?: boolean; onDelete?: (rowItem: T) => void; - disabled?: boolean; + onEdit?: (rowItem: T) => void; + rowItem: T; } type RowActionsComponentType = (props: Props) => JSX.Element; -const RowActionsComponent = ({ disabled, rowItem, onEdit, onDelete }: Props) => { +const RowActionsComponent = ({ + isDeletable = true, + isEditable = true, + onDelete, + onEdit, + rowItem, +}: Props) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const closePopover = useCallback(() => setIsPopoverOpen(false), []); @@ -54,15 +57,26 @@ const RowActionsComponent = ({ disabled, rowItem, onEdit, onDelete }: Props< {onEdit != null && ( - - {EDIT_CONNECTOR_BUTTON} + + {i18n.EDIT_BUTTON} )} {onDelete != null && ( - - {DELETE_CONNECTOR_BUTTON} + + {i18n.DELETE_BUTTON} )} 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/row_actions/translations.ts new file mode 100644 index 0000000000000..1a2430cb6428c --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/row_actions/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const EDIT_BUTTON = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.editButtonTitle', + { + defaultMessage: 'Edit', + } +); + +export const DELETE_BUTTON = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.deleteButtonTitle', + { + defaultMessage: 'Delete', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx deleted file mode 100644 index 282f2e9f6b2f5..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_selector_settings/use_conversation_selector_settings.tsx +++ /dev/null @@ -1,67 +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 { useMemo } from 'react'; -import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; -import { Conversation } from '../../../assistant_context/types'; -import { AIConnector } from '../../../connectorland/connector_selector'; -import { getConnectorTypeTitle } from '../../../connectorland/helpers'; -import { Prompt } from '../../../..'; -import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; - -const emptyConversations = {}; - -export interface UseConversationSelectorSettingsProps { - allSystemPrompts: Prompt[]; - actionTypeRegistry: ActionTypeRegistryContract; - connectors: AIConnector[] | undefined; - conversations: Record; - defaultConnector?: AIConnector; -} - -export type ConversationTableItem = Conversation & { - actionType?: string | null; -}; - -export const useConversationsList = ({ - allSystemPrompts, - actionTypeRegistry, - connectors, - conversations = emptyConversations, - defaultConnector, -}: UseConversationSelectorSettingsProps) => { - const conversationOptions = useMemo(() => { - return Object.values(conversations).map((conversation) => { - const connector: AIConnector | undefined = connectors?.find( - (c) => c.id === conversation.apiConfig?.connectorId || defaultConnector?.id - ); - - const actionType = getConnectorTypeTitle(connector, actionTypeRegistry); - - const defaultSystemPrompt = getDefaultSystemPrompt({ - allSystemPrompts, - conversation, - }); - return { - ...conversation, - actionType, - ...(defaultConnector - ? { - apiConfig: { - connectorId: defaultConnector.id, - actionTypeId: defaultConnector.actionTypeId, - provider: defaultConnector.apiProvider, - defaultSystemPromptId: defaultSystemPrompt?.id, - }, - } - : {}), - }; - }); - }, [conversations, connectors, actionTypeRegistry, allSystemPrompts, defaultConnector]); - - return conversationOptions; -}; 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 eadcd468cf841..ef3bd0511a68b 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 @@ -23,6 +23,8 @@ interface Props { onSelectedConversationChange: (conversation?: Conversation) => void; } +type OnConversationSelectionChange = (c?: string | Conversation) => void; + export const useConversationChanged = ({ allSystemPrompts, conversationSettings, @@ -38,8 +40,8 @@ export const useConversationChanged = ({ // Conversation callbacks // When top level conversation selection changes - const onConversationSelectionChange = useCallback( - (c?: Conversation | string) => { + const onConversationSelectionChange: OnConversationSelectionChange = useCallback( + (c = '') => { const isNew = typeof c === 'string'; const newSelectedConversation: Conversation | undefined = isNew @@ -83,7 +85,10 @@ export const useConversationChanged = ({ }); } - onSelectedConversationChange(newSelectedConversation); + onSelectedConversationChange({ + ...newSelectedConversation, + id: newSelectedConversation.id || newSelectedConversation.title, + }); }, [ conversationSettings, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx index f607933623961..4f2545fe6f24b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx @@ -5,39 +5,25 @@ * 2.0. */ -import { - EuiPanel, - EuiSpacer, - EuiBadge, - EuiLink, - EuiBasicTableColumn, - EuiConfirmModal, - EuiInMemoryTable, -} from '@elastic/eui'; +import { EuiPanel, EuiSpacer, EuiConfirmModal, EuiInMemoryTable } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; -import { FormattedDate } from '@kbn/i18n-react'; import { Conversation } from '../../../assistant_context/types'; -import { - ConversationTableItem, - useConversationsList, -} from '../conversation_selector_settings/use_conversation_selector_settings'; +import { ConversationTableItem, useConversationsTable } from './use_conversation_selector_settings'; import { ConversationStreamingSwitch } from '../conversation_settings/conversation_streaming_switch'; import { AIConnector } from '../../../connectorland/connector_selector'; -import { RowActions } from '../../common/components/assisttant_settings_management/row_actions'; import * as i18n from './translations'; import { Prompt } from '../../types'; import { ConversationsBulkActions } from '../../api'; import { useAssistantContext } from '../../../assistant_context'; import { useConversationDeleted } from '../conversation_settings/use_conversation_deleted'; -import { useFlyoutModalVisibility } from '../../common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility'; -import { Flyout } from '../../common/components/assisttant_settings_management/flyout'; +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 { ConversationSettingsEditor } from '../conversation_settings/conversation_settings_editor'; import { useConversationChanged } from '../conversation_settings/use_conversation_changed'; -import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; interface Props { actionTypeRegistry: ActionTypeRegistryContract; @@ -82,15 +68,13 @@ const ConversationSettingsManagementComponent: React.FC = ({ setConversationSettings, setConversationsSettingsBulkActions, }) => { - console.log('conversationSettings----', conversationSettings); - console.log('selectedConversation---', selectedConversation); const { http } = useAssistantContext(); const { isFlyoutOpen: editFlyoutVisible, openFlyout: openEditFlyout, closeFlyout: closeEditFlyout, } = useFlyoutModalVisibility(); - const [deleteConversation, setDeleteConversation] = useState(); + const [deletedConversation, setDeletedConversation] = useState(); const { isFlyoutOpen: deleteConfirmModalVisibility, @@ -125,26 +109,41 @@ const ConversationSettingsManagementComponent: React.FC = ({ const onDeleteActionClicked = useCallback( (rowItem: ConversationTableItem) => { - setDeleteConversation(rowItem); + setDeletedConversation(rowItem); + onConversationDeleted(rowItem.title); + closeEditFlyout(); openConfirmModal(); }, - [closeEditFlyout, openConfirmModal] + [closeEditFlyout, onConversationDeleted, openConfirmModal] ); const onDeleteConfirmed = useCallback(() => { - if (!deleteConversation) return; - onConversationDeleted(deleteConversation.title); + if (Object.keys(conversationsSettingsBulkActions).length === 0) { + return; + } + + handleSave(); closeConfirmModal(); - }, [deleteConversation, onConversationDeleted, closeConfirmModal]); + setConversationsSettingsBulkActions({}); + refetchConversations(); + }, [ + closeConfirmModal, + conversationsSettingsBulkActions, + handleSave, + refetchConversations, + setConversationsSettingsBulkActions, + ]); const onDeleteCancelled = useCallback(() => { - setDeleteConversation(null); + setDeletedConversation(null); closeConfirmModal(); resetSettings(); }, [closeConfirmModal, resetSettings]); - const conversationOptions = useConversationsList({ + const { getConversationsList, getColumns } = useConversationsTable(); + + const conversationOptions = getConversationsList({ allSystemPrompts, actionTypeRegistry, connectors, @@ -152,86 +151,27 @@ const ConversationSettingsManagementComponent: React.FC = ({ defaultConnector, }); - const onEditFlyoutClosed = useCallback(() => { + const onSaveCancelled = useCallback(() => { closeEditFlyout(); resetSettings(); }, [closeEditFlyout, resetSettings]); - const onEditFlyoutSaved = useCallback(() => { + const onSaveConfirmed = useCallback(() => { handleSave(); closeEditFlyout(); - }, [closeEditFlyout, handleSave]); - - const columns: Array> = useMemo( - () => [ - { - name: i18n.CONVERSATIONS_TABLE_COLUMN_TYPE, - render: (conversation: ConversationTableItem) => ( - onEditActionClicked(conversation)}>{conversation.title} - ), - }, - { - name: i18n.CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT, - align: 'center', - render: (conversation: ConversationTableItem) => { - const systemPrompt: Prompt | undefined = allSystemPrompts.find( - ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId - ); - - const defaultSystemPrompt = getDefaultSystemPrompt({ - allSystemPrompts, - conversation, - }); + refetchConversations(); + setConversationsSettingsBulkActions({}); + }, [closeEditFlyout, handleSave, refetchConversations, setConversationsSettingsBulkActions]); - const systemPromptTitle = - systemPrompt?.label || - systemPrompt?.name || - defaultSystemPrompt?.label || - defaultSystemPrompt?.name; - - return systemPromptTitle ? {systemPromptTitle} : null; - }, - }, - { - field: 'actionType', - name: i18n.CONVERSATIONS_TABLE_COLUMN_CONNECTOR, - align: 'center', - render: (actionType: ConversationTableItem['actionType']) => - actionType ? {actionType} : null, - }, - { - field: 'updatedAt', - name: i18n.CONVERSATIONS_TABLE_COLUMN_UPDATED_AT, - align: 'center', - render: (updatedAt: ConversationTableItem['updatedAt']) => - updatedAt ? ( - - - - ) : null, - }, - { - name: i18n.CONVERSATIONS_TABLE_COLUMN_ACTIONS, - width: '120px', - align: 'center', - render: (prompt: ConversationTableItem) => { - return ( - - rowItem={prompt} - onDelete={onDeleteActionClicked} - onEdit={onEditActionClicked} - /> - ); - }, - }, - ], - [allSystemPrompts, onDeleteActionClicked, onEditActionClicked] + const columns = useMemo( + () => + getColumns({ + conversations: conversationSettings, + onDeleteActionClicked, + onEditActionClicked, + allSystemPrompts, + }), + [allSystemPrompts, conversationSettings, getColumns, onDeleteActionClicked, onEditActionClicked] ); const sorting = useMemo( @@ -244,6 +184,14 @@ const ConversationSettingsManagementComponent: React.FC = ({ [] ); + const confirmationTitle = useMemo( + () => + deletedConversation?.title + ? i18n.DELETE_CONVERSATION_CONFIRMATION_TITLE(deletedConversation?.title) + : i18n.DELETE_CONVERSATION_CONFIRMATION_DEFAULT_TITLE, + [deletedConversation?.title] + ); + if (!conversationsLoaded) { return null; } @@ -267,9 +215,9 @@ const ConversationSettingsManagementComponent: React.FC = ({ {editFlyoutVisible && ( = ({ /> )} - {deleteConfirmModalVisibility && deleteConversation?.title && ( + {deleteConfirmModalVisibility && deletedConversation?.title && ( = ({ allSystemPrompts, conversation }) => { + const systemPrompt: Prompt | undefined = allSystemPrompts.find( + ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId + ); + + const defaultSystemPrompt = getDefaultSystemPrompt({ + allSystemPrompts, + conversation, + }); + + const systemPromptTitle = + systemPrompt?.label || + systemPrompt?.name || + defaultSystemPrompt?.label || + defaultSystemPrompt?.name; + + return systemPromptTitle ? {systemPromptTitle} : null; +}; + +SystemPromptColumn.displayName = 'SystemPromptColumn'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts index 783e641640b9c..a8da32d2d9153 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/translations.ts @@ -42,9 +42,15 @@ export const CONVERSATIONS_TABLE_COLUMN_ACTIONS = i18n.translate( } ); -export const DELETE_CONVERSATION_CONFIRMATION_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.conversationSettings.deleteConfirmation.title', +export const DELETE_CONVERSATION_CONFIRMATION_DEFAULT_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.deleteConfirmation.defaultTitle', { - defaultMessage: 'conversation', + defaultMessage: 'Delete conversation?', } ); + +export const DELETE_CONVERSATION_CONFIRMATION_TITLE = (conversationTitle: string) => + i18n.translate('xpack.elasticAssistant.assistant.conversationSettings.deleteConfirmation.Title', { + values: { conversationTitle }, + defaultMessage: 'Delete "{conversationTitle}"?', + }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/use_conversation_selector_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/use_conversation_selector_settings.tsx new file mode 100644 index 0000000000000..a1d4ff39d672d --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/use_conversation_selector_settings.tsx @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; +import { EuiBadge, EuiBasicTableColumn, EuiLink } from '@elastic/eui'; + +import { FormattedDate } from '@kbn/i18n-react'; +import { Conversation } from '../../../assistant_context/types'; +import { AIConnector } from '../../../connectorland/connector_selector'; +import { getConnectorTypeTitle } from '../../../connectorland/helpers'; +import { Prompt } from '../../../..'; +import { getApiConfig } from '../../use_conversation/helpers'; +import * as i18n from './translations'; +import { RowActions } from '../../common/components/assistant_settings_management/row_actions'; +import { SystemPromptColumn } from './system_prompt_column'; + +const emptyConversations = {}; + +export interface GetConversationsListParams { + allSystemPrompts: Prompt[]; + actionTypeRegistry: ActionTypeRegistryContract; + connectors: AIConnector[] | undefined; + conversations: Record; + defaultConnector?: AIConnector; +} + +export type ConversationTableItem = Conversation & { + actionType?: string | null; +}; + +export const useConversationsTable = () => { + const getColumns = useCallback( + ({ + onDeleteActionClicked, + onEditActionClicked, + allSystemPrompts, + }): Array> => { + return [ + { + name: i18n.CONVERSATIONS_TABLE_COLUMN_TYPE, + render: (conversation: ConversationTableItem) => ( + onEditActionClicked(conversation)}> + {conversation.title} + + ), + }, + { + name: i18n.CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT, + align: 'center', + render: (conversation: ConversationTableItem) => ( + + ), + }, + { + field: 'actionType', + name: i18n.CONVERSATIONS_TABLE_COLUMN_CONNECTOR, + align: 'center', + render: (actionType: ConversationTableItem['actionType']) => + actionType ? {actionType} : null, + }, + { + field: 'updatedAt', + name: i18n.CONVERSATIONS_TABLE_COLUMN_UPDATED_AT, + align: 'center', + render: (updatedAt: ConversationTableItem['updatedAt']) => + updatedAt ? ( + + + + ) : null, + }, + { + 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} + /> + ); + }, + }, + ]; + }, + [] + ); + const getConversationsList = useCallback( + ({ + allSystemPrompts, + actionTypeRegistry, + connectors, + conversations = emptyConversations, + defaultConnector, + }: GetConversationsListParams): ConversationTableItem[] => { + return Object.values(conversations).map((conversation) => { + const conversationApiConfig = getApiConfig({ + allSystemPrompts, + connectors, + conversation, + defaultConnector, + }); + const connector: AIConnector | undefined = connectors?.find( + (c) => c.id === conversationApiConfig.apiConfig?.connectorId + ); + + const actionType = getConnectorTypeTitle(connector, actionTypeRegistry); + + return { + ...conversation, + actionType, + ...conversationApiConfig, + }; + }); + }, + [] + ); + + return { getColumns, getConversationsList }; +}; 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 5f19726b1497a..15f3ba69aa5af 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 @@ -26,9 +26,12 @@ import { ConversationMultiSelector } from './conversation_multi_selector/convers import { SystemPromptSelector } from './system_prompt_selector/system_prompt_selector'; import { TEST_IDS } from '../../../constants'; import { ConversationsBulkActions } from '../../../api'; -import { getSelectedConversations } from '../../../quick_prompts/quick_prompt_settings_management.tsx/helpers'; +import { getSelectedConversations } from '../system_prompt_settings_management/utils'; +import { useSystemPromptEditor } from './use_system_prompt_editor'; +import { getApiConfig, getDefaultNewSystemPrompt } from '../../../use_conversation/helpers'; interface Props { + connectors: AIConnector[] | undefined; conversationSettings: Record; conversationsSettingsBulkActions: ConversationsBulkActions; onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; @@ -46,6 +49,7 @@ interface Props { * Settings for adding/removing system prompts. Configure name, prompt and default conversations. */ export const SystemPromptEditorComponent: React.FC = ({ + connectors, conversationSettings, onSelectedSystemPromptChange, selectedSystemPrompt, @@ -87,22 +91,42 @@ export const SystemPromptEditorComponent: React.FC = ({ [selectedSystemPrompt, setUpdatedSystemPromptSettings] ); + const conversationsWithApiConfig = Object.entries(conversationSettings).reduce< + Record + >((acc, [key, conversation]) => { + acc[key] = { + ...conversation, + ...getApiConfig({ + allSystemPrompts: systemPromptSettings, + connectors, + conversation, + defaultConnector, + }), + }; + return acc; + }, {}); // Conversations this system prompt should be a default for const conversationOptions = useMemo( - () => Object.values(conversationSettings), - [conversationSettings] + () => Object.values(conversationsWithApiConfig), + [conversationsWithApiConfig] ); + const selectedConversations = useMemo(() => { return selectedSystemPrompt != null - ? getSelectedConversations(conversationSettings, selectedSystemPrompt.id) + ? getSelectedConversations( + systemPromptSettings, + conversationsWithApiConfig, + selectedSystemPrompt.id + ) : []; - }, [conversationSettings, selectedSystemPrompt]); + }, [conversationsWithApiConfig, selectedSystemPrompt, systemPromptSettings]); const handleConversationSelectionChange = useCallback( (currentPromptConversations: Conversation[]) => { const currentPromptConversationTitles = currentPromptConversations.map( (convo) => convo.title ); + const getDefaultSystemPromptId = (convo: Conversation) => currentPromptConversationTitles.includes(convo.title) ? selectedSystemPrompt?.id @@ -110,7 +134,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 - undefined + getDefaultNewSystemPrompt(systemPromptSettings).id : // leave it as it is .. if that conversation was neither added nor removed. convo.apiConfig?.defaultSystemPromptId; @@ -145,16 +169,22 @@ export const SystemPromptEditorComponent: React.FC = ({ ); let updatedConversationsSettingsBulkActions = { ...conversationsSettingsBulkActions }; - Object.values(conversationSettings).forEach((convo) => { - const getApiConfig = (): ApiConfig | {} => { + Object.values(conversationsWithApiConfig).forEach((convo) => { + const getApiConfigWithSelectedPrompt = (): ApiConfig | {} => { if (convo.apiConfig) { return { apiConfig: { - ...convo.apiConfig, + ...getApiConfig({ + allSystemPrompts: systemPromptSettings, + connectors, + conversation: convo, + defaultConnector, + }).apiConfig, defaultSystemPromptId: getDefaultSystemPromptId(convo), }, }; } + return {}; }; const createOperation = @@ -164,14 +194,7 @@ export const SystemPromptEditorComponent: React.FC = ({ ...(updatedConversationsSettingsBulkActions.create ?? {}), [convo.id]: { ...convo, - ...(convo.apiConfig - ? { - apiConfig: { - ...convo.apiConfig, - defaultSystemPromptId: getDefaultSystemPromptId(convo), - }, - } - : {}), + ...(convo.apiConfig ? getApiConfigWithSelectedPrompt() : {}), }, }, } @@ -186,7 +209,7 @@ export const SystemPromptEditorComponent: React.FC = ({ ...(updatedConversationsSettingsBulkActions.update ? updatedConversationsSettingsBulkActions.update[convo.id] ?? {} : {}), - ...getApiConfig(), + ...getApiConfigWithSelectedPrompt(), }, }, } @@ -198,17 +221,19 @@ export const SystemPromptEditorComponent: React.FC = ({ ...updateOperation, }; }); + setConversationsSettingsBulkActions(updatedConversationsSettingsBulkActions); } }, [ - conversationSettings, + connectors, conversationsSettingsBulkActions, - defaultConnector?.actionTypeId, - defaultConnector?.id, + conversationsWithApiConfig, + defaultConnector, selectedSystemPrompt, setConversationSettings, setConversationsSettingsBulkActions, + systemPromptSettings, ] ); @@ -236,42 +261,10 @@ export const SystemPromptEditorComponent: React.FC = ({ [selectedSystemPrompt, setUpdatedSystemPromptSettings] ); - // When top level system prompt selection changes - const onSystemPromptSelectionChange = useCallback( - (systemPrompt?: Prompt | string) => { - const isNew = typeof systemPrompt === 'string'; - const newSelectedSystemPrompt: Prompt | undefined = isNew - ? { - id: systemPrompt ?? '', - content: '', - name: systemPrompt ?? '', - promptType: 'system', - } - : systemPrompt; - - if (newSelectedSystemPrompt != null) { - setUpdatedSystemPromptSettings((prev) => { - const alreadyExists = prev.some((sp) => sp.id === newSelectedSystemPrompt.id); - - if (!alreadyExists) { - return [...prev, newSelectedSystemPrompt]; - } - - return prev; - }); - } - - onSelectedSystemPromptChange(newSelectedSystemPrompt); - }, - [onSelectedSystemPromptChange, setUpdatedSystemPromptSettings] - ); - - const onSystemPromptDeleted = useCallback( - (id: string) => { - setUpdatedSystemPromptSettings((prev) => prev.filter((sp) => sp.id !== id)); - }, - [setUpdatedSystemPromptSettings] - ); + const { onSystemPromptSelectionChange, onSystemPromptDeleted } = useSystemPromptEditor({ + setUpdatedSystemPromptSettings, + onSelectedSystemPromptChange, + }); return ( <> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx index c228cc7df49c6..b7f66acba85c7 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx @@ -17,6 +17,7 @@ import { SystemPromptSettingsProps } from './types'; */ export const SystemPromptSettings: React.FC = React.memo( ({ + connectors, conversationSettings, onSelectedSystemPromptChange, selectedSystemPrompt, @@ -37,6 +38,7 @@ export const SystemPromptSettings: React.FC = React.m ; conversationsSettingsBulkActions: ConversationsBulkActions; onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; 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 new file mode 100644 index 0000000000000..87e284d6dcf25 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.tsx @@ -0,0 +1,58 @@ +/* + * 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 { useCallback } from 'react'; +import { Prompt } from '../../../types'; + +interface Props { + setUpdatedSystemPromptSettings: React.Dispatch>; + onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; +} + +export const useSystemPromptEditor = ({ + setUpdatedSystemPromptSettings, + onSelectedSystemPromptChange, +}: Props) => { + // When top level system prompt selection changes + const onSystemPromptSelectionChange = useCallback( + (systemPrompt?: Prompt | string) => { + const isNew = typeof systemPrompt === 'string'; + const newSelectedSystemPrompt: Prompt | undefined = isNew + ? { + id: systemPrompt ?? '', + content: '', + name: systemPrompt ?? '', + promptType: 'system', + } + : systemPrompt; + + if (newSelectedSystemPrompt != null) { + setUpdatedSystemPromptSettings((prev) => { + const alreadyExists = prev.some((sp) => sp.id === newSelectedSystemPrompt.id); + + if (!alreadyExists) { + return [...prev, newSelectedSystemPrompt]; + } + + return prev; + }); + } + + onSelectedSystemPromptChange(newSelectedSystemPrompt); + }, + [onSelectedSystemPromptChange, setUpdatedSystemPromptSettings] + ); + + const onSystemPromptDeleted = useCallback( + (id: string) => { + setUpdatedSystemPromptSettings((prev) => prev.filter((sp) => sp.id !== id)); + }, + [setUpdatedSystemPromptSettings] + ); + + return { onSystemPromptSelectionChange, onSystemPromptDeleted }; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/default_conversations_column.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/default_conversations_column.tsx new file mode 100644 index 0000000000000..7567775909a09 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/default_conversations_column.tsx @@ -0,0 +1,50 @@ +/* + * 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 { EuiBadge, EuiLink } from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; + +interface Props { + defaultConversations: string[]; +} + +export const DefaultConversationsColumn: React.FC = React.memo( + ({ defaultConversations }) => { + const maxConversationsToShow = 5; + const isOverflow = defaultConversations.length > maxConversationsToShow; + + const [isExpanded, setIsExpanded] = useState(false); + + const currentDisplaying = isExpanded + ? defaultConversations.length + : Math.min(maxConversationsToShow, defaultConversations.length); + const itemsToDisplay = defaultConversations.slice(0, currentDisplaying - 1); + + const toggleContent = useCallback((prev) => { + setIsExpanded(!prev); + }, []); + + if (!defaultConversations || defaultConversations?.length === 0) { + return null; + } + + return ( + <> + {itemsToDisplay.map((c, idx) => ( + + {c} + + ))} + {isOverflow && ( + {isExpanded ? 'Show less' : 'Show All'} + )} + + ); + } +); + +DefaultConversationsColumn.displayName = 'DefaultConversationsColumn'; 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 fcaf88be2b3e9..2377ea0a2b861 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 @@ -12,24 +12,24 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, - EuiLink, - EuiBadge, EuiSpacer, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; + import { Conversation, ConversationsBulkActions } from '../../../../..'; import { AIConnector } from '../../../../connectorland/connector_selector'; -import { Flyout } from '../../../common/components/assisttant_settings_management/flyout'; -import { useFlyoutModalVisibility } from '../../../common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility'; -import { RowActions } from '../../../common/components/assisttant_settings_management/row_actions'; -import { getSystemPromptsList } from '../../../quick_prompts/quick_prompt_settings_management.tsx/helpers'; +import { Flyout } from '../../../common/components/assistant_settings_management/flyout'; +import { useFlyoutModalVisibility } from '../../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; import { CANCEL, DELETE } from '../../../settings/translations'; import { Prompt } from '../../../types'; import { SystemPromptEditor } from '../system_prompt_modal/system_prompt_editor'; import { SETTINGS_TITLE } from '../system_prompt_modal/translations'; +import { useSystemPromptEditor } from '../system_prompt_modal/use_system_prompt_editor'; import * as i18n from './translations'; +import { useSystemPromptTable } from './use_system_prompt_table'; interface Props { + connectors: AIConnector[] | undefined; conversations: Record; conversationSettings: Record; conversationsSettingsBulkActions: ConversationsBulkActions; @@ -47,6 +47,7 @@ interface Props { } const SystemPromptSettingsManagementComponent = ({ + connectors, conversations, conversationSettings, onSelectedSystemPromptChange, @@ -78,87 +79,49 @@ const SystemPromptSettingsManagementComponent = ({ openFlyout(); }, [onSelectedSystemPromptChange, openFlyout]); + const { onSystemPromptSelectionChange, onSystemPromptDeleted } = useSystemPromptEditor({ + setUpdatedSystemPromptSettings, + onSelectedSystemPromptChange, + }); + const onEditActionClicked = useCallback( (prompt: Prompt) => { - onSelectedSystemPromptChange(prompt); + onSystemPromptSelectionChange(prompt); openFlyout(); }, - [onSelectedSystemPromptChange, openFlyout] + [onSystemPromptSelectionChange, openFlyout] ); const onDeleteActionClicked = useCallback( (prompt: Prompt) => { setDeletedPrompt(prompt); + onSystemPromptDeleted(prompt.id); openConfirmModal(); }, - [openConfirmModal] + [onSystemPromptDeleted, openConfirmModal] ); const onDeleteCancelled = useCallback(() => { setDeletedPrompt(null); closeConfirmModal(); - }, [closeConfirmModal]); + resetSettings(); + }, [closeConfirmModal, resetSettings]); const onDeleteConfirmed = useCallback(() => { - setUpdatedSystemPromptSettings((prev) => prev.filter((sp) => sp.id !== deletedPrompt?.id)); handleSave(); closeConfirmModal(); - }, [closeConfirmModal, deletedPrompt?.id, handleSave, setUpdatedSystemPromptSettings]); + resetSettings(); + }, [closeConfirmModal, handleSave, resetSettings]); const onSaveCancelled = useCallback(() => { - onSelectedSystemPromptChange(); closeFlyout(); resetSettings(); - }, [onSelectedSystemPromptChange, closeFlyout, resetSettings]); + }, [closeFlyout, resetSettings]); const onSaveConfirmed = useCallback(() => { handleSave(); - onSelectedSystemPromptChange(); closeFlyout(); - }, [closeFlyout, handleSave, onSelectedSystemPromptChange]); - - const columns = useMemo( - () => [ - { - name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_NAME, - render: (prompt: Prompt) => - prompt?.name ? ( - onEditActionClicked(prompt)}>{prompt?.name} - ) : null, - }, - { - field: 'defaultConversations', - name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, - render: (defaultConversations: string[]) => - defaultConversations.map((c, idx) => ( - - {c} - - )), - }, - /* TODO: enable when createdAt is added - { - field: 'createdAt', - name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_CREATED_ON, - }, - */ - { - name: 'Actions', - align: 'center', - width: '120px', - render: (prompt: Prompt) => { - return ( - - rowItem={prompt} - onEdit={onEditActionClicked} - onDelete={onDeleteActionClicked} - /> - ); - }, - }, - ], - [onEditActionClicked, onDeleteActionClicked] - ); + }, [closeFlyout, handleSave]); const confirmationTitle = useMemo( () => @@ -168,9 +131,21 @@ const SystemPromptSettingsManagementComponent = ({ [deletedPrompt?.name] ); + const { getColumns, getSystemPromptsList } = useSystemPromptTable(); + + const columns = useMemo( + () => getColumns({ onEditActionClicked, onDeleteActionClicked }), + [getColumns, onEditActionClicked, onDeleteActionClicked] + ); const systemPromptListItems = useMemo( - () => getSystemPromptsList(systemPromptSettings, conversations), - [systemPromptSettings, conversations] + () => + getSystemPromptsList({ + connectors, + conversationSettings, + defaultConnector, + systemPromptSettings, + }), + [getSystemPromptsList, connectors, conversationSettings, defaultConnector, systemPromptSettings] ); return ( <> @@ -194,6 +169,7 @@ const SystemPromptSettingsManagementComponent = ({ onSaveConfirmed={onSaveConfirmed} > ; + +type SystemPromptTableItem = Prompt & { defaultConversations: string[] }; + +export const useSystemPromptTable = () => { + const getColumns = ({ + onEditActionClicked, + onDeleteActionClicked, + }: { + onEditActionClicked: (prompt: SystemPromptTableItem) => void; + onDeleteActionClicked: (prompt: SystemPromptTableItem) => void; + }) => [ + { + name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_NAME, + render: (prompt: SystemPromptTableItem) => + prompt?.name ? ( + onEditActionClicked(prompt)}>{prompt?.name} + ) : null, + }, + { + field: 'defaultConversations', + name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, + render: (defaultConversations: string[]) => + defaultConversations.map((c, idx) => ( + + {c} + + )), + }, + /* TODO: enable when createdAt is added + { + field: 'createdAt', + name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_CREATED_ON, + }, + */ + { + name: 'Actions', + width: '120px', + render: (prompt: SystemPromptTableItem) => { + const isDeletable = !prompt.isDefault; + return ( + + rowItem={prompt} + onEdit={onEditActionClicked} + onDelete={isDeletable ? onDeleteActionClicked : undefined} + isDeletable={isDeletable} + /> + ); + }, + }, + ]; + + const getSystemPromptsList = ({ + connectors, + conversationSettings, + defaultConnector, + systemPromptSettings, + }: { + connectors: AIConnector[] | undefined; + conversationSettings: Record; + defaultConnector: AIConnector | undefined; + systemPromptSettings: Prompt[]; + }): SystemPromptTableItem[] => { + const conversationsWithApiConfig = Object.entries( + conversationSettings + ).reduce((acc, [key, conversation]) => { + const defaultSystemPrompt = getDefaultSystemPrompt({ + allSystemPrompts: systemPromptSettings, + conversation, + }); + acc[key] = { + ...conversation, + ...getApiConfig({ + allSystemPrompts: systemPromptSettings, + connectors, + conversation, + defaultConnector, + }), + systemPrompt: defaultSystemPrompt, + }; + return acc; + }, {}); + + return systemPromptSettings.map((systemPrompt) => { + return { + ...systemPrompt, + defaultConversations: getSelectedConversations( + systemPromptSettings, + conversationsWithApiConfig, + systemPrompt?.id + ).map(({ title }) => title), + }; + }); + }; + + return { getColumns, getSystemPromptsList }; +}; 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 new file mode 100644 index 0000000000000..fc9bbaed9bba0 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.tsx @@ -0,0 +1,24 @@ +/* + * 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 { Conversation } from '../../../../assistant_context/types'; +import { Prompt } from '../../../types'; +import { getDefaultSystemPrompt } from '../../../use_conversation/helpers'; + +export const getSelectedConversations = ( + allSystemPrompts: Prompt[], + conversationSettings: Record, + systemPromptId: string +) => { + return Object.values(conversationSettings).filter((conversation) => { + const defaultSystemPrompt = getDefaultSystemPrompt({ + allSystemPrompts, + conversation, + }); + return defaultSystemPrompt?.id === systemPromptId; + }); +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/helpers.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/helpers.ts deleted file mode 100644 index 294527b5d8191..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/helpers.ts +++ /dev/null @@ -1,31 +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 { Conversation } from '../../../..'; -import { Prompt } from '../../types'; - -export const getSelectedConversations = ( - conversationSettings: Record, - systemPromptId: string -) => - Object.values(conversationSettings).filter( - (conversation) => conversation.apiConfig?.defaultSystemPromptId === systemPromptId - ); - -export const getSystemPromptsList = ( - systemPromptSettings: Prompt[], - conversationSettings: Record -): Array => { - return systemPromptSettings.map((systemPrompt) => { - return { - ...systemPrompt, - defaultConversations: getSelectedConversations(conversationSettings, systemPrompt.id).map( - ({ title }) => title - ), - }; - }); -}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx index 32e6fae193cd0..df6d37049631b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx @@ -17,11 +17,11 @@ import { EuiSpacer, } from '@elastic/eui'; import { QuickPrompt } from '../types'; -import { RowActions } from '../../common/components/assisttant_settings_management/row_actions'; +import { RowActions } from '../../common/components/assistant_settings_management/row_actions'; import { QuickPromptSettingsEditor } from '../quick_prompt_settings/quick_prompt_editor'; import * as i18n from './translations'; -import { useFlyoutModalVisibility } from '../../common/components/assisttant_settings_management/flyout/use_flyout_modal_visibility'; -import { Flyout } from '../../common/components/assisttant_settings_management/flyout'; +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'; interface Props { @@ -114,11 +114,13 @@ const QuickPromptSettingsManagementComponent = ({ width: '120px', align: 'center', render: (prompt: QuickPrompt) => { + const isDeletable = !prompt.isDefault; return ( rowItem={prompt} onDelete={onDeleteActionClicked} - onEdit={onEditActionClicked} + onEdit={isDeletable ? onEditActionClicked : undefined} + isDeletable={isDeletable} /> ); }, 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 ef505a3b4fa1d..2fb49b3cea07c 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 @@ -152,26 +152,13 @@ export const AssistantSettingsManagement: React.FC = React.memo( }, [selectedSystemPrompt, systemPromptSettings]); const handleSave = useCallback(() => { - // If the selected conversation is deleted, we need to select a new conversation to prevent a crash creating a conversation that already exists - const isSelectedConversationDeleted = - conversationSettings[defaultSelectedConversation.title] == null; - const newSelectedConversationId: string | undefined = Object.keys(conversationSettings)[0]; - if (isSelectedConversationDeleted && newSelectedConversationId != null) { - setSelectedConversationId(conversationSettings[newSelectedConversationId].title); - } saveSettings(); toasts?.addSuccess({ iconType: 'check', title: i18n.SETTINGS_UPDATED_TOAST_TITLE, }); setHasPendingChanges(false); - }, [ - conversationSettings, - defaultSelectedConversation.title, - saveSettings, - setSelectedConversationId, - toasts, - ]); + }, [saveSettings, toasts]); const tabsConfig = useMemo( () => [ @@ -183,14 +170,14 @@ export const AssistantSettingsManagement: React.FC = React.memo( id: CONVERSATIONS_TAB, label: i18n.CONVERSATIONS_MENU_ITEM, }, - { - id: QUICK_PROMPTS_TAB, - label: i18n.QUICK_PROMPTS_MENU_ITEM, - }, { id: SYSTEM_PROMPTS_TAB, label: i18n.SYSTEM_PROMPTS_MENU_ITEM, }, + { + id: QUICK_PROMPTS_TAB, + label: i18n.QUICK_PROMPTS_MENU_ITEM, + }, { id: ANONYMIZATION_TAB, label: i18n.ANONYMIZATION_MENU_ITEM, @@ -275,6 +262,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( )} {selectedSettingsTab === SYSTEM_PROMPTS_TAB && ( >(conversations); const [conversationsSettingsBulkActions, setConversationsSettingsBulkActions] = useState({}); + console.log('updater---', conversationsSettingsBulkActions); // Quick Prompts const [updatedQuickPromptSettings, setUpdatedQuickPromptSettings] = useState(allQuickPrompts); 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 de766085e1aee..e3cfa09f8073b 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 @@ -8,6 +8,7 @@ import React from 'react'; import { Prompt } from '../types'; import { Conversation } from '../../assistant_context/types'; +import { AIConnector } from '../../connectorland/connector_selector'; export interface CodeBlockDetails { type: QueryType; @@ -68,6 +69,9 @@ export const analyzeMarkdown = (markdown: string): CodeBlockDetails[] => { return result; }; +export const getDefaultNewSystemPrompt = (allSystemPrompts: Prompt[]) => + allSystemPrompts.find((prompt) => prompt.isNewConversationDefault) ?? allSystemPrompts?.[0]; + /** * Returns the default system prompt for a given conversation * @@ -84,7 +88,37 @@ export const getDefaultSystemPrompt = ({ const conversationSystemPrompt = allSystemPrompts.find( (prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId ); - const defaultNewSystemPrompt = allSystemPrompts.find((prompt) => prompt.isNewConversationDefault); + const defaultNewSystemPrompt = getDefaultNewSystemPrompt(allSystemPrompts); + + return conversationSystemPrompt ?? defaultNewSystemPrompt; +}; - return conversationSystemPrompt ?? defaultNewSystemPrompt ?? allSystemPrompts?.[0]; +export const getApiConfig = ({ + allSystemPrompts, + conversation, + connectors, + defaultConnector, +}: { + allSystemPrompts: Prompt[]; + conversation: Conversation; + connectors?: AIConnector[]; + defaultConnector?: AIConnector; +}) => { + const connector: AIConnector | undefined = connectors?.find( + (c) => c.id === conversation.apiConfig?.connectorId || defaultConnector?.id + ); + const defaultSystemPrompt = getDefaultSystemPrompt({ + allSystemPrompts, + conversation, + }); + return connector + ? { + apiConfig: { + connectorId: connector.id, + actionTypeId: connector.actionTypeId, + provider: connector.apiProvider, + defaultSystemPromptId: defaultSystemPrompt?.id, + }, + } + : {}; }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts index 86c4bebeb320b..1ce84fdb6e9b6 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts @@ -163,17 +163,3 @@ export const REFRESH_CONNECTORS_BUTTON = i18n.translate( defaultMessage: 'Refresh', } ); - -export const EDIT_CONNECTOR_BUTTON = i18n.translate( - 'xpack.elasticAssistant.assistant.connectors.editConnectorButton', - { - defaultMessage: 'Edit', - } -); - -export const DELETE_CONNECTOR_BUTTON = i18n.translate( - 'xpack.elasticAssistant.assistant.connectors.deleteConnectorButton', - { - defaultMessage: 'Delete', - } -); From b591eb53fa5480f11143c89c69ef8ad843eb8ea6 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 20 Jun 2024 17:29:43 +0100 Subject: [PATCH 08/37] connector title --- .../index.tsx | 18 +++--- .../index.tsx | 16 ++--- .../use_system_prompt_table.tsx | 16 +++-- .../quick_prompt_editor.tsx | 39 ++----------- .../use_quick_prompt_editor.tsx | 58 +++++++++++++++++++ .../index.tsx | 54 +++++++++++------ .../translations.ts | 7 +++ .../assistant_settings_management.tsx | 45 ++++++++++++-- .../use_settings_updater.tsx | 4 +- .../assistant/use_conversation/helpers.ts | 5 +- .../impl/connectorland/helpers.tsx | 5 +- 11 files changed, 178 insertions(+), 89 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx index 4f2545fe6f24b..e03419925ecda 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx @@ -38,7 +38,7 @@ interface Props { isDisabled?: boolean; isFlyoutMode: boolean; refetchConversations: () => void; - resetSettings: () => void; + onCancelClick: () => void; setAssistantStreamingEnabled: React.Dispatch>; setConversationSettings: React.Dispatch>>; setConversationsSettingsBulkActions: React.Dispatch< @@ -62,7 +62,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ isFlyoutMode, onSelectedConversationChange, refetchConversations, - resetSettings, + onCancelClick, selectedConversation, setAssistantStreamingEnabled, setConversationSettings, @@ -138,8 +138,8 @@ const ConversationSettingsManagementComponent: React.FC = ({ const onDeleteCancelled = useCallback(() => { setDeletedConversation(null); closeConfirmModal(); - resetSettings(); - }, [closeConfirmModal, resetSettings]); + onCancelClick(); + }, [closeConfirmModal, onCancelClick]); const { getConversationsList, getColumns } = useConversationsTable(); @@ -153,15 +153,13 @@ const ConversationSettingsManagementComponent: React.FC = ({ const onSaveCancelled = useCallback(() => { closeEditFlyout(); - resetSettings(); - }, [closeEditFlyout, resetSettings]); + onCancelClick(); + }, [closeEditFlyout, onCancelClick]); const onSaveConfirmed = useCallback(() => { - handleSave(); closeEditFlyout(); - refetchConversations(); - setConversationsSettingsBulkActions({}); - }, [closeEditFlyout, handleSave, refetchConversations, setConversationsSettingsBulkActions]); + handleSave(); + }, [closeEditFlyout, handleSave]); const columns = useMemo( () => 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 2377ea0a2b861..59134aade4373 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 @@ -43,7 +43,7 @@ interface Props { >; defaultConnector?: AIConnector; handleSave: () => void; - resetSettings: () => void; + onCancelClick: () => void; } const SystemPromptSettingsManagementComponent = ({ @@ -59,7 +59,7 @@ const SystemPromptSettingsManagementComponent = ({ setConversationsSettingsBulkActions, defaultConnector, handleSave, - resetSettings, + onCancelClick, }: Props) => { const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); const { @@ -104,19 +104,19 @@ const SystemPromptSettingsManagementComponent = ({ const onDeleteCancelled = useCallback(() => { setDeletedPrompt(null); closeConfirmModal(); - resetSettings(); - }, [closeConfirmModal, resetSettings]); + onCancelClick(); + }, [closeConfirmModal, onCancelClick]); const onDeleteConfirmed = useCallback(() => { handleSave(); closeConfirmModal(); - resetSettings(); - }, [closeConfirmModal, handleSave, resetSettings]); + onCancelClick(); + }, [closeConfirmModal, handleSave, onCancelClick]); const onSaveCancelled = useCallback(() => { closeFlyout(); - resetSettings(); - }, [closeFlyout, resetSettings]); + onCancelClick(); + }, [closeFlyout, onCancelClick]); const onSaveConfirmed = useCallback(() => { handleSave(); 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 aa323d27be38c..c1e93e471e828 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 @@ -5,6 +5,7 @@ * 2.0. */ import { EuiLink, EuiBadge } from '@elastic/eui'; +import { css } from '@emotion/react'; import React from 'react'; import { Conversation } from '../../../../assistant_context/types'; import { AIConnector } from '../../../../connectorland/connector_selector'; @@ -39,12 +40,15 @@ export const useSystemPromptTable = () => { { field: 'defaultConversations', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, - render: (defaultConversations: string[]) => - defaultConversations.map((c, idx) => ( - - {c} - - )), + render: (defaultConversations: string[]) => ( +
+ {defaultConversations.map((c, idx) => ( + + {c} + + ))} +
+ ), }, /* TODO: enable when createdAt is added { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx index 8a7e90a0f890b..ea903c1abce9d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx @@ -16,6 +16,7 @@ import { QuickPrompt } from '../types'; import { QuickPromptSelector } from '../quick_prompt_selector/quick_prompt_selector'; import { PromptContextSelector } from '../prompt_context_selector/prompt_context_selector'; import { useAssistantContext } from '../../../assistant_context'; +import { useQuickPromptEditor } from './use_quick_prompt_editor'; const DEFAULT_COLOR = '#D36086'; @@ -126,41 +127,11 @@ const QuickPromptSettingsEditorComponent = ({ ); // When top level quick prompt selection changes - const onQuickPromptSelectionChange = useCallback( - (quickPrompt?: QuickPrompt | string) => { - const isNew = typeof quickPrompt === 'string'; - const newSelectedQuickPrompt: QuickPrompt | undefined = isNew - ? { - title: quickPrompt ?? '', - prompt: '', - color: DEFAULT_COLOR, - categories: [], - } - : quickPrompt; - - if (newSelectedQuickPrompt != null) { - setUpdatedQuickPromptSettings((prev) => { - const alreadyExists = prev.some((qp) => qp.title === newSelectedQuickPrompt.title); - - if (!alreadyExists) { - return [...prev, newSelectedQuickPrompt]; - } - - return prev; - }); - } + const { onQuickPromptDeleted, onQuickPromptSelectionChange } = useQuickPromptEditor({ + onSelectedQuickPromptChange, + setUpdatedQuickPromptSettings, + }); - onSelectedQuickPromptChange(newSelectedQuickPrompt); - }, - [onSelectedQuickPromptChange, setUpdatedQuickPromptSettings] - ); - - const onQuickPromptDeleted = useCallback( - (title: string) => { - setUpdatedQuickPromptSettings((prev) => prev.filter((qp) => qp.title !== title)); - }, - [setUpdatedQuickPromptSettings] - ); return ( <> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.tsx new file mode 100644 index 0000000000000..716298afb21da --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.tsx @@ -0,0 +1,58 @@ +/* + * 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 { useCallback } from 'react'; +import { QuickPrompt } from '../types'; + +export const DEFAULT_COLOR = '#D36086'; + +export const useQuickPromptEditor = ({ + onSelectedQuickPromptChange, + setUpdatedQuickPromptSettings, +}: { + onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; + setUpdatedQuickPromptSettings: React.Dispatch>; +}) => { + const onQuickPromptDeleted = useCallback( + (title: string) => { + setUpdatedQuickPromptSettings((prev) => prev.filter((qp) => qp.title !== title)); + }, + [setUpdatedQuickPromptSettings] + ); + + // When top level quick prompt selection changes + const onQuickPromptSelectionChange = useCallback( + (quickPrompt?: QuickPrompt | string) => { + const isNew = typeof quickPrompt === 'string'; + const newSelectedQuickPrompt: QuickPrompt | undefined = isNew + ? { + title: quickPrompt ?? '', + prompt: '', + color: DEFAULT_COLOR, + categories: [], + } + : quickPrompt; + + if (newSelectedQuickPrompt != null) { + setUpdatedQuickPromptSettings((prev) => { + const alreadyExists = prev.some((qp) => qp.title === newSelectedQuickPrompt.title); + + if (!alreadyExists) { + return [...prev, newSelectedQuickPrompt]; + } + + return prev; + }); + } + + onSelectedQuickPromptChange(newSelectedQuickPrompt); + }, + [onSelectedQuickPromptChange, setUpdatedQuickPromptSettings] + ); + + return { onQuickPromptDeleted, onQuickPromptSelectionChange }; +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx index df6d37049631b..c9a2c90e46090 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx @@ -6,6 +6,7 @@ */ import React, { useCallback, useMemo, useState } from 'react'; import { + EuiBadge, EuiBasicTableColumn, EuiButton, EuiConfirmModal, @@ -23,6 +24,7 @@ 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 { useQuickPromptEditor } from '../quick_prompt_settings/use_quick_prompt_editor'; interface Props { onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; @@ -30,7 +32,7 @@ interface Props { selectedQuickPrompt: QuickPrompt | undefined; setUpdatedQuickPromptSettings: React.Dispatch>; handleSave: () => void; - resetSettings: () => void; + onCancelClick: () => void; } const QuickPromptSettingsManagementComponent = ({ onSelectedQuickPromptChange, @@ -38,7 +40,7 @@ const QuickPromptSettingsManagementComponent = ({ selectedQuickPrompt, setUpdatedQuickPromptSettings, handleSave, - resetSettings, + onCancelClick, }: Props) => { const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); const [deletedQuickPrompt, setDeletedQuickPrompt] = useState(); @@ -48,47 +50,51 @@ const QuickPromptSettingsManagementComponent = ({ closeFlyout: closeConfirmModal, } = useFlyoutModalVisibility(); + const { onQuickPromptDeleted, onQuickPromptSelectionChange } = useQuickPromptEditor({ + onSelectedQuickPromptChange, + setUpdatedQuickPromptSettings, + }); + const onEditActionClicked = useCallback( (prompt: QuickPrompt) => { - onSelectedQuickPromptChange(prompt); + onQuickPromptSelectionChange(prompt); openFlyout(); }, - [onSelectedQuickPromptChange, openFlyout] + [onQuickPromptSelectionChange, openFlyout] ); const onDeleteActionClicked = useCallback( (prompt: QuickPrompt) => { setDeletedQuickPrompt(prompt); + onQuickPromptDeleted(prompt.title); openConfirmModal(); }, - [openConfirmModal] + [onQuickPromptDeleted, openConfirmModal] ); const onDeleteCancelled = useCallback(() => { setDeletedQuickPrompt(null); closeConfirmModal(); - }, [closeConfirmModal]); + onCancelClick(); + }, [closeConfirmModal, onCancelClick]); const onDeleteConfirmed = useCallback(() => { - setUpdatedQuickPromptSettings((prev) => - prev.filter((p) => p.title !== deletedQuickPrompt?.title) - ); handleSave(); closeConfirmModal(); - }, [closeConfirmModal, deletedQuickPrompt?.title, handleSave, setUpdatedQuickPromptSettings]); + }, [closeConfirmModal, handleSave]); const onCreate = useCallback(() => { onSelectedQuickPromptChange(); openFlyout(); }, [onSelectedQuickPromptChange, openFlyout]); - const onCancel = useCallback(() => { + const onSaveCancelled = useCallback(() => { onSelectedQuickPromptChange(); closeFlyout(); - resetSettings(); - }, [closeFlyout, onSelectedQuickPromptChange, resetSettings]); + onCancelClick(); + }, [closeFlyout, onSelectedQuickPromptChange, onCancelClick]); - const onSave = useCallback(() => { + const onSaveConfirmed = useCallback(() => { handleSave(); onSelectedQuickPromptChange(); closeFlyout(); @@ -103,6 +109,16 @@ const QuickPromptSettingsManagementComponent = ({ onEditActionClicked(prompt)}>{prompt?.title} ) : null, }, + { + field: 'categories', + name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CONTEXTS, + render: (categories: QuickPrompt['categories']) => + categories?.map((c, idx) => ( + + {c} + + )), + }, /* TODO: enable when createdAt is added { field: 'createdAt', @@ -118,8 +134,8 @@ const QuickPromptSettingsManagementComponent = ({ return ( rowItem={prompt} - onDelete={onDeleteActionClicked} - onEdit={isDeletable ? onEditActionClicked : undefined} + onDelete={isDeletable ? onDeleteActionClicked : undefined} + onEdit={onEditActionClicked} isDeletable={isDeletable} /> ); @@ -152,9 +168,9 @@ const QuickPromptSettingsManagementComponent = ({ i18n.translate( 'xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationTitle', 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 2fb49b3cea07c..288613ba7d907 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 @@ -6,7 +6,13 @@ */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { EuiPageTemplate } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiPageTemplate, +} from '@elastic/eui'; import { css } from '@emotion/react'; import { Conversation, Prompt, QuickPrompt } from '../../..'; @@ -250,7 +256,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( handleSave={handleSave} isFlyoutMode={isFlyoutMode} refetchConversations={refetchConversations} - resetSettings={resetSettings} + onCancelClick={onCancelClick} selectedConversation={selectedConversation} setAssistantStreamingEnabled={handleChange(setUpdatedAssistantStreamingEnabled)} setConversationSettings={handleChange(setConversationSettings)} @@ -276,7 +282,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( )} conversationsSettingsBulkActions={conversationsSettingsBulkActions} setUpdatedSystemPromptSettings={handleChange(setUpdatedSystemPromptSettings)} - resetSettings={resetSettings} + onCancelClick={onCancelClick} /> )} {selectedSettingsTab === QUICK_PROMPTS_TAB && ( @@ -286,7 +292,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( selectedQuickPrompt={selectedQuickPrompt} setUpdatedQuickPromptSettings={handleChange(setUpdatedQuickPromptSettings)} handleSave={handleSave} - resetSettings={resetSettings} + onCancelClick={onCancelClick} /> )} {selectedSettingsTab === ANONYMIZATION_TAB && ( @@ -306,9 +312,38 @@ export const AssistantSettingsManagement: React.FC = React.memo( )} {selectedSettingsTab === EVALUATION_TAB && } + {hasPendingChanges && ( + + + + + {i18n.CANCEL} + + + + + {i18n.SAVE} + + + + + )} ); } ); -AssistantSettingsManagement.displayName = 'AssistantSettingsNew'; +AssistantSettingsManagement.displayName = 'AssistantSettingsManagement'; 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 caff404937308..f2a4eacf47a40 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 @@ -72,7 +72,6 @@ export const useSettingsUpdater = ( useState>(conversations); const [conversationsSettingsBulkActions, setConversationsSettingsBulkActions] = useState({}); - console.log('updater---', conversationsSettingsBulkActions); // Quick Prompts const [updatedQuickPromptSettings, setUpdatedQuickPromptSettings] = useState(allQuickPrompts); @@ -153,7 +152,6 @@ export const useSettingsUpdater = ( const bulkAnonymizationFieldsResult = hasBulkAnonymizationFields ? await bulkUpdateAnonymizationFields(http, anonymizationFieldsBulkActions, toasts) : undefined; - return (bulkResult?.success ?? true) && (bulkAnonymizationFieldsResult?.success ?? true); }, [ setAllQuickPrompts, @@ -165,9 +163,9 @@ export const useSettingsUpdater = ( toasts, knowledgeBase.isEnabledKnowledgeBase, knowledgeBase.isEnabledRAGAlerts, - updatedAssistantStreamingEnabled, updatedKnowledgeBaseSettings, assistantStreamingEnabled, + updatedAssistantStreamingEnabled, setAssistantStreamingEnabled, setKnowledgeBase, anonymizationFieldsBulkActions, 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 e3cfa09f8073b..08a88f4bc0af0 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 @@ -104,9 +104,8 @@ export const getApiConfig = ({ connectors?: AIConnector[]; defaultConnector?: AIConnector; }) => { - const connector: AIConnector | undefined = connectors?.find( - (c) => c.id === conversation.apiConfig?.connectorId || defaultConnector?.id - ); + const connector: AIConnector | undefined = + connectors?.find((c) => c.id === conversation.apiConfig?.connectorId) ?? defaultConnector; const defaultSystemPrompt = getDefaultSystemPrompt({ allSystemPrompts, conversation, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx index 74d6ef1bc169b..580d152e096e4 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx @@ -70,7 +70,10 @@ export const getConnectorTypeTitle = ( const connectorTypeTitle = getGenAiConfig(connector)?.apiProvider ?? getActionTypeTitle(actionTypeRegistry.get(connector.actionTypeId)); - + console.log('connector', connector); + console.log('genaiconfig', getGenAiConfig(connector)); + console.log('genaiconfig apiProvider', getGenAiConfig(connector)?.apiProvider); + console.log('title', getActionTypeTitle(actionTypeRegistry.get(connector.actionTypeId))); const actionType = connector.isPreconfigured ? PRECONFIGURED_CONNECTOR : connectorTypeTitle; return actionType; From 376d82ea003534d50276f42f676f55aaba9600e1 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 21 Jun 2024 10:14:37 +0100 Subject: [PATCH 09/37] clean up --- .../use_system_prompt_table.tsx | 20 +++--- .../index.tsx | 54 ++------------- .../use_quick_prompt_table.tsx | 68 +++++++++++++++++++ 3 files changed, 84 insertions(+), 58 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/use_quick_prompt_table.tsx 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 c1e93e471e828..c9b0454a19518 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 @@ -5,7 +5,6 @@ * 2.0. */ import { EuiLink, EuiBadge } from '@elastic/eui'; -import { css } from '@emotion/react'; import React from 'react'; import { Conversation } from '../../../../assistant_context/types'; import { AIConnector } from '../../../../connectorland/connector_selector'; @@ -40,15 +39,16 @@ export const useSystemPromptTable = () => { { field: 'defaultConversations', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, - render: (defaultConversations: string[]) => ( -
- {defaultConversations.map((c, idx) => ( - - {c} - - ))} -
- ), + render: (defaultConversations: string[]) => + defaultConversations.length ? ( +
+ {defaultConversations.map((c, idx) => ( + + {c} + + ))} +
+ ) : null, }, /* TODO: enable when createdAt is added { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx index c9a2c90e46090..7879a8da2d069 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx @@ -6,25 +6,22 @@ */ import React, { useCallback, useMemo, useState } from 'react'; import { - EuiBadge, - EuiBasicTableColumn, EuiButton, EuiConfirmModal, EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, - EuiLink, EuiPanel, EuiSpacer, } from '@elastic/eui'; import { QuickPrompt } from '../types'; -import { RowActions } from '../../common/components/assistant_settings_management/row_actions'; 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 { useQuickPromptEditor } from '../quick_prompt_settings/use_quick_prompt_editor'; +import { useQuickPromptTable } from './use_quick_prompt_table'; interface Props { onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; @@ -100,50 +97,11 @@ const QuickPromptSettingsManagementComponent = ({ closeFlyout(); }, [closeFlyout, handleSave, onSelectedQuickPromptChange]); - const columns: Array> = useMemo( - () => [ - { - name: i18n.QUICK_PROMPTS_TABLE_COLUMN_NAME, - render: (prompt: QuickPrompt) => - prompt?.title ? ( - onEditActionClicked(prompt)}>{prompt?.title} - ) : null, - }, - { - field: 'categories', - name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CONTEXTS, - render: (categories: QuickPrompt['categories']) => - categories?.map((c, idx) => ( - - {c} - - )), - }, - /* TODO: enable when createdAt is added - { - field: 'createdAt', - name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CREATED_AT, - }, - */ - { - name: i18n.QUICK_PROMPTS_TABLE_COLUMN_ACTIONS, - width: '120px', - align: 'center', - render: (prompt: QuickPrompt) => { - const isDeletable = !prompt.isDefault; - return ( - - rowItem={prompt} - onDelete={isDeletable ? onDeleteActionClicked : undefined} - onEdit={onEditActionClicked} - isDeletable={isDeletable} - /> - ); - }, - }, - ], - [onDeleteActionClicked, onEditActionClicked] - ); + const { getColumns } = useQuickPromptTable(); + const columns = getColumns({ + onEditActionClicked, + onDeleteActionClicked, + }); const confirmationTitle = useMemo( () => diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/use_quick_prompt_table.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/use_quick_prompt_table.tsx new file mode 100644 index 0000000000000..4fe7edab67945 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/use_quick_prompt_table.tsx @@ -0,0 +1,68 @@ +/* + * 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 { EuiBadge, EuiLink } from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { RowActions } from '../../common/components/assistant_settings_management/row_actions'; +import { QuickPrompt } from '../types'; +import * as i18n from './translations'; + +export const useQuickPromptTable = () => { + const getColumns = useCallback( + ({ + onEditActionClicked, + onDeleteActionClicked, + }: { + onEditActionClicked: (prompt: QuickPrompt) => void; + onDeleteActionClicked: (prompt: QuickPrompt) => void; + }) => [ + { + name: i18n.QUICK_PROMPTS_TABLE_COLUMN_NAME, + render: (prompt: QuickPrompt) => + prompt?.title ? ( + onEditActionClicked(prompt)}>{prompt?.title} + ) : null, + }, + { + field: 'categories', + name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CONTEXTS, + render: (categories: QuickPrompt['categories']) => + categories?.length + ? categories.map((c, idx) => ( + + {c} + + )) + : null, + }, + /* TODO: enable when createdAt is added + { + field: 'createdAt', + name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CREATED_AT, + }, + */ + { + name: i18n.QUICK_PROMPTS_TABLE_COLUMN_ACTIONS, + width: '120px', + render: (prompt: QuickPrompt) => { + const isDeletable = !prompt.isDefault; + return ( + + rowItem={prompt} + onDelete={isDeletable ? onDeleteActionClicked : undefined} + onEdit={onEditActionClicked} + isDeletable={isDeletable} + /> + ); + }, + }, + ], + [] + ); + + return { getColumns }; +}; From f97b70106757e6478b82d2726ebded2e1831d1b9 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 21 Jun 2024 13:17:24 +0100 Subject: [PATCH 10/37] default page size to 25 --- .../badges/index.tsx | 23 +++++++++++++++++++ .../flyout/use_flyout_modal_visibility.ts | 15 +++++++----- .../index.tsx | 17 +++----------- ...ttings.tsx => use_conversations_table.tsx} | 0 .../index.tsx | 11 ++++++++- .../use_system_prompt_table.tsx | 15 ++++-------- .../index.tsx | 12 +++++++++- .../use_quick_prompt_table.tsx | 12 +++------- .../assistant_settings_management.tsx | 5 ---- .../stack_management/management_settings.tsx | 20 ++++++---------- 10 files changed, 70 insertions(+), 60 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx rename x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/{use_conversation_selector_settings.tsx => use_conversations_table.tsx} (100%) diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx new file mode 100644 index 0000000000000..12cb3f49b92b0 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx @@ -0,0 +1,23 @@ +/* + * 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 { EuiBadge } from '@elastic/eui'; +import React from 'react'; + +export const BadgesColumn: React.FC<{ items: string[] | null | undefined }> = React.memo( + ({ items }) => + items && items.length > 0 ? ( +
+ {items.map((c, idx) => ( + + {c} + + ))} +
+ ) : null +); +BadgesColumn.displayName = 'BadgesColumn'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/use_flyout_modal_visibility.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/use_flyout_modal_visibility.ts index 01291b6f77252..ad3da6b12242e 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/use_flyout_modal_visibility.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/flyout/use_flyout_modal_visibility.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; export const useFlyoutModalVisibility = () => { const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); @@ -18,9 +18,12 @@ export const useFlyoutModalVisibility = () => { setIsFlyoutOpen(false); }; - return { - isFlyoutOpen, - openFlyout, - closeFlyout, - }; + return useMemo( + () => ({ + isFlyoutOpen, + openFlyout, + closeFlyout, + }), + [isFlyoutOpen] + ); }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx index e03419925ecda..100d629e1bb56 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx @@ -10,7 +10,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; import { Conversation } from '../../../assistant_context/types'; -import { ConversationTableItem, useConversationsTable } from './use_conversation_selector_settings'; +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'; @@ -37,7 +37,6 @@ interface Props { handleSave: () => void; isDisabled?: boolean; isFlyoutMode: boolean; - refetchConversations: () => void; onCancelClick: () => void; setAssistantStreamingEnabled: React.Dispatch>; setConversationSettings: React.Dispatch>>; @@ -61,7 +60,6 @@ const ConversationSettingsManagementComponent: React.FC = ({ isDisabled, isFlyoutMode, onSelectedConversationChange, - refetchConversations, onCancelClick, selectedConversation, setAssistantStreamingEnabled, @@ -122,18 +120,9 @@ const ConversationSettingsManagementComponent: React.FC = ({ if (Object.keys(conversationsSettingsBulkActions).length === 0) { return; } - - handleSave(); closeConfirmModal(); - setConversationsSettingsBulkActions({}); - refetchConversations(); - }, [ - closeConfirmModal, - conversationsSettingsBulkActions, - handleSave, - refetchConversations, - setConversationsSettingsBulkActions, - ]); + handleSave(); + }, [closeConfirmModal, conversationsSettingsBulkActions, handleSave]); const onDeleteCancelled = useCallback(() => { setDeletedConversation(null); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/use_conversation_selector_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/use_conversations_table.tsx similarity index 100% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/use_conversation_selector_settings.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/use_conversations_table.tsx 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 59134aade4373..d6db15c8ac166 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 @@ -147,6 +147,15 @@ const SystemPromptSettingsManagementComponent = ({ }), [getSystemPromptsList, connectors, conversationSettings, defaultConnector, systemPromptSettings] ); + + const pagination = useMemo( + () => ({ + pageIndex: 0, + pageSize: 25, + totalItemCount: systemPromptSettings.length, + }), + [systemPromptSettings.length] + ); return ( <> @@ -159,7 +168,7 @@ const SystemPromptSettingsManagementComponent = ({
- + { }) => [ { name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_NAME, + truncateText: { lines: 3 }, render: (prompt: SystemPromptTableItem) => prompt?.name ? ( onEditActionClicked(prompt)}>{prompt?.name} @@ -39,16 +41,7 @@ export const useSystemPromptTable = () => { { field: 'defaultConversations', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, - render: (defaultConversations: string[]) => - defaultConversations.length ? ( -
- {defaultConversations.map((c, idx) => ( - - {c} - - ))} -
- ) : null, + render: (defaultConversations: string[]) => , }, /* TODO: enable when createdAt is added { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx index 7879a8da2d069..297da47f1d9ef 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx @@ -31,6 +31,7 @@ interface Props { handleSave: () => void; onCancelClick: () => void; } + const QuickPromptSettingsManagementComponent = ({ onSelectedQuickPromptChange, quickPromptSettings, @@ -110,6 +111,15 @@ const QuickPromptSettingsManagementComponent = ({ : i18n.DELETE_QUICK_PROMPT_MODAL_DEFAULT_TITLE, [deletedQuickPrompt?.title] ); + + const pagination = useMemo( + () => ({ + pageIndex: 0, + pageSize: 25, + totalItemCount: quickPromptSettings.length, + }), + [quickPromptSettings.length] + ); return ( <> @@ -121,7 +131,7 @@ const QuickPromptSettingsManagementComponent = ({ - + { { field: 'categories', name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CONTEXTS, - render: (categories: QuickPrompt['categories']) => - categories?.length - ? categories.map((c, idx) => ( - - {c} - - )) - : null, + render: (categories: QuickPrompt['categories']) => , }, /* TODO: enable when createdAt is added { 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 288613ba7d907..ee36259102b5a 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 @@ -42,9 +42,7 @@ interface Props { conversations: Record; conversationsLoaded: boolean; selectedConversation: Conversation; - setSelectedConversationId: React.Dispatch>; isFlyoutMode: boolean; - refetchConversations: () => void; } /** @@ -54,11 +52,9 @@ interface Props { export const AssistantSettingsManagement: React.FC = React.memo( ({ selectedConversation: defaultSelectedConversation, - setSelectedConversationId, conversations, conversationsLoaded, isFlyoutMode, - refetchConversations, }) => { const { actionTypeRegistry, @@ -255,7 +251,6 @@ export const AssistantSettingsManagement: React.FC = React.memo( defaultConnector={defaultConnector} handleSave={handleSave} isFlyoutMode={isFlyoutMode} - refetchConversations={refetchConversations} onCancelClick={onCancelClick} selectedConversation={selectedConversation} setAssistantStreamingEnabled={handleChange(setUpdatedAssistantStreamingEnabled)} 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 c648f8f6a50c6..728038bb51cea 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 @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { AssistantSettingsManagement } from '@kbn/elastic-assistant/impl/assistant/settings/assistant_settings_management'; import type { Conversation } from '@kbn/elastic-assistant'; import { @@ -18,6 +18,8 @@ import { useConversation } from '@kbn/elastic-assistant/impl/assistant/use_conve import type { FetchConversationsResponse } from '@kbn/elastic-assistant/impl/assistant/api'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; +const defaultSelectedConversationId = WELCOME_CONVERSATION_TITLE; + export const ManagementSettings = React.memo(() => { const isFlyoutMode = useIsExperimentalFeatureEnabled('aiAssistantFlyoutMode'); @@ -32,36 +34,28 @@ export const ManagementSettings = React.memo(() => { mergeBaseWithPersistedConversations(baseConversations, conversationsData), [baseConversations] ); - const { - data: conversations, - refetch: refetchConversations, - isFetched: conversationsLoaded, - } = useFetchCurrentUserConversations({ + const { data: conversations, isFetched: conversationsLoaded } = useFetchCurrentUserConversations({ http, onFetch: onFetchedConversations, isAssistantEnabled, }); - const [selectedConversationId, setSelectedConversationId] = useState( - WELCOME_CONVERSATION_TITLE - ); + const { getDefaultConversation } = useConversation(); const currentConversation = useMemo( () => - conversations?.[selectedConversationId] ?? + conversations?.[defaultSelectedConversationId] ?? getDefaultConversation({ cTitle: WELCOME_CONVERSATION_TITLE, isFlyoutMode }), - [conversations, getDefaultConversation, selectedConversationId, isFlyoutMode] + [conversations, getDefaultConversation, isFlyoutMode] ); if (conversations) { return ( ); } From 577128eda06dd13a75eeacb8dcaa5018c5bc2e59 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 21 Jun 2024 13:47:48 +0100 Subject: [PATCH 11/37] revert evaluation tab --- .../assistant_settings_button.test.tsx | 1 + .../assistant_settings_management.tsx | 4 +- .../use_evaluation_details.tsx | 87 ---- .../evaluation_settings.tsx | 479 +++++++++++++++--- .../prediction_details/index.tsx | 60 --- .../prediction_details/translations.ts | 35 -- .../use_predictions_details.tsx | 108 ---- .../run_details/data_set_toggle_button.tsx | 44 -- .../evaluation_settings/run_details/index.tsx | 213 -------- .../run_details/translations.ts | 158 ------ .../run_details/use_dataset.tsx | 64 --- .../run_details/use_run_details.tsx | 55 -- .../run_details/use_trace_options.tsx | 54 -- .../evaluation_settings/translations.ts | 187 ++++++- .../evaluation_settings_management/index.tsx | 291 ----------- .../translations.ts | 28 - 16 files changed, 596 insertions(+), 1272 deletions(-) delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_details/use_evaluation_details.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/index.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/translations.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/use_predictions_details.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/data_set_toggle_button.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/index.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/translations.ts delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_dataset.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_run_details.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_trace_options.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/index.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/translations.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.test.tsx index 4fee52fb3bc99..a94f3ea578494 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_button.test.tsx @@ -25,6 +25,7 @@ const testProps = { isFlyoutMode: false, onConversationSelected, conversations: {}, + conversationsLoaded: true, refetchConversationsState: jest.fn(), anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] }, refetchAnonymizationFieldsResults: jest.fn(), diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx index ee36259102b5a..cffac7e79ec42 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 @@ -28,7 +28,7 @@ import { ConversationSettingsManagement } from '../conversations/converstaion_se import { QuickPromptSettingsManagement } from '../quick_prompts/quick_prompt_settings_management.tsx'; import { SystemPromptSettingsManagement } from '../prompt_editor/system_prompt/system_prompt_settings_management'; import { AnonymizationSettingsManagement } from '../../data_anonymization/settings/anonomization_settings_management'; -import { EvaluationSettingsManagement } from './evaluation_settings_management'; +import { EvaluationSettings } from './evaluation_settings/evaluation_settings'; export const CONNECTORS_TAB = 'CONNECTORS_TAB' as const; export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const; @@ -305,7 +305,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( setUpdatedKnowledgeBaseSettings={handleChange(setUpdatedKnowledgeBaseSettings)} /> )} - {selectedSettingsTab === EVALUATION_TAB && } + {selectedSettingsTab === EVALUATION_TAB && } {hasPendingChanges && ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_details/use_evaluation_details.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_details/use_evaluation_details.tsx deleted file mode 100644 index 6f877deae07ff..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/evaluation_details/use_evaluation_details.tsx +++ /dev/null @@ -1,87 +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 { EuiComboBoxOptionOption } from '@elastic/eui'; -import { useCallback, useMemo, useState } from 'react'; - -const DEFAULT_EVAL_TYPES_OPTIONS = [ - { label: 'correctness' }, - { label: 'esql-validator', disabled: true }, - { label: 'custom', disabled: true }, -]; - -export interface EvaluationSettings { - selectedEvaluationType: Array>; - onEvaluationTypeChange: (evaluationType: Array>) => void; - onEvaluationTypeOptionsCreate: (searchValue: string) => void; - evaluationTypeOptions: Array>; - selectedEvaluatorModelOptions: Array>; - onEvaluatorModelOptionsChange: (selectedOptions: Array>) => void; - evalPrompt: string; - onEvalPromptChange: (e: React.ChangeEvent) => void; -} - -export const useEvaluationDetails = (): EvaluationSettings => { - // Evaluation - // Evaluation Type - const [selectedEvaluationType, setSelectedEvaluationType] = useState< - Array> - >([]); - const onEvaluationTypeChange = useCallback( - (evaluationType: Array>) => { - setSelectedEvaluationType(evaluationType); - }, - [setSelectedEvaluationType] - ); - const onEvaluationTypeOptionsCreate = useCallback( - (searchValue: string) => { - const normalizedSearchValue = searchValue.trim(); - - if (!normalizedSearchValue) { - return; - } - - setSelectedEvaluationType([{ label: normalizedSearchValue }]); - }, - [setSelectedEvaluationType] - ); - const evaluationTypeOptions = useMemo(() => { - return DEFAULT_EVAL_TYPES_OPTIONS; - }, []); - - // Eval Model - const [selectedEvaluatorModelOptions, setSelectedEvaluatorModelOptions] = useState< - Array> - >([]); - const onEvaluatorModelOptionsChange = useCallback( - (selectedOptions: Array>) => { - setSelectedEvaluatorModelOptions(selectedOptions); - }, - [setSelectedEvaluatorModelOptions] - ); - - // Eval Prompt - const sampleEvalPrompt: string = `For the below input: \n\n{{input}} \n\na prediction: \n\n{{prediction}} \n\nwas made. How's it stack up against this reference: \n\n{{reference}} \n\nReturn output in a succinct sentence ranking on a simple grading rubric focused on correctness.`; - const [evalPrompt, setEvalPrompt] = useState(sampleEvalPrompt); - const onEvalPromptChange = useCallback( - (e) => { - setEvalPrompt(e.target.value); - }, - [setEvalPrompt] - ); - - return { - selectedEvaluationType, - onEvaluationTypeChange, - onEvaluationTypeOptionsCreate, - evaluationTypeOptions, - selectedEvaluatorModelOptions, - onEvaluatorModelOptionsChange, - evalPrompt, - onEvalPromptChange, - }; -}; 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 cb0aa0ebe789d..fe4d75be04004 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 @@ -5,9 +5,10 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiAccordion, + euiPaletteComplementary, EuiFormRow, EuiTitle, EuiText, @@ -15,8 +16,10 @@ import { EuiSpacer, EuiComboBox, EuiButton, + EuiComboBoxOptionOption, EuiTextArea, EuiTextColor, + EuiFieldText, EuiFlexItem, EuiFlexGroup, EuiLink, @@ -24,43 +27,31 @@ import { import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { PostEvaluateResponse } from '@kbn/elastic-assistant-common'; +import type { GetEvaluateResponse, PostEvaluateResponse } from '@kbn/elastic-assistant-common'; import * as i18n from './translations'; import { useAssistantContext } from '../../../assistant_context'; import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; - +import { getActionTypeTitle, getGenAiConfig } from '../../../connectorland/helpers'; +import { PRECONFIGURED_CONNECTOR } from '../../../connectorland/translations'; import { usePerformEvaluation } from '../../api/evaluate/use_perform_evaluation'; import { getApmLink, getDiscoverLink } from './utils'; -import { useRunDetails } from './run_details/use_run_details'; -import { useDataset } from './run_details/use_dataset'; -import { useTraceOptions } from './run_details/use_trace_options'; -import { RunDetailsEditor } from './run_details'; -import { PredictionDetails } from './prediction_details'; -import { usePredictionsDetails } from './prediction_details/use_predictions_details'; -import { useEvaluationDetails } from './evaluation_details/use_evaluation_details'; +import { useEvaluationData } from '../../api/evaluate/use_evaluation_data'; -const getSection = (title: string, description: string) => ( -
- - - -

{title}

-
-
-
+const DEFAULT_EVAL_TYPES_OPTIONS = [ + { label: 'correctness' }, + { label: 'esql-validator', disabled: true }, + { label: 'custom', disabled: true }, +]; +const DEFAULT_OUTPUT_INDEX = '.kibana-elastic-ai-assistant-evaluation-results'; - -

- {description} -

-
-
-); +interface Props { + onEvaluationSettingsChange?: () => void; +} /** * Evaluation Settings -- development-only feature for evaluating models */ -export const EvaluationSettings: React.FC = React.memo(() => { +export const EvaluationSettings: React.FC = React.memo(({ onEvaluationSettingsChange }) => { const { actionTypeRegistry, basePath, http, setTraceOptions, traceOptions } = useAssistantContext(); const { data: connectors } = useLoadConnectors({ http }); @@ -71,66 +62,246 @@ export const EvaluationSettings: React.FC = React.memo(() => { } = usePerformEvaluation({ http, }); + const { data: evalData } = useEvaluationData({ http }); + const defaultAgents = useMemo( + () => (evalData as GetEvaluateResponse)?.agentExecutors ?? [], + [evalData] + ); // Run Details - const runDetailsSettings = useRunDetails(); + // Project Name + const [projectName, setProjectName] = useState(); + const onProjectNameChange = useCallback( + (e) => { + setProjectName(e.target.value); + }, + [setProjectName] + ); + // Run Name + const [runName, setRunName] = useState(); + const onRunNameChange = useCallback( + (e) => { + setRunName(e.target.value); + }, + [setRunName] + ); + // Local Output Index + const [outputIndex, setOutputIndex] = useState(DEFAULT_OUTPUT_INDEX); + const onOutputIndexChange = useCallback( + (e) => { + setOutputIndex(e.target.value); + }, + [setOutputIndex] + ); /** Trace Options **/ - const tracedOptionsSettings = useTraceOptions({ setTraceOptions, traceOptions }); + const [showTraceOptions, setShowTraceOptions] = useState(false); + const onApmUrlChange = useCallback( + (e) => { + setTraceOptions({ ...traceOptions, apmUrl: e.target.value }); + }, + [setTraceOptions, traceOptions] + ); + const onLangSmithProjectChange = useCallback( + (e) => { + setTraceOptions({ ...traceOptions, langSmithProject: e.target.value }); + }, + [setTraceOptions, traceOptions] + ); + const onLangSmithApiKeyChange = useCallback( + (e) => { + setTraceOptions({ ...traceOptions, langSmithApiKey: e.target.value }); + }, + [setTraceOptions, traceOptions] + ); /** Dataset **/ - const datasetSettings = useDataset(); + const [useLangSmithDataset, setUseLangSmithDataset] = useState(true); + const datasetToggleButton = useMemo(() => { + return ( + + {i18n.EVALUATOR_DATASET_LABEL} + {' ('} + setUseLangSmithDataset(true)} + > + {i18n.LANGSMITH_DATASET_LABEL} + + {' / '} + setUseLangSmithDataset(false)} + > + {i18n.CUSTOM_DATASET_LABEL} + + {')'} + + ); + }, [useLangSmithDataset]); + const [datasetName, setDatasetName] = useState(); + const onDatasetNameChange = useCallback( + (e) => { + setDatasetName(e.target.value); + }, + [setDatasetName] + ); + const sampleDataset = [ + { + input: + 'As an expert user of Elastic Security, please generate an accurate and valid ESQL query to detect the use case below. Your response should be formatted to be able to use immediately in an Elastic Security timeline or detection rule. Take your time with the answer, and really make sure you check your knowledge really well on all the functions I am asking for. check it multiple times if you need to. I cannot afford for queries to be inaccurate. Assume I am using the Elastic Common Schema. Ensure the answers are formatted in a way which is easily copyable.\n\n' + + 'Write an ESQL query for detecting cryptomining activity on an AWS EC2 instance.', + reference: + 'FROM metrics-apm*\n| WHERE metricset.name == ""transaction"" AND metricset.interval == ""1m""\n| EVAL bucket = AUTO_BUCKET(transaction.duration.histogram, 50, , )\n| STATS avg_duration = AVG(transaction.duration.histogram) BY bucket', + }, + ]; + const [datasetText, setDatasetText] = useState(JSON.stringify(sampleDataset, null, 2)); + const onDatasetTextChange = useCallback( + (e) => { + setDatasetText(e.target.value); + }, + [setDatasetText] + ); // Predictions - const predictionsSettings = usePredictionsDetails({ - http, - connectors, - actionTypeRegistry, - }); + // Connectors / Models + const [selectedModelOptions, setSelectedModelOptions] = useState< + Array> + >([]); + const onModelOptionsChange = useCallback( + (selectedOptions: Array>) => { + setSelectedModelOptions(selectedOptions); + }, + [setSelectedModelOptions] + ); + const visColorsBehindText = euiPaletteComplementary(connectors?.length ?? 0); + const modelOptions = useMemo(() => { + return ( + connectors?.map((c, index) => { + const apiProvider = getGenAiConfig(c)?.apiProvider; + const connectorTypeTitle = + apiProvider ?? getActionTypeTitle(actionTypeRegistry.get(c.actionTypeId)); + const connectorDetails = c.isPreconfigured ? PRECONFIGURED_CONNECTOR : connectorTypeTitle; + return { + key: c.id, + label: `${c.name} (${connectorDetails})`, + color: visColorsBehindText[index], + }; + }) ?? [] + ); + }, [actionTypeRegistry, connectors, visColorsBehindText]); - const { - selectedEvaluationType, - onEvaluationTypeChange, - onEvaluationTypeOptionsCreate, - evaluationTypeOptions, - selectedEvaluatorModelOptions, - onEvaluatorModelOptionsChange, - evalPrompt, - onEvalPromptChange, - } = useEvaluationDetails(); + // Agents + const [selectedAgentOptions, setSelectedAgentOptions] = useState< + Array> + >([]); + const onAgentOptionsChange = useCallback( + (agentOptions: Array>) => { + setSelectedAgentOptions(agentOptions); + }, + [setSelectedAgentOptions] + ); + const onAgentOptionsCreate = useCallback( + (searchValue: string) => { + const normalizedSearchValue = searchValue.trim(); + + if (!normalizedSearchValue) { + return; + } + + setSelectedAgentOptions([...selectedAgentOptions, { label: normalizedSearchValue }]); + }, + [selectedAgentOptions] + ); + const agentOptions = useMemo(() => { + return defaultAgents.map((label) => ({ label })); + }, [defaultAgents]); + + // Evaluation + // Evaluation Type + const [selectedEvaluationType, setSelectedEvaluationType] = useState< + Array> + >([]); + const onEvaluationTypeChange = useCallback( + (evaluationType: Array>) => { + setSelectedEvaluationType(evaluationType); + }, + [setSelectedEvaluationType] + ); + const onEvaluationTypeOptionsCreate = useCallback( + (searchValue: string) => { + const normalizedSearchValue = searchValue.trim(); + + if (!normalizedSearchValue) { + return; + } + + setSelectedEvaluationType([{ label: normalizedSearchValue }]); + }, + [setSelectedEvaluationType] + ); + const evaluationTypeOptions = useMemo(() => { + return DEFAULT_EVAL_TYPES_OPTIONS; + }, []); + + // Eval Model + const [selectedEvaluatorModelOptions, setSelectedEvaluatorModelOptions] = useState< + Array> + >([]); + const onEvaluatorModelOptionsChange = useCallback( + (selectedOptions: Array>) => { + setSelectedEvaluatorModelOptions(selectedOptions); + }, + [setSelectedEvaluatorModelOptions] + ); + + // Eval Prompt + const sampleEvalPrompt: string = `For the below input: \n\n{{input}} \n\na prediction: \n\n{{prediction}} \n\nwas made. How's it stack up against this reference: \n\n{{reference}} \n\nReturn output in a succinct sentence ranking on a simple grading rubric focused on correctness.`; + const [evalPrompt, setEvalPrompt] = useState(sampleEvalPrompt); + const onEvalPromptChange = useCallback( + (e) => { + setEvalPrompt(e.target.value); + }, + [setEvalPrompt] + ); // Required fields by eval API const isPerformEvaluationDisabled = - predictionsSettings.selectedModelOptions.length === 0 || - predictionsSettings.selectedAgentOptions.length === 0 || - runDetailsSettings.outputIndex.length === 0; + selectedModelOptions.length === 0 || + selectedAgentOptions.length === 0 || + outputIndex.length === 0; // Perform Evaluation Button const handlePerformEvaluation = useCallback(async () => { const evalParams = { - models: predictionsSettings.selectedModelOptions.flatMap((option) => option.key ?? []), - agents: predictionsSettings.selectedAgentOptions.map((option) => option.label), - dataset: datasetSettings.useLangSmithDataset ? undefined : datasetSettings.datasetText, - datasetName: datasetSettings.useLangSmithDataset ? datasetSettings.datasetName : undefined, + models: selectedModelOptions.flatMap((option) => option.key ?? []), + agents: selectedAgentOptions.map((option) => option.label), + dataset: useLangSmithDataset ? undefined : datasetText, + datasetName: useLangSmithDataset ? datasetName : undefined, evalModel: selectedEvaluatorModelOptions.flatMap((option) => option.key ?? []), evalPrompt, evaluationType: selectedEvaluationType.map((option) => option.label), - outputIndex: runDetailsSettings.outputIndex, - projectName: runDetailsSettings.projectName, - runName: runDetailsSettings.runName, + outputIndex, + projectName, + runName, }; performEvaluation(evalParams); }, [ - datasetSettings.datasetName, - datasetSettings.datasetText, - datasetSettings.useLangSmithDataset, + datasetName, + datasetText, evalPrompt, + outputIndex, performEvaluation, - predictionsSettings.selectedAgentOptions, - predictionsSettings.selectedModelOptions, - runDetailsSettings.outputIndex, - runDetailsSettings.projectName, - runDetailsSettings.runName, + projectName, + runName, + selectedAgentOptions, selectedEvaluationType, selectedEvaluatorModelOptions, + selectedModelOptions, + useLangSmithDataset, ]); const discoverLink = useMemo( @@ -143,6 +314,24 @@ export const EvaluationSettings: React.FC = React.memo(() => { [basePath, evalResponse] ); + const getSection = (title: string, description: string) => ( +
+ + + +

{title}

+
+
+
+ + +

+ {description} +

+
+
+ ); + const runDetailsSection = useMemo( () => getSection(i18n.RUN_DETAILS_TITLE, i18n.RUN_DETAILS_DESCRIPTION), [] @@ -180,11 +369,134 @@ export const EvaluationSettings: React.FC = React.memo(() => { initialIsOpen={true} paddingSize="s" > - + + + + + + + + + + + + + + {useLangSmithDataset ? ( + + ) : ( + + )} + + + + + + setShowTraceOptions(!showTraceOptions)}> + {i18n.SHOW_TRACE_OPTIONS} + + + {showTraceOptions && ( + <> + + + + + + + + + + + )} {/* Prediction Details*/} @@ -197,7 +509,34 @@ export const EvaluationSettings: React.FC = React.memo(() => { initialIsOpen={true} paddingSize="s" > - + + + + + + + {/* Evaluation Details*/} @@ -217,7 +556,7 @@ export const EvaluationSettings: React.FC = React.memo(() => { = React.memo(({ predictionsSettings }) => { - const { - modelOptions, - selectedModelOptions, - onModelOptionsChange, - agentOptions, - selectedAgentOptions, - onAgentOptionsChange, - onAgentOptionsCreate, - } = predictionsSettings; - return ( - <> - - - - - - - - - ); -}); - -PredictionDetails.displayName = 'PredictionDetails'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/translations.ts deleted file mode 100644 index bf0f07714ec3c..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/translations.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { i18n } from '@kbn/i18n'; - -export const CONNECTORS_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsLabel', - { - defaultMessage: 'Connectors / Models', - } -); - -export const CONNECTORS_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsDescription', - { - defaultMessage: 'Select whichever models you want to evaluate the dataset against', - } -); - -export const AGENTS_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.agentsLabel', - { - defaultMessage: 'Agents', - } -); - -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', - } -); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/use_predictions_details.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/use_predictions_details.tsx deleted file mode 100644 index 4b41bb38a75a4..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/prediction_details/use_predictions_details.tsx +++ /dev/null @@ -1,108 +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 { EuiComboBoxOptionOption, euiPaletteComplementary } from '@elastic/eui'; -import { useCallback, useState, useMemo } from 'react'; -import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; -import { HttpSetup } from '@kbn/core/public'; -import type { GetEvaluateResponse } from '@kbn/elastic-assistant-common'; -import { AIConnector } from '../../../../connectorland/connector_selector'; -import { getActionTypeTitle, getGenAiConfig } from '../../../../connectorland/helpers'; -import { PRECONFIGURED_CONNECTOR } from '../../../../connectorland/translations'; -import { useEvaluationData } from '../../../api/evaluate/use_evaluation_data'; - -interface Props { - actionTypeRegistry: ActionTypeRegistryContract; - connectors: AIConnector[] | undefined; - http: HttpSetup; -} - -export interface PredictionsSettings { - modelOptions: Array>; - selectedModelOptions: Array>; - onModelOptionsChange: (selectedOptions: Array>) => void; - agentOptions: Array>; - selectedAgentOptions: Array>; - onAgentOptionsChange: (agentOptions: Array>) => void; - onAgentOptionsCreate: (searchValue: string) => void; -} - -export const usePredictionsDetails = ({ - http, - connectors, - actionTypeRegistry, -}: Props): PredictionsSettings => { - const { data: evalData } = useEvaluationData({ http }); - const defaultAgents = useMemo( - () => (evalData as GetEvaluateResponse)?.agentExecutors ?? [], - [evalData] - ); - - // Predictions - // Connectors / Models - const [selectedModelOptions, setSelectedModelOptions] = useState< - Array> - >([]); - const onModelOptionsChange = useCallback( - (selectedOptions: Array>) => { - setSelectedModelOptions(selectedOptions); - }, - [setSelectedModelOptions] - ); - const visColorsBehindText = euiPaletteComplementary(connectors?.length ?? 0); - const modelOptions = useMemo(() => { - return ( - connectors?.map((c, index) => { - const apiProvider = getGenAiConfig(c)?.apiProvider; - const connectorTypeTitle = - apiProvider ?? getActionTypeTitle(actionTypeRegistry.get(c.actionTypeId)); - const connectorDetails = c.isPreconfigured ? PRECONFIGURED_CONNECTOR : connectorTypeTitle; - return { - key: c.id, - label: `${c.name} (${connectorDetails})`, - color: visColorsBehindText[index], - }; - }) ?? [] - ); - }, [actionTypeRegistry, connectors, visColorsBehindText]); - - // Agents - const [selectedAgentOptions, setSelectedAgentOptions] = useState< - Array> - >([]); - const onAgentOptionsChange = useCallback( - (agentOptions: Array>) => { - setSelectedAgentOptions(agentOptions); - }, - [setSelectedAgentOptions] - ); - const onAgentOptionsCreate = useCallback( - (searchValue: string) => { - const normalizedSearchValue = searchValue.trim(); - - if (!normalizedSearchValue) { - return; - } - - setSelectedAgentOptions([...selectedAgentOptions, { label: normalizedSearchValue }]); - }, - [selectedAgentOptions] - ); - const agentOptions = useMemo(() => { - return defaultAgents.map((label) => ({ label })); - }, [defaultAgents]); - - return { - modelOptions, - selectedModelOptions, - onModelOptionsChange, - agentOptions, - selectedAgentOptions, - onAgentOptionsChange, - onAgentOptionsCreate, - }; -}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/data_set_toggle_button.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/data_set_toggle_button.tsx deleted file mode 100644 index b877fcd201a29..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/data_set_toggle_button.tsx +++ /dev/null @@ -1,44 +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 { EuiLink, EuiText } from '@elastic/eui'; -import React from 'react'; -import { css } from '@emotion/react'; - -import * as i18n from './translations'; - -interface Props { - useLangSmithDataset: boolean; - onUseLangSmith: () => void; - onUseCustom: () => void; -} - -export const DatasetToggleButton: React.FC = React.memo( - ({ useLangSmithDataset, onUseLangSmith, onUseCustom }) => { - return ( - - {i18n.EVALUATOR_DATASET_LABEL} - {' ('} - onUseLangSmith()}> - {i18n.LANGSMITH_DATASET_LABEL} - - {' / '} - onUseCustom()}> - {i18n.CUSTOM_DATASET_LABEL} - - {')'} - - ); - } -); - -DatasetToggleButton.displayName = 'DatasetToggleButton'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/index.tsx deleted file mode 100644 index 2962cb4fcc8ca..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/index.tsx +++ /dev/null @@ -1,213 +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. - */ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { - EuiFormRow, - EuiText, - EuiTextArea, - EuiFieldText, - EuiFlexItem, - EuiFlexGroup, - EuiLink, -} from '@elastic/eui'; - -import { css } from '@emotion/react'; - -import * as i18n from './translations'; -import { DatasetToggleButton } from './data_set_toggle_button'; -import { RunDetailsSettings } from './use_run_details'; -import { DataSetSettings } from './use_dataset'; -import { TraceOptionsSettings } from './use_trace_options'; - -/** - * Evaluation Settings -- development-only feature for evaluating models - */ -interface Props { - runDetailsSettings: RunDetailsSettings; - datasetSettings: DataSetSettings; - traceOptionsSettings: TraceOptionsSettings; -} - -export const RunDetailsEditor: React.FC = React.memo( - ({ runDetailsSettings, datasetSettings, traceOptionsSettings }) => { - const { - projectName, - onProjectNameChange, - runName, - onRunNameChange, - outputIndex, - onOutputIndexChange, - } = runDetailsSettings; - - const { - onUseLangSmith, - onUseCustom, - useLangSmithDataset, - datasetName, - onDatasetNameChange, - datasetText, - onDatasetTextChange, - } = datasetSettings; - - const { - showTraceOptions, - setShowTraceOptions, - traceOptions, - onApmUrlChange, - onLangSmithProjectChange, - onLangSmithApiKeyChange, - } = traceOptionsSettings; - - return ( - <> - - - - - - - - - - - - - - } - fullWidth - helpText={ - useLangSmithDataset - ? i18n.LANGSMITH_DATASET_DESCRIPTION - : i18n.CUSTOM_DATASET_DESCRIPTION - } - > - {useLangSmithDataset ? ( - - ) : ( - - )} - - - - - - setShowTraceOptions(!showTraceOptions)}> - {i18n.SHOW_TRACE_OPTIONS} - - - {showTraceOptions && ( - <> - - - - - - - - - - - )} - - ); - } -); - -RunDetailsEditor.displayName = 'RunDetailsEditor'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/translations.ts deleted file mode 100644 index 3f62bd40a520e..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/translations.ts +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { i18n } from '@kbn/i18n'; - -export const SHOW_TRACE_OPTIONS = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.showTraceOptionsLabel', - { - defaultMessage: 'Show Trace Options (for internal use only)', - } -); - -export const APM_URL_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.apmUrlLabel', - { - defaultMessage: 'APM URL', - } -); - -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 "$\\{basePath\\}/app/apm"', - } -); - -export const EVALUATOR_DATASET_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorDatasetLabel', - { - defaultMessage: 'Dataset', - } -); - -export const LANGSMITH_DATASET_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetLabel', - { - defaultMessage: 'LangSmith', - } -); - -export const LANGSMITH_DATASET_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetDescription', - { - defaultMessage: 'Name of dataset hosted on LangSmith to evaluate', - } -); - -export const LANGSMITH_DATASET_PLACEHOLDER = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetPlaceholder', - { - defaultMessage: 'ESQL Query Generation', - } -); - -export const LANGSMITH_PROJECT_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithProjectLabel', - { - defaultMessage: 'LangSmith Project', - } -); - -export const LANGSMITH_PROJECT_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithProjectDescription', - { - defaultMessage: 'LangSmith Project to write traces to', - } -); - -export const LANGSMITH_API_KEY_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithApiKeyLabel', - { - defaultMessage: 'LangSmith API Key', - } -); - -export const LANGSMITH_API_KEY_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithApiKeyDescription', - { - defaultMessage: - 'API Key for writing traces to LangSmith. Stored in Session Storage. Close tab to clear session.', - } -); - -export const CUSTOM_DATASET_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.customDatasetLabel', - { - defaultMessage: 'Custom', - } -); - -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', - } -); - -export const EVALUATOR_OUTPUT_INDEX_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorOutputIndexLabel', - { - defaultMessage: 'Output index', - } -); - -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-"', - } -); - -export const PROJECT_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectLabel', - { - defaultMessage: 'Project', - } -); - -export const PROJECT_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectDescription', - { - defaultMessage: 'LangSmith project to write results to', - } -); - -export const PROJECT_PLACEHOLDER = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectPlaceholder', - { - defaultMessage: '8.12 Testing', - } -); - -export const RUN_NAME_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNameLabel', - { - defaultMessage: 'Run name', - } -); - -export const RUN_NAME_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNameDescription', - { - defaultMessage: 'Name for this specific test run', - } -); - -export const RUN_NAME_PLACEHOLDER = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNamePlaceholder', - { - defaultMessage: '8.12 ESQL Query Generation', - } -); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_dataset.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_dataset.tsx deleted file mode 100644 index 0959096d92c9a..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_dataset.tsx +++ /dev/null @@ -1,64 +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 { useCallback, useState } from 'react'; - -export interface DataSetSettings { - onUseLangSmith: () => void; - onUseCustom: () => void; - useLangSmithDataset: boolean; - datasetName: string | undefined; - onDatasetNameChange: (e: React.ChangeEvent) => void; - datasetText: string; - onDatasetTextChange: (e: React.ChangeEvent) => void; -} - -export const useDataset = (): DataSetSettings => { - /** Dataset **/ - const [useLangSmithDataset, setUseLangSmithDataset] = useState(true); - - const [datasetName, setDatasetName] = useState(); - const onDatasetNameChange = useCallback( - (e) => { - setDatasetName(e.target.value); - }, - [setDatasetName] - ); - const sampleDataset = [ - { - input: - 'As an expert user of Elastic Security, please generate an accurate and valid ESQL query to detect the use case below. Your response should be formatted to be able to use immediately in an Elastic Security timeline or detection rule. Take your time with the answer, and really make sure you check your knowledge really well on all the functions I am asking for. check it multiple times if you need to. I cannot afford for queries to be inaccurate. Assume I am using the Elastic Common Schema. Ensure the answers are formatted in a way which is easily copyable.\n\n' + - 'Write an ESQL query for detecting cryptomining activity on an AWS EC2 instance.', - reference: - 'FROM metrics-apm*\n| WHERE metricset.name == ""transaction"" AND metricset.interval == ""1m""\n| EVAL bucket = AUTO_BUCKET(transaction.duration.histogram, 50, , )\n| STATS avg_duration = AVG(transaction.duration.histogram) BY bucket', - }, - ]; - const [datasetText, setDatasetText] = useState(JSON.stringify(sampleDataset, null, 2)); - const onDatasetTextChange = useCallback( - (e) => { - setDatasetText(e.target.value); - }, - [setDatasetText] - ); - - const onUseLangSmith = useCallback(() => { - setUseLangSmithDataset(true); - }, []); - - const onUseCustom = useCallback(() => { - setUseLangSmithDataset(false); - }, []); - - return { - onUseLangSmith, - onUseCustom, - useLangSmithDataset, - datasetName, - onDatasetNameChange, - datasetText, - onDatasetTextChange, - }; -}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_run_details.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_run_details.tsx deleted file mode 100644 index 7b594ac55af33..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_run_details.tsx +++ /dev/null @@ -1,55 +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 { useCallback, useState } from 'react'; - -const DEFAULT_OUTPUT_INDEX = '.kibana-elastic-ai-assistant-evaluation-results'; - -export interface RunDetailsSettings { - projectName: string | undefined; - onProjectNameChange: (e: React.ChangeEvent) => void; - runName: string | undefined; - onRunNameChange: (e: React.ChangeEvent) => void; - outputIndex: string; - onOutputIndexChange: (e: React.ChangeEvent) => void; -} - -export const useRunDetails = (): RunDetailsSettings => { - // Run Details - // Project Name - const [projectName, setProjectName] = useState(); - const onProjectNameChange = useCallback( - (e) => { - setProjectName(e.target.value); - }, - [setProjectName] - ); - // Run Name - const [runName, setRunName] = useState(); - const onRunNameChange = useCallback( - (e) => { - setRunName(e.target.value); - }, - [setRunName] - ); - // Local Output Index - const [outputIndex, setOutputIndex] = useState(DEFAULT_OUTPUT_INDEX); - const onOutputIndexChange = useCallback( - (e) => { - setOutputIndex(e.target.value); - }, - [setOutputIndex] - ); - - return { - projectName, - onProjectNameChange, - runName, - onRunNameChange, - outputIndex, - onOutputIndexChange, - }; -}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_trace_options.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_trace_options.tsx deleted file mode 100644 index be25925d71776..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings/run_details/use_trace_options.tsx +++ /dev/null @@ -1,54 +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 { useCallback, useState } from 'react'; -import { UseAssistantContext } from '../../../../assistant_context'; - -interface Props { - setTraceOptions: UseAssistantContext['setTraceOptions']; - traceOptions: UseAssistantContext['traceOptions']; -} - -export interface TraceOptionsSettings { - showTraceOptions: boolean; - setShowTraceOptions: (value: boolean) => void; - traceOptions: UseAssistantContext['traceOptions']; - onApmUrlChange: (e: React.ChangeEvent) => void; - onLangSmithProjectChange: (e: React.ChangeEvent) => void; - onLangSmithApiKeyChange: (e: React.ChangeEvent) => void; -} - -export const useTraceOptions = ({ setTraceOptions, traceOptions }: Props): TraceOptionsSettings => { - /** Trace Options **/ - const [showTraceOptions, setShowTraceOptions] = useState(false); - const onApmUrlChange = useCallback( - (e) => { - setTraceOptions({ ...traceOptions, apmUrl: e.target.value }); - }, - [setTraceOptions, traceOptions] - ); - const onLangSmithProjectChange = useCallback( - (e) => { - setTraceOptions({ ...traceOptions, langSmithProject: e.target.value }); - }, - [setTraceOptions, traceOptions] - ); - const onLangSmithApiKeyChange = useCallback( - (e) => { - setTraceOptions({ ...traceOptions, langSmithApiKey: e.target.value }); - }, - [setTraceOptions, traceOptions] - ); - - return { - showTraceOptions, - setShowTraceOptions, - traceOptions, - onApmUrlChange, - onLangSmithProjectChange, - onLangSmithApiKeyChange, - }; -}; 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 be33991b0178d..517f4456b49e8 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 @@ -24,7 +24,7 @@ export const SETTINGS_DESCRIPTION = i18n.translate( export const RUN_DETAILS_TITLE = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runDetailsTitle', { - defaultMessage: '🏃 Run Details', + defaultMessage: 'Run Details', } ); @@ -38,7 +38,7 @@ export const RUN_DETAILS_DESCRIPTION = i18n.translate( export const PREDICTION_DETAILS_TITLE = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.predictionDetailsTitle', { - defaultMessage: '🔮 Predictions', + defaultMessage: 'Predictions', } ); @@ -53,7 +53,7 @@ export const PREDICTION_DETAILS_DESCRIPTION = i18n.translate( export const EVALUATION_DETAILS_TITLE = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluationDetailsTitle', { - defaultMessage: '🧮 Evaluation (Optional)', + defaultMessage: 'Evaluation (Optional)', } ); @@ -65,6 +65,76 @@ export const EVALUATION_DETAILS_DESCRIPTION = i18n.translate( } ); +export const PROJECT_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectLabel', + { + defaultMessage: 'Project', + } +); + +export const PROJECT_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectDescription', + { + defaultMessage: 'LangSmith project to write results to', + } +); + +export const PROJECT_PLACEHOLDER = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectPlaceholder', + { + defaultMessage: '8.12 Testing', + } +); + +export const RUN_NAME_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNameLabel', + { + defaultMessage: 'Run name', + } +); + +export const RUN_NAME_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNameDescription', + { + defaultMessage: 'Name for this specific test run', + } +); + +export const RUN_NAME_PLACEHOLDER = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNamePlaceholder', + { + defaultMessage: '8.12 ESQL Query Generation', + } +); + +export const CONNECTORS_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsLabel', + { + defaultMessage: 'Connectors / Models', + } +); + +export const CONNECTORS_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsDescription', + { + defaultMessage: 'Select whichever models you want to evaluate the dataset against', + } +); + +export const AGENTS_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.agentsLabel', + { + defaultMessage: 'Agents', + } +); + +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', + } +); + export const EVALUATOR_MODEL_LABEL = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorModelLabel', { @@ -108,6 +178,117 @@ export const EVALUATION_PROMPT_DESCRIPTION = i18n.translate( 'Prompt template given `input`, `reference` and `prediction` template variables', } ); +export const EVALUATOR_OUTPUT_INDEX_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorOutputIndexLabel', + { + defaultMessage: 'Output index', + } +); + +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-"', + } +); + +export const SHOW_TRACE_OPTIONS = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.showTraceOptionsLabel', + { + defaultMessage: 'Show Trace Options (for internal use only)', + } +); + +export const APM_URL_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.apmUrlLabel', + { + defaultMessage: 'APM URL', + } +); + +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}"', + values: { + defaultUrlPath: '${basePath}/app/apm', + }, + } +); + +export const LANGSMITH_PROJECT_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithProjectLabel', + { + defaultMessage: 'LangSmith Project', + } +); + +export const LANGSMITH_PROJECT_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithProjectDescription', + { + defaultMessage: 'LangSmith Project to write traces to', + } +); + +export const LANGSMITH_API_KEY_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithApiKeyLabel', + { + defaultMessage: 'LangSmith API Key', + } +); + +export const LANGSMITH_API_KEY_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithApiKeyDescription', + { + defaultMessage: + 'API Key for writing traces to LangSmith. Stored in Session Storage. Close tab to clear session.', + } +); + +export const EVALUATOR_DATASET_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorDatasetLabel', + { + defaultMessage: 'Dataset', + } +); + +export const LANGSMITH_DATASET_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetLabel', + { + defaultMessage: 'LangSmith', + } +); + +export const LANGSMITH_DATASET_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetDescription', + { + defaultMessage: 'Name of dataset hosted on LangSmith to evaluate', + } +); + +export const LANGSMITH_DATASET_PLACEHOLDER = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetPlaceholder', + { + defaultMessage: 'ESQL Query Generation', + } +); + +export const CUSTOM_DATASET_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.evaluationSettings.customDatasetLabel', + { + defaultMessage: 'Custom', + } +); + +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', + } +); export const PERFORM_EVALUATION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.evaluationSettings.performEvaluationTitle', diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/index.tsx deleted file mode 100644 index cb8c8ea8983f2..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/index.tsx +++ /dev/null @@ -1,291 +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 React, { useCallback, useMemo } from 'react'; -import { - EuiAccordion, - EuiFormRow, - EuiTitle, - EuiText, - EuiHorizontalRule, - EuiSpacer, - EuiComboBox, - EuiButton, - EuiTextArea, - EuiFlexItem, - EuiFlexGroup, - EuiLink, -} from '@elastic/eui'; - -import { css } from '@emotion/react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { PostEvaluateResponse } from '@kbn/elastic-assistant-common'; -import { useAssistantContext } from '../../../assistant_context'; -import { useLoadConnectors } from '../../../connectorland/use_load_connectors'; - -import { usePerformEvaluation } from '../../api/evaluate/use_perform_evaluation'; -import { getApmLink, getDiscoverLink } from '../evaluation_settings/utils'; -import { useEvaluationDetails } from '../evaluation_settings/evaluation_details/use_evaluation_details'; -import { useRunDetails } from '../evaluation_settings/run_details/use_run_details'; -import { useTraceOptions } from '../evaluation_settings/run_details/use_trace_options'; -import { useDataset } from '../evaluation_settings/run_details/use_dataset'; -import { usePredictionsDetails } from '../evaluation_settings/prediction_details/use_predictions_details'; -import * as i18n from '../evaluation_settings/translations'; -import { RunDetailsEditor } from '../evaluation_settings/run_details'; -import { PredictionDetails } from '../evaluation_settings/prediction_details'; -import { - EVALUATION_DETAILS_TITLE, - PREDICTION_DETAILS_TITLE, - RUN_DETAILS_TITLE, -} from './translations'; - -const getSection = (title: string, description?: string) => ( - <> - -

{title}

-
- - {description && {description}} - - -); - -export const EvaluationSettingsManagement: React.FC = React.memo(() => { - const { actionTypeRegistry, basePath, http, setTraceOptions, traceOptions } = - useAssistantContext(); - const { data: connectors } = useLoadConnectors({ http }); - const { - data: evalResponse, - mutate: performEvaluation, - isLoading: isPerformingEvaluation, - } = usePerformEvaluation({ - http, - }); - - // Run Details - const runDetailsSettings = useRunDetails(); - /** Trace Options **/ - const tracedOptionsSettings = useTraceOptions({ setTraceOptions, traceOptions }); - /** Dataset **/ - const datasetSettings = useDataset(); - // Predictions - const predictionsSettings = usePredictionsDetails({ - http, - connectors, - actionTypeRegistry, - }); - - const { - selectedEvaluationType, - onEvaluationTypeChange, - onEvaluationTypeOptionsCreate, - evaluationTypeOptions, - selectedEvaluatorModelOptions, - onEvaluatorModelOptionsChange, - evalPrompt, - onEvalPromptChange, - } = useEvaluationDetails(); - - // Required fields by eval API - const isPerformEvaluationDisabled = - predictionsSettings.selectedModelOptions.length === 0 || - predictionsSettings.selectedAgentOptions.length === 0 || - runDetailsSettings.outputIndex.length === 0; - - // Perform Evaluation Button - const handlePerformEvaluation = useCallback(async () => { - const evalParams = { - models: predictionsSettings.selectedModelOptions.flatMap((option) => option.key ?? []), - agents: predictionsSettings.selectedAgentOptions.map((option) => option.label), - dataset: datasetSettings.useLangSmithDataset ? undefined : datasetSettings.datasetText, - datasetName: datasetSettings.useLangSmithDataset ? datasetSettings.datasetName : undefined, - evalModel: selectedEvaluatorModelOptions.flatMap((option) => option.key ?? []), - evalPrompt, - evaluationType: selectedEvaluationType.map((option) => option.label), - outputIndex: runDetailsSettings.outputIndex, - projectName: runDetailsSettings.projectName, - runName: runDetailsSettings.runName, - }; - performEvaluation(evalParams); - }, [ - datasetSettings.datasetName, - datasetSettings.datasetText, - datasetSettings.useLangSmithDataset, - evalPrompt, - performEvaluation, - predictionsSettings.selectedAgentOptions, - predictionsSettings.selectedModelOptions, - runDetailsSettings.outputIndex, - runDetailsSettings.projectName, - runDetailsSettings.runName, - selectedEvaluationType, - selectedEvaluatorModelOptions, - ]); - - const discoverLink = useMemo( - () => getDiscoverLink(basePath, (evalResponse as PostEvaluateResponse)?.evaluationId ?? ''), - [basePath, evalResponse] - ); - - const apmLink = useMemo( - () => getApmLink(basePath, (evalResponse as PostEvaluateResponse)?.evaluationId ?? ''), - [basePath, evalResponse] - ); - - const runDetailsSection = useMemo( - () => getSection(RUN_DETAILS_TITLE, i18n.RUN_DETAILS_DESCRIPTION), - [] - ); - const predictionDetailsSection = useMemo( - () => getSection(PREDICTION_DETAILS_TITLE, i18n.PREDICTION_DETAILS_DESCRIPTION), - [] - ); - const evalDetailsSection = useMemo(() => getSection(EVALUATION_DETAILS_TITLE), []); - - const buttonCss = css` - &:hover { - text-decoration: none; - } - `; - - return ( - <> - -

{i18n.SETTINGS_TITLE}

-
- - {i18n.SETTINGS_DESCRIPTION} - - {/* Run Details*/} - - - - - {/* Prediction Details*/} - - - - - {/* Evaluation Details*/} - - - - - - - - - - {i18n.PERFORM_EVALUATION} - - - - - - {i18n.EVALUATOR_FUN_FACT_DISCOVER_LINK} - - ), - apm: ( - - {i18n.EVALUATOR_FUN_FACT_APM_LINK} - - ), - }} - /> - - - - - - - - - - - - - - - - ); -}); -EvaluationSettingsManagement.displayName = 'EvaluationSettingsManagement'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/translations.ts deleted file mode 100644 index ebec3c45c4bdc..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/evaluation_settings_management/translations.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { i18n } from '@kbn/i18n'; - -export const RUN_DETAILS_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettingsManagement.runDetailsTitle', - { - defaultMessage: 'Run Details', - } -); - -export const PREDICTION_DETAILS_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettingsManagement.predictionDetailsTitle', - { - defaultMessage: 'Predictions', - } -); - -export const EVALUATION_DETAILS_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.evaluationSettingsManagement.evaluationDetailsTitle', - { - defaultMessage: 'Evaluation (Optional)', - } -); From ce384662aece9ea8c046af93ad6511ce65ad4bba Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 21 Jun 2024 20:40:27 +0100 Subject: [PATCH 12/37] unit tests --- .../index.tsx | 2 +- .../system_prompt_column.tsx | 0 .../translations.ts | 6 +- .../use_conversations_table.tsx | 2 +- .../system_prompt_editor.tsx | 1 - .../index.tsx | 7 +- .../index.tsx | 0 .../translations.ts | 0 .../use_quick_prompt_table.tsx | 0 .../settings/assistant_settings.test.tsx | 15 +- .../assistant/settings/assistant_settings.tsx | 74 ++++----- .../assistant_settings_button.test.tsx | 2 +- .../settings/assistant_settings_button.tsx | 3 +- .../assistant_settings_management.test.tsx | 156 ++++++++++++++++++ .../assistant_settings_management.tsx | 43 +++-- .../impl/assistant/settings/const.ts | 13 ++ .../impl/assistant/settings/types.ts | 28 ++++ .../impl/assistant_context/index.tsx | 4 +- .../impl/connectorland/helpers.tsx | 4 - .../index.tsx | 0 20 files changed, 271 insertions(+), 89 deletions(-) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/{converstaion_settings_management => conversation_settings_management}/index.tsx (99%) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/{converstaion_settings_management => conversation_settings_management}/system_prompt_column.tsx (100%) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/{converstaion_settings_management => conversation_settings_management}/translations.ts (92%) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/{converstaion_settings_management => conversation_settings_management}/use_conversations_table.tsx (98%) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/{quick_prompt_settings_management.tsx => quick_prompt_settings_management}/index.tsx (100%) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/{quick_prompt_settings_management.tsx => quick_prompt_settings_management}/translations.ts (100%) rename x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/{quick_prompt_settings_management.tsx => quick_prompt_settings_management}/use_quick_prompt_table.tsx (100%) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.test.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/types.ts rename x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/{anonomization_settings_management => anonymization_settings_management}/index.tsx (100%) diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx similarity index 99% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx index 100d629e1bb56..f6d98f6e11f1e 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/converstaion_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -205,7 +205,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ onClose={onSaveCancelled} onSaveConfirmed={onSaveConfirmed} onSaveCancelled={onSaveCancelled} - title={selectedConversation?.title ?? i18n.CONVERSATIONS_TABLE_COLUMN_TYPE} + title={selectedConversation?.title ?? i18n.CONVERSATIONS_TABLE_COLUMN_CONVERSATIONS} > { }): Array> => { return [ { - name: i18n.CONVERSATIONS_TABLE_COLUMN_TYPE, + name: i18n.CONVERSATIONS_TABLE_COLUMN_CONVERSATIONS, render: (conversation: ConversationTableItem) => ( onEditActionClicked(conversation)}> {conversation.title} 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 15f3ba69aa5af..d5f8c02686532 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 @@ -319,7 +319,6 @@ export const SystemPromptEditorComponent: React.FC = ({ } checked={isNewConversationDefault} onChange={handleNewConversationDefaultChange} - compressed /> 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 d6db15c8ac166..7d30aee9041be 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 @@ -108,10 +108,9 @@ const SystemPromptSettingsManagementComponent = ({ }, [closeConfirmModal, onCancelClick]); const onDeleteConfirmed = useCallback(() => { - handleSave(); closeConfirmModal(); - onCancelClick(); - }, [closeConfirmModal, handleSave, onCancelClick]); + handleSave(); + }, [closeConfirmModal, handleSave]); const onSaveCancelled = useCallback(() => { closeFlyout(); @@ -119,8 +118,8 @@ const SystemPromptSettingsManagementComponent = ({ }, [closeFlyout, onCancelClick]); const onSaveConfirmed = useCallback(() => { - handleSave(); closeFlyout(); + handleSave(); }, [closeFlyout, handleSave]); const confirmationTitle = useMemo( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx similarity index 100% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/index.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/index.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/translations.ts similarity index 100% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/translations.ts rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/translations.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/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 similarity index 100% rename from x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management.tsx/use_quick_prompt_table.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx index 8278cb1559535..ea9580285872d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx @@ -8,19 +8,19 @@ import { alertConvo, customConvo, welcomeConvo } from '../../mock/conversation'; import { useAssistantContext } from '../../assistant_context'; import { fireEvent, render, act } from '@testing-library/react'; +import { AssistantSettings } from './assistant_settings'; +import React from 'react'; +import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; +import { MOCK_QUICK_PROMPTS } from '../../mock/quick_prompt'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { - AssistantSettings, ANONYMIZATION_TAB, CONVERSATIONS_TAB, EVALUATION_TAB, KNOWLEDGE_BASE_TAB, QUICK_PROMPTS_TAB, SYSTEM_PROMPTS_TAB, -} from './assistant_settings'; -import React from 'react'; -import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; -import { MOCK_QUICK_PROMPTS } from '../../mock/quick_prompt'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +} from './const'; const mockConversations = { [alertConvo.title]: alertConvo, @@ -49,6 +49,7 @@ const onSave = jest.fn().mockResolvedValue(() => {}); const onConversationSelected = jest.fn(); const testProps = { + conversationsLoaded: true, defaultConnectorId: '123', defaultProvider: OpenAiProviderType.OpenAi, selectedConversationId: welcomeConvo.title, @@ -65,7 +66,7 @@ jest.mock('../../assistant_context'); jest.mock('.', () => { return { AnonymizationSettings: () => , - ConversationSettings: () => , + ConversationSettings: () => , EvaluationSettings: () => , KnowledgeBaseSettings: () => , QuickPromptSettings: () => , 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 3bf4c9d89d60c..0373b55cfcc2b 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 @@ -24,7 +24,7 @@ import { import styled from 'styled-components'; import { css } from '@emotion/react'; import { AIConnector } from '../../connectorland/connector_selector'; -import { Conversation, Prompt, QuickPrompt } from '../../..'; +import { Conversation, Prompt, QuickPrompt, useLoadConnectors } from '../../..'; import * as i18n from './translations'; import { useAssistantContext } from '../../assistant_context'; import { TEST_IDS } from '../constants'; @@ -38,32 +38,20 @@ import { SystemPromptSettings, } from '.'; import { useFetchAnonymizationFields } from '../api/anonymization_fields/use_fetch_anonymization_fields'; +import { + ANONYMIZATION_TAB, + CONVERSATIONS_TAB, + EVALUATION_TAB, + KNOWLEDGE_BASE_TAB, + QUICK_PROMPTS_TAB, + SYSTEM_PROMPTS_TAB, +} from './const'; const StyledEuiModal = styled(EuiModal)` width: 800px; height: 575px; `; -export const CONNECTORS_TAB = 'CONNECTORS_TAB' as const; -export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const; -export const QUICK_PROMPTS_TAB = 'QUICK_PROMPTS_TAB' as const; -export const SYSTEM_PROMPTS_TAB = 'SYSTEM_PROMPTS_TAB' as const; -export const ANONYMIZATION_TAB = 'ANONYMIZATION_TAB' as const; -export const KNOWLEDGE_BASE_TAB = 'KNOWLEDGE_BASE_TAB' as const; -export const EVALUATION_TAB = 'EVALUATION_TAB' as const; - -export type BaseSettingsTabs = - | typeof CONVERSATIONS_TAB - | typeof QUICK_PROMPTS_TAB - | typeof SYSTEM_PROMPTS_TAB - | typeof ANONYMIZATION_TAB - | typeof KNOWLEDGE_BASE_TAB - | typeof EVALUATION_TAB; - -export type AdditionalSettingsTabs = typeof CONNECTORS_TAB; - -export type SettingsTabs = BaseSettingsTabs | AdditionalSettingsTabs; - interface Props { defaultConnector?: AIConnector; onClose: ( @@ -110,6 +98,10 @@ export const AssistantSettings: React.FC = React.memo( const { data: anonymizationFields, refetch: refetchAnonymizationFieldsResults } = useFetchAnonymizationFields(); + const { data: connectors } = useLoadConnectors({ + http, + }); + const { conversationSettings, setConversationSettings, @@ -219,7 +211,7 @@ export const AssistantSettings: React.FC = React.memo( setSelectedSettingsTab(CONVERSATIONS_TAB)} data-test-subj={`${CONVERSATIONS_TAB}-button`} > @@ -324,24 +316,25 @@ export const AssistantSettings: React.FC = React.memo( overflow-y: scroll; `} > - {selectedSettingsTab === CONVERSATIONS_TAB && ( - - )} + {!selectedSettingsTab || + (selectedSettingsTab === CONVERSATIONS_TAB && ( + + ))} {selectedSettingsTab === QUICK_PROMPTS_TAB && ( = React.memo( )} {selectedSettingsTab === SYSTEM_PROMPTS_TAB && ( {}); +const onConversationSelected = jest.fn(); + +const testProps = { + conversationsLoaded: true, + defaultConnectorId: '123', + defaultProvider: OpenAiProviderType.OpenAi, + selectedConversation: welcomeConvo, + onClose, + onSave, + isFlyoutMode: false, + onConversationSelected, + conversations: {}, + anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] }, + refetchAnonymizationFieldsResults: jest.fn(), +}; +jest.mock('../../assistant_context'); + +jest.mock('../../connectorland/connector_settings_management', () => ({ + ConnectorsSettingsManagement: () => , +})); + +jest.mock('../conversations/conversation_settings_management', () => ({ + ConversationSettingsManagement: () => , +})); + +jest.mock('../quick_prompts/quick_prompt_settings_management', () => ({ + QuickPromptSettingsManagement: () => , +})); + +jest.mock('../prompt_editor/system_prompt/system_prompt_settings_management', () => ({ + SystemPromptSettingsManagement: () => , +})); + +jest.mock('../../data_anonymization/settings/anonymization_settings_management', () => ({ + AnonymizationSettingsManagement: () => , +})); + +jest.mock('.', () => { + return { + EvaluationSettings: () => , + KnowledgeBaseSettings: () => , + }; +}); + +jest.mock('./use_settings_updater/use_settings_updater', () => { + const original = jest.requireActual('./use_settings_updater/use_settings_updater'); + return { + ...original, + useSettingsUpdater: jest.fn().mockImplementation(() => mockValues), + }; +}); + +const queryClient = new QueryClient(); + +const wrapper = (props: { children: React.ReactNode }) => ( + {props.children} +); + +describe('AssistantSettingsManagement', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useAssistantContext as jest.Mock).mockImplementation(() => mockContext); + }); + + it('Bottom bar is hidden when no pending changes', async () => { + const { queryByTestId } = render(, { + wrapper, + }); + + expect(queryByTestId(`bottom-bar`)).not.toBeInTheDocument(); + }); + + describe.each([ + CONNECTORS_TAB, + ANONYMIZATION_TAB, + CONVERSATIONS_TAB, + EVALUATION_TAB, + KNOWLEDGE_BASE_TAB, + QUICK_PROMPTS_TAB, + SYSTEM_PROMPTS_TAB, + ])('%s', (tab) => { + it('Opens the tab on button click', () => { + (useAssistantContext as jest.Mock).mockImplementation(() => ({ + ...mockContext, + selectedSettingsTab: tab, + })); + const { getByTestId } = render(, { + wrapper, + }); + fireEvent.click(getByTestId(`settingsPageTab-${tab}`)); + expect(setSelectedSettingsTab).toHaveBeenCalledWith(tab); + }); + it('renders with the correct tab open', () => { + (useAssistantContext as jest.Mock).mockImplementation(() => ({ + ...mockContext, + selectedSettingsTab: tab, + })); + const { getByTestId } = render(, { + wrapper, + }); + expect(getByTestId(`${tab}-tab`)).toBeInTheDocument(); + }); + }); +}); 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 cffac7e79ec42..c177bfe3da52d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx @@ -19,24 +19,25 @@ import { Conversation, Prompt, QuickPrompt } from '../../..'; import * as i18n from './translations'; import { useAssistantContext } from '../../assistant_context'; import { useSettingsUpdater } from './use_settings_updater/use_settings_updater'; -import { KnowledgeBaseSettings } from '.'; +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/converstaion_settings_management'; -import { QuickPromptSettingsManagement } from '../quick_prompts/quick_prompt_settings_management.tsx'; +import { ConversationSettingsManagement } from '../conversations/conversation_settings_management'; +import { QuickPromptSettingsManagement } from '../quick_prompts/quick_prompt_settings_management'; import { SystemPromptSettingsManagement } from '../prompt_editor/system_prompt/system_prompt_settings_management'; -import { AnonymizationSettingsManagement } from '../../data_anonymization/settings/anonomization_settings_management'; -import { EvaluationSettings } from './evaluation_settings/evaluation_settings'; +import { AnonymizationSettingsManagement } from '../../data_anonymization/settings/anonymization_settings_management'; -export const CONNECTORS_TAB = 'CONNECTORS_TAB' as const; -export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const; -export const QUICK_PROMPTS_TAB = 'QUICK_PROMPTS_TAB' as const; -export const SYSTEM_PROMPTS_TAB = 'SYSTEM_PROMPTS_TAB' as const; -export const ANONYMIZATION_TAB = 'ANONYMIZATION_TAB' as const; -export const KNOWLEDGE_BASE_TAB = 'KNOWLEDGE_BASE_TAB' as const; -export const EVALUATION_TAB = 'EVALUATION_TAB' as const; +import { + ANONYMIZATION_TAB, + CONNECTORS_TAB, + CONVERSATIONS_TAB, + EVALUATION_TAB, + KNOWLEDGE_BASE_TAB, + QUICK_PROMPTS_TAB, + SYSTEM_PROMPTS_TAB, +} from './const'; interface Props { conversations: Record; @@ -68,7 +69,6 @@ export const AssistantSettingsManagement: React.FC = React.memo( const { data: anonymizationFields } = useFetchAnonymizationFields(); - // Connector details const { data: connectors } = useLoadConnectors({ http, }); @@ -221,7 +221,6 @@ export const AssistantSettingsManagement: React.FC = React.memo( resetSettings(); setHasPendingChanges(false); }, [resetSettings]); - return ( <> = React.memo( onCancelClick={onCancelClick} selectedConversation={selectedConversation} setAssistantStreamingEnabled={handleChange(setUpdatedAssistantStreamingEnabled)} - setConversationSettings={handleChange(setConversationSettings)} - setConversationsSettingsBulkActions={handleChange( - setConversationsSettingsBulkActions - )} + setConversationSettings={setConversationSettings} + setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} onSelectedConversationChange={onHandleSelectedConversationChange} /> )} @@ -271,10 +268,8 @@ export const AssistantSettingsManagement: React.FC = React.memo( handleSave={handleSave} selectedSystemPrompt={selectedSystemPrompt} onSelectedSystemPromptChange={onHandleSelectedSystemPromptChange} - setConversationSettings={handleChange(setConversationSettings)} - setConversationsSettingsBulkActions={handleChange( - setConversationsSettingsBulkActions - )} + setConversationSettings={setConversationSettings} + setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} conversationsSettingsBulkActions={conversationsSettingsBulkActions} setUpdatedSystemPromptSettings={handleChange(setUpdatedSystemPromptSettings)} onCancelClick={onCancelClick} @@ -285,7 +280,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( quickPromptSettings={quickPromptSettings} onSelectedQuickPromptChange={onHandleSelectedQuickPromptChange} selectedQuickPrompt={selectedQuickPrompt} - setUpdatedQuickPromptSettings={handleChange(setUpdatedQuickPromptSettings)} + setUpdatedQuickPromptSettings={setUpdatedQuickPromptSettings} handleSave={handleSave} onCancelClick={onCancelClick} /> @@ -308,7 +303,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( {selectedSettingsTab === EVALUATION_TAB && } {hasPendingChanges && ( - + >; setKnowledgeBase: React.Dispatch>; setLastConversationId: React.Dispatch>; - setSelectedSettingsTab: React.Dispatch>; + setSelectedSettingsTab: React.Dispatch>; setShowAssistantOverlay: (showAssistantOverlay: ShowAssistantOverlay) => void; showAssistantOverlay: ShowAssistantOverlay; setTraceOptions: (traceOptions: { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx index 580d152e096e4..2bbc74af5a45a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/helpers.tsx @@ -70,10 +70,6 @@ export const getConnectorTypeTitle = ( const connectorTypeTitle = getGenAiConfig(connector)?.apiProvider ?? getActionTypeTitle(actionTypeRegistry.get(connector.actionTypeId)); - console.log('connector', connector); - console.log('genaiconfig', getGenAiConfig(connector)); - console.log('genaiconfig apiProvider', getGenAiConfig(connector)?.apiProvider); - console.log('title', getActionTypeTitle(actionTypeRegistry.get(connector.actionTypeId))); const actionType = connector.isPreconfigured ? PRECONFIGURED_CONNECTOR : connectorTypeTitle; return actionType; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonomization_settings_management/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx similarity index 100% rename from x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonomization_settings_management/index.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/data_anonymization/settings/anonymization_settings_management/index.tsx From 6aac96e28b19f27ab01ee3cdb3541816b88bd269 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 21 Jun 2024 22:17:37 +0100 Subject: [PATCH 13/37] fix update connector from modal --- .../conversation_settings.test.tsx | 7 ++++-- .../conversation_settings.tsx | 23 +++++++++++++++++-- .../settings/assistant_settings.test.tsx | 2 +- .../assistant/settings/assistant_settings.tsx | 1 + .../use_settings_updater.test.tsx | 15 ++++++------ 5 files changed, 36 insertions(+), 12 deletions(-) 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 10fbcdbb842a7..104b55987bc42 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 @@ -180,7 +180,10 @@ describe('ConversationSettings', () => { ); fireEvent.click(getByTestId('change-new-convo')); - expect(onSelectedConversationChange).toHaveBeenCalledWith({ ...mockConvo, id: '' }); + expect(onSelectedConversationChange).toHaveBeenCalledWith({ + ...mockConvo, + id: mockConvo.title, + }); expect(setConversationsSettingsBulkActions).toHaveBeenCalledWith({ create: { [mockConvo.title]: { ...mockConvo, id: '' }, @@ -205,7 +208,7 @@ describe('ConversationSettings', () => { ...mockConvos, [newConvo.title]: newConvo, }); - expect(onSelectedConversationChange).toHaveBeenCalledWith(newConvo); + expect(onSelectedConversationChange).toHaveBeenCalledWith({ ...newConvo, id: newConvo.title }); }); it('Deleting a conversation removes it from the convo settings', () => { const { getByTestId } = render( 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 c52af6eb878ab..fb4c19b7ba4a1 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 @@ -13,7 +13,7 @@ import { EuiFormRow, EuiSwitch, } from '@elastic/eui'; -import React from 'react'; +import React, { useMemo } from 'react'; import { HttpSetup } from '@kbn/core-http-browser'; @@ -29,10 +29,12 @@ import { ConversationsBulkActions } from '../../api'; import { useConversationDeleted } from './use_conversation_deleted'; import { ConversationSettingsEditor } from './conversation_settings_editor'; import { useConversationChanged } from './use_conversation_changed'; +import { getApiConfig } from '../../use_conversation/helpers'; export interface ConversationSettingsProps { actionTypeRegistry: ActionTypeRegistryContract; allSystemPrompts: Prompt[]; + connectors?: AIConnector[]; conversationSettings: Record; conversationsSettingsBulkActions: ConversationsBulkActions; defaultConnector?: AIConnector; @@ -56,6 +58,7 @@ export const ConversationSettings: React.FC = React.m ({ allSystemPrompts, assistantStreamingEnabled, + connectors, defaultConnector, selectedConversation, onSelectedConversationChange, @@ -85,6 +88,22 @@ export const ConversationSettings: React.FC = React.m setConversationsSettingsBulkActions, }); + const selectedConversationWithApiConfig = useMemo( + () => + selectedConversation + ? { + ...selectedConversation, + ...getApiConfig({ + allSystemPrompts, + conversation: selectedConversation, + connectors, + defaultConnector, + }), + } + : selectedConversation, + [allSystemPrompts, connectors, defaultConnector, selectedConversation] + ); + return ( <> @@ -108,7 +127,7 @@ export const ConversationSettings: React.FC = React.m http={http} isDisabled={isDisabled} isFlyoutMode={isFlyoutMode} - selectedConversation={selectedConversation} + selectedConversation={selectedConversationWithApiConfig} setConversationSettings={setConversationSettings} setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} /> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx index ea9580285872d..8f4a8680f9c57 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.test.tsx @@ -66,7 +66,7 @@ jest.mock('../../assistant_context'); jest.mock('.', () => { return { AnonymizationSettings: () => , - ConversationSettings: () => , + ConversationSettings: () => , EvaluationSettings: () => , KnowledgeBaseSettings: () => , QuickPromptSettings: () => , 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 0373b55cfcc2b..68a8049b825b3 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 @@ -320,6 +320,7 @@ export const AssistantSettings: React.FC = React.memo( (selectedSettingsTab === CONVERSATIONS_TAB && ( { it('should set all state variables to their initial values when resetSettings is called', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => - useSettingsUpdater(mockConversations, anonymizationFields) + useSettingsUpdater(mockConversations, conversationsLoaded, anonymizationFields) ); await waitForNextUpdate(); const { @@ -148,7 +149,7 @@ describe('useSettingsUpdater', () => { it('should update all state variables to their updated values when saveSettings is called', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => - useSettingsUpdater(mockConversations, anonymizationFields) + useSettingsUpdater(mockConversations, conversationsLoaded, anonymizationFields) ); await waitForNextUpdate(); const { @@ -189,7 +190,7 @@ describe('useSettingsUpdater', () => { it('should track which toggles have been updated when saveSettings is called', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => - useSettingsUpdater(mockConversations, anonymizationFields) + useSettingsUpdater(mockConversations, conversationsLoaded, anonymizationFields) ); await waitForNextUpdate(); const { setUpdatedKnowledgeBaseSettings } = result.current; @@ -206,7 +207,7 @@ describe('useSettingsUpdater', () => { it('should track only toggles that updated', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => - useSettingsUpdater(mockConversations, anonymizationFields) + useSettingsUpdater(mockConversations, conversationsLoaded, anonymizationFields) ); await waitForNextUpdate(); const { setUpdatedKnowledgeBaseSettings } = result.current; @@ -224,7 +225,7 @@ describe('useSettingsUpdater', () => { it('if no toggles update, do not track anything', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => - useSettingsUpdater(mockConversations, anonymizationFields) + useSettingsUpdater(mockConversations, conversationsLoaded, anonymizationFields) ); await waitForNextUpdate(); const { setUpdatedKnowledgeBaseSettings } = result.current; From d758032d4e3539e3be9b887c7e2aba09cfa01b9c Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sat, 22 Jun 2024 21:55:51 +0100 Subject: [PATCH 14/37] fix types --- .../impl/assistant/assistant_header/index.test.tsx | 1 + .../conversation_settings/conversation_settings.tsx | 4 ++-- .../use_conversations_table.tsx | 4 ++-- .../system_prompt/select_system_prompt/index.tsx | 2 +- .../system_prompt_modal/system_prompt_editor.tsx | 9 ++++++--- .../system_prompt_modal/system_prompt_settings.test.tsx | 1 + .../use_system_prompt_table.tsx | 4 ++-- .../impl/assistant/quick_prompts/quick_prompts.test.tsx | 2 +- .../impl/assistant/quick_prompts/quick_prompts.tsx | 2 +- .../impl/assistant/use_conversation/helpers.ts | 2 +- .../connectorland/connector_missing_callout/index.tsx | 2 +- .../impl/mock/test_providers/test_providers.tsx | 2 ++ 12 files changed, 21 insertions(+), 14 deletions(-) diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx index 4cf6afada5367..2301078d57ba1 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx @@ -19,6 +19,7 @@ const mockConversations = { [welcomeConvo.title]: welcomeConvo, }; const testProps = { + conversationsLoaded: true, currentConversation: welcomeConvo, title: 'Test Title', docLinks: { 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 fb4c19b7ba4a1..adf2e012c0b8f 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 @@ -29,7 +29,7 @@ import { ConversationsBulkActions } from '../../api'; import { useConversationDeleted } from './use_conversation_deleted'; import { ConversationSettingsEditor } from './conversation_settings_editor'; import { useConversationChanged } from './use_conversation_changed'; -import { getApiConfig } from '../../use_conversation/helpers'; +import { getConversationApiConfig } from '../../use_conversation/helpers'; export interface ConversationSettingsProps { actionTypeRegistry: ActionTypeRegistryContract; @@ -93,7 +93,7 @@ export const ConversationSettings: React.FC = React.m selectedConversation ? { ...selectedConversation, - ...getApiConfig({ + ...getConversationApiConfig({ allSystemPrompts, conversation: selectedConversation, connectors, 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 d3208e8a743b0..b501abfef970d 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 @@ -14,7 +14,7 @@ import { Conversation } from '../../../assistant_context/types'; import { AIConnector } from '../../../connectorland/connector_selector'; import { getConnectorTypeTitle } from '../../../connectorland/helpers'; import { Prompt } from '../../../..'; -import { getApiConfig } 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 { SystemPromptColumn } from './system_prompt_column'; @@ -108,7 +108,7 @@ export const useConversationsTable = () => { defaultConnector, }: GetConversationsListParams): ConversationTableItem[] => { return Object.values(conversations).map((conversation) => { - const conversationApiConfig = getApiConfig({ + const conversationApiConfig = getConversationApiConfig({ allSystemPrompts, connectors, 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 a2b8d0e1c4b7b..2cbbdf68b307c 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 @@ -24,9 +24,9 @@ import * as i18n from '../translations'; import type { Prompt } from '../../../types'; import { useAssistantContext } from '../../../../assistant_context'; import { useConversation } from '../../../use_conversation'; -import { SYSTEM_PROMPTS_TAB } from '../../../settings/assistant_settings'; import { TEST_IDS } from '../../../constants'; import { PROMPT_CONTEXT_SELECTOR_PREFIX } from '../../../quick_prompts/prompt_context_selector/translations'; +import { SYSTEM_PROMPTS_TAB } from '../../../settings/const'; export interface Props { allSystemPrompts: Prompt[]; 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 d5f8c02686532..605dcef717106 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 @@ -28,7 +28,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 { getApiConfig, getDefaultNewSystemPrompt } from '../../../use_conversation/helpers'; +import { + getConversationApiConfig, + getDefaultNewSystemPrompt, +} from '../../../use_conversation/helpers'; interface Props { connectors: AIConnector[] | undefined; @@ -96,7 +99,7 @@ export const SystemPromptEditorComponent: React.FC = ({ >((acc, [key, conversation]) => { acc[key] = { ...conversation, - ...getApiConfig({ + ...getConversationApiConfig({ allSystemPrompts: systemPromptSettings, connectors, conversation, @@ -174,7 +177,7 @@ export const SystemPromptEditorComponent: React.FC = ({ if (convo.apiConfig) { return { apiConfig: { - ...getApiConfig({ + ...getConversationApiConfig({ allSystemPrompts: systemPromptSettings, connectors, conversation: convo, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.test.tsx index 3892383b3d584..be9e33f615e4b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.test.tsx @@ -25,6 +25,7 @@ const setConversationSettings = jest.fn().mockImplementation((fn) => { }); const testProps = { + connectors: [], conversationSettings: { [welcomeConvo.title]: welcomeConvo, }, 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 bef3d744b544d..9973d2d2695b5 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 @@ -11,7 +11,7 @@ import { AIConnector } from '../../../../connectorland/connector_selector'; import { BadgesColumn } from '../../../common/components/assistant_settings_management/badges'; import { RowActions } from '../../../common/components/assistant_settings_management/row_actions'; import { Prompt } from '../../../types'; -import { getApiConfig, getDefaultSystemPrompt } from '../../../use_conversation/helpers'; +import { getConversationApiConfig, getDefaultSystemPrompt } from '../../../use_conversation/helpers'; import * as i18n from './translations'; import { getSelectedConversations } from './utils'; @@ -86,7 +86,7 @@ export const useSystemPromptTable = () => { }); acc[key] = { ...conversation, - ...getApiConfig({ + ...getConversationApiConfig({ allSystemPrompts: systemPromptSettings, connectors, conversation, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx index 2632a66ac7f5a..7fb2c9760fc7b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.test.tsx @@ -10,7 +10,7 @@ import { fireEvent, render } from '@testing-library/react'; import { QuickPrompts } from './quick_prompts'; import { TestProviders } from '../../mock/test_providers/test_providers'; import { MOCK_QUICK_PROMPTS } from '../../mock/quick_prompt'; -import { QUICK_PROMPTS_TAB } from '../settings/assistant_settings'; +import { QUICK_PROMPTS_TAB } from '../settings/const'; const setInput = jest.fn(); const setIsSettingsModalVisible = jest.fn(); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx index 63e57424fa49b..7d08d20f432b9 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompts.tsx @@ -20,7 +20,7 @@ import { css } from '@emotion/react'; import { QuickPrompt } from '../../..'; import * as i18n from './translations'; import { useAssistantContext } from '../../assistant_context'; -import { QUICK_PROMPTS_TAB } from '../settings/assistant_settings'; +import { QUICK_PROMPTS_TAB } from '../settings/const'; export const KNOWLEDGE_BASE_CATEGORY = 'knowledge-base'; 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 08a88f4bc0af0..85dda40cf4d94 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 @@ -93,7 +93,7 @@ export const getDefaultSystemPrompt = ({ return conversationSystemPrompt ?? defaultNewSystemPrompt; }; -export const getApiConfig = ({ +export const getConversationApiConfig = ({ allSystemPrompts, conversation, connectors, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_missing_callout/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_missing_callout/index.tsx index 8cecb7d6332e3..8853ca0a67d33 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_missing_callout/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_missing_callout/index.tsx @@ -13,8 +13,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import * as i18n from '../translations'; import { useAssistantContext } from '../../assistant_context'; -import { CONVERSATIONS_TAB } from '../../assistant/settings/assistant_settings'; import { ConnectorButton } from '../connector_button'; +import { CONVERSATIONS_TAB } from '../../assistant/settings/const'; interface Props { isConnectorConfigured: boolean; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx index 1b3a5183bd208..17e977fdbf80f 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx @@ -49,6 +49,7 @@ export const TestProvidersComponent: React.FC = ({ }); const mockGetComments = jest.fn(() => []); const mockHttp = httpServiceMock.createStartContract({ basePath: '/test' }); + const mockNavigateToApp = jest.fn(); const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -78,6 +79,7 @@ export const TestProvidersComponent: React.FC = ({ getComments={mockGetComments} http={mockHttp} baseConversations={{}} + navigateToApp={mockNavigateToApp} {...providerContext} > {children} From d229af8a3a4670880a9660a2da6a0cc9c044a37f Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sat, 22 Jun 2024 22:27:10 +0100 Subject: [PATCH 15/37] unit tests --- .../use_system_prompt_table.tsx | 5 +- .../use_conversation/helpers.test.ts | 283 ++++++++++++++++-- .../assistant/use_conversation/helpers.ts | 13 + 3 files changed, 274 insertions(+), 27 deletions(-) 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 9973d2d2695b5..ece150281709b 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 @@ -11,7 +11,10 @@ import { AIConnector } from '../../../../connectorland/connector_selector'; import { BadgesColumn } from '../../../common/components/assistant_settings_management/badges'; import { RowActions } from '../../../common/components/assistant_settings_management/row_actions'; import { Prompt } from '../../../types'; -import { getConversationApiConfig, getDefaultSystemPrompt } from '../../../use_conversation/helpers'; +import { + getConversationApiConfig, + getDefaultSystemPrompt, +} from '../../../use_conversation/helpers'; import * as i18n from './translations'; import { getSelectedConversations } from './utils'; 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 f348049eec8b6..82867e485b2b5 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 @@ -4,8 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common'; -import { analyzeMarkdown, getDefaultSystemPrompt } from './helpers'; +import { + analyzeMarkdown, + getConversationApiConfig, + getDefaultNewSystemPrompt, + getDefaultSystemPrompt, +} from './helpers'; +import { AIConnector } from '../../connectorland/connector_selector'; import { Conversation, Prompt } from '../../..'; const tilde = '`'; @@ -54,6 +61,31 @@ ${codeDelimiter} This query will filter the events based on the condition that the ${tilde}user.name${tilde} field should exactly match the value \"9dcc9960-78cf-4ef6-9a2e-dbd5816daa60\".`; describe('useConversation helpers', () => { + const allSystemPrompts: Prompt[] = [ + { + id: '1', + content: 'Prompt 1', + name: 'Prompt 1', + promptType: 'user', + }, + { + id: '2', + content: 'Prompt 2', + name: 'Prompt 2', + promptType: 'user', + isNewConversationDefault: true, + }, + { + id: '3', + content: 'Prompt 3', + name: 'Prompt 3', + promptType: 'user', + }, + ]; + const allSystemPromptsNoDefault: Prompt[] = allSystemPrompts.filter( + ({ isNewConversationDefault }) => isNewConversationDefault !== true + ); + describe('analyzeMarkdown', () => { it('should identify dsl Query successfully.', () => { const result = analyzeMarkdown(markDownWithDSLQuery); @@ -65,31 +97,27 @@ describe('useConversation helpers', () => { }); }); + describe('getDefaultNewSystemPrompt', () => { + test('should return the default (starred) isNewConversationDefault system prompt', () => { + const result = getDefaultNewSystemPrompt(allSystemPrompts); + + expect(result).toEqual(allSystemPrompts[1]); + }); + + test('should return the first prompt if default new system prompt do not exist', () => { + const result = getDefaultNewSystemPrompt(allSystemPromptsNoDefault); + + expect(result).toEqual(allSystemPromptsNoDefault[0]); + }); + + test('should return undefined if default (starred) isNewConversationDefault system prompt does not exist and there are no system prompts', () => { + const result = getDefaultNewSystemPrompt([]); + + expect(result).toEqual(undefined); + }); + }); + describe('getDefaultSystemPrompt', () => { - const allSystemPrompts: Prompt[] = [ - { - id: '1', - content: 'Prompt 1', - name: 'Prompt 1', - promptType: 'user', - }, - { - id: '2', - content: 'Prompt 2', - name: 'Prompt 2', - promptType: 'user', - isNewConversationDefault: true, - }, - { - id: '3', - content: 'Prompt 3', - name: 'Prompt 3', - promptType: 'user', - }, - ]; - const allSystemPromptsNoDefault: Prompt[] = allSystemPrompts.filter( - ({ isNewConversationDefault }) => isNewConversationDefault !== true - ); const conversation: Conversation = { apiConfig: { connectorId: '123', @@ -102,7 +130,6 @@ describe('useConversation helpers', () => { replacements: {}, title: '1', }; - test('should return the conversation system prompt if it exists', () => { const result = getDefaultSystemPrompt({ allSystemPrompts, conversation }); @@ -208,3 +235,207 @@ describe('useConversation helpers', () => { }); }); }); + +describe('getConversationApiConfig', () => { + const allSystemPrompts: Prompt[] = [ + { + id: '1', + content: 'Prompt 1', + name: 'Prompt 1', + promptType: 'user', + }, + { + id: '2', + content: 'Prompt 2', + name: 'Prompt 2', + promptType: 'user', + isNewConversationDefault: true, + }, + { + id: '3', + content: 'Prompt 3', + name: 'Prompt 3', + promptType: 'user', + }, + ]; + + const conversation: Conversation = { + apiConfig: { + connectorId: '123', + actionTypeId: '.gen-ai', + defaultSystemPromptId: '2', + }, + 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({ + apiConfig: { + connectorId: '123', + actionTypeId: '.gen-ai', + provider: OpenAiProviderType.OpenAi, + defaultSystemPromptId: '2', + }, + }); + }); + + 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, + conversation: conversationWithMissingConnector, + connectors, + defaultConnector, + }); + + expect(result).toEqual({ + apiConfig: { + connectorId: '456', + actionTypeId: '.gen-ai', + provider: OpenAiProviderType.AzureAi, + defaultSystemPromptId: '2', + }, + }); + }); + + 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', + actionTypeId: '.gen-ai', + provider: OpenAiProviderType.OpenAi, + defaultSystemPromptId: '2', + }, + }); + }); + + 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', + }, + }); + }); + + test('should return the first system prompt if both conversation system prompt and default new system prompt do not exist', () => { + const allSystemPromptsNoDefault: Prompt[] = allSystemPrompts.filter( + ({ isNewConversationDefault }) => isNewConversationDefault !== true + ); + + const conversationWithoutSystemPrompt: Conversation = { + ...conversation, + apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' }, + }; + + const result = getConversationApiConfig({ + allSystemPrompts: allSystemPromptsNoDefault, + conversation: conversationWithoutSystemPrompt, + connectors, + defaultConnector, + }); + + expect(result).toEqual({ + apiConfig: { + connectorId: '123', + actionTypeId: '.gen-ai', + provider: OpenAiProviderType.OpenAi, + defaultSystemPromptId: '1', + }, + }); + }); + + test('should return undefined system prompt if conversation system prompt does not exist within all system prompts', () => { + const allSystemPromptsNoDefault: Prompt[] = allSystemPrompts.filter( + ({ isNewConversationDefault }) => isNewConversationDefault !== true + ); + + const conversationWithoutSystemPrompt: Conversation = { + ...conversation, + apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' }, + id: '4', // this id does not exist within allSystemPrompts + }; + + const result = getConversationApiConfig({ + allSystemPrompts: allSystemPromptsNoDefault, + conversation: conversationWithoutSystemPrompt, + connectors, + defaultConnector, + }); + + expect(result).toEqual({ + apiConfig: { + connectorId: '123', + actionTypeId: '.gen-ai', + provider: OpenAiProviderType.OpenAi, + defaultSystemPromptId: '1', + }, + }); + }); +}); 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 85dda40cf4d94..8012da1b4c90f 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 @@ -69,6 +69,11 @@ export const analyzeMarkdown = (markdown: string): CodeBlockDetails[] => { return result; }; +/** + * Returns the default system prompt + * + * @param allSystemPrompts All available System Prompts + */ export const getDefaultNewSystemPrompt = (allSystemPrompts: Prompt[]) => allSystemPrompts.find((prompt) => prompt.isNewConversationDefault) ?? allSystemPrompts?.[0]; @@ -93,6 +98,14 @@ export const getDefaultSystemPrompt = ({ return conversationSystemPrompt ?? defaultNewSystemPrompt; }; +/** + * Returns the API config for a conversation + * + * @param allSystemPrompts All available System Prompts + * @param conversation Conversation to get the API config for + * @param connectors All available connectors + * @param defaultConnector Default connector to use + */ export const getConversationApiConfig = ({ allSystemPrompts, conversation, From 6cd17784fbd17d54386ce11bcdcdf82ee9bc5709 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sun, 23 Jun 2024 11:40:09 +0100 Subject: [PATCH 16/37] types --- .../conversation_settings_management/index.tsx | 12 +++++++++++- .../system_prompt_settings_management/index.tsx | 5 +++-- .../quick_prompt_settings_management/index.tsx | 5 +++-- .../impl/assistant/settings/const.ts | 3 +++ .../mock/test_providers/test_providers.tsx | 2 ++ 5 files changed, 22 insertions(+), 5 deletions(-) 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 f6d98f6e11f1e..04a5398987142 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 @@ -24,6 +24,7 @@ import { Flyout } from '../../common/components/assistant_settings_management/fl import { CANCEL, DELETE } from '../../settings/translations'; import { ConversationSettingsEditor } from '../conversation_settings/conversation_settings_editor'; import { useConversationChanged } from '../conversation_settings/use_conversation_changed'; +import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from '../../settings/const'; interface Props { actionTypeRegistry: ActionTypeRegistryContract; @@ -179,6 +180,15 @@ const ConversationSettingsManagementComponent: React.FC = ({ [deletedConversation?.title] ); + const pagination = useMemo( + () => ({ + pageIndex: DEFAULT_PAGE_INDEX, + pageSize: DEFAULT_PAGE_SIZE, + totalItemCount: conversationOptions.length, + }), + [conversationOptions.length] + ); + if (!conversationsLoaded) { return null; } @@ -195,7 +205,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ 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 7d30aee9041be..96617bd35fb30 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 @@ -20,6 +20,7 @@ import { Conversation, ConversationsBulkActions } from '../../../../..'; import { AIConnector } from '../../../../connectorland/connector_selector'; import { Flyout } from '../../../common/components/assistant_settings_management/flyout'; import { useFlyoutModalVisibility } from '../../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; +import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from '../../../settings/const'; import { CANCEL, DELETE } from '../../../settings/translations'; import { Prompt } from '../../../types'; import { SystemPromptEditor } from '../system_prompt_modal/system_prompt_editor'; @@ -149,8 +150,8 @@ const SystemPromptSettingsManagementComponent = ({ const pagination = useMemo( () => ({ - pageIndex: 0, - pageSize: 25, + pageIndex: DEFAULT_PAGE_INDEX, + pageSize: DEFAULT_PAGE_SIZE, totalItemCount: systemPromptSettings.length, }), [systemPromptSettings.length] 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 297da47f1d9ef..fd0cc1e990168 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 @@ -22,6 +22,7 @@ import { Flyout } from '../../common/components/assistant_settings_management/fl import { CANCEL, DELETE } from '../../settings/translations'; import { useQuickPromptEditor } from '../quick_prompt_settings/use_quick_prompt_editor'; import { useQuickPromptTable } from './use_quick_prompt_table'; +import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from '../../settings/const'; interface Props { onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; @@ -114,8 +115,8 @@ const QuickPromptSettingsManagementComponent = ({ const pagination = useMemo( () => ({ - pageIndex: 0, - pageSize: 25, + pageIndex: DEFAULT_PAGE_INDEX, + pageSize: DEFAULT_PAGE_SIZE, totalItemCount: quickPromptSettings.length, }), [quickPromptSettings.length] diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts index f7dde86d96f77..8bd5542612e8e 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts @@ -11,3 +11,6 @@ export const SYSTEM_PROMPTS_TAB = 'SYSTEM_PROMPTS_TAB' as const; export const ANONYMIZATION_TAB = 'ANONYMIZATION_TAB' as const; export const KNOWLEDGE_BASE_TAB = 'KNOWLEDGE_BASE_TAB' as const; export const EVALUATION_TAB = 'EVALUATION_TAB' as const; + +export const DEFAULT_PAGE_SIZE = 25; +export const DEFAULT_PAGE_INDEX = 0; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx index ac3cfea820df7..a4e6d39e720ec 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx @@ -31,6 +31,7 @@ export const TestProvidersComponent: React.FC = ({ children, isILMAvailab const actionTypeRegistry = actionTypeRegistryMock.create(); const mockGetComments = jest.fn(() => []); const mockHttp = httpServiceMock.createStartContract({ basePath: '/test' }); + const mockNavigateToApp = jest.fn(); const mockTelemetryEvents = { reportDataQualityIndexChecked: jest.fn(), reportDataQualityCheckAllCompleted: jest.fn(), @@ -71,6 +72,7 @@ export const TestProvidersComponent: React.FC = ({ children, isILMAvailab getComments={mockGetComments} http={mockHttp} baseConversations={{}} + navigateToApp={mockNavigateToApp} > Date: Sun, 23 Jun 2024 12:13:31 +0100 Subject: [PATCH 17/37] pending changes --- .../impl/assistant/settings/assistant_settings_management.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c177bfe3da52d..9446f3d0cde18 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 @@ -271,7 +271,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( setConversationSettings={setConversationSettings} setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} conversationsSettingsBulkActions={conversationsSettingsBulkActions} - setUpdatedSystemPromptSettings={handleChange(setUpdatedSystemPromptSettings)} + setUpdatedSystemPromptSettings={setUpdatedSystemPromptSettings} onCancelClick={onCancelClick} /> )} From 7fe3085fe73150597fef600d682646803d7a88c7 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sun, 23 Jun 2024 19:16:54 +0100 Subject: [PATCH 18/37] types --- .../public/common/mock/mock_assistant_provider.tsx | 2 ++ .../rule_execution_status/rule_status_failed_callout.test.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index 63ed6b5cac7f6..b9a83fd280b10 100644 --- a/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -27,6 +27,7 @@ export const MockAssistantProviderComponent: React.FC = ({ }) => { const actionTypeRegistry = actionTypeRegistryMock.create(); const mockHttp = httpServiceMock.createStartContract({ basePath: '/test' }); + const mockNavigateToApp = jest.fn(); const defaultAssistantAvailability: AssistantAvailability = { hasAssistantPrivilege: false, hasConnectorsAllPrivilege: true, @@ -47,6 +48,7 @@ export const MockAssistantProviderComponent: React.FC = ({ }} getComments={jest.fn(() => [])} http={mockHttp} + navigateToApp={mockNavigateToApp} baseConversations={BASE_SECURITY_CONVERSATIONS} > {children} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx index 45291cb5d0315..b5e7737be38f7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx @@ -28,6 +28,7 @@ const MESSAGE = 'This rule is attempting to query data but...'; const actionTypeRegistry = actionTypeRegistryMock.create(); const mockGetComments = jest.fn(() => []); const mockHttp = httpServiceMock.createStartContract({ basePath: '/test' }); +const mockNavigationToApp = jest.fn(); const mockAssistantAvailability: AssistantAvailability = { hasAssistantPrivilege: false, hasConnectorsAllPrivilege: true, @@ -61,6 +62,7 @@ const ContextWrapper: FC> = ({ children }) => ( }} getComments={mockGetComments} http={mockHttp} + navigateToApp={mockNavigationToApp} baseConversations={BASE_SECURITY_CONVERSATIONS} > {children} From 0bfdeed9469cef569524cfe41f7db198553aaeae Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 24 Jun 2024 22:43:46 +0100 Subject: [PATCH 19/37] update connector and model --- .../conversation_settings_editor.tsx | 9 ++++++--- .../conversation_settings/use_conversation_changed.tsx | 3 ++- .../impl/assistant/use_conversation/helpers.ts | 4 ++++ 3 files changed, 12 insertions(+), 4 deletions(-) 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 12f13a4c9d6d0..c1accbcc5c4f3 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 @@ -65,6 +65,7 @@ export const ConversationSettingsEditor: React.FC { - const selectedConnectorId = selectedConversation?.apiConfig?.connectorId; + const selectedConnectorId: string | undefined = selectedConversation?.apiConfig?.connectorId; if (areConnectorsFetched) { return connectors?.find((c) => c.id === selectedConnectorId); } @@ -141,6 +142,7 @@ export const ConversationSettingsEditor: React.FC { const isNew = typeof c === 'string'; - + // const updatedAt = new Date(); const newSelectedConversation: Conversation | undefined = isNew ? { id: '', @@ -51,6 +51,7 @@ export const useConversationChanged = ({ category: 'assistant', messages: [], replacements: {}, + // updatedAt, ...(defaultConnector ? { apiConfig: { 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 8012da1b4c90f..264af11472a71 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 @@ -9,6 +9,7 @@ import React from 'react'; import { Prompt } from '../types'; import { Conversation } from '../../assistant_context/types'; import { AIConnector } from '../../connectorland/connector_selector'; +import { getGenAiConfig } from '../../connectorland/helpers'; export interface CodeBlockDetails { type: QueryType; @@ -119,6 +120,8 @@ export const getConversationApiConfig = ({ }) => { const connector: AIConnector | undefined = connectors?.find((c) => c.id === conversation.apiConfig?.connectorId) ?? defaultConnector; + const connectorModel = getGenAiConfig(connector)?.defaultModel; + const defaultSystemPrompt = getDefaultSystemPrompt({ allSystemPrompts, conversation, @@ -130,6 +133,7 @@ export const getConversationApiConfig = ({ actionTypeId: connector.actionTypeId, provider: connector.apiProvider, defaultSystemPromptId: defaultSystemPrompt?.id, + model: conversation?.apiConfig?.model ?? connectorModel, }, } : {}; From 5353d20748012beac709148b1aac0a103307c5b6 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 25 Jun 2024 00:06:43 +0100 Subject: [PATCH 20/37] refetch conversations --- .../conversation_settings_editor.tsx | 10 +++--- .../use_conversation_changed.tsx | 6 ++-- .../index.tsx | 17 ++++++--- .../index.tsx | 12 ++++--- .../index.tsx | 2 +- .../assistant_settings_management.tsx | 36 ++++++++++++------- .../stack_management/management_settings.tsx | 9 +++-- 7 files changed, 59 insertions(+), 33 deletions(-) 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 c1accbcc5c4f3..de48e13f4c445 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 @@ -80,7 +80,7 @@ export const ConversationSettingsEditor: React.FC { const isNew = typeof c === 'string'; - // const updatedAt = new Date(); + const updatedAt = new Date(); const newSelectedConversation: Conversation | undefined = isNew ? { id: '', @@ -51,7 +51,7 @@ export const useConversationChanged = ({ category: 'assistant', messages: [], replacements: {}, - // updatedAt, + updatedAt, ...(defaultConnector ? { apiConfig: { @@ -63,7 +63,7 @@ export const useConversationChanged = ({ } : {}), } - : c; + : { ...c, updatedAt }; if (newSelectedConversation && (isNew || newSelectedConversation.id === '')) { setConversationSettings({ 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 04a5398987142..203c5c34ce910 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 @@ -35,7 +35,7 @@ interface Props { conversationsSettingsBulkActions: ConversationsBulkActions; conversationsLoaded: boolean; defaultConnector?: AIConnector; - handleSave: () => void; + handleSave: (shouldRefetchConversation?: boolean) => void; isDisabled?: boolean; isFlyoutMode: boolean; onCancelClick: () => void; @@ -122,8 +122,14 @@ const ConversationSettingsManagementComponent: React.FC = ({ return; } closeConfirmModal(); - handleSave(); - }, [closeConfirmModal, conversationsSettingsBulkActions, handleSave]); + handleSave(true); + setConversationsSettingsBulkActions({}); + }, [ + closeConfirmModal, + conversationsSettingsBulkActions, + handleSave, + setConversationsSettingsBulkActions, + ]); const onDeleteCancelled = useCallback(() => { setDeletedConversation(null); @@ -148,8 +154,9 @@ const ConversationSettingsManagementComponent: React.FC = ({ const onSaveConfirmed = useCallback(() => { closeEditFlyout(); - handleSave(); - }, [closeEditFlyout, handleSave]); + handleSave(true); + setConversationsSettingsBulkActions({}); + }, [closeEditFlyout, handleSave, setConversationsSettingsBulkActions]); const columns = useMemo( () => 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 96617bd35fb30..e1c7220651aa1 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 @@ -43,7 +43,7 @@ interface Props { React.SetStateAction >; defaultConnector?: AIConnector; - handleSave: () => void; + handleSave: (shouldRefetchConversation?: boolean) => void; onCancelClick: () => void; } @@ -110,8 +110,9 @@ const SystemPromptSettingsManagementComponent = ({ const onDeleteConfirmed = useCallback(() => { closeConfirmModal(); - handleSave(); - }, [closeConfirmModal, handleSave]); + handleSave(true); + setConversationsSettingsBulkActions({}); + }, [closeConfirmModal, handleSave, setConversationsSettingsBulkActions]); const onSaveCancelled = useCallback(() => { closeFlyout(); @@ -120,8 +121,9 @@ const SystemPromptSettingsManagementComponent = ({ const onSaveConfirmed = useCallback(() => { closeFlyout(); - handleSave(); - }, [closeFlyout, handleSave]); + handleSave(true); + setConversationsSettingsBulkActions({}); + }, [closeFlyout, handleSave, setConversationsSettingsBulkActions]); const confirmationTitle = useMemo( () => 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 fd0cc1e990168..2464e07c5e367 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 @@ -29,7 +29,7 @@ interface Props { quickPromptSettings: QuickPrompt[]; selectedQuickPrompt: QuickPrompt | undefined; setUpdatedQuickPromptSettings: React.Dispatch>; - handleSave: () => void; + handleSave: (shouldRefetchConversation?: boolean) => void; onCancelClick: () => void; } 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 9446f3d0cde18..fa6055e13962b 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 @@ -44,6 +44,7 @@ interface Props { conversationsLoaded: boolean; selectedConversation: Conversation; isFlyoutMode: boolean; + refetchConversations: () => void; } /** @@ -52,10 +53,11 @@ interface Props { */ export const AssistantSettingsManagement: React.FC = React.memo( ({ - selectedConversation: defaultSelectedConversation, conversations, conversationsLoaded, isFlyoutMode, + refetchConversations, + selectedConversation: defaultSelectedConversation, }) => { const { actionTypeRegistry, @@ -153,14 +155,24 @@ export const AssistantSettingsManagement: React.FC = React.memo( } }, [selectedSystemPrompt, systemPromptSettings]); - const handleSave = useCallback(() => { - saveSettings(); - toasts?.addSuccess({ - iconType: 'check', - title: i18n.SETTINGS_UPDATED_TOAST_TITLE, - }); - setHasPendingChanges(false); - }, [saveSettings, toasts]); + 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( () => [ @@ -245,17 +257,17 @@ export const AssistantSettingsManagement: React.FC = React.memo( assistantStreamingEnabled={assistantStreamingEnabled} connectors={connectors} conversationSettings={conversationSettings} - conversationsSettingsBulkActions={conversationsSettingsBulkActions} conversationsLoaded={conversationsLoaded} + conversationsSettingsBulkActions={conversationsSettingsBulkActions} defaultConnector={defaultConnector} handleSave={handleSave} isFlyoutMode={isFlyoutMode} onCancelClick={onCancelClick} + onSelectedConversationChange={onHandleSelectedConversationChange} selectedConversation={selectedConversation} setAssistantStreamingEnabled={handleChange(setUpdatedAssistantStreamingEnabled)} setConversationSettings={setConversationSettings} setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} - onSelectedConversationChange={onHandleSelectedConversationChange} /> )} {selectedSettingsTab === SYSTEM_PROMPTS_TAB && ( @@ -321,7 +333,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( size="s" type="submit" data-test-subj="save-button" - onClick={handleSave} + onClick={onSaveButtonClicked} iconType="check" fill > 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 728038bb51cea..525315ca36eb2 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 @@ -34,7 +34,11 @@ export const ManagementSettings = React.memo(() => { mergeBaseWithPersistedConversations(baseConversations, conversationsData), [baseConversations] ); - const { data: conversations, isFetched: conversationsLoaded } = useFetchCurrentUserConversations({ + const { + data: conversations, + isFetched: conversationsLoaded, + refetch: refetchConversations, + } = useFetchCurrentUserConversations({ http, onFetch: onFetchedConversations, isAssistantEnabled, @@ -52,10 +56,11 @@ export const ManagementSettings = React.memo(() => { if (conversations) { return ( ); } From 559478a77d5ad082b9985088957d5e066954ff28 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 25 Jun 2024 00:54:13 +0100 Subject: [PATCH 21/37] fix create conversations from system prompt tab --- .../system_prompt_modal/system_prompt_editor.tsx | 4 +--- .../use_system_prompt_table.tsx | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) 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 605dcef717106..1b13fe141c46d 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 @@ -195,14 +195,13 @@ export const SystemPromptEditorComponent: React.FC = ({ ? { create: { ...(updatedConversationsSettingsBulkActions.create ?? {}), - [convo.id]: { + [convo.title]: { ...convo, ...(convo.apiConfig ? getApiConfigWithSelectedPrompt() : {}), }, }, } : {}; - const updateOperation = convo.id !== '' ? { @@ -217,7 +216,6 @@ export const SystemPromptEditorComponent: React.FC = ({ }, } : {}; - updatedConversationsSettingsBulkActions = { ...updatedConversationsSettingsBulkActions, ...createOperation, 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 ece150281709b..d7d4788f7447f 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,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiLink } from '@elastic/eui'; +import { EuiIcon, EuiLink } from '@elastic/eui'; import React from 'react'; import { Conversation } from '../../../../assistant_context/types'; import { AIConnector } from '../../../../connectorland/connector_selector'; @@ -15,6 +15,7 @@ import { getConversationApiConfig, getDefaultSystemPrompt, } from '../../../use_conversation/helpers'; +import { SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION } from '../system_prompt_modal/translations'; import * as i18n from './translations'; import { getSelectedConversations } from './utils'; @@ -38,7 +39,16 @@ export const useSystemPromptTable = () => { truncateText: { lines: 3 }, render: (prompt: SystemPromptTableItem) => prompt?.name ? ( - onEditActionClicked(prompt)}>{prompt?.name} + onEditActionClicked(prompt)}> + {prompt?.name} + {prompt.isNewConversationDefault && ( + + )} + ) : null, }, { From 3a3f4bcf2d244eb39e4ebf0c0c957d436e4ed94b Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 25 Jun 2024 12:46:59 +0100 Subject: [PATCH 22/37] fix prompts --- .../badges/index.tsx | 8 ++--- .../conversation_settings_editor.tsx | 4 +-- .../system_prompt_column.tsx | 4 +-- .../system_prompt_editor.tsx | 8 ++--- .../use_system_prompt_table.tsx | 11 +++--- .../utils.tsx | 10 +++--- .../index.tsx | 4 +++ .../use_quick_prompt_table.tsx | 18 ++++++++-- .../assistant_settings_management.tsx | 2 ++ .../assistant/use_conversation/helpers.ts | 35 ++++++++++++++++--- 10 files changed, 73 insertions(+), 31 deletions(-) diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx index 12cb3f49b92b0..3a93da1e6f72a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/badges/index.tsx @@ -8,16 +8,16 @@ import { EuiBadge } from '@elastic/eui'; import React from 'react'; -export const BadgesColumn: React.FC<{ items: string[] | null | undefined }> = React.memo( - ({ items }) => +export const BadgesColumn: React.FC<{ items: string[] | null | undefined; prefix: string }> = + React.memo(({ items, prefix }) => items && items.length > 0 ? (
{items.map((c, idx) => ( - + {c} ))}
) : null -); + ); BadgesColumn.displayName = 'BadgesColumn'; 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 de48e13f4c445..8fc3d8f643660 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 @@ -65,7 +65,7 @@ export const ConversationSettingsEditor: React.FC = ({ allSystemPrompts, conversa ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId ); - const defaultSystemPrompt = getDefaultSystemPrompt({ + const defaultSystemPrompt = getInitialDefaultSystemPrompt({ allSystemPrompts, conversation, }); 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 1b13fe141c46d..6e504d95aef74 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 @@ -28,10 +28,7 @@ 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, - getDefaultNewSystemPrompt, -} from '../../../use_conversation/helpers'; +import { getConversationApiConfig } from '../../../use_conversation/helpers'; interface Props { connectors: AIConnector[] | undefined; @@ -137,7 +134,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 - getDefaultNewSystemPrompt(systemPromptSettings).id + systemPromptSettings?.[0].id : // leave it as it is .. if that conversation was neither added nor removed. convo.apiConfig?.defaultSystemPromptId; @@ -216,6 +213,7 @@ export const SystemPromptEditorComponent: React.FC = ({ }, } : {}; + updatedConversationsSettingsBulkActions = { ...updatedConversationsSettingsBulkActions, ...createOperation, 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 d7d4788f7447f..f19dd987e32a9 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 @@ -13,7 +13,7 @@ import { RowActions } from '../../../common/components/assistant_settings_manage import { Prompt } from '../../../types'; import { getConversationApiConfig, - getDefaultSystemPrompt, + getInitialDefaultSystemPrompt, } from '../../../use_conversation/helpers'; import { SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION } from '../system_prompt_modal/translations'; import * as i18n from './translations'; @@ -52,9 +52,10 @@ export const useSystemPromptTable = () => { ) : null, }, { - field: 'defaultConversations', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, - render: (defaultConversations: string[]) => , + render: ({ defaultConversations, id }: SystemPromptTableItem) => ( + + ), }, /* TODO: enable when createdAt is added { @@ -93,10 +94,11 @@ export const useSystemPromptTable = () => { const conversationsWithApiConfig = Object.entries( conversationSettings ).reduce((acc, [key, conversation]) => { - const defaultSystemPrompt = getDefaultSystemPrompt({ + const defaultSystemPrompt = getInitialDefaultSystemPrompt({ allSystemPrompts: systemPromptSettings, conversation, }); + acc[key] = { ...conversation, ...getConversationApiConfig({ @@ -109,7 +111,6 @@ export const useSystemPromptTable = () => { }; return acc; }, {}); - return systemPromptSettings.map((systemPrompt) => { return { ...systemPrompt, 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 fc9bbaed9bba0..5fde200db9b17 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 @@ -7,7 +7,6 @@ import { Conversation } from '../../../../assistant_context/types'; import { Prompt } from '../../../types'; -import { getDefaultSystemPrompt } from '../../../use_conversation/helpers'; export const getSelectedConversations = ( allSystemPrompts: Prompt[], @@ -15,10 +14,9 @@ export const getSelectedConversations = ( systemPromptId: string ) => { return Object.values(conversationSettings).filter((conversation) => { - const defaultSystemPrompt = getDefaultSystemPrompt({ - allSystemPrompts, - conversation, - }); - return defaultSystemPrompt?.id === systemPromptId; + const conversationSystemPrompt = allSystemPrompts.find( + (prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId + ); + return conversationSystemPrompt?.id === systemPromptId; }); }; 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 2464e07c5e367..26dd2de8c4d97 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 @@ -23,8 +23,10 @@ import { CANCEL, DELETE } from '../../settings/translations'; import { useQuickPromptEditor } from '../quick_prompt_settings/use_quick_prompt_editor'; import { useQuickPromptTable } from './use_quick_prompt_table'; import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from '../../settings/const'; +import { PromptContextTemplate } from '../../prompt_context/types'; interface Props { + basePromptContexts: PromptContextTemplate[]; onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; quickPromptSettings: QuickPrompt[]; selectedQuickPrompt: QuickPrompt | undefined; @@ -34,6 +36,7 @@ interface Props { } const QuickPromptSettingsManagementComponent = ({ + basePromptContexts, onSelectedQuickPromptChange, quickPromptSettings, selectedQuickPrompt, @@ -101,6 +104,7 @@ const QuickPromptSettingsManagementComponent = ({ const { getColumns } = useQuickPromptTable(); const columns = getColumns({ + basePromptContexts, onEditActionClicked, onDeleteActionClicked, }); 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 90f7d7f780ceb..8e285c4fc75fb 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 @@ -9,15 +9,18 @@ import { EuiLink } from '@elastic/eui'; import React, { useCallback } from '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 { QuickPrompt } from '../types'; import * as i18n from './translations'; export const useQuickPromptTable = () => { const getColumns = useCallback( ({ + basePromptContexts, onEditActionClicked, onDeleteActionClicked, }: { + basePromptContexts: PromptContextTemplate[]; onEditActionClicked: (prompt: QuickPrompt) => void; onDeleteActionClicked: (prompt: QuickPrompt) => void; }) => [ @@ -29,9 +32,17 @@ export const useQuickPromptTable = () => { ) : null, }, { - field: 'categories', name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CONTEXTS, - render: (categories: QuickPrompt['categories']) => , + render: (prompt: QuickPrompt) => { + const selectedPromptContexts = ( + basePromptContexts.filter((bpc) => + prompt?.categories?.some((cat) => bpc?.category === cat) + ) ?? [] + ).map((bpc) => bpc?.description); + return selectedPromptContexts ? ( + + ) : null; + }, }, /* TODO: enable when createdAt is added { @@ -43,6 +54,9 @@ export const useQuickPromptTable = () => { name: i18n.QUICK_PROMPTS_TABLE_COLUMN_ACTIONS, width: '120px', render: (prompt: QuickPrompt) => { + if (!prompt) { + return null; + } const isDeletable = !prompt.isDefault; return ( 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 fa6055e13962b..b8b1bb4640957 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 @@ -62,6 +62,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( const { actionTypeRegistry, assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled }, + basePromptContexts, http, navigateToApp, selectedSettingsTab, @@ -289,6 +290,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( )} {selectedSettingsTab === QUICK_PROMPTS_TAB && ( allSystemPrompts.find((prompt) => prompt.isNewConversationDefault) ?? allSystemPrompts?.[0]; /** - * Returns the default system prompt for a given conversation + * Returns the default system prompt for a given (New Custom) conversation * * @param allSystemPrompts All available System Prompts * @param conversation Conversation to get the default system prompt for @@ -99,6 +99,26 @@ export const getDefaultSystemPrompt = ({ return conversationSystemPrompt ?? defaultNewSystemPrompt; }; +/** + * Returns the default system prompt for an existing conversation that has never been given a system prompt + * + * @param allSystemPrompts All available System Prompts + * @param conversation Conversation to get the default system prompt for + */ +export const getInitialDefaultSystemPrompt = ({ + allSystemPrompts, + conversation, +}: { + allSystemPrompts: Prompt[]; + conversation: Conversation | undefined; +}): Prompt | undefined => { + const conversationSystemPrompt = allSystemPrompts.find( + (prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId + ); + + return conversationSystemPrompt ?? allSystemPrompts?.[0]; +}; + /** * Returns the API config for a conversation * @@ -122,10 +142,15 @@ export const getConversationApiConfig = ({ connectors?.find((c) => c.id === conversation.apiConfig?.connectorId) ?? defaultConnector; const connectorModel = getGenAiConfig(connector)?.defaultModel; - const defaultSystemPrompt = getDefaultSystemPrompt({ - allSystemPrompts, - conversation, - }); + const defaultSystemPrompt = conversation.apiConfig?.defaultSystemPromptId + ? getDefaultSystemPrompt({ + allSystemPrompts, + conversation, + }) + : getInitialDefaultSystemPrompt({ + allSystemPrompts, + conversation, + }); return connector ? { apiConfig: { From bcdd052b08fb890b2365696b791aa30121cf396d Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 25 Jun 2024 13:18:22 +0100 Subject: [PATCH 23/37] rm updatedAt --- .../conversation_settings/conversation_settings_editor.tsx | 3 --- .../conversation_settings/use_conversation_changed.tsx | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) 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 8fc3d8f643660..1e854674d458c 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 @@ -65,7 +65,6 @@ export const ConversationSettingsEditor: React.FC { const isNew = typeof c === 'string'; - const updatedAt = new Date(); const newSelectedConversation: Conversation | undefined = isNew ? { id: '', @@ -51,7 +50,6 @@ export const useConversationChanged = ({ category: 'assistant', messages: [], replacements: {}, - updatedAt, ...(defaultConnector ? { apiConfig: { @@ -63,7 +61,7 @@ export const useConversationChanged = ({ } : {}), } - : { ...c, updatedAt }; + : c; if (newSelectedConversation && (isNew || newSelectedConversation.id === '')) { setConversationSettings({ From cdfe57009923fc3aeb255c50cde6334c2228caad Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 25 Jun 2024 14:22:17 +0100 Subject: [PATCH 24/37] styling --- .../assistant_settings_management/flyout/index.tsx | 4 ++-- .../conversation_streaming_switch.tsx | 2 +- .../use_conversations_table.tsx | 4 ++-- .../use_system_prompt_table.tsx | 8 ++++++-- .../use_quick_prompt_table.tsx | 8 ++++++-- .../assistant/settings/assistant_settings_management.tsx | 7 +++++-- .../connectorland/connector_settings_management/index.tsx | 4 ++-- 7 files changed, 24 insertions(+), 13 deletions(-) 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 d82dc77f7a505..0340667241d0a 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 @@ -48,7 +48,7 @@ const FlyoutComponent: React.FC = ({ = ({ = ({ } > {STREAMING_HELP_TEXT_TITLE}} + label={{STREAMING_HELP_TEXT_TITLE}} checked={assistantStreamingEnabled} onChange={(e) => setAssistantStreamingEnabled(e.target.checked)} compressed={compressed} 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 b501abfef970d..9c134ea4d5b01 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 @@ -51,7 +51,7 @@ export const useConversationsTable = () => { }, { name: i18n.CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT, - align: 'center', + align: 'left', render: (conversation: ConversationTableItem) => ( ), @@ -59,7 +59,7 @@ export const useConversationsTable = () => { { field: 'actionType', name: i18n.CONVERSATIONS_TABLE_COLUMN_CONNECTOR, - align: 'center', + align: 'left', render: (actionType: ConversationTableItem['actionType']) => actionType ? {actionType} : null, }, 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 f19dd987e32a9..1b8b40ab617ce 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,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiIcon, EuiLink } from '@elastic/eui'; +import { EuiBasicTableColumn, EuiIcon, EuiLink } from '@elastic/eui'; import React from 'react'; import { Conversation } from '../../../../assistant_context/types'; import { AIConnector } from '../../../../connectorland/connector_selector'; @@ -33,8 +33,9 @@ export const useSystemPromptTable = () => { }: { onEditActionClicked: (prompt: SystemPromptTableItem) => void; onDeleteActionClicked: (prompt: SystemPromptTableItem) => void; - }) => [ + }): Array> => [ { + align: 'left', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_NAME, truncateText: { lines: 3 }, render: (prompt: SystemPromptTableItem) => @@ -52,6 +53,7 @@ export const useSystemPromptTable = () => { ) : null, }, { + align: 'left', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, render: ({ defaultConversations, id }: SystemPromptTableItem) => ( @@ -59,11 +61,13 @@ export const useSystemPromptTable = () => { }, /* TODO: enable when createdAt is added { + align: 'left', field: 'createdAt', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_CREATED_ON, }, */ { + align: 'center', name: 'Actions', width: '120px', render: (prompt: SystemPromptTableItem) => { 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 8e285c4fc75fb..2d58ddb902bbe 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,7 +5,7 @@ * 2.0. */ -import { EuiLink } from '@elastic/eui'; +import { EuiBasicTableColumn, EuiLink } from '@elastic/eui'; import React, { useCallback } from 'react'; import { BadgesColumn } from '../../common/components/assistant_settings_management/badges'; import { RowActions } from '../../common/components/assistant_settings_management/row_actions'; @@ -23,8 +23,9 @@ export const useQuickPromptTable = () => { basePromptContexts: PromptContextTemplate[]; onEditActionClicked: (prompt: QuickPrompt) => void; onDeleteActionClicked: (prompt: QuickPrompt) => void; - }) => [ + }): Array> => [ { + align: 'left', name: i18n.QUICK_PROMPTS_TABLE_COLUMN_NAME, render: (prompt: QuickPrompt) => prompt?.title ? ( @@ -32,6 +33,7 @@ export const useQuickPromptTable = () => { ) : null, }, { + align: 'left', name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CONTEXTS, render: (prompt: QuickPrompt) => { const selectedPromptContexts = ( @@ -46,11 +48,13 @@ export const useQuickPromptTable = () => { }, /* TODO: enable when createdAt is added { + align: 'left', field: 'createdAt', name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CREATED_AT, }, */ { + align: 'center', name: i18n.QUICK_PROMPTS_TABLE_COLUMN_ACTIONS, width: '120px', render: (prompt: QuickPrompt) => { 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 b8b1bb4640957..cbefa6558aa54 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 @@ -12,6 +12,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPageTemplate, + useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/react'; @@ -78,7 +79,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]); const [hasPendingChanges, setHasPendingChanges] = useState(false); - + const { euiTheme } = useEuiTheme(); const { conversationSettings, setConversationSettings, @@ -242,10 +243,12 @@ export const AssistantSettingsManagement: React.FC = React.memo( paddingSize="none" /> {selectedSettingsTab === CONNECTORS_TAB && ( 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 34dd24e80c99f..ee97a644f50e0 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 @@ -36,10 +36,10 @@ const ConnectorsSettingsManagementComponent: React.FC = () => { return ( - +

{i18n.CONNECTOR_SETTINGS_MANAGEMENT_TITLE}

- + {i18n.CONNECTOR_SETTINGS_MANAGEMENT_DESCRIPTION} From d83882154a442b95846b2cfa124336c2fa73ea99 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 25 Jun 2024 14:25:36 +0100 Subject: [PATCH 25/37] types --- .../assistant/settings/assistant_settings_management.test.tsx | 1 + 1 file changed, 1 insertion(+) 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 7816381cc7e07..3b34b3467aa84 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 @@ -64,6 +64,7 @@ const testProps = { conversations: {}, anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] }, refetchAnonymizationFieldsResults: jest.fn(), + refetchConversations: jest.fn(), }; jest.mock('../../assistant_context'); From 842e9e01a6f6adba483ba4debd77747785ede238 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 25 Jun 2024 14:31:57 +0100 Subject: [PATCH 26/37] revert ui-action changes --- .../components/delete_modal_confirmation.tsx | 30 ++++++++----------- .../common/get_delete_modal_confirmation.tsx | 29 ------------------ .../triggers_actions_ui/public/mocks.ts | 7 ----- .../triggers_actions_ui/public/plugin.ts | 13 -------- 4 files changed, 13 insertions(+), 66 deletions(-) delete mode 100644 x-pack/plugins/triggers_actions_ui/public/common/get_delete_modal_confirmation.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx index f8a777cda589a..a0334c6ff7694 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx @@ -17,7 +17,18 @@ import { CANCEL_BUTTON_TEXT, } from '../sections/rules_list/translations'; -export interface DeleteModalConfirmationProps { +export const DeleteModalConfirmation = ({ + idsToDelete, + apiDeleteCall, + onDeleted, + onCancel, + onErrors, + singleTitle, + multipleTitle, + showWarningText, + warningText, + setIsLoadingState, +}: { idsToDelete: string[]; apiDeleteCall: ({ ids, @@ -34,20 +45,7 @@ export interface DeleteModalConfirmationProps { setIsLoadingState: (isLoading: boolean) => void; showWarningText?: boolean; warningText?: string; -} - -export const DeleteModalConfirmation = ({ - idsToDelete, - apiDeleteCall, - onDeleted, - onCancel, - onErrors, - singleTitle, - multipleTitle, - showWarningText, - warningText, - setIsLoadingState, -}: DeleteModalConfirmationProps) => { +}) => { const [deleteModalFlyoutVisible, setDeleteModalVisibility] = useState(false); useEffect(() => { @@ -104,5 +102,3 @@ export const DeleteModalConfirmation = ({ ); }; -// eslint-disable-next-line import/no-default-export -export { DeleteModalConfirmation as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_delete_modal_confirmation.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_delete_modal_confirmation.tsx deleted file mode 100644 index 2e78b09db431b..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_delete_modal_confirmation.tsx +++ /dev/null @@ -1,29 +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 { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import React from 'react'; -import { - DeleteModalConfirmation, - DeleteModalConfirmationProps, -} from '../application/components/delete_modal_confirmation'; -import { ConnectorProvider } from '../application/context/connector_context'; -import { ConnectorServices } from '../types'; - -const queryClient = new QueryClient(); - -export const getDeleteConnectorModalConfirmationLazy = ( - props: DeleteModalConfirmationProps & { connectorServices: ConnectorServices } -) => { - return ( - - - - - - ); -}; diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index 72a2329b995f8..e6d98240d2c5f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -52,8 +52,6 @@ import { getRulesSettingsLinkLazy } from './common/get_rules_settings_link'; import { AlertTableConfigRegistry } from './application/alert_table_config_registry'; import { AlertActionsProps } from './types'; import { AlertSummaryWidgetDependencies } from './application/sections/alert_summary_widget/types'; -import { getDeleteConnectorModalConfirmationLazy } from './common/get_delete_modal_confirmation'; -import { DeleteModalConfirmationProps } from './application/components/delete_modal_confirmation'; function createStartMock(): TriggersAndActionsUIPublicPluginStart { const actionTypeRegistry = new TypeRegistry(); @@ -87,11 +85,6 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart { connectorServices, }); }, - getDeleteConnectorModalConfirmation: ( - props: Omit - ) => { - return getDeleteConnectorModalConfirmationLazy({ ...props, connectorServices }); - }, getAddRuleFlyout: (props) => { return getAddRuleFlyoutLazy({ ...props, diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index a73d98275d4da..f3fd01e66856f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -98,8 +98,6 @@ import { getRulesSettingsLinkLazy } from './common/get_rules_settings_link'; import { getGlobalRuleEventLogListLazy } from './common/get_global_rule_event_log_list'; import { AlertTableConfigRegistry } from './application/alert_table_config_registry'; import { AlertSummaryWidgetDependencies } from './application/sections/alert_summary_widget/types'; -import { DeleteModalConfirmationProps } from './application/components/delete_modal_confirmation'; -import { getDeleteConnectorModalConfirmationLazy } from './common/get_delete_modal_confirmation'; export interface TriggersAndActionsUIPublicPluginSetup { actionTypeRegistry: TypeRegistry; @@ -122,9 +120,6 @@ export interface TriggersAndActionsUIPublicPluginStart { getEditConnectorFlyout: ( props: Omit ) => ReactElement; - getDeleteConnectorModalConfirmation: ( - props: Omit - ) => ReactElement; getAddRuleFlyout: < Params extends RuleTypeParams = RuleTypeParams, MetaData extends RuleTypeMetaData = RuleTypeMetaData @@ -494,14 +489,6 @@ export class Plugin connectorServices: this.connectorServices!, }); }, - getDeleteConnectorModalConfirmation: ( - props: Omit - ) => { - return getDeleteConnectorModalConfirmationLazy({ - ...props, - connectorServices: this.connectorServices!, - }); - }, getAddRuleFlyout: (props) => { return getAddRuleFlyoutLazy({ ...props, From 70ef92d97ec84bafbce64326d7bb93bb5ef1ef31 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 25 Jun 2024 19:42:57 +0100 Subject: [PATCH 27/37] avoid prompt saved automatically --- .../system_prompt_editor.tsx | 5 ++++- .../system_prompt_selector.tsx | 12 ++++++++---- .../index.tsx | 3 +++ .../quick_prompt_selector.tsx | 6 +++++- .../quick_prompt_editor.tsx | 3 +++ .../quick_prompt_settings_management/index.tsx | 11 +++++++---- .../settings/assistant_settings_management.tsx | 18 ++++++++++-------- 7 files changed, 40 insertions(+), 18 deletions(-) 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 6e504d95aef74..59b608ef68511 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 @@ -43,6 +43,7 @@ interface Props { React.SetStateAction >; defaultConnector?: AIConnector; + resetSettings?: () => void; } /** @@ -59,6 +60,7 @@ export const SystemPromptEditorComponent: React.FC = ({ conversationsSettingsBulkActions, setConversationsSettingsBulkActions, defaultConnector, + resetSettings, }) => { // Prompt const promptContent = useMemo( @@ -271,8 +273,9 @@ export const SystemPromptEditorComponent: React.FC = ({ 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 1ec1c4b721065..53b6414d05b53 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 @@ -26,11 +26,12 @@ import { SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION } from '../translations'; export const SYSTEM_PROMPT_SELECTOR_CLASSNAME = 'systemPromptSelector'; interface Props { + autoFocus?: boolean; onSystemPromptDeleted: (systemPromptTitle: string) => void; onSystemPromptSelectionChange: (systemPrompt?: Prompt | string) => void; - systemPrompts: Prompt[]; - autoFocus?: boolean; + resetSettings?: () => void; selectedSystemPrompt?: Prompt; + systemPrompts: Prompt[]; } export type SystemPromptSelectorOption = EuiComboBoxOptionOption<{ @@ -44,10 +45,11 @@ export type SystemPromptSelectorOption = EuiComboBoxOptionOption<{ export const SystemPromptSelector: React.FC = React.memo( ({ autoFocus = false, - systemPrompts, onSystemPromptDeleted, onSystemPromptSelectionChange, + resetSettings, selectedSystemPrompt, + systemPrompts, }) => { // Form options const [options, setOptions] = useState( @@ -76,6 +78,8 @@ export const SystemPromptSelector: React.FC = React.memo( const handleSelectionChange = useCallback( (systemPromptSelectorOption: SystemPromptSelectorOption[]) => { + // Reset settings on every selection change to avoid option saved automatically on settings management page + resetSettings?.(); const newSystemPrompt = systemPromptSelectorOption.length === 0 ? undefined @@ -83,7 +87,7 @@ export const SystemPromptSelector: React.FC = React.memo( systemPromptSelectorOption[0]?.label; onSystemPromptSelectionChange(newSystemPrompt); }, - [onSystemPromptSelectionChange, systemPrompts] + [onSystemPromptSelectionChange, resetSettings, systemPrompts] ); // Callback for when user types to create a new system prompt 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 e1c7220651aa1..35168fc4d8e86 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 @@ -45,6 +45,7 @@ interface Props { defaultConnector?: AIConnector; handleSave: (shouldRefetchConversation?: boolean) => void; onCancelClick: () => void; + resetSettings: () => void; } const SystemPromptSettingsManagementComponent = ({ @@ -61,6 +62,7 @@ const SystemPromptSettingsManagementComponent = ({ defaultConnector, handleSave, onCancelClick, + resetSettings, }: Props) => { const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); const { @@ -190,6 +192,7 @@ const SystemPromptSettingsManagementComponent = ({ conversationsSettingsBulkActions={conversationsSettingsBulkActions} setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} defaultConnector={defaultConnector} + resetSettings={resetSettings} />
{deleteConfirmModalVisibility && deletedPrompt?.name && ( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.tsx index 5aaff2bbc8fc9..3fb0ba17cf4bf 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.tsx @@ -26,6 +26,7 @@ interface Props { onQuickPromptDeleted: (quickPromptTitle: string) => void; onQuickPromptSelectionChange: (quickPrompt?: QuickPrompt | string) => void; quickPrompts: QuickPrompt[]; + resetSettings?: () => void; selectedQuickPrompt?: QuickPrompt; } @@ -40,6 +41,7 @@ export const QuickPromptSelector: React.FC = React.memo( quickPrompts, onQuickPromptDeleted, onQuickPromptSelectionChange, + resetSettings, selectedQuickPrompt, }) => { // Form options @@ -69,6 +71,8 @@ export const QuickPromptSelector: React.FC = React.memo( const handleSelectionChange = useCallback( (quickPromptSelectorOption: QuickPromptSelectorOption[]) => { + // Reset settings on every selection change to avoid option saved automatically on settings management page + resetSettings?.(); const newQuickPrompt = quickPromptSelectorOption.length === 0 ? undefined @@ -76,7 +80,7 @@ export const QuickPromptSelector: React.FC = React.memo( quickPromptSelectorOption[0]?.label; onQuickPromptSelectionChange(newQuickPrompt); }, - [onQuickPromptSelectionChange, quickPrompts] + [onQuickPromptSelectionChange, resetSettings, quickPrompts] ); // Callback for when user types to create a new quick prompt diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx index ea903c1abce9d..e20ccc489405f 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx @@ -23,6 +23,7 @@ const DEFAULT_COLOR = '#D36086'; interface Props { onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; quickPromptSettings: QuickPrompt[]; + resetSettings?: () => void; selectedQuickPrompt: QuickPrompt | undefined; setUpdatedQuickPromptSettings: React.Dispatch>; } @@ -30,6 +31,7 @@ interface Props { const QuickPromptSettingsEditorComponent = ({ onSelectedQuickPromptChange, quickPromptSettings, + resetSettings, selectedQuickPrompt, setUpdatedQuickPromptSettings, }: Props) => { @@ -139,6 +141,7 @@ const QuickPromptSettingsEditorComponent = ({ onQuickPromptDeleted={onQuickPromptDeleted} onQuickPromptSelectionChange={onQuickPromptSelectionChange} quickPrompts={quickPromptSettings} + resetSettings={resetSettings} selectedQuickPrompt={selectedQuickPrompt} /> 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 26dd2de8c4d97..55a7f10958bf5 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 @@ -27,22 +27,24 @@ import { PromptContextTemplate } from '../../prompt_context/types'; interface Props { basePromptContexts: PromptContextTemplate[]; + handleSave: (shouldRefetchConversation?: boolean) => void; + onCancelClick: () => void; onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; quickPromptSettings: QuickPrompt[]; + resetSettings?: () => void; selectedQuickPrompt: QuickPrompt | undefined; setUpdatedQuickPromptSettings: React.Dispatch>; - handleSave: (shouldRefetchConversation?: boolean) => void; - onCancelClick: () => void; } const QuickPromptSettingsManagementComponent = ({ basePromptContexts, + handleSave, + onCancelClick, onSelectedQuickPromptChange, quickPromptSettings, + resetSettings, selectedQuickPrompt, setUpdatedQuickPromptSettings, - handleSave, - onCancelClick, }: Props) => { const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); const [deletedQuickPrompt, setDeletedQuickPrompt] = useState(); @@ -148,6 +150,7 @@ const QuickPromptSettingsManagementComponent = ({ 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 cbefa6558aa54..8fd0faf4ecfb8 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 @@ -279,34 +279,36 @@ export const AssistantSettingsManagement: React.FC = React.memo( connectors={connectors} conversations={conversations} conversationSettings={conversationSettings} + conversationsSettingsBulkActions={conversationsSettingsBulkActions} defaultConnector={defaultConnector} - systemPromptSettings={systemPromptSettings} handleSave={handleSave} - selectedSystemPrompt={selectedSystemPrompt} + onCancelClick={onCancelClick} onSelectedSystemPromptChange={onHandleSelectedSystemPromptChange} + resetSettings={resetSettings} + selectedSystemPrompt={selectedSystemPrompt} setConversationSettings={setConversationSettings} setConversationsSettingsBulkActions={setConversationsSettingsBulkActions} - conversationsSettingsBulkActions={conversationsSettingsBulkActions} setUpdatedSystemPromptSettings={setUpdatedSystemPromptSettings} - onCancelClick={onCancelClick} + systemPromptSettings={systemPromptSettings} /> )} {selectedSettingsTab === QUICK_PROMPTS_TAB && ( )} {selectedSettingsTab === ANONYMIZATION_TAB && ( From 302a901cb55f49e611f0c209aa4a26a689f7761c Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 26 Jun 2024 14:49:45 +0100 Subject: [PATCH 28/37] tests --- .../system_prompt_selector.test.tsx | 24 +++- .../use_system_prompt_table.test.tsx | 107 ++++++++++++++++++ .../utils.test.tsx | 70 ++++++++++++ .../quick_prompt_selector.test.tsx | 16 +++ .../use_conversation/helpers.test.ts | 40 ++++++- .../assistant/use_conversation/helpers.ts | 19 ++-- 6 files changed, 260 insertions(+), 16 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.test.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.test.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.test.tsx index d8d14a8ffebe0..45f320528ec64 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector.test.tsx @@ -23,7 +23,7 @@ describe('SystemPromptSelector', () => { beforeEach(() => { jest.clearAllMocks(); }); - it('Selects an existing quick prompt', () => { + it('Selects an existing system prompt', () => { const { getByTestId } = render(); expect(getByTestId('comboBoxSearchInput')).toHaveValue(mockSystemPrompts[0].name); fireEvent.click(getByTestId('comboBoxToggleListButton')); @@ -33,7 +33,7 @@ describe('SystemPromptSelector', () => { it('Deletes a system prompt that is not selected', () => { const { getByTestId, getAllByTestId } = render(); fireEvent.click(getByTestId('comboBoxToggleListButton')); - // there is only one delete quick prompt because there is only one custom option + // there is only one delete system prompt because there is only one custom option fireEvent.click(getAllByTestId('delete-prompt')[1]); expect(onSystemPromptDeleted).toHaveBeenCalledWith(mockSystemPrompts[1].name); expect(onSystemPromptSelectionChange).not.toHaveBeenCalled(); @@ -41,12 +41,12 @@ describe('SystemPromptSelector', () => { it('Deletes a system prompt that is selected', () => { const { getByTestId, getAllByTestId } = render(); fireEvent.click(getByTestId('comboBoxToggleListButton')); - // there is only one delete quick prompt because there is only one custom option + // there is only one delete system prompt because there is only one custom option fireEvent.click(getAllByTestId('delete-prompt')[0]); expect(onSystemPromptDeleted).toHaveBeenCalledWith(mockSystemPrompts[0].name); expect(onSystemPromptSelectionChange).toHaveBeenCalledWith(undefined); }); - it('Selects existing quick prompt from the search input', () => { + it('Selects existing system prompt from the search input', () => { const { getByTestId } = render(); fireEvent.change(getByTestId('comboBoxSearchInput'), { target: { value: mockSystemPrompts[1].name }, @@ -58,6 +58,22 @@ describe('SystemPromptSelector', () => { }); expect(onSystemPromptSelectionChange).toHaveBeenCalledWith(mockSystemPrompts[1]); }); + it('Reset settings every time before selecting an system prompt from the input if resetSettings is provided', () => { + const mockResetSettings = jest.fn(); + const { getByTestId } = render( + + ); + // changing the selection + fireEvent.change(getByTestId('comboBoxSearchInput'), { + target: { value: mockSystemPrompts[1].name }, + }); + fireEvent.keyDown(getByTestId('comboBoxSearchInput'), { + key: 'Enter', + code: 'Enter', + charCode: 13, + }); + expect(mockResetSettings).toHaveBeenCalled(); + }); it('Creates a new system prompt', () => { const { getByTestId } = render(); const customOption = 'Cool new 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 new file mode 100644 index 0000000000000..90cea2319714d --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/use_system_prompt_table.test.tsx @@ -0,0 +1,107 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { useSystemPromptTable } from './use_system_prompt_table'; +import { Prompt } from '../../../types'; +import { Conversation } from '../../../../assistant_context/types'; +import { AIConnector } from '../../../../connectorland/connector_selector'; +import { customConvo, welcomeConvo } from '../../../../mock/conversation'; +import { mockConnectors } from '../../../../mock/connectors'; +import { ApiConfig } from '@kbn/elastic-assistant-common'; + +// Mock data for tests +const mockSystemPrompts: Prompt[] = [ + { + id: 'prompt-1', + content: 'Prompt 1', + name: 'Prompt 1', + promptType: 'user', + }, + { + id: 'prompt-2', + content: 'Prompt 2', + name: 'Prompt 2', + promptType: 'user', + isNewConversationDefault: true, + }, +]; + +const mockConversationSettings: Record = { + 'conv-1': { + ...welcomeConvo, + id: 'conv-1', + apiConfig: { + ...welcomeConvo.apiConfig, + defaultSystemPromptId: 'prompt-1', + } as ApiConfig, + }, + 'conv-2': { + ...customConvo, + id: 'conv-2', + apiConfig: { + ...customConvo.apiConfig, + defaultSystemPromptId: 'prompt-2', + } as ApiConfig, + }, +}; + +const mockAiConnectors: AIConnector[] = [...mockConnectors]; + +const mockDefaultConnector: AIConnector = { + id: 'default-connector', + actionTypeId: '.gen-ai', + apiProvider: 'OpenAI', +} as AIConnector; + +describe('useSystemPromptTable', () => { + const { result } = renderHook(() => useSystemPromptTable()); + + describe('getColumns', () => { + it('should return columns with correct render functions', () => { + const onEditActionClicked = jest.fn(); + const onDeleteActionClicked = jest.fn(); + const columns = result.current.getColumns({ + onEditActionClicked, + onDeleteActionClicked, + }); + + expect(columns).toHaveLength(3); + expect(columns[0].name).toBe('Name'); + expect(columns[1].name).toBe('Default conversations'); + expect(columns[2].name).toBe('Actions'); + }); + }); + + describe('getSystemPromptsList', () => { + it('should return system prompts with associated default conversations', () => { + const systemPromptsList = result.current.getSystemPromptsList({ + connectors: mockAiConnectors, + conversationSettings: mockConversationSettings, + defaultConnector: mockDefaultConnector, + systemPromptSettings: mockSystemPrompts, + }); + + expect(systemPromptsList).toHaveLength(2); + expect(systemPromptsList[0].defaultConversations).toEqual(['Welcome']); + expect(systemPromptsList[1].defaultConversations).toEqual(['Custom option']); + }); + + it('should return empty defaultConversations if no conversations match', () => { + const systemPromptsList = result.current.getSystemPromptsList({ + connectors: [], + conversationSettings: {}, + defaultConnector: undefined, + systemPromptSettings: mockSystemPrompts, + }); + + systemPromptsList.forEach((prompt) => { + expect(prompt.defaultConversations).toEqual([]); + }); + }); + }); +}); 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 new file mode 100644 index 0000000000000..5f10e3bb59c65 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/utils.test.tsx @@ -0,0 +1,70 @@ +/* + * 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 { ProviderEnum } from '@kbn/elastic-assistant-common'; +import { mockSystemPrompts } from '../../../../mock/system_prompt'; +import { PromptType } from '../../../types'; +import { getSelectedConversations } from './utils'; +describe('getSelectedConversations', () => { + const allSystemPrompts = [...mockSystemPrompts]; + const conversationSettings = { + '8f1e3218-0b02-480a-8791-78c1ed5f3708': { + timestamp: '2024-06-25T12:33:26.779Z', + createdAt: '2024-06-25T12:33:26.779Z' as unknown as Date, + users: [ + { + id: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', + name: 'elastic', + }, + ], + title: 'New chat', + category: 'assistant', + apiConfig: { + connectorId: 'acdeb074-f863-4e04-a22b-014391dd1be4', + actionTypeId: '.gen-ai', + provider: ProviderEnum.OpenAI, + defaultSystemPromptId: 'mock-system-prompt-1', + model: 'gpt-4', + }, + messages: [], + updatedAt: '2024-06-25T18:35:28.217Z' as unknown as Date, + namespace: 'default', + id: '8f1e3218-0b02-480a-8791-78c1ed5f3708', + replacements: {}, + systemPrompt: { + id: 'mock-system-prompt-1', + content: + 'You are a helpful, expert assistant who answers questions about Elastic Security. Do not answer questions unrelated to Elastic Security.\nProvide the most detailed and relevant answer possible, as if you were relaying this information back to a cyber security expert.\nIf you answer a question related to KQL, EQL, or ES|QL, it should be immediately usable within an Elastic Security timeline; please always format the output correctly with back ticks. Any answer provided for Query DSL should also be usable in a security timeline. This means you should only ever include the "filter" portion of the query. xxx', + name: 'Enhanced system prompt', + promptType: 'system' as PromptType, + isDefault: true, + isNewConversationDefault: true, + }, + }, + }; + test('should return selected conversations', () => { + const systemPromptId = 'mock-system-prompt-1'; + + const conversations = getSelectedConversations( + allSystemPrompts, + 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 + ); + + expect(conversations).toEqual([]); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.test.tsx index 6e24d662955b1..04ccd478e3bc5 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_selector/quick_prompt_selector.test.tsx @@ -53,6 +53,22 @@ describe('QuickPromptSelector', () => { title: 'A_CUSTOM_OPTION', }); }); + it('Reset settings every time before selecting an system prompt from the input if resetSettings is provided', () => { + const mockResetSettings = jest.fn(); + const { getByTestId } = render( + + ); + // changing the selection + fireEvent.change(getByTestId('comboBoxSearchInput'), { + target: { value: MOCK_QUICK_PROMPTS[1].title }, + }); + fireEvent.keyDown(getByTestId('comboBoxSearchInput'), { + key: 'Enter', + code: 'Enter', + charCode: 13, + }); + expect(mockResetSettings).toHaveBeenCalled(); + }); it('Creates a new quick prompt', () => { const { getByTestId } = render(); const customOption = 'Cool new prompt'; 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 82867e485b2b5..c8c8ab5ff7727 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 @@ -264,6 +264,7 @@ describe('getConversationApiConfig', () => { connectorId: '123', actionTypeId: '.gen-ai', defaultSystemPromptId: '2', + model: 'gpt-3', }, category: 'assistant', id: '1', @@ -305,6 +306,7 @@ describe('getConversationApiConfig', () => { actionTypeId: '.gen-ai', provider: OpenAiProviderType.OpenAi, defaultSystemPromptId: '2', + model: 'gpt-3', }, }); }); @@ -328,6 +330,7 @@ describe('getConversationApiConfig', () => { actionTypeId: '.gen-ai', provider: OpenAiProviderType.AzureAi, defaultSystemPromptId: '2', + model: 'gpt-3', }, }); }); @@ -362,7 +365,8 @@ describe('getConversationApiConfig', () => { connectorId: '123', actionTypeId: '.gen-ai', provider: OpenAiProviderType.OpenAi, - defaultSystemPromptId: '2', + defaultSystemPromptId: '2', // Returns the default system prompt for new conversations + model: 'gpt-3', }, }); }); @@ -380,6 +384,7 @@ describe('getConversationApiConfig', () => { actionTypeId: '.gen-ai', provider: OpenAiProviderType.AzureAi, defaultSystemPromptId: '2', + model: 'gpt-3', }, }); }); @@ -406,12 +411,13 @@ describe('getConversationApiConfig', () => { connectorId: '123', actionTypeId: '.gen-ai', provider: OpenAiProviderType.OpenAi, - defaultSystemPromptId: '1', + defaultSystemPromptId: '1', // Uses the first prompt in the list + model: undefined, // default connector's model }, }); }); - test('should return undefined system prompt if conversation system prompt does not exist within all system prompts', () => { + test('should return the first system prompt if conversation system prompt does not exist within all system prompts', () => { const allSystemPromptsNoDefault: Prompt[] = allSystemPrompts.filter( ({ isNewConversationDefault }) => isNewConversationDefault !== true ); @@ -429,12 +435,40 @@ describe('getConversationApiConfig', () => { 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 + }, + }); + }); + + 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: conversationWithUndefinedPrompt, + connectors, + defaultConnector, + }); + expect(result).toEqual({ apiConfig: { connectorId: '123', actionTypeId: '.gen-ai', provider: OpenAiProviderType.OpenAi, defaultSystemPromptId: '1', + 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 7301a393fd36b..2d6c4075fba0e 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 @@ -141,16 +141,17 @@ 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 defaultSystemPrompt = conversation.apiConfig?.defaultSystemPromptId - ? getDefaultSystemPrompt({ - allSystemPrompts, - conversation, - }) - : getInitialDefaultSystemPrompt({ - allSystemPrompts, - conversation, - }); return connector ? { apiConfig: { From bbbc34596fcfa02fda79e64139d18ce765b110e2 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 26 Jun 2024 18:40:57 +0100 Subject: [PATCH 29/37] unit tests --- .../use_system_prompt_editor.test.tsx | 107 ++++++++++++++++++ .../use_quick_prompt_editor.test.tsx | 94 +++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.test.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.test.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.test.tsx new file mode 100644 index 0000000000000..85efe99979650 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/use_system_prompt_editor.test.tsx @@ -0,0 +1,107 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; +import { useSystemPromptEditor } from './use_system_prompt_editor'; +import { Prompt } from '../../../types'; +import { + mockSystemPrompt, + mockSuperheroSystemPrompt, + mockSystemPrompts, +} from '../../../../mock/system_prompt'; + +// Mock functions for the tests +const mockOnSelectedSystemPromptChange = jest.fn(); +const mockSetUpdatedSystemPromptSettings = jest.fn(); +const mockPreviousSystemPrompts = [...mockSystemPrompts]; + +describe('useSystemPromptEditor', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should delete a system prompt by id', () => { + const { result } = renderHook(() => + useSystemPromptEditor({ + onSelectedSystemPromptChange: mockOnSelectedSystemPromptChange, + setUpdatedSystemPromptSettings: mockSetUpdatedSystemPromptSettings, + }) + ); + + act(() => { + result.current.onSystemPromptDeleted('mock-system-prompt-1'); + }); + + expect( + mockSetUpdatedSystemPromptSettings.mock.calls[0][0]?.(mockPreviousSystemPrompts) + ).toEqual(mockSystemPrompts.filter((sp) => sp.id !== 'mock-system-prompt-1')); + }); + + test('should handle selection of an existing system prompt', () => { + const existingPrompt: Prompt = mockSystemPrompt; + const { result } = renderHook(() => + useSystemPromptEditor({ + onSelectedSystemPromptChange: mockOnSelectedSystemPromptChange, + setUpdatedSystemPromptSettings: mockSetUpdatedSystemPromptSettings, + }) + ); + + act(() => { + result.current.onSystemPromptSelectionChange(existingPrompt); + }); + + expect(mockOnSelectedSystemPromptChange).toHaveBeenCalledWith(existingPrompt); + expect( + mockSetUpdatedSystemPromptSettings.mock.calls[0][0]?.(mockPreviousSystemPrompts) + ).toEqual(mockSystemPrompts); + }); + + test('should handle selection of a new system prompt', () => { + const newPromptId = 'new-system-prompt'; + const { result } = renderHook(() => + useSystemPromptEditor({ + onSelectedSystemPromptChange: mockOnSelectedSystemPromptChange, + setUpdatedSystemPromptSettings: mockSetUpdatedSystemPromptSettings, + }) + ); + + act(() => { + result.current.onSystemPromptSelectionChange(newPromptId); + }); + + const newPrompt: Prompt = { + id: newPromptId, + content: '', + name: newPromptId, + promptType: 'system', + }; + + expect(mockOnSelectedSystemPromptChange).toHaveBeenCalledWith(newPrompt); + expect( + mockSetUpdatedSystemPromptSettings.mock.calls[0][0]?.(mockPreviousSystemPrompts) + ).toEqual([...mockSystemPrompts, newPrompt]); + }); + + test('should handle prompt selection with an existing system prompt id', () => { + const { result } = renderHook(() => + useSystemPromptEditor({ + onSelectedSystemPromptChange: mockOnSelectedSystemPromptChange, + setUpdatedSystemPromptSettings: mockSetUpdatedSystemPromptSettings, + }) + ); + + const expectedPrompt: Prompt = mockSuperheroSystemPrompt; + + act(() => { + result.current.onSystemPromptSelectionChange(expectedPrompt); + }); + + expect(mockOnSelectedSystemPromptChange).toHaveBeenCalledWith(expectedPrompt); + expect( + mockSetUpdatedSystemPromptSettings.mock.calls[0][0]?.(mockPreviousSystemPrompts) + ).toContain(expectedPrompt); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.test.tsx new file mode 100644 index 0000000000000..ec3a0256716ae --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/use_quick_prompt_editor.test.tsx @@ -0,0 +1,94 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; +import { useQuickPromptEditor, DEFAULT_COLOR } from './use_quick_prompt_editor'; +import { QuickPrompt } from '../types'; +import { mockAlertPromptContext } from '../../../mock/prompt_context'; +import { MOCK_QUICK_PROMPTS } from '../../../mock/quick_prompt'; + +// Mock functions for the tests +const mockOnSelectedQuickPromptChange = jest.fn(); +const mockSetUpdatedQuickPromptSettings = jest.fn(); +const mockPreviousQuickPrompts = [...MOCK_QUICK_PROMPTS]; + +describe('useQuickPromptEditor', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should delete a quick prompt by title', () => { + const { result } = renderHook(() => + useQuickPromptEditor({ + onSelectedQuickPromptChange: mockOnSelectedQuickPromptChange, + setUpdatedQuickPromptSettings: mockSetUpdatedQuickPromptSettings, + }) + ); + + act(() => { + result.current.onQuickPromptDeleted('ALERT_SUMMARIZATION_TITLE'); + }); + + expect(mockSetUpdatedQuickPromptSettings.mock.calls[0][0]?.(mockPreviousQuickPrompts)).toEqual( + MOCK_QUICK_PROMPTS.filter((qp) => qp.title !== 'ALERT_SUMMARIZATION_TITLE') + ); + }); + + test('should handle selection of a new quick prompt', () => { + const newPromptTitle = 'New Prompt'; + const { result } = renderHook(() => + useQuickPromptEditor({ + onSelectedQuickPromptChange: mockOnSelectedQuickPromptChange, + setUpdatedQuickPromptSettings: mockSetUpdatedQuickPromptSettings, + }) + ); + + act(() => { + result.current.onQuickPromptSelectionChange(newPromptTitle); + }); + + const newPrompt: QuickPrompt = { + title: newPromptTitle, + prompt: '', + color: DEFAULT_COLOR, + categories: [], + }; + + expect(mockOnSelectedQuickPromptChange).toHaveBeenCalledWith(newPrompt); + expect(mockSetUpdatedQuickPromptSettings.mock.calls[0][0]?.(mockPreviousQuickPrompts)).toEqual([ + ...MOCK_QUICK_PROMPTS, + newPrompt, + ]); + }); + + test('should handle prompt selection', async () => { + const { result } = renderHook(() => + useQuickPromptEditor({ + onSelectedQuickPromptChange: mockOnSelectedQuickPromptChange, + setUpdatedQuickPromptSettings: mockSetUpdatedQuickPromptSettings, + }) + ); + + const alertData = await mockAlertPromptContext.getPromptContext(); + + const expectedPrompt: QuickPrompt = { + title: mockAlertPromptContext.description, + prompt: alertData, + color: DEFAULT_COLOR, + categories: [mockAlertPromptContext.category], + } as QuickPrompt; + + act(() => { + result.current.onQuickPromptSelectionChange(expectedPrompt); + }); + + expect(mockOnSelectedQuickPromptChange).toHaveBeenCalledWith(expectedPrompt); + expect( + mockSetUpdatedQuickPromptSettings.mock.calls[0][0]?.(mockPreviousQuickPrompts) + ).toContain(expectedPrompt); + }); +}); From 01decb1672d2868fd40110a89d15c3dbf77d5d20 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 26 Jun 2024 18:49:01 +0100 Subject: [PATCH 30/37] remove additional props --- .../assistant/settings/assistant_settings_management.tsx | 5 +---- .../connectorland/connector_settings_management/index.tsx | 6 +----- 2 files changed, 2 insertions(+), 9 deletions(-) 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 8fd0faf4ecfb8..2a16e06c3c38d 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 @@ -65,7 +65,6 @@ export const AssistantSettingsManagement: React.FC = React.memo( assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled }, basePromptContexts, http, - navigateToApp, selectedSettingsTab, setSelectedSettingsTab, toasts, @@ -251,9 +250,7 @@ export const AssistantSettingsManagement: React.FC = React.memo( padding-bottom: ${euiTheme.base * 0.75}px; `} > - {selectedSettingsTab === CONNECTORS_TAB && ( - - )} + {selectedSettingsTab === CONNECTORS_TAB && } {selectedSettingsTab === CONVERSATIONS_TAB && ( ) => Promise; -} - -const ConnectorsSettingsManagementComponent: React.FC = () => { +const ConnectorsSettingsManagementComponent: React.FC = () => { const { navigateToApp } = useAssistantContext(); const onClick = useCallback( From a6352881b1cfb00ebd5f739ab3828cf6eee7f76a Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 27 Jun 2024 10:17:30 +0100 Subject: [PATCH 31/37] styling --- .../ai_assistant_selection_page.tsx | 4 ++-- .../assistant_settings_management.tsx | 23 ++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) 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 7d88715600da9..a237e791e9e9d 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 @@ -100,7 +100,7 @@ export function AiAssistantSelectionPage() { > {i18n.translate( 'aiAssistantManagementSelection.aiAssistantSettingsPage.obsAssistant.documentationLinkLabel', - { defaultMessage: 'Documentation' } + { defaultMessage: 'documentation' } )}

@@ -165,7 +165,7 @@ export function AiAssistantSelectionPage() { > {i18n.translate( 'aiAssistantManagementSelection.aiAssistantSettingsPage.securityAssistant.documentationLinkLabel', - { defaultMessage: 'Documentation' } + { defaultMessage: 'documentation' } )}

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 2a16e06c3c38d..74cc1fa4154e8 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 @@ -7,11 +7,14 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { + EuiAvatar, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPageTemplate, + EuiTitle, + useEuiShadow, useEuiTheme, } from '@elastic/eui'; @@ -79,6 +82,8 @@ export const AssistantSettingsManagement: React.FC = React.memo( const [hasPendingChanges, setHasPendingChanges] = useState(false); const { euiTheme } = useEuiTheme(); + const headerIconShadow = useEuiShadow('s'); + const { conversationSettings, setConversationSettings, @@ -237,7 +242,23 @@ export const AssistantSettingsManagement: React.FC = React.memo( return ( <> + + +

{i18n.SECURITY_AI_SETTINGS}

+
+ + } tabs={tabs} paddingSize="none" /> From 8b8f545bae50f4a29b8ee92791151d341473d08b Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 27 Jun 2024 13:23:54 +0100 Subject: [PATCH 32/37] add unit tests --- .../use_quick_prompt_table.test.tsx | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.test.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.test.tsx new file mode 100644 index 0000000000000..316b43f6cfb3d --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings_management/use_quick_prompt_table.test.tsx @@ -0,0 +1,98 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { useQuickPromptTable } from './use_quick_prompt_table'; +import { EuiTableComputedColumnType } from '@elastic/eui'; +import { QuickPrompt } from '../types'; +import { MOCK_QUICK_PROMPTS } from '../../../mock/quick_prompt'; +import { mockPromptContexts } from '../../../mock/prompt_context'; + +const mockOnEditActionClicked = jest.fn(); +const mockOnDeleteActionClicked = jest.fn(); + +describe('useQuickPromptTable', () => { + const { result } = renderHook(() => useQuickPromptTable()); + + describe('getColumns', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should return columns with correct render functions', () => { + const columns = result.current.getColumns({ + basePromptContexts: mockPromptContexts, + onEditActionClicked: mockOnEditActionClicked, + onDeleteActionClicked: mockOnDeleteActionClicked, + }); + + expect(columns).toHaveLength(3); + expect(columns[0].name).toBe('Name'); + expect(columns[1].name).toBe('Contexts'); + expect(columns[2].name).toBe('Actions'); + }); + + it('should render contexts column correctly', () => { + const columns = result.current.getColumns({ + basePromptContexts: mockPromptContexts, + onEditActionClicked: mockOnEditActionClicked, + onDeleteActionClicked: mockOnDeleteActionClicked, + }); + + const mockQuickPrompt = { ...MOCK_QUICK_PROMPTS[0], categories: ['alert'] }; + const mockBadgesColumn = (columns[1] as EuiTableComputedColumnType).render( + mockQuickPrompt + ); + const selectedPromptContexts = mockPromptContexts + .filter((bpc) => mockQuickPrompt.categories?.some((cat) => bpc.category === cat)) + .map((bpc) => bpc.description); + expect(mockBadgesColumn).toHaveProperty('props', { + items: selectedPromptContexts, + prefix: MOCK_QUICK_PROMPTS[0].title, + }); + }); + + 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] + ); + + expect(mockRowActions).toHaveProperty('props', { + rowItem: MOCK_QUICK_PROMPTS[0], + onDelete: undefined, + onEdit: mockOnEditActionClicked, + isDeletable: false, + }); + }); + + it('should render delete actions correctly for deletable prompt', () => { + const columns = result.current.getColumns({ + basePromptContexts: mockPromptContexts, + onEditActionClicked: mockOnEditActionClicked, + onDeleteActionClicked: mockOnDeleteActionClicked, + }); + + 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, + }); + } + }); + }); +}); From 379bf182ac22d692f26a62e72fd16e3158bb6e96 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 27 Jun 2024 14:23:40 +0100 Subject: [PATCH 33/37] i18n --- .../ai_assistant_selection_page.tsx | 2 +- .../translations/translations/fr-FR.json | 83 +++++++++---------- .../translations/translations/ja-JP.json | 83 +++++++++---------- .../translations/translations/zh-CN.json | 83 +++++++++---------- 4 files changed, 124 insertions(+), 127 deletions(-) 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 a237e791e9e9d..1d5b2ed2b1b3b 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 @@ -99,7 +99,7 @@ export function AiAssistantSelectionPage() { href="https://www.elastic.co/guide/en/observability/current/obs-ai-assistant.html" > {i18n.translate( - 'aiAssistantManagementSelection.aiAssistantSettingsPage.obsAssistant.documentationLinkLabel', + 'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkLabel', { defaultMessage: 'documentation' } )} diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 3702b46aebd4f..92084b8628596 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2396,12 +2396,7 @@ "discover.fieldChooser.discoverField.removeFieldTooltip": "Supprimer le champ du tableau", "discover.globalSearch.esqlSearchTitle": "Créer des recherches ES|QL", "discover.goToDiscoverButtonText": "Aller à Discover", - "unifiedDocViewer.flyout.documentNavigation": "Navigation dans le document", - "unifiedDocViewer.flyout.toastColumnAdded": "La colonne \"{columnName}\" a été ajoutée.", - "unifiedDocViewer.flyout.toastColumnRemoved": "La colonne \"{columnName}\" a été supprimée.", "discover.grid.tableRow.actionsLabel": "Actions", - "unifiedDocViewer.flyout.docViewerDetailHeading": "Document", - "unifiedDocViewer.flyout.docViewerEsqlDetailHeading": "Ligne", "discover.grid.tableRow.mobileFlyoutActionsButton": "Actions", "discover.grid.tableRow.moreFlyoutActionsButton": "Plus d'actions", "discover.grid.tableRow.esqlDetailHeading": "Ligne développée", @@ -2494,6 +2489,46 @@ "discover.viewAlert.searchSourceErrorTitle": "Erreur lors de la récupération de la source de recherche", "discover.viewModes.document.label": "Documents", "discover.viewModes.fieldStatistics.label": "Statistiques de champ", + "unifiedDocViewer.flyout.documentNavigation": "Navigation dans le document", + "unifiedDocViewer.flyout.toastColumnAdded": "La colonne \"{columnName}\" a été ajoutée.", + "unifiedDocViewer.flyout.toastColumnRemoved": "La colonne \"{columnName}\" a été supprimée.", + "unifiedDocViewer.flyout.docViewerDetailHeading": "Document", + "unifiedDocViewer.flyout.docViewerEsqlDetailHeading": "Ligne", + "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "Une ou plusieurs valeurs dans ce champ sont trop longues et ne peuvent pas être recherchées ni filtrées.", + "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "Ce champ comporte une ou plusieurs valeurs mal formées qui ne peuvent pas être recherchées ni filtrées.", + "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "Une ou plusieurs valeurs dans ce champ ont été ignorées par Elasticsearch et ne peuvent pas être recherchées ni filtrées.", + "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "La valeur dans ce champ est trop longue et ne peut pas être recherchée ni filtrée.", + "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "La valeur dans ce champ est mal formée et ne peut pas être recherchée ni filtrée.", + "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "La valeur dans ce champ a été ignorée par Elasticsearch et ne peut pas être recherchée ni filtrée.", + "unifiedDocViewer.docView.table.searchPlaceHolder": "Rechercher les noms de champs", + "unifiedDocViewer.docViews.json.jsonTitle": "JSON", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "Filtrer sur le champ", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "Filtrer sur le champ", + "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "Filtrer sur la valeur", + "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "Filtrer sur la valeur", + "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "Exclure la valeur", + "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "Exclure la valeur", + "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "Contient des valeurs ignorées", + "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "Valeur ignorée", + "unifiedDocViewer.docViews.table.pinFieldLabel": "Épingler le champ", + "unifiedDocViewer.docViews.table.tableTitle": "Tableau", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "Afficher/Masquer la colonne dans le tableau", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "Afficher/Masquer la colonne dans le tableau", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "Impossible de filtrer sur les champs méta", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "Impossible de filtrer sur les champs scriptés", + "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "Les champs non indexés ou les valeurs ignorées ne peuvent pas être recherchés", + "unifiedDocViewer.docViews.table.unpinFieldLabel": "Désépingler le champ", + "unifiedDocViewer.fieldChooser.discoverField.actions": "Actions", + "unifiedDocViewer.fieldChooser.discoverField.multiField": "champ multiple", + "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "Les champs multiples peuvent avoir plusieurs valeurs.", + "unifiedDocViewer.fieldChooser.discoverField.name": "Champ", + "unifiedDocViewer.fieldChooser.discoverField.value": "Valeur", + "unifiedDocViewer.json.codeEditorAriaLabel": "Affichage JSON en lecture seule d’un document Elasticsearch", + "unifiedDocViewer.json.copyToClipboardLabel": "Copier dans le presse-papiers", + "unifiedDocViewer.loadingJSON": "Chargement de JSON", + "unifiedDocViewer.sourceViewer.errorMessage": "Impossible de récupérer les données pour le moment. Actualisez l'onglet et réessayez.", + "unifiedDocViewer.sourceViewer.errorMessageTitle": "Une erreur s'est produite.", + "unifiedDocViewer.sourceViewer.refresh": "Actualiser", "textBasedLanguages.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", "textBasedLanguages.advancedSettings.enableESQLTitle": "Activer ES|QL", "domDragDrop.announce.cancelled": "Mouvement annulé. {label} revenu à sa position initiale", @@ -43899,7 +43934,6 @@ "aiAssistantManagementSelection.aiAssistantSelectionPage.observabilityLabel": "Assistant d'IA Elastic pour Observability", "aiAssistantManagementSelection.aiAssistantSettingsPage.descriptionTextLabel": "L'Assistant d'IA utilise l'IA générative pour aider votre équipe en expliquant les erreurs, en suggérant une résolution et en vous aidant à demander, analyser et visualiser vos données.", "aiAssistantManagementSelection.aiAssistantSettingsPage.h2.aIAssistantLabel": "Assistant d'intelligence artificielle", - "aiAssistantManagementSelection.aiAssistantSettingsPage.obsAssistant.documentationLinkLabel": "Documentation", "aiAssistantManagementSelection.app.description": "Gérer les Assistants d'IA.", "aiAssistantManagementSelection.app.title": "Assistants d'IA", "aiAssistantManagementSelection.app.titleBar": "Assistants d'IA", @@ -44223,41 +44257,6 @@ "uiActions.errors.incompatibleAction": "Action non compatible", "uiActions.triggers.rowClickkDescription": "Un clic sur une ligne de tableau", "uiActions.triggers.rowClickTitle": "Clic sur ligne de tableau", - "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "Une ou plusieurs valeurs dans ce champ sont trop longues et ne peuvent pas être recherchées ni filtrées.", - "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "Ce champ comporte une ou plusieurs valeurs mal formées qui ne peuvent pas être recherchées ni filtrées.", - "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "Une ou plusieurs valeurs dans ce champ ont été ignorées par Elasticsearch et ne peuvent pas être recherchées ni filtrées.", - "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "La valeur dans ce champ est trop longue et ne peut pas être recherchée ni filtrée.", - "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "La valeur dans ce champ est mal formée et ne peut pas être recherchée ni filtrée.", - "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "La valeur dans ce champ a été ignorée par Elasticsearch et ne peut pas être recherchée ni filtrée.", - "unifiedDocViewer.docView.table.searchPlaceHolder": "Rechercher les noms de champs", - "unifiedDocViewer.docViews.json.jsonTitle": "JSON", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "Filtrer sur le champ", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "Filtrer sur le champ", - "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "Filtrer sur la valeur", - "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "Filtrer sur la valeur", - "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "Exclure la valeur", - "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "Exclure la valeur", - "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "Contient des valeurs ignorées", - "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "Valeur ignorée", - "unifiedDocViewer.docViews.table.pinFieldLabel": "Épingler le champ", - "unifiedDocViewer.docViews.table.tableTitle": "Tableau", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "Afficher/Masquer la colonne dans le tableau", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "Afficher/Masquer la colonne dans le tableau", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "Impossible de filtrer sur les champs méta", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "Impossible de filtrer sur les champs scriptés", - "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "Les champs non indexés ou les valeurs ignorées ne peuvent pas être recherchés", - "unifiedDocViewer.docViews.table.unpinFieldLabel": "Désépingler le champ", - "unifiedDocViewer.fieldChooser.discoverField.actions": "Actions", - "unifiedDocViewer.fieldChooser.discoverField.multiField": "champ multiple", - "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "Les champs multiples peuvent avoir plusieurs valeurs.", - "unifiedDocViewer.fieldChooser.discoverField.name": "Champ", - "unifiedDocViewer.fieldChooser.discoverField.value": "Valeur", - "unifiedDocViewer.json.codeEditorAriaLabel": "Affichage JSON en lecture seule d’un document Elasticsearch", - "unifiedDocViewer.json.copyToClipboardLabel": "Copier dans le presse-papiers", - "unifiedDocViewer.loadingJSON": "Chargement de JSON", - "unifiedDocViewer.sourceViewer.errorMessage": "Impossible de récupérer les données pour le moment. Actualisez l'onglet et réessayez.", - "unifiedDocViewer.sourceViewer.errorMessageTitle": "Une erreur s'est produite.", - "unifiedDocViewer.sourceViewer.refresh": "Actualiser", "unsavedChangesBadge.contextMenu.openButton": "Afficher les actions disponibles", "unsavedChangesBadge.contextMenu.revertChangesButton": "Restaurer les modifications", "unsavedChangesBadge.contextMenu.revertingChangesButtonStatus": "Annuler les modifications", @@ -44610,4 +44609,4 @@ "xpack.serverlessObservability.nav.projectSettings": "Paramètres de projet", "xpack.serverlessObservability.nav.synthetics": "Synthetics" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 521ed09cc7acf..7899cc6cba348 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2393,12 +2393,7 @@ "discover.fieldChooser.discoverField.removeFieldTooltip": "フィールドを表から削除", "discover.globalSearch.esqlSearchTitle": "ES|QLクエリを作成", "discover.goToDiscoverButtonText": "Discoverに移動", - "unifiedDocViewer.flyout.documentNavigation": "ドキュメントナビゲーション", - "unifiedDocViewer.flyout.toastColumnAdded": "列'{columnName}'が追加されました", - "unifiedDocViewer.flyout.toastColumnRemoved": "列'{columnName}'が削除されました", "discover.grid.tableRow.actionsLabel": "アクション", - "unifiedDocViewer.flyout.docViewerDetailHeading": "ドキュメント", - "unifiedDocViewer.flyout.docViewerEsqlDetailHeading": "行", "discover.grid.tableRow.mobileFlyoutActionsButton": "アクション", "discover.grid.tableRow.moreFlyoutActionsButton": "さらにアクションを表示", "discover.grid.tableRow.esqlDetailHeading": "展開された行", @@ -2491,6 +2486,46 @@ "discover.viewAlert.searchSourceErrorTitle": "検索ソースの取得エラー", "discover.viewModes.document.label": "ドキュメント", "discover.viewModes.fieldStatistics.label": "フィールド統計情報", + "unifiedDocViewer.flyout.documentNavigation": "ドキュメントナビゲーション", + "unifiedDocViewer.flyout.toastColumnAdded": "列'{columnName}'が追加されました", + "unifiedDocViewer.flyout.toastColumnRemoved": "列'{columnName}'が削除されました", + "unifiedDocViewer.flyout.docViewerDetailHeading": "ドキュメント", + "unifiedDocViewer.flyout.docViewerEsqlDetailHeading": "行", + "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "このフィールドの1つ以上の値が長すぎるため、検索またはフィルタリングできません。", + "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "このフィールドは、検索またはフィルタリングできない正しくない形式の値が1つ以上あります。", + "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "このフィールドの1つ以上の値がElasticsearchによって無視されたため、検索またはフィルタリングできません。", + "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "このフィールドの値が長すぎるため、検索またはフィルタリングできません。", + "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "このフィールドの値の形式が正しくないため、検索またはフィルタリングできません。", + "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "このフィールドの値はElasticsearchによって無視されたため、検索またはフィルタリングできません。", + "unifiedDocViewer.docView.table.searchPlaceHolder": "検索フィールド名", + "unifiedDocViewer.docViews.json.jsonTitle": "JSON", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "フィールド表示のフィルター", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "フィールド表示のフィルター", + "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "値でフィルター", + "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "値でフィルター", + "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "値を除外", + "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "値を除外", + "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "無視された値を含む", + "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "無視された値", + "unifiedDocViewer.docViews.table.pinFieldLabel": "フィールドを固定", + "unifiedDocViewer.docViews.table.tableTitle": "表", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "表の列を切り替える", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "表の列を切り替える", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "メタフィールドの有無でフィルタリングできません", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "スクリプトフィールドの有無でフィルタリングできません", + "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "インデックスがないフィールドまたは無視された値は検索できません", + "unifiedDocViewer.docViews.table.unpinFieldLabel": "フィールドを固定解除", + "unifiedDocViewer.fieldChooser.discoverField.actions": "アクション", + "unifiedDocViewer.fieldChooser.discoverField.multiField": "複数フィールド", + "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "複数フィールドにはフィールドごとに複数の値を入力できます", + "unifiedDocViewer.fieldChooser.discoverField.name": "フィールド", + "unifiedDocViewer.fieldChooser.discoverField.value": "値", + "unifiedDocViewer.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", + "unifiedDocViewer.json.copyToClipboardLabel": "クリップボードにコピー", + "unifiedDocViewer.loadingJSON": "JSONを読み込んでいます", + "unifiedDocViewer.sourceViewer.errorMessage": "現在データを取得できませんでした。タブを更新して、再試行してください。", + "unifiedDocViewer.sourceViewer.errorMessageTitle": "エラーが発生しました", + "unifiedDocViewer.sourceViewer.refresh": "更新", "textBasedLanguages.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", "textBasedLanguages.advancedSettings.enableESQLTitle": "ES|QLを有効化", "domDragDrop.announce.cancelled": "移動がキャンセルされました。{label}は初期位置に戻りました", @@ -43875,7 +43910,6 @@ "aiAssistantManagementSelection.aiAssistantSelectionPage.observabilityLabel": "Elastic AI Assistant for Observability", "aiAssistantManagementSelection.aiAssistantSettingsPage.descriptionTextLabel": "AI Assistantは、生成AIを使用して、エラーを説明したり、改善策を提案したり、データのリクエスト、分析、可視化を支援したりすることで、チームを支援します。", "aiAssistantManagementSelection.aiAssistantSettingsPage.h2.aIAssistantLabel": "AI Assistant", - "aiAssistantManagementSelection.aiAssistantSettingsPage.obsAssistant.documentationLinkLabel": "ドキュメント", "aiAssistantManagementSelection.app.description": "AI Assistantを管理します。", "aiAssistantManagementSelection.app.title": "AI Assistant", "aiAssistantManagementSelection.app.titleBar": "AI Assistant", @@ -44199,41 +44233,6 @@ "uiActions.errors.incompatibleAction": "操作に互換性がありません", "uiActions.triggers.rowClickkDescription": "テーブル行をクリック", "uiActions.triggers.rowClickTitle": "テーブル行クリック", - "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "このフィールドの1つ以上の値が長すぎるため、検索またはフィルタリングできません。", - "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "このフィールドは、検索またはフィルタリングできない正しくない形式の値が1つ以上あります。", - "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "このフィールドの1つ以上の値がElasticsearchによって無視されたため、検索またはフィルタリングできません。", - "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "このフィールドの値が長すぎるため、検索またはフィルタリングできません。", - "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "このフィールドの値の形式が正しくないため、検索またはフィルタリングできません。", - "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "このフィールドの値はElasticsearchによって無視されたため、検索またはフィルタリングできません。", - "unifiedDocViewer.docView.table.searchPlaceHolder": "検索フィールド名", - "unifiedDocViewer.docViews.json.jsonTitle": "JSON", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "フィールド表示のフィルター", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "フィールド表示のフィルター", - "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "値でフィルター", - "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "値でフィルター", - "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "値を除外", - "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "値を除外", - "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "無視された値を含む", - "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "無視された値", - "unifiedDocViewer.docViews.table.pinFieldLabel": "フィールドを固定", - "unifiedDocViewer.docViews.table.tableTitle": "表", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "表の列を切り替える", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "表の列を切り替える", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "メタフィールドの有無でフィルタリングできません", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "スクリプトフィールドの有無でフィルタリングできません", - "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "インデックスがないフィールドまたは無視された値は検索できません", - "unifiedDocViewer.docViews.table.unpinFieldLabel": "フィールドを固定解除", - "unifiedDocViewer.fieldChooser.discoverField.actions": "アクション", - "unifiedDocViewer.fieldChooser.discoverField.multiField": "複数フィールド", - "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "複数フィールドにはフィールドごとに複数の値を入力できます", - "unifiedDocViewer.fieldChooser.discoverField.name": "フィールド", - "unifiedDocViewer.fieldChooser.discoverField.value": "値", - "unifiedDocViewer.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", - "unifiedDocViewer.json.copyToClipboardLabel": "クリップボードにコピー", - "unifiedDocViewer.loadingJSON": "JSONを読み込んでいます", - "unifiedDocViewer.sourceViewer.errorMessage": "現在データを取得できませんでした。タブを更新して、再試行してください。", - "unifiedDocViewer.sourceViewer.errorMessageTitle": "エラーが発生しました", - "unifiedDocViewer.sourceViewer.refresh": "更新", "unsavedChangesBadge.contextMenu.openButton": "使用可能なアクションを表示", "unsavedChangesBadge.contextMenu.revertChangesButton": "変更を元に戻す", "unsavedChangesBadge.contextMenu.revertingChangesButtonStatus": "変更を元に戻しています...", @@ -44586,4 +44585,4 @@ "xpack.serverlessObservability.nav.projectSettings": "プロジェクト設定", "xpack.serverlessObservability.nav.synthetics": "Synthetics" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 51315fd4ef8ca..f3d3b3409f593 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2397,12 +2397,7 @@ "discover.fieldChooser.discoverField.removeFieldTooltip": "从表中移除字段", "discover.globalSearch.esqlSearchTitle": "创建 ES|QL 查询", "discover.goToDiscoverButtonText": "前往 Discover", - "unifiedDocViewer.flyout.documentNavigation": "文档导航", - "unifiedDocViewer.flyout.toastColumnAdded": "已添加列“{columnName}”", - "unifiedDocViewer.flyout.toastColumnRemoved": "已移除列“{columnName}”", "discover.grid.tableRow.actionsLabel": "操作", - "unifiedDocViewer.flyout.docViewerDetailHeading": "文档", - "unifiedDocViewer.flyout.docViewerEsqlDetailHeading": "行", "discover.grid.tableRow.mobileFlyoutActionsButton": "操作", "discover.grid.tableRow.moreFlyoutActionsButton": "更多操作", "discover.grid.tableRow.esqlDetailHeading": "已展开行", @@ -2495,6 +2490,46 @@ "discover.viewAlert.searchSourceErrorTitle": "提取搜索源时出错", "discover.viewModes.document.label": "文档", "discover.viewModes.fieldStatistics.label": "字段统计信息", + "unifiedDocViewer.flyout.documentNavigation": "文档导航", + "unifiedDocViewer.flyout.toastColumnAdded": "已添加列“{columnName}”", + "unifiedDocViewer.flyout.toastColumnRemoved": "已移除列“{columnName}”", + "unifiedDocViewer.flyout.docViewerDetailHeading": "文档", + "unifiedDocViewer.flyout.docViewerEsqlDetailHeading": "行", + "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "此字段中的一个或多个值过长,无法搜索或筛选。", + "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "此字段包含一个或多个格式错误的值,无法搜索或筛选。", + "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "此字段中的一个或多个值被 Elasticsearch 忽略,无法搜索或筛选。", + "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "此字段中的值过长,无法搜索或筛选。", + "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "此字段中的值格式错误,无法搜索或筛选。", + "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "此字段中的值被 Elasticsearch 忽略,无法搜索或筛选。", + "unifiedDocViewer.docView.table.searchPlaceHolder": "搜索字段名称", + "unifiedDocViewer.docViews.json.jsonTitle": "JSON", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "筛留存在的字段", + "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "字段是否存在筛选", + "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "筛留值", + "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "筛留值", + "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "筛除值", + "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "筛除值", + "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "包含被忽略的值", + "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "被忽略的值", + "unifiedDocViewer.docViews.table.pinFieldLabel": "固定字段", + "unifiedDocViewer.docViews.table.tableTitle": "表", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "在表中切换列", + "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "在表中切换列", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "无法筛选元数据字段是否存在", + "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "无法筛选脚本字段是否存在", + "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "无法搜索未编入索引的字段或被忽略的值", + "unifiedDocViewer.docViews.table.unpinFieldLabel": "取消固定字段", + "unifiedDocViewer.fieldChooser.discoverField.actions": "操作", + "unifiedDocViewer.fieldChooser.discoverField.multiField": "多字段", + "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "多字段的每个字段可以有多个值", + "unifiedDocViewer.fieldChooser.discoverField.name": "字段", + "unifiedDocViewer.fieldChooser.discoverField.value": "值", + "unifiedDocViewer.json.codeEditorAriaLabel": "Elasticsearch 文档的只读 JSON 视图", + "unifiedDocViewer.json.copyToClipboardLabel": "复制到剪贴板", + "unifiedDocViewer.loadingJSON": "正在加载 JSON", + "unifiedDocViewer.sourceViewer.errorMessage": "当前无法获取数据。请刷新选项卡以重试。", + "unifiedDocViewer.sourceViewer.errorMessageTitle": "发生错误", + "unifiedDocViewer.sourceViewer.refresh": "刷新", "textBasedLanguages.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", "textBasedLanguages.advancedSettings.enableESQLTitle": "启用 ES|QL", "domDragDrop.announce.cancelled": "移动已取消。{label} 将返回至其初始位置", @@ -43923,7 +43958,6 @@ "aiAssistantManagementSelection.aiAssistantSelectionPage.observabilityLabel": "适用于 Observability 的 Elastic AI 助手", "aiAssistantManagementSelection.aiAssistantSettingsPage.descriptionTextLabel": "通过解释错误,建议补救措施并帮助您请求、分析和可视化数据,AI 助手使用生成式 AI 来为您的团队提供帮助。", "aiAssistantManagementSelection.aiAssistantSettingsPage.h2.aIAssistantLabel": "AI 助手", - "aiAssistantManagementSelection.aiAssistantSettingsPage.obsAssistant.documentationLinkLabel": "文档", "aiAssistantManagementSelection.app.description": "管理您的 AI 助手。", "aiAssistantManagementSelection.app.title": "AI 助手", "aiAssistantManagementSelection.app.titleBar": "AI 助手", @@ -44247,41 +44281,6 @@ "uiActions.errors.incompatibleAction": "操作不兼容", "uiActions.triggers.rowClickkDescription": "表格行的单击", "uiActions.triggers.rowClickTitle": "表格行单击", - "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "此字段中的一个或多个值过长,无法搜索或筛选。", - "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "此字段包含一个或多个格式错误的值,无法搜索或筛选。", - "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "此字段中的一个或多个值被 Elasticsearch 忽略,无法搜索或筛选。", - "unifiedDocViewer.docView.table.ignored.singleAboveTooltip": "此字段中的值过长,无法搜索或筛选。", - "unifiedDocViewer.docView.table.ignored.singleMalformedTooltip": "此字段中的值格式错误,无法搜索或筛选。", - "unifiedDocViewer.docView.table.ignored.singleUnknownTooltip": "此字段中的值被 Elasticsearch 忽略,无法搜索或筛选。", - "unifiedDocViewer.docView.table.searchPlaceHolder": "搜索字段名称", - "unifiedDocViewer.docViews.json.jsonTitle": "JSON", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel": "筛留存在的字段", - "unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip": "字段是否存在筛选", - "unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel": "筛留值", - "unifiedDocViewer.docViews.table.filterForValueButtonTooltip": "筛留值", - "unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel": "筛除值", - "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "筛除值", - "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "包含被忽略的值", - "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "被忽略的值", - "unifiedDocViewer.docViews.table.pinFieldLabel": "固定字段", - "unifiedDocViewer.docViews.table.tableTitle": "表", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "在表中切换列", - "unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip": "在表中切换列", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "无法筛选元数据字段是否存在", - "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "无法筛选脚本字段是否存在", - "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "无法搜索未编入索引的字段或被忽略的值", - "unifiedDocViewer.docViews.table.unpinFieldLabel": "取消固定字段", - "unifiedDocViewer.fieldChooser.discoverField.actions": "操作", - "unifiedDocViewer.fieldChooser.discoverField.multiField": "多字段", - "unifiedDocViewer.fieldChooser.discoverField.multiFieldTooltipContent": "多字段的每个字段可以有多个值", - "unifiedDocViewer.fieldChooser.discoverField.name": "字段", - "unifiedDocViewer.fieldChooser.discoverField.value": "值", - "unifiedDocViewer.json.codeEditorAriaLabel": "Elasticsearch 文档的只读 JSON 视图", - "unifiedDocViewer.json.copyToClipboardLabel": "复制到剪贴板", - "unifiedDocViewer.loadingJSON": "正在加载 JSON", - "unifiedDocViewer.sourceViewer.errorMessage": "当前无法获取数据。请刷新选项卡以重试。", - "unifiedDocViewer.sourceViewer.errorMessageTitle": "发生错误", - "unifiedDocViewer.sourceViewer.refresh": "刷新", "unsavedChangesBadge.contextMenu.openButton": "查看可用操作", "unsavedChangesBadge.contextMenu.revertChangesButton": "恢复更改", "unsavedChangesBadge.contextMenu.revertingChangesButtonStatus": "正在恢复更改......", @@ -44634,4 +44633,4 @@ "xpack.serverlessObservability.nav.projectSettings": "项目设置", "xpack.serverlessObservability.nav.synthetics": "Synthetics" } -} \ No newline at end of file +} From ff48987efb63016d4d7c30020e0b9d9220f24587 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 27 Jun 2024 14:57:34 +0100 Subject: [PATCH 34/37] add pageSizeOptions --- .../conversation_settings/conversation_settings_editor.tsx | 4 ++-- .../conversations/conversation_settings_management/index.tsx | 1 + .../system_prompt/system_prompt_settings_management/index.tsx | 1 + .../quick_prompts/quick_prompt_settings_management/index.tsx | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) 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 1e854674d458c..a0ddd33b34d05 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 @@ -79,7 +79,7 @@ export const ConversationSettingsEditor: React.FC = ({ () => ({ pageIndex: DEFAULT_PAGE_INDEX, pageSize: DEFAULT_PAGE_SIZE, + pageSizeOptions: [DEFAULT_PAGE_SIZE], totalItemCount: conversationOptions.length, }), [conversationOptions.length] 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 35168fc4d8e86..0cd21113178c7 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 @@ -156,6 +156,7 @@ const SystemPromptSettingsManagementComponent = ({ () => ({ pageIndex: DEFAULT_PAGE_INDEX, pageSize: DEFAULT_PAGE_SIZE, + pageSizeOptions: [DEFAULT_PAGE_SIZE], totalItemCount: systemPromptSettings.length, }), [systemPromptSettings.length] 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 55a7f10958bf5..c98a7357825cf 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 @@ -123,6 +123,7 @@ const QuickPromptSettingsManagementComponent = ({ () => ({ pageIndex: DEFAULT_PAGE_INDEX, pageSize: DEFAULT_PAGE_SIZE, + pageSizeOptions: [DEFAULT_PAGE_SIZE], totalItemCount: quickPromptSettings.length, }), [quickPromptSettings.length] From 60f271e50f0803f652b031136df413c130a918fc Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 27 Jun 2024 22:39:12 +0100 Subject: [PATCH 35/37] fix typo and pagination --- .../ai_assistant_selection_page.tsx | 2 +- .../use_conveersation_changed.test.tsx | 125 ++++++++++++++++++ .../use_conversation_deleted.tsx | 12 +- .../use_conversation_deletex.test.tsx | 94 +++++++++++++ .../index.tsx | 12 +- .../system_prompt_column.tsx | 38 ------ .../translations.ts | 13 +- .../use_conversations_table.tsx | 35 ++++- .../index.tsx | 10 +- .../use_system_prompt_table.tsx | 104 ++++++++------- .../index.tsx | 10 +- .../use_quick_prompt_table.tsx | 1 + .../impl/assistant/settings/const.ts | 1 - .../translations.ts | 2 +- 14 files changed, 337 insertions(+), 122 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conveersation_changed.test.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deletex.test.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/system_prompt_column.tsx 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 1d5b2ed2b1b3b..9b0941d86a5d4 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 @@ -189,7 +189,7 @@ export function AiAssistantSelectionPage() { isDisabled={!securityAIAssistantEnabled} title={i18n.translate( 'aiAssistantManagementSelection.aiAssistantSelectionPage.securityLabel', - { defaultMessage: 'Elastic AI for Security' } + { defaultMessage: 'Elastic AI Assistant for Security' } )} titleSize="xs" /> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conveersation_changed.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conveersation_changed.test.tsx new file mode 100644 index 0000000000000..091c691d8e324 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conveersation_changed.test.tsx @@ -0,0 +1,125 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; +import { useConversationChanged } from './use_conversation_changed'; +import { customConvo } from '../../../mock/conversation'; +import { mockConnectors } from '../../../mock/connectors'; +import { mockSystemPrompts } from '../../../mock/system_prompt'; +import { getDefaultSystemPrompt } from '../../use_conversation/helpers'; +import { Conversation, ConversationsBulkActions } from '../../../..'; + +jest.mock('../../use_conversation/helpers', () => ({ + getDefaultSystemPrompt: jest.fn(), +})); + +const mockAllSystemPrompts = mockSystemPrompts; + +const mockDefaultConnector = mockConnectors[0]; + +const mockConversationSettings = {}; +const mockConversationsSettingsBulkActions: ConversationsBulkActions = {}; +const mockSetConversationSettings = jest.fn(); +const mockSetConversationsSettingsBulkActions = jest.fn(); +const mockOnSelectedConversationChange = jest.fn(); + +describe('useConversationChanged', () => { + beforeEach(() => { + jest.clearAllMocks(); + (getDefaultSystemPrompt as jest.Mock).mockReturnValue(mockAllSystemPrompts[2]); + }); + + test('should return a function', () => { + const { result } = renderHook(() => + useConversationChanged({ + allSystemPrompts: mockAllSystemPrompts, + conversationSettings: mockConversationSettings, + conversationsSettingsBulkActions: mockConversationsSettingsBulkActions, + defaultConnector: mockDefaultConnector, + setConversationSettings: mockSetConversationSettings, + setConversationsSettingsBulkActions: mockSetConversationsSettingsBulkActions, + onSelectedConversationChange: mockOnSelectedConversationChange, + }) + ); + + expect(typeof result.current).toBe('function'); + }); + + test('should handle new conversation selection', () => { + const newConversationTitle = 'New Conversation'; + const { result } = renderHook(() => + useConversationChanged({ + allSystemPrompts: mockAllSystemPrompts, + conversationSettings: mockConversationSettings, + conversationsSettingsBulkActions: mockConversationsSettingsBulkActions, + defaultConnector: mockDefaultConnector, + setConversationSettings: mockSetConversationSettings, + setConversationsSettingsBulkActions: mockSetConversationsSettingsBulkActions, + onSelectedConversationChange: mockOnSelectedConversationChange, + }) + ); + + act(() => { + result.current(newConversationTitle); + }); + + const expectedNewConversation: Conversation = { + id: '', + title: newConversationTitle, + category: 'assistant', + messages: [], + replacements: {}, + apiConfig: { + connectorId: mockDefaultConnector.id, + actionTypeId: mockDefaultConnector.actionTypeId, + provider: mockDefaultConnector.apiProvider, + defaultSystemPromptId: mockAllSystemPrompts[2].id, + }, + }; + + expect(mockSetConversationSettings).toHaveBeenCalledWith({ + ...mockConversationSettings, + [newConversationTitle]: expectedNewConversation, + }); + expect(mockSetConversationsSettingsBulkActions).toHaveBeenCalledWith({ + ...mockConversationsSettingsBulkActions, + create: { + ...(mockConversationsSettingsBulkActions.create ?? {}), + [newConversationTitle]: expectedNewConversation, + }, + }); + expect(mockOnSelectedConversationChange).toHaveBeenCalledWith({ + ...expectedNewConversation, + id: expectedNewConversation.title, + }); + }); + + test('should handle existing conversation selection', () => { + const existingConversation = { ...customConvo, id: 'mock-id' }; + + const { result } = renderHook(() => + useConversationChanged({ + allSystemPrompts: mockAllSystemPrompts, + conversationSettings: mockConversationSettings, + conversationsSettingsBulkActions: mockConversationsSettingsBulkActions, + defaultConnector: mockDefaultConnector, + setConversationSettings: mockSetConversationSettings, + setConversationsSettingsBulkActions: mockSetConversationsSettingsBulkActions, + onSelectedConversationChange: mockOnSelectedConversationChange, + }) + ); + + act(() => { + result.current(existingConversation); + }); + + expect(mockSetConversationSettings.mock.calls[0][0](mockConversationSettings)).toEqual({ + ...mockConversationSettings, + [existingConversation.id]: existingConversation, + }); + expect(mockOnSelectedConversationChange).toHaveBeenCalledWith(existingConversation); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deleted.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deleted.tsx index 855f08d588e48..c8e5fcfbab01e 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deleted.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deleted.tsx @@ -25,12 +25,18 @@ export const useConversationDeleted = ({ }: Props) => { const onConversationDeleted = useCallback( (conversationTitle: string) => { - const conversationId = - Object.values(conversationSettings).find((c) => c.title === conversationTitle)?.id ?? ''; + const conversationId = Object.values(conversationSettings).find( + (c) => c.title === conversationTitle + )?.id; + // If matching conversation is not found, do nothing + if (!conversationId) { + return; + } + const updatedConversationSettings = { ...conversationSettings }; delete updatedConversationSettings[conversationId]; - setConversationSettings(updatedConversationSettings); + setConversationSettings(updatedConversationSettings); setConversationsSettingsBulkActions({ ...conversationsSettingsBulkActions, delete: { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deletex.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deletex.test.tsx new file mode 100644 index 0000000000000..f96de69b8ae7c --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/use_conversation_deletex.test.tsx @@ -0,0 +1,94 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; +import { useConversationDeleted } from './use_conversation_deleted'; +import { customConvo, alertConvo, welcomeConvo } from '../../../mock/conversation'; +import { Conversation, ConversationsBulkActions } from '../../../..'; + +const customConveId = '1'; +const alertConvoId = '2'; +const welcomeConvoId = '3'; +const mockConversationSettings: Record = { + [customConveId]: { ...customConvo, id: customConveId }, + [alertConvoId]: { ...alertConvo, id: alertConvoId }, + [welcomeConvoId]: { ...welcomeConvo, id: welcomeConvoId }, +}; + +const mockConversationsSettingsBulkActions: ConversationsBulkActions = { + create: {}, + update: {}, + delete: { ids: [] }, +}; + +const mockSetConversationSettings = jest.fn(); +const mockSetConversationsSettingsBulkActions = jest.fn(); + +describe('useConversationDeleted', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should return a function', () => { + const { result } = renderHook(() => + useConversationDeleted({ + conversationSettings: mockConversationSettings, + conversationsSettingsBulkActions: mockConversationsSettingsBulkActions, + setConversationSettings: mockSetConversationSettings, + setConversationsSettingsBulkActions: mockSetConversationsSettingsBulkActions, + }) + ); + + expect(typeof result.current).toBe('function'); + }); + + test('should handle conversation deletion', () => { + const conversationTitleToDelete = customConvo.title; + const { result } = renderHook(() => + useConversationDeleted({ + conversationSettings: mockConversationSettings, + conversationsSettingsBulkActions: mockConversationsSettingsBulkActions, + setConversationSettings: mockSetConversationSettings, + setConversationsSettingsBulkActions: mockSetConversationsSettingsBulkActions, + }) + ); + + act(() => { + result.current(conversationTitleToDelete); + }); + + const expectedConversationSettings = { ...mockConversationSettings }; + delete expectedConversationSettings[customConveId]; + expect(mockSetConversationSettings).toHaveBeenCalledWith(expectedConversationSettings); + + expect(mockSetConversationsSettingsBulkActions).toHaveBeenCalledWith({ + ...mockConversationsSettingsBulkActions, + delete: { + ids: [customConveId], + }, + }); + }); + + test('should do nothing when no matching conversation title exists', () => { + const conversationTitleToDelete = 'Non-existent Conversation'; + const { result } = renderHook(() => + useConversationDeleted({ + conversationSettings: mockConversationSettings, + conversationsSettingsBulkActions: mockConversationsSettingsBulkActions, + setConversationSettings: mockSetConversationSettings, + setConversationsSettingsBulkActions: mockSetConversationsSettingsBulkActions, + }) + ); + + act(() => { + result.current(conversationTitleToDelete); + }); + + expect(mockSetConversationSettings).not.toHaveBeenCalled(); + + expect(mockSetConversationsSettingsBulkActions).not.toHaveBeenCalled(); + }); +}); 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 bbec3fd480ac9..cffafc63bbbf6 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 @@ -24,7 +24,7 @@ import { Flyout } from '../../common/components/assistant_settings_management/fl import { CANCEL, DELETE } from '../../settings/translations'; import { ConversationSettingsEditor } from '../conversation_settings/conversation_settings_editor'; import { useConversationChanged } from '../conversation_settings/use_conversation_changed'; -import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from '../../settings/const'; +import { DEFAULT_PAGE_SIZE } from '../../settings/const'; interface Props { actionTypeRegistry: ActionTypeRegistryContract; @@ -189,12 +189,10 @@ const ConversationSettingsManagementComponent: React.FC = ({ const pagination = useMemo( () => ({ - pageIndex: DEFAULT_PAGE_INDEX, - pageSize: DEFAULT_PAGE_SIZE, - pageSizeOptions: [DEFAULT_PAGE_SIZE], - totalItemCount: conversationOptions.length, + initialPageSize: DEFAULT_PAGE_SIZE, + pageSizeOptions: [10, DEFAULT_PAGE_SIZE, 50], }), - [conversationOptions.length] + [] ); if (!conversationsLoaded) { @@ -223,7 +221,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ onClose={onSaveCancelled} onSaveConfirmed={onSaveConfirmed} onSaveCancelled={onSaveCancelled} - title={selectedConversation?.title ?? i18n.CONVERSATIONS_TABLE_COLUMN_CONVERSATIONS} + title={selectedConversation?.title ?? i18n.CONVERSATIONS_FLYOUT_DEFAULT_TITLE} > = ({ allSystemPrompts, conversation }) => { - const systemPrompt: Prompt | undefined = allSystemPrompts.find( - ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId - ); - - const defaultSystemPrompt = getInitialDefaultSystemPrompt({ - allSystemPrompts, - conversation, - }); - - const systemPromptTitle = - systemPrompt?.label || - systemPrompt?.name || - defaultSystemPrompt?.label || - defaultSystemPrompt?.name; - - return systemPromptTitle ? {systemPromptTitle} : null; -}; - -SystemPromptColumn.displayName = 'SystemPromptColumn'; 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 9b02ec8015b2c..5761ba8e6e99f 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,10 @@ import { i18n } from '@kbn/i18n'; -export const CONVERSATIONS_TABLE_COLUMN_CONVERSATIONS = i18n.translate( - 'xpack.elasticAssistant.assistant.conversationSettings.column.conversations', +export const CONVERSATIONS_TABLE_COLUMN_NAME = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.column.name', { - defaultMessage: 'Conversations', + defaultMessage: 'Name', } ); @@ -42,6 +42,13 @@ export const CONVERSATIONS_TABLE_COLUMN_ACTIONS = i18n.translate( } ); +export const CONVERSATIONS_FLYOUT_DEFAULT_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.conversationSettings.flyout.defaultTitle', + { + defaultMessage: 'Conversation', + } +); + export const DELETE_CONVERSATION_CONFIRMATION_DEFAULT_TITLE = i18n.translate( 'xpack.elasticAssistant.assistant.conversationSettings.deleteConfirmation.defaultTitle', { 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 9c134ea4d5b01..3debd5c75f564 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 @@ -14,10 +14,12 @@ import { Conversation } from '../../../assistant_context/types'; import { AIConnector } from '../../../connectorland/connector_selector'; import { getConnectorTypeTitle } from '../../../connectorland/helpers'; import { Prompt } from '../../../..'; -import { getConversationApiConfig } from '../../use_conversation/helpers'; +import { + getConversationApiConfig, + getInitialDefaultSystemPrompt, +} from '../../use_conversation/helpers'; import * as i18n from './translations'; import { RowActions } from '../../common/components/assistant_settings_management/row_actions'; -import { SystemPromptColumn } from './system_prompt_column'; const emptyConversations = {}; @@ -31,6 +33,7 @@ export interface GetConversationsListParams { export type ConversationTableItem = Conversation & { actionType?: string | null; + systemPromptTitle?: string | null; }; export const useConversationsTable = () => { @@ -42,19 +45,21 @@ export const useConversationsTable = () => { }): Array> => { return [ { - name: i18n.CONVERSATIONS_TABLE_COLUMN_CONVERSATIONS, + name: i18n.CONVERSATIONS_TABLE_COLUMN_NAME, render: (conversation: ConversationTableItem) => ( onEditActionClicked(conversation)}> {conversation.title} ), + sortable: ({ title }: ConversationTableItem) => title, }, { + field: 'systemPromptTitle', name: i18n.CONVERSATIONS_TABLE_COLUMN_SYSTEM_PROMPT, align: 'left', - render: (conversation: ConversationTableItem) => ( - - ), + render: (systemPromptTitle: ConversationTableItem['systemPromptTitle']) => + systemPromptTitle ? {systemPromptTitle} : null, + sortable: true, }, { field: 'actionType', @@ -62,6 +67,7 @@ export const useConversationsTable = () => { align: 'left', render: (actionType: ConversationTableItem['actionType']) => actionType ? {actionType} : null, + sortable: true, }, { field: 'updatedAt', @@ -78,6 +84,7 @@ export const useConversationsTable = () => { /> ) : null, + sortable: true, }, { name: i18n.CONVERSATIONS_TABLE_COLUMN_ACTIONS, @@ -120,9 +127,25 @@ export const useConversationsTable = () => { const actionType = getConnectorTypeTitle(connector, actionTypeRegistry); + const systemPrompt: Prompt | undefined = allSystemPrompts.find( + ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId + ); + + const defaultSystemPrompt = getInitialDefaultSystemPrompt({ + allSystemPrompts, + conversation, + }); + + const systemPromptTitle = + systemPrompt?.label || + systemPrompt?.name || + defaultSystemPrompt?.label || + defaultSystemPrompt?.name; + return { ...conversation, actionType, + systemPromptTitle, ...conversationApiConfig, }; }); 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 0cd21113178c7..e77acc1ea04da 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 @@ -20,7 +20,7 @@ import { Conversation, ConversationsBulkActions } from '../../../../..'; import { AIConnector } from '../../../../connectorland/connector_selector'; import { Flyout } from '../../../common/components/assistant_settings_management/flyout'; import { useFlyoutModalVisibility } from '../../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; -import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from '../../../settings/const'; +import { DEFAULT_PAGE_SIZE } from '../../../settings/const'; import { CANCEL, DELETE } from '../../../settings/translations'; import { Prompt } from '../../../types'; import { SystemPromptEditor } from '../system_prompt_modal/system_prompt_editor'; @@ -154,12 +154,10 @@ const SystemPromptSettingsManagementComponent = ({ const pagination = useMemo( () => ({ - pageIndex: DEFAULT_PAGE_INDEX, - pageSize: DEFAULT_PAGE_SIZE, - pageSizeOptions: [DEFAULT_PAGE_SIZE], - totalItemCount: systemPromptSettings.length, + initialPageSize: DEFAULT_PAGE_SIZE, + pageSizeOptions: [10, DEFAULT_PAGE_SIZE, 50], }), - [systemPromptSettings.length] + [] ); return ( <> 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 1b8b40ab617ce..7cf907bb7adf5 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 @@ -5,7 +5,7 @@ * 2.0. */ import { EuiBasicTableColumn, EuiIcon, EuiLink } from '@elastic/eui'; -import React from 'react'; +import React, { useCallback } from 'react'; import { Conversation } from '../../../../assistant_context/types'; import { AIConnector } from '../../../../connectorland/connector_selector'; import { BadgesColumn } from '../../../common/components/assistant_settings_management/badges'; @@ -27,62 +27,66 @@ type ConversationsWithSystemPrompt = Record< type SystemPromptTableItem = Prompt & { defaultConversations: string[] }; export const useSystemPromptTable = () => { - const getColumns = ({ - onEditActionClicked, - onDeleteActionClicked, - }: { - onEditActionClicked: (prompt: SystemPromptTableItem) => void; - onDeleteActionClicked: (prompt: SystemPromptTableItem) => void; - }): Array> => [ - { - align: 'left', - name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_NAME, - truncateText: { lines: 3 }, - render: (prompt: SystemPromptTableItem) => - prompt?.name ? ( - onEditActionClicked(prompt)}> - {prompt?.name} - {prompt.isNewConversationDefault && ( - - )} - - ) : null, - }, - { - align: 'left', - name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, - render: ({ defaultConversations, id }: SystemPromptTableItem) => ( - - ), - }, - /* TODO: enable when createdAt is added + const getColumns = useCallback( + ({ + onEditActionClicked, + onDeleteActionClicked, + }: { + onEditActionClicked: (prompt: SystemPromptTableItem) => void; + onDeleteActionClicked: (prompt: SystemPromptTableItem) => void; + }): Array> => [ + { + align: 'left', + name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_NAME, + truncateText: { lines: 3 }, + render: (prompt: SystemPromptTableItem) => + prompt?.name ? ( + onEditActionClicked(prompt)}> + {prompt?.name} + {prompt.isNewConversationDefault && ( + + )} + + ) : null, + sortable: ({ name }: SystemPromptTableItem) => name, + }, + { + align: 'left', + name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS, + render: ({ defaultConversations, id }: SystemPromptTableItem) => ( + + ), + }, + /* TODO: enable when createdAt is added { align: 'left', field: 'createdAt', name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_CREATED_ON, }, */ - { - align: 'center', - name: 'Actions', - width: '120px', - render: (prompt: SystemPromptTableItem) => { - const isDeletable = !prompt.isDefault; - return ( - - rowItem={prompt} - onEdit={onEditActionClicked} - onDelete={isDeletable ? onDeleteActionClicked : undefined} - isDeletable={isDeletable} - /> - ); + { + align: 'center', + name: 'Actions', + width: '120px', + render: (prompt: SystemPromptTableItem) => { + const isDeletable = !prompt.isDefault; + return ( + + rowItem={prompt} + onEdit={onEditActionClicked} + onDelete={isDeletable ? onDeleteActionClicked : undefined} + isDeletable={isDeletable} + /> + ); + }, }, - }, - ]; + ], + [] + ); const getSystemPromptsList = ({ connectors, 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 c98a7357825cf..873b8b033bce8 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 @@ -22,7 +22,7 @@ import { Flyout } from '../../common/components/assistant_settings_management/fl import { CANCEL, DELETE } from '../../settings/translations'; import { useQuickPromptEditor } from '../quick_prompt_settings/use_quick_prompt_editor'; import { useQuickPromptTable } from './use_quick_prompt_table'; -import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from '../../settings/const'; +import { DEFAULT_PAGE_SIZE } from '../../settings/const'; import { PromptContextTemplate } from '../../prompt_context/types'; interface Props { @@ -121,12 +121,10 @@ const QuickPromptSettingsManagementComponent = ({ const pagination = useMemo( () => ({ - pageIndex: DEFAULT_PAGE_INDEX, - pageSize: DEFAULT_PAGE_SIZE, - pageSizeOptions: [DEFAULT_PAGE_SIZE], - totalItemCount: quickPromptSettings.length, + initialPageSize: DEFAULT_PAGE_SIZE, + pageSizeOptions: [10, DEFAULT_PAGE_SIZE, 50], }), - [quickPromptSettings.length] + [] ); return ( <> 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 2d58ddb902bbe..9ec334f817340 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 @@ -31,6 +31,7 @@ export const useQuickPromptTable = () => { prompt?.title ? ( onEditActionClicked(prompt)}>{prompt?.title} ) : null, + sortable: ({ title }: QuickPrompt) => title, }, { align: 'left', diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts index 8bd5542612e8e..c61a6dda8d235 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/const.ts @@ -13,4 +13,3 @@ export const KNOWLEDGE_BASE_TAB = 'KNOWLEDGE_BASE_TAB' as const; export const EVALUATION_TAB = 'EVALUATION_TAB' as const; export const DEFAULT_PAGE_SIZE = 25; -export const DEFAULT_PAGE_INDEX = 0; 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 c564338df0913..941337761fadc 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 @@ -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 OpenIA or Bedrock large language models. ', + 'Using the Elastic AI Assistant requires setting up a connector with API access to OpenAI or Bedrock large language models. ', } ); From e63e692dd401a9a88b745972a3c419b316d481c6 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 28 Jun 2024 17:41:53 +0100 Subject: [PATCH 36/37] store pagination and sorting in seession --- .../flyout/index.tsx | 9 +- .../pagination/use_session_pagination.ts | 65 +++++++++++++ .../row_actions/index.tsx | 8 +- .../index.tsx | 42 ++++----- .../use_conversations_table.test.tsx | 91 +++++++++++++++++++ .../use_conversations_table.tsx | 28 +++--- .../index.tsx | 32 ++++--- .../index.tsx | 33 ++++--- .../assistant_settings_management.tsx | 5 - .../use_settings_updater.tsx | 1 + .../impl/assistant_context/constants.tsx | 4 + .../context_editor/index.tsx | 24 +++-- 12 files changed, 261 insertions(+), 81 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.test.tsx 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 0340667241d0a..e749fb483d504 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 @@ -16,6 +16,7 @@ import { EuiFlyoutHeader, EuiTitle, } from '@elastic/eui'; +import { css } from '@emotion/react'; import React from 'react'; import * as i18n from './translations'; @@ -37,7 +38,13 @@ const FlyoutComponent: React.FC = ({ onSaveConfirmed, }) => { return flyoutVisible ? ( - +

{title}

diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts new file mode 100644 index 0000000000000..daaccc1fc6992 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/common/components/assistant_settings_management/pagination/use_session_pagination.ts @@ -0,0 +1,65 @@ +/* + * 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 { Direction } from '@elastic/eui'; +import { useCallback, useMemo } from 'react'; +import useSessionStorage from 'react-use/lib/useSessionStorage'; +import { DEFAULT_ASSISTANT_NAMESPACE } from '../../../../../assistant_context/constants'; +import { DEFAULT_PAGE_SIZE } from '../../../../settings/const'; + +export const DEFAULT_TABLE_OPTIONS = { + page: { size: DEFAULT_PAGE_SIZE, index: 0 }, + sort: { field: '', direction: 'asc' as const }, +}; + +export const useSessionPagination = ({ + defaultTableOptions, + nameSpace = DEFAULT_ASSISTANT_NAMESPACE, + storageKey, +}: { + defaultTableOptions: { + page: { size: number; index: number }; + sort: { field: string; direction: Direction }; + }; + nameSpace?: string; + storageKey: string; +}) => { + const [sessionStorageTableOptions = defaultTableOptions, setSessionStorageTableOptions] = + useSessionStorage(`${nameSpace}.${storageKey}`, defaultTableOptions); + + const pagination = useMemo( + () => ({ + initialPageSize: sessionStorageTableOptions.page.size, + pageSizeOptions: [5, 10, DEFAULT_PAGE_SIZE, 50], + pageIndex: sessionStorageTableOptions.page.index, + }), + [sessionStorageTableOptions] + ); + + const sorting = useMemo( + () => ({ + sort: sessionStorageTableOptions.sort, + }), + [sessionStorageTableOptions.sort] + ); + + const onTableChange = useCallback( + ({ page, sort }) => { + setSessionStorageTableOptions({ + page, + sort, + }); + }, + [setSessionStorageTableOptions] + ); + + return { + onTableChange, + pagination, + sorting, + }; +}; 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 index 558d17cd66086..b2d31ec80433f 100644 --- 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 @@ -29,12 +29,12 @@ const RowActionsComponent = ({ const [isPopoverOpen, setIsPopoverOpen] = useState(false); const closePopover = useCallback(() => setIsPopoverOpen(false), []); - const handleEditConnector = useCallback(() => { + const handleEdit = useCallback(() => { closePopover(); onEdit?.(rowItem); }, [closePopover, onEdit, rowItem]); - const handleDeleteConnector = useCallback(() => { + const handleDelete = useCallback(() => { closePopover(); onDelete?.(rowItem); }, [closePopover, onDelete, rowItem]); @@ -59,7 +59,7 @@ const RowActionsComponent = ({ @@ -72,7 +72,7 @@ const RowActionsComponent = ({ 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 cffafc63bbbf6..c4b2f834ecec5 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/index.tsx @@ -8,7 +8,6 @@ import { EuiPanel, EuiSpacer, EuiConfirmModal, EuiInMemoryTable } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; -import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; import { Conversation } from '../../../assistant_context/types'; import { ConversationTableItem, useConversationsTable } from './use_conversations_table'; import { ConversationStreamingSwitch } from '../conversation_settings/conversation_streaming_switch'; @@ -24,10 +23,10 @@ import { Flyout } from '../../common/components/assistant_settings_management/fl import { CANCEL, DELETE } 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'; - interface Props { - actionTypeRegistry: ActionTypeRegistryContract; allSystemPrompts: Prompt[]; assistantStreamingEnabled: boolean; connectors: AIConnector[] | undefined; @@ -48,8 +47,12 @@ interface Props { onSelectedConversationChange: (conversation?: Conversation) => void; } +export const DEFAULT_TABLE_OPTIONS = { + page: { size: DEFAULT_PAGE_SIZE, index: 0 }, + sort: { field: 'createdAt', direction: 'desc' as const }, +}; + const ConversationSettingsManagementComponent: React.FC = ({ - actionTypeRegistry, allSystemPrompts, assistantStreamingEnabled, connectors, @@ -67,7 +70,8 @@ const ConversationSettingsManagementComponent: React.FC = ({ setConversationSettings, setConversationsSettingsBulkActions, }) => { - const { http } = useAssistantContext(); + const { http, nameSpace, actionTypeRegistry } = useAssistantContext(); + const { isFlyoutOpen: editFlyoutVisible, openFlyout: openEditFlyout, @@ -139,6 +143,12 @@ const ConversationSettingsManagementComponent: React.FC = ({ const { getConversationsList, getColumns } = useConversationsTable(); + const { onTableChange, pagination, sorting } = useSessionPagination({ + nameSpace, + storageKey: CONVERSATION_TABLE_SESSION_STORAGE_KEY, + defaultTableOptions: DEFAULT_TABLE_OPTIONS, + }); + const conversationOptions = getConversationsList({ allSystemPrompts, actionTypeRegistry, @@ -164,19 +174,8 @@ const ConversationSettingsManagementComponent: React.FC = ({ conversations: conversationSettings, onDeleteActionClicked, onEditActionClicked, - allSystemPrompts, }), - [allSystemPrompts, conversationSettings, getColumns, onDeleteActionClicked, onEditActionClicked] - ); - - const sorting = useMemo( - () => ({ - sort: { - field: 'updatedAt', - direction: 'desc' as const, - }, - }), - [] + [conversationSettings, getColumns, onDeleteActionClicked, onEditActionClicked] ); const confirmationTitle = useMemo( @@ -187,14 +186,6 @@ const ConversationSettingsManagementComponent: React.FC = ({ [deletedConversation?.title] ); - const pagination = useMemo( - () => ({ - initialPageSize: DEFAULT_PAGE_SIZE, - pageSizeOptions: [10, DEFAULT_PAGE_SIZE, 50], - }), - [] - ); - if (!conversationsLoaded) { return null; } @@ -213,6 +204,7 @@ const ConversationSettingsManagementComponent: React.FC = ({ columns={columns} pagination={pagination} sorting={sorting} + onTableChange={onTableChange} /> {editFlyoutVisible && ( 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 new file mode 100644 index 0000000000000..465ffa792fb0b --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings_management/use_conversations_table.test.tsx @@ -0,0 +1,91 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { + useConversationsTable, + GetConversationsListParams, + ConversationTableItem, +} from './use_conversations_table'; +import { alertConvo, welcomeConvo, customConvo } from '../../../mock/conversation'; +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 + .fn() + .mockImplementation((id: string) => + mockActionTypes.some((actionType: { id: string }) => actionType.id === id) + ), + get: jest + .fn() + .mockImplementation((id: string) => + mockActionTypes.find((actionType: { id: string }) => actionType.id === id) + ), + list: jest.fn().mockReturnValue(mockActionTypes), + register: jest.fn(), +}; + +describe('useConversationsTable', () => { + it('should return columns', () => { + const { result } = renderHook(() => useConversationsTable()); + const columns = result.current.getColumns({ + onDeleteActionClicked: jest.fn(), + onEditActionClicked: jest.fn(), + }); + + 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); + }); + + it('should return a list of conversations', () => { + const alertConvoId = 'alert-convo-id'; + const welcomeConvoId = 'welcome-convo-id'; + const customConvoId = 'custom-convo-id'; + const params: GetConversationsListParams = { + allSystemPrompts: mockSystemPrompts, + actionTypeRegistry: mockActionTypeRegistry, + connectors: mockConnectors, + conversations: { + [alertConvoId]: { ...alertConvo, id: alertConvoId }, + [welcomeConvoId]: { ...welcomeConvo, id: welcomeConvoId }, + [customConvoId]: { ...customConvo, id: customConvoId }, + }, + defaultConnector: mockConnectors[0], + }; + + const { result } = renderHook(() => useConversationsTable()); + const conversationsList: ConversationTableItem[] = result.current.getConversationsList(params); + + expect(conversationsList).toHaveLength(3); + + expect(conversationsList[0].title).toBe(alertConvo.title); + expect(conversationsList[0].connectorTypeTitle).toBe('OpenAI'); + expect(conversationsList[0].systemPromptTitle).toBe('Mock system prompt'); + + expect(conversationsList[1].title).toBe(welcomeConvo.title); + expect(conversationsList[1].connectorTypeTitle).toBe('OpenAI'); + expect(conversationsList[1].systemPromptTitle).toBe('Mock system prompt'); + + expect(conversationsList[2].title).toBe(customConvo.title); + expect(conversationsList[2].connectorTypeTitle).toBe('OpenAI'); + expect(conversationsList[2].systemPromptTitle).toBe('Mock system prompt'); + }); +}); 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 3debd5c75f564..fb705db6bb33c 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 @@ -6,6 +6,7 @@ */ import React, { useCallback } from 'react'; + import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; import { EuiBadge, EuiBasicTableColumn, EuiLink } from '@elastic/eui'; @@ -32,7 +33,7 @@ export interface GetConversationsListParams { } export type ConversationTableItem = Conversation & { - actionType?: string | null; + connectorTypeTitle?: string | null; systemPromptTitle?: string | null; }; @@ -41,7 +42,6 @@ export const useConversationsTable = () => { ({ onDeleteActionClicked, onEditActionClicked, - allSystemPrompts, }): Array> => { return [ { @@ -62,11 +62,11 @@ export const useConversationsTable = () => { sortable: true, }, { - field: 'actionType', + field: 'connectorTypeTitle', name: i18n.CONVERSATIONS_TABLE_COLUMN_CONNECTOR, align: 'left', - render: (actionType: ConversationTableItem['actionType']) => - actionType ? {actionType} : null, + render: (connectorTypeTitle: ConversationTableItem['connectorTypeTitle']) => + connectorTypeTitle ? {connectorTypeTitle} : null, sortable: true, }, { @@ -113,8 +113,8 @@ export const useConversationsTable = () => { connectors, conversations = emptyConversations, defaultConnector, - }: GetConversationsListParams): ConversationTableItem[] => { - return Object.values(conversations).map((conversation) => { + }: GetConversationsListParams): ConversationTableItem[] => + Object.values(conversations).map((conversation) => { const conversationApiConfig = getConversationApiConfig({ allSystemPrompts, connectors, @@ -124,13 +124,11 @@ export const useConversationsTable = () => { const connector: AIConnector | undefined = connectors?.find( (c) => c.id === conversationApiConfig.apiConfig?.connectorId ); - - const actionType = getConnectorTypeTitle(connector, actionTypeRegistry); + const connectorTypeTitle = getConnectorTypeTitle(connector, actionTypeRegistry); const systemPrompt: Prompt | undefined = allSystemPrompts.find( ({ id }) => id === conversation.apiConfig?.defaultSystemPromptId ); - const defaultSystemPrompt = getInitialDefaultSystemPrompt({ allSystemPrompts, conversation, @@ -144,14 +142,16 @@ export const useConversationsTable = () => { return { ...conversation, - actionType, + connectorTypeTitle, systemPromptTitle, ...conversationApiConfig, }; - }); - }, + }), [] ); - return { getColumns, getConversationsList }; + return { + getColumns, + getConversationsList, + }; }; 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 e77acc1ea04da..e0f27f3fa8c7d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_settings_management/index.tsx @@ -16,11 +16,15 @@ import { } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; -import { Conversation, ConversationsBulkActions } from '../../../../..'; +import { Conversation, ConversationsBulkActions, useAssistantContext } from '../../../../..'; +import { SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../../assistant_context/constants'; import { AIConnector } from '../../../../connectorland/connector_selector'; import { Flyout } from '../../../common/components/assistant_settings_management/flyout'; import { useFlyoutModalVisibility } from '../../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility'; -import { DEFAULT_PAGE_SIZE } from '../../../settings/const'; +import { + DEFAULT_TABLE_OPTIONS, + useSessionPagination, +} from '../../../common/components/assistant_settings_management/pagination/use_session_pagination'; import { CANCEL, DELETE } from '../../../settings/translations'; import { Prompt } from '../../../types'; import { SystemPromptEditor } from '../system_prompt_modal/system_prompt_editor'; @@ -31,7 +35,6 @@ import { useSystemPromptTable } from './use_system_prompt_table'; interface Props { connectors: AIConnector[] | undefined; - conversations: Record; conversationSettings: Record; conversationsSettingsBulkActions: ConversationsBulkActions; onSelectedSystemPromptChange: (systemPrompt?: Prompt) => void; @@ -50,7 +53,6 @@ interface Props { const SystemPromptSettingsManagementComponent = ({ connectors, - conversations, conversationSettings, onSelectedSystemPromptChange, setUpdatedSystemPromptSettings, @@ -64,6 +66,7 @@ const SystemPromptSettingsManagementComponent = ({ onCancelClick, resetSettings, }: Props) => { + const { nameSpace } = useAssistantContext(); const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); const { isFlyoutOpen: deleteConfirmModalVisibility, @@ -137,6 +140,12 @@ const SystemPromptSettingsManagementComponent = ({ const { getColumns, getSystemPromptsList } = useSystemPromptTable(); + const { onTableChange, pagination, sorting } = useSessionPagination({ + defaultTableOptions: DEFAULT_TABLE_OPTIONS, + nameSpace, + storageKey: SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY, + }); + const columns = useMemo( () => getColumns({ onEditActionClicked, onDeleteActionClicked }), [getColumns, onEditActionClicked, onDeleteActionClicked] @@ -152,13 +161,6 @@ const SystemPromptSettingsManagementComponent = ({ [getSystemPromptsList, connectors, conversationSettings, defaultConnector, systemPromptSettings] ); - const pagination = useMemo( - () => ({ - initialPageSize: DEFAULT_PAGE_SIZE, - pageSizeOptions: [10, DEFAULT_PAGE_SIZE, 50], - }), - [] - ); return ( <> @@ -171,7 +173,13 @@ const SystemPromptSettingsManagementComponent = ({ - + void; onCancelClick: () => void; onSelectedQuickPromptChange: (quickPrompt?: QuickPrompt) => void; @@ -37,7 +40,6 @@ interface Props { } const QuickPromptSettingsManagementComponent = ({ - basePromptContexts, handleSave, onCancelClick, onSelectedQuickPromptChange, @@ -46,6 +48,8 @@ const QuickPromptSettingsManagementComponent = ({ selectedQuickPrompt, setUpdatedQuickPromptSettings, }: Props) => { + const { nameSpace, basePromptContexts } = useAssistantContext(); + const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility(); const [deletedQuickPrompt, setDeletedQuickPrompt] = useState(); const { @@ -111,6 +115,12 @@ const QuickPromptSettingsManagementComponent = ({ onDeleteActionClicked, }); + const { onTableChange, pagination, sorting } = useSessionPagination({ + defaultTableOptions: DEFAULT_TABLE_OPTIONS, + nameSpace, + storageKey: QUICK_PROMPT_TABLE_SESSION_STORAGE_KEY, + }); + const confirmationTitle = useMemo( () => deletedQuickPrompt?.title @@ -119,13 +129,6 @@ const QuickPromptSettingsManagementComponent = ({ [deletedQuickPrompt?.title] ); - const pagination = useMemo( - () => ({ - initialPageSize: DEFAULT_PAGE_SIZE, - pageSizeOptions: [10, DEFAULT_PAGE_SIZE, 50], - }), - [] - ); return ( <> @@ -137,7 +140,13 @@ const QuickPromptSettingsManagementComponent = ({ - + = React.memo( selectedConversation: defaultSelectedConversation, }) => { const { - actionTypeRegistry, assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled }, - basePromptContexts, http, selectedSettingsTab, setSelectedSettingsTab, @@ -274,7 +272,6 @@ export const AssistantSettingsManagement: React.FC = React.memo( {selectedSettingsTab === CONNECTORS_TAB && } {selectedSettingsTab === CONVERSATIONS_TAB && ( = React.memo( {selectedSettingsTab === SYSTEM_PROMPTS_TAB && ( = React.memo( )} {selectedSettingsTab === QUICK_PROMPTS_TAB && ( { + // Update conversation settings when conversations are loaded if (conversationsLoaded) { setConversationSettings(conversations); } diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx index d54f6f8b4d28d..0262dbe3ed778 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx @@ -15,6 +15,10 @@ export const LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY = 'lastConversationId'; export const KNOWLEDGE_BASE_LOCAL_STORAGE_KEY = 'knowledgeBase'; export const STREAMING_LOCAL_STORAGE_KEY = 'streaming'; export const TRACE_OPTIONS_SESSION_STORAGE_KEY = 'traceOptions'; +export const CONVERSATION_TABLE_SESSION_STORAGE_KEY = 'conversationTable'; +export const QUICK_PROMPT_TABLE_SESSION_STORAGE_KEY = 'quickPromptTable'; +export const SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY = 'systemPromptTable'; +export const ANONYMIZATION_TABLE_SESSION_STORAGE_KEY = 'anonymizationTable'; /** The default `n` latest alerts, ordered by risk score, sent as context to the assistant */ export const DEFAULT_LATEST_ALERTS = 20; 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 e215c8270746e..13ed9bd866513 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 @@ -17,8 +17,10 @@ import { Toolbar } from './toolbar'; import * as i18n from './translations'; import { BatchUpdateListItem, ContextEditorRow, FIELDS, SortConfig } from './types'; import { useAssistantContext } from '../../assistant_context'; +import { useSessionPagination } from '../../assistant/common/components/assistant_settings_management/pagination/use_session_pagination'; +import { ANONYMIZATION_TABLE_SESSION_STORAGE_KEY } from '../../assistant_context/constants'; -export const DEFAULT_PAGE_SIZE = 10; +const DEFAULT_PAGE_SIZE = 10; const Wrapper = styled.div` > div > .euiSpacer { @@ -33,6 +35,11 @@ const defaultSort: SortConfig = { }, }; +export const DEFAULT_TABLE_OPTIONS = { + page: { size: DEFAULT_PAGE_SIZE, index: 0 }, + ...defaultSort, +}; + export interface Props { anonymizationFields: FindAnonymizationFieldsResponse; compressed?: boolean; @@ -69,6 +76,7 @@ const ContextEditorComponent: React.FC = ({ const isAllSelected = useRef(false); // Must be a ref and not state in order not to re-render `selectionValue`, which fires `onSelectionChange` twice const { assistantAvailability: { hasUpdateAIAssistantAnonymization }, + nameSpace, } = useAssistantContext(); const [selected, setSelection] = useState([]); const selectionValue: EuiTableSelectionType = useMemo( @@ -108,12 +116,11 @@ const ContextEditorComponent: React.FC = ({ setSelection(rows); }, [rows]); - const pagination = useMemo(() => { - return { - initialPageSize: pageSize, - pageSizeOptions: [5, DEFAULT_PAGE_SIZE, 25, 50], - }; - }, [pageSize]); + const { onTableChange, pagination, sorting } = useSessionPagination({ + defaultTableOptions: DEFAULT_TABLE_OPTIONS, + nameSpace, + storageKey: ANONYMIZATION_TABLE_SESSION_STORAGE_KEY, + }); const toolbar = useMemo( () => ( @@ -140,7 +147,8 @@ const ContextEditorComponent: React.FC = ({ pagination={pagination} search={search} selection={selectionValue} - sorting={defaultSort} + sorting={sorting} + onTableChange={onTableChange} /> ); From 7c30b205eb1f1c543b55feef35f950b39c7969e6 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 28 Jun 2024 18:42:13 +0100 Subject: [PATCH 37/37] fix jumping cursor --- .../system_prompt_modal/system_prompt_editor.tsx | 5 +++-- .../quick_prompt_settings/quick_prompt_editor.tsx | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) 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 59b608ef68511..3fd7dfeb00e73 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 @@ -64,8 +64,9 @@ export const SystemPromptEditorComponent: React.FC = ({ }) => { // Prompt const promptContent = useMemo( - () => selectedSystemPrompt?.content ?? '', - [selectedSystemPrompt?.content] + // Fixing Cursor Jump in text area + () => systemPromptSettings.find((sp) => sp.id === selectedSystemPrompt?.id)?.content ?? '', + [selectedSystemPrompt?.id, systemPromptSettings] ); const handlePromptContentChange = useCallback( diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx index e20ccc489405f..4300e53525b33 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/quick_prompt_settings/quick_prompt_editor.tsx @@ -38,7 +38,11 @@ const QuickPromptSettingsEditorComponent = ({ const { basePromptContexts } = useAssistantContext(); // Prompt - const prompt = useMemo(() => selectedQuickPrompt?.prompt ?? '', [selectedQuickPrompt?.prompt]); + const prompt = useMemo( + // Fixing Cursor Jump in text area + () => quickPromptSettings.find((p) => p.title === selectedQuickPrompt?.title)?.prompt ?? '', + [selectedQuickPrompt?.title, quickPromptSettings] + ); const handlePromptChange = useCallback( (e: React.ChangeEvent) => {