mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Cleanup aiAssistantFlyoutMode feature flag (#182992)
## Summary Cleanup Security `aiAssistantFlyoutMode` feature flag --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
df22162faf
commit
27ccd4d539
88 changed files with 831 additions and 2595 deletions
|
@ -5,13 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Replacements } from '../../schemas';
|
||||
|
||||
/** This mock returns the reverse of `value` */
|
||||
export const mockGetAnonymizedValue = ({
|
||||
currentReplacements,
|
||||
rawValue,
|
||||
}: {
|
||||
currentReplacements: Replacements | undefined;
|
||||
rawValue: string;
|
||||
}): string => rawValue.split('').reverse().join('');
|
||||
export const mockGetAnonymizedValue = ({ rawValue }: { rawValue: string }): string =>
|
||||
rawValue.split('').reverse().join('');
|
||||
|
|
|
@ -1,284 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPopover,
|
||||
EuiContextMenu,
|
||||
EuiButtonIcon,
|
||||
EuiPanel,
|
||||
EuiConfirmModal,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { DocLinksStart } from '@kbn/core-doc-links-browser';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Conversation } from '../../..';
|
||||
import { AssistantTitle } from '../assistant_title';
|
||||
import { ConnectorSelectorInline } from '../../connectorland/connector_selector_inline/connector_selector_inline';
|
||||
import { FlyoutNavigation } from '../assistant_overlay/flyout_navigation';
|
||||
import { AssistantSettingsButton } from '../settings/assistant_settings_button';
|
||||
import * as i18n from './translations';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
|
||||
interface OwnProps {
|
||||
selectedConversation: Conversation | undefined;
|
||||
defaultConnector?: AIConnector;
|
||||
docLinks: Omit<DocLinksStart, 'links'>;
|
||||
isDisabled: boolean;
|
||||
isSettingsModalVisible: boolean;
|
||||
onToggleShowAnonymizedValues: () => void;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
showAnonymizedValues: boolean;
|
||||
onChatCleared: () => void;
|
||||
onCloseFlyout?: () => void;
|
||||
chatHistoryVisible?: boolean;
|
||||
setChatHistoryVisible?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void;
|
||||
conversations: Record<string, Conversation>;
|
||||
conversationsLoaded: boolean;
|
||||
refetchConversationsState: () => Promise<void>;
|
||||
onConversationCreate: () => Promise<void>;
|
||||
isAssistantEnabled: boolean;
|
||||
refetchPrompts?: (
|
||||
options?: RefetchOptions & RefetchQueryFilters<unknown>
|
||||
) => Promise<QueryObserverResult<unknown, unknown>>;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
/**
|
||||
* Renders the header of the Elastic AI Assistant.
|
||||
* Provide a user interface for selecting and managing conversations,
|
||||
* toggling the display of anonymized values, and accessing the assistant settings.
|
||||
*/
|
||||
export const AssistantHeaderFlyout: React.FC<Props> = ({
|
||||
selectedConversation,
|
||||
defaultConnector,
|
||||
docLinks,
|
||||
isDisabled,
|
||||
isSettingsModalVisible,
|
||||
onToggleShowAnonymizedValues,
|
||||
setIsSettingsModalVisible,
|
||||
showAnonymizedValues,
|
||||
onChatCleared,
|
||||
chatHistoryVisible,
|
||||
setChatHistoryVisible,
|
||||
onCloseFlyout,
|
||||
onConversationSelected,
|
||||
conversations,
|
||||
conversationsLoaded,
|
||||
refetchConversationsState,
|
||||
onConversationCreate,
|
||||
isAssistantEnabled,
|
||||
refetchPrompts,
|
||||
}) => {
|
||||
const showAnonymizedValuesChecked = useMemo(
|
||||
() =>
|
||||
selectedConversation?.replacements != null &&
|
||||
Object.keys(selectedConversation?.replacements).length > 0 &&
|
||||
showAnonymizedValues,
|
||||
[selectedConversation?.replacements, showAnonymizedValues]
|
||||
);
|
||||
|
||||
const selectedConnectorId = useMemo(
|
||||
() => selectedConversation?.apiConfig?.connectorId,
|
||||
[selectedConversation?.apiConfig?.connectorId]
|
||||
);
|
||||
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
||||
const onButtonClick = useCallback(() => {
|
||||
setPopover(!isPopoverOpen);
|
||||
}, [isPopoverOpen]);
|
||||
|
||||
const closePopover = useCallback(() => {
|
||||
setPopover(false);
|
||||
}, []);
|
||||
|
||||
const [isResetConversationModalVisible, setIsResetConversationModalVisible] = useState(false);
|
||||
|
||||
const closeDestroyModal = useCallback(() => setIsResetConversationModalVisible(false), []);
|
||||
const showDestroyModal = useCallback(() => setIsResetConversationModalVisible(true), []);
|
||||
|
||||
const onConversationChange = useCallback(
|
||||
(updatedConversation) => {
|
||||
onConversationSelected({
|
||||
cId: updatedConversation.id,
|
||||
cTitle: updatedConversation.title,
|
||||
});
|
||||
},
|
||||
[onConversationSelected]
|
||||
);
|
||||
|
||||
const panels = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 0,
|
||||
items: [
|
||||
{
|
||||
name: i18n.RESET_CONVERSATION,
|
||||
css: css`
|
||||
color: ${euiThemeVars.euiColorDanger};
|
||||
`,
|
||||
onClick: showDestroyModal,
|
||||
icon: 'refresh',
|
||||
'data-test-subj': 'clear-chat',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[showDestroyModal]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
onChatCleared();
|
||||
closeDestroyModal();
|
||||
closePopover();
|
||||
}, [onChatCleared, closeDestroyModal, closePopover]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlyoutNavigation
|
||||
isExpanded={!!chatHistoryVisible}
|
||||
setIsExpanded={setChatHistoryVisible}
|
||||
onConversationCreate={onConversationCreate}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantSettingsButton
|
||||
defaultConnector={defaultConnector}
|
||||
isDisabled={isDisabled}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
selectedConversationId={
|
||||
!isEmpty(selectedConversation?.id)
|
||||
? selectedConversation?.id
|
||||
: selectedConversation?.title
|
||||
}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
onConversationSelected={onConversationSelected}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
isFlyoutMode={true}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
{onCloseFlyout && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
aria-label="xxx"
|
||||
iconType="cross"
|
||||
color="text"
|
||||
size="xs"
|
||||
onClick={onCloseFlyout}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</FlyoutNavigation>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
css={css`
|
||||
padding-top: ${euiThemeVars.euiSizeS};
|
||||
padding-bottom: ${euiThemeVars.euiSizeS};
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup alignItems={'center'} justifyContent={'spaceBetween'} gutterSize="s">
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<AssistantTitle
|
||||
docLinks={docLinks}
|
||||
title={selectedConversation?.title}
|
||||
selectedConversation={selectedConversation}
|
||||
onChange={onConversationChange}
|
||||
isFlyoutMode={true}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems={'center'}>
|
||||
<EuiFlexItem>
|
||||
<ConnectorSelectorInline
|
||||
isDisabled={isDisabled || selectedConversation === undefined}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
selectedConversation={selectedConversation}
|
||||
isFlyoutMode={true}
|
||||
onConnectorSelected={onConversationChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
content={
|
||||
showAnonymizedValuesChecked ? i18n.SHOW_REAL_VALUES : i18n.SHOW_ANONYMIZED
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
css={css`
|
||||
border-radius: 50%;
|
||||
`}
|
||||
display="base"
|
||||
data-test-subj="showAnonymizedValues"
|
||||
isSelected={showAnonymizedValuesChecked}
|
||||
aria-label={
|
||||
showAnonymizedValuesChecked ? i18n.SHOW_ANONYMIZED : i18n.SHOW_REAL_VALUES
|
||||
}
|
||||
iconType={showAnonymizedValuesChecked ? 'eye' : 'eyeClosed'}
|
||||
onClick={onToggleShowAnonymizedValues}
|
||||
isDisabled={isEmpty(selectedConversation?.replacements)}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
aria-label="test"
|
||||
iconType="boxesVertical"
|
||||
onClick={onButtonClick}
|
||||
data-test-subj="chat-context-menu"
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
{isResetConversationModalVisible && (
|
||||
<EuiConfirmModal
|
||||
title={i18n.RESET_CONVERSATION}
|
||||
onCancel={closeDestroyModal}
|
||||
onConfirm={handleReset}
|
||||
cancelButtonText={i18n.CANCEL_BUTTON_TEXT}
|
||||
confirmButtonText={i18n.RESET_BUTTON_TEXT}
|
||||
buttonColor="danger"
|
||||
defaultFocusedButton="confirm"
|
||||
data-test-subj="reset-conversation-modal"
|
||||
>
|
||||
<p>{i18n.CLEAR_CHAT_CONFIRMATION}</p>
|
||||
</EuiConfirmModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -20,7 +20,7 @@ const mockConversations = {
|
|||
};
|
||||
const testProps = {
|
||||
conversationsLoaded: true,
|
||||
currentConversation: welcomeConvo,
|
||||
selectedConversation: welcomeConvo,
|
||||
title: 'Test Title',
|
||||
docLinks: {
|
||||
ELASTIC_WEBSITE_URL: 'https://www.elastic.co/',
|
||||
|
@ -30,12 +30,13 @@ const testProps = {
|
|||
isSettingsModalVisible: false,
|
||||
onConversationSelected,
|
||||
onToggleShowAnonymizedValues: jest.fn(),
|
||||
selectedConversationId: emptyWelcomeConvo.id,
|
||||
setIsSettingsModalVisible: jest.fn(),
|
||||
onConversationDeleted: jest.fn(),
|
||||
onConversationCreate: jest.fn(),
|
||||
onChatCleared: jest.fn(),
|
||||
showAnonymizedValues: false,
|
||||
conversations: mockConversations,
|
||||
refetchConversationsState: jest.fn(),
|
||||
isAssistantEnabled: true,
|
||||
anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] },
|
||||
refetchAnonymizationFieldsResults: jest.fn(),
|
||||
allPrompts: [],
|
||||
|
@ -69,53 +70,64 @@ describe('AssistantHeader', () => {
|
|||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('showAnonymizedValues is not checked when currentConversation.replacements is null', () => {
|
||||
it('showAnonymizedValues is not checked when selectedConversation.replacements is null', () => {
|
||||
const { getByText, getByTestId } = render(<AssistantHeader {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(getByText('Test Title')).toBeInTheDocument();
|
||||
expect(getByTestId('showAnonymizedValues')).toHaveAttribute('aria-checked', 'false');
|
||||
expect(getByText(welcomeConvo.title)).toBeInTheDocument();
|
||||
expect(getByTestId('showAnonymizedValues').firstChild).toHaveAttribute(
|
||||
'data-euiicon-type',
|
||||
'eyeClosed'
|
||||
);
|
||||
});
|
||||
|
||||
it('showAnonymizedValues is not checked when currentConversation.replacements is empty', () => {
|
||||
it('showAnonymizedValues is not checked when selectedConversation.replacements is empty', () => {
|
||||
const { getByText, getByTestId } = render(
|
||||
<AssistantHeader
|
||||
{...testProps}
|
||||
currentConversation={{ ...emptyWelcomeConvo, replacements: {} }}
|
||||
selectedConversation={{ ...emptyWelcomeConvo, replacements: {} }}
|
||||
/>,
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
expect(getByText('Test Title')).toBeInTheDocument();
|
||||
expect(getByTestId('showAnonymizedValues')).toHaveAttribute('aria-checked', 'false');
|
||||
expect(getByText(welcomeConvo.title)).toBeInTheDocument();
|
||||
expect(getByTestId('showAnonymizedValues').firstChild).toHaveAttribute(
|
||||
'data-euiicon-type',
|
||||
'eyeClosed'
|
||||
);
|
||||
});
|
||||
|
||||
it('showAnonymizedValues is not checked when currentConversation.replacements has values and showAnonymizedValues is false', () => {
|
||||
it('showAnonymizedValues is not checked when selectedConversation.replacements has values and showAnonymizedValues is false', () => {
|
||||
const { getByTestId } = render(
|
||||
<AssistantHeader {...testProps} currentConversation={alertConvo} />,
|
||||
<AssistantHeader {...testProps} selectedConversation={alertConvo} />,
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
expect(getByTestId('showAnonymizedValues')).toHaveAttribute('aria-checked', 'false');
|
||||
expect(getByTestId('showAnonymizedValues').firstChild).toHaveAttribute(
|
||||
'data-euiicon-type',
|
||||
'eyeClosed'
|
||||
);
|
||||
});
|
||||
|
||||
it('showAnonymizedValues is checked when currentConversation.replacements has values and showAnonymizedValues is true', () => {
|
||||
it('showAnonymizedValues is checked when selectedConversation.replacements has values and showAnonymizedValues is true', () => {
|
||||
const { getByTestId } = render(
|
||||
<AssistantHeader {...testProps} currentConversation={alertConvo} showAnonymizedValues />,
|
||||
<AssistantHeader {...testProps} selectedConversation={alertConvo} showAnonymizedValues />,
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
expect(getByTestId('showAnonymizedValues')).toHaveAttribute('aria-checked', 'true');
|
||||
expect(getByTestId('showAnonymizedValues').firstChild).toHaveAttribute(
|
||||
'data-euiicon-type',
|
||||
'eye'
|
||||
);
|
||||
});
|
||||
|
||||
it('Conversation is updated when connector change occurs', async () => {
|
||||
const { getByTestId } = render(<AssistantHeader {...testProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
fireEvent.click(getByTestId('connectorSelectorPlaceholderButton'));
|
||||
fireEvent.click(getByTestId('connector-selector'));
|
||||
|
||||
await act(async () => {
|
||||
|
|
|
@ -5,44 +5,47 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiPopover,
|
||||
EuiContextMenu,
|
||||
EuiButtonIcon,
|
||||
EuiPanel,
|
||||
EuiConfirmModal,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
|
||||
import { css } from '@emotion/react';
|
||||
import { DocLinksStart } from '@kbn/core-doc-links-browser';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
import { Conversation } from '../../..';
|
||||
import { AssistantTitle } from '../assistant_title';
|
||||
import { ConversationSelector } from '../conversations/conversation_selector';
|
||||
import { ConnectorSelectorInline } from '../../connectorland/connector_selector_inline/connector_selector_inline';
|
||||
import { FlyoutNavigation } from '../assistant_overlay/flyout_navigation';
|
||||
import { AssistantSettingsButton } from '../settings/assistant_settings_button';
|
||||
import * as i18n from './translations';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
|
||||
interface OwnProps {
|
||||
currentConversation?: Conversation;
|
||||
selectedConversation: Conversation | undefined;
|
||||
defaultConnector?: AIConnector;
|
||||
docLinks: Omit<DocLinksStart, 'links'>;
|
||||
isDisabled: boolean;
|
||||
isSettingsModalVisible: boolean;
|
||||
onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void;
|
||||
onConversationDeleted: (conversationId: string) => void;
|
||||
onToggleShowAnonymizedValues: () => void;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
shouldDisableKeyboardShortcut?: () => boolean;
|
||||
showAnonymizedValues: boolean;
|
||||
title: string;
|
||||
onChatCleared: () => void;
|
||||
onCloseFlyout?: () => void;
|
||||
chatHistoryVisible?: boolean;
|
||||
setChatHistoryVisible?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void;
|
||||
conversations: Record<string, Conversation>;
|
||||
conversationsLoaded: boolean;
|
||||
refetchConversationsState: () => Promise<void>;
|
||||
allPrompts: PromptResponse[];
|
||||
onConversationCreate: () => Promise<void>;
|
||||
isAssistantEnabled: boolean;
|
||||
refetchPrompts?: (
|
||||
options?: RefetchOptions & RefetchQueryFilters<unknown>
|
||||
) => Promise<QueryObserverResult<unknown, unknown>>;
|
||||
|
@ -55,31 +58,53 @@ type Props = OwnProps;
|
|||
* toggling the display of anonymized values, and accessing the assistant settings.
|
||||
*/
|
||||
export const AssistantHeader: React.FC<Props> = ({
|
||||
currentConversation,
|
||||
selectedConversation,
|
||||
defaultConnector,
|
||||
docLinks,
|
||||
isDisabled,
|
||||
isSettingsModalVisible,
|
||||
onConversationSelected,
|
||||
onConversationDeleted,
|
||||
onToggleShowAnonymizedValues,
|
||||
setIsSettingsModalVisible,
|
||||
shouldDisableKeyboardShortcut,
|
||||
showAnonymizedValues,
|
||||
title,
|
||||
onChatCleared,
|
||||
chatHistoryVisible,
|
||||
setChatHistoryVisible,
|
||||
onCloseFlyout,
|
||||
onConversationSelected,
|
||||
conversations,
|
||||
conversationsLoaded,
|
||||
refetchConversationsState,
|
||||
allPrompts,
|
||||
onConversationCreate,
|
||||
isAssistantEnabled,
|
||||
refetchPrompts,
|
||||
}) => {
|
||||
const showAnonymizedValuesChecked = useMemo(
|
||||
() =>
|
||||
currentConversation?.replacements != null &&
|
||||
Object.keys(currentConversation?.replacements).length > 0 &&
|
||||
selectedConversation?.replacements != null &&
|
||||
Object.keys(selectedConversation?.replacements).length > 0 &&
|
||||
showAnonymizedValues,
|
||||
[currentConversation?.replacements, showAnonymizedValues]
|
||||
[selectedConversation?.replacements, showAnonymizedValues]
|
||||
);
|
||||
|
||||
const selectedConnectorId = useMemo(
|
||||
() => selectedConversation?.apiConfig?.connectorId,
|
||||
[selectedConversation?.apiConfig?.connectorId]
|
||||
);
|
||||
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
||||
const onButtonClick = useCallback(() => {
|
||||
setPopover(!isPopoverOpen);
|
||||
}, [isPopoverOpen]);
|
||||
|
||||
const closePopover = useCallback(() => {
|
||||
setPopover(false);
|
||||
}, []);
|
||||
|
||||
const [isResetConversationModalVisible, setIsResetConversationModalVisible] = useState(false);
|
||||
|
||||
const closeDestroyModal = useCallback(() => setIsResetConversationModalVisible(false), []);
|
||||
const showDestroyModal = useCallback(() => setIsResetConversationModalVisible(true), []);
|
||||
|
||||
const onConversationChange = useCallback(
|
||||
(updatedConversation) => {
|
||||
onConversationSelected({
|
||||
|
@ -89,90 +114,163 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
},
|
||||
[onConversationSelected]
|
||||
);
|
||||
const selectedConversationId = useMemo(
|
||||
() =>
|
||||
!isEmpty(currentConversation?.id) ? currentConversation?.id : currentConversation?.title,
|
||||
[currentConversation?.id, currentConversation?.title]
|
||||
|
||||
const panels = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 0,
|
||||
items: [
|
||||
{
|
||||
name: i18n.RESET_CONVERSATION,
|
||||
css: css`
|
||||
color: ${euiThemeVars.euiColorDanger};
|
||||
`,
|
||||
onClick: showDestroyModal,
|
||||
icon: 'refresh',
|
||||
'data-test-subj': 'clear-chat',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[showDestroyModal]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
onChatCleared();
|
||||
closeDestroyModal();
|
||||
closePopover();
|
||||
}, [onChatCleared, closeDestroyModal, closePopover]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup
|
||||
css={css`
|
||||
width: 100%;
|
||||
`}
|
||||
alignItems={'center'}
|
||||
justifyContent={'spaceBetween'}
|
||||
<FlyoutNavigation
|
||||
isExpanded={!!chatHistoryVisible}
|
||||
setIsExpanded={setChatHistoryVisible}
|
||||
onConversationCreate={onConversationCreate}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantTitle
|
||||
isDisabled={isDisabled}
|
||||
docLinks={docLinks}
|
||||
selectedConversation={currentConversation}
|
||||
onChange={onConversationChange}
|
||||
title={title}
|
||||
isFlyoutMode={false}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantSettingsButton
|
||||
defaultConnector={defaultConnector}
|
||||
isDisabled={isDisabled}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
selectedConversationId={
|
||||
!isEmpty(selectedConversation?.id)
|
||||
? selectedConversation?.id
|
||||
: selectedConversation?.title
|
||||
}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
onConversationSelected={onConversationSelected}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
width: 335px;
|
||||
`}
|
||||
>
|
||||
<ConversationSelector
|
||||
defaultConnector={defaultConnector}
|
||||
selectedConversationId={selectedConversationId}
|
||||
onConversationSelected={onConversationSelected}
|
||||
shouldDisableKeyboardShortcut={shouldDisableKeyboardShortcut}
|
||||
isDisabled={isDisabled}
|
||||
conversations={conversations}
|
||||
onConversationDeleted={onConversationDeleted}
|
||||
allPrompts={allPrompts}
|
||||
/>
|
||||
{onCloseFlyout && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="euiFlyoutCloseButton"
|
||||
iconType="cross"
|
||||
color="text"
|
||||
size="xs"
|
||||
onClick={onCloseFlyout}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</FlyoutNavigation>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
css={css`
|
||||
padding-top: ${euiThemeVars.euiSizeS};
|
||||
padding-bottom: ${euiThemeVars.euiSizeS};
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup alignItems={'center'} justifyContent={'spaceBetween'} gutterSize="s">
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<AssistantTitle
|
||||
title={selectedConversation?.title}
|
||||
selectedConversation={selectedConversation}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems={'center'}>
|
||||
<EuiFlexItem>
|
||||
<ConnectorSelectorInline
|
||||
isDisabled={isDisabled || selectedConversation === undefined}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
selectedConversation={selectedConversation}
|
||||
onConnectorSelected={onConversationChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
content={i18n.SHOW_ANONYMIZED_TOOLTIP}
|
||||
position="left"
|
||||
repositionOnScroll={true}
|
||||
content={
|
||||
showAnonymizedValuesChecked ? i18n.SHOW_REAL_VALUES : i18n.SHOW_ANONYMIZED
|
||||
}
|
||||
>
|
||||
<EuiSwitch
|
||||
<EuiButtonIcon
|
||||
css={css`
|
||||
border-radius: 50%;
|
||||
`}
|
||||
display="base"
|
||||
data-test-subj="showAnonymizedValues"
|
||||
checked={showAnonymizedValuesChecked}
|
||||
compressed={true}
|
||||
disabled={isEmpty(currentConversation?.replacements)}
|
||||
label={i18n.SHOW_ANONYMIZED}
|
||||
onChange={onToggleShowAnonymizedValues}
|
||||
isSelected={showAnonymizedValuesChecked}
|
||||
aria-label={
|
||||
showAnonymizedValuesChecked ? i18n.SHOW_ANONYMIZED : i18n.SHOW_REAL_VALUES
|
||||
}
|
||||
iconType={showAnonymizedValuesChecked ? 'eye' : 'eyeClosed'}
|
||||
onClick={onToggleShowAnonymizedValues}
|
||||
isDisabled={isEmpty(selectedConversation?.replacements)}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantSettingsButton
|
||||
defaultConnector={defaultConnector}
|
||||
isDisabled={isDisabled}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
selectedConversationId={selectedConversationId}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
onConversationSelected={onConversationSelected}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
isFlyoutMode={false}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
<EuiFlexItem>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
aria-label="test"
|
||||
iconType="boxesVertical"
|
||||
onClick={onButtonClick}
|
||||
data-test-subj="chat-context-menu"
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin={'m'} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
{isResetConversationModalVisible && (
|
||||
<EuiConfirmModal
|
||||
title={i18n.RESET_CONVERSATION}
|
||||
onCancel={closeDestroyModal}
|
||||
onConfirm={handleReset}
|
||||
cancelButtonText={i18n.CANCEL_BUTTON_TEXT}
|
||||
confirmButtonText={i18n.RESET_BUTTON_TEXT}
|
||||
buttonColor="danger"
|
||||
defaultFocusedButton="confirm"
|
||||
data-test-subj="reset-conversation-modal"
|
||||
>
|
||||
<p>{i18n.CLEAR_CHAT_CONFIRMATION}</p>
|
||||
</EuiConfirmModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -48,6 +48,7 @@ export const FlyoutNavigation = memo<FlyoutNavigationProps>(
|
|||
onClick={onToggle}
|
||||
iconType={isExpanded ? 'arrowEnd' : 'arrowStart'}
|
||||
size="xs"
|
||||
data-test-subj="aiAssistantFlyoutNavigationToggle"
|
||||
aria-label={
|
||||
isExpanded
|
||||
? i18n.translate(
|
||||
|
|
|
@ -24,31 +24,33 @@ describe('AssistantOverlay', () => {
|
|||
it('renders when isAssistantEnabled prop is true and keyboard shortcut is pressed', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders providerContext={{ assistantTelemetry }}>
|
||||
<AssistantOverlay isFlyoutMode={false} />
|
||||
<AssistantOverlay />
|
||||
</TestProviders>
|
||||
);
|
||||
fireEvent.keyDown(document, { key: ';', ctrlKey: true });
|
||||
const modal = getByTestId('ai-assistant-modal');
|
||||
expect(modal).toBeInTheDocument();
|
||||
const flyout = getByTestId('ai-assistant-flyout');
|
||||
expect(flyout).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('modal closes when close button is clicked', () => {
|
||||
const { getByLabelText, queryByTestId } = render(
|
||||
it('flyout closes when close button is clicked', () => {
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<AssistantOverlay isFlyoutMode={false} />
|
||||
<AssistantOverlay />
|
||||
</TestProviders>
|
||||
);
|
||||
fireEvent.keyDown(document, { key: ';', ctrlKey: true });
|
||||
const closeButton = getByLabelText('Closes this modal window');
|
||||
fireEvent.click(closeButton);
|
||||
const modal = queryByTestId('ai-assistant-modal');
|
||||
expect(modal).not.toBeInTheDocument();
|
||||
const closeButton = queryByTestId('euiFlyoutCloseButton');
|
||||
if (closeButton) {
|
||||
fireEvent.click(closeButton);
|
||||
}
|
||||
const flyout = queryByTestId('ai-assistant-flyout');
|
||||
expect(flyout).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Assistant invoked from shortcut tracking happens on modal open only (not close)', () => {
|
||||
it('Assistant invoked from shortcut tracking happens on flyout open only (not close)', () => {
|
||||
render(
|
||||
<TestProviders providerContext={{ assistantTelemetry }}>
|
||||
<AssistantOverlay isFlyoutMode={false} />
|
||||
<AssistantOverlay />
|
||||
</TestProviders>
|
||||
);
|
||||
fireEvent.keyDown(document, { key: ';', ctrlKey: true });
|
||||
|
@ -61,26 +63,26 @@ describe('AssistantOverlay', () => {
|
|||
expect(reportAssistantInvoked).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('modal closes when shortcut is pressed and modal is already open', () => {
|
||||
it('flyout closes when shortcut is pressed and flyout is already open', () => {
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<AssistantOverlay isFlyoutMode={false} />
|
||||
<AssistantOverlay />
|
||||
</TestProviders>
|
||||
);
|
||||
fireEvent.keyDown(document, { key: ';', ctrlKey: true });
|
||||
fireEvent.keyDown(document, { key: ';', ctrlKey: true });
|
||||
const modal = queryByTestId('ai-assistant-modal');
|
||||
expect(modal).not.toBeInTheDocument();
|
||||
const flyout = queryByTestId('ai-assistant-flyout');
|
||||
expect(flyout).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('modal does not open when incorrect shortcut is pressed', () => {
|
||||
it('flyout does not open when incorrect shortcut is pressed', () => {
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<AssistantOverlay isFlyoutMode={false} />
|
||||
<AssistantOverlay />
|
||||
</TestProviders>
|
||||
);
|
||||
fireEvent.keyDown(document, { key: 'a', ctrlKey: true });
|
||||
const modal = queryByTestId('ai-assistant-modal');
|
||||
expect(modal).not.toBeInTheDocument();
|
||||
const flyout = queryByTestId('ai-assistant-flyout');
|
||||
expect(flyout).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { EuiModal, EuiFlyoutResizable, useEuiTheme } from '@elastic/eui';
|
||||
import { EuiFlyoutResizable } from '@elastic/eui';
|
||||
|
||||
import useEvent from 'react-use/lib/useEvent';
|
||||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import styled from 'styled-components';
|
||||
import { css } from '@emotion/react';
|
||||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
import {
|
||||
ShowAssistantOverlayProps,
|
||||
useAssistantContext,
|
||||
|
@ -22,23 +22,21 @@ import { WELCOME_CONVERSATION_TITLE } from '../use_conversation/translations';
|
|||
|
||||
const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
|
||||
|
||||
const StyledEuiModal = styled(EuiModal)`
|
||||
${({ theme }) => `margin-top: ${theme.eui.euiSizeXXL};`}
|
||||
min-width: 95vw;
|
||||
min-height: 25vh;
|
||||
`;
|
||||
|
||||
/**
|
||||
* Modal container for Elastic AI Assistant conversations, receiving the page contents as context, plus whatever
|
||||
* component currently has focus and any specific context it may provide through the SAssInterface.
|
||||
*/
|
||||
export interface Props {
|
||||
isFlyoutMode: boolean;
|
||||
currentUserAvatar?: UserAvatar;
|
||||
}
|
||||
|
||||
export const AssistantOverlay = React.memo<Props>(({ isFlyoutMode, currentUserAvatar }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
export const UnifiedTimelineGlobalStyles = createGlobalStyle`
|
||||
body:has(.timeline-portal-overlay-mask) .euiOverlayMask {
|
||||
z-index: 1003 !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AssistantOverlay = React.memo<Props>(({ currentUserAvatar }) => {
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [conversationTitle, setConversationTitle] = useState<string | undefined>(
|
||||
WELCOME_CONVERSATION_TITLE
|
||||
|
@ -130,8 +128,8 @@ export const AssistantOverlay = React.memo<Props>(({ isFlyoutMode, currentUserAv
|
|||
|
||||
if (!isModalVisible) return null;
|
||||
|
||||
if (isFlyoutMode) {
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutResizable
|
||||
ref={flyoutRef}
|
||||
css={css`
|
||||
|
@ -145,35 +143,17 @@ export const AssistantOverlay = React.memo<Props>(({ isFlyoutMode, currentUserAv
|
|||
data-test-subj="ai-assistant-flyout"
|
||||
paddingSize="none"
|
||||
hideCloseButton
|
||||
// EUI TODO: This z-index override of EuiOverlayMask is a workaround, and ideally should be resolved with a cleaner UI/UX flow long-term
|
||||
maskProps={{ style: `z-index: ${(euiTheme.levels.flyout as number) + 3}` }} // we need this flyout to be above the timeline flyout (which has a z-index of 1002)
|
||||
>
|
||||
<Assistant
|
||||
conversationTitle={conversationTitle}
|
||||
promptContextId={promptContextId}
|
||||
onCloseFlyout={handleCloseModal}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
chatHistoryVisible={chatHistoryVisible}
|
||||
setChatHistoryVisible={toggleChatHistory}
|
||||
currentUserAvatar={currentUserAvatar}
|
||||
/>
|
||||
</EuiFlyoutResizable>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isModalVisible && (
|
||||
<StyledEuiModal onClose={handleCloseModal} data-test-subj="ai-assistant-modal">
|
||||
<Assistant
|
||||
conversationTitle={conversationTitle}
|
||||
promptContextId={promptContextId}
|
||||
chatHistoryVisible={chatHistoryVisible}
|
||||
setChatHistoryVisible={toggleChatHistory}
|
||||
currentUserAvatar={currentUserAvatar}
|
||||
/>
|
||||
</StyledEuiModal>
|
||||
)}
|
||||
<UnifiedTimelineGlobalStyles />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { AssistantTitle } from '.';
|
||||
import { TestProviders } from '../../mock/test_providers/test_providers';
|
||||
|
||||
|
@ -14,7 +14,6 @@ const testProps = {
|
|||
title: 'Test Title',
|
||||
docLinks: { ELASTIC_WEBSITE_URL: 'https://www.elastic.co/', DOC_LINK_VERSION: '7.15' },
|
||||
selectedConversation: undefined,
|
||||
isFlyoutMode: false,
|
||||
onChange: jest.fn(),
|
||||
refetchConversationsState: jest.fn(),
|
||||
};
|
||||
|
@ -28,22 +27,4 @@ describe('AssistantTitle', () => {
|
|||
);
|
||||
expect(getByText('Test Title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clicking on the popover button opens the popover with the correct link', () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<AssistantTitle {...testProps} />
|
||||
</TestProviders>,
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
expect(queryByTestId('tooltipContent')).not.toBeInTheDocument();
|
||||
fireEvent.click(getByTestId('tooltipIcon'));
|
||||
expect(getByTestId('tooltipContent')).toBeInTheDocument();
|
||||
expect(getByTestId('externalDocumentationLink')).toHaveAttribute(
|
||||
'href',
|
||||
'https://www.elastic.co/guide/en/security/7.15/security-assistant.html'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,24 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiInlineEditTitle,
|
||||
EuiLink,
|
||||
EuiModalHeaderTitle,
|
||||
EuiPopover,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import type { DocLinksStart } from '@kbn/core-doc-links-browser';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiInlineEditTitle } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import * as i18n from '../translations';
|
||||
import type { Conversation } from '../../..';
|
||||
import { ConnectorSelectorInline } from '../../connectorland/connector_selector_inline/connector_selector_inline';
|
||||
import { AssistantAvatar } from '../assistant_avatar/assistant_avatar';
|
||||
import { useConversation } from '../use_conversation';
|
||||
import { NEW_CHAT } from '../conversations/conversation_sidepanel/translations';
|
||||
|
@ -32,63 +18,14 @@ import { NEW_CHAT } from '../conversations/conversation_sidepanel/translations';
|
|||
* information about the assistant feature and access to documentation.
|
||||
*/
|
||||
export const AssistantTitle: React.FC<{
|
||||
isDisabled?: boolean;
|
||||
title?: string;
|
||||
docLinks: Omit<DocLinksStart, 'links'>;
|
||||
selectedConversation: Conversation | undefined;
|
||||
isFlyoutMode: boolean;
|
||||
onChange: (updatedConversation: Conversation) => void;
|
||||
refetchConversationsState: () => Promise<void>;
|
||||
}> = ({
|
||||
isDisabled = false,
|
||||
title,
|
||||
docLinks,
|
||||
selectedConversation,
|
||||
isFlyoutMode,
|
||||
onChange,
|
||||
refetchConversationsState,
|
||||
}) => {
|
||||
}> = ({ title, selectedConversation, refetchConversationsState }) => {
|
||||
const [newTitle, setNewTitle] = useState(title);
|
||||
const [newTitleError, setNewTitleError] = useState(false);
|
||||
const { updateConversationTitle } = useConversation();
|
||||
|
||||
const selectedConnectorId = selectedConversation?.apiConfig?.connectorId;
|
||||
|
||||
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks;
|
||||
const url = `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/security-assistant.html`;
|
||||
|
||||
const documentationLink = useMemo(
|
||||
() => (
|
||||
<EuiLink
|
||||
aria-label={i18n.TOOLTIP_ARIA_LABEL}
|
||||
data-test-subj="externalDocumentationLink"
|
||||
external
|
||||
href={url}
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.DOCUMENTATION}
|
||||
</EuiLink>
|
||||
),
|
||||
[url]
|
||||
);
|
||||
|
||||
const content = useMemo(
|
||||
() => (
|
||||
<FormattedMessage
|
||||
defaultMessage="Responses from AI systems may not always be entirely accurate. For more information on the assistant feature and its usage, please reference the {documentationLink}."
|
||||
id="xpack.elasticAssistant.assistant.technicalPreview.tooltipContent"
|
||||
values={{
|
||||
documentationLink,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[documentationLink]
|
||||
);
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const onButtonClick = useCallback(() => setIsPopoverOpen((isOpen: boolean) => !isOpen), []);
|
||||
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
|
||||
|
||||
const handleUpdateTitle = useCallback(
|
||||
async (updatedTitle: string) => {
|
||||
setNewTitleError(false);
|
||||
|
@ -109,108 +46,33 @@ export const AssistantTitle: React.FC<{
|
|||
setNewTitle(title);
|
||||
}, [title]);
|
||||
|
||||
if (isFlyoutMode) {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="m" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantAvatar data-test-subj="titleIcon" size={isFlyoutMode ? 's' : 'm'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<EuiInlineEditTitle
|
||||
heading="h2"
|
||||
inputAriaLabel="Edit text inline"
|
||||
value={newTitle ?? NEW_CHAT}
|
||||
size="xs"
|
||||
isInvalid={!!newTitleError}
|
||||
isReadOnly={selectedConversation?.isDefault}
|
||||
onChange={(e) => setNewTitle(e.currentTarget.nodeValue || '')}
|
||||
onCancel={() => setNewTitle(title)}
|
||||
onSave={handleUpdateTitle}
|
||||
editModeProps={{
|
||||
formRowProps: {
|
||||
fullWidth: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiModalHeaderTitle>
|
||||
<EuiFlexGroup gutterSize="m" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantAvatar data-test-subj="titleIcon" size={isFlyoutMode ? 's' : 'm'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup
|
||||
direction={isFlyoutMode ? 'row' : 'column'}
|
||||
gutterSize="none"
|
||||
justifyContent={isFlyoutMode ? 'spaceBetween' : 'center'}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size={'s'}>
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
css={
|
||||
isFlyoutMode &&
|
||||
css`
|
||||
display: inline-flex;
|
||||
`
|
||||
}
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.TOOLTIP_ARIA_LABEL}
|
||||
data-test-subj="tooltipIcon"
|
||||
iconType="iInCircle"
|
||||
onClick={onButtonClick}
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
anchorPosition="rightUp"
|
||||
>
|
||||
<EuiText
|
||||
data-test-subj="tooltipContent"
|
||||
grow={false}
|
||||
css={{ maxWidth: '400px' }}
|
||||
>
|
||||
<EuiText size={'s'}>
|
||||
<p>{content}</p>
|
||||
</EuiText>
|
||||
</EuiText>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{!isFlyoutMode && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<ConnectorSelectorInline
|
||||
isDisabled={isDisabled || selectedConversation === undefined}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
selectedConversation={selectedConversation}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
onConnectorSelected={onChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalHeaderTitle>
|
||||
<EuiFlexGroup gutterSize="m" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantAvatar data-test-subj="titleIcon" size={'s'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<EuiInlineEditTitle
|
||||
heading="h2"
|
||||
inputAriaLabel="Edit text inline"
|
||||
value={newTitle ?? NEW_CHAT}
|
||||
size="xs"
|
||||
isInvalid={!!newTitleError}
|
||||
isReadOnly={selectedConversation?.isDefault}
|
||||
onChange={(e) => setNewTitle(e.currentTarget.nodeValue || '')}
|
||||
onCancel={() => setNewTitle(title)}
|
||||
onSave={handleUpdateTitle}
|
||||
editModeProps={{
|
||||
formRowProps: {
|
||||
fullWidth: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,14 +9,11 @@ import React from 'react';
|
|||
import { render, fireEvent, within } from '@testing-library/react';
|
||||
import { ChatActions } from '.';
|
||||
|
||||
const onChatCleared = jest.fn();
|
||||
const onSendMessage = jest.fn();
|
||||
const testProps = {
|
||||
isDisabled: false,
|
||||
isLoading: false,
|
||||
onChatCleared,
|
||||
onSendMessage,
|
||||
isFlyoutMode: false,
|
||||
promptValue: 'prompt',
|
||||
};
|
||||
|
||||
|
@ -26,16 +23,9 @@ describe('ChatActions', () => {
|
|||
});
|
||||
it('the component renders with all props', () => {
|
||||
const { getByTestId } = render(<ChatActions {...testProps} />);
|
||||
expect(getByTestId('clear-chat')).toHaveAttribute('aria-label', 'Clear chat');
|
||||
expect(getByTestId('submit-chat')).toHaveAttribute('aria-label', 'Submit message');
|
||||
});
|
||||
|
||||
it('onChatCleared function is called when clear chat button is clicked', () => {
|
||||
const { getByTestId } = render(<ChatActions {...testProps} />);
|
||||
fireEvent.click(getByTestId('clear-chat'));
|
||||
expect(onChatCleared).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onSendMessage function is called when send message button is clicked', () => {
|
||||
const { getByTestId } = render(<ChatActions {...testProps} />);
|
||||
|
||||
|
@ -49,7 +39,6 @@ describe('ChatActions', () => {
|
|||
isDisabled: true,
|
||||
};
|
||||
const { getByTestId } = render(<ChatActions {...props} />);
|
||||
expect(getByTestId('clear-chat')).toBeDisabled();
|
||||
expect(getByTestId('submit-chat')).toBeDisabled();
|
||||
});
|
||||
|
||||
|
|
|
@ -7,14 +7,12 @@
|
|||
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
|
||||
import { CLEAR_CHAT, SUBMIT_MESSAGE } from '../translations';
|
||||
import { SUBMIT_MESSAGE } from '../translations';
|
||||
|
||||
interface OwnProps {
|
||||
isDisabled: boolean;
|
||||
isLoading: boolean;
|
||||
isFlyoutMode: boolean;
|
||||
promptValue?: string;
|
||||
onChatCleared: () => void;
|
||||
onSendMessage: () => void;
|
||||
}
|
||||
|
||||
|
@ -26,9 +24,7 @@ type Props = OwnProps;
|
|||
export const ChatActions: React.FC<Props> = ({
|
||||
isDisabled,
|
||||
isLoading,
|
||||
onChatCleared,
|
||||
onSendMessage,
|
||||
isFlyoutMode,
|
||||
promptValue,
|
||||
}) => {
|
||||
const submitTooltipRef = useRef<EuiToolTip | null>(null);
|
||||
|
@ -39,21 +35,6 @@ export const ChatActions: React.FC<Props> = ({
|
|||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
{!isFlyoutMode && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip position="right" content={CLEAR_CHAT}>
|
||||
<EuiButtonIcon
|
||||
aria-label={CLEAR_CHAT}
|
||||
color="danger"
|
||||
data-test-subj="clear-chat"
|
||||
display="base"
|
||||
iconType="cross"
|
||||
isDisabled={isDisabled}
|
||||
onClick={onChatCleared}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
ref={submitTooltipRef}
|
||||
|
@ -66,9 +47,9 @@ export const ChatActions: React.FC<Props> = ({
|
|||
aria-label={SUBMIT_MESSAGE}
|
||||
data-test-subj="submit-chat"
|
||||
color="primary"
|
||||
display={isFlyoutMode && promptValue?.length ? 'fill' : 'base'}
|
||||
size={isFlyoutMode ? 'm' : 'xs'}
|
||||
iconType={isFlyoutMode ? 'kqlFunction' : 'returnKey'}
|
||||
display={promptValue?.length ? 'fill' : 'base'}
|
||||
size={'m'}
|
||||
iconType={'kqlFunction'}
|
||||
isDisabled={isDisabled || !promptValue?.length}
|
||||
isLoading={isLoading}
|
||||
onClick={onSendMessage}
|
||||
|
|
|
@ -12,12 +12,10 @@ import { TestProviders } from '../../mock/test_providers/test_providers';
|
|||
|
||||
jest.mock('./use_chat_send');
|
||||
|
||||
const handleOnChatCleared = jest.fn();
|
||||
const handlePromptChange = jest.fn();
|
||||
const handleSendMessage = jest.fn();
|
||||
const handleRegenerateResponse = jest.fn();
|
||||
const testProps: Props = {
|
||||
handleOnChatCleared,
|
||||
handlePromptChange,
|
||||
handleSendMessage,
|
||||
handleRegenerateResponse,
|
||||
|
@ -25,7 +23,6 @@ const testProps: Props = {
|
|||
isDisabled: false,
|
||||
shouldRefocusPrompt: false,
|
||||
userPrompt: '',
|
||||
isFlyoutMode: false,
|
||||
};
|
||||
describe('ChatSend', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -14,11 +14,10 @@ import { ChatActions } from '../chat_actions';
|
|||
import { PromptTextArea } from '../prompt_textarea';
|
||||
import { useAutosizeTextArea } from './use_autosize_textarea';
|
||||
|
||||
export interface Props extends Omit<UseChatSend, 'abortStream'> {
|
||||
export interface Props extends Omit<UseChatSend, 'abortStream' | 'handleOnChatCleared'> {
|
||||
isDisabled: boolean;
|
||||
shouldRefocusPrompt: boolean;
|
||||
userPrompt: string | null;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,12 +25,10 @@ export interface Props extends Omit<UseChatSend, 'abortStream'> {
|
|||
* Allows the user to clear the chat and switch between different system prompts.
|
||||
*/
|
||||
export const ChatSend: React.FC<Props> = ({
|
||||
handleOnChatCleared,
|
||||
handlePromptChange,
|
||||
handleSendMessage,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
isFlyoutMode,
|
||||
shouldRefocusPrompt,
|
||||
userPrompt,
|
||||
}) => {
|
||||
|
@ -58,7 +55,7 @@ export const ChatSend: React.FC<Props> = ({
|
|||
return (
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
alignItems={isFlyoutMode ? 'flexEnd' : 'flexStart'}
|
||||
alignItems={'flexEnd'}
|
||||
css={css`
|
||||
position: relative;
|
||||
`}
|
||||
|
@ -74,32 +71,21 @@ export const ChatSend: React.FC<Props> = ({
|
|||
handlePromptChange={handlePromptChange}
|
||||
value={promptValue}
|
||||
isDisabled={isDisabled}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
css={
|
||||
isFlyoutMode
|
||||
? css`
|
||||
right: 0;
|
||||
position: absolute;
|
||||
margin-right: ${euiThemeVars.euiSizeS};
|
||||
margin-bottom: ${euiThemeVars.euiSizeS};
|
||||
`
|
||||
: css`
|
||||
left: -34px;
|
||||
position: relative;
|
||||
top: 11px;
|
||||
`
|
||||
}
|
||||
css={css`
|
||||
right: 0;
|
||||
position: absolute;
|
||||
margin-right: ${euiThemeVars.euiSizeS};
|
||||
margin-bottom: ${euiThemeVars.euiSizeS};
|
||||
`}
|
||||
grow={false}
|
||||
>
|
||||
<ChatActions
|
||||
onChatCleared={handleOnChatCleared}
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
onSendMessage={onSendMessage}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
promptValue={promptValue}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -21,7 +21,6 @@ jest.mock('../use_conversation');
|
|||
jest.mock('../../..');
|
||||
|
||||
const setEditingSystemPromptId = jest.fn();
|
||||
const setPromptTextPreview = jest.fn();
|
||||
const setSelectedPromptContexts = jest.fn();
|
||||
const setUserPrompt = jest.fn();
|
||||
const sendMessage = jest.fn();
|
||||
|
@ -43,7 +42,6 @@ export const testProps: UseChatSendProps = {
|
|||
} as unknown as HttpSetup,
|
||||
editingSystemPromptId: defaultSystemPrompt.id,
|
||||
setEditingSystemPromptId,
|
||||
setPromptTextPreview,
|
||||
setSelectedPromptContexts,
|
||||
setUserPrompt,
|
||||
setCurrentConversation,
|
||||
|
@ -75,7 +73,6 @@ describe('use chat send', () => {
|
|||
});
|
||||
result.current.handleOnChatCleared();
|
||||
expect(clearConversation).toHaveBeenCalled();
|
||||
expect(setPromptTextPreview).toHaveBeenCalledWith('');
|
||||
expect(setUserPrompt).toHaveBeenCalledWith('');
|
||||
expect(setSelectedPromptContexts).toHaveBeenCalledWith({});
|
||||
await waitFor(() => {
|
||||
|
@ -89,7 +86,6 @@ describe('use chat send', () => {
|
|||
wrapper: TestProviders,
|
||||
});
|
||||
result.current.handlePromptChange('new prompt');
|
||||
expect(setPromptTextPreview).toHaveBeenCalledWith('new prompt');
|
||||
expect(setUserPrompt).toHaveBeenCalledWith('new prompt');
|
||||
});
|
||||
it('handleSendMessage sends message with context prompt when a valid prompt text is provided', async () => {
|
||||
|
|
|
@ -25,7 +25,6 @@ export interface UseChatSendProps {
|
|||
http: HttpSetup;
|
||||
selectedPromptContexts: Record<string, SelectedPromptContext>;
|
||||
setEditingSystemPromptId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
setPromptTextPreview: React.Dispatch<React.SetStateAction<string>>;
|
||||
setSelectedPromptContexts: React.Dispatch<
|
||||
React.SetStateAction<Record<string, SelectedPromptContext>>
|
||||
>;
|
||||
|
@ -54,7 +53,6 @@ export const useChatSend = ({
|
|||
http,
|
||||
selectedPromptContexts,
|
||||
setEditingSystemPromptId,
|
||||
setPromptTextPreview,
|
||||
setSelectedPromptContexts,
|
||||
setUserPrompt,
|
||||
setCurrentConversation,
|
||||
|
@ -69,7 +67,6 @@ export const useChatSend = ({
|
|||
const { clearConversation, removeLastMessage } = useConversation();
|
||||
|
||||
const handlePromptChange = (prompt: string) => {
|
||||
setPromptTextPreview(prompt);
|
||||
setUserPrompt(prompt);
|
||||
};
|
||||
|
||||
|
@ -120,7 +117,6 @@ export const useChatSend = ({
|
|||
|
||||
// Reset prompt context selection and preview before sending:
|
||||
setSelectedPromptContexts({});
|
||||
setPromptTextPreview('');
|
||||
|
||||
const rawResponse = await sendMessage({
|
||||
apiConfig: currentConversation.apiConfig,
|
||||
|
@ -168,7 +164,6 @@ export const useChatSend = ({
|
|||
selectedPromptContexts,
|
||||
sendMessage,
|
||||
setCurrentConversation,
|
||||
setPromptTextPreview,
|
||||
setSelectedPromptContexts,
|
||||
toasts,
|
||||
]
|
||||
|
@ -214,7 +209,6 @@ export const useChatSend = ({
|
|||
conversation: currentConversation,
|
||||
})?.id;
|
||||
|
||||
setPromptTextPreview('');
|
||||
setUserPrompt('');
|
||||
setSelectedPromptContexts({});
|
||||
if (currentConversation) {
|
||||
|
@ -230,7 +224,6 @@ export const useChatSend = ({
|
|||
currentConversation,
|
||||
setCurrentConversation,
|
||||
setEditingSystemPromptId,
|
||||
setPromptTextPreview,
|
||||
setSelectedPromptContexts,
|
||||
setUserPrompt,
|
||||
]);
|
||||
|
|
|
@ -33,7 +33,6 @@ const mockPromptContexts: Record<string, PromptContext> = {
|
|||
const defaultProps = {
|
||||
anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] },
|
||||
promptContexts: mockPromptContexts,
|
||||
isFlyoutMode: false,
|
||||
};
|
||||
|
||||
describe('ContextPills', () => {
|
||||
|
|
|
@ -5,20 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
|
||||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
|
||||
import { sortBy } from 'lodash/fp';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { FindAnonymizationFieldsResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.gen';
|
||||
import { getNewSelectedPromptContext } from '../../data_anonymization/get_new_selected_prompt_context';
|
||||
import type { PromptContext, SelectedPromptContext } from '../prompt_context/types';
|
||||
|
||||
const PillButton = styled(EuiButton)`
|
||||
margin-right: ${({ theme }) => theme.eui.euiSizeXS};
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
anonymizationFields: FindAnonymizationFieldsResponse;
|
||||
promptContexts: Record<string, PromptContext>;
|
||||
|
@ -26,7 +20,6 @@ interface Props {
|
|||
setSelectedPromptContexts: React.Dispatch<
|
||||
React.SetStateAction<Record<string, SelectedPromptContext>>
|
||||
>;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
|
||||
const ContextPillsComponent: React.FC<Props> = ({
|
||||
|
@ -34,7 +27,6 @@ const ContextPillsComponent: React.FC<Props> = ({
|
|||
promptContexts,
|
||||
selectedPromptContexts,
|
||||
setSelectedPromptContexts,
|
||||
isFlyoutMode,
|
||||
}) => {
|
||||
const sortedPromptContexts = useMemo(
|
||||
() => sortBy('description', Object.values(promptContexts)),
|
||||
|
@ -63,7 +55,7 @@ const ContextPillsComponent: React.FC<Props> = ({
|
|||
{sortedPromptContexts.map(({ description, id, tooltip }) => {
|
||||
// Workaround for known issue where tooltip won't dismiss after button state is changed once clicked
|
||||
// See: https://github.com/elastic/eui/issues/6488#issuecomment-1379656704
|
||||
const button = isFlyoutMode ? (
|
||||
const button = (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj={`pillButton-${id}`}
|
||||
disabled={selectedPromptContexts[id] != null}
|
||||
|
@ -74,16 +66,6 @@ const ContextPillsComponent: React.FC<Props> = ({
|
|||
>
|
||||
{description}
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<PillButton
|
||||
data-test-subj={`pillButton-${id}`}
|
||||
disabled={selectedPromptContexts[id] != null}
|
||||
iconSide="left"
|
||||
iconType="plus"
|
||||
onClick={() => selectPromptContext(id)}
|
||||
>
|
||||
{description}
|
||||
</PillButton>
|
||||
);
|
||||
return (
|
||||
<EuiFlexItem grow={false} key={id}>
|
||||
|
|
|
@ -35,7 +35,6 @@ interface Props {
|
|||
selectedConversationId: string | undefined;
|
||||
onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void;
|
||||
onConversationDeleted: (conversationId: string) => void;
|
||||
shouldDisableKeyboardShortcut?: () => boolean;
|
||||
isDisabled?: boolean;
|
||||
conversations: Record<string, Conversation>;
|
||||
allPrompts: PromptResponse[];
|
||||
|
@ -65,7 +64,6 @@ export const ConversationSelector: React.FC<Props> = React.memo(
|
|||
defaultConnector,
|
||||
onConversationSelected,
|
||||
onConversationDeleted,
|
||||
shouldDisableKeyboardShortcut = () => false,
|
||||
isDisabled = false,
|
||||
conversations,
|
||||
allPrompts,
|
||||
|
@ -199,9 +197,8 @@ export const ConversationSelector: React.FC<Props> = React.memo(
|
|||
|
||||
const renderOption: (
|
||||
option: ConversationSelectorOption,
|
||||
searchValue: string,
|
||||
OPTION_CONTENT_CLASSNAME: string
|
||||
) => React.ReactNode = (option, searchValue, contentClassName) => {
|
||||
searchValue: string
|
||||
) => React.ReactNode = (option, searchValue) => {
|
||||
const { label, id, value } = option;
|
||||
|
||||
return (
|
||||
|
|
|
@ -27,7 +27,6 @@ interface Props {
|
|||
onConversationDeleted: (conversationTitle: string) => void;
|
||||
onConversationSelectionChange: (conversation?: Conversation | string) => void;
|
||||
selectedConversationTitle: string;
|
||||
shouldDisableKeyboardShortcut?: () => boolean;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
|
@ -62,7 +61,6 @@ export const ConversationSelectorSettings: React.FC<Props> = React.memo(
|
|||
onConversationSelectionChange,
|
||||
selectedConversationTitle,
|
||||
isDisabled,
|
||||
shouldDisableKeyboardShortcut = () => false,
|
||||
}) => {
|
||||
const conversationTitles = useMemo(
|
||||
() => Object.values(conversations).map((c) => c.title),
|
||||
|
|
|
@ -49,7 +49,6 @@ export interface ConversationSettingsProps {
|
|||
React.SetStateAction<ConversationsBulkActions>
|
||||
>;
|
||||
isDisabled?: boolean;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,7 +65,6 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
|
|||
conversationSettings,
|
||||
http,
|
||||
isDisabled = false,
|
||||
isFlyoutMode,
|
||||
setAssistantStreamingEnabled,
|
||||
setConversationSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
|
@ -127,7 +125,6 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
|
|||
conversationsSettingsBulkActions={conversationsSettingsBulkActions}
|
||||
http={http}
|
||||
isDisabled={isDisabled}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
selectedConversation={selectedConversationWithApiConfig}
|
||||
setConversationSettings={setConversationSettings}
|
||||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
|
|
|
@ -31,7 +31,6 @@ export interface ConversationSettingsEditorProps {
|
|||
conversationsSettingsBulkActions: ConversationsBulkActions;
|
||||
http: HttpSetup;
|
||||
isDisabled?: boolean;
|
||||
isFlyoutMode: boolean;
|
||||
selectedConversation?: Conversation;
|
||||
setConversationSettings: React.Dispatch<React.SetStateAction<Record<string, Conversation>>>;
|
||||
setConversationsSettingsBulkActions: React.Dispatch<
|
||||
|
@ -49,7 +48,6 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
|
|||
conversationSettings,
|
||||
http,
|
||||
isDisabled = false,
|
||||
isFlyoutMode,
|
||||
setConversationSettings,
|
||||
conversationsSettingsBulkActions,
|
||||
setConversationsSettingsBulkActions,
|
||||
|
@ -272,14 +270,11 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
|
|||
allPrompts={allSystemPrompts}
|
||||
compressed
|
||||
conversation={selectedConversation}
|
||||
isEditing={true}
|
||||
isDisabled={isDisabled}
|
||||
onSystemPromptSelectionChange={handleOnSystemPromptSelectionChange}
|
||||
selectedPrompt={selectedSystemPrompt}
|
||||
showTitles={true}
|
||||
isSettingsModalVisible={true}
|
||||
setIsSettingsModalVisible={noop} // noop, already in settings
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
|
@ -304,7 +299,6 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
|
|||
isDisabled={isDisabled}
|
||||
onConnectorSelectionChange={handleOnConnectorSelectionChange}
|
||||
selectedConnectorId={selectedConnector?.id}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ interface Props {
|
|||
defaultConnector?: AIConnector;
|
||||
handleSave: (shouldRefetchConversation?: boolean) => void;
|
||||
isDisabled?: boolean;
|
||||
isFlyoutMode: boolean;
|
||||
onCancelClick: () => void;
|
||||
setAssistantStreamingEnabled: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setConversationSettings: React.Dispatch<React.SetStateAction<Record<string, Conversation>>>;
|
||||
|
@ -62,7 +61,6 @@ const ConversationSettingsManagementComponent: React.FC<Props> = ({
|
|||
conversationsLoaded,
|
||||
handleSave,
|
||||
isDisabled,
|
||||
isFlyoutMode,
|
||||
onSelectedConversationChange,
|
||||
onCancelClick,
|
||||
selectedConversation,
|
||||
|
@ -221,7 +219,6 @@ const ConversationSettingsManagementComponent: React.FC<Props> = ({
|
|||
conversationsSettingsBulkActions={conversationsSettingsBulkActions}
|
||||
http={http}
|
||||
isDisabled={isDisabled}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
selectedConversation={selectedConversation}
|
||||
setConversationSettings={setConversationSettings}
|
||||
setConversationsSettingsBulkActions={setConversationsSettingsBulkActions}
|
||||
|
|
|
@ -32,7 +32,7 @@ const TitleFieldComponent = ({ conversationIds, euiFieldProps }: TitleFieldProps
|
|||
),
|
||||
value: true,
|
||||
},
|
||||
validate: (text: string) => {
|
||||
validate: () => {
|
||||
if (conversationIds?.includes(value)) {
|
||||
return i18n.translate(
|
||||
'xpack.elasticAssistant.conversationSidepanel.titleField.uniqueTitle',
|
||||
|
|
|
@ -22,12 +22,11 @@ const defaultConversation = {
|
|||
replacements: {},
|
||||
title: 'conversation_id',
|
||||
};
|
||||
const isFlyoutMode = false;
|
||||
describe('helpers', () => {
|
||||
describe('isAssistantEnabled = false', () => {
|
||||
const isAssistantEnabled = false;
|
||||
it('When no conversation history, return only enterprise messaging', () => {
|
||||
const result = getBlockBotConversation(defaultConversation, isAssistantEnabled, isFlyoutMode);
|
||||
const result = getBlockBotConversation(defaultConversation, isAssistantEnabled);
|
||||
expect(result.messages).toEqual(enterpriseMessaging);
|
||||
expect(result.messages.length).toEqual(1);
|
||||
});
|
||||
|
@ -47,7 +46,7 @@ describe('helpers', () => {
|
|||
},
|
||||
],
|
||||
};
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled, isFlyoutMode);
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled);
|
||||
expect(result.messages.length).toEqual(2);
|
||||
});
|
||||
|
||||
|
@ -56,7 +55,7 @@ describe('helpers', () => {
|
|||
...defaultConversation,
|
||||
messages: enterpriseMessaging,
|
||||
};
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled, isFlyoutMode);
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled);
|
||||
expect(result.messages.length).toEqual(1);
|
||||
expect(result.messages).toEqual(enterpriseMessaging);
|
||||
});
|
||||
|
@ -77,7 +76,7 @@ describe('helpers', () => {
|
|||
},
|
||||
],
|
||||
};
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled, isFlyoutMode);
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled);
|
||||
expect(result.messages.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
@ -85,8 +84,8 @@ describe('helpers', () => {
|
|||
describe('isAssistantEnabled = true', () => {
|
||||
const isAssistantEnabled = true;
|
||||
it('when no conversation history, returns the welcome conversation', () => {
|
||||
const result = getBlockBotConversation(defaultConversation, isAssistantEnabled, isFlyoutMode);
|
||||
expect(result.messages.length).toEqual(3);
|
||||
const result = getBlockBotConversation(defaultConversation, isAssistantEnabled);
|
||||
expect(result.messages.length).toEqual(0);
|
||||
});
|
||||
it('returns a conversation history with the welcome conversation appended', () => {
|
||||
const conversation = {
|
||||
|
@ -103,8 +102,8 @@ describe('helpers', () => {
|
|||
},
|
||||
],
|
||||
};
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled, isFlyoutMode);
|
||||
expect(result.messages.length).toEqual(4);
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled);
|
||||
expect(result.messages.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { AIConnector } from '../connectorland/connector_selector';
|
|||
import { FetchConnectorExecuteResponse, FetchConversationsResponse } from './api';
|
||||
import { Conversation } from '../..';
|
||||
import type { ClientMessage } from '../assistant_context/types';
|
||||
import { enterpriseMessaging, WELCOME_CONVERSATION } from './use_conversation/sample_conversations';
|
||||
import { enterpriseMessaging } from './use_conversation/sample_conversations';
|
||||
|
||||
export const getMessageFromRawResponse = (
|
||||
rawResponse: FetchConnectorExecuteResponse
|
||||
|
@ -57,8 +57,7 @@ export const mergeBaseWithPersistedConversations = (
|
|||
|
||||
export const getBlockBotConversation = (
|
||||
conversation: Conversation,
|
||||
isAssistantEnabled: boolean,
|
||||
isFlyoutMode: boolean
|
||||
isAssistantEnabled: boolean
|
||||
): Conversation => {
|
||||
if (!isAssistantEnabled) {
|
||||
if (
|
||||
|
@ -76,7 +75,7 @@ export const getBlockBotConversation = (
|
|||
|
||||
return {
|
||||
...conversation,
|
||||
messages: [...conversation.messages, ...(!isFlyoutMode ? WELCOME_CONVERSATION.messages : [])],
|
||||
messages: conversation.messages,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -7,12 +7,11 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { Assistant } from '.';
|
||||
import type { IHttpFetchError } from '@kbn/core/public';
|
||||
|
||||
import { useLoadConnectors } from '../connectorland/use_load_connectors';
|
||||
import { useConnectorSetup } from '../connectorland/connector_setup';
|
||||
|
||||
import { DefinedUseQueryResult, UseQueryResult } from '@tanstack/react-query';
|
||||
|
||||
|
@ -40,7 +39,7 @@ jest.mock('./use_conversation');
|
|||
const renderAssistant = (extraProps = {}, providerProps = {}) =>
|
||||
render(
|
||||
<TestProviders>
|
||||
<Assistant chatHistoryVisible={false} setChatHistoryVisible={jest.fn()} {...extraProps} />
|
||||
<Assistant chatHistoryVisible={true} setChatHistoryVisible={jest.fn()} {...extraProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -63,11 +62,12 @@ const mockData = {
|
|||
},
|
||||
};
|
||||
const mockDeleteConvo = jest.fn();
|
||||
const mockGetDefaultConversation = jest.fn().mockReturnValue(mockData.welcome_id);
|
||||
const clearConversation = jest.fn();
|
||||
const mockUseConversation = {
|
||||
clearConversation: clearConversation.mockResolvedValue(mockData.welcome_id),
|
||||
getConversation: jest.fn(),
|
||||
getDefaultConversation: jest.fn().mockReturnValue(mockData.welcome_id),
|
||||
getDefaultConversation: mockGetDefaultConversation,
|
||||
deleteConversation: mockDeleteConvo,
|
||||
setApiConfig: jest.fn().mockResolvedValue({}),
|
||||
};
|
||||
|
@ -83,10 +83,6 @@ describe('Assistant', () => {
|
|||
persistToLocalStorage = jest.fn();
|
||||
persistToSessionStorage = jest.fn();
|
||||
(useConversation as jest.Mock).mockReturnValue(mockUseConversation);
|
||||
jest.mocked(useConnectorSetup).mockReturnValue({
|
||||
comments: [],
|
||||
prompt: <></>,
|
||||
});
|
||||
|
||||
jest.mocked(PromptEditor).mockReturnValue(null);
|
||||
jest.mocked(QuickPrompts).mockReturnValue(null);
|
||||
|
@ -221,22 +217,21 @@ describe('Assistant', () => {
|
|||
|
||||
it('should delete conversation when delete button is clicked', async () => {
|
||||
renderAssistant();
|
||||
await act(async () => {
|
||||
fireEvent.click(
|
||||
within(screen.getByTestId('conversation-selector')).getByTestId(
|
||||
'comboBoxToggleListButton'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const deleteButton = screen.getAllByTestId('delete-option')[0];
|
||||
await act(async () => {
|
||||
fireEvent.click(deleteButton);
|
||||
});
|
||||
expect(mockDeleteConvo).toHaveBeenCalledWith(mockData.welcome_id.id);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByTestId('confirmModalConfirmButton'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteConvo).toHaveBeenCalledWith(mockData.electric_sheep_id.id);
|
||||
});
|
||||
});
|
||||
it('should refetchConversationsState after clear chat history button click', async () => {
|
||||
renderAssistant({ isFlyoutMode: true });
|
||||
renderAssistant();
|
||||
fireEvent.click(screen.getByTestId('chat-context-menu'));
|
||||
fireEvent.click(screen.getByTestId('clear-chat'));
|
||||
fireEvent.click(screen.getByTestId('confirmModalConfirmButton'));
|
||||
|
@ -259,7 +254,7 @@ describe('Assistant', () => {
|
|||
|
||||
expect(persistToLocalStorage).toHaveBeenLastCalledWith(mockData.welcome_id.id);
|
||||
|
||||
const previousConversationButton = screen.getByLabelText('Previous conversation');
|
||||
const previousConversationButton = await screen.findByText(mockData.electric_sheep_id.title);
|
||||
|
||||
expect(previousConversationButton).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
|
@ -295,13 +290,13 @@ describe('Assistant', () => {
|
|||
isFetched: true,
|
||||
} as unknown as DefinedUseQueryResult<Record<string, Conversation>, unknown>);
|
||||
|
||||
const { getByLabelText } = renderAssistant();
|
||||
const { findByText } = renderAssistant();
|
||||
|
||||
expect(persistToLocalStorage).toHaveBeenCalled();
|
||||
|
||||
expect(persistToLocalStorage).toHaveBeenLastCalledWith(mockData.welcome_id.id);
|
||||
|
||||
const previousConversationButton = getByLabelText('Previous conversation');
|
||||
const previousConversationButton = await findByText(mockData.electric_sheep_id.title);
|
||||
|
||||
expect(previousConversationButton).toBeInTheDocument();
|
||||
|
||||
|
@ -321,7 +316,7 @@ describe('Assistant', () => {
|
|||
renderAssistant({ setConversationTitle });
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByLabelText('Previous conversation'));
|
||||
fireEvent.click(await screen.findByText(mockData.electric_sheep_id.title));
|
||||
});
|
||||
|
||||
expect(setConversationTitle).toHaveBeenLastCalledWith('electric sheep');
|
||||
|
@ -351,7 +346,7 @@ describe('Assistant', () => {
|
|||
} as unknown as DefinedUseQueryResult<Record<string, Conversation>, unknown>);
|
||||
renderAssistant();
|
||||
|
||||
const previousConversationButton = screen.getByLabelText('Previous conversation');
|
||||
const previousConversationButton = await screen.findByText('updated title');
|
||||
await act(async () => {
|
||||
fireEvent.click(previousConversationButton);
|
||||
});
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
/* eslint-disable complexity */
|
||||
|
||||
import React, {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
|
@ -26,9 +24,6 @@ import {
|
|||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiFlyoutBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalBody,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
|
@ -43,7 +38,6 @@ import { PromptTypeEnum } from '@kbn/elastic-assistant-common/impl/schemas/promp
|
|||
import { useChatSend } from './chat_send/use_chat_send';
|
||||
import { ChatSend } from './chat_send';
|
||||
import { BlockBotCallToAction } from './block_bot/cta';
|
||||
import { AssistantHeader } from './assistant_header';
|
||||
import { WELCOME_CONVERSATION_TITLE } from './use_conversation/translations';
|
||||
import {
|
||||
getDefaultConnector,
|
||||
|
@ -57,16 +51,15 @@ import { getNewSelectedPromptContext } from '../data_anonymization/get_new_selec
|
|||
import type { PromptContext, SelectedPromptContext } from './prompt_context/types';
|
||||
import { useConversation } from './use_conversation';
|
||||
import { CodeBlockDetails, getDefaultSystemPrompt } from './use_conversation/helpers';
|
||||
import { PromptEditor } from './prompt_editor';
|
||||
import { QuickPrompts } from './quick_prompts/quick_prompts';
|
||||
import { useLoadConnectors } from '../connectorland/use_load_connectors';
|
||||
import { useConnectorSetup } from '../connectorland/connector_setup';
|
||||
import { ConnectorSetup } from '../connectorland/connector_setup';
|
||||
import { ConnectorMissingCallout } from '../connectorland/connector_missing_callout';
|
||||
import { ConversationSidePanel } from './conversations/conversation_sidepanel';
|
||||
import { NEW_CHAT } from './conversations/conversation_sidepanel/translations';
|
||||
import { SystemPrompt } from './prompt_editor/system_prompt';
|
||||
import { SelectedPromptContexts } from './prompt_editor/selected_prompt_contexts';
|
||||
import { AssistantHeaderFlyout } from './assistant_header/assistant_header_flyout';
|
||||
import { AssistantHeader } from './assistant_header';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const CONVERSATION_SIDE_PANEL_WIDTH = 220;
|
||||
|
@ -77,17 +70,12 @@ const CommentContainer = styled('span')`
|
|||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const ModalPromptEditorWrapper = styled.div`
|
||||
margin-right: 24px;
|
||||
`;
|
||||
|
||||
import {
|
||||
FetchConversationsResponse,
|
||||
useFetchCurrentUserConversations,
|
||||
CONVERSATIONS_QUERY_KEYS,
|
||||
} from './api/conversations/use_fetch_current_user_conversations';
|
||||
import { Conversation } from '../assistant_context/types';
|
||||
import { clearPresentationData } from '../connectorland/connector_setup/helpers';
|
||||
import { getGenAiConfig } from '../connectorland/helpers';
|
||||
import { AssistantAnimatedIcon } from './assistant_animated_icon';
|
||||
import { useFetchAnonymizationFields } from './api/anonymization_fields/use_fetch_anonymization_fields';
|
||||
|
@ -102,7 +90,6 @@ export interface Props {
|
|||
showTitle?: boolean;
|
||||
setConversationTitle?: Dispatch<SetStateAction<string>>;
|
||||
onCloseFlyout?: () => void;
|
||||
isFlyoutMode?: boolean;
|
||||
chatHistoryVisible?: boolean;
|
||||
setChatHistoryVisible?: Dispatch<SetStateAction<boolean>>;
|
||||
currentUserAvatar?: UserAvatar;
|
||||
|
@ -120,7 +107,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
showTitle = true,
|
||||
setConversationTitle,
|
||||
onCloseFlyout,
|
||||
isFlyoutMode = false,
|
||||
chatHistoryVisible,
|
||||
setChatHistoryVisible,
|
||||
currentUserAvatar,
|
||||
|
@ -129,14 +115,12 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
assistantTelemetry,
|
||||
augmentMessageCodeBlocks,
|
||||
assistantAvailability: { isAssistantEnabled },
|
||||
docLinks,
|
||||
getComments,
|
||||
http,
|
||||
knowledgeBase: { isEnabledKnowledgeBase, isEnabledRAGAlerts },
|
||||
promptContexts,
|
||||
setLastConversationId,
|
||||
getLastConversationId,
|
||||
title,
|
||||
baseConversations,
|
||||
} = useAssistantContext();
|
||||
|
||||
|
@ -251,7 +235,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
nextConversation?.id !== '' ? nextConversation?.id : nextConversation?.title
|
||||
]) ??
|
||||
conversations[WELCOME_CONVERSATION_TITLE] ??
|
||||
getDefaultConversation({ cTitle: WELCOME_CONVERSATION_TITLE, isFlyoutMode });
|
||||
getDefaultConversation({ cTitle: WELCOME_CONVERSATION_TITLE });
|
||||
|
||||
if (
|
||||
prev &&
|
||||
|
@ -278,7 +262,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
getDefaultConversation,
|
||||
getLastConversationId,
|
||||
isAssistantEnabled,
|
||||
isFlyoutMode,
|
||||
]);
|
||||
|
||||
// Welcome setup state
|
||||
|
@ -295,10 +278,8 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
// Welcome conversation is a special 'setup' case when no connector exists, mostly extracted to `ConnectorSetup` component,
|
||||
// but currently a bit of state is littered throughout the assistant component. TODO: clean up/isolate this state
|
||||
const blockBotConversation = useMemo(
|
||||
() =>
|
||||
currentConversation &&
|
||||
getBlockBotConversation(currentConversation, isAssistantEnabled, isFlyoutMode),
|
||||
[currentConversation, isAssistantEnabled, isFlyoutMode]
|
||||
() => currentConversation && getBlockBotConversation(currentConversation, isAssistantEnabled),
|
||||
[currentConversation, isAssistantEnabled]
|
||||
);
|
||||
|
||||
// Settings modal state (so it isn't shared between assistant instances like Timeline)
|
||||
|
@ -325,7 +306,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
setLastConversationId,
|
||||
]);
|
||||
|
||||
const [promptTextPreview, setPromptTextPreview] = useState<string>('');
|
||||
const [autoPopulatedOnce, setAutoPopulatedOnce] = useState<boolean>(false);
|
||||
const [userPrompt, setUserPrompt] = useState<string | null>(null);
|
||||
|
||||
|
@ -398,16 +378,10 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
// when scrollHeight changes, parent is scrolled to bottom
|
||||
parent.scrollTop = parent.scrollHeight;
|
||||
|
||||
if (isFlyoutMode) {
|
||||
(
|
||||
commentsContainerRef.current?.childNodes[0].childNodes[0] as HTMLElement
|
||||
).lastElementChild?.scrollIntoView();
|
||||
}
|
||||
(
|
||||
commentsContainerRef.current?.childNodes[0].childNodes[0] as HTMLElement
|
||||
).lastElementChild?.scrollIntoView();
|
||||
});
|
||||
|
||||
const getWrapper = (children: React.ReactNode, isCommentContainer: boolean) =>
|
||||
isCommentContainer ? <span ref={commentsContainerRef}>{children}</span> : <>{children}</>;
|
||||
|
||||
// End Scrolling
|
||||
|
||||
const selectedSystemPrompt = useMemo(
|
||||
|
@ -446,17 +420,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
[allSystemPrompts, refetchCurrentConversation, refetchResults]
|
||||
);
|
||||
|
||||
const { comments: connectorComments, prompt: connectorPrompt } = useConnectorSetup({
|
||||
isFlyoutMode,
|
||||
conversation: blockBotConversation,
|
||||
onConversationUpdate: handleOnConversationSelected,
|
||||
onSetupComplete: () => {
|
||||
if (currentConversation) {
|
||||
setCurrentConversation(clearPresentationData(currentConversation));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const handleOnConversationDeleted = useCallback(
|
||||
async (cTitle: string) => {
|
||||
await deleteConversation(conversations[cTitle].id);
|
||||
|
@ -538,14 +501,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
isFetchedAnonymizationFields,
|
||||
]);
|
||||
|
||||
useEffect(() => {}, [
|
||||
areConnectorsFetched,
|
||||
connectors,
|
||||
conversationsLoaded,
|
||||
currentConversation,
|
||||
isLoading,
|
||||
]);
|
||||
|
||||
const createCodeBlockPortals = useCallback(
|
||||
() =>
|
||||
messageCodeBlocks?.map((codeBlocks: CodeBlockDetails[], i: number) => {
|
||||
|
@ -576,7 +531,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
} = useChatSend({
|
||||
allSystemPrompts,
|
||||
currentConversation,
|
||||
setPromptTextPreview,
|
||||
setUserPrompt,
|
||||
editingSystemPromptId,
|
||||
http,
|
||||
|
@ -601,7 +555,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
[currentConversation, handleSendMessage, refetchResults]
|
||||
);
|
||||
|
||||
const chatbotComments = useMemo(
|
||||
const comments = useMemo(
|
||||
() => (
|
||||
<>
|
||||
<EuiCommentList
|
||||
|
@ -615,53 +569,20 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
isFetchingResponse: isLoadingChatSend,
|
||||
setIsStreaming,
|
||||
currentUserAvatar,
|
||||
isFlyoutMode,
|
||||
})}
|
||||
{...(!isFlyoutMode
|
||||
? {
|
||||
css: css`
|
||||
margin-right: ${euiThemeVars.euiSizeL};
|
||||
// Avoid comments going off the flyout
|
||||
css={css`
|
||||
padding-bottom: ${euiThemeVars.euiSizeL};
|
||||
|
||||
> li > div:nth-child(2) {
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
}
|
||||
: {
|
||||
// Avoid comments going off the flyout
|
||||
css: css`
|
||||
padding-bottom: ${euiThemeVars.euiSizeL};
|
||||
|
||||
> li > div:nth-child(2) {
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
})}
|
||||
> li > div:nth-child(2) {
|
||||
overflow: hidden;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
|
||||
{currentConversation?.messages.length !== 0 && selectedPromptContextsCount > 0 && (
|
||||
<EuiSpacer size={'m'} />
|
||||
)}
|
||||
|
||||
{!isFlyoutMode &&
|
||||
(currentConversation?.messages.length === 0 || selectedPromptContextsCount > 0) && (
|
||||
<ModalPromptEditorWrapper>
|
||||
<PromptEditor
|
||||
conversation={currentConversation}
|
||||
editingSystemPromptId={editingSystemPromptId}
|
||||
isNewConversation={isNewConversation}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
promptContexts={promptContexts}
|
||||
promptTextPreview={promptTextPreview}
|
||||
onSystemPromptSelectionChange={handleOnSystemPromptSelectionChange}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
/>
|
||||
</ModalPromptEditorWrapper>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
[
|
||||
|
@ -675,34 +596,10 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
isEnabledRAGAlerts,
|
||||
isLoadingChatSend,
|
||||
currentUserAvatar,
|
||||
isFlyoutMode,
|
||||
selectedPromptContextsCount,
|
||||
editingSystemPromptId,
|
||||
isNewConversation,
|
||||
isSettingsModalVisible,
|
||||
promptContexts,
|
||||
promptTextPreview,
|
||||
handleOnSystemPromptSelectionChange,
|
||||
selectedPromptContexts,
|
||||
allSystemPrompts,
|
||||
]
|
||||
);
|
||||
|
||||
const comments = useMemo(() => {
|
||||
if (isDisabled && !isFlyoutMode) {
|
||||
return (
|
||||
<EuiCommentList
|
||||
comments={connectorComments}
|
||||
css={css`
|
||||
margin-right: 20px;
|
||||
`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return chatbotComments;
|
||||
}, [isDisabled, isFlyoutMode, chatbotComments, connectorComments]);
|
||||
|
||||
const trackPrompt = useCallback(
|
||||
(promptTitle: string) => {
|
||||
if (currentConversation?.title) {
|
||||
|
@ -800,19 +697,14 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
textAlign="center"
|
||||
color={euiThemeVars.euiColorMediumShade}
|
||||
size="xs"
|
||||
css={
|
||||
isFlyoutMode
|
||||
? css`
|
||||
margin: 0 ${euiThemeVars.euiSizeL} ${euiThemeVars.euiSizeM}
|
||||
${euiThemeVars.euiSizeL};
|
||||
`
|
||||
: {}
|
||||
}
|
||||
css={css`
|
||||
margin: 0 ${euiThemeVars.euiSizeL} ${euiThemeVars.euiSizeM} ${euiThemeVars.euiSizeL};
|
||||
`}
|
||||
>
|
||||
{i18n.DISCLAIMER}
|
||||
</EuiText>
|
||||
),
|
||||
[isFlyoutMode, isNewConversation]
|
||||
[isNewConversation]
|
||||
);
|
||||
|
||||
const flyoutBodyContent = useMemo(() => {
|
||||
|
@ -842,7 +734,10 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} data-test-subj="connector-prompt">
|
||||
{connectorPrompt}
|
||||
<ConnectorSetup
|
||||
conversation={blockBotConversation}
|
||||
onConversationUpdate={handleOnConversationSelected}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
|
@ -879,7 +774,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
onSystemPromptSelectionChange={handleOnSystemPromptSelectionChange}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -905,328 +799,212 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
);
|
||||
}, [
|
||||
allSystemPrompts,
|
||||
blockBotConversation,
|
||||
comments,
|
||||
connectorPrompt,
|
||||
currentConversation,
|
||||
editingSystemPromptId,
|
||||
handleOnConversationSelected,
|
||||
handleOnSystemPromptSelectionChange,
|
||||
isSettingsModalVisible,
|
||||
isWelcomeSetup,
|
||||
]);
|
||||
|
||||
if (isFlyoutMode) {
|
||||
return (
|
||||
<EuiFlexGroup direction={'row'} wrap={false} gutterSize="none">
|
||||
{chatHistoryVisible && (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
inline-size: ${CONVERSATION_SIDE_PANEL_WIDTH}px;
|
||||
border-right: 1px solid ${euiThemeVars.euiColorLightShade};
|
||||
`}
|
||||
>
|
||||
<ConversationSidePanel
|
||||
currentConversation={currentConversation}
|
||||
onConversationSelected={handleOnConversationSelected}
|
||||
conversations={conversations}
|
||||
onConversationDeleted={handleOnConversationDeleted}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
return (
|
||||
<EuiFlexGroup direction={'row'} wrap={false} gutterSize="none">
|
||||
{chatHistoryVisible && (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
inline-size: ${CONVERSATION_SIDE_PANEL_WIDTH}px;
|
||||
border-right: 1px solid ${euiThemeVars.euiColorLightShade};
|
||||
`}
|
||||
>
|
||||
<CommentContainer>
|
||||
<EuiFlexGroup
|
||||
<ConversationSidePanel
|
||||
currentConversation={currentConversation}
|
||||
onConversationSelected={handleOnConversationSelected}
|
||||
conversations={conversations}
|
||||
onConversationDeleted={handleOnConversationDeleted}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<CommentContainer>
|
||||
<EuiFlexGroup
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<AssistantHeader
|
||||
selectedConversation={currentConversation}
|
||||
defaultConnector={defaultConnector}
|
||||
isDisabled={isDisabled || isLoadingChatSend}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onToggleShowAnonymizedValues={onToggleShowAnonymizedValues}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
showAnonymizedValues={showAnonymizedValues}
|
||||
onCloseFlyout={onCloseFlyout}
|
||||
onChatCleared={handleOnChatCleared}
|
||||
chatHistoryVisible={chatHistoryVisible}
|
||||
setChatHistoryVisible={setChatHistoryVisible}
|
||||
onConversationSelected={handleOnConversationSelected}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
|
||||
{/* Create portals for each EuiCodeBlock to add the `Investigate in Timeline` action */}
|
||||
{createCodeBlockPortals()}
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody
|
||||
css={css`
|
||||
max-width: 100%;
|
||||
`}
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<AssistantHeaderFlyout
|
||||
selectedConversation={currentConversation}
|
||||
defaultConnector={defaultConnector}
|
||||
docLinks={docLinks}
|
||||
isDisabled={isDisabled || isLoadingChatSend}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onToggleShowAnonymizedValues={onToggleShowAnonymizedValues}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
showAnonymizedValues={showAnonymizedValues}
|
||||
onCloseFlyout={onCloseFlyout}
|
||||
onChatCleared={handleOnChatCleared}
|
||||
chatHistoryVisible={chatHistoryVisible}
|
||||
setChatHistoryVisible={setChatHistoryVisible}
|
||||
onConversationSelected={handleOnConversationSelected}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
min-height: 100px;
|
||||
flex: 1;
|
||||
|
||||
{/* Create portals for each EuiCodeBlock to add the `Investigate in Timeline` action */}
|
||||
{createCodeBlockPortals()}
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody
|
||||
css={css`
|
||||
min-height: 100px;
|
||||
flex: 1;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
> .euiFlyoutBody__banner {
|
||||
overflow-x: unset;
|
||||
}
|
||||
|
||||
> .euiFlyoutBody__overflowContent {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
`}
|
||||
banner={
|
||||
!isDisabled &&
|
||||
showMissingConnectorCallout &&
|
||||
areConnectorsFetched && (
|
||||
<ConnectorMissingCallout
|
||||
isConnectorConfigured={(connectors?.length ?? 0) > 0}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{!isAssistantEnabled ? (
|
||||
<BlockBotCallToAction
|
||||
connectorPrompt={connectorPrompt}
|
||||
http={http}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
isWelcomeSetup={isWelcomeSetup}
|
||||
/>
|
||||
) : (
|
||||
<EuiFlexGroup direction="column" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>{flyoutBodyContent}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{disclaimer}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter
|
||||
css={css`
|
||||
background: none;
|
||||
border-top: 1px solid ${euiThemeVars.euiColorLightShade};
|
||||
overflow: hidden;
|
||||
max-height: 60%;
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
> .euiFlyoutBody__banner {
|
||||
overflow-x: unset;
|
||||
}
|
||||
|
||||
> .euiFlyoutBody__overflowContent {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
`}
|
||||
banner={
|
||||
!isDisabled &&
|
||||
showMissingConnectorCallout &&
|
||||
areConnectorsFetched && (
|
||||
<ConnectorMissingCallout
|
||||
isConnectorConfigured={(connectors?.length ?? 0) > 0}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{!isAssistantEnabled ? (
|
||||
<BlockBotCallToAction
|
||||
connectorPrompt={
|
||||
<ConnectorSetup
|
||||
conversation={blockBotConversation}
|
||||
onConversationUpdate={handleOnConversationSelected}
|
||||
/>
|
||||
}
|
||||
http={http}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
isWelcomeSetup={isWelcomeSetup}
|
||||
/>
|
||||
) : (
|
||||
<EuiFlexGroup direction="column" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>{flyoutBodyContent}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{disclaimer}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter
|
||||
css={css`
|
||||
background: none;
|
||||
border-top: 1px solid ${euiThemeVars.euiColorLightShade};
|
||||
overflow: hidden;
|
||||
max-height: 60%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`}
|
||||
>
|
||||
<EuiPanel
|
||||
paddingSize="m"
|
||||
hasShadow={false}
|
||||
css={css`
|
||||
overflow: auto;
|
||||
`}
|
||||
>
|
||||
<EuiPanel
|
||||
paddingSize="m"
|
||||
hasShadow={false}
|
||||
css={css`
|
||||
overflow: auto;
|
||||
`}
|
||||
>
|
||||
{!isDisabled &&
|
||||
Object.keys(promptContexts).length !== selectedPromptContextsCount && (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<>
|
||||
<ContextPills
|
||||
anonymizationFields={anonymizationFields}
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
{Object.keys(promptContexts).length > 0 && <EuiSpacer size={'s'} />}
|
||||
</>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{Object.keys(selectedPromptContexts).length ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectedPromptContexts
|
||||
isNewConversation={isNewConversation}
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
currentReplacements={currentConversation?.replacements}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
{!isDisabled &&
|
||||
Object.keys(promptContexts).length !== selectedPromptContextsCount && (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<>
|
||||
<ContextPills
|
||||
anonymizationFields={anonymizationFields}
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
/>
|
||||
{Object.keys(promptContexts).length > 0 && <EuiSpacer size={'s'} />}
|
||||
</>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{Object.keys(selectedPromptContexts).length ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<ChatSend
|
||||
isDisabled={isSendingDisabled}
|
||||
shouldRefocusPrompt={shouldRefocusPrompt}
|
||||
userPrompt={userPrompt}
|
||||
handleOnChatCleared={handleOnChatCleared}
|
||||
handlePromptChange={handlePromptChange}
|
||||
handleSendMessage={handleChatSend}
|
||||
handleRegenerateResponse={handleRegenerateResponse}
|
||||
isLoading={isLoadingChatSend}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
<SelectedPromptContexts
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
currentReplacements={currentConversation?.replacements}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
) : null}
|
||||
|
||||
{!isDisabled && (
|
||||
<EuiPanel
|
||||
css={css`
|
||||
background: ${euiThemeVars.euiColorLightestShade};
|
||||
`}
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
borderRadius="none"
|
||||
>
|
||||
<QuickPrompts
|
||||
setInput={setUserPrompt}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
trackPrompt={trackPrompt}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
allPrompts={allPrompts}
|
||||
<EuiFlexItem grow={false}>
|
||||
<ChatSend
|
||||
isDisabled={isSendingDisabled}
|
||||
shouldRefocusPrompt={shouldRefocusPrompt}
|
||||
userPrompt={userPrompt}
|
||||
handlePromptChange={handlePromptChange}
|
||||
handleSendMessage={handleChatSend}
|
||||
handleRegenerateResponse={handleRegenerateResponse}
|
||||
isLoading={isLoadingChatSend}
|
||||
/>
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</CommentContainer>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
|
||||
return getWrapper(
|
||||
<>
|
||||
<EuiModalHeader
|
||||
css={css`
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
`}
|
||||
>
|
||||
{showTitle && (
|
||||
<AssistantHeader
|
||||
currentConversation={currentConversation}
|
||||
defaultConnector={defaultConnector}
|
||||
docLinks={docLinks}
|
||||
isDisabled={isDisabled}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onConversationSelected={handleOnConversationSelected}
|
||||
onToggleShowAnonymizedValues={onToggleShowAnonymizedValues}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
showAnonymizedValues={showAnonymizedValues}
|
||||
title={title}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
onConversationDeleted={handleOnConversationDeleted}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
allPrompts={allPrompts}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Create portals for each EuiCodeBlock to add the `Investigate in Timeline` action */}
|
||||
{createCodeBlockPortals()}
|
||||
|
||||
{!isDisabled && !isLoadingAnonymizationFields && !isErrorAnonymizationFields && (
|
||||
<>
|
||||
<ContextPills
|
||||
anonymizationFields={anonymizationFields}
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
{Object.keys(promptContexts).length > 0 && <EuiSpacer size={'s'} />}
|
||||
</>
|
||||
)}
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiFlexGroup direction="column" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
{' '}
|
||||
{getWrapper(
|
||||
<>
|
||||
{comments}
|
||||
|
||||
{!isDisabled && showMissingConnectorCallout && areConnectorsFetched && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ConnectorMissingCallout
|
||||
isConnectorConfigured={(connectors?.length ?? 0) > 0}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
{!isDisabled && (
|
||||
<EuiPanel
|
||||
css={css`
|
||||
background: ${euiThemeVars.euiColorLightestShade};
|
||||
`}
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
borderRadius="none"
|
||||
>
|
||||
<QuickPrompts
|
||||
setInput={setUserPrompt}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
trackPrompt={trackPrompt}
|
||||
allPrompts={allPrompts}
|
||||
/>
|
||||
</EuiPanel>
|
||||
)}
|
||||
</>,
|
||||
!embeddedLayout
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{disclaimer}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter
|
||||
css={css`
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
`}
|
||||
>
|
||||
<BlockBotCallToAction
|
||||
connectorPrompt={connectorPrompt}
|
||||
http={http}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
isWelcomeSetup={isWelcomeSetup}
|
||||
/>
|
||||
<ChatSend
|
||||
isDisabled={isSendingDisabled}
|
||||
shouldRefocusPrompt={shouldRefocusPrompt}
|
||||
userPrompt={userPrompt}
|
||||
handleOnChatCleared={handleOnChatCleared}
|
||||
handlePromptChange={handlePromptChange}
|
||||
handleSendMessage={handleChatSend}
|
||||
handleRegenerateResponse={handleRegenerateResponse}
|
||||
isLoading={isLoadingChatSend}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
{!isDisabled && (
|
||||
<QuickPrompts
|
||||
setInput={setUserPrompt}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
trackPrompt={trackPrompt}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
allPrompts={allPrompts}
|
||||
/>
|
||||
)}
|
||||
</EuiModalFooter>
|
||||
</>,
|
||||
embeddedLayout
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</CommentContainer>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@ const defaultProps: Props = {
|
|||
selectedPromptContexts: {},
|
||||
setIsSettingsModalVisible: jest.fn(),
|
||||
setSelectedPromptContexts: jest.fn(),
|
||||
isFlyoutMode: false,
|
||||
allSystemPrompts: [],
|
||||
};
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ export interface Props {
|
|||
setSelectedPromptContexts: React.Dispatch<
|
||||
React.SetStateAction<Record<string, SelectedPromptContext>>
|
||||
>;
|
||||
isFlyoutMode: boolean;
|
||||
allSystemPrompts: PromptResponse[];
|
||||
}
|
||||
|
||||
|
@ -50,7 +49,6 @@ const PromptEditorComponent: React.FC<Props> = ({
|
|||
selectedPromptContexts,
|
||||
setIsSettingsModalVisible,
|
||||
setSelectedPromptContexts,
|
||||
isFlyoutMode,
|
||||
allSystemPrompts,
|
||||
}) => {
|
||||
const commentBody = useMemo(
|
||||
|
@ -64,17 +62,14 @@ const PromptEditorComponent: React.FC<Props> = ({
|
|||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SelectedPromptContexts
|
||||
isNewConversation={isNewConversation}
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
currentReplacements={conversation?.replacements}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
|
||||
<PreviewText color="subdued" data-test-subj="previewText">
|
||||
|
@ -90,7 +85,6 @@ const PromptEditorComponent: React.FC<Props> = ({
|
|||
onSystemPromptSelectionChange,
|
||||
isSettingsModalVisible,
|
||||
setIsSettingsModalVisible,
|
||||
isFlyoutMode,
|
||||
promptContexts,
|
||||
selectedPromptContexts,
|
||||
setSelectedPromptContexts,
|
||||
|
|
|
@ -15,7 +15,6 @@ import type { SelectedPromptContext } from '../../prompt_context/types';
|
|||
import { Props, SelectedPromptContexts } from '.';
|
||||
|
||||
const defaultProps: Props = {
|
||||
isNewConversation: false,
|
||||
promptContexts: {
|
||||
[mockAlertPromptContext.id]: mockAlertPromptContext,
|
||||
[mockEventPromptContext.id]: mockEventPromptContext,
|
||||
|
@ -23,7 +22,6 @@ const defaultProps: Props = {
|
|||
selectedPromptContexts: {},
|
||||
setSelectedPromptContexts: jest.fn(),
|
||||
currentReplacements: {},
|
||||
isFlyoutMode: false,
|
||||
};
|
||||
|
||||
const mockSelectedAlertPromptContext: SelectedPromptContext = {
|
||||
|
@ -53,61 +51,6 @@ describe('SelectedPromptContexts', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('it does NOT render a spacer when isNewConversation is false and selectedPromptContextIds.length is 1', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<SelectedPromptContexts
|
||||
{...defaultProps}
|
||||
isNewConversation={false} // <--
|
||||
selectedPromptContexts={{
|
||||
[mockAlertPromptContext.id]: mockSelectedAlertPromptContext,
|
||||
}} // <-- length 1
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('spacer')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('it renders a spacer when isNewConversation is true and selectedPromptContextIds.length is 1', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<SelectedPromptContexts
|
||||
{...defaultProps}
|
||||
isNewConversation={true} // <--
|
||||
selectedPromptContexts={{
|
||||
[mockAlertPromptContext.id]: mockSelectedAlertPromptContext,
|
||||
}} // <-- length 1
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('spacer')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('it renders a spacer for each selected prompt context when isNewConversation is false and selectedPromptContextIds.length is 2', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<SelectedPromptContexts
|
||||
{...defaultProps}
|
||||
isNewConversation={false} // <--
|
||||
selectedPromptContexts={{
|
||||
[mockAlertPromptContext.id]: mockSelectedAlertPromptContext,
|
||||
[mockEventPromptContext.id]: mockSelectedEventPromptContext,
|
||||
}} // <-- length 2
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByTestId('spacer')).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the selected prompt contexts', async () => {
|
||||
const selectedPromptContexts = {
|
||||
[mockAlertPromptContext.id]: mockSelectedAlertPromptContext,
|
||||
|
|
|
@ -5,19 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiAccordion,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { EuiAccordion, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
|
||||
import { isEmpty, omit } from 'lodash/fp';
|
||||
import React, { useCallback } from 'react';
|
||||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import styled from 'styled-components';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { Conversation } from '../../../assistant_context/types';
|
||||
|
@ -26,14 +17,12 @@ import type { PromptContext, SelectedPromptContext } from '../../prompt_context/
|
|||
import * as i18n from './translations';
|
||||
|
||||
export interface Props {
|
||||
isNewConversation: boolean;
|
||||
promptContexts: Record<string, PromptContext>;
|
||||
selectedPromptContexts: Record<string, SelectedPromptContext>;
|
||||
setSelectedPromptContexts: React.Dispatch<
|
||||
React.SetStateAction<Record<string, SelectedPromptContext>>
|
||||
>;
|
||||
currentReplacements: Conversation['replacements'] | undefined;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
|
||||
export const EditorContainer = styled.div<{
|
||||
|
@ -45,20 +34,11 @@ export const EditorContainer = styled.div<{
|
|||
`;
|
||||
|
||||
const SelectedPromptContextsComponent: React.FC<Props> = ({
|
||||
isNewConversation,
|
||||
promptContexts,
|
||||
selectedPromptContexts,
|
||||
setSelectedPromptContexts,
|
||||
currentReplacements,
|
||||
isFlyoutMode,
|
||||
}) => {
|
||||
const [accordionState, setAccordionState] = React.useState<'closed' | 'open'>('closed');
|
||||
|
||||
const onToggle = useCallback(
|
||||
() => setAccordionState((prev) => (prev === 'open' ? 'closed' : 'open')),
|
||||
[]
|
||||
);
|
||||
|
||||
const unselectPromptContext = useCallback(
|
||||
(unselectedId: string) => {
|
||||
setSelectedPromptContexts((prev) => omit(unselectedId, prev));
|
||||
|
@ -71,22 +51,13 @@ const SelectedPromptContextsComponent: React.FC<Props> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
data-test-subj="selectedPromptContexts"
|
||||
direction="column"
|
||||
gutterSize={isFlyoutMode ? 's' : 'none'}
|
||||
>
|
||||
<EuiFlexGroup data-test-subj="selectedPromptContexts" direction="column" gutterSize={'s'}>
|
||||
{Object.keys(selectedPromptContexts)
|
||||
.sort()
|
||||
.map((id) => (
|
||||
<EuiFlexItem data-test-subj={`selectedPromptContext-${id}`} grow={false} key={id}>
|
||||
{!isFlyoutMode &&
|
||||
(isNewConversation || Object.keys(selectedPromptContexts).length > 1) ? (
|
||||
<EuiSpacer data-test-subj="spacer" />
|
||||
) : null}
|
||||
<EuiAccordion
|
||||
buttonContent={promptContexts[id]?.description}
|
||||
{...(!isFlyoutMode && { forceState: accordionState })}
|
||||
extraAction={
|
||||
<EuiToolTip content={i18n.REMOVE_CONTEXT}>
|
||||
<EuiButtonIcon
|
||||
|
@ -98,43 +69,26 @@ const SelectedPromptContextsComponent: React.FC<Props> = ({
|
|||
</EuiToolTip>
|
||||
}
|
||||
id={id}
|
||||
{...(!isFlyoutMode && { onToggle })}
|
||||
paddingSize="s"
|
||||
{...(isFlyoutMode
|
||||
? {
|
||||
css: css`
|
||||
background: ${euiThemeVars.euiPageBackgroundColor};
|
||||
border-radius: ${euiThemeVars.euiBorderRadius};
|
||||
css={css`
|
||||
background: ${euiThemeVars.euiPageBackgroundColor};
|
||||
border-radius: ${euiThemeVars.euiBorderRadius};
|
||||
|
||||
> div:first-child {
|
||||
color: ${euiThemeVars.euiColorPrimary};
|
||||
padding: ${euiThemeVars.euiFormControlPadding};
|
||||
}
|
||||
`,
|
||||
borders: 'all',
|
||||
arrowProps: {
|
||||
color: 'primary',
|
||||
},
|
||||
}
|
||||
: {})}
|
||||
> div:first-child {
|
||||
color: ${euiThemeVars.euiColorPrimary};
|
||||
padding: ${euiThemeVars.euiFormControlPadding};
|
||||
}
|
||||
`}
|
||||
borders={'all'}
|
||||
arrowProps={{
|
||||
color: 'primary',
|
||||
}}
|
||||
>
|
||||
{isFlyoutMode ? (
|
||||
<DataAnonymizationEditor
|
||||
currentReplacements={currentReplacements}
|
||||
selectedPromptContext={selectedPromptContexts[id]}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
) : (
|
||||
<EditorContainer $accordionState={accordionState}>
|
||||
<DataAnonymizationEditor
|
||||
currentReplacements={currentReplacements}
|
||||
selectedPromptContext={selectedPromptContexts[id]}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
</EditorContainer>
|
||||
)}
|
||||
<DataAnonymizationEditor
|
||||
currentReplacements={currentReplacements}
|
||||
selectedPromptContext={selectedPromptContexts[id]}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
|
|
|
@ -16,21 +16,21 @@ import { getOptions, getOptionFromPrompt } from './helpers';
|
|||
describe('helpers', () => {
|
||||
describe('getOptionFromPrompt', () => {
|
||||
it('returns an EuiSuperSelectOption with the correct value', () => {
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt, isFlyoutMode: true });
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt });
|
||||
|
||||
expect(option.value).toBe(mockSystemPrompt.id);
|
||||
});
|
||||
|
||||
it('returns an EuiSuperSelectOption with the correct inputDisplay', () => {
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt, isFlyoutMode: false });
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt });
|
||||
|
||||
render(<>{option.inputDisplay}</>);
|
||||
|
||||
expect(screen.getByTestId('systemPromptText')).toHaveTextContent(mockSystemPrompt.content);
|
||||
expect(screen.getByTestId('systemPromptText')).toHaveTextContent(mockSystemPrompt.name);
|
||||
});
|
||||
|
||||
it('shows the expected name in the dropdownDisplay', () => {
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt, isFlyoutMode: true });
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt });
|
||||
|
||||
render(<TestProviders>{option.dropdownDisplay}</TestProviders>);
|
||||
|
||||
|
@ -38,7 +38,7 @@ describe('helpers', () => {
|
|||
});
|
||||
|
||||
it('shows the expected prompt content in the dropdownDisplay', () => {
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt, isFlyoutMode: true });
|
||||
const option = getOptionFromPrompt({ ...mockSystemPrompt });
|
||||
|
||||
render(<TestProviders>{option.dropdownDisplay}</TestProviders>);
|
||||
|
||||
|
@ -51,7 +51,7 @@ describe('helpers', () => {
|
|||
const prompts = [mockSystemPrompt, mockSuperheroSystemPrompt];
|
||||
const promptIds = prompts.map(({ id }) => id);
|
||||
|
||||
const options = getOptions({ prompts, isFlyoutMode: false });
|
||||
const options = getOptions({ prompts });
|
||||
const optionValues = options.map(({ value }) => value);
|
||||
|
||||
expect(optionValues).toEqual(promptIds);
|
||||
|
|
|
@ -8,46 +8,23 @@
|
|||
import { EuiText, EuiToolTip } from '@elastic/eui';
|
||||
import type { EuiSuperSelectOption } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { EMPTY_PROMPT } from './translations';
|
||||
|
||||
const Strong = styled.strong`
|
||||
margin-right: ${({ theme }) => theme.eui.euiSizeS};
|
||||
margin-right: ${euiThemeVars.euiSizeS};
|
||||
`;
|
||||
|
||||
export const getOptionFromPrompt = ({
|
||||
content,
|
||||
id,
|
||||
name,
|
||||
showTitles = false,
|
||||
isFlyoutMode,
|
||||
}: PromptResponse & {
|
||||
showTitles?: boolean;
|
||||
isFlyoutMode: boolean;
|
||||
}): EuiSuperSelectOption<string> => ({
|
||||
}: PromptResponse): EuiSuperSelectOption<string> => ({
|
||||
value: id,
|
||||
inputDisplay: isFlyoutMode ? (
|
||||
name
|
||||
) : (
|
||||
<EuiText
|
||||
color="subdued"
|
||||
data-test-subj="systemPromptText"
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`}
|
||||
>
|
||||
{showTitles ? name : content}
|
||||
</EuiText>
|
||||
),
|
||||
inputDisplay: <span data-test-subj="systemPromptText">{name}</span>,
|
||||
dropdownDisplay: (
|
||||
<>
|
||||
<Strong data-test-subj="name">{name}</Strong>
|
||||
|
@ -64,12 +41,6 @@ export const getOptionFromPrompt = ({
|
|||
|
||||
interface GetOptionsProps {
|
||||
prompts: PromptResponse[] | undefined;
|
||||
showTitles?: boolean;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
export const getOptions = ({
|
||||
prompts,
|
||||
showTitles = false,
|
||||
isFlyoutMode,
|
||||
}: GetOptionsProps): Array<EuiSuperSelectOption<string>> =>
|
||||
prompts?.map((p) => getOptionFromPrompt({ ...p, showTitles, isFlyoutMode })) ?? [];
|
||||
export const getOptions = ({ prompts }: GetOptionsProps): Array<EuiSuperSelectOption<string>> =>
|
||||
prompts?.map(getOptionFromPrompt) ?? [];
|
||||
|
|
|
@ -90,7 +90,6 @@ describe('SystemPrompt', () => {
|
|||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
);
|
||||
|
@ -100,10 +99,6 @@ describe('SystemPrompt', () => {
|
|||
expect(screen.getByTestId('selectSystemPrompt')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does NOT render the system prompt text', () => {
|
||||
expect(screen.queryByTestId('systemPromptText')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does NOT render the edit button', () => {
|
||||
expect(screen.queryByTestId('edit')).not.toBeInTheDocument();
|
||||
});
|
||||
|
@ -122,26 +117,21 @@ describe('SystemPrompt', () => {
|
|||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('does NOT render the system prompt select', () => {
|
||||
expect(screen.queryByTestId('selectSystemPrompt')).not.toBeInTheDocument();
|
||||
it('does render the system prompt select', () => {
|
||||
expect(screen.queryByTestId('selectSystemPrompt')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the system prompt text', () => {
|
||||
expect(screen.getByTestId('systemPromptText')).toHaveTextContent(mockSystemPrompt.content);
|
||||
});
|
||||
|
||||
it('renders the edit button', () => {
|
||||
expect(screen.getByTestId('edit')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('systemPromptText')).toHaveTextContent(mockSystemPrompt.name);
|
||||
});
|
||||
|
||||
it('renders the clear button', () => {
|
||||
expect(screen.getByTestId('clear')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('clearSystemPrompt')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -158,7 +148,6 @@ describe('SystemPrompt', () => {
|
|||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -206,7 +195,6 @@ describe('SystemPrompt', () => {
|
|||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -268,7 +256,6 @@ describe('SystemPrompt', () => {
|
|||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -337,7 +324,6 @@ describe('SystemPrompt', () => {
|
|||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -421,7 +407,6 @@ describe('SystemPrompt', () => {
|
|||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -483,26 +468,6 @@ describe('SystemPrompt', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('shows the system prompt select when the edit button is clicked', () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={mockSystemPrompt.id}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
userEvent.click(screen.getByTestId('edit'));
|
||||
|
||||
expect(screen.getByTestId('selectSystemPrompt')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the system prompt select when system prompt text is clicked', () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
|
@ -512,7 +477,6 @@ describe('SystemPrompt', () => {
|
|||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={false}
|
||||
allSystemPrompts={mockSystemPrompts}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
|
|
@ -5,14 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../../..';
|
||||
import * as i18n from './translations';
|
||||
import { SelectSystemPrompt } from './select_system_prompt';
|
||||
|
||||
interface Props {
|
||||
|
@ -21,7 +16,6 @@ interface Props {
|
|||
isSettingsModalVisible: boolean;
|
||||
onSystemPromptSelectionChange: (systemPromptId: string | undefined) => void;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isFlyoutMode: boolean;
|
||||
allSystemPrompts: PromptResponse[];
|
||||
}
|
||||
|
||||
|
@ -31,7 +25,6 @@ const SystemPromptComponent: React.FC<Props> = ({
|
|||
isSettingsModalVisible,
|
||||
onSystemPromptSelectionChange,
|
||||
setIsSettingsModalVisible,
|
||||
isFlyoutMode,
|
||||
allSystemPrompts,
|
||||
}) => {
|
||||
const selectedPrompt = useMemo(() => {
|
||||
|
@ -42,99 +35,24 @@ const SystemPromptComponent: React.FC<Props> = ({
|
|||
}
|
||||
}, [allSystemPrompts, conversation?.apiConfig?.defaultSystemPromptId, editingSystemPromptId]);
|
||||
|
||||
const [isEditing, setIsEditing] = React.useState<boolean>(false);
|
||||
|
||||
const handleClearSystemPrompt = useCallback(() => {
|
||||
if (conversation) {
|
||||
onSystemPromptSelectionChange(undefined);
|
||||
}
|
||||
}, [conversation, onSystemPromptSelectionChange]);
|
||||
|
||||
const handleEditSystemPrompt = useCallback(() => setIsEditing(true), []);
|
||||
|
||||
if (isFlyoutMode) {
|
||||
return (
|
||||
<SelectSystemPrompt
|
||||
allPrompts={allSystemPrompts}
|
||||
clearSelectedSystemPrompt={handleClearSystemPrompt}
|
||||
conversation={conversation}
|
||||
data-test-subj="systemPrompt"
|
||||
isClearable={true}
|
||||
isEditing={true}
|
||||
setIsEditing={setIsEditing}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
selectedPrompt={selectedPrompt}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{selectedPrompt == null || isEditing ? (
|
||||
<SelectSystemPrompt
|
||||
allPrompts={allSystemPrompts}
|
||||
clearSelectedSystemPrompt={handleClearSystemPrompt}
|
||||
conversation={conversation}
|
||||
data-test-subj="systemPrompt"
|
||||
isClearable={true}
|
||||
isEditing={isEditing}
|
||||
isOpen={isEditing}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
selectedPrompt={selectedPrompt}
|
||||
setIsEditing={setIsEditing}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
) : (
|
||||
<EuiFlexGroup alignItems="flexStart" gutterSize="none">
|
||||
<EuiFlexItem grow>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
data-test-subj="systemPromptText"
|
||||
onClick={handleEditSystemPrompt}
|
||||
css={css`
|
||||
white-space: pre-line;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`}
|
||||
>
|
||||
{isEmpty(selectedPrompt?.content) ? i18n.EMPTY_PROMPT : selectedPrompt?.content}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={i18n.SELECT_A_SYSTEM_PROMPT}>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.SELECT_A_SYSTEM_PROMPT}
|
||||
data-test-subj="edit"
|
||||
iconType="documentEdit"
|
||||
onClick={handleEditSystemPrompt}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={i18n.CLEAR_SYSTEM_PROMPT}>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.CLEAR_SYSTEM_PROMPT}
|
||||
data-test-subj="clear"
|
||||
iconType="cross"
|
||||
onClick={handleClearSystemPrompt}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</div>
|
||||
<SelectSystemPrompt
|
||||
allPrompts={allSystemPrompts}
|
||||
clearSelectedSystemPrompt={handleClearSystemPrompt}
|
||||
conversation={conversation}
|
||||
data-test-subj="systemPrompt"
|
||||
isClearable={true}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
selectedPrompt={selectedPrompt}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -48,9 +48,9 @@ const props: Props = {
|
|||
],
|
||||
conversation: undefined,
|
||||
isSettingsModalVisible: false,
|
||||
isClearable: true,
|
||||
selectedPrompt: { id: 'default-system-prompt', content: '', name: '', promptType: 'system' },
|
||||
setIsSettingsModalVisible: jest.fn(),
|
||||
isFlyoutMode: false,
|
||||
};
|
||||
|
||||
const mockUseAssistantContext = {
|
||||
|
@ -91,93 +91,27 @@ jest.mock('../../../../assistant_context', () => {
|
|||
describe('SelectSystemPrompt', () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('renders the prompt super select when isEditing is true', () => {
|
||||
const { getByTestId } = render(<SelectSystemPrompt {...props} isEditing={true} />);
|
||||
it('renders the prompt super select', () => {
|
||||
const { getByTestId } = render(<SelectSystemPrompt {...props} />);
|
||||
|
||||
expect(getByTestId(TEST_IDS.PROMPT_SUPERSELECT)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does NOT render the prompt super select when isEditing is false', () => {
|
||||
const { queryByTestId } = render(<SelectSystemPrompt {...props} isEditing={false} />);
|
||||
|
||||
expect(queryByTestId(TEST_IDS.PROMPT_SUPERSELECT)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does NOT render the clear system prompt button when isEditing is true', () => {
|
||||
const { queryByTestId } = render(<SelectSystemPrompt {...props} isEditing={true} />);
|
||||
|
||||
expect(queryByTestId('clearSystemPrompt')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the clear system prompt button when isEditing is true AND isClearable is true', () => {
|
||||
const { getByTestId } = render(
|
||||
<SelectSystemPrompt {...props} isClearable={true} isEditing={true} />
|
||||
);
|
||||
it('renders the clear system prompt button', () => {
|
||||
const { getByTestId } = render(<SelectSystemPrompt {...props} />);
|
||||
|
||||
expect(getByTestId('clearSystemPrompt')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does NOT render the clear system prompt button when isEditing is false', () => {
|
||||
const { queryByTestId } = render(<SelectSystemPrompt {...props} isEditing={false} />);
|
||||
|
||||
expect(queryByTestId('clearSystemPrompt')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the add system prompt button when isEditing is false', () => {
|
||||
const { getByTestId } = render(<SelectSystemPrompt {...props} isEditing={false} />);
|
||||
|
||||
expect(getByTestId('addSystemPrompt')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does NOT render the add system prompt button when isEditing is true', () => {
|
||||
const { queryByTestId } = render(<SelectSystemPrompt {...props} isEditing={true} />);
|
||||
|
||||
expect(queryByTestId('addSystemPrompt')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clears the selected system prompt when the clear button is clicked', () => {
|
||||
const clearSelectedSystemPrompt = jest.fn();
|
||||
|
||||
const { getByTestId } = render(
|
||||
<SelectSystemPrompt
|
||||
{...props}
|
||||
clearSelectedSystemPrompt={clearSelectedSystemPrompt}
|
||||
isEditing={true}
|
||||
isClearable={true}
|
||||
/>
|
||||
<SelectSystemPrompt {...props} clearSelectedSystemPrompt={clearSelectedSystemPrompt} />
|
||||
);
|
||||
|
||||
userEvent.click(getByTestId('clearSystemPrompt'));
|
||||
|
||||
expect(clearSelectedSystemPrompt).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('hides the select when the clear button is clicked', () => {
|
||||
const setIsEditing = jest.fn();
|
||||
|
||||
const { getByTestId } = render(
|
||||
<SelectSystemPrompt
|
||||
{...props}
|
||||
setIsEditing={setIsEditing}
|
||||
isEditing={true}
|
||||
isClearable={true}
|
||||
/>
|
||||
);
|
||||
|
||||
userEvent.click(getByTestId('clearSystemPrompt'));
|
||||
|
||||
expect(setIsEditing).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('shows the select when the add button is clicked', () => {
|
||||
const setIsEditing = jest.fn();
|
||||
|
||||
const { getByTestId } = render(
|
||||
<SelectSystemPrompt {...props} setIsEditing={setIsEditing} isEditing={false} />
|
||||
);
|
||||
|
||||
userEvent.click(getByTestId('addSystemPrompt'));
|
||||
|
||||
expect(setIsEditing).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,15 +38,11 @@ export interface Props {
|
|||
selectedPrompt: PromptResponse | undefined;
|
||||
clearSelectedSystemPrompt?: () => void;
|
||||
isClearable?: boolean;
|
||||
isEditing?: boolean;
|
||||
isDisabled?: boolean;
|
||||
isOpen?: boolean;
|
||||
isSettingsModalVisible: boolean;
|
||||
setIsEditing?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
showTitles?: boolean;
|
||||
onSystemPromptSelectionChange?: (promptId: string | undefined) => void;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
|
||||
const ADD_NEW_SYSTEM_PROMPT = 'ADD_NEW_SYSTEM_PROMPT';
|
||||
|
@ -58,15 +54,11 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
selectedPrompt,
|
||||
clearSelectedSystemPrompt,
|
||||
isClearable = false,
|
||||
isEditing = false,
|
||||
isDisabled = false,
|
||||
isOpen = false,
|
||||
isSettingsModalVisible,
|
||||
onSystemPromptSelectionChange,
|
||||
setIsEditing,
|
||||
setIsSettingsModalVisible,
|
||||
showTitles = false,
|
||||
isFlyoutMode = false,
|
||||
}) => {
|
||||
const { setSelectedSettingsTab } = useAssistantContext();
|
||||
const { setApiConfig } = useConversation();
|
||||
|
@ -117,10 +109,7 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
}, []);
|
||||
|
||||
// SuperSelect State/Actions
|
||||
const options = useMemo(
|
||||
() => getOptions({ prompts: allSystemPrompts, showTitles, isFlyoutMode }),
|
||||
[allSystemPrompts, showTitles, isFlyoutMode]
|
||||
);
|
||||
const options = useMemo(() => getOptions({ prompts: allSystemPrompts }), [allSystemPrompts]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(selectedSystemPromptId) => {
|
||||
|
@ -134,11 +123,9 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
onSystemPromptSelectionChange(selectedSystemPromptId);
|
||||
}
|
||||
setSelectedSystemPrompt(selectedSystemPromptId);
|
||||
setIsEditing?.(false);
|
||||
},
|
||||
[
|
||||
onSystemPromptSelectionChange,
|
||||
setIsEditing,
|
||||
setIsSettingsModalVisible,
|
||||
setSelectedSettingsTab,
|
||||
setSelectedSystemPrompt,
|
||||
|
@ -147,14 +134,8 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
|
||||
const clearSystemPrompt = useCallback(() => {
|
||||
setSelectedSystemPrompt(undefined);
|
||||
setIsEditing?.(false);
|
||||
clearSelectedSystemPrompt?.();
|
||||
}, [clearSelectedSystemPrompt, setIsEditing, setSelectedSystemPrompt]);
|
||||
|
||||
const onShowSelectSystemPrompt = useCallback(() => {
|
||||
setIsEditing?.(true);
|
||||
setIsOpenLocal(true);
|
||||
}, [setIsEditing]);
|
||||
}, [clearSelectedSystemPrompt, setSelectedSystemPrompt]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
|
@ -170,94 +151,69 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
|
|||
max-width: 100%;
|
||||
`}
|
||||
>
|
||||
{isEditing && (
|
||||
<EuiFormRow
|
||||
<EuiFormRow
|
||||
css={css`
|
||||
min-width: 100%;
|
||||
`}
|
||||
>
|
||||
<EuiSuperSelect
|
||||
// Limits popover z-index to prevent it from getting too high and covering tooltips.
|
||||
// If the z-index is not defined, when a popover is opened, it sets the target z-index + 2000
|
||||
popoverProps={{ zIndex: euiThemeVars.euiZLevel8 }}
|
||||
compressed={compressed}
|
||||
data-test-subj={TEST_IDS.PROMPT_SUPERSELECT}
|
||||
fullWidth
|
||||
hasDividers
|
||||
itemLayoutAlign="top"
|
||||
disabled={isDisabled}
|
||||
isOpen={isOpenLocal && !isSettingsModalVisible}
|
||||
onChange={onChange}
|
||||
onBlur={handleOnBlur}
|
||||
options={[...options, addNewSystemPrompt]}
|
||||
placeholder={i18n.SELECT_A_SYSTEM_PROMPT}
|
||||
valueOfSelected={valueOfSelected}
|
||||
prepend={!isSettingsModalVisible ? PROMPT_CONTEXT_SELECTOR_PREFIX : undefined}
|
||||
css={css`
|
||||
min-width: 100%;
|
||||
padding-right: 56px !important;
|
||||
`}
|
||||
>
|
||||
<EuiSuperSelect
|
||||
// Limits popover z-index to prevent it from getting too high and covering tooltips.
|
||||
// If the z-index is not defined, when a popover is opened, it sets the target z-index + 2000
|
||||
popoverProps={{ zIndex: euiThemeVars.euiZLevel8 }}
|
||||
compressed={compressed}
|
||||
data-test-subj={TEST_IDS.PROMPT_SUPERSELECT}
|
||||
fullWidth
|
||||
hasDividers
|
||||
itemLayoutAlign="top"
|
||||
disabled={isDisabled}
|
||||
isOpen={isOpenLocal && !isSettingsModalVisible}
|
||||
onChange={onChange}
|
||||
onBlur={handleOnBlur}
|
||||
options={[...options, addNewSystemPrompt]}
|
||||
placeholder={i18n.SELECT_A_SYSTEM_PROMPT}
|
||||
valueOfSelected={valueOfSelected}
|
||||
prepend={
|
||||
isFlyoutMode && !isSettingsModalVisible ? PROMPT_CONTEXT_SELECTOR_PREFIX : undefined
|
||||
}
|
||||
css={
|
||||
isFlyoutMode &&
|
||||
css`
|
||||
padding-right: 56px !important;
|
||||
`
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={
|
||||
isFlyoutMode
|
||||
? css`
|
||||
position: absolute;
|
||||
right: 36px;
|
||||
`
|
||||
: undefined
|
||||
}
|
||||
css={css`
|
||||
position: absolute;
|
||||
right: 36px;
|
||||
`}
|
||||
>
|
||||
{isEditing && isClearable && selectedPrompt && (
|
||||
{isClearable && selectedPrompt && (
|
||||
<EuiToolTip content={i18n.CLEAR_SYSTEM_PROMPT}>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.CLEAR_SYSTEM_PROMPT}
|
||||
data-test-subj="clearSystemPrompt"
|
||||
iconType="cross"
|
||||
onClick={clearSystemPrompt}
|
||||
css={
|
||||
isFlyoutMode
|
||||
? // mimic EuiComboBox clear button
|
||||
css`
|
||||
inline-size: 16px;
|
||||
block-size: 16px;
|
||||
border-radius: 16px;
|
||||
background: ${euiThemeVars.euiColorMediumShade};
|
||||
// mimic EuiComboBox clear button
|
||||
css={css`
|
||||
inline-size: 16px;
|
||||
block-size: 16px;
|
||||
border-radius: 16px;
|
||||
background: ${euiThemeVars.euiColorMediumShade};
|
||||
|
||||
:hover:not(:disabled) {
|
||||
background: ${euiThemeVars.euiColorMediumShade};
|
||||
transform: none;
|
||||
}
|
||||
:hover:not(:disabled) {
|
||||
background: ${euiThemeVars.euiColorMediumShade};
|
||||
transform: none;
|
||||
}
|
||||
|
||||
> svg {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
stroke-width: 2px;
|
||||
fill: #fff;
|
||||
stroke: #fff;
|
||||
}
|
||||
`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
{!isEditing && (
|
||||
<EuiToolTip content={i18n.ADD_SYSTEM_PROMPT_TOOLTIP}>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.ADD_SYSTEM_PROMPT_TOOLTIP}
|
||||
data-test-subj="addSystemPrompt"
|
||||
iconType="plus"
|
||||
onClick={onShowSelectSystemPrompt}
|
||||
> svg {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
stroke-width: 2px;
|
||||
fill: #fff;
|
||||
stroke: #fff;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
|
|
|
@ -147,9 +147,8 @@ export const SystemPromptSelector: React.FC<Props> = React.memo(
|
|||
|
||||
const renderOption: (
|
||||
option: SystemPromptSelectorOption,
|
||||
searchValue: string,
|
||||
OPTION_CONTENT_CLASSNAME: string
|
||||
) => React.ReactNode = (option, searchValue, contentClassName) => {
|
||||
searchValue: string
|
||||
) => React.ReactNode = (option, searchValue) => {
|
||||
const { label, value } = option;
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
|
|
|
@ -16,11 +16,10 @@ export interface Props extends React.TextareaHTMLAttributes<HTMLTextAreaElement>
|
|||
isDisabled?: boolean;
|
||||
onPromptSubmit: (value: string) => void;
|
||||
value: string;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
|
||||
export const PromptTextArea = forwardRef<HTMLTextAreaElement, Props>(
|
||||
({ isDisabled = false, value, onPromptSubmit, handlePromptChange, isFlyoutMode }, ref) => {
|
||||
({ isDisabled = false, value, onPromptSubmit, handlePromptChange }, ref) => {
|
||||
const onChangeCallback = useCallback(
|
||||
(event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
handlePromptChange(event.target.value);
|
||||
|
@ -46,8 +45,8 @@ export const PromptTextArea = forwardRef<HTMLTextAreaElement, Props>(
|
|||
<EuiTextArea
|
||||
css={css`
|
||||
padding-right: 64px !important;
|
||||
min-height: ${!isFlyoutMode ? '125px' : '64px'};
|
||||
max-height: ${!isFlyoutMode ? 'auto' : '350px'};
|
||||
min-height: 64px;
|
||||
max-height: 350px;
|
||||
`}
|
||||
className="eui-scrollBar"
|
||||
inputRef={ref}
|
||||
|
@ -61,7 +60,7 @@ export const PromptTextArea = forwardRef<HTMLTextAreaElement, Props>(
|
|||
value={value}
|
||||
onChange={onChangeCallback}
|
||||
onKeyDown={onKeyDown}
|
||||
rows={isFlyoutMode ? 1 : 6}
|
||||
rows={1}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -140,9 +140,8 @@ export const QuickPromptSelector: React.FC<Props> = React.memo(
|
|||
|
||||
const renderOption: (
|
||||
option: QuickPromptSelectorOption,
|
||||
searchValue: string,
|
||||
OPTION_CONTENT_CLASSNAME: string
|
||||
) => React.ReactNode = (option, searchValue, contentClassName) => {
|
||||
searchValue: string
|
||||
) => React.ReactNode = (option, searchValue) => {
|
||||
const { color, label, value } = option;
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
|
|
|
@ -19,7 +19,6 @@ const testProps = {
|
|||
setInput,
|
||||
setIsSettingsModalVisible,
|
||||
trackPrompt,
|
||||
isFlyoutMode: false,
|
||||
allPrompts: MOCK_QUICK_PROMPTS,
|
||||
};
|
||||
const setSelectedSettingsTab = jest.fn();
|
||||
|
@ -36,6 +35,16 @@ const testTitle = 'SPL_QUERY_CONVERSION_TITLE';
|
|||
const testPrompt = 'SPL_QUERY_CONVERSION_PROMPT';
|
||||
const customTitle = 'A_CUSTOM_OPTION';
|
||||
|
||||
jest.mock('react-use', () => ({
|
||||
...jest.requireActual('react-use'),
|
||||
useMeasure: () => [
|
||||
() => {},
|
||||
{
|
||||
width: 500,
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
jest.mock('../../assistant_context', () => ({
|
||||
...jest.requireActual('../../assistant_context'),
|
||||
useAssistantContext: () => mockUseAssistantContext,
|
||||
|
|
|
@ -27,12 +27,10 @@ import { QUICK_PROMPTS_TAB } from '../settings/const';
|
|||
|
||||
export const KNOWLEDGE_BASE_CATEGORY = 'knowledge-base';
|
||||
|
||||
const COUNT_BEFORE_OVERFLOW = 5;
|
||||
interface QuickPromptsProps {
|
||||
setInput: (input: string) => void;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
trackPrompt: (prompt: string) => void;
|
||||
isFlyoutMode: boolean;
|
||||
allPrompts: PromptResponse[];
|
||||
}
|
||||
|
||||
|
@ -42,7 +40,7 @@ interface QuickPromptsProps {
|
|||
* and localstorage for storing new and edited prompts.
|
||||
*/
|
||||
export const QuickPrompts: React.FC<QuickPromptsProps> = React.memo(
|
||||
({ setInput, setIsSettingsModalVisible, trackPrompt, isFlyoutMode, allPrompts }) => {
|
||||
({ setInput, setIsSettingsModalVisible, trackPrompt, allPrompts }) => {
|
||||
const [quickPromptsContainerRef, { width }] = useMeasure();
|
||||
|
||||
const { knowledgeBase, promptContexts, setSelectedSettingsTab } = useAssistantContext();
|
||||
|
@ -103,25 +101,15 @@ export const QuickPrompts: React.FC<QuickPromptsProps> = React.memo(
|
|||
}, [setIsSettingsModalVisible, setSelectedSettingsTab]);
|
||||
|
||||
const quickPrompts = useMemo(() => {
|
||||
const visibleCount = isFlyoutMode ? Math.floor(width / 120) : COUNT_BEFORE_OVERFLOW;
|
||||
const visibleCount = Math.floor(width / 120);
|
||||
const visibleItems = contextFilteredQuickPrompts.slice(0, visibleCount);
|
||||
const overflowItems = contextFilteredQuickPrompts.slice(visibleCount);
|
||||
|
||||
return { visible: visibleItems, overflow: overflowItems };
|
||||
}, [contextFilteredQuickPrompts, isFlyoutMode, width]);
|
||||
}, [contextFilteredQuickPrompts, width]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
justifyContent={isFlyoutMode ? 'spaceBetween' : 'flexStart'}
|
||||
css={
|
||||
!isFlyoutMode &&
|
||||
css`
|
||||
margin: 16px;
|
||||
`
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" justifyContent={'spaceBetween'}>
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
|
@ -154,20 +142,12 @@ export const QuickPrompts: React.FC<QuickPromptsProps> = React.memo(
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
button={
|
||||
isFlyoutMode ? (
|
||||
<EuiButtonIcon
|
||||
color={'primary'}
|
||||
iconType={'boxesHorizontal'}
|
||||
onClick={toggleOverflowPopover}
|
||||
/>
|
||||
) : (
|
||||
<EuiBadge
|
||||
color={'hollow'}
|
||||
iconType={'boxesHorizontal'}
|
||||
onClick={toggleOverflowPopover}
|
||||
onClickAriaLabel={i18n.QUICK_PROMPT_OVERFLOW_ARIA}
|
||||
/>
|
||||
)
|
||||
<EuiButtonIcon
|
||||
color={'primary'}
|
||||
iconType={'boxesHorizontal'}
|
||||
onClick={toggleOverflowPopover}
|
||||
aria-label={i18n.QUICK_PROMPT_OVERFLOW_ARIA}
|
||||
/>
|
||||
}
|
||||
isOpen={isOverflowPopoverOpen}
|
||||
closePopover={closeOverflowPopover}
|
||||
|
|
|
@ -55,7 +55,6 @@ const testProps = {
|
|||
selectedConversationId: welcomeConvo.title,
|
||||
onClose,
|
||||
onSave,
|
||||
isFlyoutMode: false,
|
||||
onConversationSelected,
|
||||
conversations: {},
|
||||
anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] },
|
||||
|
|
|
@ -59,7 +59,6 @@ interface Props {
|
|||
onClose: (
|
||||
event?: React.KeyboardEvent<HTMLDivElement> | React.MouseEvent<HTMLButtonElement>
|
||||
) => void;
|
||||
isFlyoutMode: boolean;
|
||||
onSave: (success: boolean) => Promise<void>;
|
||||
selectedConversationId?: string;
|
||||
onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void;
|
||||
|
@ -80,7 +79,6 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
onConversationSelected,
|
||||
conversations,
|
||||
conversationsLoaded,
|
||||
isFlyoutMode,
|
||||
}) => {
|
||||
const {
|
||||
actionTypeRegistry,
|
||||
|
@ -338,7 +336,6 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
setAssistantStreamingEnabled={setUpdatedAssistantStreamingEnabled}
|
||||
onSelectedConversationChange={onHandleSelectedConversationChange}
|
||||
http={http}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
/>
|
||||
))}
|
||||
{selectedSettingsTab === QUICK_PROMPTS_TAB && (
|
||||
|
|
|
@ -22,7 +22,6 @@ const testProps = {
|
|||
isSettingsModalVisible: false,
|
||||
selectedConversation: welcomeConvo,
|
||||
setIsSettingsModalVisible,
|
||||
isFlyoutMode: false,
|
||||
onConversationSelected,
|
||||
conversations: {},
|
||||
conversationsLoaded: true,
|
||||
|
|
|
@ -23,7 +23,6 @@ interface Props {
|
|||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void;
|
||||
isDisabled?: boolean;
|
||||
isFlyoutMode: boolean;
|
||||
conversations: Record<string, Conversation>;
|
||||
conversationsLoaded: boolean;
|
||||
refetchConversationsState: () => Promise<void>;
|
||||
|
@ -42,7 +41,6 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo(
|
|||
isSettingsModalVisible,
|
||||
setIsSettingsModalVisible,
|
||||
selectedConversationId,
|
||||
isFlyoutMode,
|
||||
onConversationSelected,
|
||||
conversations,
|
||||
conversationsLoaded,
|
||||
|
@ -92,7 +90,7 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo(
|
|||
isDisabled={isDisabled}
|
||||
iconType="gear"
|
||||
size="xs"
|
||||
{...(isFlyoutMode ? { color: 'text' } : {})}
|
||||
color="text"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
|
||||
|
@ -103,7 +101,6 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo(
|
|||
onConversationSelected={onConversationSelected}
|
||||
onClose={handleCloseModal}
|
||||
onSave={handleSave}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
/>
|
||||
|
|
|
@ -62,7 +62,6 @@ const testProps = {
|
|||
selectedConversation: welcomeConvo,
|
||||
onClose,
|
||||
onSave,
|
||||
isFlyoutMode: false,
|
||||
onConversationSelected,
|
||||
conversations: {},
|
||||
anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] },
|
||||
|
|
|
@ -49,7 +49,6 @@ interface Props {
|
|||
conversations: Record<string, Conversation>;
|
||||
conversationsLoaded: boolean;
|
||||
selectedConversation: Conversation;
|
||||
isFlyoutMode: boolean;
|
||||
refetchConversations: () => void;
|
||||
}
|
||||
|
||||
|
@ -61,7 +60,6 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
({
|
||||
conversations,
|
||||
conversationsLoaded,
|
||||
isFlyoutMode,
|
||||
refetchConversations,
|
||||
selectedConversation: defaultSelectedConversation,
|
||||
}) => {
|
||||
|
@ -304,7 +302,6 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
conversationsSettingsBulkActions={conversationsSettingsBulkActions}
|
||||
defaultConnector={defaultConnector}
|
||||
handleSave={handleSave}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
onCancelClick={onCancelClick}
|
||||
onSelectedConversationChange={onHandleSelectedConversationChange}
|
||||
selectedConversation={selectedConversation}
|
||||
|
|
|
@ -44,14 +44,10 @@ const DEFAULT_EVAL_TYPES_OPTIONS = [
|
|||
];
|
||||
const DEFAULT_OUTPUT_INDEX = '.kibana-elastic-ai-assistant-evaluation-results';
|
||||
|
||||
interface Props {
|
||||
onEvaluationSettingsChange?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluation Settings -- development-only feature for evaluating models
|
||||
*/
|
||||
export const EvaluationSettings: React.FC<Props> = React.memo(({ onEvaluationSettingsChange }) => {
|
||||
export const EvaluationSettings: React.FC = React.memo(() => {
|
||||
const { actionTypeRegistry, basePath, http, setTraceOptions, traceOptions } =
|
||||
useAssistantContext();
|
||||
const { data: connectors } = useLoadConnectors({ http });
|
||||
|
|
|
@ -130,7 +130,7 @@ export const useAssistantOverlay = (
|
|||
// proxy show / hide calls to assistant context, using our internal prompt context id:
|
||||
// silent:boolean doesn't show the toast notification if the conversation is not found
|
||||
const showAssistantOverlay = useCallback(
|
||||
async (showOverlay: boolean, silent?: boolean) => {
|
||||
async (showOverlay: boolean) => {
|
||||
let conversation;
|
||||
if (!isLoading) {
|
||||
conversation = conversationTitle
|
||||
|
|
|
@ -33,7 +33,6 @@ interface CreateConversationProps {
|
|||
messages?: ClientMessage[];
|
||||
conversationIds?: string[];
|
||||
apiConfig?: Conversation['apiConfig'];
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
|
||||
interface SetApiConfigProps {
|
||||
|
@ -126,13 +125,10 @@ export const useConversation = (): UseConversation => {
|
|||
* Create a new conversation with the given conversationId, and optionally add messages
|
||||
*/
|
||||
const getDefaultConversation = useCallback(
|
||||
({ cTitle, messages, isFlyoutMode }: CreateConversationProps): Conversation => {
|
||||
({ cTitle, messages }: CreateConversationProps): Conversation => {
|
||||
const newConversation: Conversation =
|
||||
cTitle === i18n.WELCOME_CONVERSATION_TITLE
|
||||
? {
|
||||
...WELCOME_CONVERSATION,
|
||||
messages: !isFlyoutMode ? WELCOME_CONVERSATION.messages : [],
|
||||
}
|
||||
? WELCOME_CONVERSATION
|
||||
: {
|
||||
...DEFAULT_CONVERSATION_STATE,
|
||||
id: '',
|
||||
|
|
|
@ -13,35 +13,7 @@ export const WELCOME_CONVERSATION: Conversation = {
|
|||
id: '',
|
||||
title: WELCOME_CONVERSATION_TITLE,
|
||||
category: 'assistant',
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: i18n.WELCOME_GENERAL,
|
||||
timestamp: '',
|
||||
presentation: {
|
||||
delay: 2 * 1000,
|
||||
stream: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: i18n.WELCOME_GENERAL_2,
|
||||
timestamp: '',
|
||||
presentation: {
|
||||
delay: 1000,
|
||||
stream: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: i18n.WELCOME_GENERAL_3,
|
||||
timestamp: '',
|
||||
presentation: {
|
||||
delay: 1000,
|
||||
stream: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
messages: [],
|
||||
replacements: {},
|
||||
excludeFromLastConversationStorage: true,
|
||||
};
|
||||
|
|
|
@ -73,7 +73,6 @@ export interface AssistantProviderProps {
|
|||
showAnonymizedValues: boolean;
|
||||
setIsStreaming: (isStreaming: boolean) => void;
|
||||
currentUserAvatar?: UserAvatar;
|
||||
isFlyoutMode: boolean;
|
||||
}) => EuiCommentProps[];
|
||||
http: HttpSetup;
|
||||
baseConversations: Record<string, Conversation>;
|
||||
|
@ -114,7 +113,6 @@ export interface UseAssistantContext {
|
|||
showAnonymizedValues: boolean;
|
||||
currentUserAvatar?: UserAvatar;
|
||||
setIsStreaming: (isStreaming: boolean) => void;
|
||||
isFlyoutMode: boolean;
|
||||
}) => EuiCommentProps[];
|
||||
http: HttpSetup;
|
||||
knowledgeBase: KnowledgeBaseConfig;
|
||||
|
@ -234,9 +232,7 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
/**
|
||||
* Global Assistant Overlay actions
|
||||
*/
|
||||
const [showAssistantOverlay, setShowAssistantOverlay] = useState<ShowAssistantOverlay>(
|
||||
(showAssistant) => {}
|
||||
);
|
||||
const [showAssistantOverlay, setShowAssistantOverlay] = useState<ShowAssistantOverlay>(() => {});
|
||||
|
||||
/**
|
||||
* Settings State
|
||||
|
|
|
@ -30,7 +30,6 @@ describe('connectorMissingCallout', () => {
|
|||
isConnectorConfigured={false}
|
||||
isSettingsModalVisible={false}
|
||||
setIsSettingsModalVisible={jest.fn()}
|
||||
isFlyoutMode={false}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -45,7 +44,6 @@ describe('connectorMissingCallout', () => {
|
|||
isConnectorConfigured={true}
|
||||
isSettingsModalVisible={false}
|
||||
setIsSettingsModalVisible={jest.fn()}
|
||||
isFlyoutMode={false}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -70,7 +68,6 @@ describe('connectorMissingCallout', () => {
|
|||
isConnectorConfigured={true}
|
||||
isSettingsModalVisible={false}
|
||||
setIsSettingsModalVisible={jest.fn()}
|
||||
isFlyoutMode={false}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
|
|
@ -20,7 +20,6 @@ interface Props {
|
|||
isConnectorConfigured: boolean;
|
||||
isSettingsModalVisible: boolean;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,7 +30,7 @@ interface Props {
|
|||
* TODO: Add setting for 'default connector' so we can auto-resolve and not even show this
|
||||
*/
|
||||
export const ConnectorMissingCallout: React.FC<Props> = React.memo(
|
||||
({ isConnectorConfigured, isSettingsModalVisible, setIsSettingsModalVisible, isFlyoutMode }) => {
|
||||
({ isConnectorConfigured, isSettingsModalVisible, setIsSettingsModalVisible }) => {
|
||||
const { assistantAvailability, setSelectedSettingsTab } = useAssistantContext();
|
||||
|
||||
const onConversationSettingsClicked = useCallback(() => {
|
||||
|
@ -55,13 +54,10 @@ export const ConnectorMissingCallout: React.FC<Props> = React.memo(
|
|||
iconType="controlsVertical"
|
||||
size="m"
|
||||
title={i18n.MISSING_CONNECTOR_CALLOUT_TITLE}
|
||||
css={
|
||||
isFlyoutMode &&
|
||||
css`
|
||||
padding-left: ${euiLightVars.euiPanelPaddingModifiers.paddingMedium} !important;
|
||||
padding-right: ${euiLightVars.euiPanelPaddingModifiers.paddingMedium} !important;
|
||||
`
|
||||
}
|
||||
css={css`
|
||||
padding-left: ${euiLightVars.euiPanelPaddingModifiers.paddingMedium} !important;
|
||||
padding-right: ${euiLightVars.euiPanelPaddingModifiers.paddingMedium} !important;
|
||||
`}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -18,7 +18,6 @@ const defaultProps = {
|
|||
onConnectorSelectionChange,
|
||||
selectedConnectorId: 'connectorId',
|
||||
setIsOpen,
|
||||
isFlyoutMode: false,
|
||||
};
|
||||
|
||||
const connectorTwo = mockConnectors[1];
|
||||
|
@ -64,14 +63,14 @@ describe('Connector selector', () => {
|
|||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('renders empty selection if no selected connector is provided', () => {
|
||||
it('renders add new connector button if no selected connector is provided', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConnectorSelector {...defaultProps} selectedConnectorId={undefined} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId('connector-selector')).toBeInTheDocument();
|
||||
expect(getByTestId('connector-selector')).toHaveTextContent('');
|
||||
fireEvent.click(getByTestId('connector-selector'));
|
||||
expect(getByTestId('addNewConnectorButton')).toBeInTheDocument();
|
||||
});
|
||||
it('renders with provided selected connector', () => {
|
||||
const { getByTestId } = render(
|
||||
|
|
|
@ -30,7 +30,6 @@ interface Props {
|
|||
selectedConnectorId?: string;
|
||||
displayFancy?: (displayText: string) => React.ReactNode;
|
||||
setIsOpen?: (isOpen: boolean) => void;
|
||||
isFlyoutMode: boolean;
|
||||
stats?: AttackDiscoveryStats | null;
|
||||
}
|
||||
|
||||
|
@ -47,7 +46,6 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
selectedConnectorId,
|
||||
onConnectorSelectionChange,
|
||||
setIsOpen,
|
||||
isFlyoutMode,
|
||||
stats = null,
|
||||
}) => {
|
||||
const { actionTypeRegistry, http, assistantAvailability } = useAssistantContext();
|
||||
|
@ -177,7 +175,7 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
|
||||
return (
|
||||
<>
|
||||
{isFlyoutMode && !connectorExists && !connectorOptions.length ? (
|
||||
{!connectorExists && !connectorOptions.length ? (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="addNewConnectorButton"
|
||||
iconType="plusInCircle"
|
||||
|
|
|
@ -11,7 +11,6 @@ import { fireEvent, render } from '@testing-library/react';
|
|||
import { TestProviders } from '../../mock/test_providers/test_providers';
|
||||
import { mockConnectors } from '../../mock/connectors';
|
||||
import { ConnectorSelectorInline } from './connector_selector_inline';
|
||||
import * as i18n from '../translations';
|
||||
import { Conversation } from '../../..';
|
||||
import { useLoadConnectors } from '../use_load_connectors';
|
||||
|
||||
|
@ -41,7 +40,7 @@ jest.mock('@kbn/triggers-actions-ui-plugin/public/common/constants', () => ({
|
|||
jest.mock('../use_load_connectors', () => ({
|
||||
useLoadConnectors: jest.fn(() => {
|
||||
return {
|
||||
data: [],
|
||||
data: mockConnectors,
|
||||
error: null,
|
||||
isSuccess: true,
|
||||
};
|
||||
|
@ -68,67 +67,61 @@ describe('ConnectorSelectorInline', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
it('renders empty view if no selected conversation is provided', () => {
|
||||
const { getByText } = render(
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConnectorSelectorInline
|
||||
isDisabled={false}
|
||||
selectedConnectorId={undefined}
|
||||
selectedConversation={undefined}
|
||||
isFlyoutMode={false}
|
||||
onConnectorSelected={jest.fn()}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByText(i18n.INLINE_CONNECTOR_PLACEHOLDER)).toBeInTheDocument();
|
||||
fireEvent.click(getByTestId('connector-selector'));
|
||||
expect(getByTestId('addNewConnectorButton')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders empty view if selectedConnectorId is NOT in list of connectors', () => {
|
||||
const { getByText } = render(
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConnectorSelectorInline
|
||||
isDisabled={false}
|
||||
selectedConnectorId={'missing-connector-id'}
|
||||
selectedConversation={defaultConvo}
|
||||
isFlyoutMode={false}
|
||||
onConnectorSelected={jest.fn()}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByText(i18n.INLINE_CONNECTOR_PLACEHOLDER)).toBeInTheDocument();
|
||||
fireEvent.click(getByTestId('connector-selector'));
|
||||
expect(getByTestId('addNewConnectorButton')).toBeInTheDocument();
|
||||
});
|
||||
it('Clicking add connector button opens the connector selector', () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
it('renders the connector selector', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConnectorSelectorInline
|
||||
isDisabled={false}
|
||||
selectedConnectorId={'missing-connector-id'}
|
||||
selectedConnectorId={mockConnectors[0].id}
|
||||
selectedConversation={defaultConvo}
|
||||
isFlyoutMode={false}
|
||||
onConnectorSelected={jest.fn()}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(queryByTestId('connector-selector')).not.toBeInTheDocument();
|
||||
fireEvent.click(getByTestId('connectorSelectorPlaceholderButton'));
|
||||
expect(getByTestId('connector-selector')).toBeInTheDocument();
|
||||
});
|
||||
it('On connector change, update conversation API config', () => {
|
||||
const connectorTwo = mockConnectors[1];
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConnectorSelectorInline
|
||||
isDisabled={false}
|
||||
selectedConnectorId={'missing-connector-id'}
|
||||
selectedConnectorId={mockConnectors[0].id}
|
||||
selectedConversation={defaultConvo}
|
||||
isFlyoutMode={false}
|
||||
onConnectorSelected={jest.fn()}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
fireEvent.click(getByTestId('connectorSelectorPlaceholderButton'));
|
||||
fireEvent.click(getByTestId('connector-selector'));
|
||||
fireEvent.click(getByTestId(connectorTwo.id));
|
||||
expect(queryByTestId('connector-selector')).not.toBeInTheDocument();
|
||||
expect(setApiConfig).toHaveBeenCalledWith({
|
||||
apiConfig: {
|
||||
actionTypeId: '.gen-ai',
|
||||
|
@ -151,16 +144,13 @@ describe('ConnectorSelectorInline', () => {
|
|||
<TestProviders>
|
||||
<ConnectorSelectorInline
|
||||
isDisabled={false}
|
||||
selectedConnectorId={'missing-connector-id'}
|
||||
selectedConnectorId={mockConnectors[0].id}
|
||||
selectedConversation={defaultConvo}
|
||||
isFlyoutMode={false}
|
||||
onConnectorSelected={jest.fn()}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
fireEvent.click(getByTestId('connectorSelectorPlaceholderButton'));
|
||||
fireEvent.click(getByTestId('connector-selector'));
|
||||
fireEvent.click(getByTestId('addNewConnectorButton'));
|
||||
expect(getByTestId('connector-selector')).toBeInTheDocument();
|
||||
expect(setApiConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
|
@ -13,8 +13,6 @@ import { euiThemeVars } from '@kbn/ui-theme';
|
|||
import type { AttackDiscoveryStats } from '@kbn/elastic-assistant-common';
|
||||
import { AIConnector, ConnectorSelector } from '../connector_selector';
|
||||
import { Conversation } from '../../..';
|
||||
import { useLoadConnectors } from '../use_load_connectors';
|
||||
import * as i18n from '../translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { useConversation } from '../../assistant/use_conversation';
|
||||
import { getGenAiConfig } from '../helpers';
|
||||
|
@ -25,7 +23,6 @@ interface Props {
|
|||
isDisabled?: boolean;
|
||||
selectedConnectorId?: string;
|
||||
selectedConversation?: Conversation;
|
||||
isFlyoutMode: boolean;
|
||||
onConnectorIdSelected?: (connectorId: string) => void;
|
||||
onConnectorSelected?: (conversation: Conversation) => void;
|
||||
stats?: AttackDiscoveryStats | null;
|
||||
|
@ -53,14 +50,6 @@ const inputDisplayClassName = css`
|
|||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const placeholderButtonClassName = css`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 400px;
|
||||
font-weight: normal;
|
||||
padding: 0 14px 0 0;
|
||||
`;
|
||||
|
||||
/**
|
||||
* A compact wrapper of the ConnectorSelector component used in the Settings modal.
|
||||
*/
|
||||
|
@ -69,29 +58,16 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
isDisabled = false,
|
||||
selectedConnectorId,
|
||||
selectedConversation,
|
||||
isFlyoutMode,
|
||||
|
||||
onConnectorIdSelected,
|
||||
onConnectorSelected,
|
||||
stats = null,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const { assistantAvailability, http } = useAssistantContext();
|
||||
const { assistantAvailability } = useAssistantContext();
|
||||
const { setApiConfig } = useConversation();
|
||||
|
||||
const { data: aiConnectors } = useLoadConnectors({
|
||||
http,
|
||||
});
|
||||
|
||||
const selectedConnectorName =
|
||||
(aiConnectors ?? []).find((c) => c.id === selectedConnectorId)?.name ??
|
||||
i18n.INLINE_CONNECTOR_PLACEHOLDER;
|
||||
const localIsDisabled = isDisabled || !assistantAvailability.hasConnectorsReadPrivilege;
|
||||
|
||||
const onConnectorClick = useCallback(() => {
|
||||
setIsOpen(!isOpen);
|
||||
}, [isOpen]);
|
||||
|
||||
const onChange = useCallback(
|
||||
async (connector: AIConnector) => {
|
||||
const connectorId = connector.id;
|
||||
|
@ -129,40 +105,6 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
[selectedConversation, setApiConfig, onConnectorIdSelected, onConnectorSelected]
|
||||
);
|
||||
|
||||
if (isFlyoutMode) {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
className={inputContainerClassName}
|
||||
direction="row"
|
||||
gutterSize="xs"
|
||||
justifyContent={'flexStart'}
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<ConnectorSelector
|
||||
displayFancy={(displayText) => (
|
||||
<EuiText
|
||||
className={inputDisplayClassName}
|
||||
size="s"
|
||||
color={euiThemeVars.euiColorPrimaryText}
|
||||
>
|
||||
{displayText}
|
||||
</EuiText>
|
||||
)}
|
||||
isOpen={isOpen}
|
||||
isDisabled={localIsDisabled}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
setIsOpen={setIsOpen}
|
||||
onConnectorSelectionChange={onChange}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
stats={stats}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
|
@ -173,36 +115,23 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
{isOpen ? (
|
||||
<ConnectorSelector
|
||||
displayFancy={(displayText) => (
|
||||
<EuiText className={inputDisplayClassName} size="xs">
|
||||
{displayText}
|
||||
</EuiText>
|
||||
)}
|
||||
isOpen
|
||||
isDisabled={localIsDisabled}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
setIsOpen={setIsOpen}
|
||||
onConnectorSelectionChange={onChange}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
stats={stats}
|
||||
/>
|
||||
) : (
|
||||
<span>
|
||||
<EuiButtonEmpty
|
||||
className={placeholderButtonClassName}
|
||||
data-test-subj="connectorSelectorPlaceholderButton"
|
||||
iconSide={'right'}
|
||||
iconType="arrowDown"
|
||||
isDisabled={localIsDisabled}
|
||||
onClick={onConnectorClick}
|
||||
size={'xs'}
|
||||
<ConnectorSelector
|
||||
displayFancy={(displayText) => (
|
||||
<EuiText
|
||||
className={inputDisplayClassName}
|
||||
size="s"
|
||||
color={euiThemeVars.euiColorPrimaryText}
|
||||
>
|
||||
{selectedConnectorName}
|
||||
</EuiButtonEmpty>
|
||||
</span>
|
||||
)}
|
||||
{displayText}
|
||||
</EuiText>
|
||||
)}
|
||||
isOpen={isOpen}
|
||||
isDisabled={localIsDisabled}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
setIsOpen={setIsOpen}
|
||||
onConnectorSelectionChange={onChange}
|
||||
stats={stats}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Conversation } from '../../assistant_context/types';
|
||||
|
||||
/**
|
||||
* Removes all presentation data from the conversation
|
||||
* @param conversation
|
||||
*/
|
||||
export const clearPresentationData = (conversation: Conversation): Conversation => {
|
||||
const { messages, ...restConversation } = conversation;
|
||||
return {
|
||||
...restConversation,
|
||||
messages: messages.map((message) => {
|
||||
const { presentation, ...restMessages } = message;
|
||||
return {
|
||||
...restMessages,
|
||||
presentation: undefined,
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the conversation has no presentation data
|
||||
* @param conversation
|
||||
*/
|
||||
export const conversationHasNoPresentationData = (conversation: Conversation): boolean =>
|
||||
!conversation.messages.some((message) => message.presentation !== undefined);
|
|
@ -6,19 +6,15 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useConnectorSetup } from '.';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { welcomeConvo } from '../../mock/conversation';
|
||||
import { TestProviders } from '../../mock/test_providers/test_providers';
|
||||
import { EuiCommentList } from '@elastic/eui';
|
||||
import { ConnectorSetup } from '.';
|
||||
|
||||
const onSetupComplete = jest.fn();
|
||||
const onConversationUpdate = jest.fn();
|
||||
|
||||
const defaultProps = {
|
||||
conversation: welcomeConvo,
|
||||
onSetupComplete,
|
||||
onConversationUpdate,
|
||||
};
|
||||
const newConnector = { actionTypeId: '.gen-ai', name: 'cool name' };
|
||||
|
@ -50,121 +46,40 @@ jest.mock('../../assistant/use_conversation', () => ({
|
|||
}));
|
||||
|
||||
jest.spyOn(global, 'clearTimeout');
|
||||
describe('useConnectorSetup', () => {
|
||||
describe('ConnectorSetup', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should render comments and prompts', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useConnectorSetup(defaultProps), {
|
||||
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||
});
|
||||
await waitForNextUpdate();
|
||||
expect(
|
||||
result.current.comments.map((c) => ({ username: c.username, timestamp: c.timestamp }))
|
||||
).toEqual([
|
||||
{
|
||||
username: 'You',
|
||||
timestamp: `at: ${new Date('2024-03-18T18:59:18.174Z').toLocaleString()}`,
|
||||
},
|
||||
{
|
||||
username: 'Assistant',
|
||||
timestamp: `at: ${new Date('2024-03-19T18:59:18.174Z').toLocaleString()}`,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result.current.prompt.props['data-test-subj']).toEqual('prompt');
|
||||
it('should render action type selector', async () => {
|
||||
const { getByTestId } = render(<ConnectorSetup {...defaultProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(getByTestId('modal-mock')).toBeInTheDocument();
|
||||
});
|
||||
it('should set api config for each conversation when new connector is saved', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useConnectorSetup(defaultProps), {
|
||||
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||
});
|
||||
await waitForNextUpdate();
|
||||
const { getByTestId, queryByTestId, rerender } = render(result.current.prompt, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(getByTestId('connectorButton')).toBeInTheDocument();
|
||||
expect(queryByTestId('skip-setup-button')).not.toBeInTheDocument();
|
||||
fireEvent.click(getByTestId('connectorButton'));
|
||||
|
||||
rerender(result.current.prompt);
|
||||
fireEvent.click(getByTestId('modal-mock'));
|
||||
expect(setApiConfig).toHaveBeenCalledTimes(1);
|
||||
it('should set api config for each conversation when new connector is saved', async () => {
|
||||
const { getByTestId } = render(<ConnectorSetup {...defaultProps} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
fireEvent.click(getByTestId('modal-mock'));
|
||||
expect(setApiConfig).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should NOT set the api config for each conversation when a new connector is saved and updateConversationsOnSaveConnector is false', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() =>
|
||||
useConnectorSetup({
|
||||
...defaultProps,
|
||||
updateConversationsOnSaveConnector: false, // <-- don't update the conversations
|
||||
}),
|
||||
{
|
||||
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||
}
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
const { getByTestId, queryByTestId, rerender } = render(result.current.prompt, {
|
||||
const { getByTestId } = render(
|
||||
<ConnectorSetup
|
||||
{...defaultProps}
|
||||
updateConversationsOnSaveConnector={false} // <-- don't update the conversations
|
||||
/>,
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(getByTestId('connectorButton')).toBeInTheDocument();
|
||||
expect(queryByTestId('skip-setup-button')).not.toBeInTheDocument();
|
||||
fireEvent.click(getByTestId('connectorButton'));
|
||||
}
|
||||
);
|
||||
|
||||
rerender(result.current.prompt);
|
||||
fireEvent.click(getByTestId('modal-mock'));
|
||||
fireEvent.click(getByTestId('modal-mock'));
|
||||
|
||||
expect(setApiConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show skip button if message has presentation data', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() =>
|
||||
useConnectorSetup({
|
||||
...defaultProps,
|
||||
conversation: {
|
||||
...defaultProps.conversation,
|
||||
messages: [
|
||||
{
|
||||
...defaultProps.conversation.messages[0],
|
||||
presentation: {
|
||||
delay: 0,
|
||||
stream: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||
}
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
const { getByTestId, queryByTestId } = render(result.current.prompt, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(getByTestId('skip-setup-button')).toBeInTheDocument();
|
||||
expect(queryByTestId('connectorButton')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
it('should call onSetupComplete and setConversations when onHandleMessageStreamingComplete', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useConnectorSetup(defaultProps), {
|
||||
wrapper: ({ children }) => <TestProviders>{children}</TestProviders>,
|
||||
});
|
||||
await waitForNextUpdate();
|
||||
render(<EuiCommentList comments={result.current.comments} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(clearTimeout).toHaveBeenCalled();
|
||||
expect(onSetupComplete).toHaveBeenCalled();
|
||||
});
|
||||
expect(setApiConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,181 +5,44 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import type { EuiCommentProps } from '@elastic/eui';
|
||||
import { EuiAvatar, EuiBadge, EuiMarkdownFormat, EuiText, EuiTextAlign } from '@elastic/eui';
|
||||
import styled from '@emotion/styled';
|
||||
import { css } from '@emotion/react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/common/constants';
|
||||
|
||||
import { ActionType } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { AddConnectorModal } from '../add_connector_modal';
|
||||
import { WELCOME_CONVERSATION } from '../../assistant/use_conversation/sample_conversations';
|
||||
import { Conversation, ClientMessage } from '../../..';
|
||||
import { Conversation } from '../../..';
|
||||
import { useLoadActionTypes } from '../use_load_action_types';
|
||||
import { StreamingText } from '../../assistant/streaming_text';
|
||||
import { ConnectorButton } from '../connector_button';
|
||||
import { useConversation } from '../../assistant/use_conversation';
|
||||
import { conversationHasNoPresentationData } from './helpers';
|
||||
import * as i18n from '../translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { useLoadConnectors } from '../use_load_connectors';
|
||||
import { AssistantAvatar } from '../../assistant/assistant_avatar/assistant_avatar';
|
||||
import { getGenAiConfig } from '../helpers';
|
||||
|
||||
const ConnectorButtonWrapper = styled.div`
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
export interface ConnectorSetupProps {
|
||||
conversation?: Conversation;
|
||||
isFlyoutMode?: boolean;
|
||||
onSetupComplete?: () => void;
|
||||
onConversationUpdate?: ({ cId, cTitle }: { cId: string; cTitle: string }) => Promise<void>;
|
||||
updateConversationsOnSaveConnector?: boolean;
|
||||
}
|
||||
|
||||
export const useConnectorSetup = ({
|
||||
export const ConnectorSetup = ({
|
||||
conversation: defaultConversation,
|
||||
isFlyoutMode,
|
||||
onSetupComplete,
|
||||
onConversationUpdate,
|
||||
updateConversationsOnSaveConnector = true,
|
||||
}: ConnectorSetupProps): {
|
||||
comments: EuiCommentProps[];
|
||||
prompt: React.ReactElement;
|
||||
} => {
|
||||
}: ConnectorSetupProps) => {
|
||||
const conversation = useMemo(
|
||||
() =>
|
||||
defaultConversation || {
|
||||
...WELCOME_CONVERSATION,
|
||||
messages: !isFlyoutMode ? WELCOME_CONVERSATION.messages : [],
|
||||
},
|
||||
[defaultConversation, isFlyoutMode]
|
||||
() => defaultConversation || WELCOME_CONVERSATION,
|
||||
[defaultConversation]
|
||||
);
|
||||
const { setApiConfig } = useConversation();
|
||||
const bottomRef = useRef<HTMLDivElement | null>(null);
|
||||
// Access all conversations so we can add connector to all on initial setup
|
||||
const { actionTypeRegistry, http } = useAssistantContext();
|
||||
|
||||
const {
|
||||
data: connectors,
|
||||
isSuccess: areConnectorsFetched,
|
||||
refetch: refetchConnectors,
|
||||
} = useLoadConnectors({ http });
|
||||
const isConnectorConfigured = areConnectorsFetched && !!connectors?.length;
|
||||
const { refetch: refetchConnectors } = useLoadConnectors({ http });
|
||||
|
||||
const [isConnectorModalVisible, setIsConnectorModalVisible] = useState<boolean>(false);
|
||||
const [showAddConnectorButton, setShowAddConnectorButton] = useState<boolean>(() => {
|
||||
// If no presentation data on messages, default to showing add connector button so it doesn't delay render and flash on screen
|
||||
return conversationHasNoPresentationData(conversation);
|
||||
});
|
||||
const { data: actionTypes } = useLoadActionTypes({ http });
|
||||
|
||||
const [selectedActionType, setSelectedActionType] = useState<ActionType | null>(null);
|
||||
|
||||
const lastConversationMessageIndex = useMemo(
|
||||
() => conversation.messages.length - 1,
|
||||
[conversation.messages.length]
|
||||
);
|
||||
|
||||
const [currentMessageIndex, setCurrentMessageIndex] = useState(
|
||||
// If connector is configured or conversation has already been replayed show all messages immediately
|
||||
isConnectorConfigured || conversationHasNoPresentationData(conversation)
|
||||
? lastConversationMessageIndex
|
||||
: 0
|
||||
);
|
||||
|
||||
const streamingTimeoutRef = useRef<number | undefined>(undefined);
|
||||
|
||||
// Once streaming of previous message is complete, proceed to next message
|
||||
const onHandleMessageStreamingComplete = useCallback(() => {
|
||||
if (currentMessageIndex === lastConversationMessageIndex) {
|
||||
clearTimeout(streamingTimeoutRef.current);
|
||||
return;
|
||||
}
|
||||
streamingTimeoutRef.current = window.setTimeout(() => {
|
||||
bottomRef.current?.scrollIntoView({ block: 'end' });
|
||||
return setCurrentMessageIndex(currentMessageIndex + 1);
|
||||
}, conversation.messages[currentMessageIndex]?.presentation?.delay ?? 0);
|
||||
return () => clearTimeout(streamingTimeoutRef.current);
|
||||
}, [conversation.messages, currentMessageIndex, lastConversationMessageIndex]);
|
||||
|
||||
// Show button to add connector after last message has finished streaming
|
||||
const onHandleLastMessageStreamingComplete = useCallback(() => {
|
||||
setShowAddConnectorButton(true);
|
||||
bottomRef.current?.scrollIntoView({ block: 'end' });
|
||||
onSetupComplete?.();
|
||||
}, [onSetupComplete]);
|
||||
|
||||
// Show button to add connector after last message has finished streaming
|
||||
const handleSkipSetup = useCallback(() => {
|
||||
setCurrentMessageIndex(lastConversationMessageIndex);
|
||||
}, [lastConversationMessageIndex]);
|
||||
|
||||
// Create EuiCommentProps[] from conversation messages
|
||||
const commentBody = useCallback(
|
||||
(message: ClientMessage, index: number, length: number) => {
|
||||
// If timestamp is not set, set it to current time (will update conversation at end of setup)
|
||||
if (
|
||||
conversation.messages[index].timestamp == null ||
|
||||
conversation.messages[index].timestamp.length === 0
|
||||
) {
|
||||
conversation.messages[index].timestamp = new Date().toISOString();
|
||||
}
|
||||
const isLastMessage = index === length - 1;
|
||||
const enableStreaming =
|
||||
(message?.presentation?.stream ?? false) && currentMessageIndex !== length - 1;
|
||||
return (
|
||||
<StreamingText
|
||||
text={message.content ?? ''}
|
||||
delay={enableStreaming ? 50 : 0}
|
||||
onStreamingComplete={
|
||||
isLastMessage ? onHandleLastMessageStreamingComplete : onHandleMessageStreamingComplete
|
||||
}
|
||||
>
|
||||
{(streamedText, isStreamingComplete) => (
|
||||
<EuiText>
|
||||
<EuiMarkdownFormat className={`message-${index}`}>{streamedText}</EuiMarkdownFormat>
|
||||
<span ref={bottomRef} />
|
||||
</EuiText>
|
||||
)}
|
||||
</StreamingText>
|
||||
);
|
||||
},
|
||||
[
|
||||
conversation.messages,
|
||||
currentMessageIndex,
|
||||
onHandleLastMessageStreamingComplete,
|
||||
onHandleMessageStreamingComplete,
|
||||
]
|
||||
);
|
||||
|
||||
const comments = useMemo(
|
||||
() =>
|
||||
conversation.messages.slice(0, currentMessageIndex + 1).map((message, index) => {
|
||||
const isUser = message.role === 'user';
|
||||
const timestamp = `${i18n.CONNECTOR_SETUP_TIMESTAMP_AT}: ${new Date(
|
||||
message.timestamp
|
||||
).toLocaleString()}`;
|
||||
const commentProps: EuiCommentProps = {
|
||||
username: isUser ? i18n.CONNECTOR_SETUP_USER_YOU : i18n.CONNECTOR_SETUP_USER_ASSISTANT,
|
||||
children: commentBody(message, index, conversation.messages.length),
|
||||
timelineAvatar: (
|
||||
<EuiAvatar
|
||||
name={i18n.CONNECTOR_SETUP_USER_ASSISTANT}
|
||||
size="l"
|
||||
color="subdued"
|
||||
iconType={AssistantAvatar}
|
||||
/>
|
||||
),
|
||||
timestamp,
|
||||
};
|
||||
return commentProps;
|
||||
}),
|
||||
[commentBody, conversation.messages, currentMessageIndex]
|
||||
);
|
||||
|
||||
const onSaveConnector = useCallback(
|
||||
async (connector: ActionConnector) => {
|
||||
if (updateConversationsOnSaveConnector) {
|
||||
|
@ -204,7 +67,6 @@ export const useConnectorSetup = ({
|
|||
});
|
||||
|
||||
refetchConnectors?.();
|
||||
setIsConnectorModalVisible(false);
|
||||
}
|
||||
} else {
|
||||
refetchConnectors?.();
|
||||
|
@ -221,65 +83,17 @@ export const useConnectorSetup = ({
|
|||
|
||||
const handleClose = useCallback(() => {
|
||||
setSelectedActionType(null);
|
||||
setIsConnectorModalVisible(false);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
comments: isFlyoutMode ? [] : comments,
|
||||
prompt: isFlyoutMode ? (
|
||||
<div data-test-subj="prompt">
|
||||
<AddConnectorModal
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
actionTypes={actionTypes}
|
||||
onClose={handleClose}
|
||||
onSaveConnector={onSaveConnector}
|
||||
onSelectActionType={setSelectedActionType}
|
||||
selectedActionType={selectedActionType}
|
||||
actionTypeSelectorInline={true}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div data-test-subj="prompt">
|
||||
{showAddConnectorButton && (
|
||||
<ConnectorButtonWrapper>
|
||||
<ConnectorButton setIsConnectorModalVisible={setIsConnectorModalVisible} />
|
||||
</ConnectorButtonWrapper>
|
||||
)}
|
||||
{!showAddConnectorButton && (
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size={'xs'}
|
||||
css={
|
||||
!isFlyoutMode
|
||||
? css`
|
||||
margin-top: 20px;
|
||||
`
|
||||
: null
|
||||
}
|
||||
>
|
||||
<EuiTextAlign textAlign="center">
|
||||
<EuiBadge
|
||||
color="hollow"
|
||||
data-test-subj="skip-setup-button"
|
||||
onClick={handleSkipSetup}
|
||||
onClickAriaLabel={i18n.CONNECTOR_SETUP_SKIP}
|
||||
>
|
||||
{i18n.CONNECTOR_SETUP_SKIP}
|
||||
</EuiBadge>
|
||||
</EuiTextAlign>
|
||||
</EuiText>
|
||||
)}
|
||||
{isConnectorModalVisible && (
|
||||
<AddConnectorModal
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
actionTypes={actionTypes}
|
||||
onClose={handleClose}
|
||||
onSaveConnector={onSaveConnector}
|
||||
onSelectActionType={setSelectedActionType}
|
||||
selectedActionType={selectedActionType}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
return (
|
||||
<AddConnectorModal
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
actionTypes={actionTypes}
|
||||
onClose={handleClose}
|
||||
onSaveConnector={onSaveConnector}
|
||||
onSelectActionType={setSelectedActionType}
|
||||
selectedActionType={selectedActionType}
|
||||
actionTypeSelectorInline={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,30 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const WELCOME_GENERAL = i18n.translate(
|
||||
'xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneralPrompt',
|
||||
{
|
||||
defaultMessage:
|
||||
'Welcome to your Elastic AI Assistant! I am your 100% open-code portal into your Elastic life. In time, I will be able to answer questions and provide assistance across all your information in Elastic, and oh-so much more. Till then, I hope this early preview will open your mind to the possibilities of what we can create when we work together, in the open. Cheers!',
|
||||
}
|
||||
);
|
||||
|
||||
export const WELCOME_GENERAL_2 = i18n.translate(
|
||||
'xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneral2Prompt',
|
||||
{
|
||||
defaultMessage:
|
||||
"First things first, we'll need to set up a Generative AI Connector to get this chat experience going! With the Generative AI Connector, you'll be able to configure access to either an OpenAI service or an Amazon Bedrock service, but you better believe you'll be able to deploy your own models within your Elastic Cloud instance and use those here in the future... 😉",
|
||||
}
|
||||
);
|
||||
|
||||
export const WELCOME_GENERAL_3 = i18n.translate(
|
||||
'xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneral3Prompt',
|
||||
{
|
||||
defaultMessage:
|
||||
'Go ahead and click the add connector button below to continue the conversation!',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENTERPRISE = i18n.translate(
|
||||
'xpack.elasticAssistant.securityAssistant.content.prompts.welcome.enterprisePrompt',
|
||||
{
|
||||
|
|
|
@ -48,6 +48,7 @@ const SelectedPromptContextPreviewComponent = ({
|
|||
|
||||
return (
|
||||
<EuiText
|
||||
data-test-subj="selectedPromptContextPreview"
|
||||
color="subdued"
|
||||
size="xs"
|
||||
css={css`
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { SelectedPromptContext } from '../assistant/prompt_context/types';
|
||||
import { TestProviders } from '../mock/test_providers/test_providers';
|
||||
|
@ -38,21 +37,6 @@ describe('DataAnonymizationEditor', () => {
|
|||
rawData: 'test-raw-data',
|
||||
};
|
||||
|
||||
it('renders stats', () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<DataAnonymizationEditor
|
||||
selectedPromptContext={mockSelectedPromptContext}
|
||||
setSelectedPromptContexts={jest.fn()}
|
||||
currentReplacements={{}}
|
||||
isFlyoutMode={false}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('stats')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('when rawData is a string (non-anonymized data)', () => {
|
||||
it('renders the ReadOnlyContextViewer when rawData is (non-anonymized data)', () => {
|
||||
render(
|
||||
|
@ -61,7 +45,6 @@ describe('DataAnonymizationEditor', () => {
|
|||
selectedPromptContext={mockSelectedPromptContext}
|
||||
setSelectedPromptContexts={jest.fn()}
|
||||
currentReplacements={{}}
|
||||
isFlyoutMode={false}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -76,7 +59,6 @@ describe('DataAnonymizationEditor', () => {
|
|||
selectedPromptContext={mockSelectedPromptContext}
|
||||
setSelectedPromptContexts={jest.fn()}
|
||||
currentReplacements={{}}
|
||||
isFlyoutMode={false}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -105,24 +87,17 @@ describe('DataAnonymizationEditor', () => {
|
|||
selectedPromptContext={selectedPromptContextWithAnonymized}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
currentReplacements={{}}
|
||||
isFlyoutMode={false}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
});
|
||||
|
||||
it('renders the ContextEditor when rawData is anonymized data', () => {
|
||||
expect(screen.getByTestId('contextEditor')).toBeInTheDocument();
|
||||
it('renders the SelectedPromptContextPreview when rawData is anonymized data', () => {
|
||||
expect(screen.getByTestId('selectedPromptContextPreview')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does NOT render the ReadOnlyContextViewer when rawData is anonymized data', () => {
|
||||
expect(screen.queryByTestId('readOnlyContextViewer')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls setSelectedPromptContexts when a field is toggled', () => {
|
||||
userEvent.click(screen.getAllByTestId('allowed')[0]); // toggle the first field
|
||||
|
||||
expect(setSelectedPromptContexts).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { AnonymizedData } from '@kbn/elastic-assistant-common/impl/data_anonymization/types';
|
||||
|
@ -14,9 +14,7 @@ import { BatchUpdateListItem } from './context_editor/types';
|
|||
import { getIsDataAnonymizable, updateSelectedPromptContext } from './helpers';
|
||||
import { ReadOnlyContextViewer } from './read_only_context_viewer';
|
||||
import { ContextEditorFlyout } from './context_editor_flyout';
|
||||
import { ContextEditor } from './context_editor';
|
||||
import { ReplacementsContextViewer } from './replacements_context_viewer';
|
||||
import { Stats } from './stats';
|
||||
|
||||
const EditorContainer = styled.div`
|
||||
overflow-x: auto;
|
||||
|
@ -28,14 +26,12 @@ export interface Props {
|
|||
React.SetStateAction<Record<string, SelectedPromptContext>>
|
||||
>;
|
||||
currentReplacements: AnonymizedData['replacements'] | undefined;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
|
||||
const DataAnonymizationEditorComponent: React.FC<Props> = ({
|
||||
selectedPromptContext,
|
||||
setSelectedPromptContexts,
|
||||
currentReplacements,
|
||||
isFlyoutMode,
|
||||
}) => {
|
||||
const isDataAnonymizable = useMemo<boolean>(
|
||||
() => getIsDataAnonymizable(selectedPromptContext.rawData),
|
||||
|
@ -63,66 +59,27 @@ const DataAnonymizationEditorComponent: React.FC<Props> = ({
|
|||
[selectedPromptContext, setSelectedPromptContexts]
|
||||
);
|
||||
|
||||
if (isFlyoutMode) {
|
||||
return (
|
||||
<EditorContainer data-test-subj="dataAnonymizationEditor">
|
||||
<EuiPanel hasShadow={false} paddingSize="m">
|
||||
{typeof selectedPromptContext.rawData === 'string' ? (
|
||||
selectedPromptContext.replacements != null ? (
|
||||
<ReplacementsContextViewer
|
||||
markdown={selectedPromptContext.rawData}
|
||||
replacements={selectedPromptContext.replacements}
|
||||
/>
|
||||
) : (
|
||||
<ReadOnlyContextViewer rawData={selectedPromptContext.rawData} />
|
||||
)
|
||||
) : (
|
||||
<ContextEditorFlyout
|
||||
selectedPromptContext={selectedPromptContext}
|
||||
onListUpdated={onListUpdated}
|
||||
currentReplacements={currentReplacements}
|
||||
isDataAnonymizable={isDataAnonymizable}
|
||||
/>
|
||||
)}
|
||||
</EuiPanel>
|
||||
</EditorContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EditorContainer data-test-subj="dataAnonymizationEditor">
|
||||
<Stats
|
||||
isDataAnonymizable={isDataAnonymizable}
|
||||
anonymizationFields={selectedPromptContext.contextAnonymizationFields?.data}
|
||||
rawData={selectedPromptContext.rawData}
|
||||
replacements={selectedPromptContext.replacements}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
{typeof selectedPromptContext.rawData === 'string' ? (
|
||||
selectedPromptContext.replacements != null ? (
|
||||
<ReplacementsContextViewer
|
||||
markdown={selectedPromptContext.rawData}
|
||||
replacements={selectedPromptContext.replacements}
|
||||
/>
|
||||
<EuiPanel hasShadow={false} paddingSize="m">
|
||||
{typeof selectedPromptContext.rawData === 'string' ? (
|
||||
selectedPromptContext.replacements != null ? (
|
||||
<ReplacementsContextViewer
|
||||
markdown={selectedPromptContext.rawData}
|
||||
replacements={selectedPromptContext.replacements}
|
||||
/>
|
||||
) : (
|
||||
<ReadOnlyContextViewer rawData={selectedPromptContext.rawData} />
|
||||
)
|
||||
) : (
|
||||
<ReadOnlyContextViewer rawData={selectedPromptContext.rawData} />
|
||||
)
|
||||
) : (
|
||||
<ContextEditor
|
||||
anonymizationFields={
|
||||
selectedPromptContext.contextAnonymizationFields ?? {
|
||||
total: 0,
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
data: [],
|
||||
}
|
||||
}
|
||||
onListUpdated={onListUpdated}
|
||||
rawData={selectedPromptContext.rawData}
|
||||
/>
|
||||
)}
|
||||
<ContextEditorFlyout
|
||||
selectedPromptContext={selectedPromptContext}
|
||||
onListUpdated={onListUpdated}
|
||||
currentReplacements={currentReplacements}
|
||||
isDataAnonymizable={isDataAnonymizable}
|
||||
/>
|
||||
)}
|
||||
</EuiPanel>
|
||||
</EditorContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,13 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Replacements } from '@kbn/elastic-assistant-common';
|
||||
|
||||
/** This mock returns the reverse of `value` */
|
||||
export const mockGetAnonymizedValue = ({
|
||||
currentReplacements,
|
||||
rawValue,
|
||||
}: {
|
||||
currentReplacements: Replacements | undefined;
|
||||
rawValue: string;
|
||||
}): string => rawValue.split('').reverse().join('');
|
||||
export const mockGetAnonymizedValue = ({ rawValue }: { rawValue: string }): string =>
|
||||
rawValue.split('').reverse().join('');
|
||||
|
|
|
@ -134,37 +134,34 @@ export const getFlattenedBuckets = ({
|
|||
if (((isILMAvailable && ilmExplain != null) || !isILMAvailable) && stats != null) {
|
||||
return [
|
||||
...acc,
|
||||
...Object.entries(stats).reduce<FlattenedBucket[]>(
|
||||
(validStats, [indexName, indexStats]) => {
|
||||
const ilmPhase = getIlmPhase(ilmExplain?.[indexName], isILMAvailable);
|
||||
const isSelectedPhase =
|
||||
(isILMAvailable && ilmPhase != null && ilmPhasesMap[ilmPhase] != null) ||
|
||||
!isILMAvailable;
|
||||
...Object.entries(stats).reduce<FlattenedBucket[]>((validStats, [indexName]) => {
|
||||
const ilmPhase = getIlmPhase(ilmExplain?.[indexName], isILMAvailable);
|
||||
const isSelectedPhase =
|
||||
(isILMAvailable && ilmPhase != null && ilmPhasesMap[ilmPhase] != null) ||
|
||||
!isILMAvailable;
|
||||
|
||||
if (isSelectedPhase) {
|
||||
const incompatible =
|
||||
results != null && results[indexName] != null
|
||||
? results[indexName].incompatible
|
||||
: undefined;
|
||||
const sizeInBytes = getSizeInBytes({ indexName, stats });
|
||||
const docsCount = getDocsCount({ stats, indexName });
|
||||
return [
|
||||
...validStats,
|
||||
{
|
||||
ilmPhase,
|
||||
incompatible,
|
||||
indexName,
|
||||
pattern,
|
||||
sizeInBytes,
|
||||
docsCount,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return validStats;
|
||||
}
|
||||
},
|
||||
[]
|
||||
),
|
||||
if (isSelectedPhase) {
|
||||
const incompatible =
|
||||
results != null && results[indexName] != null
|
||||
? results[indexName].incompatible
|
||||
: undefined;
|
||||
const sizeInBytes = getSizeInBytes({ indexName, stats });
|
||||
const docsCount = getDocsCount({ stats, indexName });
|
||||
return [
|
||||
...validStats,
|
||||
{
|
||||
ilmPhase,
|
||||
incompatible,
|
||||
indexName,
|
||||
pattern,
|
||||
sizeInBytes,
|
||||
docsCount,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return validStats;
|
||||
}
|
||||
}, []),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -232,7 +229,7 @@ export const getLayersMultiDimensional = ({
|
|||
groupByRollup: (d: Datum) => d.indexName,
|
||||
nodeLabel: (indexName: Datum) => indexName,
|
||||
shape: {
|
||||
fillColor: (indexName: Key, sortIndex: number, node: Pick<ArrayNode, 'path'>) => {
|
||||
fillColor: (indexName: Key, _sortIndex: number, node: Pick<ArrayNode, 'path'>) => {
|
||||
const pattern = getGroupFromPath(node.path) ?? '';
|
||||
const flattenedBucket = pathToFlattenedBucketMap[`${pattern}${indexName}`];
|
||||
|
||||
|
|
|
@ -19,12 +19,11 @@ const Line2 = styled.span`
|
|||
const EMPTY = ' ';
|
||||
|
||||
interface Props {
|
||||
color?: string;
|
||||
line1?: string;
|
||||
line2?: string;
|
||||
}
|
||||
|
||||
export const StatLabel: React.FC<Props> = ({ color, line1 = EMPTY, line2 = EMPTY }) => (
|
||||
export const StatLabel: React.FC<Props> = ({ line1 = EMPTY, line2 = EMPTY }) => (
|
||||
<>
|
||||
<Line1 data-test-subj="line1">{line1}</Line1>
|
||||
<Line2 data-test-subj="line2">{line2}</Line2>
|
||||
|
|
|
@ -26,15 +26,10 @@ const EmptyPromptContainer = styled.div`
|
|||
|
||||
interface Props {
|
||||
indexName: string;
|
||||
onAddToNewCase: (markdownComments: string[]) => void;
|
||||
partitionedFieldMetadata: PartitionedFieldMetadata;
|
||||
}
|
||||
|
||||
const EcsCompliantTabComponent: React.FC<Props> = ({
|
||||
indexName,
|
||||
onAddToNewCase,
|
||||
partitionedFieldMetadata,
|
||||
}) => {
|
||||
const EcsCompliantTabComponent: React.FC<Props> = ({ indexName, partitionedFieldMetadata }) => {
|
||||
const emptyPromptBody = useMemo(() => <EmptyPromptBody body={i18n.ECS_COMPLIANT_EMPTY} />, []);
|
||||
const title = useMemo(() => <EmptyPromptTitle title={i18n.ECS_COMPLIANT_EMPTY_TITLE} />, []);
|
||||
|
||||
|
|
|
@ -212,11 +212,7 @@ export const getTabs = ({
|
|||
</EuiBadge>
|
||||
),
|
||||
content: (
|
||||
<EcsCompliantTab
|
||||
indexName={indexName}
|
||||
onAddToNewCase={onAddToNewCase}
|
||||
partitionedFieldMetadata={partitionedFieldMetadata}
|
||||
/>
|
||||
<EcsCompliantTab indexName={indexName} partitionedFieldMetadata={partitionedFieldMetadata} />
|
||||
),
|
||||
id: ECS_COMPLIANT_TAB_ID,
|
||||
name: i18n.ECS_COMPLIANT_FIELDS,
|
||||
|
|
|
@ -69,7 +69,7 @@ export const SolutionSideNavCategoryTitleStyles = (euiTheme: EuiThemeComputed<{}
|
|||
font-weight: ${euiTheme.font.weight.medium};
|
||||
`;
|
||||
|
||||
export const SolutionSideNavPanelLinksGroupStyles = (euiTheme: EuiThemeComputed<{}>) => css`
|
||||
export const SolutionSideNavPanelLinksGroupStyles = () => css`
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
`;
|
||||
|
|
|
@ -333,8 +333,7 @@ interface SolutionSideNavPanelItemsProps {
|
|||
*/
|
||||
const SolutionSideNavPanelItems: React.FC<SolutionSideNavPanelItemsProps> = React.memo(
|
||||
function SolutionSideNavPanelItems({ items, onClose }) {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const panelLinksGroupClassNames = classNames(SolutionSideNavPanelLinksGroupStyles(euiTheme));
|
||||
const panelLinksGroupClassNames = classNames(SolutionSideNavPanelLinksGroupStyles());
|
||||
return (
|
||||
<EuiListGroup className={panelLinksGroupClassNames}>
|
||||
{items.map((item) => (
|
||||
|
|
|
@ -242,11 +242,6 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
*/
|
||||
unifiedManifestEnabled: true,
|
||||
|
||||
/**
|
||||
* Enables Security AI Assistant's Flyout mode
|
||||
*/
|
||||
aiAssistantFlyoutMode: true,
|
||||
|
||||
/**
|
||||
* Enables the new modal for the value list items
|
||||
*/
|
||||
|
|
|
@ -23,16 +23,15 @@ import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experime
|
|||
|
||||
interface Props {
|
||||
message: ClientMessage;
|
||||
isFlyoutMode: boolean;
|
||||
}
|
||||
|
||||
const CommentActionsComponent: React.FC<Props> = ({ message, isFlyoutMode }) => {
|
||||
const CommentActionsComponent: React.FC<Props> = ({ message }) => {
|
||||
const toasts = useToasts();
|
||||
const { cases } = useKibana().services;
|
||||
const dispatch = useDispatch();
|
||||
const isModelEvaluationEnabled = useIsExperimentalFeatureEnabled('assistantModelEvaluation');
|
||||
|
||||
const { showAssistantOverlay, traceOptions } = useAssistantContext();
|
||||
const { traceOptions } = useAssistantContext();
|
||||
|
||||
const associateNote = useCallback(
|
||||
(noteId: string) => dispatch(timelineActions.addNote({ id: TimelineId.active, noteId })),
|
||||
|
@ -65,10 +64,6 @@ const CommentActionsComponent: React.FC<Props> = ({ message, isFlyoutMode }) =>
|
|||
});
|
||||
|
||||
const onAddToExistingCase = useCallback(() => {
|
||||
if (!isFlyoutMode) {
|
||||
showAssistantOverlay({ showOverlay: false });
|
||||
}
|
||||
|
||||
selectCaseModal.open({
|
||||
getAttachments: () => [
|
||||
{
|
||||
|
@ -78,7 +73,7 @@ const CommentActionsComponent: React.FC<Props> = ({ message, isFlyoutMode }) =>
|
|||
},
|
||||
],
|
||||
});
|
||||
}, [content, isFlyoutMode, selectCaseModal, showAssistantOverlay]);
|
||||
}, [content, selectCaseModal]);
|
||||
|
||||
// Note: This feature is behind the `isModelEvaluationEnabled` FF. If ever released, this URL should be configurable
|
||||
// as APM data may not go to the same cluster where the Kibana instance is running
|
||||
|
|
|
@ -39,7 +39,6 @@ const testProps = {
|
|||
isFetchingResponse: false,
|
||||
currentConversation,
|
||||
showAnonymizedValues,
|
||||
isFlyoutMode: false,
|
||||
};
|
||||
describe('getComments', () => {
|
||||
it('Does not add error state message has no error', () => {
|
||||
|
|
|
@ -60,7 +60,6 @@ export const getComments = ({
|
|||
refetchCurrentConversation,
|
||||
regenerateMessage,
|
||||
showAnonymizedValues,
|
||||
isFlyoutMode,
|
||||
currentUserAvatar,
|
||||
setIsStreaming,
|
||||
}: {
|
||||
|
@ -71,7 +70,6 @@ export const getComments = ({
|
|||
refetchCurrentConversation: () => void;
|
||||
regenerateMessage: (conversationId: string) => void;
|
||||
showAnonymizedValues: boolean;
|
||||
isFlyoutMode: boolean;
|
||||
currentUserAvatar?: UserAvatar;
|
||||
setIsStreaming: (isStreaming: boolean) => void;
|
||||
}): EuiCommentProps[] => {
|
||||
|
@ -187,7 +185,7 @@ export const getComments = ({
|
|||
|
||||
return {
|
||||
...messageProps,
|
||||
actions: <CommentActions message={transformedMessage} isFlyoutMode={isFlyoutMode} />,
|
||||
actions: <CommentActions message={transformedMessage} />,
|
||||
children: (
|
||||
<StreamComment
|
||||
actionTypeId={actionTypeId}
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
} from '@kbn/elastic-assistant';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { UserAvatar } from '@kbn/elastic-assistant/impl/assistant_context';
|
||||
import { useIsExperimentalFeatureEnabled } from '../common/hooks/use_experimental_features';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
|
||||
export const AssistantOverlay: React.FC = () => {
|
||||
|
@ -31,16 +30,10 @@ export const AssistantOverlay: React.FC = () => {
|
|||
});
|
||||
|
||||
const { assistantAvailability } = useAssistantContext();
|
||||
const aiAssistantFlyoutMode = useIsExperimentalFeatureEnabled('aiAssistantFlyoutMode');
|
||||
|
||||
if (!assistantAvailability.hasAssistantPrivilege) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ElasticAssistantOverlay
|
||||
isFlyoutMode={aiAssistantFlyoutMode}
|
||||
currentUserAvatar={currentUserAvatar}
|
||||
/>
|
||||
);
|
||||
return <ElasticAssistantOverlay currentUserAvatar={currentUserAvatar} />;
|
||||
};
|
||||
|
|
|
@ -16,13 +16,10 @@ import {
|
|||
} from '@kbn/elastic-assistant';
|
||||
import { useConversation } from '@kbn/elastic-assistant/impl/assistant/use_conversation';
|
||||
import type { FetchConversationsResponse } from '@kbn/elastic-assistant/impl/assistant/api';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
|
||||
|
||||
const defaultSelectedConversationId = WELCOME_CONVERSATION_TITLE;
|
||||
|
||||
export const ManagementSettings = React.memo(() => {
|
||||
const isFlyoutMode = useIsExperimentalFeatureEnabled('aiAssistantFlyoutMode');
|
||||
|
||||
const {
|
||||
baseConversations,
|
||||
http,
|
||||
|
@ -49,8 +46,8 @@ export const ManagementSettings = React.memo(() => {
|
|||
const currentConversation = useMemo(
|
||||
() =>
|
||||
conversations?.[defaultSelectedConversationId] ??
|
||||
getDefaultConversation({ cTitle: WELCOME_CONVERSATION_TITLE, isFlyoutMode }),
|
||||
[conversations, getDefaultConversation, isFlyoutMode]
|
||||
getDefaultConversation({ cTitle: WELCOME_CONVERSATION_TITLE }),
|
||||
[conversations, getDefaultConversation]
|
||||
);
|
||||
|
||||
if (conversations) {
|
||||
|
@ -58,7 +55,6 @@ export const ManagementSettings = React.memo(() => {
|
|||
<AssistantSettingsManagement
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
refetchConversations={refetchConversations}
|
||||
selectedConversation={currentConversation}
|
||||
/>
|
||||
|
|
|
@ -38,7 +38,7 @@ describe('Header', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
const connectorSelector = screen.getByTestId('connectorSelectorPlaceholderButton');
|
||||
const connectorSelector = screen.getByTestId('addNewConnectorButton');
|
||||
|
||||
expect(connectorSelector).toBeInTheDocument();
|
||||
});
|
||||
|
@ -61,7 +61,7 @@ describe('Header', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
const connectorSelector = screen.queryByTestId('connectorSelectorPlaceholderButton');
|
||||
const connectorSelector = screen.queryByTestId('addNewConnectorButton');
|
||||
|
||||
expect(connectorSelector).not.toBeInTheDocument();
|
||||
});
|
||||
|
|
|
@ -38,7 +38,6 @@ const HeaderComponent: React.FC<Props> = ({
|
|||
onCancel,
|
||||
stats,
|
||||
}) => {
|
||||
const isFlyoutMode = false; // always false for attack discovery
|
||||
const { hasAssistantPrivilege } = useAssistantAvailability();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const disabled = !hasAssistantPrivilege || connectorId == null;
|
||||
|
@ -85,7 +84,6 @@ const HeaderComponent: React.FC<Props> = ({
|
|||
{connectorsAreConfigured && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<ConnectorSelectorInline
|
||||
isFlyoutMode={isFlyoutMode}
|
||||
onConnectorSelected={noop}
|
||||
onConnectorIdSelected={onConnectorIdSelected}
|
||||
selectedConnectorId={connectorId}
|
||||
|
|
|
@ -38,10 +38,4 @@ describe('Welcome', () => {
|
|||
|
||||
expect(bodyText).toHaveTextContent(FIRST_SET_UP);
|
||||
});
|
||||
|
||||
it('renders connector prompt', () => {
|
||||
const connectorPrompt = screen.getByTestId('prompt');
|
||||
|
||||
expect(connectorPrompt).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,21 +6,13 @@
|
|||
*/
|
||||
|
||||
import { AssistantAvatar } from '@kbn/elastic-assistant';
|
||||
import { useConnectorSetup } from '@kbn/elastic-assistant/impl/connectorland/connector_setup';
|
||||
import { ConnectorSetup } from '@kbn/elastic-assistant/impl/connectorland/connector_setup';
|
||||
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import { noop } from 'lodash/fp';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
const WelcomeComponent: React.FC = () => {
|
||||
const { prompt: connectorPrompt } = useConnectorSetup({
|
||||
isFlyoutMode: true, // prevents the "Click to skip" button from showing
|
||||
onConversationUpdate: async () => {},
|
||||
onSetupComplete: noop, // this callback cannot be used to select a connector, so it's not used
|
||||
updateConversationsOnSaveConnector: false, // no conversation to update
|
||||
});
|
||||
|
||||
const title = useMemo(
|
||||
() => (
|
||||
<EuiFlexGroup
|
||||
|
@ -70,7 +62,12 @@ const WelcomeComponent: React.FC = () => {
|
|||
<EuiEmptyPrompt body={body} title={title} />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>{connectorPrompt}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ConnectorSetup
|
||||
onConversationUpdate={async () => {}}
|
||||
updateConversationsOnSaveConnector={false} // no conversation to update
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import styled from 'styled-components';
|
||||
import { Assistant } from '@kbn/elastic-assistant';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { TIMELINE_CONVERSATION_TITLE } from '../../../../../assistant/content/conversations/translations';
|
||||
|
||||
const AssistantTabContainer = styled.div`
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const AssistantTab: React.FC<{
|
||||
shouldRefocusPrompt: boolean;
|
||||
setConversationTitle: Dispatch<SetStateAction<string>>;
|
||||
}> = memo(({ shouldRefocusPrompt, setConversationTitle }) => (
|
||||
<AssistantTabContainer>
|
||||
<Assistant
|
||||
conversationTitle={TIMELINE_CONVERSATION_TITLE}
|
||||
embeddedLayout
|
||||
setConversationTitle={setConversationTitle}
|
||||
shouldRefocusPrompt={shouldRefocusPrompt}
|
||||
/>
|
||||
</AssistantTabContainer>
|
||||
));
|
||||
|
||||
AssistantTab.displayName = 'AssistantTab';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { AssistantTab as default };
|
|
@ -6,17 +6,13 @@
|
|||
*/
|
||||
|
||||
import { EuiBadge, EuiSkeletonText, EuiTabs, EuiTab } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import type { Ref, ReactElement, ComponentType, Dispatch, SetStateAction } from 'react';
|
||||
import React, { lazy, memo, Suspense, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { Ref, ReactElement, ComponentType } from 'react';
|
||||
import React, { lazy, memo, Suspense, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useEsqlAvailability } from '../../../../common/hooks/esql/use_esql_availability';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
import { useAssistantTelemetry } from '../../../../assistant/use_assistant_telemetry';
|
||||
import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability';
|
||||
import type { SessionViewConfig } from '../../../../../common/types';
|
||||
import type { RowRenderer, TimelineId } from '../../../../../common/types/timeline';
|
||||
import { TimelineTabs } from '../../../../../common/types/timeline';
|
||||
|
@ -41,7 +37,6 @@ import {
|
|||
} from './selectors';
|
||||
import * as i18n from './translations';
|
||||
import { useLicense } from '../../../../common/hooks/use_license';
|
||||
import { TIMELINE_CONVERSATION_TITLE } from '../../../../assistant/content/conversations/translations';
|
||||
import { initializeTimelineSettings } from '../../../store/actions';
|
||||
import { selectTimelineESQLSavedSearchId } from '../../../store/selectors';
|
||||
|
||||
|
@ -96,7 +91,6 @@ interface BasicTimelineTab {
|
|||
type ActiveTimelineTabProps = BasicTimelineTab & {
|
||||
activeTimelineTab: TimelineTabs;
|
||||
showTimeline: boolean;
|
||||
setConversationTitle: Dispatch<SetStateAction<string>>;
|
||||
};
|
||||
|
||||
const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
|
||||
|
@ -106,10 +100,8 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
|
|||
rowRenderers,
|
||||
timelineId,
|
||||
timelineType,
|
||||
setConversationTitle,
|
||||
showTimeline,
|
||||
}) => {
|
||||
const { hasAssistantPrivilege } = useAssistantAvailability();
|
||||
const { isTimelineEsqlEnabledByFeatureFlag, isEsqlAdvancedSettingEnabled } =
|
||||
useEsqlAvailability();
|
||||
const timelineESQLSavedSearch = useShallowEqualSelector((state) =>
|
||||
|
@ -124,7 +116,6 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
|
|||
}
|
||||
return isEsqlAdvancedSettingEnabled || timelineESQLSavedSearch != null;
|
||||
}, [isEsqlAdvancedSettingEnabled, isTimelineEsqlEnabledByFeatureFlag, timelineESQLSavedSearch]);
|
||||
const aiAssistantFlyoutMode = useIsExperimentalFeatureEnabled('aiAssistantFlyoutMode');
|
||||
const getTab = useCallback(
|
||||
(tab: TimelineTabs) => {
|
||||
switch (tab) {
|
||||
|
@ -147,33 +138,6 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
|
|||
[activeTimelineTab]
|
||||
);
|
||||
|
||||
const getAssistantTab = useCallback(() => {
|
||||
if (showTimeline) {
|
||||
const AssistantTab = tabWithSuspense(lazy(() => import('./assistant')));
|
||||
return (
|
||||
<HideShowContainer
|
||||
$isVisible={activeTimelineTab === TimelineTabs.securityAssistant}
|
||||
isOverflowYScroll={activeTimelineTab === TimelineTabs.securityAssistant}
|
||||
data-test-subj={`timeline-tab-content-security-assistant`}
|
||||
css={css`
|
||||
overflow: hidden !important;
|
||||
`}
|
||||
>
|
||||
{activeTimelineTab === TimelineTabs.securityAssistant ? (
|
||||
<AssistantTab
|
||||
setConversationTitle={setConversationTitle}
|
||||
shouldRefocusPrompt={
|
||||
showTimeline && activeTimelineTab === TimelineTabs.securityAssistant
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</HideShowContainer>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [activeTimelineTab, setConversationTitle, showTimeline]);
|
||||
|
||||
/* Future developer -> why are we doing that
|
||||
* It is really expansive to re-render the QueryTab because the drag/drop
|
||||
* Therefore, we are only hiding its dom when switching to another tab
|
||||
|
@ -228,7 +192,6 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
|
|||
>
|
||||
{isGraphOrNotesTabs && getTab(activeTimelineTab)}
|
||||
</HideShowContainer>
|
||||
{hasAssistantPrivilege && !aiAssistantFlyoutMode ? getAssistantTab() : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -271,8 +234,6 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
sessionViewConfig,
|
||||
timelineDescription,
|
||||
}) => {
|
||||
const aiAssistantFlyoutMode = useIsExperimentalFeatureEnabled('aiAssistantFlyoutMode');
|
||||
const { hasAssistantPrivilege } = useAssistantAvailability();
|
||||
const dispatch = useDispatch();
|
||||
const getActiveTab = useMemo(() => getActiveTabSelector(), []);
|
||||
const getShowTimeline = useMemo(() => getShowTimelineSelector(), []);
|
||||
|
@ -312,9 +273,6 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
|
||||
const isEnterprisePlus = useLicense().isEnterprise();
|
||||
|
||||
const [conversationTitle, setConversationTitle] = useState<string>(TIMELINE_CONVERSATION_TITLE);
|
||||
const { reportAssistantInvoked } = useAssistantTelemetry();
|
||||
|
||||
const allTimelineNoteIds = useMemo(() => {
|
||||
const eventNoteIds = Object.values(eventIdToNoteIds).reduce<string[]>(
|
||||
(acc, v) => [...acc, ...v],
|
||||
|
@ -361,16 +319,6 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
setActiveTab(TimelineTabs.session);
|
||||
}, [setActiveTab]);
|
||||
|
||||
const setSecurityAssistantAsActiveTab = useCallback(() => {
|
||||
setActiveTab(TimelineTabs.securityAssistant);
|
||||
if (activeTab !== TimelineTabs.securityAssistant) {
|
||||
reportAssistantInvoked({
|
||||
conversationId: conversationTitle,
|
||||
invokedBy: TIMELINE_CONVERSATION_TITLE,
|
||||
});
|
||||
}
|
||||
}, [activeTab, conversationTitle, reportAssistantInvoked, setActiveTab]);
|
||||
|
||||
const setEsqlAsActiveTab = useCallback(() => {
|
||||
dispatch(
|
||||
initializeTimelineSettings({
|
||||
|
@ -471,17 +419,6 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
</div>
|
||||
)}
|
||||
</StyledEuiTab>
|
||||
{hasAssistantPrivilege && !aiAssistantFlyoutMode && (
|
||||
<StyledEuiTab
|
||||
data-test-subj={`timelineTabs-${TimelineTabs.securityAssistant}`}
|
||||
onClick={setSecurityAssistantAsActiveTab}
|
||||
disabled={timelineType === TimelineType.template}
|
||||
isSelected={activeTab === TimelineTabs.securityAssistant}
|
||||
key={TimelineTabs.securityAssistant}
|
||||
>
|
||||
<span>{i18n.SECURITY_ASSISTANT}</span>
|
||||
</StyledEuiTab>
|
||||
)}
|
||||
</StyledEuiTabs>
|
||||
)}
|
||||
|
||||
|
@ -492,7 +429,6 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
timelineId={timelineId}
|
||||
timelineType={timelineType}
|
||||
timelineDescription={timelineDescription}
|
||||
setConversationTitle={setConversationTitle}
|
||||
showTimeline={showTimeline}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -13310,7 +13310,6 @@
|
|||
"xpack.elasticAssistant.assistant.settings.knowledgeBasedSettings.knowledgeBaseDescription": "Pour commencer, configurez ELSER dans {machineLearning}. {seeDocs}",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseInstalledDescription": "Initialisé sur `{kbIndexPattern}`",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.latestAndRiskiestOpenAlertsLabel": "Envoyez à l'Assistant d'IA des informations sur vos {alertsCount} alertes ouvertes ou confirmées les plus récentes et les plus risquées.",
|
||||
"xpack.elasticAssistant.assistant.technicalPreview.tooltipContent": "Les réponses des systèmes d'IA ne sont pas toujours tout à fait exactes. Pour en savoir plus sur la fonctionnalité d'assistant et son utilisation, consultez {documentationLink}.",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.contextEditor.selectAllFields": "Sélectionnez l'ensemble des {totalFields} champs",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.contextEditor.selectedFields": "{selected} champs sélectionnés",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.allowedStat.allowedTooltip": "{allowed} champs sur {total} dans ce contexte sont autorisés à être inclus dans la conversation",
|
||||
|
@ -13511,9 +13510,6 @@
|
|||
"xpack.elasticAssistant.knowledgeBase.setupError": "Erreur lors de la configuration de la base de connaissances",
|
||||
"xpack.elasticAssistant.knowledgeBase.statusError": "Erreur lors de la récupération du statut de la base de connaissances",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.enterprisePrompt": "L'assistant d'IA d'Elastic n'est accessible qu'aux entreprises. Veuillez mettre votre licence à niveau pour bénéficier de cette fonctionnalité.",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneral2Prompt": "Avant toute chose, il faut configurer un Connecteur d'intelligence artificielle générative pour lancer cette expérience de chat ! Avec le connecteur d'IA générative, vous pourrez configurer l'accès à un service OpenAI ou à un service Amazon Bedrock, mais sachez que vous serez en mesure de déployer vos propres modèles au sein d'une instance Elastic Cloud et de les y utiliser à l'avenir... 😉",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneral3Prompt": "Cliquez sur le bouton \"Ajouter un connecteur\" ci-dessous pour continuer la conversation.",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneralPrompt": "Bienvenue sur votre assistant d’intelligence artificielle Elastic. Je suis votre portail 100 % open-code vers votre vie Elastic. Avec le temps, je serai capable de répondre à vos questions et de vous apporter mon aide concernant l’ensemble de vos informations contenues dans Elastic, et bien plus encore. En attendant, j’espère que cet aperçu anticipé vous donnera une idée de ce que nous pouvons créer en travaillant ensemble, en toute transparence. À bientôt !",
|
||||
"xpack.embeddableEnhanced.actions.panelNotifications.manyDrilldowns": "Le panneau comporte {count} recherches",
|
||||
"xpack.embeddableEnhanced.actions.panelNotifications.oneDrilldown": "Le panneau comporte 1 recherche",
|
||||
"xpack.embeddableEnhanced.Drilldowns": "Explorations",
|
||||
|
|
|
@ -13289,7 +13289,6 @@
|
|||
"xpack.elasticAssistant.assistant.settings.knowledgeBasedSettings.knowledgeBaseDescription": "{machineLearning}内でELSERを構成して開始します。{seeDocs}",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseInstalledDescription": "`{kbIndexPattern}`に初期化されました",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.latestAndRiskiestOpenAlertsLabel": "{alertsCount}件の最新の最もリスクが高い未解決または確認済みのアラートに関する情報をAI Assistantに送信します。",
|
||||
"xpack.elasticAssistant.assistant.technicalPreview.tooltipContent": "AIシステムからの応答は、必ずしも完全に正確であるとは限りません。アシスタント機能とその使用方法の詳細については、{documentationLink}を参照してください。",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.contextEditor.selectAllFields": "すべての{totalFields}フィールドを選択",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.contextEditor.selectedFields": "選択した{selected}フィールド",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.allowedStat.allowedTooltip": "このコンテキストの{total}フィールドのうち{allowed}個を会話に含めることができます",
|
||||
|
@ -13490,9 +13489,6 @@
|
|||
"xpack.elasticAssistant.knowledgeBase.setupError": "ナレッジベースの設定エラー",
|
||||
"xpack.elasticAssistant.knowledgeBase.statusError": "ナレッジベースステータスの取得エラー",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.enterprisePrompt": "Elastic AI Assistantはエンタープライズユーザーのみご利用いただけます。この機能を使用するには、ライセンスをアップグレードしてください。",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneral2Prompt": "まず最初に、このチャットエクスペリエンスを開始するために生成AIコネクターを設定する必要があります。生成AIコネクターを使用すると、OpenAI ServiceまたはAmazon Bedrockサービスへのアクセスを設定できます。しかし、将来的にはElastic Cloudインスタンス内に独自のモデルをデプロイして、それをここで使うことができるようになると考えてください... 😉",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneral3Prompt": "会話を続けるには、以下の[コネクターの追加]ボタンをクリックしてください。",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneralPrompt": "Elastic AI Assistantへようこそ!Elasticを活用するための100%オープンコードのポータルです。いずれは、Elasticにあるすべての情報、そしてもっともっと多くのことについて、質問に答えたり、サポートを提供したりできるようになるでしょう。それまでは、この早期プレビューが、オープンな場で協力するときに生み出せるものの可能性を知るきっかけになることを願っています。どうぞよろしくお願いいたします。",
|
||||
"xpack.embeddableEnhanced.actions.panelNotifications.manyDrilldowns": "パネルには{count}個のドリルダウンがあります",
|
||||
"xpack.embeddableEnhanced.actions.panelNotifications.oneDrilldown": "パネルには 1 個のドリルダウンがあります",
|
||||
"xpack.embeddableEnhanced.Drilldowns": "ドリルダウン",
|
||||
|
|
|
@ -13315,7 +13315,6 @@
|
|||
"xpack.elasticAssistant.assistant.settings.knowledgeBasedSettings.knowledgeBaseDescription": "在 {machineLearning} 中配置 ELSER 以开始。{seeDocs}",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseInstalledDescription": "已初始化为 `{kbIndexPattern}`",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.latestAndRiskiestOpenAlertsLabel": "发送有关 {alertsCount} 个最新和风险最高的未决或已确认告警的 AI 助手信息。",
|
||||
"xpack.elasticAssistant.assistant.technicalPreview.tooltipContent": "来自 AI 系统的响应可能不会始终完全准确。有关辅助功能及其用法的详细信息,请参阅 {documentationLink}。",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.contextEditor.selectAllFields": "选择所有 {totalFields} 个字段",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.contextEditor.selectedFields": "已选定 {selected} 个字段",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.allowedStat.allowedTooltip": "允许在对话中包含此上下文中的 {allowed} 个(共 {total} 个)字段",
|
||||
|
@ -13516,9 +13515,6 @@
|
|||
"xpack.elasticAssistant.knowledgeBase.setupError": "设置知识库时出错",
|
||||
"xpack.elasticAssistant.knowledgeBase.statusError": "提取知识库状态时出错",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.enterprisePrompt": "Elastic AI 助手仅对企业用户可用。请升级许可证以使用此功能。",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneral2Prompt": "首先,我们需要设置生成式 AI 连接器以继续这种聊天体验!使用生成式 AI 连接器,您将能够配置 OpenAI 服务或 Amazon Bedrock 服务的访问权限,但请您相信,您将能够在 Elastic Cloud 实例中部署自己的模型,并于未来在此处使用那些模型……😉",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneral3Prompt": "接下来,单击下面的“添加连接器”按钮继续对话!",
|
||||
"xpack.elasticAssistant.securityAssistant.content.prompts.welcome.welcomeGeneralPrompt": "欢迎使用 Elastic AI 助手!我是您的 100% 开放源代码门户,可帮助您熟练使用 Elastic。一段时间后,我将能够回答问题,并利用 Elastic 中的所有信息提供帮助等。到那时,我希望这个早期预览版本将为您打开思路,为我们的公开协作创造各种可能性。加油!",
|
||||
"xpack.embeddableEnhanced.actions.panelNotifications.manyDrilldowns": "面板有 {count} 个向下钻取",
|
||||
"xpack.embeddableEnhanced.actions.panelNotifications.oneDrilldown": "面板有 1 个向下钻取",
|
||||
"xpack.embeddableEnhanced.Drilldowns": "向下钻取",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue