mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Assistant] Clean up AI settings tabs (#187705)
## Summary Code clean up for my previous PR: 1. https://github.com/elastic/kibana/pull/184678#discussion_r1657851993 - Remove additional props. 2. Add `Created on` column for quick prompt and system prompt table 3. Wording change: https://github.com/elastic/kibana/pull/184678#discussion_r1661034797 - Rename column title. **Landing page:** <img width="1282" alt="Screenshot 2024-07-09 at 19 07 34" src="20366ee7
-497f-412c-9690-953af9f6992b"> **Knowledge base:** <img width="2552" alt="Screenshot 2024-07-15 at 15 32 40" src="https://github.com/user-attachments/assets/1d651042-b187-4c08-b55d-c58c1104fd1b"> **Evaluation:** <img width="2560" alt="Screenshot 2024-07-15 at 15 34 04" src="https://github.com/user-attachments/assets/31855fe6-e5dd-462d-9c06-2fee2554361a"> <img width="2556" alt="Screenshot 2024-07-09 at 19 38 06" src="15be4f36
-261b-4652-8d4f-be8e7d14676a"> **Anonymization:** <img width="2551" alt="Screenshot 2024-07-15 at 15 32 33" src="https://github.com/user-attachments/assets/27688bb5-851e-46fc-8f75-9700ce7a248a"> **Quick prompts:** <img width="2559" alt="Screenshot 2024-07-15 at 15 30 30" src="https://github.com/user-attachments/assets/e00c39a0-fb12-46f1-bb2a-bdf5c5bd49d2"> <img width="2557" alt="Screenshot 2024-07-09 at 19 27 18" src="b581fc46
-003b-4363-9c16-22534eb1d71e"> **System prompts:** <img width="2557" alt="Screenshot 2024-07-15 at 15 30 11" src="https://github.com/user-attachments/assets/95fd4fca-5041-40b7-b500-efc192166be0"> <img width="2558" alt="Screenshot 2024-07-09 at 19 10 36" src="a701391a
-978f-4684-a2ea-f72a5f572217"> **Conversations:** <img width="2553" alt="Screenshot 2024-07-15 at 15 30 01" src="https://github.com/user-attachments/assets/3411beb8-4775-4ba7-8b3e-c4111497eed2"> <img width="2554" alt="Screenshot 2024-07-09 at 21 33 37" src="fbe2ee80
-ba20-41b6-b224-3e317dc1c20e"> Connectors: <img width="2558" alt="Screenshot 2024-07-09 at 19 09 15" src="c711ce09
-65c0-45b3-90c1-a9019d35093c"> [Design](https://www.figma.com/design/BMvpY9EhcPIaoOS7LSrkL0/[8.15]-GenAI-Security-Assistant-Settings%3A-Stack-Management-Pages?node-id=51-25207&t=JHlgCm0sCYsl8WCM-0) ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
7e67c48adb
commit
60ba001b1f
55 changed files with 1614 additions and 1184 deletions
|
@ -20,6 +20,7 @@ import {
|
|||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useAppContext } from '../../app_context';
|
||||
|
||||
export function AiAssistantSelectionPage() {
|
||||
|
@ -86,21 +87,25 @@ export function AiAssistantSelectionPage() {
|
|||
</>
|
||||
) : null}
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkDescription',
|
||||
{ defaultMessage: 'For more info, see our' }
|
||||
)}{' '}
|
||||
<EuiLink
|
||||
data-test-subj="pluginsAiAssistantSelectionPageDocumentationLink"
|
||||
external
|
||||
target="_blank"
|
||||
href="https://www.elastic.co/guide/en/observability/current/obs-ai-assistant.html"
|
||||
>
|
||||
{i18n.translate(
|
||||
'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkLabel',
|
||||
{ defaultMessage: 'documentation' }
|
||||
)}
|
||||
</EuiLink>
|
||||
<FormattedMessage
|
||||
id="aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkDescription"
|
||||
defaultMessage="For more info, see our {documentation}."
|
||||
values={{
|
||||
documentation: (
|
||||
<EuiLink
|
||||
data-test-subj="pluginsAiAssistantSelectionPageDocumentationLink"
|
||||
external
|
||||
target="_blank"
|
||||
href="https://www.elastic.co/guide/en/observability/current/obs-ai-assistant.html"
|
||||
>
|
||||
{i18n.translate(
|
||||
'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkLabel',
|
||||
{ defaultMessage: 'documentation' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<EuiButton
|
||||
iconType="gear"
|
||||
|
@ -151,21 +156,25 @@ export function AiAssistantSelectionPage() {
|
|||
</>
|
||||
) : null}
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'aiAssistantManagementSelection.aiAssistantSelectionPage.securityAssistant.documentationLinkDescription',
|
||||
{ defaultMessage: 'For more info, see our' }
|
||||
)}{' '}
|
||||
<EuiLink
|
||||
data-test-subj="securityAiAssistantSelectionPageDocumentationLink"
|
||||
external
|
||||
target="_blank"
|
||||
href="https://www.elastic.co/guide/en/security/current/security-assistant.html"
|
||||
>
|
||||
{i18n.translate(
|
||||
'aiAssistantManagementSelection.aiAssistantSettingsPage.securityAssistant.documentationLinkLabel',
|
||||
{ defaultMessage: 'documentation' }
|
||||
)}
|
||||
</EuiLink>
|
||||
<FormattedMessage
|
||||
id="aiAssistantManagementSelection.aiAssistantSelectionPage.securityAssistant.documentationLinkDescription"
|
||||
defaultMessage="For more info, see our {documentation}."
|
||||
values={{
|
||||
documentation: (
|
||||
<EuiLink
|
||||
data-test-subj="securityAiAssistantSelectionPageDocumentationLink"
|
||||
external
|
||||
target="_blank"
|
||||
href="https://www.elastic.co/guide/en/security/current/security-assistant.html"
|
||||
>
|
||||
{i18n.translate(
|
||||
'aiAssistantManagementSelection.aiAssistantSettingsPage.securityAssistant.documentationLinkLabel',
|
||||
{ defaultMessage: 'documentation' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<EuiButton
|
||||
data-test-subj="pluginsAiAssistantSelectionPageButton"
|
||||
|
|
|
@ -139,7 +139,13 @@ export const bulkUpdateConversations = async (
|
|||
toasts?.addError(error.body && error.body.message ? new Error(error.body.message) : error, {
|
||||
title: i18n.translate('xpack.elasticAssistant.conversations.bulkActionsConversationsError', {
|
||||
defaultMessage: 'Error updating conversations {error}',
|
||||
values: { error },
|
||||
values: {
|
||||
error: error.message
|
||||
? Array.isArray(error.message)
|
||||
? error.message.join(',')
|
||||
: error.message
|
||||
: error,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -47,7 +47,13 @@ export const bulkUpdatePrompts = async (
|
|||
toasts?.addError(error.body && error.body.message ? new Error(error.body.message) : error, {
|
||||
title: i18n.translate('xpack.elasticAssistant.prompts.bulkActionspromptsError', {
|
||||
defaultMessage: 'Error updating prompts {error}',
|
||||
values: { error },
|
||||
values: {
|
||||
error: error.message
|
||||
? Array.isArray(error.message)
|
||||
? error.message.join(',')
|
||||
: error.message
|
||||
: error,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import { useConversation } from '../use_conversation';
|
|||
import { getCombinedMessage } from '../prompt/helpers';
|
||||
import { Conversation, useAssistantContext } from '../../..';
|
||||
import { getMessageFromRawResponse } from '../helpers';
|
||||
import { getDefaultSystemPrompt } from '../use_conversation/helpers';
|
||||
import { getDefaultSystemPrompt, getDefaultNewSystemPrompt } from '../use_conversation/helpers';
|
||||
|
||||
export interface UseChatSendProps {
|
||||
allSystemPrompts: PromptResponse[];
|
||||
|
@ -204,10 +204,11 @@ export const useChatSend = ({
|
|||
}, [currentConversation, http, removeLastMessage, sendMessage, setCurrentConversation, toasts]);
|
||||
|
||||
const handleOnChatCleared = useCallback(async () => {
|
||||
const defaultSystemPromptId = getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: currentConversation,
|
||||
})?.id;
|
||||
const defaultSystemPromptId =
|
||||
getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: currentConversation,
|
||||
})?.id ?? getDefaultNewSystemPrompt(allSystemPrompts)?.id;
|
||||
|
||||
setUserPrompt('');
|
||||
setSelectedPromptContexts({});
|
||||
|
|
|
@ -27,6 +27,7 @@ interface Props {
|
|||
onClose: () => void;
|
||||
onSaveCancelled: () => void;
|
||||
onSaveConfirmed: () => void;
|
||||
saveButtonDisabled?: boolean;
|
||||
}
|
||||
|
||||
const FlyoutComponent: React.FC<Props> = ({
|
||||
|
@ -36,6 +37,7 @@ const FlyoutComponent: React.FC<Props> = ({
|
|||
onClose,
|
||||
onSaveCancelled,
|
||||
onSaveConfirmed,
|
||||
saveButtonDisabled = false,
|
||||
}) => {
|
||||
return flyoutVisible ? (
|
||||
<EuiFlyout
|
||||
|
@ -71,6 +73,7 @@ const FlyoutComponent: React.FC<Props> = ({
|
|||
data-test-subj="save-button"
|
||||
onClick={onSaveConfirmed}
|
||||
iconType="check"
|
||||
disabled={saveButtonDisabled}
|
||||
fill
|
||||
>
|
||||
{i18n.FLYOUT_SAVE_BUTTON_TITLE}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiTableActionsColumnType } from '@elastic/eui';
|
||||
import { useCallback } from 'react';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface Props<T> {
|
||||
disabled?: boolean;
|
||||
onDelete?: (rowItem: T) => void;
|
||||
onEdit?: (rowItem: T) => void;
|
||||
}
|
||||
|
||||
export const useInlineActions = <T extends { isDefault?: boolean | undefined }>() => {
|
||||
const getInlineActions = useCallback(({ disabled = false, onDelete, onEdit }: Props<T>) => {
|
||||
const handleEdit = (rowItem: T) => {
|
||||
onEdit?.(rowItem);
|
||||
};
|
||||
|
||||
const handleDelete = (rowItem: T) => {
|
||||
onDelete?.(rowItem);
|
||||
};
|
||||
|
||||
const actions: EuiTableActionsColumnType<T> = {
|
||||
name: i18n.ACTIONS_BUTTON,
|
||||
actions: [
|
||||
{
|
||||
name: i18n.EDIT_BUTTON,
|
||||
description: i18n.EDIT_BUTTON,
|
||||
icon: 'pencil',
|
||||
type: 'icon',
|
||||
onClick: (rowItem: T) => {
|
||||
handleEdit(rowItem);
|
||||
},
|
||||
enabled: () => !disabled,
|
||||
available: () => onEdit != null,
|
||||
},
|
||||
{
|
||||
name: i18n.DELETE_BUTTON,
|
||||
description: i18n.DELETE_BUTTON,
|
||||
icon: 'trash',
|
||||
type: 'icon',
|
||||
onClick: (rowItem: T) => {
|
||||
handleDelete(rowItem);
|
||||
},
|
||||
enabled: ({ isDefault }: { isDefault?: boolean }) => !isDefault && !disabled,
|
||||
available: () => onDelete != null,
|
||||
color: 'danger',
|
||||
},
|
||||
],
|
||||
};
|
||||
return actions;
|
||||
}, []);
|
||||
|
||||
return getInlineActions;
|
||||
};
|
|
@ -20,3 +20,10 @@ export const DELETE_BUTTON = i18n.translate(
|
|||
defaultMessage: 'Delete',
|
||||
}
|
||||
);
|
||||
|
||||
export const ACTIONS_BUTTON = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.actionsButtonTitle',
|
||||
{
|
||||
defaultMessage: 'Actions',
|
||||
}
|
||||
);
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButtonEmpty, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface Props<T> {
|
||||
isDeletable?: boolean;
|
||||
isEditable?: boolean;
|
||||
onDelete?: (rowItem: T) => void;
|
||||
onEdit?: (rowItem: T) => void;
|
||||
rowItem: T;
|
||||
}
|
||||
|
||||
type RowActionsComponentType = <T>(props: Props<T>) => JSX.Element;
|
||||
|
||||
const RowActionsComponent = <T,>({
|
||||
isDeletable = true,
|
||||
isEditable = true,
|
||||
onDelete,
|
||||
onEdit,
|
||||
rowItem,
|
||||
}: Props<T>) => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
|
||||
const handleEdit = useCallback(() => {
|
||||
closePopover();
|
||||
onEdit?.(rowItem);
|
||||
}, [closePopover, onEdit, rowItem]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
closePopover();
|
||||
onDelete?.(rowItem);
|
||||
}, [closePopover, onDelete, rowItem]);
|
||||
|
||||
const onButtonClick = useCallback(() => setIsPopoverOpen((prevState) => !prevState), []);
|
||||
return onEdit || onDelete ? (
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
color="success"
|
||||
iconType="boxesHorizontal"
|
||||
disabled={rowItem == null}
|
||||
onClick={onButtonClick}
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiFlexGroup direction="column" gutterSize="none" alignItems="flexStart">
|
||||
{onEdit != null && (
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty
|
||||
iconType="pencil"
|
||||
onClick={handleEdit}
|
||||
disabled={!isEditable}
|
||||
color="text"
|
||||
>
|
||||
{i18n.EDIT_BUTTON}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{onDelete != null && (
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty
|
||||
aria-label={i18n.DELETE_BUTTON}
|
||||
iconType="trash"
|
||||
onClick={handleDelete}
|
||||
disabled={!isDeletable}
|
||||
color="text"
|
||||
>
|
||||
{i18n.DELETE_BUTTON}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPopover>
|
||||
) : null;
|
||||
};
|
||||
|
||||
// casting to correctly infer the param of onEdit and onDelete when reusing this component
|
||||
export const RowActions = React.memo(RowActionsComponent) as RowActionsComponentType;
|
|
@ -93,7 +93,6 @@ export const ConversationSelectorSettings: React.FC<Props> = React.memo(
|
|||
(conversation) =>
|
||||
conversation.title === conversationSelectorSettingsOption[0]?.label
|
||||
) ?? conversationSelectorSettingsOption[0]?.label;
|
||||
|
||||
onConversationSelectionChange(newConversation);
|
||||
},
|
||||
[onConversationSelectionChange, conversations]
|
||||
|
|
|
@ -11,8 +11,8 @@ import { ConversationSettings, ConversationSettingsProps } from './conversation_
|
|||
import { TestProviders } from '../../../mock/test_providers/test_providers';
|
||||
import { alertConvo, customConvo, welcomeConvo } from '../../../mock/conversation';
|
||||
import { mockSystemPrompts } from '../../../mock/system_prompt';
|
||||
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants';
|
||||
import { mockConnectors } from '../../../mock/connectors';
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
|
||||
const mockConvos = {
|
||||
'1234': { ...welcomeConvo, id: '1234' },
|
||||
|
@ -24,18 +24,19 @@ const onSelectedConversationChange = jest.fn();
|
|||
const setConversationSettings = jest.fn();
|
||||
const setConversationsSettingsBulkActions = jest.fn();
|
||||
|
||||
const testProps = {
|
||||
const testProps: ConversationSettingsProps = {
|
||||
allSystemPrompts: mockSystemPrompts,
|
||||
assistantStreamingEnabled: false,
|
||||
connectors: mockConnectors,
|
||||
conversationSettings: mockConvos,
|
||||
defaultConnectorId: '123',
|
||||
defaultProvider: OpenAiProviderType.OpenAi,
|
||||
http: { basePath: { get: jest.fn() } },
|
||||
conversationsSettingsBulkActions: {},
|
||||
http: { basePath: { get: jest.fn() } } as unknown as HttpSetup,
|
||||
onSelectedConversationChange,
|
||||
selectedConversation: mockConvos['1234'],
|
||||
setAssistantStreamingEnabled: jest.fn(),
|
||||
setConversationSettings,
|
||||
conversationsSettingsBulkActions: {},
|
||||
setConversationsSettingsBulkActions,
|
||||
} as unknown as ConversationSettingsProps;
|
||||
};
|
||||
|
||||
jest.mock('../../../connectorland/use_load_connectors', () => ({
|
||||
useLoadConnectors: () => ({
|
||||
|
@ -113,7 +114,6 @@ jest.mock('../../../connectorland/connector_selector', () => ({
|
|||
/>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('ConversationSettings', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
|
|
@ -17,7 +17,6 @@ import React, { useMemo } from 'react';
|
|||
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
|
||||
import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../..';
|
||||
import * as i18n from './translations';
|
||||
|
@ -33,7 +32,6 @@ import { useConversationChanged } from './use_conversation_changed';
|
|||
import { getConversationApiConfig } from '../../use_conversation/helpers';
|
||||
|
||||
export interface ConversationSettingsProps {
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
allSystemPrompts: PromptResponse[];
|
||||
connectors?: AIConnector[];
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
|
@ -128,6 +126,7 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
|
|||
selectedConversation={selectedConversationWithApiConfig}
|
||||
setConversationSettings={setConversationSettings}
|
||||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
onSelectedConversationChange={onSelectedConversationChange}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
|
|
@ -36,6 +36,7 @@ export interface ConversationSettingsEditorProps {
|
|||
setConversationsSettingsBulkActions: React.Dispatch<
|
||||
React.SetStateAction<ConversationsBulkActions>
|
||||
>;
|
||||
onSelectedConversationChange: (conversation?: Conversation) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,11 +52,11 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
|
|||
setConversationSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
setConversationsSettingsBulkActions,
|
||||
onSelectedConversationChange,
|
||||
}) => {
|
||||
const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({
|
||||
http,
|
||||
});
|
||||
|
||||
const selectedSystemPrompt = useMemo(() => {
|
||||
return getDefaultSystemPrompt({ allSystemPrompts, conversation: selectedConversation });
|
||||
}, [allSystemPrompts, selectedConversation]);
|
||||
|
@ -95,13 +96,14 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
|
|||
},
|
||||
});
|
||||
} else {
|
||||
setConversationsSettingsBulkActions({
|
||||
const createdConversation = {
|
||||
...conversationsSettingsBulkActions,
|
||||
create: {
|
||||
...(conversationsSettingsBulkActions.create ?? {}),
|
||||
[updatedConversation.title]: updatedConversation,
|
||||
},
|
||||
});
|
||||
};
|
||||
setConversationsSettingsBulkActions(createdConversation);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -177,13 +179,14 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
|
|||
},
|
||||
});
|
||||
} else {
|
||||
setConversationsSettingsBulkActions({
|
||||
const createdConversation = {
|
||||
...conversationsSettingsBulkActions,
|
||||
create: {
|
||||
...(conversationsSettingsBulkActions.create ?? {}),
|
||||
[updatedConversation.title || updatedConversation.id]: updatedConversation,
|
||||
},
|
||||
});
|
||||
};
|
||||
setConversationsSettingsBulkActions(createdConversation);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -239,13 +242,14 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
|
|||
},
|
||||
});
|
||||
} else {
|
||||
setConversationsSettingsBulkActions({
|
||||
const createdConversation = {
|
||||
...conversationsSettingsBulkActions,
|
||||
create: {
|
||||
...(conversationsSettingsBulkActions.create ?? {}),
|
||||
[updatedConversation.id || updatedConversation.title]: updatedConversation,
|
||||
},
|
||||
});
|
||||
};
|
||||
setConversationsSettingsBulkActions(createdConversation);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -275,6 +279,9 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
|
|||
selectedPrompt={selectedSystemPrompt}
|
||||
isSettingsModalVisible={true}
|
||||
setIsSettingsModalVisible={noop} // noop, already in settings
|
||||
onSelectedConversationChange={onSelectedConversationChange}
|
||||
setConversationSettings={setConversationSettings}
|
||||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
|
@ -290,7 +297,7 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
|
|||
>
|
||||
<FormattedMessage
|
||||
id="xpack.elasticAssistant.assistant.settings.connectorHelpTextTitle"
|
||||
defaultMessage="Kibana Connector to make requests with"
|
||||
defaultMessage="The default LLM connector for this conversation type."
|
||||
/>
|
||||
</EuiLink>
|
||||
}
|
||||
|
|
|
@ -52,14 +52,14 @@ export const SETTINGS_PROMPT_TITLE = i18n.translate(
|
|||
export const SETTINGS_PROMPT_HELP_TEXT_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversations.settings.promptHelpTextTitle',
|
||||
{
|
||||
defaultMessage: 'Context provided as part of every conversation',
|
||||
defaultMessage: 'Context provided as part of every conversation.',
|
||||
}
|
||||
);
|
||||
|
||||
export const STREAMING_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversations.settings.streamingTitle',
|
||||
{
|
||||
defaultMessage: 'Streaming',
|
||||
defaultMessage: 'STREAMING',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -84,7 +84,6 @@ export const useConversationChanged = ({
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
onSelectedConversationChange({
|
||||
...newSelectedConversation,
|
||||
id: newSelectedConversation.id || newSelectedConversation.title,
|
||||
|
|
|
@ -5,45 +5,45 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiPanel, EuiSpacer, EuiConfirmModal, EuiInMemoryTable } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiConfirmModal,
|
||||
EuiInMemoryTable,
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../assistant_context/types';
|
||||
import { ConversationTableItem, useConversationsTable } from './use_conversations_table';
|
||||
import { ConversationStreamingSwitch } from '../conversation_settings/conversation_streaming_switch';
|
||||
import { AIConnector } from '../../../connectorland/connector_selector';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import { ConversationsBulkActions } from '../../api';
|
||||
import {
|
||||
FetchConversationsResponse,
|
||||
useFetchCurrentUserConversations,
|
||||
useFetchPrompts,
|
||||
} from '../../api';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
import { useConversationDeleted } from '../conversation_settings/use_conversation_deleted';
|
||||
import { useFlyoutModalVisibility } from '../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility';
|
||||
import { Flyout } from '../../common/components/assistant_settings_management/flyout';
|
||||
import { CANCEL, DELETE } from '../../settings/translations';
|
||||
import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../settings/translations';
|
||||
import { ConversationSettingsEditor } from '../conversation_settings/conversation_settings_editor';
|
||||
import { useConversationChanged } from '../conversation_settings/use_conversation_changed';
|
||||
import { CONVERSATION_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_context/constants';
|
||||
import { useSessionPagination } from '../../common/components/assistant_settings_management/pagination/use_session_pagination';
|
||||
import { DEFAULT_PAGE_SIZE } from '../../settings/const';
|
||||
import { useSettingsUpdater } from '../../settings/use_settings_updater/use_settings_updater';
|
||||
import { mergeBaseWithPersistedConversations } from '../../helpers';
|
||||
import { AssistantSettingsBottomBar } from '../../settings/assistant_settings_bottom_bar';
|
||||
interface Props {
|
||||
allSystemPrompts: PromptResponse[];
|
||||
assistantStreamingEnabled: boolean;
|
||||
connectors: AIConnector[] | undefined;
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
conversationsSettingsBulkActions: ConversationsBulkActions;
|
||||
conversationsLoaded: boolean;
|
||||
defaultConnector?: AIConnector;
|
||||
handleSave: (shouldRefetchConversation?: boolean) => void;
|
||||
defaultSelectedConversation: Conversation;
|
||||
isDisabled?: boolean;
|
||||
onCancelClick: () => void;
|
||||
setAssistantStreamingEnabled: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setConversationSettings: React.Dispatch<React.SetStateAction<Record<string, Conversation>>>;
|
||||
setConversationsSettingsBulkActions: React.Dispatch<
|
||||
React.SetStateAction<ConversationsBulkActions>
|
||||
>;
|
||||
selectedConversation: Conversation | undefined;
|
||||
onSelectedConversationChange: (conversation?: Conversation) => void;
|
||||
}
|
||||
|
||||
export const DEFAULT_TABLE_OPTIONS = {
|
||||
|
@ -52,23 +52,112 @@ export const DEFAULT_TABLE_OPTIONS = {
|
|||
};
|
||||
|
||||
const ConversationSettingsManagementComponent: React.FC<Props> = ({
|
||||
allSystemPrompts,
|
||||
assistantStreamingEnabled,
|
||||
connectors,
|
||||
defaultConnector,
|
||||
conversationSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
conversationsLoaded,
|
||||
handleSave,
|
||||
defaultSelectedConversation,
|
||||
isDisabled,
|
||||
onSelectedConversationChange,
|
||||
onCancelClick,
|
||||
selectedConversation,
|
||||
setAssistantStreamingEnabled,
|
||||
setConversationSettings,
|
||||
setConversationsSettingsBulkActions,
|
||||
}) => {
|
||||
const { http, nameSpace, actionTypeRegistry } = useAssistantContext();
|
||||
const {
|
||||
actionTypeRegistry,
|
||||
assistantAvailability: { isAssistantEnabled },
|
||||
baseConversations,
|
||||
http,
|
||||
nameSpace,
|
||||
toasts,
|
||||
} = useAssistantContext();
|
||||
|
||||
const onFetchedConversations = useCallback(
|
||||
(conversationsData: FetchConversationsResponse): Record<string, Conversation> =>
|
||||
mergeBaseWithPersistedConversations(baseConversations, conversationsData),
|
||||
[baseConversations]
|
||||
);
|
||||
|
||||
const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts();
|
||||
|
||||
const {
|
||||
data: conversations,
|
||||
isFetched: conversationsLoaded,
|
||||
refetch: refetchConversations,
|
||||
} = useFetchCurrentUserConversations({
|
||||
http,
|
||||
onFetch: onFetchedConversations,
|
||||
isAssistantEnabled,
|
||||
});
|
||||
|
||||
const refetchAll = useCallback(() => {
|
||||
refetchPrompts();
|
||||
refetchConversations();
|
||||
}, [refetchPrompts, refetchConversations]);
|
||||
|
||||
const {
|
||||
systemPromptSettings: allSystemPrompts,
|
||||
assistantStreamingEnabled,
|
||||
conversationSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
resetSettings,
|
||||
saveSettings,
|
||||
setConversationSettings,
|
||||
setConversationsSettingsBulkActions,
|
||||
setUpdatedAssistantStreamingEnabled,
|
||||
} = useSettingsUpdater(conversations, allPrompts, conversationsLoaded, promptsLoaded);
|
||||
|
||||
const [hasPendingChanges, setHasPendingChanges] = useState(false);
|
||||
|
||||
const handleSave = useCallback(
|
||||
async (param?: { callback?: () => void }) => {
|
||||
const isSuccess = await saveSettings();
|
||||
if (isSuccess) {
|
||||
toasts?.addSuccess({
|
||||
iconType: 'check',
|
||||
title: SETTINGS_UPDATED_TOAST_TITLE,
|
||||
});
|
||||
setHasPendingChanges(false);
|
||||
param?.callback?.();
|
||||
} else {
|
||||
resetSettings();
|
||||
}
|
||||
},
|
||||
[resetSettings, saveSettings, toasts]
|
||||
);
|
||||
|
||||
const setAssistantStreamingEnabled = useCallback(
|
||||
(value) => {
|
||||
setHasPendingChanges(true);
|
||||
setUpdatedAssistantStreamingEnabled(value);
|
||||
},
|
||||
[setUpdatedAssistantStreamingEnabled]
|
||||
);
|
||||
|
||||
const onSaveButtonClicked = useCallback(() => {
|
||||
handleSave({ callback: refetchAll });
|
||||
}, [handleSave, refetchAll]);
|
||||
|
||||
const onCancelClick = useCallback(() => {
|
||||
resetSettings();
|
||||
setHasPendingChanges(false);
|
||||
}, [resetSettings]);
|
||||
|
||||
// Local state for saving previously selected items so tab switching is friendlier
|
||||
// Conversation Selection State
|
||||
const [selectedConversation, setSelectedConversation] = useState<Conversation | undefined>(() => {
|
||||
return conversationSettings[defaultSelectedConversation.title];
|
||||
});
|
||||
|
||||
const onSelectedConversationChange = useCallback((conversation?: Conversation) => {
|
||||
setSelectedConversation(conversation);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedConversation != null) {
|
||||
const newConversation =
|
||||
conversationSettings[selectedConversation.id] ||
|
||||
conversationSettings[selectedConversation.title];
|
||||
setSelectedConversation(
|
||||
// conversationSettings has title as key, sometime has id as key
|
||||
newConversation
|
||||
);
|
||||
}
|
||||
}, [conversationSettings, selectedConversation]);
|
||||
|
||||
const {
|
||||
isFlyoutOpen: editFlyoutVisible,
|
||||
|
@ -124,12 +213,13 @@ const ConversationSettingsManagementComponent: React.FC<Props> = ({
|
|||
return;
|
||||
}
|
||||
closeConfirmModal();
|
||||
handleSave(true);
|
||||
handleSave({ callback: refetchAll });
|
||||
setConversationsSettingsBulkActions({});
|
||||
}, [
|
||||
closeConfirmModal,
|
||||
conversationsSettingsBulkActions,
|
||||
handleSave,
|
||||
refetchAll,
|
||||
setConversationsSettingsBulkActions,
|
||||
]);
|
||||
|
||||
|
@ -162,9 +252,9 @@ const ConversationSettingsManagementComponent: React.FC<Props> = ({
|
|||
|
||||
const onSaveConfirmed = useCallback(() => {
|
||||
closeEditFlyout();
|
||||
handleSave(true);
|
||||
handleSave({ callback: refetchAll });
|
||||
setConversationsSettingsBulkActions({});
|
||||
}, [closeEditFlyout, handleSave, setConversationsSettingsBulkActions]);
|
||||
}, [closeEditFlyout, handleSave, refetchAll, setConversationsSettingsBulkActions]);
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
|
@ -191,12 +281,21 @@ const ConversationSettingsManagementComponent: React.FC<Props> = ({
|
|||
return (
|
||||
<>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiTitle size="xs">
|
||||
<h2>{i18n.CONVERSATIONS_SETTINGS_TITLE}</h2>
|
||||
</EuiTitle>
|
||||
<ConversationStreamingSwitch
|
||||
assistantStreamingEnabled={assistantStreamingEnabled}
|
||||
setAssistantStreamingEnabled={setAssistantStreamingEnabled}
|
||||
compressed={false}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSpacer size="l" />
|
||||
<EuiTitle size="xs">
|
||||
<h2>{i18n.CONVERSATIONS_LIST_TITLE}</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size="m">{i18n.CONVERSATIONS_LIST_DESCRIPTION}</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiInMemoryTable
|
||||
items={conversationOptions}
|
||||
columns={columns}
|
||||
|
@ -212,6 +311,9 @@ const ConversationSettingsManagementComponent: React.FC<Props> = ({
|
|||
onSaveConfirmed={onSaveConfirmed}
|
||||
onSaveCancelled={onSaveCancelled}
|
||||
title={selectedConversation?.title ?? i18n.CONVERSATIONS_FLYOUT_DEFAULT_TITLE}
|
||||
saveButtonDisabled={
|
||||
selectedConversation?.title == null || selectedConversation?.title === ''
|
||||
}
|
||||
>
|
||||
<ConversationSettingsEditor
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
|
@ -222,6 +324,7 @@ const ConversationSettingsManagementComponent: React.FC<Props> = ({
|
|||
selectedConversation={selectedConversation}
|
||||
setConversationSettings={setConversationSettings}
|
||||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
onSelectedConversationChange={onSelectedConversationChange}
|
||||
/>
|
||||
</Flyout>
|
||||
)}
|
||||
|
@ -240,6 +343,11 @@ const ConversationSettingsManagementComponent: React.FC<Props> = ({
|
|||
<p />
|
||||
</EuiConfirmModal>
|
||||
)}
|
||||
<AssistantSettingsBottomBar
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
onCancelClick={onCancelClick}
|
||||
onSaveButtonClicked={onSaveButtonClicked}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,10 +7,31 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const CONVERSATIONS_TABLE_COLUMN_NAME = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversationSettings.column.name',
|
||||
export const CONVERSATIONS_SETTINGS_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversationSettings.title',
|
||||
{
|
||||
defaultMessage: 'Name',
|
||||
defaultMessage: 'Settings',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONVERSATIONS_LIST_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversationSettings.list.title',
|
||||
{
|
||||
defaultMessage: 'Conversation list',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONVERSATIONS_LIST_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversationSettings.list.description',
|
||||
{
|
||||
defaultMessage: 'Create and manage conversations with the Elastic AI Assistant.',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONVERSATIONS_TABLE_COLUMN_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversationSettings.column.Title',
|
||||
{
|
||||
defaultMessage: 'Title',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@ import { alertConvo, welcomeConvo, customConvo } from '../../../mock/conversatio
|
|||
import { mockActionTypes, mockConnectors } from '../../../mock/connectors';
|
||||
import { mockSystemPrompts } from '../../../mock/system_prompt';
|
||||
import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import * as i18n from './translations';
|
||||
import { EuiTableFieldDataColumnType } from '@elastic/eui';
|
||||
|
||||
const mockActionTypeRegistry: ActionTypeRegistryContract = {
|
||||
has: jest
|
||||
|
@ -42,17 +40,11 @@ describe('useConversationsTable', () => {
|
|||
|
||||
expect(columns).toHaveLength(5);
|
||||
|
||||
expect(columns[0].name).toBe(i18n.CONVERSATIONS_TABLE_COLUMN_NAME);
|
||||
expect((columns[1] as EuiTableFieldDataColumnType<ConversationTableItem>).field).toBe(
|
||||
'systemPromptTitle'
|
||||
);
|
||||
expect((columns[2] as EuiTableFieldDataColumnType<ConversationTableItem>).field).toBe(
|
||||
'connectorTypeTitle'
|
||||
);
|
||||
expect((columns[3] as EuiTableFieldDataColumnType<ConversationTableItem>).field).toBe(
|
||||
'updatedAt'
|
||||
);
|
||||
expect(columns[4].name).toBe(i18n.CONVERSATIONS_TABLE_COLUMN_ACTIONS);
|
||||
expect(columns[0].name).toBe('Title');
|
||||
expect(columns[1].name).toBe('System prompt');
|
||||
expect(columns[2].name).toBe('Connector');
|
||||
expect(columns[3].name).toBe('Date updated');
|
||||
expect(columns[4].name).toBe('Actions');
|
||||
});
|
||||
|
||||
it('should return a list of conversations', () => {
|
||||
|
@ -78,14 +70,14 @@ describe('useConversationsTable', () => {
|
|||
|
||||
expect(conversationsList[0].title).toBe(alertConvo.title);
|
||||
expect(conversationsList[0].connectorTypeTitle).toBe('OpenAI');
|
||||
expect(conversationsList[0].systemPromptTitle).toBe('Mock system prompt');
|
||||
expect(conversationsList[0].systemPromptTitle).toBeUndefined();
|
||||
|
||||
expect(conversationsList[1].title).toBe(welcomeConvo.title);
|
||||
expect(conversationsList[1].connectorTypeTitle).toBe('OpenAI');
|
||||
expect(conversationsList[1].systemPromptTitle).toBe('Mock system prompt');
|
||||
expect(conversationsList[1].systemPromptTitle).toBeUndefined();
|
||||
|
||||
expect(conversationsList[2].title).toBe(customConvo.title);
|
||||
expect(conversationsList[2].connectorTypeTitle).toBe('OpenAI');
|
||||
expect(conversationsList[2].systemPromptTitle).toBe('Mock system prompt');
|
||||
expect(conversationsList[2].systemPromptTitle).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,12 +15,9 @@ import { PromptResponse } from '@kbn/elastic-assistant-common';
|
|||
import { Conversation } from '../../../assistant_context/types';
|
||||
import { AIConnector } from '../../../connectorland/connector_selector';
|
||||
import { getConnectorTypeTitle } from '../../../connectorland/helpers';
|
||||
import {
|
||||
getConversationApiConfig,
|
||||
getInitialDefaultSystemPrompt,
|
||||
} from '../../use_conversation/helpers';
|
||||
import { getConversationApiConfig } from '../../use_conversation/helpers';
|
||||
import * as i18n from './translations';
|
||||
import { RowActions } from '../../common/components/assistant_settings_management/row_actions';
|
||||
import { useInlineActions } from '../../common/components/assistant_settings_management/inline_actions';
|
||||
|
||||
const emptyConversations = {};
|
||||
|
||||
|
@ -38,6 +35,7 @@ export type ConversationTableItem = Conversation & {
|
|||
};
|
||||
|
||||
export const useConversationsTable = () => {
|
||||
const getActions = useInlineActions<ConversationTableItem>();
|
||||
const getColumns = useCallback(
|
||||
({
|
||||
onDeleteActionClicked,
|
||||
|
@ -45,7 +43,7 @@ export const useConversationsTable = () => {
|
|||
}): Array<EuiBasicTableColumn<ConversationTableItem>> => {
|
||||
return [
|
||||
{
|
||||
name: i18n.CONVERSATIONS_TABLE_COLUMN_NAME,
|
||||
name: i18n.CONVERSATIONS_TABLE_COLUMN_TITLE,
|
||||
render: (conversation: ConversationTableItem) => (
|
||||
<EuiLink onClick={() => onEditActionClicked(conversation)}>
|
||||
{conversation.title}
|
||||
|
@ -87,24 +85,16 @@ export const useConversationsTable = () => {
|
|||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: i18n.CONVERSATIONS_TABLE_COLUMN_ACTIONS,
|
||||
width: '120px',
|
||||
align: 'center',
|
||||
render: (conversation: ConversationTableItem) => {
|
||||
const isDeletable = !conversation.isDefault;
|
||||
return (
|
||||
<RowActions<ConversationTableItem>
|
||||
rowItem={conversation}
|
||||
onDelete={isDeletable ? onDeleteActionClicked : undefined}
|
||||
onEdit={onEditActionClicked}
|
||||
isDeletable={isDeletable}
|
||||
/>
|
||||
);
|
||||
},
|
||||
...getActions({
|
||||
onDelete: onDeleteActionClicked,
|
||||
onEdit: onEditActionClicked,
|
||||
}),
|
||||
},
|
||||
];
|
||||
},
|
||||
[]
|
||||
[getActions]
|
||||
);
|
||||
const getConversationsList = useCallback(
|
||||
({
|
||||
|
@ -129,16 +119,8 @@ export const useConversationsTable = () => {
|
|||
const systemPrompt: PromptResponse | undefined = allSystemPrompts.find(
|
||||
({ id }) => id === conversation.apiConfig?.defaultSystemPromptId
|
||||
);
|
||||
const defaultSystemPrompt = getInitialDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
});
|
||||
|
||||
const systemPromptTitle =
|
||||
systemPrompt?.name ||
|
||||
systemPrompt?.id ||
|
||||
defaultSystemPrompt?.name ||
|
||||
defaultSystemPrompt?.id;
|
||||
const systemPromptTitle = systemPrompt?.name || systemPrompt?.id;
|
||||
|
||||
return {
|
||||
...conversation,
|
||||
|
|
|
@ -43,6 +43,9 @@ export interface Props {
|
|||
isSettingsModalVisible: boolean;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onSystemPromptSelectionChange?: (promptId: string | undefined) => void;
|
||||
onSelectedConversationChange?: (result: Conversation) => void;
|
||||
setConversationSettings?: React.Dispatch<React.SetStateAction<Record<string, Conversation>>>;
|
||||
setConversationsSettingsBulkActions?: React.Dispatch<Record<string, Conversation>>;
|
||||
}
|
||||
|
||||
const ADD_NEW_SYSTEM_PROMPT = 'ADD_NEW_SYSTEM_PROMPT';
|
||||
|
@ -59,6 +62,9 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
isSettingsModalVisible,
|
||||
onSystemPromptSelectionChange,
|
||||
setIsSettingsModalVisible,
|
||||
onSelectedConversationChange,
|
||||
setConversationSettings,
|
||||
setConversationsSettingsBulkActions,
|
||||
}) => {
|
||||
const { setSelectedSettingsTab } = useAssistantContext();
|
||||
const { setApiConfig } = useConversation();
|
||||
|
@ -74,15 +80,16 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
|
||||
// Write the selected system prompt to the conversation config
|
||||
const setSelectedSystemPrompt = useCallback(
|
||||
(promptId?: string) => {
|
||||
async (promptId?: string) => {
|
||||
if (conversation && conversation.apiConfig) {
|
||||
setApiConfig({
|
||||
const result = await setApiConfig({
|
||||
conversation,
|
||||
apiConfig: {
|
||||
...conversation.apiConfig,
|
||||
defaultSystemPromptId: promptId,
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}
|
||||
},
|
||||
[conversation, setApiConfig]
|
||||
|
@ -112,7 +119,7 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
const options = useMemo(() => getOptions({ prompts: allSystemPrompts }), [allSystemPrompts]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(selectedSystemPromptId) => {
|
||||
async (selectedSystemPromptId) => {
|
||||
if (selectedSystemPromptId === ADD_NEW_SYSTEM_PROMPT) {
|
||||
setIsSettingsModalVisible(true);
|
||||
setSelectedSettingsTab(SYSTEM_PROMPTS_TAB);
|
||||
|
@ -122,10 +129,30 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
if (onSystemPromptSelectionChange != null) {
|
||||
onSystemPromptSelectionChange(selectedSystemPromptId);
|
||||
}
|
||||
setSelectedSystemPrompt(selectedSystemPromptId);
|
||||
const result = await setSelectedSystemPrompt(selectedSystemPromptId);
|
||||
if (result) {
|
||||
setConversationSettings?.((prev: Record<string, Conversation>) => {
|
||||
const newConversationsSettings = Object.entries(prev).reduce<
|
||||
Record<string, Conversation>
|
||||
>((acc, [key, convo]) => {
|
||||
if (result.title === convo.title) {
|
||||
acc[result.id] = result;
|
||||
} else {
|
||||
acc[key] = convo;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
return newConversationsSettings;
|
||||
});
|
||||
onSelectedConversationChange?.(result);
|
||||
setConversationsSettingsBulkActions?.({});
|
||||
}
|
||||
},
|
||||
[
|
||||
onSelectedConversationChange,
|
||||
onSystemPromptSelectionChange,
|
||||
setConversationSettings,
|
||||
setConversationsSettingsBulkActions,
|
||||
setIsSettingsModalVisible,
|
||||
setSelectedSettingsTab,
|
||||
setSelectedSystemPrompt,
|
||||
|
|
|
@ -32,7 +32,10 @@ import { TEST_IDS } from '../../../constants';
|
|||
import { ConversationsBulkActions } from '../../../api';
|
||||
import { getSelectedConversations } from '../system_prompt_settings_management/utils';
|
||||
import { useSystemPromptEditor } from './use_system_prompt_editor';
|
||||
import { getConversationApiConfig } from '../../../use_conversation/helpers';
|
||||
import {
|
||||
getConversationApiConfig,
|
||||
getFallbackDefaultSystemPrompt,
|
||||
} from '../../../use_conversation/helpers';
|
||||
|
||||
interface Props {
|
||||
connectors: AIConnector[] | undefined;
|
||||
|
@ -99,7 +102,7 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
});
|
||||
const existingPrompt = systemPromptSettings.find((sp) => sp.id === selectedSystemPrompt.id);
|
||||
if (existingPrompt) {
|
||||
setPromptsBulkActions({
|
||||
const newBulkActions = {
|
||||
...promptsBulkActions,
|
||||
...(selectedSystemPrompt.name !== selectedSystemPrompt.id
|
||||
? {
|
||||
|
@ -124,7 +127,8 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
||||
setPromptsBulkActions(newBulkActions);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -159,20 +163,15 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
|
||||
const selectedConversations = useMemo(() => {
|
||||
return selectedSystemPrompt != null
|
||||
? getSelectedConversations(
|
||||
systemPromptSettings,
|
||||
conversationsWithApiConfig,
|
||||
selectedSystemPrompt.id
|
||||
)
|
||||
? getSelectedConversations(conversationsWithApiConfig, selectedSystemPrompt.id)
|
||||
: [];
|
||||
}, [conversationsWithApiConfig, selectedSystemPrompt, systemPromptSettings]);
|
||||
}, [conversationsWithApiConfig, selectedSystemPrompt]);
|
||||
|
||||
const handleConversationSelectionChange = useCallback(
|
||||
(currentPromptConversations: Conversation[]) => {
|
||||
const currentPromptConversationTitles = currentPromptConversations.map(
|
||||
(convo) => convo.title
|
||||
);
|
||||
|
||||
const getDefaultSystemPromptId = (convo: Conversation) =>
|
||||
currentPromptConversationTitles.includes(convo.title)
|
||||
? selectedSystemPrompt?.id
|
||||
|
@ -180,7 +179,7 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
? // remove the default System Prompt if it is assigned to a conversation
|
||||
// but that conversation is not in the currentPromptConversationList
|
||||
// This means conversation was removed in the current transaction
|
||||
systemPromptSettings?.[0].id
|
||||
undefined
|
||||
: // leave it as it is .. if that conversation was neither added nor removed.
|
||||
convo.apiConfig?.defaultSystemPromptId;
|
||||
|
||||
|
@ -194,23 +193,26 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
* through each conversation adds/removed the selected prompt on each conversation.
|
||||
*
|
||||
* */
|
||||
Object.values(prev).map((convo) => ({
|
||||
...convo,
|
||||
...(convo.apiConfig
|
||||
? {
|
||||
apiConfig: {
|
||||
...convo.apiConfig,
|
||||
defaultSystemPromptId: getDefaultSystemPromptId(convo),
|
||||
},
|
||||
}
|
||||
: {
|
||||
apiConfig: {
|
||||
defaultSystemPromptId: getDefaultSystemPromptId(convo),
|
||||
connectorId: defaultConnector?.id ?? '',
|
||||
actionTypeId: defaultConnector?.actionTypeId ?? '',
|
||||
},
|
||||
}),
|
||||
}))
|
||||
Object.values(prev).map((convo) => {
|
||||
const newConversationSetting = {
|
||||
...convo,
|
||||
...(convo.apiConfig
|
||||
? {
|
||||
apiConfig: {
|
||||
...convo.apiConfig,
|
||||
defaultSystemPromptId: getDefaultSystemPromptId(convo),
|
||||
},
|
||||
}
|
||||
: {
|
||||
apiConfig: {
|
||||
defaultSystemPromptId: getDefaultSystemPromptId(convo),
|
||||
connectorId: defaultConnector?.id ?? '',
|
||||
actionTypeId: defaultConnector?.actionTypeId ?? '',
|
||||
},
|
||||
}),
|
||||
};
|
||||
return newConversationSetting;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -226,7 +228,9 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
conversation: convo,
|
||||
defaultConnector,
|
||||
}).apiConfig,
|
||||
defaultSystemPromptId: getDefaultSystemPromptId(convo),
|
||||
defaultSystemPromptId:
|
||||
getDefaultSystemPromptId(convo) ??
|
||||
getFallbackDefaultSystemPrompt({ allSystemPrompts: systemPromptSettings })?.id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -266,7 +270,6 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
...updateOperation,
|
||||
};
|
||||
});
|
||||
|
||||
setConversationsSettingsBulkActions(updatedConversationsSettingsBulkActions);
|
||||
}
|
||||
},
|
||||
|
@ -291,6 +294,21 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
const handleNewConversationDefaultChange = useCallback(
|
||||
(e) => {
|
||||
const isChecked = e.target.checked;
|
||||
const defaultNewSystemPrompts = systemPromptSettings.filter(
|
||||
(p) => p.isNewConversationDefault
|
||||
);
|
||||
|
||||
const shouldCreateNewDefaultSystemPrompts = (sp?: { name: string; id: string }) =>
|
||||
sp?.name === sp?.id; // Prompts before preserving have the SAME name and id
|
||||
|
||||
const shouldUpdateNewDefaultSystemPrompts = (sp?: { name: string; id: string }) =>
|
||||
sp?.name !== sp?.id; // Prompts after preserving have different name and id
|
||||
|
||||
const shouldCreateSelectedSystemPrompt =
|
||||
selectedSystemPrompt?.name === selectedSystemPrompt?.id;
|
||||
|
||||
const shouldUpdateSelectedSystemPrompt =
|
||||
selectedSystemPrompt?.name !== selectedSystemPrompt?.id;
|
||||
|
||||
if (selectedSystemPrompt != null) {
|
||||
setUpdatedSystemPromptSettings((prev) => {
|
||||
|
@ -301,39 +319,60 @@ export const SystemPromptEditorComponent: React.FC<Props> = ({
|
|||
};
|
||||
});
|
||||
});
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
...(selectedSystemPrompt.name !== selectedSystemPrompt.id
|
||||
? {
|
||||
update: [
|
||||
...(promptsBulkActions.update ?? []).filter(
|
||||
(p) => p.id !== selectedSystemPrompt.id
|
||||
),
|
||||
{
|
||||
...selectedSystemPrompt,
|
||||
isNewConversationDefault: isChecked,
|
||||
},
|
||||
],
|
||||
}
|
||||
: {
|
||||
create: [
|
||||
...(promptsBulkActions.create ?? []).filter(
|
||||
(p) => p.name !== selectedSystemPrompt.name
|
||||
),
|
||||
{
|
||||
...selectedSystemPrompt,
|
||||
isNewConversationDefault: isChecked,
|
||||
},
|
||||
],
|
||||
}),
|
||||
// Update and Create prompts can happen at the same time, as we have to unchecked the previous default prompt
|
||||
// Each prompt can be updated or created
|
||||
setPromptsBulkActions(() => {
|
||||
const newBulkActions = {
|
||||
update: [
|
||||
...defaultNewSystemPrompts
|
||||
.filter(
|
||||
(p) => p.id !== selectedSystemPrompt.id && shouldUpdateNewDefaultSystemPrompts(p)
|
||||
)
|
||||
.map((p) => ({
|
||||
...p,
|
||||
isNewConversationDefault: false,
|
||||
})),
|
||||
|
||||
...(shouldUpdateSelectedSystemPrompt
|
||||
? [
|
||||
{
|
||||
...selectedSystemPrompt,
|
||||
isNewConversationDefault: isChecked,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
create: [
|
||||
...defaultNewSystemPrompts
|
||||
.filter(
|
||||
(p) =>
|
||||
p.name !== selectedSystemPrompt.name && shouldCreateNewDefaultSystemPrompts(p)
|
||||
)
|
||||
.map((p) => ({
|
||||
...p,
|
||||
isNewConversationDefault: false,
|
||||
})),
|
||||
|
||||
...(shouldCreateSelectedSystemPrompt
|
||||
? [
|
||||
{
|
||||
...selectedSystemPrompt,
|
||||
isNewConversationDefault: isChecked,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
|
||||
return newBulkActions;
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
promptsBulkActions,
|
||||
selectedSystemPrompt,
|
||||
setPromptsBulkActions,
|
||||
setUpdatedSystemPromptSettings,
|
||||
systemPromptSettings,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@ export const SystemPromptSelector: React.FC<Props> = React.memo(
|
|||
? undefined
|
||||
: systemPrompts.find((sp) => sp.name === systemPromptSelectorOption[0]?.label) ??
|
||||
systemPromptSelectorOption[0]?.label;
|
||||
|
||||
onSystemPromptSelectionChange(newSystemPrompt);
|
||||
},
|
||||
[onSystemPromptSelectionChange, resetSettings, systemPrompts]
|
||||
|
|
|
@ -65,7 +65,7 @@ export const SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION = i18n.translate(
|
|||
export const SYSTEM_PROMPT_DEFAULT_CONVERSATIONS_HELP_TEXT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultConversationsHelpText',
|
||||
{
|
||||
defaultMessage: 'Conversations that should use this System Prompt by default',
|
||||
defaultMessage: 'Conversations that should use this System Prompt by default.',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -52,14 +52,17 @@ export const useSystemPromptEditor = ({
|
|||
});
|
||||
|
||||
if (isNew) {
|
||||
setPromptsBulkActions({
|
||||
...promptsBulkActions,
|
||||
create: [
|
||||
...(promptsBulkActions.create ?? []),
|
||||
{
|
||||
...newSelectedSystemPrompt,
|
||||
},
|
||||
],
|
||||
setPromptsBulkActions((prev) => {
|
||||
const newBulkActions = {
|
||||
...prev,
|
||||
create: [
|
||||
...(promptsBulkActions.create ?? []),
|
||||
{
|
||||
...newSelectedSystemPrompt,
|
||||
},
|
||||
],
|
||||
};
|
||||
return newBulkActions;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,23 +13,28 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import {
|
||||
PromptResponse,
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { Conversation, ConversationsBulkActions, useAssistantContext } from '../../../../..';
|
||||
Conversation,
|
||||
mergeBaseWithPersistedConversations,
|
||||
useAssistantContext,
|
||||
useFetchCurrentUserConversations,
|
||||
} from '../../../../..';
|
||||
import { SYSTEM_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../../assistant_context/constants';
|
||||
import { AIConnector } from '../../../../connectorland/connector_selector';
|
||||
import { FetchConversationsResponse, useFetchPrompts } from '../../../api';
|
||||
import { Flyout } from '../../../common/components/assistant_settings_management/flyout';
|
||||
import { useFlyoutModalVisibility } from '../../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility';
|
||||
import {
|
||||
DEFAULT_TABLE_OPTIONS,
|
||||
useSessionPagination,
|
||||
} from '../../../common/components/assistant_settings_management/pagination/use_session_pagination';
|
||||
import { CANCEL, DELETE } from '../../../settings/translations';
|
||||
import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../../settings/translations';
|
||||
import { useSettingsUpdater } from '../../../settings/use_settings_updater/use_settings_updater';
|
||||
import { SystemPromptEditor } from '../system_prompt_modal/system_prompt_editor';
|
||||
import { SETTINGS_TITLE } from '../system_prompt_modal/translations';
|
||||
import { useSystemPromptEditor } from '../system_prompt_modal/use_system_prompt_editor';
|
||||
|
@ -38,42 +43,42 @@ import { useSystemPromptTable } from './use_system_prompt_table';
|
|||
|
||||
interface Props {
|
||||
connectors: AIConnector[] | undefined;
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
conversationsSettingsBulkActions: ConversationsBulkActions;
|
||||
onSelectedSystemPromptChange: (systemPrompt?: PromptResponse) => void;
|
||||
selectedSystemPrompt: PromptResponse | undefined;
|
||||
setUpdatedSystemPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
setConversationSettings: React.Dispatch<React.SetStateAction<Record<string, Conversation>>>;
|
||||
systemPromptSettings: PromptResponse[];
|
||||
setConversationsSettingsBulkActions: React.Dispatch<
|
||||
React.SetStateAction<ConversationsBulkActions>
|
||||
>;
|
||||
defaultConnector?: AIConnector;
|
||||
handleSave: (shouldRefetchConversation?: boolean) => void;
|
||||
onCancelClick: () => void;
|
||||
resetSettings: () => void;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
}
|
||||
|
||||
const SystemPromptSettingsManagementComponent = ({
|
||||
connectors,
|
||||
conversationSettings,
|
||||
onSelectedSystemPromptChange,
|
||||
setUpdatedSystemPromptSettings,
|
||||
setConversationSettings,
|
||||
selectedSystemPrompt,
|
||||
systemPromptSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
setConversationsSettingsBulkActions,
|
||||
defaultConnector,
|
||||
handleSave,
|
||||
onCancelClick,
|
||||
resetSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
}: Props) => {
|
||||
const { nameSpace } = useAssistantContext();
|
||||
const SystemPromptSettingsManagementComponent = ({ connectors, defaultConnector }: Props) => {
|
||||
const {
|
||||
nameSpace,
|
||||
http,
|
||||
assistantAvailability: { isAssistantEnabled },
|
||||
baseConversations,
|
||||
toasts,
|
||||
} = useAssistantContext();
|
||||
|
||||
const onFetchedConversations = useCallback(
|
||||
(conversationsData: FetchConversationsResponse): Record<string, Conversation> =>
|
||||
mergeBaseWithPersistedConversations(baseConversations, conversationsData),
|
||||
[baseConversations]
|
||||
);
|
||||
|
||||
const { data: allPrompts, refetch: refetchPrompts, isFetched: promptsLoaded } = useFetchPrompts();
|
||||
|
||||
const {
|
||||
data: conversations,
|
||||
isFetched: conversationsLoaded,
|
||||
refetch: refetchConversations,
|
||||
} = useFetchCurrentUserConversations({
|
||||
http,
|
||||
onFetch: onFetchedConversations,
|
||||
isAssistantEnabled,
|
||||
});
|
||||
|
||||
const refetchAll = useCallback(() => {
|
||||
refetchPrompts();
|
||||
refetchConversations();
|
||||
}, [refetchPrompts, refetchConversations]);
|
||||
|
||||
const isTableLoading = !conversationsLoaded || !promptsLoaded;
|
||||
const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility();
|
||||
const {
|
||||
isFlyoutOpen: deleteConfirmModalVisibility,
|
||||
|
@ -82,6 +87,48 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
} = useFlyoutModalVisibility();
|
||||
const [deletedPrompt, setDeletedPrompt] = useState<PromptResponse | null>();
|
||||
|
||||
const {
|
||||
conversationSettings,
|
||||
setConversationSettings,
|
||||
systemPromptSettings,
|
||||
setUpdatedSystemPromptSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
setConversationsSettingsBulkActions,
|
||||
resetSettings,
|
||||
saveSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
} = useSettingsUpdater(conversations, allPrompts, conversationsLoaded, promptsLoaded);
|
||||
|
||||
// System Prompt Selection State
|
||||
const [selectedSystemPrompt, setSelectedSystemPrompt] = useState<PromptResponse | undefined>();
|
||||
|
||||
const onSelectedSystemPromptChange = useCallback((systemPrompt?: PromptResponse) => {
|
||||
setSelectedSystemPrompt(systemPrompt);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSystemPrompt != null) {
|
||||
setSelectedSystemPrompt(systemPromptSettings.find((p) => p.id === selectedSystemPrompt.id));
|
||||
}
|
||||
}, [selectedSystemPrompt, systemPromptSettings]);
|
||||
|
||||
const handleSave = useCallback(
|
||||
async (param?: { callback?: () => void }) => {
|
||||
await saveSettings();
|
||||
toasts?.addSuccess({
|
||||
iconType: 'check',
|
||||
title: SETTINGS_UPDATED_TOAST_TITLE,
|
||||
});
|
||||
param?.callback?.();
|
||||
},
|
||||
[saveSettings, toasts]
|
||||
);
|
||||
|
||||
const onCancelClick = useCallback(() => {
|
||||
resetSettings();
|
||||
}, [resetSettings]);
|
||||
|
||||
const onCreate = useCallback(() => {
|
||||
onSelectedSystemPromptChange({
|
||||
id: '',
|
||||
|
@ -124,9 +171,9 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
|
||||
const onDeleteConfirmed = useCallback(() => {
|
||||
closeConfirmModal();
|
||||
handleSave(true);
|
||||
handleSave({ callback: refetchAll });
|
||||
setConversationsSettingsBulkActions({});
|
||||
}, [closeConfirmModal, handleSave, setConversationsSettingsBulkActions]);
|
||||
}, [closeConfirmModal, handleSave, refetchAll, setConversationsSettingsBulkActions]);
|
||||
|
||||
const onSaveCancelled = useCallback(() => {
|
||||
closeFlyout();
|
||||
|
@ -135,9 +182,9 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
|
||||
const onSaveConfirmed = useCallback(() => {
|
||||
closeFlyout();
|
||||
handleSave(true);
|
||||
handleSave({ callback: refetchAll });
|
||||
setConversationsSettingsBulkActions({});
|
||||
}, [closeFlyout, handleSave, setConversationsSettingsBulkActions]);
|
||||
}, [closeFlyout, handleSave, refetchAll, setConversationsSettingsBulkActions]);
|
||||
|
||||
const confirmationTitle = useMemo(
|
||||
() =>
|
||||
|
@ -156,8 +203,9 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
});
|
||||
|
||||
const columns = useMemo(
|
||||
() => getColumns({ onEditActionClicked, onDeleteActionClicked }),
|
||||
[getColumns, onEditActionClicked, onDeleteActionClicked]
|
||||
() =>
|
||||
getColumns({ isActionsDisabled: isTableLoading, onEditActionClicked, onDeleteActionClicked }),
|
||||
[getColumns, isTableLoading, onEditActionClicked, onDeleteActionClicked]
|
||||
);
|
||||
const systemPromptListItems = useMemo(
|
||||
() =>
|
||||
|
@ -173,10 +221,13 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
return (
|
||||
<>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="plusInCircle" onClick={onCreate}>
|
||||
{SETTINGS_TITLE}
|
||||
<EuiText size="m">{i18n.SYSTEM_PROMPTS_TABLE_SETTINGS_DESCRIPTION}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="plusInCircle" onClick={onCreate} disabled={isTableLoading}>
|
||||
{i18n.CREATE_SYSTEM_PROMPT_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
@ -196,6 +247,7 @@ const SystemPromptSettingsManagementComponent = ({
|
|||
onClose={onSaveCancelled}
|
||||
onSaveCancelled={onSaveCancelled}
|
||||
onSaveConfirmed={onSaveConfirmed}
|
||||
saveButtonDisabled={selectedSystemPrompt?.name == null || selectedSystemPrompt?.name === ''}
|
||||
>
|
||||
<SystemPromptEditor
|
||||
connectors={connectors}
|
||||
|
|
|
@ -20,10 +20,10 @@ export const SYSTEM_PROMPTS_TABLE_COLUMN_DEFAULT_CONVERSATIONS = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const SYSTEM_PROMPTS_TABLE_COLUMN_CREATED_ON = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnCreatedOn',
|
||||
export const SYSTEM_PROMPTS_TABLE_COLUMN_DATE_UPDATED = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnDateUpdated',
|
||||
{
|
||||
defaultMessage: 'Created on',
|
||||
defaultMessage: 'Date updated',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -34,6 +34,14 @@ export const SYSTEM_PROMPTS_TABLE_COLUMN_ACTIONS = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const SYSTEM_PROMPTS_TABLE_SETTINGS_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptsTable.settingsDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Create and manage System Prompts. System Prompts are configurable chunks of context that are always sent as part of the conversation.',
|
||||
}
|
||||
);
|
||||
|
||||
export const DELETE_SYSTEM_PROMPT_MODAL_TITLE = (prompt: string) =>
|
||||
i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptEditor.modal.deleteSystemPromptConfirmationTitle',
|
||||
|
@ -56,3 +64,10 @@ export const DELETE_SYSTEM_PROMPT_MODAL_DESCRIPTION = i18n.translate(
|
|||
defaultMessage: 'You cannot recover the prompt once deleted',
|
||||
}
|
||||
);
|
||||
|
||||
export const CREATE_SYSTEM_PROMPT_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptEditor.createSystemPromptLabel',
|
||||
{
|
||||
defaultMessage: 'System Prompt',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -65,14 +65,16 @@ describe('useSystemPromptTable', () => {
|
|||
const onEditActionClicked = jest.fn();
|
||||
const onDeleteActionClicked = jest.fn();
|
||||
const columns = result.current.getColumns({
|
||||
isActionsDisabled: false,
|
||||
onEditActionClicked,
|
||||
onDeleteActionClicked,
|
||||
});
|
||||
|
||||
expect(columns).toHaveLength(3);
|
||||
expect(columns).toHaveLength(4);
|
||||
expect(columns[0].name).toBe('Name');
|
||||
expect(columns[1].name).toBe('Default conversations');
|
||||
expect(columns[2].name).toBe('Actions');
|
||||
expect(columns[2].name).toBe('Date updated');
|
||||
expect(columns[3].name).toBe('Actions');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -4,34 +4,31 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiBasicTableColumn, EuiIcon, EuiLink } from '@elastic/eui';
|
||||
import { EuiBadge, EuiBasicTableColumn, EuiIcon, EuiLink } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { FormattedDate } from '@kbn/i18n-react';
|
||||
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../../assistant_context/types';
|
||||
import { AIConnector } from '../../../../connectorland/connector_selector';
|
||||
import { BadgesColumn } from '../../../common/components/assistant_settings_management/badges';
|
||||
import { RowActions } from '../../../common/components/assistant_settings_management/row_actions';
|
||||
import {
|
||||
getConversationApiConfig,
|
||||
getInitialDefaultSystemPrompt,
|
||||
} from '../../../use_conversation/helpers';
|
||||
import { useInlineActions } from '../../../common/components/assistant_settings_management/inline_actions';
|
||||
import { getConversationApiConfig } from '../../../use_conversation/helpers';
|
||||
import { SYSTEM_PROMPT_DEFAULT_NEW_CONVERSATION } from '../system_prompt_modal/translations';
|
||||
import * as i18n from './translations';
|
||||
import { getSelectedConversations } from './utils';
|
||||
|
||||
type ConversationsWithSystemPrompt = Record<
|
||||
string,
|
||||
Conversation & { systemPrompt: PromptResponse | undefined }
|
||||
>;
|
||||
|
||||
type SystemPromptTableItem = PromptResponse & { defaultConversations: string[] };
|
||||
|
||||
export const useSystemPromptTable = () => {
|
||||
const getActions = useInlineActions<SystemPromptTableItem>();
|
||||
const getColumns = useCallback(
|
||||
({
|
||||
isActionsDisabled,
|
||||
onEditActionClicked,
|
||||
onDeleteActionClicked,
|
||||
}: {
|
||||
isActionsDisabled: boolean;
|
||||
onEditActionClicked: (prompt: SystemPromptTableItem) => void;
|
||||
onDeleteActionClicked: (prompt: SystemPromptTableItem) => void;
|
||||
}): Array<EuiBasicTableColumn<SystemPromptTableItem>> => [
|
||||
|
@ -41,7 +38,7 @@ export const useSystemPromptTable = () => {
|
|||
truncateText: { lines: 3 },
|
||||
render: (prompt: SystemPromptTableItem) =>
|
||||
prompt?.name ? (
|
||||
<EuiLink onClick={() => onEditActionClicked(prompt)}>
|
||||
<EuiLink onClick={() => onEditActionClicked(prompt)} disabled={isActionsDisabled}>
|
||||
{prompt?.name}
|
||||
{prompt.isNewConversationDefault && (
|
||||
<EuiIcon
|
||||
|
@ -61,31 +58,33 @@ export const useSystemPromptTable = () => {
|
|||
<BadgesColumn items={defaultConversations} prefix={id} />
|
||||
),
|
||||
},
|
||||
/* TODO: enable when createdAt is added
|
||||
{
|
||||
align: 'left',
|
||||
field: 'createdAt',
|
||||
name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_CREATED_ON,
|
||||
field: 'updatedAt',
|
||||
name: i18n.SYSTEM_PROMPTS_TABLE_COLUMN_DATE_UPDATED,
|
||||
render: (updatedAt: SystemPromptTableItem['updatedAt']) =>
|
||||
updatedAt ? (
|
||||
<EuiBadge color="hollow">
|
||||
<FormattedDate
|
||||
value={new Date(updatedAt)}
|
||||
year="numeric"
|
||||
month="2-digit"
|
||||
day="numeric"
|
||||
/>
|
||||
</EuiBadge>
|
||||
) : null,
|
||||
sortable: true,
|
||||
},
|
||||
*/
|
||||
{
|
||||
align: 'center',
|
||||
name: 'Actions',
|
||||
width: '120px',
|
||||
render: (prompt: SystemPromptTableItem) => {
|
||||
const isDeletable = !prompt.isDefault;
|
||||
return (
|
||||
<RowActions<SystemPromptTableItem>
|
||||
rowItem={prompt}
|
||||
onEdit={onEditActionClicked}
|
||||
onDelete={isDeletable ? onDeleteActionClicked : undefined}
|
||||
isDeletable={isDeletable}
|
||||
/>
|
||||
);
|
||||
},
|
||||
...getActions({
|
||||
onDelete: onDeleteActionClicked,
|
||||
onEdit: onEditActionClicked,
|
||||
}),
|
||||
},
|
||||
],
|
||||
[]
|
||||
[getActions]
|
||||
);
|
||||
|
||||
const getSystemPromptsList = ({
|
||||
|
@ -99,14 +98,9 @@ export const useSystemPromptTable = () => {
|
|||
defaultConnector: AIConnector | undefined;
|
||||
systemPromptSettings: PromptResponse[];
|
||||
}): SystemPromptTableItem[] => {
|
||||
const conversationsWithApiConfig = Object.entries(
|
||||
conversationSettings
|
||||
).reduce<ConversationsWithSystemPrompt>((acc, [key, conversation]) => {
|
||||
const defaultSystemPrompt = getInitialDefaultSystemPrompt({
|
||||
allSystemPrompts: systemPromptSettings,
|
||||
conversation,
|
||||
});
|
||||
|
||||
const conversationsWithApiConfig = Object.entries(conversationSettings).reduce<
|
||||
Record<string, Conversation>
|
||||
>((acc, [key, conversation]) => {
|
||||
acc[key] = {
|
||||
...conversation,
|
||||
...getConversationApiConfig({
|
||||
|
@ -115,18 +109,19 @@ export const useSystemPromptTable = () => {
|
|||
conversation,
|
||||
defaultConnector,
|
||||
}),
|
||||
systemPrompt: defaultSystemPrompt,
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
return systemPromptSettings.map((systemPrompt) => {
|
||||
const defaultConversations = getSelectedConversations(
|
||||
conversationsWithApiConfig,
|
||||
systemPrompt?.id
|
||||
).map(({ title }) => title);
|
||||
|
||||
return {
|
||||
...systemPrompt,
|
||||
defaultConversations: getSelectedConversations(
|
||||
systemPromptSettings,
|
||||
conversationsWithApiConfig,
|
||||
systemPrompt?.id
|
||||
).map(({ title }) => title),
|
||||
defaultConversations,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,11 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { ProviderEnum } from '@kbn/elastic-assistant-common';
|
||||
import { mockSystemPrompts } from '../../../../mock/system_prompt';
|
||||
import { getSelectedConversations } from './utils';
|
||||
import { PromptTypeEnum } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
describe('getSelectedConversations', () => {
|
||||
const allSystemPrompts = [...mockSystemPrompts];
|
||||
const conversationSettings = {
|
||||
'8f1e3218-0b02-480a-8791-78c1ed5f3708': {
|
||||
timestamp: '2024-06-25T12:33:26.779Z',
|
||||
|
@ -48,22 +46,14 @@ describe('getSelectedConversations', () => {
|
|||
test('should return selected conversations', () => {
|
||||
const systemPromptId = 'mock-system-prompt-1';
|
||||
|
||||
const conversations = getSelectedConversations(
|
||||
allSystemPrompts,
|
||||
conversationSettings,
|
||||
systemPromptId
|
||||
);
|
||||
const conversations = getSelectedConversations(conversationSettings, systemPromptId);
|
||||
|
||||
expect(conversations).toEqual(Object.values(conversationSettings));
|
||||
});
|
||||
test('should return empty array if no conversations are selected', () => {
|
||||
const systemPromptId = 'ooo';
|
||||
|
||||
const conversations = getSelectedConversations(
|
||||
allSystemPrompts,
|
||||
conversationSettings,
|
||||
systemPromptId
|
||||
);
|
||||
const conversations = getSelectedConversations(conversationSettings, systemPromptId);
|
||||
|
||||
expect(conversations).toEqual([]);
|
||||
});
|
||||
|
|
|
@ -5,18 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../../assistant_context/types';
|
||||
|
||||
export const getSelectedConversations = (
|
||||
allSystemPrompts: PromptResponse[],
|
||||
conversationSettings: Record<string, Conversation>,
|
||||
systemPromptId: string
|
||||
) => {
|
||||
return Object.values(conversationSettings).filter((conversation) => {
|
||||
const conversationSystemPrompt = allSystemPrompts.find(
|
||||
(prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId
|
||||
);
|
||||
return conversationSystemPrompt?.id === systemPromptId;
|
||||
});
|
||||
return Object.values(conversationSettings).filter(
|
||||
(conversation) => conversation?.apiConfig?.defaultSystemPromptId === systemPromptId
|
||||
);
|
||||
};
|
||||
|
|
|
@ -67,7 +67,7 @@ export const QUICK_PROMPT_CONTEXTS_HELP_TEXT = i18n.translate(
|
|||
'xpack.elasticAssistant.assistant.quickPrompts.settings.contextsHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Select the Prompt Contexts that this Quick Prompt will be available for. Selecting none will make this Quick Prompt available at all times.',
|
||||
'Select where this Quick Prompt will appear. Selecting none will make this prompt appear everywhere.',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiConfirmModal,
|
||||
|
@ -13,16 +13,14 @@ import {
|
|||
EuiInMemoryTable,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
PromptResponse,
|
||||
PerformBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { QuickPromptSettingsEditor } from '../quick_prompt_settings/quick_prompt_editor';
|
||||
import * as i18n from './translations';
|
||||
import { useFlyoutModalVisibility } from '../../common/components/assistant_settings_management/flyout/use_flyout_modal_visibility';
|
||||
import { Flyout } from '../../common/components/assistant_settings_management/flyout';
|
||||
import { CANCEL, DELETE } from '../../settings/translations';
|
||||
import { CANCEL, DELETE, SETTINGS_UPDATED_TOAST_TITLE } from '../../settings/translations';
|
||||
import { useQuickPromptEditor } from '../quick_prompt_settings/use_quick_prompt_editor';
|
||||
import { useQuickPromptTable } from './use_quick_prompt_table';
|
||||
import {
|
||||
|
@ -31,31 +29,58 @@ import {
|
|||
} from '../../common/components/assistant_settings_management/pagination/use_session_pagination';
|
||||
import { QUICK_PROMPT_TABLE_SESSION_STORAGE_KEY } from '../../../assistant_context/constants';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
import {
|
||||
DEFAULT_CONVERSATIONS,
|
||||
useSettingsUpdater,
|
||||
} from '../../settings/use_settings_updater/use_settings_updater';
|
||||
import { useFetchPrompts } from '../../api';
|
||||
|
||||
interface Props {
|
||||
handleSave: (shouldRefetchConversation?: boolean) => void;
|
||||
onCancelClick: () => void;
|
||||
onSelectedQuickPromptChange: (quickPrompt?: PromptResponse) => void;
|
||||
quickPromptSettings: PromptResponse[];
|
||||
resetSettings?: () => void;
|
||||
selectedQuickPrompt: PromptResponse | undefined;
|
||||
setUpdatedQuickPromptSettings: React.Dispatch<React.SetStateAction<PromptResponse[]>>;
|
||||
promptsBulkActions: PromptsPerformBulkActionRequestBody;
|
||||
setPromptsBulkActions: React.Dispatch<React.SetStateAction<PromptsPerformBulkActionRequestBody>>;
|
||||
}
|
||||
const QuickPromptSettingsManagementComponent = () => {
|
||||
const { nameSpace, basePromptContexts, toasts } = useAssistantContext();
|
||||
|
||||
const QuickPromptSettingsManagementComponent = ({
|
||||
handleSave,
|
||||
onCancelClick,
|
||||
onSelectedQuickPromptChange,
|
||||
quickPromptSettings,
|
||||
resetSettings,
|
||||
selectedQuickPrompt,
|
||||
setUpdatedQuickPromptSettings,
|
||||
promptsBulkActions,
|
||||
setPromptsBulkActions,
|
||||
}: Props) => {
|
||||
const { nameSpace, basePromptContexts } = useAssistantContext();
|
||||
const { data: allPrompts, isFetched: promptsLoaded, refetch: refetchPrompts } = useFetchPrompts();
|
||||
|
||||
const {
|
||||
promptsBulkActions,
|
||||
quickPromptSettings,
|
||||
resetSettings,
|
||||
saveSettings,
|
||||
setPromptsBulkActions,
|
||||
setUpdatedQuickPromptSettings,
|
||||
} = useSettingsUpdater(
|
||||
DEFAULT_CONVERSATIONS, // Quick Prompt settings do not require conversations
|
||||
allPrompts,
|
||||
false, // Quick Prompt settings do not require conversations
|
||||
promptsLoaded
|
||||
);
|
||||
|
||||
// Quick Prompt Selection State
|
||||
const [selectedQuickPrompt, setSelectedQuickPrompt] = useState<PromptResponse | undefined>();
|
||||
const onSelectedQuickPromptChange = useCallback((quickPrompt?: PromptResponse) => {
|
||||
setSelectedQuickPrompt(quickPrompt);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedQuickPrompt != null) {
|
||||
setSelectedQuickPrompt(quickPromptSettings.find((q) => q.name === selectedQuickPrompt.name));
|
||||
}
|
||||
}, [quickPromptSettings, selectedQuickPrompt]);
|
||||
|
||||
const handleSave = useCallback(
|
||||
async (param?: { callback?: () => void }) => {
|
||||
await saveSettings();
|
||||
toasts?.addSuccess({
|
||||
iconType: 'check',
|
||||
title: SETTINGS_UPDATED_TOAST_TITLE,
|
||||
});
|
||||
param?.callback?.();
|
||||
},
|
||||
[saveSettings, toasts]
|
||||
);
|
||||
|
||||
const onCancelClick = useCallback(() => {
|
||||
resetSettings();
|
||||
}, [resetSettings]);
|
||||
|
||||
const { isFlyoutOpen: editFlyoutVisible, openFlyout, closeFlyout } = useFlyoutModalVisibility();
|
||||
const [deletedQuickPrompt, setDeletedQuickPrompt] = useState<PromptResponse | null>();
|
||||
|
@ -96,9 +121,9 @@ const QuickPromptSettingsManagementComponent = ({
|
|||
}, [closeConfirmModal, onCancelClick]);
|
||||
|
||||
const onDeleteConfirmed = useCallback(() => {
|
||||
handleSave();
|
||||
handleSave({ callback: refetchPrompts });
|
||||
closeConfirmModal();
|
||||
}, [closeConfirmModal, handleSave]);
|
||||
}, [closeConfirmModal, handleSave, refetchPrompts]);
|
||||
|
||||
const onCreate = useCallback(() => {
|
||||
onSelectedQuickPromptChange();
|
||||
|
@ -112,13 +137,14 @@ const QuickPromptSettingsManagementComponent = ({
|
|||
}, [closeFlyout, onSelectedQuickPromptChange, onCancelClick]);
|
||||
|
||||
const onSaveConfirmed = useCallback(() => {
|
||||
handleSave();
|
||||
handleSave({ callback: refetchPrompts });
|
||||
onSelectedQuickPromptChange();
|
||||
closeFlyout();
|
||||
}, [closeFlyout, handleSave, onSelectedQuickPromptChange]);
|
||||
}, [closeFlyout, handleSave, onSelectedQuickPromptChange, refetchPrompts]);
|
||||
|
||||
const { getColumns } = useQuickPromptTable();
|
||||
const columns = getColumns({
|
||||
isActionsDisabled: !promptsLoaded,
|
||||
basePromptContexts,
|
||||
onEditActionClicked,
|
||||
onDeleteActionClicked,
|
||||
|
@ -141,9 +167,12 @@ const QuickPromptSettingsManagementComponent = ({
|
|||
return (
|
||||
<>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="plusInCircle" onClick={onCreate}>
|
||||
<EuiText size="m">{i18n.QUICK_PROMPTS_DESCRIPTION}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="plusInCircle" onClick={onCreate} disabled={!promptsLoaded}>
|
||||
{i18n.QUICK_PROMPTS_TABLE_CREATE_BUTTON_TITLE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
@ -163,6 +192,7 @@ const QuickPromptSettingsManagementComponent = ({
|
|||
onClose={onSaveCancelled}
|
||||
onSaveCancelled={onSaveCancelled}
|
||||
onSaveConfirmed={onSaveConfirmed}
|
||||
saveButtonDisabled={selectedQuickPrompt?.name == null || selectedQuickPrompt?.name === ''}
|
||||
>
|
||||
<QuickPromptSettingsEditor
|
||||
onSelectedQuickPromptChange={onSelectedQuickPromptChange}
|
||||
|
|
|
@ -13,10 +13,10 @@ export const QUICK_PROMPTS_TABLE_COLUMN_NAME = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const QUICK_PROMPTS_TABLE_COLUMN_CREATED_AT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnCreatedAt',
|
||||
export const QUICK_PROMPTS_TABLE_COLUMN_DATE_UPDATED = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnDateUpdated',
|
||||
{
|
||||
defaultMessage: 'Created at',
|
||||
defaultMessage: 'Date updated',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -27,6 +27,14 @@ export const QUICK_PROMPTS_TABLE_COLUMN_ACTIONS = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const QUICK_PROMPTS_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.quickPromptsTable.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Create and manage Quick Prompts. Quick Prompts are shortcuts to common actions.',
|
||||
}
|
||||
);
|
||||
|
||||
export const QUICK_PROMPTS_TABLE_CREATE_BUTTON_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.quickPromptsTable.createButtonTitle',
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useQuickPromptTable } from './use_quick_prompt_table';
|
||||
import { EuiTableComputedColumnType } from '@elastic/eui';
|
||||
import { EuiTableActionsColumnType, EuiTableComputedColumnType } from '@elastic/eui';
|
||||
import { MOCK_QUICK_PROMPTS } from '../../../mock/quick_prompt';
|
||||
import { mockPromptContexts } from '../../../mock/prompt_context';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
@ -17,30 +17,29 @@ const mockOnDeleteActionClicked = jest.fn();
|
|||
|
||||
describe('useQuickPromptTable', () => {
|
||||
const { result } = renderHook(() => useQuickPromptTable());
|
||||
const props = {
|
||||
isActionsDisabled: false,
|
||||
basePromptContexts: mockPromptContexts,
|
||||
onEditActionClicked: mockOnEditActionClicked,
|
||||
onDeleteActionClicked: mockOnDeleteActionClicked,
|
||||
};
|
||||
|
||||
describe('getColumns', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should return columns with correct render functions', () => {
|
||||
const columns = result.current.getColumns({
|
||||
basePromptContexts: mockPromptContexts,
|
||||
onEditActionClicked: mockOnEditActionClicked,
|
||||
onDeleteActionClicked: mockOnDeleteActionClicked,
|
||||
});
|
||||
const columns = result.current.getColumns(props);
|
||||
|
||||
expect(columns).toHaveLength(3);
|
||||
expect(columns).toHaveLength(4);
|
||||
expect(columns[0].name).toBe('Name');
|
||||
expect(columns[1].name).toBe('Contexts');
|
||||
expect(columns[2].name).toBe('Actions');
|
||||
expect(columns[2].name).toBe('Date updated');
|
||||
expect(columns[3].name).toBe('Actions');
|
||||
});
|
||||
|
||||
it('should render contexts column correctly', () => {
|
||||
const columns = result.current.getColumns({
|
||||
basePromptContexts: mockPromptContexts,
|
||||
onEditActionClicked: mockOnEditActionClicked,
|
||||
onDeleteActionClicked: mockOnDeleteActionClicked,
|
||||
});
|
||||
const columns = result.current.getColumns(props);
|
||||
|
||||
const mockQuickPrompt = { ...MOCK_QUICK_PROMPTS[0], categories: ['alert'] };
|
||||
const mockBadgesColumn = (columns[1] as EuiTableComputedColumnType<PromptResponse>).render(
|
||||
|
@ -56,42 +55,22 @@ describe('useQuickPromptTable', () => {
|
|||
});
|
||||
|
||||
it('should not render delete action for non-deletable prompt', () => {
|
||||
const columns = result.current.getColumns({
|
||||
basePromptContexts: mockPromptContexts,
|
||||
onEditActionClicked: mockOnEditActionClicked,
|
||||
onDeleteActionClicked: mockOnDeleteActionClicked,
|
||||
});
|
||||
const columns = result.current.getColumns(props);
|
||||
|
||||
const mockRowActions = (columns[2] as EuiTableComputedColumnType<PromptResponse>).render(
|
||||
MOCK_QUICK_PROMPTS[0]
|
||||
);
|
||||
|
||||
expect(mockRowActions).toHaveProperty('props', {
|
||||
rowItem: MOCK_QUICK_PROMPTS[0],
|
||||
onDelete: undefined,
|
||||
onEdit: mockOnEditActionClicked,
|
||||
isDeletable: false,
|
||||
});
|
||||
const defaultPrompt = MOCK_QUICK_PROMPTS.find((qp) => qp.isDefault);
|
||||
if (defaultPrompt) {
|
||||
const mockRowActions = (columns[3] as EuiTableActionsColumnType<PromptResponse>).actions[1];
|
||||
expect(mockRowActions?.enabled?.(defaultPrompt)).toEqual(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should render delete actions correctly for deletable prompt', () => {
|
||||
const columns = result.current.getColumns({
|
||||
basePromptContexts: mockPromptContexts,
|
||||
onEditActionClicked: mockOnEditActionClicked,
|
||||
onDeleteActionClicked: mockOnDeleteActionClicked,
|
||||
});
|
||||
const columns = result.current.getColumns(props);
|
||||
|
||||
const nonDefaultPrompt = MOCK_QUICK_PROMPTS.find((qp) => !qp.isDefault);
|
||||
if (nonDefaultPrompt) {
|
||||
const mockRowActions = (columns[2] as EuiTableComputedColumnType<PromptResponse>).render(
|
||||
nonDefaultPrompt
|
||||
);
|
||||
expect(mockRowActions).toHaveProperty('props', {
|
||||
rowItem: nonDefaultPrompt,
|
||||
onDelete: mockOnDeleteActionClicked,
|
||||
onEdit: mockOnEditActionClicked,
|
||||
isDeletable: true,
|
||||
});
|
||||
const mockRowActions = (columns[3] as EuiTableActionsColumnType<PromptResponse>).actions[1];
|
||||
expect(mockRowActions?.enabled?.(nonDefaultPrompt)).toEqual(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,21 +5,25 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiBasicTableColumn, EuiLink } from '@elastic/eui';
|
||||
import { EuiBadge, EuiBasicTableColumn, EuiLink } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { FormattedDate } from '@kbn/i18n-react';
|
||||
import { BadgesColumn } from '../../common/components/assistant_settings_management/badges';
|
||||
import { RowActions } from '../../common/components/assistant_settings_management/row_actions';
|
||||
import { PromptContextTemplate } from '../../prompt_context/types';
|
||||
import * as i18n from './translations';
|
||||
import { useInlineActions } from '../../common/components/assistant_settings_management/inline_actions';
|
||||
|
||||
export const useQuickPromptTable = () => {
|
||||
const getActions = useInlineActions<PromptResponse>();
|
||||
const getColumns = useCallback(
|
||||
({
|
||||
isActionsDisabled,
|
||||
basePromptContexts,
|
||||
onEditActionClicked,
|
||||
onDeleteActionClicked,
|
||||
}: {
|
||||
isActionsDisabled: boolean;
|
||||
basePromptContexts: PromptContextTemplate[];
|
||||
onEditActionClicked: (prompt: PromptResponse) => void;
|
||||
onDeleteActionClicked: (prompt: PromptResponse) => void;
|
||||
|
@ -29,7 +33,9 @@ export const useQuickPromptTable = () => {
|
|||
name: i18n.QUICK_PROMPTS_TABLE_COLUMN_NAME,
|
||||
render: (prompt: PromptResponse) =>
|
||||
prompt?.name ? (
|
||||
<EuiLink onClick={() => onEditActionClicked(prompt)}>{prompt?.name}</EuiLink>
|
||||
<EuiLink onClick={() => onEditActionClicked(prompt)} disabled={isActionsDisabled}>
|
||||
{prompt?.name}
|
||||
</EuiLink>
|
||||
) : null,
|
||||
sortable: ({ name }: PromptResponse) => name,
|
||||
},
|
||||
|
@ -47,34 +53,33 @@ export const useQuickPromptTable = () => {
|
|||
) : null;
|
||||
},
|
||||
},
|
||||
/* TODO: enable when createdAt is added
|
||||
{
|
||||
align: 'left',
|
||||
field: 'createdAt',
|
||||
name: i18n.QUICK_PROMPTS_TABLE_COLUMN_CREATED_AT,
|
||||
field: 'updatedAt',
|
||||
name: i18n.QUICK_PROMPTS_TABLE_COLUMN_DATE_UPDATED,
|
||||
render: (updatedAt: PromptResponse['updatedAt']) =>
|
||||
updatedAt ? (
|
||||
<EuiBadge color="hollow">
|
||||
<FormattedDate
|
||||
value={new Date(updatedAt)}
|
||||
year="numeric"
|
||||
month="2-digit"
|
||||
day="numeric"
|
||||
/>
|
||||
</EuiBadge>
|
||||
) : null,
|
||||
sortable: true,
|
||||
},
|
||||
*/
|
||||
{
|
||||
align: 'center',
|
||||
name: i18n.QUICK_PROMPTS_TABLE_COLUMN_ACTIONS,
|
||||
width: '120px',
|
||||
render: (prompt: PromptResponse) => {
|
||||
if (!prompt) {
|
||||
return null;
|
||||
}
|
||||
const isDeletable = !prompt.isDefault;
|
||||
return (
|
||||
<RowActions<PromptResponse>
|
||||
rowItem={prompt}
|
||||
onDelete={isDeletable ? onDeleteActionClicked : undefined}
|
||||
onEdit={onEditActionClicked}
|
||||
isDeletable={isDeletable}
|
||||
/>
|
||||
);
|
||||
},
|
||||
...getActions({
|
||||
onDelete: onDeleteActionClicked,
|
||||
onEdit: onEditActionClicked,
|
||||
}),
|
||||
},
|
||||
],
|
||||
[]
|
||||
[getActions]
|
||||
);
|
||||
|
||||
return { getColumns };
|
||||
|
|
|
@ -81,7 +81,6 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
conversationsLoaded,
|
||||
}) => {
|
||||
const {
|
||||
actionTypeRegistry,
|
||||
assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled },
|
||||
http,
|
||||
toasts,
|
||||
|
@ -97,7 +96,7 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
|
||||
const { data: anonymizationFields, refetch: refetchAnonymizationFieldsResults } =
|
||||
useFetchAnonymizationFields();
|
||||
const { data: allPrompts } = useFetchPrompts();
|
||||
const { data: allPrompts, isFetched: promptsLoaded } = useFetchPrompts();
|
||||
|
||||
const { data: connectors } = useLoadConnectors({
|
||||
http,
|
||||
|
@ -123,7 +122,13 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
setUpdatedAnonymizationData,
|
||||
setPromptsBulkActions,
|
||||
setUpdatedSystemPromptSettings,
|
||||
} = useSettingsUpdater(conversations, allPrompts, conversationsLoaded, anonymizationFields);
|
||||
} = useSettingsUpdater(
|
||||
conversations,
|
||||
allPrompts,
|
||||
conversationsLoaded,
|
||||
promptsLoaded,
|
||||
anonymizationFields
|
||||
);
|
||||
|
||||
// Local state for saving previously selected items so tab switching is friendlier
|
||||
// Conversation Selection State
|
||||
|
@ -315,14 +320,13 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
className="eui-scrollBar"
|
||||
grow={true}
|
||||
css={css`
|
||||
max-height: 550px;
|
||||
max-height: 519px;
|
||||
overflow-y: scroll;
|
||||
`}
|
||||
>
|
||||
{!selectedSettingsTab ||
|
||||
(selectedSettingsTab === CONVERSATIONS_TAB && (
|
||||
<ConversationSettings
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
connectors={connectors}
|
||||
defaultConnector={defaultConnector}
|
||||
conversationSettings={conversationSettings}
|
||||
|
|
|
@ -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 {
|
||||
EuiPageTemplate,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { CANCEL, SAVE } from './translations';
|
||||
|
||||
export const AssistantSettingsBottomBar: React.FC<{
|
||||
hasPendingChanges: boolean;
|
||||
onCancelClick: () => void;
|
||||
onSaveButtonClicked: () => void;
|
||||
}> = React.memo(({ hasPendingChanges, onCancelClick, onSaveButtonClicked }) =>
|
||||
hasPendingChanges ? (
|
||||
<EuiPageTemplate.BottomBar paddingSize="s" position="fixed" data-test-subj="bottom-bar">
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
color="text"
|
||||
iconType="cross"
|
||||
data-test-subj="cancel-button"
|
||||
onClick={onCancelClick}
|
||||
>
|
||||
{CANCEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
type="submit"
|
||||
data-test-subj="save-button"
|
||||
onClick={onSaveButtonClicked}
|
||||
iconType="check"
|
||||
fill
|
||||
>
|
||||
{SAVE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageTemplate.BottomBar>
|
||||
) : null
|
||||
);
|
||||
AssistantSettingsBottomBar.displayName = 'AssistantSettingsBottomBar';
|
|
@ -10,10 +10,11 @@ import { useAssistantContext } from '../../assistant_context';
|
|||
import { fireEvent, render } from '@testing-library/react';
|
||||
|
||||
import React from 'react';
|
||||
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { MOCK_QUICK_PROMPTS } from '../../mock/quick_prompt';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { AssistantSettingsManagement } from './assistant_settings_management';
|
||||
|
||||
import {
|
||||
ANONYMIZATION_TAB,
|
||||
CONNECTORS_TAB,
|
||||
|
@ -51,22 +52,9 @@ const mockContext = {
|
|||
isAssistantEnabled: true,
|
||||
},
|
||||
};
|
||||
const onClose = jest.fn();
|
||||
const onSave = jest.fn().mockResolvedValue(() => {});
|
||||
const onConversationSelected = jest.fn();
|
||||
|
||||
const testProps = {
|
||||
conversationsLoaded: true,
|
||||
defaultConnectorId: '123',
|
||||
defaultProvider: OpenAiProviderType.OpenAi,
|
||||
selectedConversation: welcomeConvo,
|
||||
onClose,
|
||||
onSave,
|
||||
onConversationSelected,
|
||||
conversations: {},
|
||||
anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] },
|
||||
refetchAnonymizationFieldsResults: jest.fn(),
|
||||
refetchConversations: jest.fn(),
|
||||
};
|
||||
jest.mock('../../assistant_context');
|
||||
|
||||
|
@ -86,6 +74,10 @@ jest.mock('../prompt_editor/system_prompt/system_prompt_settings_management', ()
|
|||
SystemPromptSettingsManagement: () => <span data-test-subj="SYSTEM_PROMPTS_TAB-tab" />,
|
||||
}));
|
||||
|
||||
jest.mock('../../knowledge_base/knowledge_base_settings_management', () => ({
|
||||
KnowledgeBaseSettingsManagement: () => <span data-test-subj="KNOWLEDGE_BASE_TAB-tab" />,
|
||||
}));
|
||||
|
||||
jest.mock('../../data_anonymization/settings/anonymization_settings_management', () => ({
|
||||
AnonymizationSettingsManagement: () => <span data-test-subj="ANONYMIZATION_TAB-tab" />,
|
||||
}));
|
||||
|
@ -93,7 +85,6 @@ jest.mock('../../data_anonymization/settings/anonymization_settings_management',
|
|||
jest.mock('.', () => {
|
||||
return {
|
||||
EvaluationSettings: () => <span data-test-subj="EVALUATION_TAB-tab" />,
|
||||
KnowledgeBaseSettings: () => <span data-test-subj="KNOWLEDGE_BASE_TAB-tab" />,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -105,10 +96,16 @@ jest.mock('./use_settings_updater/use_settings_updater', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('../../connectorland/use_load_connectors', () => ({
|
||||
useLoadConnectors: jest.fn().mockReturnValue({ data: [] }),
|
||||
}));
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const wrapper = (props: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{props.children}</QueryClientProvider>
|
||||
<I18nProvider>
|
||||
<QueryClientProvider client={queryClient}>{props.children}</QueryClientProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
describe('AssistantSettingsManagement', () => {
|
||||
|
|
|
@ -5,29 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiAvatar,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPageTemplate,
|
||||
EuiTitle,
|
||||
useEuiShadow,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { EuiAvatar, EuiPageTemplate, EuiTitle, useEuiShadow, useEuiTheme } from '@elastic/eui';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { PromptResponse, PromptTypeEnum } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../..';
|
||||
import * as i18n from './translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { useSettingsUpdater } from './use_settings_updater/use_settings_updater';
|
||||
import { KnowledgeBaseSettings, EvaluationSettings } from '.';
|
||||
import { useLoadConnectors } from '../../connectorland/use_load_connectors';
|
||||
import { getDefaultConnector } from '../helpers';
|
||||
import { useFetchAnonymizationFields } from '../api/anonymization_fields/use_fetch_anonymization_fields';
|
||||
import { ConnectorsSettingsManagement } from '../../connectorland/connector_settings_management';
|
||||
import { ConversationSettingsManagement } from '../conversations/conversation_settings_management';
|
||||
import { QuickPromptSettingsManagement } from '../quick_prompts/quick_prompt_settings_management';
|
||||
|
@ -43,13 +29,11 @@ import {
|
|||
QUICK_PROMPTS_TAB,
|
||||
SYSTEM_PROMPTS_TAB,
|
||||
} from './const';
|
||||
import { useFetchPrompts } from '../api/prompts/use_fetch_prompts';
|
||||
import { KnowledgeBaseSettingsManagement } from '../../knowledge_base/knowledge_base_settings_management';
|
||||
import { EvaluationSettings } from '.';
|
||||
|
||||
interface Props {
|
||||
conversations: Record<string, Conversation>;
|
||||
conversationsLoaded: boolean;
|
||||
selectedConversation: Conversation;
|
||||
refetchConversations: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,149 +41,28 @@ interface Props {
|
|||
* anonymization, knowledge base, and evaluation via the `isModelEvaluationEnabled` feature flag.
|
||||
*/
|
||||
export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
||||
({
|
||||
conversations,
|
||||
conversationsLoaded,
|
||||
refetchConversations,
|
||||
selectedConversation: defaultSelectedConversation,
|
||||
}) => {
|
||||
({ selectedConversation: defaultSelectedConversation }) => {
|
||||
const {
|
||||
assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled },
|
||||
http,
|
||||
selectedSettingsTab,
|
||||
setSelectedSettingsTab,
|
||||
toasts,
|
||||
} = useAssistantContext();
|
||||
|
||||
const { data: anonymizationFields } = useFetchAnonymizationFields();
|
||||
|
||||
const { data: allPrompts } = useFetchPrompts();
|
||||
|
||||
// Connector details
|
||||
const { data: connectors } = useLoadConnectors({
|
||||
http,
|
||||
});
|
||||
const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]);
|
||||
|
||||
const [hasPendingChanges, setHasPendingChanges] = useState(false);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const headerIconShadow = useEuiShadow('s');
|
||||
|
||||
const {
|
||||
conversationSettings,
|
||||
setConversationSettings,
|
||||
knowledgeBase,
|
||||
quickPromptSettings,
|
||||
systemPromptSettings,
|
||||
assistantStreamingEnabled,
|
||||
setUpdatedAssistantStreamingEnabled,
|
||||
setUpdatedKnowledgeBaseSettings,
|
||||
setUpdatedQuickPromptSettings,
|
||||
setPromptsBulkActions,
|
||||
saveSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
updatedAnonymizationData,
|
||||
setConversationsSettingsBulkActions,
|
||||
anonymizationFieldsBulkActions,
|
||||
setAnonymizationFieldsBulkActions,
|
||||
setUpdatedAnonymizationData,
|
||||
setUpdatedSystemPromptSettings,
|
||||
promptsBulkActions,
|
||||
resetSettings,
|
||||
} = useSettingsUpdater(
|
||||
conversations,
|
||||
allPrompts,
|
||||
conversationsLoaded,
|
||||
anonymizationFields ?? { page: 0, perPage: 0, total: 0, data: [] }
|
||||
);
|
||||
|
||||
const quickPrompts = useMemo(
|
||||
() =>
|
||||
quickPromptSettings.length === 0
|
||||
? allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.quick)
|
||||
: quickPromptSettings,
|
||||
[allPrompts.data, quickPromptSettings]
|
||||
);
|
||||
|
||||
const systemPrompts = useMemo(
|
||||
() =>
|
||||
systemPromptSettings.length === 0
|
||||
? allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system)
|
||||
: systemPromptSettings,
|
||||
[allPrompts.data, systemPromptSettings]
|
||||
);
|
||||
|
||||
// Local state for saving previously selected items so tab switching is friendlier
|
||||
// Conversation Selection State
|
||||
const [selectedConversation, setSelectedConversation] = useState<Conversation | undefined>(
|
||||
() => {
|
||||
return conversationSettings[defaultSelectedConversation.title];
|
||||
}
|
||||
);
|
||||
|
||||
const onHandleSelectedConversationChange = useCallback((conversation?: Conversation) => {
|
||||
setSelectedConversation(conversation);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedConversation != null) {
|
||||
setSelectedConversation(
|
||||
// conversationSettings has title as key, sometime has id as key
|
||||
conversationSettings[selectedConversation.id] ||
|
||||
conversationSettings[selectedConversation.title]
|
||||
);
|
||||
}
|
||||
}, [conversationSettings, selectedConversation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSettingsTab == null) {
|
||||
setSelectedSettingsTab(CONNECTORS_TAB);
|
||||
}
|
||||
}, [selectedSettingsTab, setSelectedSettingsTab]);
|
||||
|
||||
// Quick Prompt Selection State
|
||||
const [selectedQuickPrompt, setSelectedQuickPrompt] = useState<PromptResponse | undefined>();
|
||||
const onHandleSelectedQuickPromptChange = useCallback((quickPrompt?: PromptResponse) => {
|
||||
setSelectedQuickPrompt(quickPrompt);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (selectedQuickPrompt != null) {
|
||||
setSelectedQuickPrompt(
|
||||
quickPromptSettings.find((q) => q.name === selectedQuickPrompt.name)
|
||||
);
|
||||
}
|
||||
}, [quickPromptSettings, selectedQuickPrompt]);
|
||||
|
||||
// System Prompt Selection State
|
||||
const [selectedSystemPrompt, setSelectedSystemPrompt] = useState<PromptResponse | undefined>();
|
||||
const onHandleSelectedSystemPromptChange = useCallback((systemPrompt?: PromptResponse) => {
|
||||
setSelectedSystemPrompt(systemPrompt);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (selectedSystemPrompt != null) {
|
||||
setSelectedSystemPrompt(systemPromptSettings.find((p) => p.id === selectedSystemPrompt.id));
|
||||
}
|
||||
}, [selectedSystemPrompt, systemPromptSettings]);
|
||||
|
||||
const handleSave = useCallback(
|
||||
async (shouldRefetchConversation?: boolean) => {
|
||||
await saveSettings();
|
||||
toasts?.addSuccess({
|
||||
iconType: 'check',
|
||||
title: i18n.SETTINGS_UPDATED_TOAST_TITLE,
|
||||
});
|
||||
setHasPendingChanges(false);
|
||||
if (shouldRefetchConversation) {
|
||||
refetchConversations();
|
||||
}
|
||||
},
|
||||
[refetchConversations, saveSettings, toasts]
|
||||
);
|
||||
|
||||
const onSaveButtonClicked = useCallback(() => {
|
||||
handleSave(true);
|
||||
}, [handleSave]);
|
||||
|
||||
const tabsConfig = useMemo(
|
||||
() => [
|
||||
{
|
||||
|
@ -247,18 +110,6 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
}));
|
||||
}, [setSelectedSettingsTab, selectedSettingsTab, tabsConfig]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(callback) => (value: unknown) => {
|
||||
setHasPendingChanges(true);
|
||||
callback(value);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const onCancelClick = useCallback(() => {
|
||||
resetSettings();
|
||||
setHasPendingChanges(false);
|
||||
}, [resetSettings]);
|
||||
return (
|
||||
<>
|
||||
<EuiPageTemplate.Header
|
||||
|
@ -275,7 +126,7 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
`}
|
||||
/>
|
||||
<EuiTitle size="m" className="eui-displayInlineBlock">
|
||||
<h2>{i18n.SECURITY_AI_SETTINGS}</h2>
|
||||
<span>{i18n.SECURITY_AI_SETTINGS}</span>
|
||||
</EuiTitle>
|
||||
</>
|
||||
}
|
||||
|
@ -294,100 +145,22 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
{selectedSettingsTab === CONNECTORS_TAB && <ConnectorsSettingsManagement />}
|
||||
{selectedSettingsTab === CONVERSATIONS_TAB && (
|
||||
<ConversationSettingsManagement
|
||||
allSystemPrompts={systemPromptSettings}
|
||||
assistantStreamingEnabled={assistantStreamingEnabled}
|
||||
connectors={connectors}
|
||||
conversationSettings={conversationSettings}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
conversationsSettingsBulkActions={conversationsSettingsBulkActions}
|
||||
defaultConnector={defaultConnector}
|
||||
handleSave={handleSave}
|
||||
onCancelClick={onCancelClick}
|
||||
onSelectedConversationChange={onHandleSelectedConversationChange}
|
||||
selectedConversation={selectedConversation}
|
||||
setAssistantStreamingEnabled={handleChange(setUpdatedAssistantStreamingEnabled)}
|
||||
setConversationSettings={setConversationSettings}
|
||||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
defaultSelectedConversation={defaultSelectedConversation}
|
||||
/>
|
||||
)}
|
||||
{selectedSettingsTab === SYSTEM_PROMPTS_TAB && (
|
||||
<SystemPromptSettingsManagement
|
||||
connectors={connectors}
|
||||
conversationSettings={conversationSettings}
|
||||
conversationsSettingsBulkActions={conversationsSettingsBulkActions}
|
||||
defaultConnector={defaultConnector}
|
||||
handleSave={handleSave}
|
||||
onCancelClick={onCancelClick}
|
||||
onSelectedSystemPromptChange={onHandleSelectedSystemPromptChange}
|
||||
resetSettings={resetSettings}
|
||||
selectedSystemPrompt={selectedSystemPrompt}
|
||||
setConversationSettings={setConversationSettings}
|
||||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
setUpdatedSystemPromptSettings={setUpdatedSystemPromptSettings}
|
||||
systemPromptSettings={systemPrompts}
|
||||
promptsBulkActions={promptsBulkActions}
|
||||
setPromptsBulkActions={setPromptsBulkActions}
|
||||
/>
|
||||
)}
|
||||
{selectedSettingsTab === QUICK_PROMPTS_TAB && (
|
||||
<QuickPromptSettingsManagement
|
||||
handleSave={handleSave}
|
||||
onCancelClick={onCancelClick}
|
||||
onSelectedQuickPromptChange={onHandleSelectedQuickPromptChange}
|
||||
quickPromptSettings={quickPrompts}
|
||||
resetSettings={resetSettings}
|
||||
selectedQuickPrompt={selectedQuickPrompt}
|
||||
setUpdatedQuickPromptSettings={setUpdatedQuickPromptSettings}
|
||||
promptsBulkActions={promptsBulkActions}
|
||||
setPromptsBulkActions={setPromptsBulkActions}
|
||||
/>
|
||||
)}
|
||||
{selectedSettingsTab === ANONYMIZATION_TAB && (
|
||||
<AnonymizationSettingsManagement
|
||||
anonymizationFields={updatedAnonymizationData}
|
||||
anonymizationFieldsBulkActions={anonymizationFieldsBulkActions}
|
||||
defaultPageSize={5}
|
||||
setAnonymizationFieldsBulkActions={handleChange(setAnonymizationFieldsBulkActions)}
|
||||
setUpdatedAnonymizationData={handleChange(setUpdatedAnonymizationData)}
|
||||
/>
|
||||
)}
|
||||
{selectedSettingsTab === KNOWLEDGE_BASE_TAB && (
|
||||
<KnowledgeBaseSettings
|
||||
knowledgeBase={knowledgeBase}
|
||||
setUpdatedKnowledgeBaseSettings={handleChange(setUpdatedKnowledgeBaseSettings)}
|
||||
/>
|
||||
)}
|
||||
{selectedSettingsTab === QUICK_PROMPTS_TAB && <QuickPromptSettingsManagement />}
|
||||
{selectedSettingsTab === ANONYMIZATION_TAB && <AnonymizationSettingsManagement />}
|
||||
{selectedSettingsTab === KNOWLEDGE_BASE_TAB && <KnowledgeBaseSettingsManagement />}
|
||||
{selectedSettingsTab === EVALUATION_TAB && <EvaluationSettings />}
|
||||
</EuiPageTemplate.Section>
|
||||
{hasPendingChanges && (
|
||||
<EuiPageTemplate.BottomBar paddingSize="s" position="fixed" data-test-subj="bottom-bar">
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
color="text"
|
||||
iconType="cross"
|
||||
data-test-subj="cancel-button"
|
||||
onClick={onCancelClick}
|
||||
>
|
||||
{i18n.CANCEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
type="submit"
|
||||
data-test-subj="save-button"
|
||||
onClick={onSaveButtonClicked}
|
||||
iconType="check"
|
||||
fill
|
||||
>
|
||||
{i18n.SAVE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageTemplate.BottomBar>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
|
@ -348,12 +349,8 @@ export const EvaluationSettings: React.FC = React.memo(() => {
|
|||
`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size={'s'}>
|
||||
<h2>{i18n.SETTINGS_TITLE}</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size={'s'}>{i18n.SETTINGS_DESCRIPTION}</EuiText>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiText size={'m'}>{i18n.SETTINGS_DESCRIPTION}</EuiText>
|
||||
<EuiHorizontalRule margin={'s'} />
|
||||
{/* Run Details*/}
|
||||
<EuiAccordion
|
||||
|
@ -611,7 +608,7 @@ export const EvaluationSettings: React.FC = React.memo(() => {
|
|||
<EuiFlexItem>
|
||||
<EuiText color={'subdued'} size={'xs'}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Fun Facts: Watch the Kibana server logs for progress, and view results in {discover} / {apm} once complete. Will take (many) minutes depending on dataset, and closing this dialog will cancel the evaluation!"
|
||||
defaultMessage="Closing this dialog will cancel the evaluation. You can watch the Kibana server logs for progress, and view results in {discover} {apm}. Can take many minutes for large datasets."
|
||||
id="xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorFunFactText"
|
||||
values={{
|
||||
discover: (
|
||||
|
@ -630,7 +627,7 @@ export const EvaluationSettings: React.FC = React.memo(() => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
</EuiPanel>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ export const RUN_DETAILS_TITLE = i18n.translate(
|
|||
export const RUN_DETAILS_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.runDetailsDescription',
|
||||
{
|
||||
defaultMessage: 'Configure test run details like project, run name, dataset, and output index',
|
||||
defaultMessage: 'Configure test run details like project, run name, dataset, and output index.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -46,7 +46,7 @@ export const PREDICTION_DETAILS_DESCRIPTION = i18n.translate(
|
|||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.predictionDetailsDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Choose models (connectors) and corresponding agents the dataset should run against',
|
||||
'Choose models (connectors) and corresponding agents the dataset should run against.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -61,7 +61,7 @@ export const EVALUATION_DETAILS_DESCRIPTION = i18n.translate(
|
|||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluationDetailsDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Evaluate prediction results using a specific model (connector) and evaluation criterion',
|
||||
'Evaluate prediction results using a specific model (connector) and evaluation criterion.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -75,7 +75,7 @@ export const PROJECT_LABEL = i18n.translate(
|
|||
export const PROJECT_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.projectDescription',
|
||||
{
|
||||
defaultMessage: 'LangSmith project to write results to',
|
||||
defaultMessage: 'LangSmith project to write results to.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -96,7 +96,7 @@ export const RUN_NAME_LABEL = i18n.translate(
|
|||
export const RUN_NAME_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.runNameDescription',
|
||||
{
|
||||
defaultMessage: 'Name for this specific test run',
|
||||
defaultMessage: 'Name for this specific test run.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -117,7 +117,7 @@ export const CONNECTORS_LABEL = i18n.translate(
|
|||
export const CONNECTORS_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.connectorsDescription',
|
||||
{
|
||||
defaultMessage: 'Select whichever models you want to evaluate the dataset against',
|
||||
defaultMessage: 'Select models to evaluate the dataset against.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -131,7 +131,7 @@ export const AGENTS_LABEL = i18n.translate(
|
|||
export const AGENTS_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.agentsDescription',
|
||||
{
|
||||
defaultMessage: 'Select the agents (i.e. RAG algos) to evaluate the dataset against',
|
||||
defaultMessage: 'Select the agents (RAG algorithms) to evaluate the dataset against.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -145,7 +145,7 @@ export const EVALUATOR_MODEL_LABEL = i18n.translate(
|
|||
export const EVALUATOR_MODEL_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorModelDescription',
|
||||
{
|
||||
defaultMessage: 'Model to perform the final evaluation with',
|
||||
defaultMessage: 'Model that performs the final evaluation.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -160,7 +160,7 @@ export const EVALUATION_TYPE_DESCRIPTION = i18n.translate(
|
|||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluationTypeDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Type of evaluation to perform, e.g. "correctness" "esql-validator", or "custom" and provide your own evaluation prompt',
|
||||
'Type of evaluation to perform, e.g. "correctness" "esql-validator", or "custom".',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -175,7 +175,7 @@ export const EVALUATION_PROMPT_DESCRIPTION = i18n.translate(
|
|||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluationPromptDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Prompt template given `input`, `reference` and `prediction` template variables',
|
||||
'Prompt template given `input`, `reference` and `prediction` template variables.',
|
||||
}
|
||||
);
|
||||
export const EVALUATOR_OUTPUT_INDEX_LABEL = i18n.translate(
|
||||
|
@ -189,7 +189,7 @@ export const EVALUATOR_OUTPUT_INDEX_DESCRIPTION = i18n.translate(
|
|||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.evaluatorOutputIndexDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Index to write results to. Must be prefixed with ".kibana-elastic-ai-assistant-"',
|
||||
'Index to write results to. Must be prefixed with ".kibana-elastic-ai-assistant-".',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -211,7 +211,7 @@ export const APM_URL_DESCRIPTION = i18n.translate(
|
|||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.apmUrlDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'URL for the Kibana APM app. Used to link to APM traces for evaluation results. Defaults to "{defaultUrlPath}"',
|
||||
'URL for the Kibana APM app. Used to link to APM traces for evaluation results. Defaults to "{defaultUrlPath}".',
|
||||
values: {
|
||||
defaultUrlPath: '${basePath}/app/apm',
|
||||
},
|
||||
|
@ -228,7 +228,7 @@ export const LANGSMITH_PROJECT_LABEL = i18n.translate(
|
|||
export const LANGSMITH_PROJECT_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.langSmithProjectDescription',
|
||||
{
|
||||
defaultMessage: 'LangSmith Project to write traces to',
|
||||
defaultMessage: 'LangSmith Project to write traces to.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -264,7 +264,7 @@ export const LANGSMITH_DATASET_LABEL = i18n.translate(
|
|||
export const LANGSMITH_DATASET_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.langsmithDatasetDescription',
|
||||
{
|
||||
defaultMessage: 'Name of dataset hosted on LangSmith to evaluate',
|
||||
defaultMessage: 'Name of dataset hosted on LangSmith to evaluate.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -286,7 +286,7 @@ export const CUSTOM_DATASET_DESCRIPTION = i18n.translate(
|
|||
'xpack.elasticAssistant.assistant.settings.evaluationSettings.customDatasetDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Custom dataset to evaluate. Array of objects with "input" and "references" properties',
|
||||
'Custom dataset to evaluate. Array of objects with "input" and "references" properties.',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -17,7 +17,8 @@ const mockConversations = {
|
|||
[alertConvo.title]: alertConvo,
|
||||
[welcomeConvo.title]: welcomeConvo,
|
||||
};
|
||||
const conversationsLoaded = true;
|
||||
const conversationsLoaded = false;
|
||||
const promptsLoaded = false;
|
||||
|
||||
const mockHttp = {
|
||||
fetch: jest.fn(),
|
||||
|
@ -112,6 +113,7 @@ describe('useSettingsUpdater', () => {
|
|||
total: 10,
|
||||
},
|
||||
conversationsLoaded,
|
||||
promptsLoaded,
|
||||
anonymizationFields
|
||||
)
|
||||
);
|
||||
|
@ -168,6 +170,7 @@ describe('useSettingsUpdater', () => {
|
|||
total: 10,
|
||||
},
|
||||
conversationsLoaded,
|
||||
promptsLoaded,
|
||||
anonymizationFields
|
||||
)
|
||||
);
|
||||
|
@ -215,6 +218,7 @@ describe('useSettingsUpdater', () => {
|
|||
total: 10,
|
||||
},
|
||||
conversationsLoaded,
|
||||
promptsLoaded,
|
||||
anonymizationFields
|
||||
)
|
||||
);
|
||||
|
@ -242,6 +246,7 @@ describe('useSettingsUpdater', () => {
|
|||
total: 10,
|
||||
},
|
||||
conversationsLoaded,
|
||||
promptsLoaded,
|
||||
anonymizationFields
|
||||
)
|
||||
);
|
||||
|
@ -270,6 +275,7 @@ describe('useSettingsUpdater', () => {
|
|||
total: 10,
|
||||
},
|
||||
conversationsLoaded,
|
||||
promptsLoaded,
|
||||
anonymizationFields
|
||||
)
|
||||
);
|
||||
|
|
|
@ -24,6 +24,16 @@ import {
|
|||
import { bulkUpdateAnonymizationFields } from '../../api/anonymization_fields/bulk_update_anonymization_fields';
|
||||
import { bulkUpdatePrompts } from '../../api/prompts/bulk_update_prompts';
|
||||
|
||||
export const DEFAULT_ANONYMIZATION_FIELDS = {
|
||||
page: 0,
|
||||
perPage: 0,
|
||||
total: 0,
|
||||
data: [],
|
||||
};
|
||||
|
||||
export const DEFAULT_CONVERSATIONS: Record<string, Conversation> = {};
|
||||
|
||||
export const DEFAULT_PROMPTS: FindPromptsResponse = { page: 0, perPage: 0, total: 0, data: [] };
|
||||
interface UseSettingsUpdater {
|
||||
assistantStreamingEnabled: boolean;
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
|
@ -57,7 +67,8 @@ export const useSettingsUpdater = (
|
|||
conversations: Record<string, Conversation>,
|
||||
allPrompts: FindPromptsResponse,
|
||||
conversationsLoaded: boolean,
|
||||
anonymizationFields: FindAnonymizationFieldsResponse
|
||||
promptsLoaded: boolean,
|
||||
anonymizationFields: FindAnonymizationFieldsResponse = DEFAULT_ANONYMIZATION_FIELDS // Put default as a constant to avoid re-creating it on every render
|
||||
): UseSettingsUpdater => {
|
||||
// Initial state from assistant context
|
||||
const {
|
||||
|
@ -100,7 +111,6 @@ export const useSettingsUpdater = (
|
|||
// Knowledge Base
|
||||
const [updatedKnowledgeBaseSettings, setUpdatedKnowledgeBaseSettings] =
|
||||
useState<KnowledgeBaseConfig>(knowledgeBase);
|
||||
|
||||
/**
|
||||
* Reset all pending settings
|
||||
*/
|
||||
|
@ -115,6 +125,7 @@ export const useSettingsUpdater = (
|
|||
setUpdatedSystemPromptSettings(
|
||||
allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system)
|
||||
);
|
||||
setPromptsBulkActions({});
|
||||
setUpdatedAnonymizationData(anonymizationFields);
|
||||
}, [allPrompts, anonymizationFields, assistantStreamingEnabled, conversations, knowledgeBase]);
|
||||
|
||||
|
@ -188,6 +199,8 @@ export const useSettingsUpdater = (
|
|||
? await bulkUpdateAnonymizationFields(http, anonymizationFieldsBulkActions, toasts)
|
||||
: undefined;
|
||||
|
||||
setPromptsBulkActions({});
|
||||
setConversationsSettingsBulkActions({});
|
||||
return (
|
||||
(bulkResult?.success ?? true) &&
|
||||
(bulkAnonymizationFieldsResult?.success ?? true) &&
|
||||
|
@ -235,6 +248,18 @@ export const useSettingsUpdater = (
|
|||
}
|
||||
}, [conversations, conversationsLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
// Update quick prompts settings when prompts are loaded
|
||||
if (promptsLoaded) {
|
||||
setUpdatedQuickPromptSettings(
|
||||
allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.quick)
|
||||
);
|
||||
setUpdatedSystemPromptSettings(
|
||||
allPrompts.data.filter((p) => p.promptType === PromptTypeEnum.system)
|
||||
);
|
||||
}
|
||||
}, [allPrompts.data, promptsLoaded]);
|
||||
|
||||
return {
|
||||
conversationSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
|
|
|
@ -72,7 +72,7 @@ describe('useConversation helpers', () => {
|
|||
{
|
||||
id: '2',
|
||||
content: 'Prompt 2',
|
||||
name: 'Prompt 2',
|
||||
name: 'Default system prompt',
|
||||
promptType: 'quick',
|
||||
isNewConversationDefault: true,
|
||||
},
|
||||
|
@ -99,16 +99,31 @@ describe('useConversation helpers', () => {
|
|||
});
|
||||
|
||||
describe('getDefaultNewSystemPrompt', () => {
|
||||
const systemPrompts: PromptResponse[] = [
|
||||
{
|
||||
id: '1',
|
||||
content: 'Prompt 1',
|
||||
name: 'Default system prompt',
|
||||
promptType: 'system',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
content: 'Prompt 2',
|
||||
name: 'Prompt 2',
|
||||
promptType: 'system',
|
||||
isNewConversationDefault: true,
|
||||
},
|
||||
];
|
||||
test('should return the default (starred) isNewConversationDefault system prompt', () => {
|
||||
const result = getDefaultNewSystemPrompt(allSystemPrompts);
|
||||
const result = getDefaultNewSystemPrompt(systemPrompts);
|
||||
|
||||
expect(result).toEqual(allSystemPrompts[1]);
|
||||
expect(result).toEqual(systemPrompts[1]);
|
||||
});
|
||||
|
||||
test('should return the first prompt if default new system prompt do not exist', () => {
|
||||
const result = getDefaultNewSystemPrompt(allSystemPromptsNoDefault);
|
||||
test('should return the fallback prompt if default new system prompt do not exist', () => {
|
||||
const result = getDefaultNewSystemPrompt([systemPrompts[0]]);
|
||||
|
||||
expect(result).toEqual(allSystemPromptsNoDefault[0]);
|
||||
expect(result).toEqual(systemPrompts[0]);
|
||||
});
|
||||
|
||||
test('should return undefined if default (starred) isNewConversationDefault system prompt does not exist and there are no system prompts', () => {
|
||||
|
@ -131,53 +146,25 @@ describe('useConversation helpers', () => {
|
|||
replacements: {},
|
||||
title: '1',
|
||||
};
|
||||
test('should return the conversation system prompt if it exists', () => {
|
||||
test('should return the conversation system prompt', () => {
|
||||
const result = getDefaultSystemPrompt({ allSystemPrompts, conversation });
|
||||
|
||||
expect(result).toEqual(allSystemPrompts[2]);
|
||||
});
|
||||
|
||||
test('should return the default (starred) isNewConversationDefault system prompt if conversation system prompt does not exist', () => {
|
||||
const conversationWithoutSystemPrompt: Conversation = {
|
||||
...conversation,
|
||||
apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' },
|
||||
};
|
||||
test('should return undefined if the conversation system prompt does not exist', () => {
|
||||
const result = getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: conversationWithoutSystemPrompt,
|
||||
conversation: {
|
||||
...conversation,
|
||||
apiConfig: {
|
||||
...conversation.apiConfig,
|
||||
defaultSystemPromptId: undefined,
|
||||
},
|
||||
} as Conversation,
|
||||
});
|
||||
|
||||
expect(result).toEqual(allSystemPrompts[1]);
|
||||
});
|
||||
|
||||
test('should return the default (starred) isNewConversationDefault system prompt if conversation system prompt does not exist within all system prompts', () => {
|
||||
const conversationWithoutSystemPrompt: Conversation = {
|
||||
apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' },
|
||||
replacements: {},
|
||||
category: 'assistant',
|
||||
id: '4', // this id does not exist within allSystemPrompts
|
||||
messages: [],
|
||||
title: '4',
|
||||
};
|
||||
const result = getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: conversationWithoutSystemPrompt,
|
||||
});
|
||||
|
||||
expect(result).toEqual(allSystemPrompts[1]);
|
||||
});
|
||||
|
||||
test('should return the first prompt if both conversation system prompt and default new system prompt do not exist', () => {
|
||||
const conversationWithoutSystemPrompt: Conversation = {
|
||||
...conversation,
|
||||
apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' },
|
||||
};
|
||||
const result = getDefaultSystemPrompt({
|
||||
allSystemPrompts: allSystemPromptsNoDefault,
|
||||
conversation: conversationWithoutSystemPrompt,
|
||||
});
|
||||
|
||||
expect(result).toEqual(allSystemPromptsNoDefault[0]);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should return undefined if conversation system prompt does not exist and there are no system prompts', () => {
|
||||
|
@ -190,40 +177,21 @@ describe('useConversation helpers', () => {
|
|||
conversation: conversationWithoutSystemPrompt,
|
||||
});
|
||||
|
||||
expect(result).toEqual(undefined);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should return undefined if conversation system prompt does not exist within all system prompts', () => {
|
||||
const conversationWithoutSystemPrompt: Conversation = {
|
||||
...conversation,
|
||||
apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' },
|
||||
replacements: {},
|
||||
id: '4', // this id does not exist within allSystemPrompts
|
||||
apiConfig: { connectorId: '123', actionTypeId: '.gen-ai', defaultSystemPromptId: 'xxx' },
|
||||
id: '4',
|
||||
};
|
||||
const result = getDefaultSystemPrompt({
|
||||
allSystemPrompts: allSystemPromptsNoDefault,
|
||||
conversation: conversationWithoutSystemPrompt,
|
||||
});
|
||||
|
||||
expect(result).toEqual(allSystemPromptsNoDefault[0]);
|
||||
});
|
||||
|
||||
test('should return (starred) isNewConversationDefault system prompt if conversation is undefined', () => {
|
||||
const result = getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: undefined,
|
||||
});
|
||||
|
||||
expect(result).toEqual(allSystemPrompts[1]);
|
||||
});
|
||||
|
||||
test('should return the first system prompt if the conversation is undefined and isNewConversationDefault is not present in system prompts', () => {
|
||||
const result = getDefaultSystemPrompt({
|
||||
allSystemPrompts: allSystemPromptsNoDefault,
|
||||
conversation: undefined,
|
||||
});
|
||||
|
||||
expect(result).toEqual(allSystemPromptsNoDefault[0]);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should return undefined if conversation is undefined and no system prompts are provided', () => {
|
||||
|
@ -235,242 +203,142 @@ describe('useConversation helpers', () => {
|
|||
expect(result).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConversationApiConfig', () => {
|
||||
const allSystemPrompts: PromptResponse[] = [
|
||||
{
|
||||
describe('getConversationApiConfig', () => {
|
||||
const conversation: Conversation = {
|
||||
apiConfig: {
|
||||
connectorId: '123',
|
||||
actionTypeId: '.gen-ai',
|
||||
defaultSystemPromptId: '2',
|
||||
model: 'gpt-3',
|
||||
},
|
||||
category: 'assistant',
|
||||
id: '1',
|
||||
content: 'Prompt 1',
|
||||
name: 'Prompt 1',
|
||||
promptType: 'quick',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
content: 'Prompt 2',
|
||||
name: 'Prompt 2',
|
||||
promptType: 'quick',
|
||||
isNewConversationDefault: true,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
content: 'Prompt 3',
|
||||
name: 'Prompt 3',
|
||||
promptType: 'quick',
|
||||
},
|
||||
];
|
||||
messages: [],
|
||||
replacements: {},
|
||||
title: 'Test Conversation',
|
||||
};
|
||||
|
||||
const conversation: Conversation = {
|
||||
apiConfig: {
|
||||
connectorId: '123',
|
||||
actionTypeId: '.gen-ai',
|
||||
defaultSystemPromptId: '2',
|
||||
model: 'gpt-3',
|
||||
},
|
||||
category: 'assistant',
|
||||
id: '1',
|
||||
messages: [],
|
||||
replacements: {},
|
||||
title: 'Test Conversation',
|
||||
};
|
||||
const connectors: AIConnector[] = [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.gen-ai',
|
||||
apiProvider: OpenAiProviderType.OpenAi,
|
||||
config: {
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
actionTypeId: '.gen-ai',
|
||||
apiProvider: OpenAiProviderType.AzureAi,
|
||||
},
|
||||
] as AIConnector[];
|
||||
|
||||
const connectors: AIConnector[] = [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.gen-ai',
|
||||
apiProvider: OpenAiProviderType.OpenAi,
|
||||
},
|
||||
{
|
||||
const defaultConnector: AIConnector = {
|
||||
id: '456',
|
||||
actionTypeId: '.gen-ai',
|
||||
apiProvider: OpenAiProviderType.AzureAi,
|
||||
},
|
||||
] as AIConnector[];
|
||||
} 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,
|
||||
});
|
||||
|
||||
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',
|
||||
model: 'gpt-3',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
apiConfig: {
|
||||
connectorId: '123',
|
||||
actionTypeId: '.gen-ai',
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
defaultSystemPromptId: '2',
|
||||
model: 'gpt-3',
|
||||
},
|
||||
});
|
||||
});
|
||||
test('should return the default connector when specific connector is not found', () => {
|
||||
const conversationWithMissingConnector: Conversation = {
|
||||
...conversation,
|
||||
apiConfig: { ...conversation.apiConfig, connectorId: '999' } as Conversation['apiConfig'],
|
||||
};
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
const result = getConversationApiConfig({
|
||||
allSystemPrompts,
|
||||
conversation: conversationWithMissingConnector,
|
||||
connectors,
|
||||
defaultConnector,
|
||||
expect(result).toEqual({
|
||||
apiConfig: {
|
||||
connectorId: '456',
|
||||
actionTypeId: '.gen-ai',
|
||||
provider: OpenAiProviderType.AzureAi,
|
||||
defaultSystemPromptId: '2',
|
||||
model: 'gpt-3',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
apiConfig: {
|
||||
connectorId: '456',
|
||||
actionTypeId: '.gen-ai',
|
||||
provider: OpenAiProviderType.AzureAi,
|
||||
defaultSystemPromptId: '2',
|
||||
model: 'gpt-3',
|
||||
},
|
||||
});
|
||||
});
|
||||
test('should return an empty object when no connectors are provided and default connector is missing', () => {
|
||||
const result = getConversationApiConfig({
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
});
|
||||
|
||||
test('should return an empty object when no connectors are provided and default connector is missing', () => {
|
||||
const result = getConversationApiConfig({
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
expect(result).toEqual({
|
||||
apiConfig: {
|
||||
defaultSystemPromptId: '2',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
test('should set default system prompt as undefined if conversation system prompt is not found', () => {
|
||||
const conversationWithMissingSystemPrompt: Conversation = {
|
||||
...conversation,
|
||||
apiConfig: {
|
||||
...conversation.apiConfig,
|
||||
defaultSystemPromptId: '999',
|
||||
} as Conversation['apiConfig'],
|
||||
};
|
||||
|
||||
test('should return the 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,
|
||||
});
|
||||
|
||||
const result = getConversationApiConfig({
|
||||
allSystemPrompts,
|
||||
conversation: conversationWithMissingSystemPrompt,
|
||||
connectors,
|
||||
defaultConnector,
|
||||
expect(result).toEqual({
|
||||
apiConfig: {
|
||||
connectorId: '123',
|
||||
actionTypeId: '.gen-ai',
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
model: 'gpt-3',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
apiConfig: {
|
||||
connectorId: '123',
|
||||
actionTypeId: '.gen-ai',
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
defaultSystemPromptId: '2', // Returns the default system prompt for new conversations
|
||||
model: 'gpt-3',
|
||||
},
|
||||
});
|
||||
});
|
||||
test('should return the correct config when connectors are not provided', () => {
|
||||
const result = getConversationApiConfig({
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
defaultConnector,
|
||||
});
|
||||
|
||||
test('should return the correct config when connectors are not provided', () => {
|
||||
const result = getConversationApiConfig({
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
defaultConnector,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
apiConfig: {
|
||||
connectorId: '456',
|
||||
actionTypeId: '.gen-ai',
|
||||
provider: OpenAiProviderType.AzureAi,
|
||||
defaultSystemPromptId: '2',
|
||||
model: 'gpt-3',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should return the first system prompt if both conversation system prompt and default new system prompt do not exist', () => {
|
||||
const allSystemPromptsNoDefault: PromptResponse[] = allSystemPrompts.filter(
|
||||
({ isNewConversationDefault }) => isNewConversationDefault !== true
|
||||
);
|
||||
|
||||
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', // Uses the first prompt in the list
|
||||
model: undefined, // default connector's model
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should return the first system prompt if conversation system prompt does not exist within all system prompts', () => {
|
||||
const allSystemPromptsNoDefault: PromptResponse[] = allSystemPrompts.filter(
|
||||
({ isNewConversationDefault }) => isNewConversationDefault !== true
|
||||
);
|
||||
|
||||
const conversationWithoutSystemPrompt: Conversation = {
|
||||
...conversation,
|
||||
apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' },
|
||||
id: '4', // this id does not exist within allSystemPrompts
|
||||
};
|
||||
|
||||
const result = getConversationApiConfig({
|
||||
allSystemPrompts: allSystemPromptsNoDefault,
|
||||
conversation: conversationWithoutSystemPrompt,
|
||||
connectors,
|
||||
defaultConnector,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
apiConfig: {
|
||||
connectorId: '123',
|
||||
actionTypeId: '.gen-ai',
|
||||
provider: OpenAiProviderType.OpenAi,
|
||||
defaultSystemPromptId: '1', // Uses the first prompt in the list
|
||||
model: undefined, // default connector's model
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
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',
|
||||
},
|
||||
expect(result).toEqual({
|
||||
apiConfig: {
|
||||
connectorId: '456',
|
||||
actionTypeId: '.gen-ai',
|
||||
provider: OpenAiProviderType.AzureAi,
|
||||
defaultSystemPromptId: '2',
|
||||
model: 'gpt-3',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { ApiConfig, PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../assistant_context/types';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
import { getGenAiConfig } from '../../connectorland/helpers';
|
||||
import { DEFAULT_SYSTEM_PROMPT_NAME } from '../../content/prompts/system/translations';
|
||||
|
||||
export interface CodeBlockDetails {
|
||||
type: QueryType;
|
||||
|
@ -71,15 +72,19 @@ export const analyzeMarkdown = (markdown: string): CodeBlockDetails[] => {
|
|||
};
|
||||
|
||||
/**
|
||||
* Returns the default system prompt
|
||||
* Returns the new default system prompt, fallback to the default system prompt if not found
|
||||
*
|
||||
* @param allSystemPrompts All available System Prompts
|
||||
*/
|
||||
export const getDefaultNewSystemPrompt = (allSystemPrompts: PromptResponse[]) =>
|
||||
allSystemPrompts.find((prompt) => prompt.isNewConversationDefault) ?? allSystemPrompts?.[0];
|
||||
export const getDefaultNewSystemPrompt = (allSystemPrompts: PromptResponse[]) => {
|
||||
const fallbackSystemPrompt = allSystemPrompts.find(
|
||||
(prompt) => prompt.name === DEFAULT_SYSTEM_PROMPT_NAME
|
||||
);
|
||||
return allSystemPrompts.find((prompt) => prompt.isNewConversationDefault) ?? fallbackSystemPrompt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the default system prompt for a given (New Custom) conversation
|
||||
* Returns the default system prompt for a given conversation
|
||||
*
|
||||
* @param allSystemPrompts All available System Prompts
|
||||
* @param conversation Conversation to get the default system prompt for
|
||||
|
@ -94,29 +99,26 @@ export const getDefaultSystemPrompt = ({
|
|||
const conversationSystemPrompt = allSystemPrompts.find(
|
||||
(prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId
|
||||
);
|
||||
const defaultNewSystemPrompt = getDefaultNewSystemPrompt(allSystemPrompts);
|
||||
|
||||
return conversationSystemPrompt?.id ? conversationSystemPrompt : defaultNewSystemPrompt;
|
||||
return conversationSystemPrompt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the default system prompt for an existing conversation that has never been given a system prompt
|
||||
* Returns the default system prompt
|
||||
*
|
||||
* @param allSystemPrompts All available System Prompts
|
||||
* @param conversation Conversation to get the default system prompt for
|
||||
*/
|
||||
export const getInitialDefaultSystemPrompt = ({
|
||||
export const getFallbackDefaultSystemPrompt = ({
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
}: {
|
||||
allSystemPrompts: PromptResponse[];
|
||||
conversation: Conversation | undefined;
|
||||
}): PromptResponse | undefined => {
|
||||
const conversationSystemPrompt = allSystemPrompts.find(
|
||||
(prompt) => prompt.id === conversation?.apiConfig?.defaultSystemPromptId
|
||||
const fallbackSystemPrompt = allSystemPrompts.find(
|
||||
(prompt) => prompt.name === DEFAULT_SYSTEM_PROMPT_NAME
|
||||
);
|
||||
|
||||
return conversationSystemPrompt ?? allSystemPrompts?.[0];
|
||||
return fallbackSystemPrompt;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -140,27 +142,29 @@ export const getConversationApiConfig = ({
|
|||
}) => {
|
||||
const connector: AIConnector | undefined =
|
||||
connectors?.find((c) => c.id === conversation.apiConfig?.connectorId) ?? defaultConnector;
|
||||
const connectorModel = getGenAiConfig(connector)?.defaultModel;
|
||||
const defaultSystemPrompt =
|
||||
conversation.apiConfig?.defaultSystemPromptId == null
|
||||
? getInitialDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
})
|
||||
: getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
});
|
||||
|
||||
const { apiProvider: connectorApiProvider, defaultModel: connectorModel } =
|
||||
getGenAiConfig(connector) ?? {};
|
||||
|
||||
const defaultSystemPrompt = getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
});
|
||||
|
||||
return connector
|
||||
? {
|
||||
apiConfig: {
|
||||
connectorId: connector.id,
|
||||
actionTypeId: connector.actionTypeId,
|
||||
provider: connector.apiProvider,
|
||||
provider: connector.apiProvider ?? connectorApiProvider,
|
||||
defaultSystemPromptId: defaultSystemPrompt?.id,
|
||||
model: conversation?.apiConfig?.model ?? connectorModel,
|
||||
},
|
||||
}
|
||||
: {};
|
||||
: ({
|
||||
// Scenario when no connectors is configured
|
||||
apiConfig: {
|
||||
defaultSystemPromptId: defaultSystemPrompt?.id,
|
||||
},
|
||||
} as unknown as { apiConfig: ApiConfig });
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
|
||||
|
@ -32,13 +33,17 @@ const ConnectorsSettingsManagementComponent: React.FC = () => {
|
|||
|
||||
return (
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiTitle size={'s'}>
|
||||
<EuiTitle size="xs">
|
||||
<h2>{i18n.CONNECTOR_SETTINGS_MANAGEMENT_TITLE}</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiText>{i18n.CONNECTOR_SETTINGS_MANAGEMENT_DESCRIPTION}</EuiText>
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
align-self: center;
|
||||
`}
|
||||
>
|
||||
<EuiText size="m">{i18n.CONNECTOR_SETTINGS_MANAGEMENT_DESCRIPTION}</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
|
|
|
@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
|
|||
export const CONNECTOR_SETTINGS_MANAGEMENT_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.connectors.connectorSettingsManagement.title',
|
||||
{
|
||||
defaultMessage: 'Connector Settings',
|
||||
defaultMessage: 'Settings',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -18,7 +18,7 @@ export const CONNECTOR_SETTINGS_MANAGEMENT_DESCRIPTION = i18n.translate(
|
|||
'xpack.elasticAssistant.connectors.connectorSettingsManagement.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Using the Elastic AI Assistant requires setting up a connector with API access to OpenAI or Bedrock large language models. ',
|
||||
'To use Elastic AI Assistant, you must set up a connector to an external large language model.',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -38,6 +38,6 @@ export const SETTINGS_DESCRIPTION = i18n.translate(
|
|||
'xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.settingsDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
"When adding Prompt Context throughout the Security App that may contain sensitive information, you can choose which fields are sent, and whether to enable anonymization for these fields. This will replace the field's value with a random string before sending the conversation. Helpful defaults are provided below.",
|
||||
'Define privacy settings for event data sent to third-party LLM providers. You can choose which fields to include, and which to anonymize by replacing their values with random strings. Helpful defaults are provided below.',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -31,7 +31,6 @@ export const useAnonymizationListUpdate = ({
|
|||
const onListUpdated = useCallback(
|
||||
async (updates: BatchUpdateListItem[]) => {
|
||||
const updatedFieldsKeys = updates.map((u) => u.field);
|
||||
|
||||
const updatedFields = updates.map((u) => ({
|
||||
...(anonymizationFields.data.find((f) => f.field === u.field) ?? { id: '', field: '' }),
|
||||
...(u.update === 'allow' || u.update === 'defaultAllow'
|
||||
|
|
|
@ -5,71 +5,125 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { FindAnonymizationFieldsResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.gen';
|
||||
import { PerformBulkActionRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { Stats } from '../../../data_anonymization_editor/stats';
|
||||
import { ContextEditor } from '../../../data_anonymization_editor/context_editor';
|
||||
import * as i18n from '../anonymization_settings/translations';
|
||||
import { useAnonymizationListUpdate } from '../anonymization_settings/use_anonymization_list_update';
|
||||
import {
|
||||
DEFAULT_ANONYMIZATION_FIELDS,
|
||||
DEFAULT_CONVERSATIONS,
|
||||
DEFAULT_PROMPTS,
|
||||
useSettingsUpdater,
|
||||
} from '../../../assistant/settings/use_settings_updater/use_settings_updater';
|
||||
import { useFetchAnonymizationFields } from '../../../assistant/api/anonymization_fields/use_fetch_anonymization_fields';
|
||||
import { AssistantSettingsBottomBar } from '../../../assistant/settings/assistant_settings_bottom_bar';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
import { SETTINGS_UPDATED_TOAST_TITLE } from '../../../assistant/settings/translations';
|
||||
|
||||
export interface Props {
|
||||
defaultPageSize?: number;
|
||||
anonymizationFields: FindAnonymizationFieldsResponse;
|
||||
anonymizationFieldsBulkActions: PerformBulkActionRequestBody;
|
||||
setAnonymizationFieldsBulkActions: React.Dispatch<
|
||||
React.SetStateAction<PerformBulkActionRequestBody>
|
||||
>;
|
||||
setUpdatedAnonymizationData: React.Dispatch<
|
||||
React.SetStateAction<FindAnonymizationFieldsResponse>
|
||||
>;
|
||||
}
|
||||
|
||||
const AnonymizationSettingsManagementComponent: React.FC<Props> = ({
|
||||
defaultPageSize,
|
||||
anonymizationFields,
|
||||
anonymizationFieldsBulkActions,
|
||||
setAnonymizationFieldsBulkActions,
|
||||
setUpdatedAnonymizationData,
|
||||
}) => {
|
||||
const onListUpdated = useAnonymizationListUpdate({
|
||||
anonymizationFields,
|
||||
const AnonymizationSettingsManagementComponent: React.FC<Props> = ({ defaultPageSize = 5 }) => {
|
||||
const { toasts } = useAssistantContext();
|
||||
const { data: anonymizationFields } = useFetchAnonymizationFields();
|
||||
const [hasPendingChanges, setHasPendingChanges] = useState(false);
|
||||
|
||||
const {
|
||||
anonymizationFieldsBulkActions,
|
||||
setAnonymizationFieldsBulkActions,
|
||||
setUpdatedAnonymizationData,
|
||||
resetSettings,
|
||||
saveSettings,
|
||||
updatedAnonymizationData,
|
||||
} = useSettingsUpdater(
|
||||
DEFAULT_CONVERSATIONS, // Anonymization settings do not require conversations
|
||||
DEFAULT_PROMPTS, // Anonymization settings do not require prompts
|
||||
false, // Anonymization settings do not require conversations
|
||||
false, // Anonymization settings do not require prompts
|
||||
anonymizationFields ?? DEFAULT_ANONYMIZATION_FIELDS
|
||||
);
|
||||
|
||||
const onCancelClick = useCallback(() => {
|
||||
resetSettings();
|
||||
setHasPendingChanges(false);
|
||||
}, [resetSettings]);
|
||||
|
||||
const handleSave = useCallback(
|
||||
async (param?: { callback?: () => void }) => {
|
||||
await saveSettings();
|
||||
toasts?.addSuccess({
|
||||
iconType: 'check',
|
||||
title: SETTINGS_UPDATED_TOAST_TITLE,
|
||||
});
|
||||
setHasPendingChanges(false);
|
||||
param?.callback?.();
|
||||
},
|
||||
[saveSettings, toasts]
|
||||
);
|
||||
|
||||
const onSaveButtonClicked = useCallback(() => {
|
||||
handleSave();
|
||||
}, [handleSave]);
|
||||
|
||||
const handleAnonymizationFieldsBulkActions = useCallback(
|
||||
(value) => {
|
||||
setHasPendingChanges(true);
|
||||
setAnonymizationFieldsBulkActions(value);
|
||||
},
|
||||
[setAnonymizationFieldsBulkActions]
|
||||
);
|
||||
|
||||
const handleUpdatedAnonymizationData = useCallback(
|
||||
(value) => {
|
||||
setHasPendingChanges(true);
|
||||
setUpdatedAnonymizationData(value);
|
||||
},
|
||||
[setUpdatedAnonymizationData]
|
||||
);
|
||||
|
||||
const onListUpdated = useAnonymizationListUpdate({
|
||||
anonymizationFields: updatedAnonymizationData,
|
||||
anonymizationFieldsBulkActions,
|
||||
setAnonymizationFieldsBulkActions: handleAnonymizationFieldsBulkActions,
|
||||
setUpdatedAnonymizationData: handleUpdatedAnonymizationData,
|
||||
});
|
||||
return (
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiTitle size={'xs'}>
|
||||
<h2>{i18n.SETTINGS_TITLE}</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size={'xs'}>{i18n.SETTINGS_DESCRIPTION}</EuiText>
|
||||
<>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiText size="m">{i18n.SETTINGS_DESCRIPTION}</EuiText>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup alignItems="center" data-test-subj="summary" gutterSize="none">
|
||||
<Stats
|
||||
isDataAnonymizable={true}
|
||||
anonymizationFields={anonymizationFields.data}
|
||||
titleSize="m"
|
||||
gap={euiThemeVars.euiSizeS}
|
||||
<EuiFlexGroup alignItems="center" data-test-subj="summary" gutterSize="none">
|
||||
<Stats
|
||||
isDataAnonymizable={true}
|
||||
anonymizationFields={updatedAnonymizationData.data}
|
||||
titleSize="m"
|
||||
gap={euiThemeVars.euiSizeS}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<ContextEditor
|
||||
anonymizationFields={updatedAnonymizationData}
|
||||
compressed={false}
|
||||
onListUpdated={onListUpdated}
|
||||
rawData={null}
|
||||
pageSize={defaultPageSize}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<ContextEditor
|
||||
anonymizationFields={anonymizationFields}
|
||||
compressed={false}
|
||||
onListUpdated={onListUpdated}
|
||||
rawData={null}
|
||||
pageSize={defaultPageSize}
|
||||
</EuiPanel>
|
||||
<AssistantSettingsBottomBar
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
onCancelClick={onCancelClick}
|
||||
onSaveButtonClicked={onSaveButtonClicked}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -19,13 +19,15 @@ const AnonymizedButton = styled(EuiButtonEmpty)`
|
|||
`;
|
||||
|
||||
export const getColumns = ({
|
||||
compressed = true,
|
||||
hasUpdateAIAssistantAnonymization,
|
||||
onListUpdated,
|
||||
rawData,
|
||||
hasUpdateAIAssistantAnonymization,
|
||||
}: {
|
||||
compressed?: boolean;
|
||||
hasUpdateAIAssistantAnonymization: boolean;
|
||||
onListUpdated: (updates: BatchUpdateListItem[]) => void;
|
||||
rawData: Record<string, string[]> | null;
|
||||
hasUpdateAIAssistantAnonymization: boolean;
|
||||
}): Array<EuiBasicTableColumn<ContextEditorRow>> => {
|
||||
const actionsColumn: EuiBasicTableColumn<ContextEditorRow> = {
|
||||
field: FIELDS.ACTIONS,
|
||||
|
@ -68,6 +70,7 @@ export const getColumns = ({
|
|||
disabled={!hasUpdateAIAssistantAnonymization}
|
||||
label=""
|
||||
showLabel={false}
|
||||
compressed={compressed}
|
||||
onChange={() => {
|
||||
onListUpdated([
|
||||
{
|
||||
|
|
|
@ -98,8 +98,8 @@ const ContextEditorComponent: React.FC<Props> = ({
|
|||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => getColumns({ onListUpdated, rawData, hasUpdateAIAssistantAnonymization }),
|
||||
[hasUpdateAIAssistantAnonymization, onListUpdated, rawData]
|
||||
() => getColumns({ onListUpdated, rawData, hasUpdateAIAssistantAnonymization, compressed }),
|
||||
[hasUpdateAIAssistantAnonymization, onListUpdated, rawData, compressed]
|
||||
);
|
||||
|
||||
const rows = useMemo(
|
||||
|
|
|
@ -227,7 +227,23 @@ export const KnowledgeBaseSettings: React.FC<Props> = React.memo(
|
|||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size={'s'}>{i18n.SETTINGS_DESCRIPTION}</EuiText>
|
||||
<EuiText size={'s'}>
|
||||
<FormattedMessage
|
||||
id="xpack.elasticAssistant.assistant.settings.knowledgeBasedSetting.knowledgeBaseDescription"
|
||||
defaultMessage="Powered by ELSER, the knowledge base enables the AI Assistant to recall documents and other relevant context within your conversation. For more information about user access refer to our {documentation}."
|
||||
values={{
|
||||
documentation: (
|
||||
<EuiLink
|
||||
external
|
||||
href="https://www.elastic.co/guide/en/security/current/security-assistant.html"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.KNOWLEDGE_BASE_DOCUMENTATION}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiHorizontalRule margin={'s'} />
|
||||
|
||||
<EuiFormRow
|
||||
|
|
|
@ -0,0 +1,387 @@
|
|||
/*
|
||||
* 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, useState } from 'react';
|
||||
import {
|
||||
EuiFormRow,
|
||||
EuiText,
|
||||
EuiHorizontalRule,
|
||||
EuiLoadingSpinner,
|
||||
EuiSpacer,
|
||||
EuiSwitchEvent,
|
||||
EuiLink,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHealth,
|
||||
EuiButtonEmpty,
|
||||
EuiToolTip,
|
||||
EuiSwitch,
|
||||
EuiPanel,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import { AlertsSettings } from '../alerts/settings/alerts_settings';
|
||||
import { useAssistantContext } from '../assistant_context';
|
||||
import * as i18n from './translations';
|
||||
import { useDeleteKnowledgeBase } from '../assistant/api/knowledge_base/use_delete_knowledge_base';
|
||||
import { useKnowledgeBaseStatus } from '../assistant/api/knowledge_base/use_knowledge_base_status';
|
||||
import { useSetupKnowledgeBase } from '../assistant/api/knowledge_base/use_setup_knowledge_base';
|
||||
import {
|
||||
useSettingsUpdater,
|
||||
DEFAULT_CONVERSATIONS,
|
||||
DEFAULT_PROMPTS,
|
||||
} from '../assistant/settings/use_settings_updater/use_settings_updater';
|
||||
import { AssistantSettingsBottomBar } from '../assistant/settings/assistant_settings_bottom_bar';
|
||||
import { SETTINGS_UPDATED_TOAST_TITLE } from '../assistant/settings/translations';
|
||||
|
||||
const ESQL_RESOURCE = 'esql';
|
||||
const KNOWLEDGE_BASE_INDEX_PATTERN_OLD = '.kibana-elastic-ai-assistant-kb';
|
||||
const KNOWLEDGE_BASE_INDEX_PATTERN = '.kibana-elastic-ai-assistant-knowledge-base-(SPACE)';
|
||||
|
||||
/**
|
||||
* Knowledge Base Settings -- enable and disable LangChain integration, Knowledge Base, and ESQL KB Documents
|
||||
*/
|
||||
export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
|
||||
const {
|
||||
assistantFeatures: { assistantKnowledgeBaseByDefault: enableKnowledgeBaseByDefault },
|
||||
http,
|
||||
toasts,
|
||||
} = useAssistantContext();
|
||||
const [hasPendingChanges, setHasPendingChanges] = useState(false);
|
||||
|
||||
const { knowledgeBase, setUpdatedKnowledgeBaseSettings, resetSettings, saveSettings } =
|
||||
useSettingsUpdater(
|
||||
DEFAULT_CONVERSATIONS, // Knowledge Base settings do not require conversations
|
||||
DEFAULT_PROMPTS, // Knowledge Base settings do not require prompts
|
||||
false, // Knowledge Base settings do not require prompts
|
||||
false // Knowledge Base settings do not require conversations
|
||||
);
|
||||
|
||||
const handleSave = useCallback(
|
||||
async (param?: { callback?: () => void }) => {
|
||||
await saveSettings();
|
||||
toasts?.addSuccess({
|
||||
iconType: 'check',
|
||||
title: SETTINGS_UPDATED_TOAST_TITLE,
|
||||
});
|
||||
setHasPendingChanges(false);
|
||||
param?.callback?.();
|
||||
},
|
||||
[saveSettings, toasts]
|
||||
);
|
||||
|
||||
const handleUpdateKnowledgeBaseSettings = useCallback(
|
||||
(updatedKnowledgebase) => {
|
||||
setHasPendingChanges(true);
|
||||
setUpdatedKnowledgeBaseSettings(updatedKnowledgebase);
|
||||
},
|
||||
[setUpdatedKnowledgeBaseSettings]
|
||||
);
|
||||
|
||||
const onCancelClick = useCallback(() => {
|
||||
resetSettings();
|
||||
setHasPendingChanges(false);
|
||||
}, [resetSettings]);
|
||||
|
||||
const onSaveButtonClicked = useCallback(() => {
|
||||
handleSave();
|
||||
}, [handleSave]);
|
||||
|
||||
const {
|
||||
data: kbStatus,
|
||||
isLoading,
|
||||
isFetching,
|
||||
} = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE });
|
||||
const { mutate: setupKB, isLoading: isSettingUpKB } = useSetupKnowledgeBase({ http });
|
||||
const { mutate: deleteKB, isLoading: isDeletingUpKB } = useDeleteKnowledgeBase({ http });
|
||||
|
||||
// Resource enabled state
|
||||
const isElserEnabled = kbStatus?.elser_exists ?? false;
|
||||
const isKnowledgeBaseEnabled = (kbStatus?.index_exists && kbStatus?.pipeline_exists) ?? false;
|
||||
const isESQLEnabled = kbStatus?.esql_exists ?? false;
|
||||
const isSetupInProgress = kbStatus?.is_setup_in_progress ?? false;
|
||||
|
||||
// Resource availability state
|
||||
const isLoadingKb =
|
||||
isLoading || isFetching || isSettingUpKB || isDeletingUpKB || isSetupInProgress;
|
||||
const isKnowledgeBaseAvailable = knowledgeBase.isEnabledKnowledgeBase && kbStatus?.elser_exists;
|
||||
const isESQLAvailable =
|
||||
knowledgeBase.isEnabledKnowledgeBase && isKnowledgeBaseAvailable && isKnowledgeBaseEnabled;
|
||||
// Prevent enabling if elser doesn't exist, but always allow to disable
|
||||
const isSwitchDisabled = enableKnowledgeBaseByDefault
|
||||
? false
|
||||
: !kbStatus?.elser_exists && !knowledgeBase.isEnabledKnowledgeBase;
|
||||
|
||||
// Calculated health state for EuiHealth component
|
||||
const elserHealth = isElserEnabled ? 'success' : 'subdued';
|
||||
const knowledgeBaseHealth = isKnowledgeBaseEnabled ? 'success' : 'subdued';
|
||||
const esqlHealth = isESQLEnabled ? 'success' : 'subdued';
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Main `Knowledge Base` switch, which toggles the `isEnabledKnowledgeBase` UI feature toggle
|
||||
// setting that is saved to localstorage
|
||||
const onEnableAssistantLangChainChange = useCallback(
|
||||
(event: EuiSwitchEvent) => {
|
||||
handleUpdateKnowledgeBaseSettings({
|
||||
...knowledgeBase,
|
||||
isEnabledKnowledgeBase: event.target.checked,
|
||||
});
|
||||
|
||||
// If enabling and ELSER exists or automatic KB setup FF is enabled, try to set up automatically
|
||||
if (event.target.checked && (enableKnowledgeBaseByDefault || kbStatus?.elser_exists)) {
|
||||
setupKB(ESQL_RESOURCE);
|
||||
}
|
||||
},
|
||||
[
|
||||
enableKnowledgeBaseByDefault,
|
||||
handleUpdateKnowledgeBaseSettings,
|
||||
kbStatus?.elser_exists,
|
||||
knowledgeBase,
|
||||
setupKB,
|
||||
]
|
||||
);
|
||||
|
||||
const isEnabledKnowledgeBaseSwitch = useMemo(() => {
|
||||
return isLoadingKb ? (
|
||||
<EuiLoadingSpinner size="s" />
|
||||
) : (
|
||||
<EuiToolTip content={isSwitchDisabled && i18n.KNOWLEDGE_BASE_TOOLTIP} position={'right'}>
|
||||
<EuiSwitch
|
||||
showLabel={false}
|
||||
data-test-subj="isEnabledKnowledgeBaseSwitch"
|
||||
disabled={isSwitchDisabled}
|
||||
checked={knowledgeBase.isEnabledKnowledgeBase}
|
||||
onChange={onEnableAssistantLangChainChange}
|
||||
label={i18n.KNOWLEDGE_BASE_LABEL}
|
||||
compressed
|
||||
/>
|
||||
</EuiToolTip>
|
||||
);
|
||||
}, [
|
||||
isLoadingKb,
|
||||
isSwitchDisabled,
|
||||
knowledgeBase.isEnabledKnowledgeBase,
|
||||
onEnableAssistantLangChainChange,
|
||||
]);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Knowledge Base Resource
|
||||
const onEnableKB = useCallback(
|
||||
(enabled: boolean) => {
|
||||
if (enabled) {
|
||||
setupKB();
|
||||
} else {
|
||||
deleteKB();
|
||||
}
|
||||
},
|
||||
[deleteKB, setupKB]
|
||||
);
|
||||
|
||||
const knowledgeBaseActionButton = useMemo(() => {
|
||||
return isLoadingKb || !isKnowledgeBaseAvailable ? (
|
||||
<></>
|
||||
) : (
|
||||
<EuiButtonEmpty
|
||||
color={isKnowledgeBaseEnabled ? 'danger' : 'primary'}
|
||||
flush="left"
|
||||
data-test-subj={'knowledgeBaseActionButton'}
|
||||
onClick={() => onEnableKB(!isKnowledgeBaseEnabled)}
|
||||
size="xs"
|
||||
>
|
||||
{isKnowledgeBaseEnabled
|
||||
? i18n.KNOWLEDGE_BASE_DELETE_BUTTON
|
||||
: i18n.KNOWLEDGE_BASE_INIT_BUTTON}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}, [isKnowledgeBaseAvailable, isKnowledgeBaseEnabled, isLoadingKb, onEnableKB]);
|
||||
|
||||
const knowledgeBaseDescription = useMemo(() => {
|
||||
return isKnowledgeBaseEnabled ? (
|
||||
<span data-test-subj="kb-installed">
|
||||
{i18n.KNOWLEDGE_BASE_DESCRIPTION_INSTALLED(
|
||||
enableKnowledgeBaseByDefault
|
||||
? KNOWLEDGE_BASE_INDEX_PATTERN
|
||||
: KNOWLEDGE_BASE_INDEX_PATTERN_OLD
|
||||
)}{' '}
|
||||
{knowledgeBaseActionButton}
|
||||
</span>
|
||||
) : (
|
||||
<span data-test-subj="install-kb">
|
||||
{i18n.KNOWLEDGE_BASE_DESCRIPTION} {knowledgeBaseActionButton}
|
||||
</span>
|
||||
);
|
||||
}, [enableKnowledgeBaseByDefault, isKnowledgeBaseEnabled, knowledgeBaseActionButton]);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ESQL Resource
|
||||
const onEnableESQL = useCallback(
|
||||
(enabled: boolean) => {
|
||||
if (enabled) {
|
||||
setupKB(ESQL_RESOURCE);
|
||||
} else {
|
||||
deleteKB(ESQL_RESOURCE);
|
||||
}
|
||||
},
|
||||
[deleteKB, setupKB]
|
||||
);
|
||||
|
||||
const esqlActionButton = useMemo(() => {
|
||||
return isLoadingKb || !isESQLAvailable ? (
|
||||
<></>
|
||||
) : (
|
||||
<EuiButtonEmpty
|
||||
color={isESQLEnabled ? 'danger' : 'primary'}
|
||||
flush="left"
|
||||
data-test-subj="esqlEnableButton"
|
||||
onClick={() => onEnableESQL(!isESQLEnabled)}
|
||||
size="xs"
|
||||
>
|
||||
{isESQLEnabled ? i18n.KNOWLEDGE_BASE_DELETE_BUTTON : i18n.KNOWLEDGE_BASE_INIT_BUTTON}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}, [isLoadingKb, isESQLAvailable, isESQLEnabled, onEnableESQL]);
|
||||
|
||||
const esqlDescription = useMemo(() => {
|
||||
return isESQLEnabled ? (
|
||||
<span data-test-subj="esql-installed">
|
||||
{i18n.ESQL_DESCRIPTION_INSTALLED} {esqlActionButton}
|
||||
</span>
|
||||
) : (
|
||||
<span data-test-subj="install-esql">
|
||||
{i18n.ESQL_DESCRIPTION} {esqlActionButton}
|
||||
</span>
|
||||
);
|
||||
}, [esqlActionButton, isESQLEnabled]);
|
||||
|
||||
return (
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiText size="m">
|
||||
<FormattedMessage
|
||||
id="xpack.elasticAssistant.assistant.settings.knowledgeBasedSettingManagements.knowledgeBaseDescription"
|
||||
defaultMessage="Powered by ELSER, the knowledge base enables the AI Assistant to recall documents and other relevant context within your conversation. For more information about user access refer to our {documentation}."
|
||||
values={{
|
||||
documentation: (
|
||||
<EuiLink
|
||||
external
|
||||
href="https://www.elastic.co/guide/en/security/current/security-assistant.html"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.KNOWLEDGE_BASE_DOCUMENTATION}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiHorizontalRule margin={'s'} />
|
||||
|
||||
<EuiFormRow
|
||||
display="columnCompressedSwitch"
|
||||
label={i18n.KNOWLEDGE_BASE_LABEL}
|
||||
css={css`
|
||||
.euiFormRow__labelWrapper {
|
||||
min-width: 95px !important;
|
||||
}
|
||||
`}
|
||||
>
|
||||
{isEnabledKnowledgeBaseSwitch}
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiFlexGroup
|
||||
direction={'column'}
|
||||
gutterSize={'s'}
|
||||
css={css`
|
||||
padding-left: 5px;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div>
|
||||
<EuiHealth color={elserHealth}>{i18n.KNOWLEDGE_BASE_ELSER_LABEL}</EuiHealth>
|
||||
<EuiText
|
||||
size={'xs'}
|
||||
color={'subdued'}
|
||||
css={css`
|
||||
padding-left: 20px;
|
||||
`}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Configure ELSER within {machineLearning} to get started. {seeDocs}"
|
||||
id="xpack.elasticAssistant.assistant.settings.knowledgeBasedSettings.knowledgeBaseDescription"
|
||||
values={{
|
||||
machineLearning: (
|
||||
<EuiLink
|
||||
external
|
||||
href={http.basePath.prepend('/app/ml/trained_models')}
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.KNOWLEDGE_BASE_ELSER_MACHINE_LEARNING}
|
||||
</EuiLink>
|
||||
),
|
||||
seeDocs: (
|
||||
<EuiLink
|
||||
external
|
||||
href={
|
||||
'https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html#download-deploy-elser'
|
||||
}
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.KNOWLEDGE_BASE_ELSER_SEE_DOCS}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div>
|
||||
<EuiHealth color={knowledgeBaseHealth}>{i18n.KNOWLEDGE_BASE_LABEL}</EuiHealth>
|
||||
<EuiText
|
||||
size={'xs'}
|
||||
color={'subdued'}
|
||||
css={css`
|
||||
padding-left: 20px;
|
||||
`}
|
||||
>
|
||||
{knowledgeBaseDescription}
|
||||
</EuiText>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<span>
|
||||
<EuiHealth color={esqlHealth}>{i18n.ESQL_LABEL}</EuiHealth>
|
||||
<EuiText
|
||||
size={'xs'}
|
||||
color={'subdued'}
|
||||
css={css`
|
||||
padding-left: 20px;
|
||||
`}
|
||||
>
|
||||
{esqlDescription}
|
||||
</EuiText>
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<AlertsSettings
|
||||
knowledgeBase={knowledgeBase}
|
||||
setUpdatedKnowledgeBaseSettings={handleUpdateKnowledgeBaseSettings}
|
||||
/>
|
||||
|
||||
<AssistantSettingsBottomBar
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
onCancelClick={onCancelClick}
|
||||
onSaveButtonClicked={onSaveButtonClicked}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
});
|
||||
|
||||
KnowledgeBaseSettingsManagement.displayName = 'KnowledgeBaseSettingsManagement';
|
|
@ -66,11 +66,10 @@ export const SETTINGS_BADGE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const SETTINGS_DESCRIPTION = i18n.translate(
|
||||
export const KNOWLEDGE_BASE_DOCUMENTATION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Powered by ELSER, the Knowledge Base enables the ability to recall documents and other relevant context within your conversation.',
|
||||
defaultMessage: 'documentation',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -31,11 +31,7 @@ export const ManagementSettings = React.memo(() => {
|
|||
mergeBaseWithPersistedConversations(baseConversations, conversationsData),
|
||||
[baseConversations]
|
||||
);
|
||||
const {
|
||||
data: conversations,
|
||||
isFetched: conversationsLoaded,
|
||||
refetch: refetchConversations,
|
||||
} = useFetchCurrentUserConversations({
|
||||
const { data: conversations } = useFetchCurrentUserConversations({
|
||||
http,
|
||||
onFetch: onFetchedConversations,
|
||||
isAssistantEnabled,
|
||||
|
@ -51,14 +47,7 @@ export const ManagementSettings = React.memo(() => {
|
|||
);
|
||||
|
||||
if (conversations) {
|
||||
return (
|
||||
<AssistantSettingsManagement
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
refetchConversations={refetchConversations}
|
||||
selectedConversation={currentConversation}
|
||||
/>
|
||||
);
|
||||
return <AssistantSettingsManagement selectedConversation={currentConversation} />;
|
||||
}
|
||||
|
||||
return <></>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue