mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Security solution] Assistant package assistant/index.tsx
cleanup (#190151)
This commit is contained in:
parent
e3d6cf69b4
commit
730f8eae87
79 changed files with 1351 additions and 2469 deletions
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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, { Dispatch, SetStateAction } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { QueryObserverResult } from '@tanstack/react-query';
|
||||
import { Conversation } from '../../..';
|
||||
import { AssistantAnimatedIcon } from '../assistant_animated_icon';
|
||||
import { SystemPrompt } from '../prompt_editor/system_prompt';
|
||||
import { SetupKnowledgeBaseButton } from '../../knowledge_base/setup_knowledge_base_button';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
interface Props {
|
||||
currentConversation: Conversation | undefined;
|
||||
currentSystemPromptId: string | undefined;
|
||||
isSettingsModalVisible: boolean;
|
||||
refetchCurrentUserConversations: () => Promise<
|
||||
QueryObserverResult<Record<string, Conversation>, unknown>
|
||||
>;
|
||||
setIsSettingsModalVisible: Dispatch<SetStateAction<boolean>>;
|
||||
setCurrentSystemPromptId: Dispatch<SetStateAction<string | undefined>>;
|
||||
allSystemPrompts: PromptResponse[];
|
||||
}
|
||||
|
||||
export const EmptyConvo: React.FC<Props> = ({
|
||||
allSystemPrompts,
|
||||
currentConversation,
|
||||
currentSystemPromptId,
|
||||
isSettingsModalVisible,
|
||||
refetchCurrentUserConversations,
|
||||
setCurrentSystemPromptId,
|
||||
setIsSettingsModalVisible,
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
css={css`
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantAnimatedIcon />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h3>{i18n.EMPTY_SCREEN_TITLE}</h3>
|
||||
<p>{i18n.EMPTY_SCREEN_DESCRIPTION}</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SystemPrompt
|
||||
conversation={currentConversation}
|
||||
currentSystemPromptId={currentSystemPromptId}
|
||||
onSystemPromptSelectionChange={setCurrentSystemPromptId}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
refetchConversations={refetchCurrentUserConversations}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SetupKnowledgeBaseButton />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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, {
|
||||
Dispatch,
|
||||
FunctionComponent,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { css } from '@emotion/react';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { QueryObserverResult } from '@tanstack/react-query';
|
||||
import { AssistantAnimatedIcon } from '../assistant_animated_icon';
|
||||
import { EmptyConvo } from './empty_convo';
|
||||
import { WelcomeSetup } from './welcome_setup';
|
||||
import { Conversation } from '../../..';
|
||||
import { UpgradeLicenseCallToAction } from '../upgrade_license_cta';
|
||||
import * as i18n from '../translations';
|
||||
interface Props {
|
||||
allSystemPrompts: PromptResponse[];
|
||||
comments: JSX.Element;
|
||||
currentConversation: Conversation | undefined;
|
||||
currentSystemPromptId: string | undefined;
|
||||
handleOnConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => Promise<void>;
|
||||
isAssistantEnabled: boolean;
|
||||
isSettingsModalVisible: boolean;
|
||||
isWelcomeSetup: boolean;
|
||||
isLoading: boolean;
|
||||
refetchCurrentUserConversations: () => Promise<
|
||||
QueryObserverResult<Record<string, Conversation>, unknown>
|
||||
>;
|
||||
http: HttpSetup;
|
||||
setCurrentSystemPromptId: Dispatch<SetStateAction<string | undefined>>;
|
||||
setIsSettingsModalVisible: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const AssistantBody: FunctionComponent<Props> = ({
|
||||
allSystemPrompts,
|
||||
comments,
|
||||
currentConversation,
|
||||
currentSystemPromptId,
|
||||
handleOnConversationSelected,
|
||||
setCurrentSystemPromptId,
|
||||
http,
|
||||
isAssistantEnabled,
|
||||
isLoading,
|
||||
isSettingsModalVisible,
|
||||
isWelcomeSetup,
|
||||
refetchCurrentUserConversations,
|
||||
setIsSettingsModalVisible,
|
||||
}) => {
|
||||
const isNewConversation = useMemo(
|
||||
() => currentConversation?.messages.length === 0,
|
||||
[currentConversation?.messages.length]
|
||||
);
|
||||
|
||||
const disclaimer = useMemo(
|
||||
() =>
|
||||
isNewConversation && (
|
||||
<EuiText
|
||||
data-test-subj="assistant-disclaimer"
|
||||
textAlign="center"
|
||||
color={euiThemeVars.euiColorMediumShade}
|
||||
size="xs"
|
||||
css={css`
|
||||
margin: 0 ${euiThemeVars.euiSizeL} ${euiThemeVars.euiSizeM} ${euiThemeVars.euiSizeL};
|
||||
`}
|
||||
>
|
||||
{i18n.DISCLAIMER}
|
||||
</EuiText>
|
||||
),
|
||||
[isNewConversation]
|
||||
);
|
||||
|
||||
// Start Scrolling
|
||||
const commentsContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const parent = commentsContainerRef.current?.parentElement;
|
||||
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
// when scrollHeight changes, parent is scrolled to bottom
|
||||
parent.scrollTop = parent.scrollHeight;
|
||||
|
||||
(
|
||||
commentsContainerRef.current?.childNodes[0].childNodes[0] as HTMLElement
|
||||
).lastElementChild?.scrollIntoView();
|
||||
});
|
||||
// End Scrolling
|
||||
|
||||
if (!isAssistantEnabled) {
|
||||
return <UpgradeLicenseCallToAction http={http} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
{isLoading ? (
|
||||
<EuiEmptyPrompt data-test-subj="animatedLogo" icon={<AssistantAnimatedIcon />} />
|
||||
) : isWelcomeSetup ? (
|
||||
<WelcomeSetup
|
||||
currentConversation={currentConversation}
|
||||
handleOnConversationSelected={handleOnConversationSelected}
|
||||
/>
|
||||
) : currentConversation?.messages.length === 0 ? (
|
||||
<EmptyConvo
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
currentConversation={currentConversation}
|
||||
currentSystemPromptId={currentSystemPromptId}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
refetchCurrentUserConversations={refetchCurrentUserConversations}
|
||||
setCurrentSystemPromptId={setCurrentSystemPromptId}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
) : (
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
panelRef={(element) => {
|
||||
commentsContainerRef.current = (element?.parentElement as HTMLDivElement) || null;
|
||||
}}
|
||||
>
|
||||
{comments}
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{disclaimer}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { Conversation } from '../../..';
|
||||
import { AssistantAnimatedIcon } from '../assistant_animated_icon';
|
||||
import { ConnectorSetup } from '../../connectorland/connector_setup';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
interface Props {
|
||||
currentConversation: Conversation | undefined;
|
||||
handleOnConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => Promise<void>;
|
||||
}
|
||||
|
||||
export const WelcomeSetup: React.FC<Props> = ({
|
||||
handleOnConversationSelected,
|
||||
currentConversation,
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
css={css`
|
||||
text-align: center;
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantAnimatedIcon />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h3>{i18n.WELCOME_SCREEN_TITLE}</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
css={css`
|
||||
max-width: 400px;
|
||||
`}
|
||||
>
|
||||
<p>{i18n.WELCOME_SCREEN_DESCRIPTION}</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} data-test-subj="connector-prompt">
|
||||
<ConnectorSetup
|
||||
conversation={currentConversation}
|
||||
onConversationUpdate={handleOnConversationSelected}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -26,6 +26,7 @@ const testProps = {
|
|||
ELASTIC_WEBSITE_URL: 'https://www.elastic.co/',
|
||||
DOC_LINK_VERSION: 'master',
|
||||
},
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
isSettingsModalVisible: false,
|
||||
onConversationSelected,
|
||||
|
@ -35,7 +36,7 @@ const testProps = {
|
|||
onChatCleared: jest.fn(),
|
||||
showAnonymizedValues: false,
|
||||
conversations: mockConversations,
|
||||
refetchConversationsState: jest.fn(),
|
||||
refetchCurrentUserConversations: jest.fn(),
|
||||
isAssistantEnabled: true,
|
||||
anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] },
|
||||
refetchAnonymizationFieldsResults: jest.fn(),
|
||||
|
|
|
@ -16,10 +16,12 @@ import {
|
|||
EuiPanel,
|
||||
EuiConfirmModal,
|
||||
EuiToolTip,
|
||||
EuiSkeletonTitle,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { DataStreamApis } from '../use_data_stream_apis';
|
||||
import { Conversation } from '../../..';
|
||||
import { AssistantTitle } from '../assistant_title';
|
||||
import { ConnectorSelectorInline } from '../../connectorland/connector_selector_inline/connector_selector_inline';
|
||||
|
@ -32,6 +34,7 @@ interface OwnProps {
|
|||
selectedConversation: Conversation | undefined;
|
||||
defaultConnector?: AIConnector;
|
||||
isDisabled: boolean;
|
||||
isLoading: boolean;
|
||||
isSettingsModalVisible: boolean;
|
||||
onToggleShowAnonymizedValues: () => void;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
@ -43,7 +46,7 @@ interface OwnProps {
|
|||
onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void;
|
||||
conversations: Record<string, Conversation>;
|
||||
conversationsLoaded: boolean;
|
||||
refetchConversationsState: () => Promise<void>;
|
||||
refetchCurrentUserConversations: DataStreamApis['refetchCurrentUserConversations'];
|
||||
onConversationCreate: () => Promise<void>;
|
||||
isAssistantEnabled: boolean;
|
||||
refetchPrompts?: (
|
||||
|
@ -61,6 +64,7 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
selectedConversation,
|
||||
defaultConnector,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
isSettingsModalVisible,
|
||||
onToggleShowAnonymizedValues,
|
||||
setIsSettingsModalVisible,
|
||||
|
@ -72,7 +76,7 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
onConversationSelected,
|
||||
conversations,
|
||||
conversationsLoaded,
|
||||
refetchConversationsState,
|
||||
refetchCurrentUserConversations,
|
||||
onConversationCreate,
|
||||
isAssistantEnabled,
|
||||
refetchPrompts,
|
||||
|
@ -144,6 +148,7 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
return (
|
||||
<>
|
||||
<FlyoutNavigation
|
||||
isLoading={isLoading}
|
||||
isExpanded={!!chatHistoryVisible}
|
||||
setIsExpanded={setChatHistoryVisible}
|
||||
onConversationCreate={onConversationCreate}
|
||||
|
@ -164,7 +169,7 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
onConversationSelected={onConversationSelected}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
refetchCurrentUserConversations={refetchCurrentUserConversations}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -196,11 +201,16 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<AssistantTitle
|
||||
title={selectedConversation?.title}
|
||||
selectedConversation={selectedConversation}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<EuiSkeletonTitle data-test-subj="skeletonTitle" size="xs" />
|
||||
) : (
|
||||
<AssistantTitle
|
||||
isDisabled={isDisabled}
|
||||
title={selectedConversation?.title}
|
||||
selectedConversation={selectedConversation}
|
||||
refetchCurrentUserConversations={refetchCurrentUserConversations}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -240,6 +250,7 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
button={
|
||||
<EuiButtonIcon
|
||||
aria-label="test"
|
||||
isDisabled={isDisabled}
|
||||
iconType="boxesVertical"
|
||||
onClick={onButtonClick}
|
||||
data-test-subj="chat-context-menu"
|
||||
|
|
|
@ -7,13 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ANONYMIZED_VALUES = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.anonymizedValues',
|
||||
{
|
||||
defaultMessage: 'Anonymized values',
|
||||
}
|
||||
);
|
||||
|
||||
export const RESET_CONVERSATION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.resetConversation',
|
||||
{
|
||||
|
@ -21,13 +14,6 @@ export const RESET_CONVERSATION = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const CONNECTOR_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.connectorTitle',
|
||||
{
|
||||
defaultMessage: 'Connector',
|
||||
}
|
||||
);
|
||||
|
||||
export const SHOW_ANONYMIZED = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.showAnonymizedToggleLabel',
|
||||
{
|
||||
|
@ -42,13 +28,6 @@ export const SHOW_REAL_VALUES = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const SHOW_ANONYMIZED_TOOLTIP = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.showAnonymizedTooltip',
|
||||
{
|
||||
defaultMessage: 'Show the anonymized values sent to and from the assistant',
|
||||
}
|
||||
);
|
||||
|
||||
export const CANCEL_BUTTON_TEXT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.resetConversationModal.cancelButtonText',
|
||||
{
|
||||
|
|
|
@ -15,6 +15,7 @@ import { NEW_CHAT } from '../conversations/conversation_sidepanel/translations';
|
|||
|
||||
export interface FlyoutNavigationProps {
|
||||
isExpanded: boolean;
|
||||
isLoading: boolean;
|
||||
setIsExpanded?: (value: boolean) => void;
|
||||
children: React.ReactNode;
|
||||
onConversationCreate?: () => Promise<void>;
|
||||
|
@ -35,7 +36,14 @@ const VerticalSeparator = styled.div`
|
|||
*/
|
||||
|
||||
export const FlyoutNavigation = memo<FlyoutNavigationProps>(
|
||||
({ isExpanded, setIsExpanded, children, onConversationCreate, isAssistantEnabled }) => {
|
||||
({
|
||||
isLoading,
|
||||
isExpanded,
|
||||
setIsExpanded,
|
||||
children,
|
||||
onConversationCreate,
|
||||
isAssistantEnabled,
|
||||
}) => {
|
||||
const onToggle = useCallback(
|
||||
() => setIsExpanded && setIsExpanded(!isExpanded),
|
||||
[isExpanded, setIsExpanded]
|
||||
|
@ -44,7 +52,7 @@ export const FlyoutNavigation = memo<FlyoutNavigationProps>(
|
|||
const toggleButton = useMemo(
|
||||
() => (
|
||||
<EuiButtonIcon
|
||||
disabled={!isAssistantEnabled}
|
||||
disabled={isLoading || !isAssistantEnabled}
|
||||
onClick={onToggle}
|
||||
iconType={isExpanded ? 'arrowEnd' : 'arrowStart'}
|
||||
size="xs"
|
||||
|
@ -66,7 +74,7 @@ export const FlyoutNavigation = memo<FlyoutNavigationProps>(
|
|||
}
|
||||
/>
|
||||
),
|
||||
[isAssistantEnabled, isExpanded, onToggle]
|
||||
[isAssistantEnabled, isExpanded, isLoading, onToggle]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -99,7 +107,7 @@ export const FlyoutNavigation = memo<FlyoutNavigationProps>(
|
|||
color="primary"
|
||||
iconType="newChat"
|
||||
onClick={onConversationCreate}
|
||||
disabled={!isAssistantEnabled}
|
||||
disabled={isLoading || !isAssistantEnabled}
|
||||
>
|
||||
{NEW_CHAT}
|
||||
</EuiButtonEmpty>
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
UserAvatar,
|
||||
} from '../../assistant_context';
|
||||
import { Assistant, CONVERSATION_SIDE_PANEL_WIDTH } from '..';
|
||||
import { WELCOME_CONVERSATION_TITLE } from '../use_conversation/translations';
|
||||
|
||||
const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
|
||||
|
||||
|
@ -38,9 +37,8 @@ export const UnifiedTimelineGlobalStyles = createGlobalStyle`
|
|||
|
||||
export const AssistantOverlay = React.memo<Props>(({ currentUserAvatar }) => {
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [conversationTitle, setConversationTitle] = useState<string | undefined>(
|
||||
WELCOME_CONVERSATION_TITLE
|
||||
);
|
||||
// Why is this named Title and not Id?
|
||||
const [conversationTitle, setConversationTitle] = useState<string | undefined>(undefined);
|
||||
const [promptContextId, setPromptContextId] = useState<string | undefined>();
|
||||
const { assistantTelemetry, setShowAssistantOverlay, getLastConversationId } =
|
||||
useAssistantContext();
|
||||
|
@ -55,16 +53,12 @@ export const AssistantOverlay = React.memo<Props>(({ currentUserAvatar }) => {
|
|||
promptContextId: pid,
|
||||
conversationTitle: cTitle,
|
||||
}: ShowAssistantOverlayProps) => {
|
||||
const newConversationTitle = getLastConversationId(cTitle);
|
||||
if (so)
|
||||
assistantTelemetry?.reportAssistantInvoked({
|
||||
conversationId: newConversationTitle,
|
||||
invokedBy: 'click',
|
||||
});
|
||||
const conversationId = getLastConversationId(cTitle);
|
||||
if (so) assistantTelemetry?.reportAssistantInvoked({ conversationId, invokedBy: 'click' });
|
||||
|
||||
setIsModalVisible(so);
|
||||
setPromptContextId(pid);
|
||||
setConversationTitle(newConversationTitle);
|
||||
setConversationTitle(conversationId);
|
||||
},
|
||||
[assistantTelemetry, getLastConversationId]
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@ const testProps = {
|
|||
docLinks: { ELASTIC_WEBSITE_URL: 'https://www.elastic.co/', DOC_LINK_VERSION: '7.15' },
|
||||
selectedConversation: undefined,
|
||||
onChange: jest.fn(),
|
||||
refetchConversationsState: jest.fn(),
|
||||
refetchCurrentUserConversations: jest.fn(),
|
||||
};
|
||||
|
||||
describe('AssistantTitle', () => {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiInlineEditTitle } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { DataStreamApis } from '../use_data_stream_apis';
|
||||
import type { Conversation } from '../../..';
|
||||
import { AssistantAvatar } from '../assistant_avatar/assistant_avatar';
|
||||
import { useConversation } from '../use_conversation';
|
||||
|
@ -18,10 +19,11 @@ 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;
|
||||
selectedConversation: Conversation | undefined;
|
||||
refetchConversationsState: () => Promise<void>;
|
||||
}> = ({ title, selectedConversation, refetchConversationsState }) => {
|
||||
refetchCurrentUserConversations: DataStreamApis['refetchCurrentUserConversations'];
|
||||
}> = ({ title, selectedConversation, refetchCurrentUserConversations, isDisabled = false }) => {
|
||||
const [newTitle, setNewTitle] = useState(title);
|
||||
const [newTitleError, setNewTitleError] = useState(false);
|
||||
const { updateConversationTitle } = useConversation();
|
||||
|
@ -35,10 +37,10 @@ export const AssistantTitle: React.FC<{
|
|||
conversationId: selectedConversation.id,
|
||||
updatedTitle,
|
||||
});
|
||||
await refetchConversationsState();
|
||||
await refetchCurrentUserConversations();
|
||||
}
|
||||
},
|
||||
[refetchConversationsState, selectedConversation, updateConversationTitle]
|
||||
[refetchCurrentUserConversations, selectedConversation, updateConversationTitle]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -62,7 +64,7 @@ export const AssistantTitle: React.FC<{
|
|||
value={newTitle ?? NEW_CHAT}
|
||||
size="xs"
|
||||
isInvalid={!!newTitleError}
|
||||
isReadOnly={selectedConversation?.isDefault}
|
||||
isReadOnly={isDisabled || selectedConversation?.isDefault}
|
||||
onChange={(e) => setNewTitle(e.currentTarget.nodeValue || '')}
|
||||
onCancel={() => setNewTitle(title)}
|
||||
onSave={handleUpdateTitle}
|
||||
|
|
|
@ -1,48 +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 from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { BlockBotCallToAction } from './cta';
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
|
||||
const testProps = {
|
||||
connectorPrompt: <div>{'Connector Prompt'}</div>,
|
||||
http: { basePath: { get: jest.fn(() => 'http://localhost:5601') } } as unknown as HttpSetup,
|
||||
isAssistantEnabled: false,
|
||||
isWelcomeSetup: false,
|
||||
};
|
||||
|
||||
describe('BlockBotCallToAction', () => {
|
||||
it('UpgradeButtons is rendered when isAssistantEnabled is false and isWelcomeSetup is false', () => {
|
||||
const { getByTestId, queryByTestId } = render(<BlockBotCallToAction {...testProps} />);
|
||||
expect(getByTestId('upgrade-buttons')).toBeInTheDocument();
|
||||
expect(queryByTestId('connector-prompt')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('connectorPrompt is rendered when isAssistantEnabled is true and isWelcomeSetup is true', () => {
|
||||
const props = {
|
||||
...testProps,
|
||||
isAssistantEnabled: true,
|
||||
isWelcomeSetup: true,
|
||||
};
|
||||
const { getByTestId, queryByTestId } = render(<BlockBotCallToAction {...props} />);
|
||||
expect(getByTestId('connector-prompt')).toBeInTheDocument();
|
||||
expect(queryByTestId('upgrade-buttons')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('null is returned when isAssistantEnabled is true and isWelcomeSetup is false', () => {
|
||||
const props = {
|
||||
...testProps,
|
||||
isAssistantEnabled: true,
|
||||
isWelcomeSetup: false,
|
||||
};
|
||||
const { container, queryByTestId } = render(<BlockBotCallToAction {...props} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(queryByTestId('connector-prompt')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('upgrade-buttons')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -12,12 +12,12 @@ import { TestProviders } from '../../mock/test_providers/test_providers';
|
|||
|
||||
jest.mock('./use_chat_send');
|
||||
|
||||
const handlePromptChange = jest.fn();
|
||||
const handleSendMessage = jest.fn();
|
||||
const setUserPrompt = jest.fn();
|
||||
const handleChatSend = jest.fn();
|
||||
const handleRegenerateResponse = jest.fn();
|
||||
const testProps: Props = {
|
||||
handlePromptChange,
|
||||
handleSendMessage,
|
||||
setUserPrompt,
|
||||
handleChatSend,
|
||||
handleRegenerateResponse,
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
|
@ -35,7 +35,7 @@ describe('ChatSend', () => {
|
|||
const promptTextArea = getByTestId('prompt-textarea');
|
||||
const promptText = 'valid prompt text';
|
||||
fireEvent.change(promptTextArea, { target: { value: promptText } });
|
||||
expect(handlePromptChange).toHaveBeenCalledWith(promptText);
|
||||
expect(setUserPrompt).toHaveBeenCalledWith(promptText);
|
||||
});
|
||||
|
||||
it('a message is sent when send button is clicked', async () => {
|
||||
|
@ -46,7 +46,7 @@ describe('ChatSend', () => {
|
|||
expect(getByTestId('prompt-textarea')).toHaveTextContent(promptText);
|
||||
fireEvent.click(getByTestId('submit-chat'));
|
||||
await waitFor(() => {
|
||||
expect(handleSendMessage).toHaveBeenCalledWith(promptText);
|
||||
expect(handleChatSend).toHaveBeenCalledWith(promptText);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@ export interface Props extends Omit<UseChatSend, 'abortStream' | 'handleOnChatCl
|
|||
* Allows the user to clear the chat and switch between different system prompts.
|
||||
*/
|
||||
export const ChatSend: React.FC<Props> = ({
|
||||
handlePromptChange,
|
||||
handleSendMessage,
|
||||
setUserPrompt,
|
||||
handleChatSend,
|
||||
isDisabled,
|
||||
isLoading,
|
||||
shouldRefocusPrompt,
|
||||
|
@ -42,15 +42,15 @@ export const ChatSend: React.FC<Props> = ({
|
|||
const promptValue = useMemo(() => (isDisabled ? '' : userPrompt ?? ''), [isDisabled, userPrompt]);
|
||||
|
||||
const onSendMessage = useCallback(() => {
|
||||
handleSendMessage(promptTextAreaRef.current?.value?.trim() ?? '');
|
||||
handlePromptChange('');
|
||||
}, [handleSendMessage, promptTextAreaRef, handlePromptChange]);
|
||||
handleChatSend(promptTextAreaRef.current?.value?.trim() ?? '');
|
||||
setUserPrompt('');
|
||||
}, [handleChatSend, promptTextAreaRef, setUserPrompt]);
|
||||
|
||||
useAutosizeTextArea(promptTextAreaRef?.current, promptValue);
|
||||
|
||||
useEffect(() => {
|
||||
handlePromptChange(promptValue);
|
||||
}, [handlePromptChange, promptValue]);
|
||||
setUserPrompt(promptValue);
|
||||
}, [setUserPrompt, promptValue]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
|
@ -66,9 +66,9 @@ export const ChatSend: React.FC<Props> = ({
|
|||
`}
|
||||
>
|
||||
<PromptTextArea
|
||||
onPromptSubmit={handleSendMessage}
|
||||
onPromptSubmit={handleChatSend}
|
||||
ref={promptTextAreaRef}
|
||||
handlePromptChange={handlePromptChange}
|
||||
setUserPrompt={setUserPrompt}
|
||||
value={promptValue}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
|
|
|
@ -11,7 +11,7 @@ import { useConversation } from '../use_conversation';
|
|||
import { emptyWelcomeConvo, welcomeConvo } from '../../mock/conversation';
|
||||
import { defaultSystemPrompt, mockSystemPrompt } from '../../mock/system_prompt';
|
||||
import { useChatSend, UseChatSendProps } from './use_chat_send';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { TestProviders } from '../../mock/test_providers/test_providers';
|
||||
import { useAssistantContext } from '../../..';
|
||||
|
@ -20,9 +20,7 @@ jest.mock('../use_send_message');
|
|||
jest.mock('../use_conversation');
|
||||
jest.mock('../../..');
|
||||
|
||||
const setEditingSystemPromptId = jest.fn();
|
||||
const setSelectedPromptContexts = jest.fn();
|
||||
const setUserPrompt = jest.fn();
|
||||
const sendMessage = jest.fn();
|
||||
const removeLastMessage = jest.fn();
|
||||
const clearConversation = jest.fn();
|
||||
|
@ -40,11 +38,10 @@ export const testProps: UseChatSendProps = {
|
|||
anonymousPaths: {},
|
||||
externalUrl: {},
|
||||
} as unknown as HttpSetup,
|
||||
editingSystemPromptId: defaultSystemPrompt.id,
|
||||
setEditingSystemPromptId,
|
||||
currentSystemPromptId: defaultSystemPrompt.id,
|
||||
setSelectedPromptContexts,
|
||||
setUserPrompt,
|
||||
setCurrentConversation,
|
||||
refetchCurrentUserConversations: jest.fn(),
|
||||
};
|
||||
const robotMessage = { response: 'Response message from the robot', isError: false };
|
||||
const reportAssistantMessageSent = jest.fn();
|
||||
|
@ -70,29 +67,23 @@ describe('use chat send', () => {
|
|||
const { result } = renderHook(() => useChatSend(testProps), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
result.current.handleOnChatCleared();
|
||||
await act(async () => {
|
||||
result.current.handleOnChatCleared();
|
||||
});
|
||||
expect(clearConversation).toHaveBeenCalled();
|
||||
expect(setUserPrompt).toHaveBeenCalledWith('');
|
||||
expect(result.current.userPrompt).toEqual('');
|
||||
expect(setSelectedPromptContexts).toHaveBeenCalledWith({});
|
||||
await waitFor(() => {
|
||||
expect(clearConversation).toHaveBeenCalledWith(testProps.currentConversation);
|
||||
expect(setCurrentConversation).toHaveBeenCalled();
|
||||
});
|
||||
expect(setEditingSystemPromptId).toHaveBeenCalledWith(defaultSystemPrompt.id);
|
||||
});
|
||||
it('handlePromptChange updates prompt successfully', () => {
|
||||
const { result } = renderHook(() => useChatSend(testProps), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
result.current.handlePromptChange('new prompt');
|
||||
expect(setUserPrompt).toHaveBeenCalledWith('new prompt');
|
||||
});
|
||||
it('handleSendMessage sends message with context prompt when a valid prompt text is provided', async () => {
|
||||
it('handleChatSend sends message with context prompt when a valid prompt text is provided', async () => {
|
||||
const promptText = 'prompt text';
|
||||
const { result } = renderHook(() => useChatSend(testProps), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
result.current.handleSendMessage(promptText);
|
||||
result.current.handleChatSend(promptText);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendMessage).toHaveBeenCalled();
|
||||
|
@ -102,7 +93,7 @@ describe('use chat send', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
it('handleSendMessage sends message with only provided prompt text and context already exists in convo history', async () => {
|
||||
it('handleChatSend sends message with only provided prompt text and context already exists in convo history', async () => {
|
||||
const promptText = 'prompt text';
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
|
@ -112,7 +103,7 @@ describe('use chat send', () => {
|
|||
}
|
||||
);
|
||||
|
||||
result.current.handleSendMessage(promptText);
|
||||
result.current.handleChatSend(promptText);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendMessage).toHaveBeenCalled();
|
||||
|
@ -143,7 +134,7 @@ describe('use chat send', () => {
|
|||
const { result } = renderHook(() => useChatSend(testProps), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
result.current.handleSendMessage(promptText);
|
||||
result.current.handleChatSend(promptText);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(reportAssistantMessageSent).toHaveBeenNthCalledWith(1, {
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PromptResponse, Replacements } from '@kbn/elastic-assistant-common';
|
||||
import { DataStreamApis } from '../use_data_stream_apis';
|
||||
import { NEW_CHAT } from '../conversations/conversation_sidepanel/translations';
|
||||
import type { ClientMessage } from '../../assistant_context/types';
|
||||
import { SelectedPromptContext } from '../prompt_context/types';
|
||||
import { useSendMessage } from '../use_send_message';
|
||||
|
@ -16,56 +18,49 @@ import { useConversation } from '../use_conversation';
|
|||
import { getCombinedMessage } from '../prompt/helpers';
|
||||
import { Conversation, useAssistantContext } from '../../..';
|
||||
import { getMessageFromRawResponse } from '../helpers';
|
||||
import { getDefaultSystemPrompt, getDefaultNewSystemPrompt } from '../use_conversation/helpers';
|
||||
|
||||
export interface UseChatSendProps {
|
||||
allSystemPrompts: PromptResponse[];
|
||||
currentConversation?: Conversation;
|
||||
editingSystemPromptId: string | undefined;
|
||||
currentSystemPromptId: string | undefined;
|
||||
http: HttpSetup;
|
||||
refetchCurrentUserConversations: DataStreamApis['refetchCurrentUserConversations'];
|
||||
selectedPromptContexts: Record<string, SelectedPromptContext>;
|
||||
setEditingSystemPromptId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
setSelectedPromptContexts: React.Dispatch<
|
||||
React.SetStateAction<Record<string, SelectedPromptContext>>
|
||||
>;
|
||||
setUserPrompt: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
setCurrentConversation: React.Dispatch<React.SetStateAction<Conversation | undefined>>;
|
||||
}
|
||||
|
||||
export interface UseChatSend {
|
||||
abortStream: () => void;
|
||||
handleOnChatCleared: () => Promise<void>;
|
||||
handlePromptChange: (prompt: string) => void;
|
||||
handleSendMessage: (promptText: string) => void;
|
||||
handleRegenerateResponse: () => void;
|
||||
handleChatSend: (promptText: string) => Promise<void>;
|
||||
setUserPrompt: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
isLoading: boolean;
|
||||
userPrompt: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* handles sending messages to an API and updating the conversation state.
|
||||
* Provides a set of functions that can be used to handle user input, send messages to an API,
|
||||
* and update the conversation state based on the API response.
|
||||
* Handles sending user messages to the API and updating the conversation state.
|
||||
*/
|
||||
export const useChatSend = ({
|
||||
allSystemPrompts,
|
||||
currentConversation,
|
||||
editingSystemPromptId,
|
||||
currentSystemPromptId,
|
||||
http,
|
||||
refetchCurrentUserConversations,
|
||||
selectedPromptContexts,
|
||||
setEditingSystemPromptId,
|
||||
setSelectedPromptContexts,
|
||||
setUserPrompt,
|
||||
setCurrentConversation,
|
||||
}: UseChatSendProps): UseChatSend => {
|
||||
const { assistantTelemetry, toasts } = useAssistantContext();
|
||||
const [userPrompt, setUserPrompt] = useState<string | null>(null);
|
||||
|
||||
const { isLoading, sendMessage, abortStream } = useSendMessage();
|
||||
const { clearConversation, removeLastMessage } = useConversation();
|
||||
|
||||
const handlePromptChange = (prompt: string) => {
|
||||
setUserPrompt(prompt);
|
||||
};
|
||||
|
||||
// Handles sending latest user prompt to API
|
||||
const handleSendMessage = useCallback(
|
||||
async (promptText: string) => {
|
||||
|
@ -80,7 +75,7 @@ export const useChatSend = ({
|
|||
);
|
||||
return;
|
||||
}
|
||||
const systemPrompt = allSystemPrompts.find((prompt) => prompt.id === editingSystemPromptId);
|
||||
const systemPrompt = allSystemPrompts.find((prompt) => prompt.id === currentSystemPromptId);
|
||||
|
||||
const userMessage = getCombinedMessage({
|
||||
isNewChat: currentConversation.messages.length === 0,
|
||||
|
@ -149,7 +144,7 @@ export const useChatSend = ({
|
|||
allSystemPrompts,
|
||||
assistantTelemetry,
|
||||
currentConversation,
|
||||
editingSystemPromptId,
|
||||
currentSystemPromptId,
|
||||
http,
|
||||
selectedPromptContexts,
|
||||
sendMessage,
|
||||
|
@ -193,13 +188,7 @@ export const useChatSend = ({
|
|||
});
|
||||
}, [currentConversation, http, removeLastMessage, sendMessage, setCurrentConversation, toasts]);
|
||||
|
||||
const handleOnChatCleared = useCallback(async () => {
|
||||
const defaultSystemPromptId =
|
||||
getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: currentConversation,
|
||||
})?.id ?? getDefaultNewSystemPrompt(allSystemPrompts)?.id;
|
||||
|
||||
const onChatCleared = useCallback(async () => {
|
||||
setUserPrompt('');
|
||||
setSelectedPromptContexts({});
|
||||
if (currentConversation) {
|
||||
|
@ -208,23 +197,36 @@ export const useChatSend = ({
|
|||
setCurrentConversation(updatedConversation);
|
||||
}
|
||||
}
|
||||
setEditingSystemPromptId(defaultSystemPromptId);
|
||||
}, [
|
||||
allSystemPrompts,
|
||||
clearConversation,
|
||||
currentConversation,
|
||||
setCurrentConversation,
|
||||
setEditingSystemPromptId,
|
||||
setSelectedPromptContexts,
|
||||
setUserPrompt,
|
||||
]);
|
||||
|
||||
const handleOnChatCleared = useCallback(async () => {
|
||||
await onChatCleared();
|
||||
await refetchCurrentUserConversations();
|
||||
}, [onChatCleared, refetchCurrentUserConversations]);
|
||||
|
||||
const handleChatSend = useCallback(
|
||||
async (promptText: string) => {
|
||||
await handleSendMessage(promptText);
|
||||
if (currentConversation?.title === NEW_CHAT) {
|
||||
await refetchCurrentUserConversations();
|
||||
}
|
||||
},
|
||||
[currentConversation, handleSendMessage, refetchCurrentUserConversations]
|
||||
);
|
||||
|
||||
return {
|
||||
abortStream,
|
||||
handleOnChatCleared,
|
||||
handlePromptChange,
|
||||
handleSendMessage,
|
||||
handleChatSend,
|
||||
abortStream,
|
||||
handleRegenerateResponse,
|
||||
isLoading,
|
||||
userPrompt,
|
||||
setUserPrompt,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,209 +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 from 'react';
|
||||
import { ConversationSelector } from '.';
|
||||
import { render, fireEvent, within } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../mock/test_providers/test_providers';
|
||||
import { alertConvo, customConvo, welcomeConvo } from '../../../mock/conversation';
|
||||
import { CONVERSATION_SELECTOR_PLACE_HOLDER } from './translations';
|
||||
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants';
|
||||
|
||||
const setConversation = jest.fn();
|
||||
const deleteConversation = jest.fn();
|
||||
const mockConversation = {
|
||||
appendMessage: jest.fn(),
|
||||
appendReplacements: jest.fn(),
|
||||
clearConversation: jest.fn(),
|
||||
createConversation: jest.fn(),
|
||||
deleteConversation,
|
||||
setApiConfig: jest.fn(),
|
||||
setConversation,
|
||||
};
|
||||
|
||||
const mockConversations = {
|
||||
[alertConvo.title]: alertConvo,
|
||||
[welcomeConvo.title]: welcomeConvo,
|
||||
};
|
||||
|
||||
const mockConversationsWithCustom = {
|
||||
[alertConvo.title]: alertConvo,
|
||||
[welcomeConvo.title]: welcomeConvo,
|
||||
[customConvo.title]: customConvo,
|
||||
};
|
||||
|
||||
jest.mock('../../use_conversation', () => ({
|
||||
useConversation: () => mockConversation,
|
||||
}));
|
||||
|
||||
const onConversationSelected = jest.fn();
|
||||
const onConversationDeleted = jest.fn();
|
||||
const defaultProps = {
|
||||
isDisabled: false,
|
||||
onConversationSelected,
|
||||
selectedConversationId: 'Welcome',
|
||||
defaultConnectorId: '123',
|
||||
defaultProvider: OpenAiProviderType.OpenAi,
|
||||
conversations: mockConversations,
|
||||
onConversationDeleted,
|
||||
allPrompts: [],
|
||||
};
|
||||
describe('Conversation selector', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('renders with correct selected conversation', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConversationSelector {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId('conversation-selector')).toBeInTheDocument();
|
||||
expect(getByTestId('comboBoxSearchInput')).toHaveValue(welcomeConvo.title);
|
||||
});
|
||||
it('On change, selects new item', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConversationSelector {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
fireEvent.click(getByTestId('comboBoxSearchInput'));
|
||||
fireEvent.click(getByTestId(`convo-option-${alertConvo.title}`));
|
||||
expect(onConversationSelected).toHaveBeenCalledWith({
|
||||
cId: '',
|
||||
cTitle: alertConvo.title,
|
||||
});
|
||||
});
|
||||
it('On clear input, clears selected options', () => {
|
||||
const { getByPlaceholderText, queryByPlaceholderText, getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConversationSelector {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId('comboBoxSearchInput')).toBeInTheDocument();
|
||||
expect(queryByPlaceholderText(CONVERSATION_SELECTOR_PLACE_HOLDER)).not.toBeInTheDocument();
|
||||
fireEvent.click(getByTestId('comboBoxClearButton'));
|
||||
expect(getByPlaceholderText(CONVERSATION_SELECTOR_PLACE_HOLDER)).toBeInTheDocument();
|
||||
expect(queryByTestId('euiComboBoxPill')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('We can add a custom option', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConversationSelector {...defaultProps} conversations={mockConversationsWithCustom} />
|
||||
</TestProviders>
|
||||
);
|
||||
const customOption = 'Custom option';
|
||||
fireEvent.change(getByTestId('comboBoxSearchInput'), { target: { value: customOption } });
|
||||
fireEvent.keyDown(getByTestId('comboBoxSearchInput'), {
|
||||
key: 'Enter',
|
||||
code: 'Enter',
|
||||
charCode: 13,
|
||||
});
|
||||
expect(onConversationSelected).toHaveBeenCalledWith({
|
||||
cId: '',
|
||||
cTitle: customOption,
|
||||
});
|
||||
});
|
||||
|
||||
it('Only custom options can be deleted', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConversationSelector
|
||||
{...{ ...defaultProps, conversations: mockConversationsWithCustom }}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('comboBoxSearchInput'));
|
||||
expect(
|
||||
within(getByTestId(`convo-option-${customConvo.title}`)).getByTestId('delete-option')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
within(getByTestId(`convo-option-${alertConvo.title}`)).queryByTestId('delete-option')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Custom options can be deleted', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConversationSelector
|
||||
{...{ ...defaultProps, conversations: mockConversationsWithCustom }}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('comboBoxSearchInput'));
|
||||
fireEvent.click(
|
||||
within(getByTestId(`convo-option-${customConvo.title}`)).getByTestId('delete-option')
|
||||
);
|
||||
jest.runAllTimers();
|
||||
expect(onConversationSelected).not.toHaveBeenCalled();
|
||||
|
||||
expect(onConversationDeleted).toHaveBeenCalledWith(customConvo.title);
|
||||
});
|
||||
|
||||
it('Previous conversation is set to active when selected conversation is deleted', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConversationSelector
|
||||
{...{ ...defaultProps, conversations: mockConversationsWithCustom }}
|
||||
selectedConversationId={customConvo.title}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('comboBoxSearchInput'));
|
||||
fireEvent.click(
|
||||
within(getByTestId(`convo-option-${customConvo.title}`)).getByTestId('delete-option')
|
||||
);
|
||||
expect(onConversationSelected).toHaveBeenCalledWith({
|
||||
cId: '',
|
||||
cTitle: welcomeConvo.title,
|
||||
});
|
||||
});
|
||||
|
||||
it('Right arrow does nothing when ctrlKey is false', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConversationSelector {...defaultProps} conversations={mockConversationsWithCustom} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.keyDown(getByTestId('comboBoxSearchInput'), {
|
||||
key: 'ArrowRight',
|
||||
ctrlKey: false,
|
||||
code: 'ArrowRight',
|
||||
charCode: 26,
|
||||
});
|
||||
expect(onConversationSelected).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Right arrow does nothing when conversation lenth is 1', () => {
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<ConversationSelector
|
||||
{...defaultProps}
|
||||
conversations={{
|
||||
[welcomeConvo.title]: welcomeConvo,
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.keyDown(getByTestId('comboBoxSearchInput'), {
|
||||
key: 'ArrowRight',
|
||||
ctrlKey: true,
|
||||
code: 'ArrowRight',
|
||||
charCode: 26,
|
||||
});
|
||||
expect(onConversationSelected).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -1,302 +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 {
|
||||
EuiButtonIcon,
|
||||
EuiComboBox,
|
||||
EuiComboBoxOptionOption,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiHighlight,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import {
|
||||
PromptResponse,
|
||||
PromptTypeEnum,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { getGenAiConfig } from '../../../connectorland/helpers';
|
||||
import { AIConnector } from '../../../connectorland/connector_selector';
|
||||
import { Conversation } from '../../../..';
|
||||
import * as i18n from './translations';
|
||||
import { DEFAULT_CONVERSATION_TITLE } from '../../use_conversation/translations';
|
||||
import { useConversation } from '../../use_conversation';
|
||||
import { SystemPromptSelectorOption } from '../../prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector';
|
||||
|
||||
interface Props {
|
||||
defaultConnector?: AIConnector;
|
||||
selectedConversationId: string | undefined;
|
||||
onConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => void;
|
||||
onConversationDeleted: (conversationId: string) => void;
|
||||
isDisabled?: boolean;
|
||||
conversations: Record<string, Conversation>;
|
||||
allPrompts: PromptResponse[];
|
||||
}
|
||||
|
||||
const getPreviousConversationId = (conversationIds: string[], selectedConversationId: string) => {
|
||||
return conversationIds.indexOf(selectedConversationId) === 0
|
||||
? conversationIds[conversationIds.length - 1]
|
||||
: conversationIds[conversationIds.indexOf(selectedConversationId) - 1];
|
||||
};
|
||||
|
||||
const getNextConversationId = (conversationIds: string[], selectedConversationId: string) => {
|
||||
return conversationIds.indexOf(selectedConversationId) + 1 >= conversationIds.length
|
||||
? conversationIds[0]
|
||||
: conversationIds[conversationIds.indexOf(selectedConversationId) + 1];
|
||||
};
|
||||
|
||||
const getConvoId = (cId: string, cTitle: string): string => (cId === cTitle ? '' : cId);
|
||||
|
||||
export type ConversationSelectorOption = EuiComboBoxOptionOption<{
|
||||
isDefault: boolean;
|
||||
}>;
|
||||
|
||||
export const ConversationSelector: React.FC<Props> = React.memo(
|
||||
({
|
||||
selectedConversationId = DEFAULT_CONVERSATION_TITLE,
|
||||
defaultConnector,
|
||||
onConversationSelected,
|
||||
onConversationDeleted,
|
||||
isDisabled = false,
|
||||
conversations,
|
||||
allPrompts,
|
||||
}) => {
|
||||
const { createConversation } = useConversation();
|
||||
const allSystemPrompts = useMemo(
|
||||
() => allPrompts.filter((p) => p.promptType === PromptTypeEnum.system),
|
||||
[allPrompts]
|
||||
);
|
||||
const conversationIds = useMemo(() => Object.keys(conversations), [conversations]);
|
||||
const conversationOptions = useMemo<ConversationSelectorOption[]>(() => {
|
||||
return Object.values(conversations).map((conversation) => ({
|
||||
value: { isDefault: conversation.isDefault ?? false },
|
||||
id: conversation.id !== '' ? conversation.id : conversation.title,
|
||||
label: conversation.title,
|
||||
}));
|
||||
}, [conversations]);
|
||||
|
||||
const [selectedOptions, setSelectedOptions] = useState<ConversationSelectorOption[]>(() => {
|
||||
return conversationOptions.filter((c) => c.id === selectedConversationId) ?? [];
|
||||
});
|
||||
|
||||
// Callback for when user types to create a new system prompt
|
||||
const onCreateOption = useCallback(
|
||||
async (searchValue, flattenedOptions = []) => {
|
||||
if (!searchValue || !searchValue.trim().toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedSearchValue = searchValue.trim().toLowerCase();
|
||||
const defaultSystemPrompt = allSystemPrompts.find(
|
||||
(systemPrompt) => systemPrompt.isNewConversationDefault
|
||||
);
|
||||
const optionExists =
|
||||
flattenedOptions.findIndex(
|
||||
(option: SystemPromptSelectorOption) =>
|
||||
option.label.trim().toLowerCase() === normalizedSearchValue
|
||||
) !== -1;
|
||||
|
||||
let createdConversation;
|
||||
if (!optionExists) {
|
||||
const config = getGenAiConfig(defaultConnector);
|
||||
const newConversation: Conversation = {
|
||||
id: '',
|
||||
title: searchValue,
|
||||
category: 'assistant',
|
||||
messages: [],
|
||||
replacements: {},
|
||||
...(defaultConnector
|
||||
? {
|
||||
apiConfig: {
|
||||
connectorId: defaultConnector.id,
|
||||
actionTypeId: defaultConnector.actionTypeId,
|
||||
provider: defaultConnector.apiProvider,
|
||||
defaultSystemPromptId: defaultSystemPrompt?.id,
|
||||
model: config?.defaultModel,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
createdConversation = await createConversation(newConversation);
|
||||
}
|
||||
|
||||
onConversationSelected(
|
||||
createdConversation
|
||||
? { cId: createdConversation.id, cTitle: createdConversation.title }
|
||||
: { cId: '', cTitle: DEFAULT_CONVERSATION_TITLE }
|
||||
);
|
||||
},
|
||||
[allSystemPrompts, onConversationSelected, defaultConnector, createConversation]
|
||||
);
|
||||
|
||||
// Callback for when user deletes a conversation
|
||||
const onDelete = useCallback(
|
||||
(conversationId: string) => {
|
||||
onConversationDeleted(conversationId);
|
||||
if (selectedConversationId === conversationId) {
|
||||
const prevConversationId = getPreviousConversationId(
|
||||
conversationIds,
|
||||
selectedConversationId
|
||||
);
|
||||
|
||||
onConversationSelected({
|
||||
cId: getConvoId(conversations[prevConversationId].id, prevConversationId),
|
||||
cTitle: prevConversationId,
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
selectedConversationId,
|
||||
onConversationDeleted,
|
||||
onConversationSelected,
|
||||
conversationIds,
|
||||
conversations,
|
||||
]
|
||||
);
|
||||
|
||||
const onChange = useCallback(
|
||||
async (newOptions: ConversationSelectorOption[]) => {
|
||||
if (newOptions.length === 0 || !newOptions?.[0].id) {
|
||||
setSelectedOptions([]);
|
||||
} else if (conversationOptions.findIndex((o) => o.id === newOptions?.[0].id) !== -1) {
|
||||
const { id, label } = newOptions?.[0];
|
||||
|
||||
await onConversationSelected({ cId: getConvoId(id, label), cTitle: label });
|
||||
}
|
||||
},
|
||||
[conversationOptions, onConversationSelected]
|
||||
);
|
||||
|
||||
const onLeftArrowClick = useCallback(() => {
|
||||
const prevId = getPreviousConversationId(conversationIds, selectedConversationId);
|
||||
|
||||
onConversationSelected({
|
||||
cId: getConvoId(prevId, conversations[prevId]?.title),
|
||||
cTitle: conversations[prevId]?.title,
|
||||
});
|
||||
}, [conversationIds, selectedConversationId, onConversationSelected, conversations]);
|
||||
const onRightArrowClick = useCallback(() => {
|
||||
const nextId = getNextConversationId(conversationIds, selectedConversationId);
|
||||
|
||||
onConversationSelected({
|
||||
cId: getConvoId(nextId, conversations[nextId]?.title),
|
||||
cTitle: conversations[nextId]?.title,
|
||||
});
|
||||
}, [conversationIds, selectedConversationId, onConversationSelected, conversations]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedOptions(conversationOptions.filter((c) => c.id === selectedConversationId));
|
||||
}, [conversationOptions, selectedConversationId]);
|
||||
|
||||
const renderOption: (
|
||||
option: ConversationSelectorOption,
|
||||
searchValue: string
|
||||
) => React.ReactNode = (option, searchValue) => {
|
||||
const { label, id, value } = option;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
className={'parentFlexGroup'}
|
||||
component={'span'}
|
||||
justifyContent="spaceBetween"
|
||||
data-test-subj={`convo-option-${label}`}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component={'span'}
|
||||
grow={false}
|
||||
css={css`
|
||||
width: calc(100% - 60px);
|
||||
`}
|
||||
>
|
||||
<EuiHighlight
|
||||
search={searchValue}
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`}
|
||||
>
|
||||
{label}
|
||||
</EuiHighlight>
|
||||
</EuiFlexItem>
|
||||
{!value?.isDefault && id && (
|
||||
<EuiFlexItem grow={false} component={'span'}>
|
||||
<EuiToolTip position="right" content={i18n.DELETE_CONVERSATION}>
|
||||
<EuiButtonIcon
|
||||
iconType="cross"
|
||||
aria-label={i18n.DELETE_CONVERSATION}
|
||||
color="danger"
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onDelete(id);
|
||||
}}
|
||||
data-test-subj="delete-option"
|
||||
css={css`
|
||||
visibility: hidden;
|
||||
.parentFlexGroup:hover & {
|
||||
visibility: visible;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={i18n.SELECTED_CONVERSATION_LABEL}
|
||||
display="rowCompressed"
|
||||
css={css`
|
||||
min-width: 300px;
|
||||
`}
|
||||
>
|
||||
<EuiComboBox
|
||||
data-test-subj="conversation-selector"
|
||||
aria-label={i18n.CONVERSATION_SELECTOR_ARIA_LABEL}
|
||||
customOptionText={`${i18n.CONVERSATION_SELECTOR_CUSTOM_OPTION_TEXT} {searchValue}`}
|
||||
placeholder={i18n.CONVERSATION_SELECTOR_PLACE_HOLDER}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={conversationOptions}
|
||||
selectedOptions={selectedOptions}
|
||||
onChange={onChange}
|
||||
onCreateOption={onCreateOption as unknown as () => void}
|
||||
renderOption={renderOption}
|
||||
compressed={true}
|
||||
isDisabled={isDisabled}
|
||||
prepend={
|
||||
<EuiToolTip content={`${i18n.PREVIOUS_CONVERSATION_TITLE}`} display="block">
|
||||
<EuiButtonIcon
|
||||
iconType="arrowLeft"
|
||||
aria-label={i18n.PREVIOUS_CONVERSATION_TITLE}
|
||||
onClick={onLeftArrowClick}
|
||||
disabled={isDisabled || conversationIds.length <= 1}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
}
|
||||
append={
|
||||
<EuiToolTip content={`${i18n.NEXT_CONVERSATION_TITLE}`} display="block">
|
||||
<EuiButtonIcon
|
||||
iconType="arrowRight"
|
||||
aria-label={i18n.NEXT_CONVERSATION_TITLE}
|
||||
onClick={onRightArrowClick}
|
||||
disabled={isDisabled || conversationIds.length <= 1}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ConversationSelector.displayName = 'ConversationSelector';
|
|
@ -18,7 +18,7 @@ import React, { useCallback, useMemo, useState } from 'react';
|
|||
import { css } from '@emotion/react';
|
||||
|
||||
import { Conversation } from '../../../..';
|
||||
import * as i18n from '../conversation_selector/translations';
|
||||
import * as i18n from './translations';
|
||||
import { SystemPromptSelectorOption } from '../../prompt_editor/system_prompt/system_prompt_modal/system_prompt_selector/system_prompt_selector';
|
||||
import { ConversationSelectorSettingsOption } from './types';
|
||||
|
||||
|
|
|
@ -56,13 +56,6 @@ export const CONVERSATIONS_TABLE_COLUMN_UPDATED_AT = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const CONVERSATIONS_TABLE_COLUMN_ACTIONS = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversationSettings.column.actions',
|
||||
{
|
||||
defaultMessage: 'Actions',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONVERSATIONS_FLYOUT_DEFAULT_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversationSettings.flyout.defaultTitle',
|
||||
{
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EuiComboBoxOptionOption,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButton,
|
||||
|
@ -20,6 +19,7 @@ import useEvent from 'react-use/lib/useEvent';
|
|||
|
||||
import { css } from '@emotion/react';
|
||||
import { isEmpty, findIndex, orderBy } from 'lodash';
|
||||
import { DataStreamApis } from '../../use_data_stream_apis';
|
||||
import { Conversation } from '../../../..';
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -33,7 +33,7 @@ interface Props {
|
|||
conversations: Record<string, Conversation>;
|
||||
onConversationDeleted: (conversationId: string) => void;
|
||||
onConversationCreate: () => void;
|
||||
refetchConversationsState: () => Promise<void>;
|
||||
refetchCurrentUserConversations: DataStreamApis['refetchCurrentUserConversations'];
|
||||
}
|
||||
|
||||
const getCurrentConversationIndex = (
|
||||
|
@ -69,11 +69,6 @@ const getNextConversation = (
|
|||
? conversationList[0]
|
||||
: conversationList[conversationIndex + 1];
|
||||
};
|
||||
|
||||
export type ConversationSelectorOption = EuiComboBoxOptionOption<{
|
||||
isDefault: boolean;
|
||||
}>;
|
||||
|
||||
export const ConversationSidePanel = React.memo<Props>(
|
||||
({
|
||||
currentConversation,
|
||||
|
|
|
@ -1,72 +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, { useMemo } from 'react';
|
||||
import { useController } from 'react-hook-form';
|
||||
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
interface TitleFieldProps {
|
||||
conversationIds?: string[];
|
||||
euiFieldProps?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const TitleFieldComponent = ({ conversationIds, euiFieldProps }: TitleFieldProps) => {
|
||||
const {
|
||||
field: { onChange, value, name: fieldName },
|
||||
fieldState: { error },
|
||||
} = useController({
|
||||
name: 'title',
|
||||
defaultValue: '',
|
||||
rules: {
|
||||
required: {
|
||||
message: i18n.translate(
|
||||
'xpack.elasticAssistant.conversationSidepanel.titleField.titleIsRequired',
|
||||
{
|
||||
defaultMessage: 'Title is required',
|
||||
}
|
||||
),
|
||||
value: true,
|
||||
},
|
||||
validate: () => {
|
||||
if (conversationIds?.includes(value)) {
|
||||
return i18n.translate(
|
||||
'xpack.elasticAssistant.conversationSidepanel.titleField.uniqueTitle',
|
||||
{
|
||||
defaultMessage: 'Title must be unique',
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const hasError = useMemo(() => !!error?.message, [error?.message]);
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.elasticAssistant.conversationSidepanel.titleFieldLabel', {
|
||||
defaultMessage: 'Title',
|
||||
})}
|
||||
error={error?.message}
|
||||
isInvalid={hasError}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
isInvalid={hasError}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
name={fieldName}
|
||||
fullWidth
|
||||
data-test-subj="input"
|
||||
{...euiFieldProps}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
};
|
||||
|
||||
export const TitleField = React.memo(TitleFieldComponent, deepEqual);
|
|
@ -7,20 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const SELECTED_CONVERSATION_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversationSelector.defaultConversationTitle',
|
||||
{
|
||||
defaultMessage: 'Conversations',
|
||||
}
|
||||
);
|
||||
|
||||
export const NEXT_CONVERSATION_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.conversationSelector.nextConversationTitle',
|
||||
{
|
||||
defaultMessage: 'Next conversation',
|
||||
}
|
||||
);
|
||||
|
||||
export const DELETE_CONVERSATION_ARIA_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.sidePanel.deleteConversationAriaLabel',
|
||||
{
|
||||
|
|
|
@ -6,107 +6,13 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
getBlockBotConversation,
|
||||
getDefaultConnector,
|
||||
getOptionalRequestParams,
|
||||
mergeBaseWithPersistedConversations,
|
||||
} from './helpers';
|
||||
import { enterpriseMessaging } from './use_conversation/sample_conversations';
|
||||
import { AIConnector } from '../connectorland/connector_selector';
|
||||
const defaultConversation = {
|
||||
id: 'conversation_id',
|
||||
category: 'assistant',
|
||||
theme: {},
|
||||
messages: [],
|
||||
apiConfig: { actionTypeId: '.gen-ai', connectorId: '123' },
|
||||
replacements: {},
|
||||
title: 'conversation_id',
|
||||
};
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('isAssistantEnabled = false', () => {
|
||||
const isAssistantEnabled = false;
|
||||
it('When no conversation history, return only enterprise messaging', () => {
|
||||
const result = getBlockBotConversation(defaultConversation, isAssistantEnabled);
|
||||
expect(result.messages).toEqual(enterpriseMessaging);
|
||||
expect(result.messages.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('When conversation history and the last message is not enterprise messaging, appends enterprise messaging to conversation', () => {
|
||||
const conversation = {
|
||||
...defaultConversation,
|
||||
messages: [
|
||||
{
|
||||
role: 'user' as const,
|
||||
content: 'Hello',
|
||||
timestamp: '',
|
||||
presentation: {
|
||||
delay: 0,
|
||||
stream: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled);
|
||||
expect(result.messages.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('returns the conversation without changes when the last message is enterprise messaging', () => {
|
||||
const conversation = {
|
||||
...defaultConversation,
|
||||
messages: enterpriseMessaging,
|
||||
};
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled);
|
||||
expect(result.messages.length).toEqual(1);
|
||||
expect(result.messages).toEqual(enterpriseMessaging);
|
||||
});
|
||||
|
||||
it('returns the conversation with new enterprise message when conversation has enterprise messaging, but not as the last message', () => {
|
||||
const conversation = {
|
||||
...defaultConversation,
|
||||
messages: [
|
||||
...enterpriseMessaging,
|
||||
{
|
||||
role: 'user' as const,
|
||||
content: 'Hello',
|
||||
timestamp: '',
|
||||
presentation: {
|
||||
delay: 0,
|
||||
stream: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled);
|
||||
expect(result.messages.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAssistantEnabled = true', () => {
|
||||
const isAssistantEnabled = true;
|
||||
it('when no conversation history, returns the welcome conversation', () => {
|
||||
const result = getBlockBotConversation(defaultConversation, isAssistantEnabled);
|
||||
expect(result.messages.length).toEqual(0);
|
||||
});
|
||||
it('returns a conversation history with the welcome conversation appended', () => {
|
||||
const conversation = {
|
||||
...defaultConversation,
|
||||
messages: [
|
||||
{
|
||||
role: 'user' as const,
|
||||
content: 'Hello',
|
||||
timestamp: '',
|
||||
presentation: {
|
||||
delay: 0,
|
||||
stream: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = getBlockBotConversation(conversation, isAssistantEnabled);
|
||||
expect(result.messages.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultConnector', () => {
|
||||
const defaultConnector: AIConnector = {
|
||||
actionTypeId: '.gen-ai',
|
||||
|
|
|
@ -10,7 +10,6 @@ import { AIConnector } from '../connectorland/connector_selector';
|
|||
import { FetchConnectorExecuteResponse, FetchConversationsResponse } from './api';
|
||||
import { Conversation } from '../..';
|
||||
import type { ClientMessage } from '../assistant_context/types';
|
||||
import { enterpriseMessaging } from './use_conversation/sample_conversations';
|
||||
|
||||
export const getMessageFromRawResponse = (
|
||||
rawResponse: FetchConnectorExecuteResponse
|
||||
|
@ -54,31 +53,6 @@ export const mergeBaseWithPersistedConversations = (
|
|||
return transformed;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const getBlockBotConversation = (
|
||||
conversation: Conversation,
|
||||
isAssistantEnabled: boolean
|
||||
): Conversation => {
|
||||
if (!isAssistantEnabled) {
|
||||
if (
|
||||
conversation.messages.length === 0 ||
|
||||
conversation.messages[conversation.messages.length - 1].content !==
|
||||
enterpriseMessaging[0].content
|
||||
) {
|
||||
return {
|
||||
...conversation,
|
||||
messages: [...conversation.messages, ...enterpriseMessaging],
|
||||
};
|
||||
}
|
||||
return conversation;
|
||||
}
|
||||
|
||||
return {
|
||||
...conversation,
|
||||
messages: conversation.messages,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a default connector if there is only one connector
|
||||
* @param connectors
|
||||
|
|
|
@ -16,7 +16,6 @@ import { useLoadConnectors } from '../connectorland/use_load_connectors';
|
|||
import { DefinedUseQueryResult, UseQueryResult } from '@tanstack/react-query';
|
||||
|
||||
import { useLocalStorage, useSessionStorage } from 'react-use';
|
||||
import { PromptEditor } from './prompt_editor';
|
||||
import { QuickPrompts } from './quick_prompts/quick_prompts';
|
||||
import { mockAssistantAvailability, TestProviders } from '../mock/test_providers/test_providers';
|
||||
import { useFetchCurrentUserConversations } from './api';
|
||||
|
@ -30,19 +29,11 @@ jest.mock('../connectorland/use_load_connectors');
|
|||
jest.mock('../connectorland/connector_setup');
|
||||
jest.mock('react-use');
|
||||
|
||||
jest.mock('./prompt_editor', () => ({ PromptEditor: jest.fn() }));
|
||||
jest.mock('./quick_prompts/quick_prompts', () => ({ QuickPrompts: jest.fn() }));
|
||||
jest.mock('./api/conversations/use_fetch_current_user_conversations');
|
||||
|
||||
jest.mock('./use_conversation');
|
||||
|
||||
const renderAssistant = (extraProps = {}, providerProps = {}) =>
|
||||
render(
|
||||
<TestProviders>
|
||||
<Assistant chatHistoryVisible={true} setChatHistoryVisible={jest.fn()} {...extraProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const mockData = {
|
||||
welcome_id: {
|
||||
id: 'welcome_id',
|
||||
|
@ -61,6 +52,29 @@ const mockData = {
|
|||
replacements: {},
|
||||
},
|
||||
};
|
||||
|
||||
const renderAssistant = async (extraProps = {}, providerProps = {}) => {
|
||||
const chatSendSpy = jest.spyOn(all, 'useChatSend');
|
||||
const assistant = render(
|
||||
<TestProviders>
|
||||
<Assistant
|
||||
conversationTitle={'Welcome'}
|
||||
chatHistoryVisible={true}
|
||||
setChatHistoryVisible={jest.fn()}
|
||||
{...extraProps}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
await waitFor(() => {
|
||||
// wait for conversation to mount before performing any tests
|
||||
expect(chatSendSpy).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
currentConversation: mockData.welcome_id,
|
||||
})
|
||||
);
|
||||
});
|
||||
return assistant;
|
||||
};
|
||||
const mockDeleteConvo = jest.fn();
|
||||
const mockGetDefaultConversation = jest.fn().mockReturnValue(mockData.welcome_id);
|
||||
const clearConversation = jest.fn();
|
||||
|
@ -84,7 +98,6 @@ describe('Assistant', () => {
|
|||
persistToSessionStorage = jest.fn();
|
||||
(useConversation as jest.Mock).mockReturnValue(mockUseConversation);
|
||||
|
||||
jest.mocked(PromptEditor).mockReturnValue(null);
|
||||
jest.mocked(QuickPrompts).mockReturnValue(null);
|
||||
const connectors: unknown[] = [
|
||||
{
|
||||
|
@ -127,17 +140,9 @@ describe('Assistant', () => {
|
|||
});
|
||||
|
||||
describe('persistent storage', () => {
|
||||
it('should refetchConversationsState after settings save button click', async () => {
|
||||
it('should refetchCurrentUserConversations after settings save button click', async () => {
|
||||
const chatSendSpy = jest.spyOn(all, 'useChatSend');
|
||||
const setConversationTitle = jest.fn();
|
||||
|
||||
renderAssistant({ setConversationTitle });
|
||||
|
||||
expect(chatSendSpy).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
currentConversation: mockData.welcome_id,
|
||||
})
|
||||
);
|
||||
await renderAssistant();
|
||||
|
||||
fireEvent.click(screen.getByTestId('settings'));
|
||||
|
||||
|
@ -181,7 +186,7 @@ describe('Assistant', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should refetchConversationsState after settings save button click, but do not update convos when refetch returns bad results', async () => {
|
||||
it('should refetchCurrentUserConversations after settings save button click, but do not update convos when refetch returns bad results', async () => {
|
||||
jest.mocked(useFetchCurrentUserConversations).mockReturnValue({
|
||||
data: mockData,
|
||||
isLoading: false,
|
||||
|
@ -192,9 +197,7 @@ describe('Assistant', () => {
|
|||
isFetched: true,
|
||||
} as unknown as DefinedUseQueryResult<Record<string, Conversation>, unknown>);
|
||||
const chatSendSpy = jest.spyOn(all, 'useChatSend');
|
||||
const setConversationTitle = jest.fn();
|
||||
|
||||
renderAssistant({ setConversationTitle });
|
||||
await renderAssistant();
|
||||
|
||||
fireEvent.click(screen.getByTestId('settings'));
|
||||
await act(async () => {
|
||||
|
@ -216,7 +219,7 @@ describe('Assistant', () => {
|
|||
});
|
||||
|
||||
it('should delete conversation when delete button is clicked', async () => {
|
||||
renderAssistant();
|
||||
await renderAssistant();
|
||||
const deleteButton = screen.getAllByTestId('delete-option')[0];
|
||||
await act(async () => {
|
||||
fireEvent.click(deleteButton);
|
||||
|
@ -230,8 +233,8 @@ describe('Assistant', () => {
|
|||
expect(mockDeleteConvo).toHaveBeenCalledWith(mockData.electric_sheep_id.id);
|
||||
});
|
||||
});
|
||||
it('should refetchConversationsState after clear chat history button click', async () => {
|
||||
renderAssistant();
|
||||
it('should refetchCurrentUserConversations after clear chat history button click', async () => {
|
||||
await renderAssistant();
|
||||
fireEvent.click(screen.getByTestId('chat-context-menu'));
|
||||
fireEvent.click(screen.getByTestId('clear-chat'));
|
||||
fireEvent.click(screen.getByTestId('confirmModalConfirmButton'));
|
||||
|
@ -248,14 +251,13 @@ describe('Assistant', () => {
|
|||
...mockUseConversation,
|
||||
getConversation,
|
||||
});
|
||||
renderAssistant();
|
||||
await renderAssistant();
|
||||
|
||||
expect(persistToLocalStorage).toHaveBeenCalled();
|
||||
|
||||
expect(persistToLocalStorage).toHaveBeenLastCalledWith(mockData.welcome_id.id);
|
||||
|
||||
const previousConversationButton = await screen.findByText(mockData.electric_sheep_id.title);
|
||||
|
||||
expect(previousConversationButton).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
fireEvent.click(previousConversationButton);
|
||||
|
@ -290,7 +292,7 @@ describe('Assistant', () => {
|
|||
isFetched: true,
|
||||
} as unknown as DefinedUseQueryResult<Record<string, Conversation>, unknown>);
|
||||
|
||||
const { findByText } = renderAssistant();
|
||||
const { findByText } = await renderAssistant();
|
||||
|
||||
expect(persistToLocalStorage).toHaveBeenCalled();
|
||||
|
||||
|
@ -305,37 +307,16 @@ describe('Assistant', () => {
|
|||
});
|
||||
expect(persistToLocalStorage).toHaveBeenLastCalledWith(mockData.welcome_id.id);
|
||||
});
|
||||
it('should call the setConversationTitle callback if it is defined and the conversation id changes', async () => {
|
||||
const getConversation = jest.fn().mockResolvedValue(mockData.electric_sheep_id);
|
||||
(useConversation as jest.Mock).mockReturnValue({
|
||||
...mockUseConversation,
|
||||
getConversation,
|
||||
});
|
||||
const setConversationTitle = jest.fn();
|
||||
|
||||
renderAssistant({ setConversationTitle });
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(await screen.findByText(mockData.electric_sheep_id.title));
|
||||
});
|
||||
|
||||
expect(setConversationTitle).toHaveBeenLastCalledWith('electric sheep');
|
||||
});
|
||||
it('should fetch current conversation when id has value', async () => {
|
||||
const getConversation = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ ...mockData.electric_sheep_id, title: 'updated title' });
|
||||
(useConversation as jest.Mock).mockReturnValue({
|
||||
...mockUseConversation,
|
||||
getConversation,
|
||||
});
|
||||
const refetch = jest.fn();
|
||||
jest.mocked(useFetchCurrentUserConversations).mockReturnValue({
|
||||
data: {
|
||||
...mockData,
|
||||
electric_sheep_id: { ...mockData.electric_sheep_id, title: 'updated title' },
|
||||
},
|
||||
isLoading: false,
|
||||
refetch: jest.fn().mockResolvedValue({
|
||||
refetch: refetch.mockResolvedValue({
|
||||
isLoading: false,
|
||||
data: {
|
||||
...mockData,
|
||||
|
@ -344,14 +325,14 @@ describe('Assistant', () => {
|
|||
}),
|
||||
isFetched: true,
|
||||
} as unknown as DefinedUseQueryResult<Record<string, Conversation>, unknown>);
|
||||
renderAssistant();
|
||||
await renderAssistant();
|
||||
|
||||
const previousConversationButton = await screen.findByText('updated title');
|
||||
await act(async () => {
|
||||
fireEvent.click(previousConversationButton);
|
||||
});
|
||||
|
||||
expect(getConversation).toHaveBeenCalledWith('electric_sheep_id');
|
||||
expect(refetch).toHaveBeenCalled();
|
||||
|
||||
expect(persistToLocalStorage).toHaveBeenLastCalledWith('electric_sheep_id');
|
||||
});
|
||||
|
@ -376,7 +357,7 @@ describe('Assistant', () => {
|
|||
}),
|
||||
isFetched: true,
|
||||
} as unknown as DefinedUseQueryResult<Record<string, Conversation>, unknown>);
|
||||
renderAssistant();
|
||||
await renderAssistant();
|
||||
|
||||
const previousConversationButton = screen.getByLabelText('Previous conversation');
|
||||
await act(async () => {
|
||||
|
@ -396,7 +377,7 @@ describe('Assistant', () => {
|
|||
|
||||
describe('when no connectors are loaded', () => {
|
||||
it('should set welcome conversation id in local storage', async () => {
|
||||
renderAssistant();
|
||||
await renderAssistant();
|
||||
|
||||
expect(persistToLocalStorage).toHaveBeenCalled();
|
||||
expect(persistToLocalStorage).toHaveBeenLastCalledWith(mockData.welcome_id.id);
|
||||
|
@ -405,7 +386,7 @@ describe('Assistant', () => {
|
|||
|
||||
describe('when not authorized', () => {
|
||||
it('should be disabled', async () => {
|
||||
const { queryByTestId } = renderAssistant(
|
||||
const { queryByTestId } = await renderAssistant(
|
||||
{},
|
||||
{
|
||||
assistantAvailability: { ...mockAssistantAvailability, isAssistantEnabled: false },
|
||||
|
|
|
@ -12,7 +12,6 @@ import React, {
|
|||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
|
@ -24,44 +23,32 @@ import {
|
|||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiFlyoutBody,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { find, isEmpty, uniqBy } from 'lodash';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { PromptTypeEnum } from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { AssistantBody } from './assistant_body';
|
||||
import { useCurrentConversation } from './use_current_conversation';
|
||||
import { useDataStreamApis } from './use_data_stream_apis';
|
||||
import { useChatSend } from './chat_send/use_chat_send';
|
||||
import { ChatSend } from './chat_send';
|
||||
import { BlockBotCallToAction } from './block_bot/cta';
|
||||
import { WELCOME_CONVERSATION_TITLE } from './use_conversation/translations';
|
||||
import {
|
||||
getDefaultConnector,
|
||||
getBlockBotConversation,
|
||||
mergeBaseWithPersistedConversations,
|
||||
sleep,
|
||||
} from './helpers';
|
||||
import { getDefaultConnector } from './helpers';
|
||||
|
||||
import { useAssistantContext, UserAvatar } from '../assistant_context';
|
||||
import { ContextPills } from './context_pills';
|
||||
import { getNewSelectedPromptContext } from '../data_anonymization/get_new_selected_prompt_context';
|
||||
import type { PromptContext, SelectedPromptContext } from './prompt_context/types';
|
||||
import { useConversation } from './use_conversation';
|
||||
import { CodeBlockDetails, getDefaultSystemPrompt } from './use_conversation/helpers';
|
||||
import { CodeBlockDetails } from './use_conversation/helpers';
|
||||
import { QuickPrompts } from './quick_prompts/quick_prompts';
|
||||
import { useLoadConnectors } from '../connectorland/use_load_connectors';
|
||||
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 { AssistantHeader } from './assistant_header';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const CONVERSATION_SIDE_PANEL_WIDTH = 220;
|
||||
|
||||
|
@ -71,29 +58,14 @@ const CommentContainer = styled('span')`
|
|||
overflow: hidden;
|
||||
`;
|
||||
|
||||
import {
|
||||
FetchConversationsResponse,
|
||||
useFetchCurrentUserConversations,
|
||||
CONVERSATIONS_QUERY_KEYS,
|
||||
} from './api/conversations/use_fetch_current_user_conversations';
|
||||
import { Conversation } from '../assistant_context/types';
|
||||
import { getGenAiConfig } from '../connectorland/helpers';
|
||||
import { AssistantAnimatedIcon } from './assistant_animated_icon';
|
||||
import { useFetchAnonymizationFields } from './api/anonymization_fields/use_fetch_anonymization_fields';
|
||||
import { SetupKnowledgeBaseButton } from '../knowledge_base/setup_knowledge_base_button';
|
||||
import { useFetchPrompts } from './api/prompts/use_fetch_prompts';
|
||||
|
||||
export interface Props {
|
||||
conversationTitle?: string;
|
||||
embeddedLayout?: boolean;
|
||||
promptContextId?: string;
|
||||
shouldRefocusPrompt?: boolean;
|
||||
showTitle?: boolean;
|
||||
setConversationTitle?: Dispatch<SetStateAction<string>>;
|
||||
onCloseFlyout?: () => void;
|
||||
chatHistoryVisible?: boolean;
|
||||
setChatHistoryVisible?: Dispatch<SetStateAction<boolean>>;
|
||||
conversationTitle?: string;
|
||||
currentUserAvatar?: UserAvatar;
|
||||
onCloseFlyout?: () => void;
|
||||
promptContextId?: string;
|
||||
setChatHistoryVisible?: Dispatch<SetStateAction<boolean>>;
|
||||
shouldRefocusPrompt?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,37 +73,26 @@ export interface Props {
|
|||
* quick prompts for common actions, settings, and prompt context providers.
|
||||
*/
|
||||
const AssistantComponent: React.FC<Props> = ({
|
||||
conversationTitle,
|
||||
embeddedLayout = false,
|
||||
promptContextId = '',
|
||||
shouldRefocusPrompt = false,
|
||||
showTitle = true,
|
||||
setConversationTitle,
|
||||
onCloseFlyout,
|
||||
chatHistoryVisible,
|
||||
setChatHistoryVisible,
|
||||
conversationTitle,
|
||||
currentUserAvatar,
|
||||
onCloseFlyout,
|
||||
promptContextId = '',
|
||||
setChatHistoryVisible,
|
||||
shouldRefocusPrompt = false,
|
||||
}) => {
|
||||
const {
|
||||
assistantAvailability: { isAssistantEnabled },
|
||||
assistantTelemetry,
|
||||
augmentMessageCodeBlocks,
|
||||
assistantAvailability: { isAssistantEnabled },
|
||||
baseConversations,
|
||||
getComments,
|
||||
getLastConversationId,
|
||||
http,
|
||||
promptContexts,
|
||||
setLastConversationId,
|
||||
getLastConversationId,
|
||||
baseConversations,
|
||||
} = useAssistantContext();
|
||||
|
||||
const {
|
||||
getDefaultConversation,
|
||||
getConversation,
|
||||
deleteConversation,
|
||||
setApiConfig,
|
||||
createConversation,
|
||||
} = useConversation();
|
||||
|
||||
const [selectedPromptContexts, setSelectedPromptContexts] = useState<
|
||||
Record<string, SelectedPromptContext>
|
||||
>({});
|
||||
|
@ -141,172 +102,81 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
[selectedPromptContexts]
|
||||
);
|
||||
|
||||
const onFetchedConversations = useCallback(
|
||||
(conversationsData: FetchConversationsResponse): Record<string, Conversation> =>
|
||||
mergeBaseWithPersistedConversations(baseConversations, conversationsData),
|
||||
[baseConversations]
|
||||
);
|
||||
const [isStreaming, setIsStreaming] = useState(false);
|
||||
|
||||
const {
|
||||
data: conversations,
|
||||
isLoading,
|
||||
refetch: refetchResults,
|
||||
isFetched: conversationsLoaded,
|
||||
} = useFetchCurrentUserConversations({
|
||||
http,
|
||||
onFetch: onFetchedConversations,
|
||||
refetchOnWindowFocus: !isStreaming,
|
||||
isAssistantEnabled,
|
||||
});
|
||||
|
||||
const {
|
||||
data: anonymizationFields,
|
||||
isLoading: isLoadingAnonymizationFields,
|
||||
isError: isErrorAnonymizationFields,
|
||||
isFetched: isFetchedAnonymizationFields,
|
||||
} = useFetchAnonymizationFields();
|
||||
|
||||
const {
|
||||
data: { data: allPrompts },
|
||||
refetch: refetchPrompts,
|
||||
isLoading: isLoadingPrompts,
|
||||
} = useFetchPrompts();
|
||||
|
||||
const allSystemPrompts = useMemo(() => {
|
||||
if (!isLoadingPrompts) {
|
||||
return allPrompts.filter((p) => p.promptType === PromptTypeEnum.system);
|
||||
}
|
||||
return [];
|
||||
}, [allPrompts, isLoadingPrompts]);
|
||||
allPrompts,
|
||||
allSystemPrompts,
|
||||
anonymizationFields,
|
||||
conversations,
|
||||
isErrorAnonymizationFields,
|
||||
isFetchedAnonymizationFields,
|
||||
isFetchedCurrentUserConversations,
|
||||
isFetchedPrompts,
|
||||
isLoadingAnonymizationFields,
|
||||
isLoadingCurrentUserConversations,
|
||||
refetchPrompts,
|
||||
refetchCurrentUserConversations,
|
||||
setIsStreaming,
|
||||
} = useDataStreamApis({ http, baseConversations, isAssistantEnabled });
|
||||
|
||||
// Connector details
|
||||
const { data: connectors, isFetchedAfterMount: areConnectorsFetched } = useLoadConnectors({
|
||||
const { data: connectors, isFetchedAfterMount: isFetchedConnectors } = useLoadConnectors({
|
||||
http,
|
||||
});
|
||||
const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]);
|
||||
|
||||
const [currentConversationId, setCurrentConversationId] = useState<string | undefined>();
|
||||
|
||||
const [currentConversation, setCurrentConversation] = useState<Conversation | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (setConversationTitle && currentConversation?.title) {
|
||||
setConversationTitle(currentConversation?.title);
|
||||
}
|
||||
}, [currentConversation?.title, setConversationTitle]);
|
||||
|
||||
const refetchCurrentConversation = useCallback(
|
||||
async ({
|
||||
cId,
|
||||
cTitle,
|
||||
isStreamRefetch = false,
|
||||
}: { cId?: string; cTitle?: string; isStreamRefetch?: boolean } = {}) => {
|
||||
if (cId === '' || (cTitle && !conversations[cTitle])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const conversationId = cId ?? (cTitle && conversations[cTitle].id) ?? currentConversation?.id;
|
||||
|
||||
if (conversationId) {
|
||||
let updatedConversation = await getConversation(conversationId);
|
||||
let retries = 0;
|
||||
const maxRetries = 5;
|
||||
|
||||
// this retry is a workaround for the stream not YET being persisted to the stored conversation
|
||||
while (
|
||||
isStreamRefetch &&
|
||||
updatedConversation &&
|
||||
updatedConversation.messages[updatedConversation.messages.length - 1].role !==
|
||||
'assistant' &&
|
||||
retries < maxRetries
|
||||
) {
|
||||
retries++;
|
||||
await sleep(2000);
|
||||
updatedConversation = await getConversation(conversationId);
|
||||
}
|
||||
|
||||
if (updatedConversation) {
|
||||
setCurrentConversation(updatedConversation);
|
||||
}
|
||||
|
||||
return updatedConversation;
|
||||
}
|
||||
},
|
||||
[conversations, currentConversation?.id, getConversation]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (areConnectorsFetched && conversationsLoaded && Object.keys(conversations).length > 0) {
|
||||
setCurrentConversation((prev) => {
|
||||
const nextConversation =
|
||||
(currentConversationId && conversations[currentConversationId]) ||
|
||||
(isAssistantEnabled &&
|
||||
(conversations[getLastConversationId(conversationTitle)] ||
|
||||
find(conversations, ['title', getLastConversationId(conversationTitle)]))) ||
|
||||
find(conversations, ['title', getLastConversationId(WELCOME_CONVERSATION_TITLE)]);
|
||||
|
||||
if (deepEqual(prev, nextConversation)) return prev;
|
||||
|
||||
const conversationToReturn =
|
||||
(nextConversation &&
|
||||
conversations[
|
||||
nextConversation?.id !== '' ? nextConversation?.id : nextConversation?.title
|
||||
]) ??
|
||||
conversations[WELCOME_CONVERSATION_TITLE] ??
|
||||
getDefaultConversation({ cTitle: WELCOME_CONVERSATION_TITLE });
|
||||
|
||||
// updated selected system prompt
|
||||
setEditingSystemPromptId(
|
||||
getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: conversationToReturn,
|
||||
})?.id
|
||||
);
|
||||
if (
|
||||
prev &&
|
||||
prev.id === conversationToReturn.id &&
|
||||
// if the conversation id has not changed and the previous conversation has more messages
|
||||
// it is because the local conversation has a readable stream running
|
||||
// and it has not yet been persisted to the stored conversation
|
||||
prev.messages.length > conversationToReturn.messages.length
|
||||
) {
|
||||
return {
|
||||
...conversationToReturn,
|
||||
messages: prev.messages,
|
||||
};
|
||||
}
|
||||
return conversationToReturn;
|
||||
});
|
||||
}
|
||||
}, [
|
||||
const {
|
||||
currentConversation,
|
||||
currentSystemPromptId,
|
||||
handleCreateConversation,
|
||||
handleOnConversationDeleted,
|
||||
handleOnConversationSelected,
|
||||
refetchCurrentConversation,
|
||||
setCurrentConversation,
|
||||
setCurrentSystemPromptId,
|
||||
} = useCurrentConversation({
|
||||
allSystemPrompts,
|
||||
areConnectorsFetched,
|
||||
conversationTitle,
|
||||
conversations,
|
||||
conversationsLoaded,
|
||||
currentConversationId,
|
||||
getDefaultConversation,
|
||||
getLastConversationId,
|
||||
defaultConnector,
|
||||
refetchCurrentUserConversations,
|
||||
conversationId: getLastConversationId(conversationTitle),
|
||||
mayUpdateConversations:
|
||||
isFetchedConnectors &&
|
||||
isFetchedCurrentUserConversations &&
|
||||
isFetchedPrompts &&
|
||||
Object.keys(conversations).length > 0,
|
||||
});
|
||||
|
||||
const isInitialLoad = useMemo(() => {
|
||||
if (!isAssistantEnabled) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
(!isFetchedAnonymizationFields && !isFetchedCurrentUserConversations && !isFetchedPrompts) ||
|
||||
!(currentConversation && currentConversation?.id !== '')
|
||||
);
|
||||
}, [
|
||||
currentConversation,
|
||||
isAssistantEnabled,
|
||||
isFetchedAnonymizationFields,
|
||||
isFetchedCurrentUserConversations,
|
||||
isFetchedPrompts,
|
||||
]);
|
||||
|
||||
// Welcome setup state
|
||||
const isWelcomeSetup = useMemo(() => {
|
||||
// if any conversation has a connector id, we're not in welcome set up
|
||||
return Object.keys(conversations).some(
|
||||
(conversation) => conversations[conversation]?.apiConfig?.connectorId != null
|
||||
)
|
||||
? false
|
||||
: (connectors?.length ?? 0) === 0;
|
||||
}, [connectors?.length, conversations]);
|
||||
const isDisabled = isWelcomeSetup || !isAssistantEnabled;
|
||||
|
||||
// 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),
|
||||
[currentConversation, isAssistantEnabled]
|
||||
const isWelcomeSetup = useMemo(
|
||||
() =>
|
||||
Object.keys(conversations).some(
|
||||
(conversation) =>
|
||||
// if any conversation has a non-empty connector id, we're not in welcome set up
|
||||
conversations[conversation]?.apiConfig?.connectorId != null &&
|
||||
conversations[conversation]?.apiConfig?.connectorId !== ''
|
||||
)
|
||||
? false
|
||||
: (connectors?.length ?? 0) === 0,
|
||||
[connectors?.length, conversations]
|
||||
);
|
||||
const isDisabled = useMemo(
|
||||
() => isWelcomeSetup || !isAssistantEnabled || isInitialLoad,
|
||||
[isWelcomeSetup, isAssistantEnabled, isInitialLoad]
|
||||
);
|
||||
|
||||
// Settings modal state (so it isn't shared between assistant instances like Timeline)
|
||||
|
@ -315,7 +185,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
// Remember last selection for reuse after keyboard shortcut is pressed.
|
||||
// Clear it if there is no connectors
|
||||
useEffect(() => {
|
||||
if (areConnectorsFetched && !connectors?.length) {
|
||||
if (isFetchedConnectors && !connectors?.length) {
|
||||
return setLastConversationId(WELCOME_CONVERSATION_TITLE);
|
||||
}
|
||||
|
||||
|
@ -325,17 +195,15 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
);
|
||||
}
|
||||
}, [
|
||||
areConnectorsFetched,
|
||||
isFetchedConnectors,
|
||||
connectors?.length,
|
||||
conversations,
|
||||
currentConversation,
|
||||
isLoading,
|
||||
isLoadingCurrentUserConversations,
|
||||
setLastConversationId,
|
||||
]);
|
||||
|
||||
const [autoPopulatedOnce, setAutoPopulatedOnce] = useState<boolean>(false);
|
||||
const [userPrompt, setUserPrompt] = useState<string | null>(null);
|
||||
|
||||
const [showAnonymizedValues, setShowAnonymizedValues] = useState<boolean>(false);
|
||||
|
||||
const [messageCodeBlocks, setMessageCodeBlocks] = useState<CodeBlockDetails[][]>();
|
||||
|
@ -352,7 +220,12 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
// Show missing connector callout if no connectors are configured
|
||||
|
||||
const showMissingConnectorCallout = useMemo(() => {
|
||||
if (!isLoading && areConnectorsFetched && currentConversation?.id !== '') {
|
||||
if (
|
||||
!isLoadingCurrentUserConversations &&
|
||||
isFetchedConnectors &&
|
||||
currentConversation &&
|
||||
currentConversation.id !== ''
|
||||
) {
|
||||
if (!currentConversation?.apiConfig?.connectorId) {
|
||||
return true;
|
||||
}
|
||||
|
@ -363,13 +236,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
}
|
||||
|
||||
return false;
|
||||
}, [
|
||||
areConnectorsFetched,
|
||||
connectors,
|
||||
currentConversation?.apiConfig?.connectorId,
|
||||
currentConversation?.id,
|
||||
isLoading,
|
||||
]);
|
||||
}, [isFetchedConnectors, connectors, currentConversation, isLoadingCurrentUserConversations]);
|
||||
|
||||
const isSendingDisabled = useMemo(() => {
|
||||
return isDisabled || showMissingConnectorCallout;
|
||||
|
@ -393,72 +260,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
}, []);
|
||||
// End drill in `Add To Timeline` action
|
||||
|
||||
// Start Scrolling
|
||||
const commentsContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const parent = commentsContainerRef.current?.parentElement;
|
||||
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
// when scrollHeight changes, parent is scrolled to bottom
|
||||
parent.scrollTop = parent.scrollHeight;
|
||||
|
||||
(
|
||||
commentsContainerRef.current?.childNodes[0].childNodes[0] as HTMLElement
|
||||
).lastElementChild?.scrollIntoView();
|
||||
});
|
||||
// End Scrolling
|
||||
|
||||
const selectedSystemPrompt = useMemo(
|
||||
() =>
|
||||
getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: currentConversation,
|
||||
}),
|
||||
[allSystemPrompts, currentConversation]
|
||||
);
|
||||
|
||||
const [editingSystemPromptId, setEditingSystemPromptId] = useState<string | undefined>(
|
||||
selectedSystemPrompt?.id
|
||||
);
|
||||
|
||||
const handleOnConversationSelected = useCallback(
|
||||
async ({ cId, cTitle }: { cId: string; cTitle: string }) => {
|
||||
const updatedConv = await refetchResults();
|
||||
|
||||
let selectedConversation;
|
||||
if (cId === '') {
|
||||
setCurrentConversationId(cTitle);
|
||||
selectedConversation = updatedConv?.data?.[cTitle];
|
||||
setCurrentConversationId(cTitle);
|
||||
} else {
|
||||
selectedConversation = await refetchCurrentConversation({ cId });
|
||||
setCurrentConversationId(cId);
|
||||
}
|
||||
setEditingSystemPromptId(
|
||||
getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: selectedConversation,
|
||||
})?.id
|
||||
);
|
||||
},
|
||||
[allSystemPrompts, refetchCurrentConversation, refetchResults]
|
||||
);
|
||||
|
||||
const handleOnConversationDeleted = useCallback(
|
||||
async (cTitle: string) => {
|
||||
await deleteConversation(conversations[cTitle].id);
|
||||
await refetchResults();
|
||||
},
|
||||
[conversations, deleteConversation, refetchResults]
|
||||
);
|
||||
|
||||
const handleOnSystemPromptSelectionChange = useCallback((systemPromptId?: string) => {
|
||||
setEditingSystemPromptId(systemPromptId);
|
||||
}, []);
|
||||
|
||||
// Add min-height to all codeblocks so timeline icon doesn't overflow
|
||||
const codeBlockContainers = [...document.getElementsByClassName('euiCodeBlock')];
|
||||
// @ts-ignore-expect-error
|
||||
|
@ -469,10 +270,36 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
setShowAnonymizedValues((prevValue) => !prevValue);
|
||||
}, [setShowAnonymizedValues]);
|
||||
|
||||
const isNewConversation = useMemo(
|
||||
() => currentConversation?.messages.length === 0,
|
||||
[currentConversation?.messages.length]
|
||||
);
|
||||
const {
|
||||
abortStream,
|
||||
handleOnChatCleared: onChatCleared,
|
||||
handleChatSend,
|
||||
handleRegenerateResponse,
|
||||
isLoading: isLoadingChatSend,
|
||||
setUserPrompt,
|
||||
userPrompt,
|
||||
} = useChatSend({
|
||||
allSystemPrompts,
|
||||
currentConversation,
|
||||
currentSystemPromptId,
|
||||
http,
|
||||
refetchCurrentUserConversations,
|
||||
selectedPromptContexts,
|
||||
setSelectedPromptContexts,
|
||||
setCurrentConversation,
|
||||
});
|
||||
|
||||
const handleOnChatCleared = useCallback(() => {
|
||||
onChatCleared();
|
||||
if (!currentSystemPromptId) {
|
||||
setCurrentSystemPromptId(currentConversation?.apiConfig?.defaultSystemPromptId);
|
||||
}
|
||||
}, [
|
||||
currentConversation?.apiConfig?.defaultSystemPromptId,
|
||||
currentSystemPromptId,
|
||||
onChatCleared,
|
||||
setCurrentSystemPromptId,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// Adding `conversationTitle !== selectedConversationTitle` to prevent auto-run still executing after changing selected conversation
|
||||
|
@ -526,6 +353,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
isErrorAnonymizationFields,
|
||||
anonymizationFields,
|
||||
isFetchedAnonymizationFields,
|
||||
setUserPrompt,
|
||||
]);
|
||||
|
||||
const createCodeBlockPortals = useCallback(
|
||||
|
@ -548,40 +376,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
[messageCodeBlocks]
|
||||
);
|
||||
|
||||
const {
|
||||
abortStream,
|
||||
handleOnChatCleared: onChatCleared,
|
||||
handlePromptChange,
|
||||
handleSendMessage,
|
||||
handleRegenerateResponse,
|
||||
isLoading: isLoadingChatSend,
|
||||
} = useChatSend({
|
||||
allSystemPrompts,
|
||||
currentConversation,
|
||||
setUserPrompt,
|
||||
editingSystemPromptId,
|
||||
http,
|
||||
setEditingSystemPromptId,
|
||||
selectedPromptContexts,
|
||||
setSelectedPromptContexts,
|
||||
setCurrentConversation,
|
||||
});
|
||||
|
||||
const handleOnChatCleared = useCallback(async () => {
|
||||
await onChatCleared();
|
||||
await refetchResults();
|
||||
}, [onChatCleared, refetchResults]);
|
||||
|
||||
const handleChatSend = useCallback(
|
||||
async (promptText: string) => {
|
||||
await handleSendMessage(promptText);
|
||||
if (currentConversation?.title === NEW_CHAT) {
|
||||
await refetchResults();
|
||||
}
|
||||
},
|
||||
[currentConversation, handleSendMessage, refetchResults]
|
||||
);
|
||||
|
||||
const comments = useMemo(
|
||||
() => (
|
||||
<>
|
||||
|
@ -619,6 +413,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
refetchCurrentConversation,
|
||||
handleRegenerateResponse,
|
||||
isLoadingChatSend,
|
||||
setIsStreaming,
|
||||
currentUserAvatar,
|
||||
selectedPromptContextsCount,
|
||||
]
|
||||
|
@ -636,206 +431,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
[assistantTelemetry, currentConversation?.title]
|
||||
);
|
||||
|
||||
const refetchConversationsState = useCallback(async () => {
|
||||
await refetchResults();
|
||||
}, [refetchResults]);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutateAsync } = useMutation<Conversation | undefined, unknown, Conversation>(
|
||||
['SET_DEFAULT_CONNECTOR'],
|
||||
{
|
||||
mutationFn: async (payload) => {
|
||||
const apiConfig = getGenAiConfig(defaultConnector);
|
||||
return setApiConfig({
|
||||
conversation: payload,
|
||||
apiConfig: {
|
||||
...payload?.apiConfig,
|
||||
connectorId: (defaultConnector?.id as string) ?? '',
|
||||
actionTypeId: (defaultConnector?.actionTypeId as string) ?? '.gen-ai',
|
||||
provider: apiConfig?.apiProvider,
|
||||
model: apiConfig?.defaultModel,
|
||||
defaultSystemPromptId: allSystemPrompts.find((sp) => sp.isNewConversationDefault)?.id,
|
||||
},
|
||||
});
|
||||
},
|
||||
onSuccess: async (data) => {
|
||||
await queryClient.cancelQueries({ queryKey: CONVERSATIONS_QUERY_KEYS });
|
||||
if (data) {
|
||||
queryClient.setQueryData<{ data: Conversation[] }>(CONVERSATIONS_QUERY_KEYS, (prev) => ({
|
||||
...(prev ?? {}),
|
||||
data: uniqBy([data, ...(prev?.data ?? [])], 'id'),
|
||||
}));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (areConnectorsFetched && currentConversation?.id === '' && !isLoadingPrompts) {
|
||||
const conversation = await mutateAsync(currentConversation);
|
||||
if (currentConversation.id === '' && conversation) {
|
||||
setCurrentConversationId(conversation.id);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [areConnectorsFetched, currentConversation, isLoadingPrompts, mutateAsync]);
|
||||
|
||||
const handleCreateConversation = useCallback(async () => {
|
||||
const newChatExists = find(conversations, ['title', NEW_CHAT]);
|
||||
if (newChatExists && !newChatExists.messages.length) {
|
||||
handleOnConversationSelected({
|
||||
cId: newChatExists.id,
|
||||
cTitle: newChatExists.title,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newConversation = await createConversation({
|
||||
title: NEW_CHAT,
|
||||
apiConfig: currentConversation?.apiConfig,
|
||||
});
|
||||
|
||||
await refetchConversationsState();
|
||||
|
||||
if (newConversation) {
|
||||
handleOnConversationSelected({
|
||||
cId: newConversation.id,
|
||||
cTitle: newConversation.title,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
conversations,
|
||||
createConversation,
|
||||
currentConversation?.apiConfig,
|
||||
handleOnConversationSelected,
|
||||
refetchConversationsState,
|
||||
]);
|
||||
|
||||
const disclaimer = useMemo(
|
||||
() =>
|
||||
isNewConversation && (
|
||||
<EuiText
|
||||
data-test-subj="assistant-disclaimer"
|
||||
textAlign="center"
|
||||
color={euiThemeVars.euiColorMediumShade}
|
||||
size="xs"
|
||||
css={css`
|
||||
margin: 0 ${euiThemeVars.euiSizeL} ${euiThemeVars.euiSizeM} ${euiThemeVars.euiSizeL};
|
||||
`}
|
||||
>
|
||||
{i18n.DISCLAIMER}
|
||||
</EuiText>
|
||||
),
|
||||
[isNewConversation]
|
||||
);
|
||||
|
||||
const flyoutBodyContent = useMemo(() => {
|
||||
if (isWelcomeSetup) {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
css={css`
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantAnimatedIcon />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h3>{i18n.WELCOME_SCREEN_TITLE}</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued">
|
||||
<p>{i18n.WELCOME_SCREEN_DESCRIPTION}</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} data-test-subj="connector-prompt">
|
||||
<ConnectorSetup
|
||||
conversation={blockBotConversation}
|
||||
onConversationUpdate={handleOnConversationSelected}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentConversation?.messages.length === 0) {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
css={css`
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantAnimatedIcon />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h3>{i18n.EMPTY_SCREEN_TITLE}</h3>
|
||||
<p>{i18n.EMPTY_SCREEN_DESCRIPTION}</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SystemPrompt
|
||||
conversation={currentConversation}
|
||||
editingSystemPromptId={editingSystemPromptId}
|
||||
onSystemPromptSelectionChange={handleOnSystemPromptSelectionChange}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
refetchConversations={refetchResults}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SetupKnowledgeBaseButton />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
panelRef={(element) => {
|
||||
commentsContainerRef.current = (element?.parentElement as HTMLDivElement) || null;
|
||||
}}
|
||||
>
|
||||
{comments}
|
||||
</EuiPanel>
|
||||
);
|
||||
}, [
|
||||
allSystemPrompts,
|
||||
blockBotConversation,
|
||||
comments,
|
||||
currentConversation,
|
||||
editingSystemPromptId,
|
||||
handleOnConversationSelected,
|
||||
handleOnSystemPromptSelectionChange,
|
||||
isSettingsModalVisible,
|
||||
isWelcomeSetup,
|
||||
refetchResults,
|
||||
]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction={'row'} wrap={false} gutterSize="none">
|
||||
{chatHistoryVisible && (
|
||||
|
@ -852,7 +447,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
conversations={conversations}
|
||||
onConversationDeleted={handleOnConversationDeleted}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
refetchCurrentUserConversations={refetchCurrentUserConversations}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
@ -874,6 +469,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<AssistantHeader
|
||||
isLoading={isInitialLoad}
|
||||
selectedConversation={currentConversation}
|
||||
defaultConnector={defaultConnector}
|
||||
isDisabled={isDisabled || isLoadingChatSend}
|
||||
|
@ -887,8 +483,8 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
setChatHistoryVisible={setChatHistoryVisible}
|
||||
onConversationSelected={handleOnConversationSelected}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={conversationsLoaded}
|
||||
refetchConversationsState={refetchConversationsState}
|
||||
conversationsLoaded={isFetchedCurrentUserConversations}
|
||||
refetchCurrentUserConversations={refetchCurrentUserConversations}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
refetchPrompts={refetchPrompts}
|
||||
|
@ -921,7 +517,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
banner={
|
||||
!isDisabled &&
|
||||
showMissingConnectorCallout &&
|
||||
areConnectorsFetched && (
|
||||
isFetchedConnectors && (
|
||||
<ConnectorMissingCallout
|
||||
isConnectorConfigured={(connectors?.length ?? 0) > 0}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
|
@ -930,24 +526,21 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
)
|
||||
}
|
||||
>
|
||||
{!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>
|
||||
)}
|
||||
<AssistantBody
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
comments={comments}
|
||||
currentConversation={currentConversation}
|
||||
currentSystemPromptId={currentSystemPromptId}
|
||||
handleOnConversationSelected={handleOnConversationSelected}
|
||||
http={http}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
isLoading={isInitialLoad}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
isWelcomeSetup={isWelcomeSetup}
|
||||
refetchCurrentUserConversations={refetchCurrentUserConversations}
|
||||
setCurrentSystemPromptId={setCurrentSystemPromptId}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter
|
||||
css={css`
|
||||
|
@ -997,13 +590,13 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
|
||||
<EuiFlexItem grow={false}>
|
||||
<ChatSend
|
||||
handleChatSend={handleChatSend}
|
||||
setUserPrompt={setUserPrompt}
|
||||
handleRegenerateResponse={handleRegenerateResponse}
|
||||
isDisabled={isSendingDisabled}
|
||||
isLoading={isLoadingChatSend}
|
||||
shouldRefocusPrompt={shouldRefocusPrompt}
|
||||
userPrompt={userPrompt}
|
||||
handlePromptChange={handlePromptChange}
|
||||
handleSendMessage={handleChatSend}
|
||||
handleRegenerateResponse={handleRegenerateResponse}
|
||||
isLoading={isLoadingChatSend}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getPromptById } from './helpers';
|
||||
import { mockSystemPrompt, mockSuperheroSystemPrompt } from '../../mock/system_prompt';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('getPromptById', () => {
|
||||
const prompts: PromptResponse[] = [mockSystemPrompt, mockSuperheroSystemPrompt];
|
||||
|
||||
it('returns the correct prompt by id', () => {
|
||||
const result = getPromptById({ prompts, id: mockSuperheroSystemPrompt.id });
|
||||
|
||||
expect(result).toEqual(prompts[1]);
|
||||
});
|
||||
|
||||
it('returns undefined if the prompt is not found', () => {
|
||||
const result = getPromptById({ prompts, id: 'does-not-exist' });
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,126 +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 from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import { mockAlertPromptContext, mockEventPromptContext } from '../../mock/prompt_context';
|
||||
import { TestProviders } from '../../mock/test_providers/test_providers';
|
||||
import { SelectedPromptContext } from '../prompt_context/types';
|
||||
import { PromptEditor, Props } from '.';
|
||||
|
||||
const mockSelectedAlertPromptContext: SelectedPromptContext = {
|
||||
contextAnonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] },
|
||||
promptContextId: mockAlertPromptContext.id,
|
||||
rawData: 'alert data',
|
||||
};
|
||||
|
||||
const mockSelectedEventPromptContext: SelectedPromptContext = {
|
||||
contextAnonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] },
|
||||
promptContextId: mockEventPromptContext.id,
|
||||
rawData: 'event data',
|
||||
};
|
||||
|
||||
const defaultProps: Props = {
|
||||
conversation: undefined,
|
||||
editingSystemPromptId: undefined,
|
||||
isNewConversation: true,
|
||||
isSettingsModalVisible: false,
|
||||
onSystemPromptSelectionChange: jest.fn(),
|
||||
promptContexts: {
|
||||
[mockAlertPromptContext.id]: mockAlertPromptContext,
|
||||
[mockEventPromptContext.id]: mockEventPromptContext,
|
||||
},
|
||||
promptTextPreview: 'Preview text',
|
||||
selectedPromptContexts: {},
|
||||
setIsSettingsModalVisible: jest.fn(),
|
||||
setSelectedPromptContexts: jest.fn(),
|
||||
allSystemPrompts: [],
|
||||
};
|
||||
|
||||
describe('PromptEditorComponent', () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('renders the system prompt selector when isNewConversation is true', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<PromptEditor {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('selectSystemPrompt')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('does NOT render the system prompt selector when isNewConversation is false', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<PromptEditor {...defaultProps} isNewConversation={false} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('selectSystemPrompt')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the selected prompt contexts', async () => {
|
||||
const selectedPromptContexts = {
|
||||
[mockAlertPromptContext.id]: mockSelectedAlertPromptContext,
|
||||
[mockEventPromptContext.id]: mockSelectedEventPromptContext,
|
||||
};
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<PromptEditor {...defaultProps} selectedPromptContexts={selectedPromptContexts} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
Object.keys(selectedPromptContexts).forEach((id) =>
|
||||
expect(screen.queryByTestId(`selectedPromptContext-${id}`)).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the expected preview text', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<PromptEditor {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('previewText')).toHaveTextContent('Preview text');
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an "editing prompt" `EuiComment` event', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<PromptEditor {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('eventText')).toHaveTextContent('editing prompt');
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the user avatar', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<PromptEditor {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('userAvatar')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,125 +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 { EuiAvatar, EuiCommentList, EuiText } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
// eslint-disable-next-line @kbn/eslint/module_migration
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { Conversation } from '../../..';
|
||||
import type { PromptContext, SelectedPromptContext } from '../prompt_context/types';
|
||||
import { SystemPrompt } from './system_prompt';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { SelectedPromptContexts } from './selected_prompt_contexts';
|
||||
|
||||
export interface Props {
|
||||
conversation: Conversation | undefined;
|
||||
editingSystemPromptId: string | undefined;
|
||||
isNewConversation: boolean;
|
||||
isSettingsModalVisible: boolean;
|
||||
promptContexts: Record<string, PromptContext>;
|
||||
promptTextPreview: string;
|
||||
onSystemPromptSelectionChange: (systemPromptId: string | undefined) => void;
|
||||
selectedPromptContexts: Record<string, SelectedPromptContext>;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setSelectedPromptContexts: React.Dispatch<
|
||||
React.SetStateAction<Record<string, SelectedPromptContext>>
|
||||
>;
|
||||
allSystemPrompts: PromptResponse[];
|
||||
}
|
||||
|
||||
const PreviewText = styled(EuiText)`
|
||||
white-space: pre-line;
|
||||
`;
|
||||
|
||||
const PromptEditorComponent: React.FC<Props> = ({
|
||||
conversation,
|
||||
editingSystemPromptId,
|
||||
isNewConversation,
|
||||
isSettingsModalVisible,
|
||||
promptContexts,
|
||||
promptTextPreview,
|
||||
onSystemPromptSelectionChange,
|
||||
selectedPromptContexts,
|
||||
setIsSettingsModalVisible,
|
||||
setSelectedPromptContexts,
|
||||
allSystemPrompts,
|
||||
}) => {
|
||||
const commentBody = useMemo(
|
||||
() => (
|
||||
<>
|
||||
{isNewConversation && (
|
||||
<SystemPrompt
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
conversation={conversation}
|
||||
editingSystemPromptId={editingSystemPromptId}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SelectedPromptContexts
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
currentReplacements={conversation?.replacements}
|
||||
/>
|
||||
|
||||
<PreviewText color="subdued" data-test-subj="previewText">
|
||||
{promptTextPreview}
|
||||
</PreviewText>
|
||||
</>
|
||||
),
|
||||
[
|
||||
isNewConversation,
|
||||
allSystemPrompts,
|
||||
conversation,
|
||||
editingSystemPromptId,
|
||||
onSystemPromptSelectionChange,
|
||||
isSettingsModalVisible,
|
||||
setIsSettingsModalVisible,
|
||||
promptContexts,
|
||||
selectedPromptContexts,
|
||||
setSelectedPromptContexts,
|
||||
promptTextPreview,
|
||||
]
|
||||
);
|
||||
|
||||
const comments = useMemo(
|
||||
() => [
|
||||
{
|
||||
children: commentBody,
|
||||
event: (
|
||||
<EuiText data-test-subj="eventText" size="xs">
|
||||
<i>{i18n.EDITING_PROMPT}</i>
|
||||
</EuiText>
|
||||
),
|
||||
timelineAvatar: (
|
||||
<EuiAvatar
|
||||
data-test-subj="userAvatar"
|
||||
name="user"
|
||||
size="l"
|
||||
color="subdued"
|
||||
iconType="userAvatar"
|
||||
/>
|
||||
),
|
||||
timelineAvatarAriaLabel: i18n.YOU,
|
||||
username: i18n.YOU,
|
||||
},
|
||||
],
|
||||
[commentBody]
|
||||
);
|
||||
|
||||
return <EuiCommentList aria-label={i18n.COMMENTS_LIST_ARIA_LABEL} comments={comments} />;
|
||||
};
|
||||
|
||||
PromptEditorComponent.displayName = 'PromptEditorComponent';
|
||||
|
||||
export const PromptEditor = React.memo(PromptEditorComponent);
|
|
@ -8,7 +8,6 @@
|
|||
import { EuiAccordion, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
|
||||
import { isEmpty, omit } from 'lodash/fp';
|
||||
import React, { useCallback } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { Conversation } from '../../../assistant_context/types';
|
||||
|
@ -25,14 +24,6 @@ export interface Props {
|
|||
currentReplacements: Conversation['replacements'] | undefined;
|
||||
}
|
||||
|
||||
export const EditorContainer = styled.div<{
|
||||
$accordionState: 'closed' | 'open';
|
||||
}>`
|
||||
${({ $accordionState }) => ($accordionState === 'closed' ? 'height: 0px;' : '')}
|
||||
${({ $accordionState }) => ($accordionState === 'closed' ? 'overflow: hidden;' : '')}
|
||||
${({ $accordionState }) => ($accordionState === 'closed' ? 'position: absolute;' : '')}
|
||||
`;
|
||||
|
||||
const SelectedPromptContextsComponent: React.FC<Props> = ({
|
||||
promptContexts,
|
||||
selectedPromptContexts,
|
||||
|
|
|
@ -62,7 +62,7 @@ jest.mock('../../use_conversation', () => {
|
|||
});
|
||||
|
||||
describe('SystemPrompt', () => {
|
||||
const editingSystemPromptId = undefined;
|
||||
const currentSystemPromptId = undefined;
|
||||
const isSettingsModalVisible = false;
|
||||
const onSystemPromptSelectionChange = jest.fn();
|
||||
const setIsSettingsModalVisible = jest.fn();
|
||||
|
@ -86,7 +86,7 @@ describe('SystemPrompt', () => {
|
|||
render(
|
||||
<SystemPrompt
|
||||
conversation={conversation}
|
||||
editingSystemPromptId={editingSystemPromptId}
|
||||
currentSystemPromptId={currentSystemPromptId}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
|
@ -113,7 +113,7 @@ describe('SystemPrompt', () => {
|
|||
render(
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={mockSystemPrompt.id}
|
||||
currentSystemPromptId={mockSystemPrompt.id}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
|
@ -144,7 +144,7 @@ describe('SystemPrompt', () => {
|
|||
<TestProviders>
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={editingSystemPromptId}
|
||||
currentSystemPromptId={currentSystemPromptId}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
|
@ -191,7 +191,7 @@ describe('SystemPrompt', () => {
|
|||
<TestProviders>
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={editingSystemPromptId}
|
||||
currentSystemPromptId={currentSystemPromptId}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
|
@ -252,7 +252,7 @@ describe('SystemPrompt', () => {
|
|||
<TestProviders>
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={editingSystemPromptId}
|
||||
currentSystemPromptId={currentSystemPromptId}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
|
@ -320,7 +320,7 @@ describe('SystemPrompt', () => {
|
|||
<TestProviders>
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={editingSystemPromptId}
|
||||
currentSystemPromptId={currentSystemPromptId}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
|
@ -403,7 +403,7 @@ describe('SystemPrompt', () => {
|
|||
<TestProviders>
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={editingSystemPromptId}
|
||||
currentSystemPromptId={currentSystemPromptId}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
|
@ -473,7 +473,7 @@ describe('SystemPrompt', () => {
|
|||
<TestProviders>
|
||||
<SystemPrompt
|
||||
conversation={BASE_CONVERSATION}
|
||||
editingSystemPromptId={mockSystemPrompt.id}
|
||||
currentSystemPromptId={mockSystemPrompt.id}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
onSystemPromptSelectionChange={onSystemPromptSelectionChange}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { SelectSystemPrompt } from './select_system_prompt';
|
|||
|
||||
interface Props {
|
||||
conversation: Conversation | undefined;
|
||||
editingSystemPromptId: string | undefined;
|
||||
currentSystemPromptId: string | undefined;
|
||||
isSettingsModalVisible: boolean;
|
||||
onSystemPromptSelectionChange: (systemPromptId: string | undefined) => void;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
@ -23,7 +23,7 @@ interface Props {
|
|||
|
||||
const SystemPromptComponent: React.FC<Props> = ({
|
||||
conversation,
|
||||
editingSystemPromptId,
|
||||
currentSystemPromptId,
|
||||
isSettingsModalVisible,
|
||||
onSystemPromptSelectionChange,
|
||||
setIsSettingsModalVisible,
|
||||
|
@ -32,16 +32,16 @@ const SystemPromptComponent: React.FC<Props> = ({
|
|||
}) => {
|
||||
const [isCleared, setIsCleared] = useState(false);
|
||||
const selectedPrompt = useMemo(() => {
|
||||
if (editingSystemPromptId !== undefined) {
|
||||
if (currentSystemPromptId !== undefined) {
|
||||
setIsCleared(false);
|
||||
return allSystemPrompts.find((p) => p.id === editingSystemPromptId);
|
||||
return allSystemPrompts.find((p) => p.id === currentSystemPromptId);
|
||||
} else {
|
||||
return allSystemPrompts.find((p) => p.id === conversation?.apiConfig?.defaultSystemPromptId);
|
||||
}
|
||||
}, [allSystemPrompts, conversation?.apiConfig?.defaultSystemPromptId, editingSystemPromptId]);
|
||||
}, [allSystemPrompts, conversation?.apiConfig?.defaultSystemPromptId, currentSystemPromptId]);
|
||||
|
||||
const handleClearSystemPrompt = useCallback(() => {
|
||||
if (editingSystemPromptId === undefined) {
|
||||
if (currentSystemPromptId === undefined) {
|
||||
setIsCleared(false);
|
||||
onSystemPromptSelectionChange(
|
||||
allSystemPrompts.find((p) => p.id === conversation?.apiConfig?.defaultSystemPromptId)?.id
|
||||
|
@ -53,7 +53,7 @@ const SystemPromptComponent: React.FC<Props> = ({
|
|||
}, [
|
||||
allSystemPrompts,
|
||||
conversation?.apiConfig?.defaultSystemPromptId,
|
||||
editingSystemPromptId,
|
||||
currentSystemPromptId,
|
||||
onSystemPromptSelectionChange,
|
||||
]);
|
||||
|
||||
|
|
|
@ -20,13 +20,6 @@ export const SETTINGS_DESCRIPTION = i18n.translate(
|
|||
'Create and manage System Prompts. System Prompts are configurable chunks of context that are always sent for a given conversation.',
|
||||
}
|
||||
);
|
||||
export const ADD_SYSTEM_PROMPT_MODAL_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.modalTitle',
|
||||
{
|
||||
defaultMessage: 'System Prompts',
|
||||
}
|
||||
);
|
||||
|
||||
export const SYSTEM_PROMPT_NAME = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.nameLabel',
|
||||
{
|
||||
|
|
|
@ -27,13 +27,6 @@ export const SYSTEM_PROMPTS_TABLE_COLUMN_DATE_UPDATED = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const SYSTEM_PROMPTS_TABLE_COLUMN_ACTIONS = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnActions',
|
||||
{
|
||||
defaultMessage: 'Actions',
|
||||
}
|
||||
);
|
||||
|
||||
export const SYSTEM_PROMPTS_TABLE_SETTINGS_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptsTable.settingsDescription',
|
||||
{
|
||||
|
|
|
@ -7,13 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ADD_SYSTEM_PROMPT_TOOLTIP = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.firstPromptEditor.addSystemPromptTooltip',
|
||||
{
|
||||
defaultMessage: 'Add system prompt',
|
||||
}
|
||||
);
|
||||
|
||||
export const CLEAR_SYSTEM_PROMPT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.firstPromptEditor.clearSystemPrompt',
|
||||
{
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const COMMENTS_LIST_ARIA_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.firstPromptEditor.commentsListAriaLabel',
|
||||
{
|
||||
defaultMessage: 'List of comments',
|
||||
}
|
||||
);
|
||||
|
||||
export const EDITING_PROMPT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.firstPromptEditor.editingPromptLabel',
|
||||
{
|
||||
defaultMessage: 'editing prompt',
|
||||
}
|
||||
);
|
||||
|
||||
export const YOU = i18n.translate('xpack.elasticAssistant.assistant.firstPromptEditor.youLabel', {
|
||||
defaultMessage: 'You',
|
||||
});
|
|
@ -12,19 +12,19 @@ import { css } from '@emotion/react';
|
|||
import * as i18n from './translations';
|
||||
|
||||
export interface Props extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
||||
handlePromptChange: (value: string) => void;
|
||||
setUserPrompt: (value: string) => void;
|
||||
isDisabled?: boolean;
|
||||
onPromptSubmit: (value: string) => void;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const PromptTextArea = forwardRef<HTMLTextAreaElement, Props>(
|
||||
({ isDisabled = false, value, onPromptSubmit, handlePromptChange }, ref) => {
|
||||
({ isDisabled = false, value, onPromptSubmit, setUserPrompt }, ref) => {
|
||||
const onChangeCallback = useCallback(
|
||||
(event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
handlePromptChange(event.target.value);
|
||||
setUserPrompt(event.target.value);
|
||||
},
|
||||
[handlePromptChange]
|
||||
[setUserPrompt]
|
||||
);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
|
@ -35,13 +35,13 @@ export const PromptTextArea = forwardRef<HTMLTextAreaElement, Props>(
|
|||
|
||||
if (value.trim().length) {
|
||||
onPromptSubmit(event.target.value?.trim());
|
||||
handlePromptChange('');
|
||||
setUserPrompt('');
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
[value, onPromptSubmit, handlePromptChange]
|
||||
[value, onPromptSubmit, setUserPrompt]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -17,6 +17,7 @@ const testProps = {
|
|||
selectedQuickPrompt: MOCK_QUICK_PROMPTS[0],
|
||||
onQuickPromptDeleted,
|
||||
onQuickPromptSelectionChange,
|
||||
selectedColor: '#D36086',
|
||||
};
|
||||
|
||||
describe('QuickPromptSelector', () => {
|
||||
|
@ -28,7 +29,10 @@ describe('QuickPromptSelector', () => {
|
|||
expect(getByTestId('euiComboBoxPill')).toHaveTextContent(MOCK_QUICK_PROMPTS[0].name);
|
||||
fireEvent.click(getByTestId('comboBoxToggleListButton'));
|
||||
fireEvent.click(getByTestId(MOCK_QUICK_PROMPTS[1].name));
|
||||
expect(onQuickPromptSelectionChange).toHaveBeenCalledWith(MOCK_QUICK_PROMPTS[1]);
|
||||
expect(onQuickPromptSelectionChange).toHaveBeenCalledWith(
|
||||
MOCK_QUICK_PROMPTS[1],
|
||||
testProps.selectedColor
|
||||
);
|
||||
});
|
||||
it('Only custom option can be deleted', () => {
|
||||
const { getByTestId } = render(<QuickPromptSelector {...testProps} />);
|
||||
|
@ -46,14 +50,17 @@ describe('QuickPromptSelector', () => {
|
|||
code: 'Enter',
|
||||
charCode: 13,
|
||||
});
|
||||
expect(onQuickPromptSelectionChange).toHaveBeenCalledWith({
|
||||
categories: [],
|
||||
color: '#D36086',
|
||||
content: 'quickly prompt please',
|
||||
id: 'A_CUSTOM_OPTION',
|
||||
name: 'A_CUSTOM_OPTION',
|
||||
promptType: 'quick',
|
||||
});
|
||||
expect(onQuickPromptSelectionChange).toHaveBeenCalledWith(
|
||||
{
|
||||
categories: [],
|
||||
color: '#D36086',
|
||||
content: 'quickly prompt please',
|
||||
id: 'A_CUSTOM_OPTION',
|
||||
name: 'A_CUSTOM_OPTION',
|
||||
promptType: 'quick',
|
||||
},
|
||||
testProps.selectedColor
|
||||
);
|
||||
});
|
||||
it('Reset settings every time before selecting an system prompt from the input if resetSettings is provided', () => {
|
||||
const mockResetSettings = jest.fn();
|
||||
|
@ -80,6 +87,9 @@ describe('QuickPromptSelector', () => {
|
|||
code: 'Enter',
|
||||
charCode: 13,
|
||||
});
|
||||
expect(onQuickPromptSelectionChange).toHaveBeenCalledWith(customOption);
|
||||
expect(onQuickPromptSelectionChange).toHaveBeenCalledWith(
|
||||
customOption,
|
||||
testProps.selectedColor
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,8 +24,12 @@ import * as i18n from './translations';
|
|||
interface Props {
|
||||
isDisabled?: boolean;
|
||||
onQuickPromptDeleted: (quickPromptTitle: string) => void;
|
||||
onQuickPromptSelectionChange: (quickPrompt?: PromptResponse | string) => void;
|
||||
onQuickPromptSelectionChange: (
|
||||
quickPrompt: PromptResponse | string,
|
||||
selectedColor: string
|
||||
) => void;
|
||||
quickPrompts: PromptResponse[];
|
||||
selectedColor: string;
|
||||
selectedQuickPrompt?: PromptResponse;
|
||||
resetSettings?: () => void;
|
||||
}
|
||||
|
@ -42,6 +46,7 @@ export const QuickPromptSelector: React.FC<Props> = React.memo(
|
|||
onQuickPromptDeleted,
|
||||
onQuickPromptSelectionChange,
|
||||
resetSettings,
|
||||
selectedColor,
|
||||
selectedQuickPrompt,
|
||||
}) => {
|
||||
// Form options
|
||||
|
@ -80,9 +85,11 @@ export const QuickPromptSelector: React.FC<Props> = React.memo(
|
|||
? undefined
|
||||
: quickPrompts.find((qp) => qp.name === quickPromptSelectorOption[0]?.label) ??
|
||||
quickPromptSelectorOption[0]?.label;
|
||||
onQuickPromptSelectionChange(newQuickPrompt);
|
||||
if (newQuickPrompt) {
|
||||
onQuickPromptSelectionChange(newQuickPrompt, selectedColor);
|
||||
}
|
||||
},
|
||||
[onQuickPromptSelectionChange, resetSettings, quickPrompts]
|
||||
[onQuickPromptSelectionChange, resetSettings, quickPrompts, selectedColor]
|
||||
);
|
||||
|
||||
// Callback for when user types to create a new quick prompt
|
||||
|
|
|
@ -5,12 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { euiPaletteColorBlind } from '@elastic/eui';
|
||||
|
||||
export const getPromptById = ({
|
||||
prompts,
|
||||
id,
|
||||
}: {
|
||||
prompts: PromptResponse[];
|
||||
id: string;
|
||||
}): PromptResponse | undefined => prompts.find((p) => p.id === id);
|
||||
const euiVisPalette = euiPaletteColorBlind();
|
||||
export const getRandomEuiColor = () => {
|
||||
const randomIndex = Math.floor(Math.random() * euiVisPalette.length);
|
||||
return euiVisPalette[randomIndex];
|
||||
};
|
|
@ -14,6 +14,7 @@ import {
|
|||
PromptResponse,
|
||||
PerformPromptsBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { getRandomEuiColor } from './helpers';
|
||||
import { PromptContextTemplate } from '../../../..';
|
||||
import * as i18n from './translations';
|
||||
import { QuickPromptSelector } from '../quick_prompt_selector/quick_prompt_selector';
|
||||
|
@ -21,8 +22,6 @@ import { PromptContextSelector } from '../prompt_context_selector/prompt_context
|
|||
import { useAssistantContext } from '../../../assistant_context';
|
||||
import { useQuickPromptEditor } from './use_quick_prompt_editor';
|
||||
|
||||
const DEFAULT_COLOR = '#D36086';
|
||||
|
||||
interface Props {
|
||||
onSelectedQuickPromptChange: (quickPrompt?: PromptResponse) => void;
|
||||
quickPromptSettings: PromptResponse[];
|
||||
|
@ -112,12 +111,6 @@ const QuickPromptSettingsEditorComponent = ({
|
|||
]
|
||||
);
|
||||
|
||||
// Color
|
||||
const selectedColor = useMemo(
|
||||
() => selectedQuickPrompt?.color ?? DEFAULT_COLOR,
|
||||
[selectedQuickPrompt?.color]
|
||||
);
|
||||
|
||||
const handleColorChange = useCallback<EuiSetColorMethod>(
|
||||
(color, { hex, isValid }) => {
|
||||
if (selectedQuickPrompt != null) {
|
||||
|
@ -177,6 +170,17 @@ const QuickPromptSettingsEditorComponent = ({
|
|||
]
|
||||
);
|
||||
|
||||
const setDefaultPromptColor = useCallback((): string => {
|
||||
const randomColor = getRandomEuiColor();
|
||||
handleColorChange(randomColor, { hex: randomColor, isValid: true });
|
||||
return randomColor;
|
||||
}, [handleColorChange]);
|
||||
|
||||
// Color
|
||||
const selectedColor = useMemo(
|
||||
() => selectedQuickPrompt?.color ?? setDefaultPromptColor(),
|
||||
[selectedQuickPrompt?.color, setDefaultPromptColor]
|
||||
);
|
||||
// Prompt Contexts
|
||||
const selectedPromptContexts = useMemo(
|
||||
() =>
|
||||
|
@ -263,6 +267,7 @@ const QuickPromptSettingsEditorComponent = ({
|
|||
quickPrompts={quickPromptSettings}
|
||||
resetSettings={resetSettings}
|
||||
selectedQuickPrompt={selectedQuickPrompt}
|
||||
selectedColor={selectedColor}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
|
|
|
@ -42,17 +42,17 @@ jest.mock('../quick_prompt_selector/quick_prompt_selector', () => ({
|
|||
<button
|
||||
type="button"
|
||||
data-test-subj="delete-qp"
|
||||
onClick={() => onQuickPromptDeleted('A_CUSTOM_OPTION')}
|
||||
onClick={() => onQuickPromptDeleted('A_CUSTOM_OPTION', '#D36086')}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
data-test-subj="change-qp"
|
||||
onClick={() => onQuickPromptSelectionChange(MOCK_QUICK_PROMPTS[3])}
|
||||
onClick={() => onQuickPromptSelectionChange(MOCK_QUICK_PROMPTS[3], '#D36086')}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
data-test-subj="change-qp-custom"
|
||||
onClick={() => onQuickPromptSelectionChange('sooper custom prompt')}
|
||||
onClick={() => onQuickPromptSelectionChange('sooper custom prompt', '#D36086')}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
|
|
|
@ -21,12 +21,6 @@ export const SETTINGS_DESCRIPTION = i18n.translate(
|
|||
'Create and manage Quick Prompts. Quick Prompts are shortcuts to common actions.',
|
||||
}
|
||||
);
|
||||
export const ADD_QUICK_PROMPT_MODAL_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.quickPrompts.settings.modalTitle',
|
||||
{
|
||||
defaultMessage: 'Quick Prompts',
|
||||
}
|
||||
);
|
||||
|
||||
export const QUICK_PROMPT_NAME = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.quickPrompts.settings.nameLabel',
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { useQuickPromptEditor, DEFAULT_COLOR } from './use_quick_prompt_editor';
|
||||
import { useQuickPromptEditor } from './use_quick_prompt_editor';
|
||||
import { mockAlertPromptContext } from '../../../mock/prompt_context';
|
||||
import { MOCK_QUICK_PROMPTS } from '../../../mock/quick_prompt';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { useAssistantContext } from '../../../assistant_context';
|
||||
|
||||
const DEFAULT_COLOR = '#D36086';
|
||||
jest.mock('../../../assistant_context');
|
||||
// Mock functions for the tests
|
||||
const mockOnSelectedQuickPromptChange = jest.fn();
|
||||
|
@ -58,7 +58,7 @@ describe('useQuickPromptEditor', () => {
|
|||
);
|
||||
|
||||
act(() => {
|
||||
result.current.onQuickPromptSelectionChange(newPromptTitle);
|
||||
result.current.onQuickPromptSelectionChange(newPromptTitle, DEFAULT_COLOR);
|
||||
});
|
||||
|
||||
const newPrompt: PromptResponse = {
|
||||
|
@ -100,7 +100,7 @@ describe('useQuickPromptEditor', () => {
|
|||
};
|
||||
|
||||
act(() => {
|
||||
result.current.onQuickPromptSelectionChange(expectedPrompt);
|
||||
result.current.onQuickPromptSelectionChange(expectedPrompt, DEFAULT_COLOR);
|
||||
});
|
||||
|
||||
expect(mockOnSelectedQuickPromptChange).toHaveBeenCalledWith(expectedPrompt);
|
||||
|
|
|
@ -11,10 +11,9 @@ import {
|
|||
PerformPromptsBulkActionRequestBody as PromptsPerformBulkActionRequestBody,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/prompts/bulk_crud_prompts_route.gen';
|
||||
import { useCallback } from 'react';
|
||||
import { getRandomEuiColor } from './helpers';
|
||||
import { useAssistantContext } from '../../../..';
|
||||
|
||||
export const DEFAULT_COLOR = '#D36086';
|
||||
|
||||
export const useQuickPromptEditor = ({
|
||||
onSelectedQuickPromptChange,
|
||||
setUpdatedQuickPromptSettings,
|
||||
|
@ -42,14 +41,16 @@ export const useQuickPromptEditor = ({
|
|||
|
||||
// When top level quick prompt selection changes
|
||||
const onQuickPromptSelectionChange = useCallback(
|
||||
(quickPrompt?: PromptResponse | string) => {
|
||||
(quickPrompt: PromptResponse | string, color?: string) => {
|
||||
const isNew = typeof quickPrompt === 'string';
|
||||
const qpColor = color ? color : isNew ? getRandomEuiColor() : quickPrompt.color;
|
||||
|
||||
const newSelectedQuickPrompt: PromptResponse | undefined = isNew
|
||||
? {
|
||||
name: quickPrompt,
|
||||
id: quickPrompt,
|
||||
content: '',
|
||||
color: DEFAULT_COLOR,
|
||||
color: qpColor,
|
||||
categories: [],
|
||||
promptType: PromptTypeEnum.quick,
|
||||
consumer: currentAppId,
|
||||
|
|
|
@ -20,13 +20,6 @@ export const QUICK_PROMPTS_TABLE_COLUMN_DATE_UPDATED = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const QUICK_PROMPTS_TABLE_COLUMN_ACTIONS = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnActions',
|
||||
{
|
||||
defaultMessage: 'Actions',
|
||||
}
|
||||
);
|
||||
|
||||
export const QUICK_PROMPTS_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.quickPromptsTable.description',
|
||||
{
|
||||
|
|
|
@ -25,7 +25,7 @@ export const useQuickPromptTable = () => {
|
|||
}: {
|
||||
isActionsDisabled: boolean;
|
||||
basePromptContexts: PromptContextTemplate[];
|
||||
onEditActionClicked: (prompt: PromptResponse) => void;
|
||||
onEditActionClicked: (prompt: PromptResponse, color?: string) => void;
|
||||
onDeleteActionClicked: (prompt: PromptResponse) => void;
|
||||
}): Array<EuiBasicTableColumn<PromptResponse>> => [
|
||||
{
|
||||
|
|
|
@ -175,6 +175,7 @@ export const AssistantSettings: React.FC<Props> = React.memo(
|
|||
conversationSettings[defaultSelectedConversationId] == null;
|
||||
const newSelectedConversation: Conversation | undefined =
|
||||
Object.values(conversationSettings)[0];
|
||||
|
||||
if (isSelectedConversationDeleted && newSelectedConversation != null) {
|
||||
onConversationSelected({
|
||||
cId: newSelectedConversation.id,
|
||||
|
|
|
@ -25,7 +25,7 @@ const testProps = {
|
|||
onConversationSelected,
|
||||
conversations: {},
|
||||
conversationsLoaded: true,
|
||||
refetchConversationsState: jest.fn(),
|
||||
refetchCurrentUserConversations: jest.fn(),
|
||||
anonymizationFields: { total: 0, page: 1, perPage: 1000, data: [] },
|
||||
refetchAnonymizationFieldsResults: jest.fn(),
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import React, { useCallback } from 'react';
|
|||
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
|
||||
import { DataStreamApis } from '../use_data_stream_apis';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
import { Conversation } from '../../..';
|
||||
import { AssistantSettings } from './assistant_settings';
|
||||
|
@ -25,7 +26,7 @@ interface Props {
|
|||
isDisabled?: boolean;
|
||||
conversations: Record<string, Conversation>;
|
||||
conversationsLoaded: boolean;
|
||||
refetchConversationsState: () => Promise<void>;
|
||||
refetchCurrentUserConversations: DataStreamApis['refetchCurrentUserConversations'];
|
||||
refetchPrompts?: (
|
||||
options?: RefetchOptions & RefetchQueryFilters<unknown>
|
||||
) => Promise<QueryObserverResult<unknown, unknown>>;
|
||||
|
@ -44,7 +45,7 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo(
|
|||
onConversationSelected,
|
||||
conversations,
|
||||
conversationsLoaded,
|
||||
refetchConversationsState,
|
||||
refetchCurrentUserConversations,
|
||||
refetchPrompts,
|
||||
}) => {
|
||||
const { toasts, setSelectedSettingsTab } = useAssistantContext();
|
||||
|
@ -61,7 +62,7 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo(
|
|||
const handleSave = useCallback(
|
||||
async (success: boolean) => {
|
||||
cleanupAndCloseModal();
|
||||
await refetchConversationsState();
|
||||
await refetchCurrentUserConversations();
|
||||
if (refetchPrompts) {
|
||||
await refetchPrompts();
|
||||
}
|
||||
|
@ -72,7 +73,7 @@ export const AssistantSettingsButton: React.FC<Props> = React.memo(
|
|||
});
|
||||
}
|
||||
},
|
||||
[cleanupAndCloseModal, refetchConversationsState, refetchPrompts, toasts]
|
||||
[cleanupAndCloseModal, refetchCurrentUserConversations, refetchPrompts, toasts]
|
||||
);
|
||||
|
||||
const handleShowConversationSettings = useCallback(() => {
|
||||
|
|
|
@ -84,13 +84,6 @@ export const EVALUATION_MENU_ITEM = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const ADD_SYSTEM_PROMPT_MODAL_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.modalTitle',
|
||||
{
|
||||
defaultMessage: 'System Prompts',
|
||||
}
|
||||
);
|
||||
|
||||
export const CANCEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.slCancelButtonTitle',
|
||||
{
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { IToasts } from '@kbn/core/public';
|
||||
import { Conversation } from '../../..';
|
||||
import { SETTINGS_UPDATED_TOAST_TITLE } from './translations';
|
||||
|
||||
interface Props {
|
||||
conversationSettings: Record<string, Conversation>;
|
||||
defaultSelectedConversation: Conversation;
|
||||
setSelectedConversationId: React.Dispatch<React.SetStateAction<string>>;
|
||||
saveSettings: () => void;
|
||||
setHasPendingChanges: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
toasts: IToasts | undefined;
|
||||
}
|
||||
export const useHandleSave = ({
|
||||
conversationSettings,
|
||||
defaultSelectedConversation,
|
||||
setSelectedConversationId,
|
||||
saveSettings,
|
||||
setHasPendingChanges,
|
||||
toasts,
|
||||
}: Props) => {
|
||||
const handleSave = useCallback(() => {
|
||||
// If the selected conversation is deleted, we need to select a new conversation to prevent a crash creating a conversation that already exists
|
||||
const isSelectedConversationDeleted =
|
||||
conversationSettings[defaultSelectedConversation.title] == null;
|
||||
const newSelectedConversationId: string | undefined = Object.keys(conversationSettings)[0];
|
||||
if (isSelectedConversationDeleted && newSelectedConversationId != null) {
|
||||
setSelectedConversationId(conversationSettings[newSelectedConversationId].title);
|
||||
}
|
||||
saveSettings();
|
||||
toasts?.addSuccess({
|
||||
iconType: 'check',
|
||||
title: SETTINGS_UPDATED_TOAST_TITLE,
|
||||
});
|
||||
setHasPendingChanges(false);
|
||||
}, [
|
||||
conversationSettings,
|
||||
defaultSelectedConversation.title,
|
||||
saveSettings,
|
||||
setHasPendingChanges,
|
||||
setSelectedConversationId,
|
||||
toasts,
|
||||
]);
|
||||
|
||||
return handleSave;
|
||||
};
|
|
@ -7,10 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const CLEAR_CHAT = i18n.translate('xpack.elasticAssistant.assistant.clearChat', {
|
||||
defaultMessage: 'Clear chat',
|
||||
});
|
||||
|
||||
export const DEFAULT_ASSISTANT_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.defaultAssistantTitle',
|
||||
{
|
||||
|
@ -26,13 +22,6 @@ export const API_ERROR = i18n.translate('xpack.elasticAssistant.assistant.apiErr
|
|||
defaultMessage: 'An error occurred sending your message.',
|
||||
});
|
||||
|
||||
export const TOOLTIP_ARIA_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.documentationLinks.ariaLabel',
|
||||
{
|
||||
defaultMessage: 'Click to open Elastic Assistant documentation in a new tab',
|
||||
}
|
||||
);
|
||||
|
||||
export const DOCUMENTATION = i18n.translate(
|
||||
'xpack.elasticAssistant.documentationLinks.documentation',
|
||||
{
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { UpgradeLicenseCallToAction } from '.';
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
|
||||
const testProps = {
|
||||
connectorPrompt: <div>{'Connector Prompt'}</div>,
|
||||
http: { basePath: { get: jest.fn(() => 'http://localhost:5601') } } as unknown as HttpSetup,
|
||||
isAssistantEnabled: false,
|
||||
isWelcomeSetup: false,
|
||||
};
|
||||
|
||||
describe('UpgradeLicenseCallToAction', () => {
|
||||
it('UpgradeButtons is rendered ', () => {
|
||||
const { getByTestId, queryByTestId } = render(<UpgradeLicenseCallToAction {...testProps} />);
|
||||
expect(getByTestId('upgrade-buttons')).toBeInTheDocument();
|
||||
expect(queryByTestId('connector-prompt')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -13,26 +13,17 @@ import { ENTERPRISE } from '../../content/prompts/welcome/translations';
|
|||
import { UpgradeButtons } from '../../upgrade/upgrade_buttons';
|
||||
|
||||
interface OwnProps {
|
||||
connectorPrompt: React.ReactElement;
|
||||
http: HttpSetup;
|
||||
isAssistantEnabled: boolean;
|
||||
isWelcomeSetup: boolean;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
|
||||
/**
|
||||
* Provides a call-to-action for users to upgrade their subscription or set up a connector
|
||||
* depending on the isAssistantEnabled and isWelcomeSetup props.
|
||||
* Provides a call-to-action for users to upgrade their subscription
|
||||
*/
|
||||
export const BlockBotCallToAction: React.FC<Props> = ({
|
||||
connectorPrompt,
|
||||
http,
|
||||
isAssistantEnabled,
|
||||
isWelcomeSetup,
|
||||
}) => {
|
||||
export const UpgradeLicenseCallToAction: React.FC<Props> = ({ http }) => {
|
||||
const basePath = http.basePath.get();
|
||||
return !isAssistantEnabled ? (
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
direction="column"
|
||||
|
@ -54,13 +45,5 @@ export const BlockBotCallToAction: React.FC<Props> = ({
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{<UpgradeButtons basePath={basePath} />}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : isWelcomeSetup ? (
|
||||
<EuiFlexGroup
|
||||
css={css`
|
||||
width: 100%;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem data-test-subj="connector-prompt">{connectorPrompt}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : null;
|
||||
);
|
||||
};
|
|
@ -11,12 +11,6 @@ import { useCallback, useEffect, useMemo } from 'react';
|
|||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { getUniquePromptContextId } from '../../assistant_context/helpers';
|
||||
import type { PromptContext } from '../prompt_context/types';
|
||||
import { useConversation } from '../use_conversation';
|
||||
import { getDefaultConnector, mergeBaseWithPersistedConversations } from '../helpers';
|
||||
import { getGenAiConfig } from '../../connectorland/helpers';
|
||||
import { useLoadConnectors } from '../../connectorland/use_load_connectors';
|
||||
import { FetchConversationsResponse, useFetchCurrentUserConversations } from '../api';
|
||||
import { Conversation } from '../../assistant_context/types';
|
||||
|
||||
interface UseAssistantOverlay {
|
||||
showAssistantOverlay: (show: boolean, silent?: boolean) => void;
|
||||
|
@ -82,26 +76,6 @@ export const useAssistantOverlay = (
|
|||
*/
|
||||
replacements?: Replacements | null
|
||||
): UseAssistantOverlay => {
|
||||
const { http } = useAssistantContext();
|
||||
const { data: connectors } = useLoadConnectors({
|
||||
http,
|
||||
});
|
||||
const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]);
|
||||
const apiConfig = useMemo(() => getGenAiConfig(defaultConnector), [defaultConnector]);
|
||||
|
||||
const { createConversation } = useConversation();
|
||||
|
||||
const onFetchedConversations = useCallback(
|
||||
(conversationsData: FetchConversationsResponse): Record<string, Conversation> =>
|
||||
mergeBaseWithPersistedConversations({}, conversationsData),
|
||||
[]
|
||||
);
|
||||
const { data: conversations, isLoading } = useFetchCurrentUserConversations({
|
||||
http,
|
||||
onFetch: onFetchedConversations,
|
||||
isAssistantEnabled,
|
||||
});
|
||||
|
||||
// memoize the props so that we can use them in the effect below:
|
||||
const _category: PromptContext['category'] = useMemo(() => category, [category]);
|
||||
const _description: PromptContext['description'] = useMemo(() => description, [description]);
|
||||
|
@ -131,29 +105,6 @@ export const useAssistantOverlay = (
|
|||
// silent:boolean doesn't show the toast notification if the conversation is not found
|
||||
const showAssistantOverlay = useCallback(
|
||||
async (showOverlay: boolean) => {
|
||||
let conversation;
|
||||
if (!isLoading) {
|
||||
conversation = conversationTitle
|
||||
? Object.values(conversations).find((conv) => conv.title === conversationTitle)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
if (isAssistantEnabled && !conversation && defaultConnector && !isLoading) {
|
||||
try {
|
||||
conversation = await createConversation({
|
||||
apiConfig: {
|
||||
...apiConfig,
|
||||
actionTypeId: defaultConnector?.actionTypeId,
|
||||
connectorId: defaultConnector?.id,
|
||||
},
|
||||
category: 'assistant',
|
||||
title: conversationTitle ?? '',
|
||||
});
|
||||
} catch (e) {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
if (promptContextId != null) {
|
||||
assistantContextShowOverlay({
|
||||
showOverlay,
|
||||
|
@ -162,17 +113,7 @@ export const useAssistantOverlay = (
|
|||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
apiConfig,
|
||||
assistantContextShowOverlay,
|
||||
conversationTitle,
|
||||
conversations,
|
||||
createConversation,
|
||||
defaultConnector,
|
||||
isAssistantEnabled,
|
||||
isLoading,
|
||||
promptContextId,
|
||||
]
|
||||
[assistantContextShowOverlay, conversationTitle, promptContextId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -45,7 +45,7 @@ interface UpdateConversationTitleProps {
|
|||
updatedTitle: string;
|
||||
}
|
||||
|
||||
interface UseConversation {
|
||||
export interface UseConversation {
|
||||
clearConversation: (conversation: Conversation) => Promise<Conversation | undefined>;
|
||||
getDefaultConversation: ({ cTitle, messages }: CreateConversationProps) => Conversation;
|
||||
deleteConversation: (conversationId: string) => void;
|
||||
|
@ -135,6 +135,7 @@ export const useConversation = (): UseConversation => {
|
|||
title: cTitle,
|
||||
messages: messages != null ? messages : [],
|
||||
};
|
||||
|
||||
return newConversation;
|
||||
},
|
||||
[]
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Conversation, ClientMessage } from '../../assistant_context/types';
|
||||
import * as i18n from '../../content/prompts/welcome/translations';
|
||||
import { Conversation } from '../../assistant_context/types';
|
||||
import { WELCOME_CONVERSATION_TITLE } from './translations';
|
||||
|
||||
export const WELCOME_CONVERSATION: Conversation = {
|
||||
|
@ -17,15 +16,3 @@ export const WELCOME_CONVERSATION: Conversation = {
|
|||
replacements: {},
|
||||
excludeFromLastConversationStorage: true,
|
||||
};
|
||||
|
||||
export const enterpriseMessaging: ClientMessage[] = [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: i18n.ENTERPRISE,
|
||||
timestamp: '',
|
||||
presentation: {
|
||||
delay: 2 * 1000,
|
||||
stream: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -26,10 +26,3 @@ export const ELASTIC_AI_ASSISTANT_TITLE = i18n.translate(
|
|||
defaultMessage: 'Elastic AI Assistant',
|
||||
}
|
||||
);
|
||||
|
||||
export const ELASTIC_AI_ASSISTANT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.useConversation.elasticAiAssistantName',
|
||||
{
|
||||
defaultMessage: 'Assistant',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { useCurrentConversation, Props } from '.';
|
||||
import { useConversation } from '../use_conversation';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { Conversation } from '../../..';
|
||||
import { find } from 'lodash';
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('../use_conversation');
|
||||
jest.mock('../helpers');
|
||||
jest.mock('fast-deep-equal');
|
||||
jest.mock('lodash');
|
||||
const mockData = {
|
||||
welcome_id: {
|
||||
id: 'welcome_id',
|
||||
title: 'Welcome',
|
||||
category: 'assistant',
|
||||
messages: [],
|
||||
apiConfig: {
|
||||
connectorId: '123',
|
||||
actionTypeId: '.gen-ai',
|
||||
defaultSystemPromptId: 'system-prompt-id',
|
||||
},
|
||||
replacements: {},
|
||||
},
|
||||
electric_sheep_id: {
|
||||
id: 'electric_sheep_id',
|
||||
category: 'assistant',
|
||||
title: 'electric sheep',
|
||||
messages: [],
|
||||
apiConfig: { connectorId: '123', actionTypeId: '.gen-ai' },
|
||||
replacements: {},
|
||||
},
|
||||
};
|
||||
describe('useCurrentConversation', () => {
|
||||
const mockUseConversation = {
|
||||
createConversation: jest.fn(),
|
||||
deleteConversation: jest.fn(),
|
||||
getConversation: jest.fn(),
|
||||
getDefaultConversation: jest.fn(),
|
||||
setApiConfig: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useConversation as jest.Mock).mockReturnValue(mockUseConversation);
|
||||
(deepEqual as jest.Mock).mockReturnValue(false);
|
||||
(find as jest.Mock).mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
const defaultProps: Props = {
|
||||
// @ts-ignore not exact system prompt type, ok for test
|
||||
allSystemPrompts: [{ id: 'system-prompt-id' }, { id: 'something-crazy' }],
|
||||
conversationId: '',
|
||||
conversations: {},
|
||||
mayUpdateConversations: true,
|
||||
refetchCurrentUserConversations: jest.fn().mockResolvedValue({ data: mockData }),
|
||||
};
|
||||
|
||||
const setupHook = (props: Partial<Props> = {}) => {
|
||||
return renderHook(() => useCurrentConversation({ ...defaultProps, ...props }));
|
||||
};
|
||||
|
||||
it('should initialize with correct default values', () => {
|
||||
const { result } = setupHook();
|
||||
|
||||
expect(result.current.currentConversation).toBeUndefined();
|
||||
expect(result.current.currentSystemPromptId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should set the current system prompt ID when the prompt selection changes', () => {
|
||||
const { result } = setupHook();
|
||||
|
||||
act(() => {
|
||||
result.current.setCurrentSystemPromptId('prompt-id');
|
||||
});
|
||||
|
||||
expect(result.current.currentSystemPromptId).toBe('prompt-id');
|
||||
});
|
||||
|
||||
it('should fetch and set the current conversation', async () => {
|
||||
const conversationId = 'welcome_id';
|
||||
const conversation = mockData.welcome_id;
|
||||
mockUseConversation.getConversation.mockResolvedValue(conversation);
|
||||
|
||||
const { result } = setupHook({
|
||||
conversationId,
|
||||
conversations: { [conversationId]: conversation },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.refetchCurrentConversation({ cId: conversationId });
|
||||
});
|
||||
|
||||
expect(result.current.currentConversation).toEqual(conversation);
|
||||
});
|
||||
|
||||
it('should handle conversation selection', async () => {
|
||||
const conversationId = 'test-id';
|
||||
const conversationTitle = 'Test Conversation';
|
||||
const conversation = {
|
||||
...mockData.welcome_id,
|
||||
id: conversationId,
|
||||
title: conversationTitle,
|
||||
apiConfig: {
|
||||
...mockData.welcome_id.apiConfig,
|
||||
defaultSystemPromptId: 'something-crazy',
|
||||
},
|
||||
} as Conversation;
|
||||
const mockConversations = {
|
||||
...mockData,
|
||||
[conversationId]: conversation,
|
||||
};
|
||||
(find as jest.Mock).mockReturnValue(conversation);
|
||||
|
||||
const { result } = setupHook({
|
||||
conversationId: mockData.welcome_id.id,
|
||||
conversations: mockConversations,
|
||||
refetchCurrentUserConversations: jest.fn().mockResolvedValue({
|
||||
data: mockConversations,
|
||||
}),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleOnConversationSelected({
|
||||
cId: conversationId,
|
||||
cTitle: conversationTitle,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.currentConversation).toEqual(conversation);
|
||||
expect(result.current.currentSystemPromptId).toBe('something-crazy');
|
||||
});
|
||||
|
||||
it('should non-existing handle conversation selection', async () => {
|
||||
const conversationId = 'test-id';
|
||||
const conversationTitle = 'Test Conversation';
|
||||
const conversation = {
|
||||
...mockData.welcome_id,
|
||||
id: conversationId,
|
||||
title: conversationTitle,
|
||||
} as Conversation;
|
||||
const mockConversations = {
|
||||
...mockData,
|
||||
[conversationId]: conversation,
|
||||
};
|
||||
(find as jest.Mock).mockReturnValue(conversation);
|
||||
|
||||
const { result } = setupHook({
|
||||
conversationId: mockData.welcome_id.id,
|
||||
conversations: mockConversations,
|
||||
refetchCurrentUserConversations: jest.fn().mockResolvedValue({
|
||||
data: mockConversations,
|
||||
}),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleOnConversationSelected({
|
||||
cId: 'bad',
|
||||
cTitle: 'bad',
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.currentConversation).toEqual(mockData.welcome_id);
|
||||
expect(result.current.currentSystemPromptId).toBe('system-prompt-id');
|
||||
});
|
||||
|
||||
it('should create a new conversation', async () => {
|
||||
const newConversation = {
|
||||
...mockData.welcome_id,
|
||||
id: 'new-id',
|
||||
title: 'NEW_CHAT',
|
||||
messages: [],
|
||||
} as Conversation;
|
||||
mockUseConversation.createConversation.mockResolvedValue(newConversation);
|
||||
|
||||
const { result } = setupHook({
|
||||
conversations: {
|
||||
'old-id': {
|
||||
...mockData.welcome_id,
|
||||
id: 'old-id',
|
||||
title: 'Old Chat',
|
||||
messages: [],
|
||||
} as Conversation,
|
||||
},
|
||||
refetchCurrentUserConversations: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
'old-id': {
|
||||
...mockData.welcome_id,
|
||||
id: 'old-id',
|
||||
title: 'Old Chat',
|
||||
messages: [],
|
||||
} as Conversation,
|
||||
[newConversation.id]: newConversation,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleCreateConversation();
|
||||
});
|
||||
|
||||
expect(mockUseConversation.createConversation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should delete a conversation', async () => {
|
||||
const conversationTitle = 'Test Conversation';
|
||||
const conversation = {
|
||||
...mockData.welcome_id,
|
||||
id: 'test-id',
|
||||
title: conversationTitle,
|
||||
messages: [],
|
||||
} as Conversation;
|
||||
|
||||
const { result } = setupHook({
|
||||
conversations: { ...mockData, 'test-id': conversation },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleOnConversationDeleted('test-id');
|
||||
});
|
||||
|
||||
expect(mockUseConversation.deleteConversation).toHaveBeenCalledWith('test-id');
|
||||
expect(result.current.currentConversation).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should refetch the conversation multiple times if isStreamRefetch is true', async () => {
|
||||
const conversationId = 'test-id';
|
||||
const conversation = { id: conversationId, messages: [{ role: 'user' }] } as Conversation;
|
||||
mockUseConversation.getConversation.mockResolvedValue(conversation);
|
||||
|
||||
const { result } = setupHook({
|
||||
conversationId,
|
||||
conversations: { [conversationId]: conversation },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.refetchCurrentConversation({
|
||||
cId: conversationId,
|
||||
isStreamRefetch: true,
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockUseConversation.getConversation).toHaveBeenCalledTimes(6); // initial + 5 retries
|
||||
});
|
||||
});
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* 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 { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { QueryObserverResult } from '@tanstack/react-query';
|
||||
import { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
import { find } from 'lodash';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
import { getGenAiConfig } from '../../connectorland/helpers';
|
||||
import { NEW_CHAT } from '../conversations/conversation_sidepanel/translations';
|
||||
import { getDefaultSystemPrompt } from '../use_conversation/helpers';
|
||||
import { useConversation } from '../use_conversation';
|
||||
import { sleep } from '../helpers';
|
||||
import { Conversation, WELCOME_CONVERSATION_TITLE } from '../../..';
|
||||
|
||||
export interface Props {
|
||||
allSystemPrompts: PromptResponse[];
|
||||
conversationId: string;
|
||||
conversations: Record<string, Conversation>;
|
||||
defaultConnector?: AIConnector;
|
||||
mayUpdateConversations: boolean;
|
||||
refetchCurrentUserConversations: () => Promise<
|
||||
QueryObserverResult<Record<string, Conversation>, unknown>
|
||||
>;
|
||||
}
|
||||
|
||||
interface UseCurrentConversation {
|
||||
currentConversation: Conversation | undefined;
|
||||
currentSystemPromptId: string | undefined;
|
||||
handleCreateConversation: () => Promise<void>;
|
||||
handleOnConversationDeleted: (cTitle: string) => Promise<void>;
|
||||
handleOnConversationSelected: ({ cId, cTitle }: { cId: string; cTitle: string }) => Promise<void>;
|
||||
refetchCurrentConversation: (options?: {
|
||||
cId?: string;
|
||||
cTitle?: string;
|
||||
isStreamRefetch?: boolean;
|
||||
}) => Promise<Conversation | undefined>;
|
||||
setCurrentConversation: Dispatch<SetStateAction<Conversation | undefined>>;
|
||||
setCurrentSystemPromptId: Dispatch<SetStateAction<string | undefined>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the current conversation state. Interacts with the conversation API and keeps local state up to date.
|
||||
* Manages system prompt as that is per conversation
|
||||
* Provides methods to handle conversation selection, creation, deletion, and system prompt selection.
|
||||
*/
|
||||
export const useCurrentConversation = ({
|
||||
allSystemPrompts,
|
||||
conversationId,
|
||||
conversations,
|
||||
defaultConnector,
|
||||
mayUpdateConversations,
|
||||
refetchCurrentUserConversations,
|
||||
}: Props): UseCurrentConversation => {
|
||||
const {
|
||||
createConversation,
|
||||
deleteConversation,
|
||||
getConversation,
|
||||
getDefaultConversation,
|
||||
setApiConfig,
|
||||
} = useConversation();
|
||||
const [currentConversation, setCurrentConversation] = useState<Conversation | undefined>();
|
||||
const [currentConversationId, setCurrentConversationId] = useState<string | undefined>(
|
||||
conversationId
|
||||
);
|
||||
useEffect(() => {
|
||||
setCurrentConversationId(conversationId);
|
||||
}, [conversationId]);
|
||||
/**
|
||||
* START SYSTEM PROMPT
|
||||
*/
|
||||
const currentSystemPrompt = useMemo(
|
||||
() =>
|
||||
getDefaultSystemPrompt({
|
||||
allSystemPrompts,
|
||||
conversation: currentConversation,
|
||||
}),
|
||||
[allSystemPrompts, currentConversation]
|
||||
);
|
||||
|
||||
const [currentSystemPromptId, setCurrentSystemPromptId] = useState<string | undefined>(
|
||||
currentSystemPrompt?.id
|
||||
);
|
||||
useEffect(() => {
|
||||
setCurrentSystemPromptId(currentSystemPrompt?.id);
|
||||
}, [currentSystemPrompt?.id]);
|
||||
|
||||
/**
|
||||
* END SYSTEM PROMPT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Refetches the current conversation, optionally by conversation ID or title.
|
||||
* @param cId - The conversation ID to refetch.
|
||||
* @param cTitle - The conversation title to refetch.
|
||||
* @param isStreamRefetch - Are we refetching because stream completed? If so retry several times to ensure the message has updated on the server
|
||||
*/
|
||||
const refetchCurrentConversation = useCallback(
|
||||
async ({
|
||||
cId,
|
||||
cTitle,
|
||||
isStreamRefetch = false,
|
||||
}: { cId?: string; cTitle?: string; isStreamRefetch?: boolean } = {}) => {
|
||||
if (cId === '' || (cTitle && !conversations[cTitle])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cConversationId =
|
||||
cId ?? (cTitle && conversations[cTitle].id) ?? currentConversation?.id;
|
||||
|
||||
if (cConversationId) {
|
||||
let updatedConversation = await getConversation(cConversationId);
|
||||
let retries = 0;
|
||||
const maxRetries = 5;
|
||||
|
||||
// this retry is a workaround for the stream not YET being persisted to the stored conversation
|
||||
while (
|
||||
isStreamRefetch &&
|
||||
updatedConversation &&
|
||||
updatedConversation.messages[updatedConversation.messages.length - 1].role !==
|
||||
'assistant' &&
|
||||
retries < maxRetries
|
||||
) {
|
||||
retries++;
|
||||
await sleep(2000);
|
||||
updatedConversation = await getConversation(cConversationId);
|
||||
}
|
||||
|
||||
if (updatedConversation) {
|
||||
setCurrentConversation(updatedConversation);
|
||||
}
|
||||
|
||||
return updatedConversation;
|
||||
}
|
||||
},
|
||||
[conversations, currentConversation?.id, getConversation]
|
||||
);
|
||||
|
||||
const initializeDefaultConversationWithConnector = useCallback(
|
||||
async (defaultConvo: Conversation): Promise<Conversation> => {
|
||||
const apiConfig = getGenAiConfig(defaultConnector);
|
||||
const updatedConvo =
|
||||
(await setApiConfig({
|
||||
conversation: defaultConvo,
|
||||
apiConfig: {
|
||||
...defaultConvo?.apiConfig,
|
||||
connectorId: (defaultConnector?.id as string) ?? '',
|
||||
actionTypeId: (defaultConnector?.actionTypeId as string) ?? '.gen-ai',
|
||||
provider: apiConfig?.apiProvider,
|
||||
model: apiConfig?.defaultModel,
|
||||
defaultSystemPromptId: allSystemPrompts.find((sp) => sp.isNewConversationDefault)?.id,
|
||||
},
|
||||
})) ?? defaultConvo;
|
||||
await refetchCurrentUserConversations();
|
||||
return updatedConvo;
|
||||
},
|
||||
[allSystemPrompts, defaultConnector, refetchCurrentUserConversations, setApiConfig]
|
||||
);
|
||||
|
||||
const handleOnConversationSelected = useCallback(
|
||||
async ({ cId, cTitle }: { cId: string; cTitle: string }) => {
|
||||
const allConversations = await refetchCurrentUserConversations();
|
||||
|
||||
// This is a default conversation that has not yet been initialized
|
||||
// add the default connector config
|
||||
if (cId === '' && allConversations?.data?.[cTitle]) {
|
||||
const updatedConvo = await initializeDefaultConversationWithConnector(
|
||||
allConversations.data[cTitle]
|
||||
);
|
||||
setCurrentConversationId(updatedConvo.id);
|
||||
} else if (allConversations?.data?.[cId]) {
|
||||
setCurrentConversationId(cId);
|
||||
}
|
||||
},
|
||||
[
|
||||
initializeDefaultConversationWithConnector,
|
||||
refetchCurrentUserConversations,
|
||||
setCurrentConversationId,
|
||||
]
|
||||
);
|
||||
|
||||
// update currentConversation when conversations or currentConversationId update
|
||||
useEffect(() => {
|
||||
if (!mayUpdateConversations) return;
|
||||
const updateConversation = async () => {
|
||||
const nextConversation: Conversation =
|
||||
(currentConversationId && conversations[currentConversationId]) ||
|
||||
// if currentConversationId is not an id, it should be a title from a
|
||||
// default conversation that has not yet been initialized
|
||||
find(conversations, ['title', currentConversationId]) ||
|
||||
find(conversations, ['title', WELCOME_CONVERSATION_TITLE]) ||
|
||||
// if no Welcome convo exists, create one
|
||||
getDefaultConversation({ cTitle: WELCOME_CONVERSATION_TITLE });
|
||||
|
||||
if (nextConversation && nextConversation.id === '') {
|
||||
// This is a default conversation that has not yet been initialized
|
||||
const conversation = await initializeDefaultConversationWithConnector(nextConversation);
|
||||
return setCurrentConversation(conversation);
|
||||
}
|
||||
setCurrentConversation((prev) => {
|
||||
if (deepEqual(prev, nextConversation)) return prev;
|
||||
|
||||
if (
|
||||
prev &&
|
||||
prev.id === nextConversation.id &&
|
||||
// if the conversation id has not changed and the previous conversation has more messages
|
||||
// it is because the local conversation has a readable stream running
|
||||
// and it has not yet been persisted to the stored conversation
|
||||
prev.messages.length > nextConversation.messages.length
|
||||
) {
|
||||
return {
|
||||
...nextConversation,
|
||||
messages: prev.messages,
|
||||
};
|
||||
}
|
||||
|
||||
return nextConversation;
|
||||
});
|
||||
};
|
||||
updateConversation();
|
||||
}, [
|
||||
currentConversationId,
|
||||
conversations,
|
||||
getDefaultConversation,
|
||||
initializeDefaultConversationWithConnector,
|
||||
mayUpdateConversations,
|
||||
]);
|
||||
|
||||
const handleOnConversationDeleted = useCallback(
|
||||
async (cTitle: string) => {
|
||||
await deleteConversation(conversations[cTitle].id);
|
||||
await refetchCurrentUserConversations();
|
||||
},
|
||||
[conversations, deleteConversation, refetchCurrentUserConversations]
|
||||
);
|
||||
|
||||
const handleCreateConversation = useCallback(async () => {
|
||||
const newChatExists = find(conversations, ['title', NEW_CHAT]);
|
||||
if (newChatExists && !newChatExists.messages.length) {
|
||||
handleOnConversationSelected({
|
||||
cId: newChatExists.id,
|
||||
cTitle: newChatExists.title,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newConversation = await createConversation({
|
||||
title: NEW_CHAT,
|
||||
apiConfig: currentConversation?.apiConfig,
|
||||
});
|
||||
|
||||
if (newConversation) {
|
||||
handleOnConversationSelected({
|
||||
cId: newConversation.id,
|
||||
cTitle: newConversation.title,
|
||||
});
|
||||
} else {
|
||||
await refetchCurrentUserConversations();
|
||||
}
|
||||
}, [
|
||||
conversations,
|
||||
createConversation,
|
||||
currentConversation?.apiConfig,
|
||||
handleOnConversationSelected,
|
||||
refetchCurrentUserConversations,
|
||||
]);
|
||||
|
||||
return {
|
||||
currentConversation,
|
||||
currentSystemPromptId,
|
||||
handleCreateConversation,
|
||||
handleOnConversationDeleted,
|
||||
handleOnConversationSelected,
|
||||
refetchCurrentConversation,
|
||||
setCurrentConversation,
|
||||
setCurrentSystemPromptId,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import type { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { PromptResponse, PromptTypeEnum } from '@kbn/elastic-assistant-common';
|
||||
import type { FindAnonymizationFieldsResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.gen';
|
||||
import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
|
||||
import { useFetchAnonymizationFields } from './api/anonymization_fields/use_fetch_anonymization_fields';
|
||||
import { FetchConversationsResponse, useFetchPrompts } from './api';
|
||||
import {
|
||||
Conversation,
|
||||
mergeBaseWithPersistedConversations,
|
||||
useFetchCurrentUserConversations,
|
||||
} from '../..';
|
||||
|
||||
interface Props {
|
||||
baseConversations: Record<string, Conversation>;
|
||||
http: HttpSetup;
|
||||
isAssistantEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface DataStreamApis {
|
||||
allPrompts: PromptResponse[];
|
||||
allSystemPrompts: PromptResponse[];
|
||||
anonymizationFields: FindAnonymizationFieldsResponse;
|
||||
conversations: Record<string, Conversation>;
|
||||
isErrorAnonymizationFields: boolean;
|
||||
isFetchedAnonymizationFields: boolean;
|
||||
isFetchedCurrentUserConversations: boolean;
|
||||
isLoadingAnonymizationFields: boolean;
|
||||
isLoadingCurrentUserConversations: boolean;
|
||||
isLoadingPrompts: boolean;
|
||||
isFetchedPrompts: boolean;
|
||||
refetchPrompts: (
|
||||
options?: RefetchOptions & RefetchQueryFilters<unknown>
|
||||
) => Promise<QueryObserverResult<unknown, unknown>>;
|
||||
refetchCurrentUserConversations: () => Promise<
|
||||
QueryObserverResult<Record<string, Conversation>, unknown>
|
||||
>;
|
||||
setIsStreaming: (isStreaming: boolean) => void;
|
||||
}
|
||||
|
||||
export const useDataStreamApis = ({
|
||||
http,
|
||||
baseConversations,
|
||||
isAssistantEnabled,
|
||||
}: Props): DataStreamApis => {
|
||||
const [isStreaming, setIsStreaming] = useState(false);
|
||||
const onFetchedConversations = useCallback(
|
||||
(conversationsData: FetchConversationsResponse): Record<string, Conversation> =>
|
||||
mergeBaseWithPersistedConversations(baseConversations, conversationsData),
|
||||
[baseConversations]
|
||||
);
|
||||
const {
|
||||
data: conversations,
|
||||
isLoading: isLoadingCurrentUserConversations,
|
||||
refetch: refetchCurrentUserConversations,
|
||||
isFetched: isFetchedCurrentUserConversations,
|
||||
} = useFetchCurrentUserConversations({
|
||||
http,
|
||||
onFetch: onFetchedConversations,
|
||||
refetchOnWindowFocus: !isStreaming,
|
||||
isAssistantEnabled,
|
||||
});
|
||||
|
||||
const {
|
||||
data: anonymizationFields,
|
||||
isLoading: isLoadingAnonymizationFields,
|
||||
isError: isErrorAnonymizationFields,
|
||||
isFetched: isFetchedAnonymizationFields,
|
||||
} = useFetchAnonymizationFields();
|
||||
|
||||
const {
|
||||
data: { data: allPrompts },
|
||||
refetch: refetchPrompts,
|
||||
isLoading: isLoadingPrompts,
|
||||
isFetched: isFetchedPrompts,
|
||||
} = useFetchPrompts();
|
||||
const allSystemPrompts = useMemo(() => {
|
||||
if (!isLoadingPrompts) {
|
||||
return allPrompts.filter((p) => p.promptType === PromptTypeEnum.system);
|
||||
}
|
||||
return [];
|
||||
}, [allPrompts, isLoadingPrompts]);
|
||||
return {
|
||||
allPrompts,
|
||||
allSystemPrompts,
|
||||
anonymizationFields,
|
||||
conversations,
|
||||
isErrorAnonymizationFields,
|
||||
isFetchedAnonymizationFields,
|
||||
isFetchedCurrentUserConversations,
|
||||
isLoadingAnonymizationFields,
|
||||
isLoadingCurrentUserConversations,
|
||||
isLoadingPrompts,
|
||||
isFetchedPrompts,
|
||||
refetchPrompts,
|
||||
refetchCurrentUserConversations,
|
||||
setIsStreaming,
|
||||
};
|
||||
};
|
|
@ -9,8 +9,6 @@ import { KnowledgeBaseConfig } from '../assistant/types';
|
|||
|
||||
export const ATTACK_DISCOVERY_STORAGE_KEY = 'attackDiscovery';
|
||||
export const DEFAULT_ASSISTANT_NAMESPACE = 'elasticAssistantDefault';
|
||||
export const QUICK_PROMPT_LOCAL_STORAGE_KEY = 'quickPrompts';
|
||||
export const SYSTEM_PROMPT_LOCAL_STORAGE_KEY = 'systemPrompts';
|
||||
export const LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY = 'lastConversationId';
|
||||
export const KNOWLEDGE_BASE_LOCAL_STORAGE_KEY = 'knowledgeBase';
|
||||
export const STREAMING_LOCAL_STORAGE_KEY = 'streaming';
|
||||
|
|
|
@ -19,23 +19,6 @@ export interface ClientMessage extends Omit<Message, 'content' | 'reader'> {
|
|||
content?: string;
|
||||
presentation?: MessagePresentation;
|
||||
}
|
||||
|
||||
export interface ConversationTheme {
|
||||
title?: string;
|
||||
titleIcon?: string;
|
||||
user?: {
|
||||
name?: string;
|
||||
icon?: string;
|
||||
};
|
||||
assistant?: {
|
||||
name?: string;
|
||||
icon?: string;
|
||||
};
|
||||
system?: {
|
||||
name?: string;
|
||||
icon?: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Complete state to reconstruct a conversation instance.
|
||||
* Includes all messages, connector configured, and relevant UI state.
|
||||
|
|
|
@ -78,6 +78,7 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
<EuiButtonEmpty
|
||||
data-test-subj="addNewConnectorButton"
|
||||
href="#"
|
||||
isDisabled={localIsDisabled}
|
||||
iconType="plus"
|
||||
size="xs"
|
||||
>
|
||||
|
@ -91,7 +92,7 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
</EuiFlexGroup>
|
||||
),
|
||||
};
|
||||
}, []);
|
||||
}, [localIsDisabled]);
|
||||
|
||||
const connectorOptions = useMemo(
|
||||
() =>
|
||||
|
@ -188,6 +189,7 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
<EuiButtonEmpty
|
||||
data-test-subj="addNewConnectorButton"
|
||||
iconType="plusInCircle"
|
||||
isDisabled={localIsDisabled}
|
||||
size="xs"
|
||||
onClick={() => setIsConnectorModalVisible(true)}
|
||||
>
|
||||
|
|
|
@ -29,7 +29,7 @@ interface Props {
|
|||
actionTypeSelectorInline: boolean;
|
||||
}
|
||||
const itemClassName = css`
|
||||
inline-size: 240px;
|
||||
inline-size: 220px;
|
||||
|
||||
.euiKeyPadMenuItem__label {
|
||||
white-space: nowrap;
|
||||
|
|
|
@ -84,34 +84,6 @@ export const ADD_CONNECTOR_MISSING_PRIVILEGES_DESCRIPTION = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const CONNECTOR_SETUP_USER_YOU = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.setup.userYouTitle',
|
||||
{
|
||||
defaultMessage: 'You',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONNECTOR_SETUP_USER_ASSISTANT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.setup.userAssistantTitle',
|
||||
{
|
||||
defaultMessage: 'Assistant',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONNECTOR_SETUP_TIMESTAMP_AT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.setup.timestampAtTitle',
|
||||
{
|
||||
defaultMessage: 'at',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONNECTOR_SETUP_SKIP = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.setup.skipTitle',
|
||||
{
|
||||
defaultMessage: 'Click to skip...',
|
||||
}
|
||||
);
|
||||
|
||||
export const MISSING_CONNECTOR_CALLOUT_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.connectorMissingCallout.calloutTitle',
|
||||
{
|
||||
|
@ -125,17 +97,3 @@ export const MISSING_CONNECTOR_CONVERSATION_SETTINGS_LINK = i18n.translate(
|
|||
defaultMessage: 'Conversation Settings',
|
||||
}
|
||||
);
|
||||
|
||||
export const CREATE_CONNECTOR_BUTTON = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.createConnectorButton',
|
||||
{
|
||||
defaultMessage: 'Connector',
|
||||
}
|
||||
);
|
||||
|
||||
export const REFRESH_CONNECTORS_BUTTON = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.refreshConnectorsButton',
|
||||
{
|
||||
defaultMessage: 'Refresh',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import type { UseQueryResult } from '@tanstack/react-query';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { UserProfile } from '@kbn/security-plugin/common';
|
||||
import type { ServerError } from '@kbn/cases-plugin/public/types';
|
||||
import { loadActionTypes } from '@kbn/triggers-actions-ui-plugin/public/common/constants';
|
||||
import type { IHttpFetchError } from '@kbn/core-http-browser';
|
||||
|
@ -62,5 +61,3 @@ export const useLoadActionTypes = ({
|
|||
}
|
||||
);
|
||||
};
|
||||
|
||||
export type UseSuggestUserProfiles = UseQueryResult<UserProfile[], ServerError>;
|
||||
|
|
|
@ -7,31 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const YOU_ARE_A_HELPFUL_EXPERT_ASSISTANT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.content.prompts.system.youAreAHelpfulExpertAssistant',
|
||||
{
|
||||
defaultMessage:
|
||||
'You are a helpful, expert assistant who answers questions about Elastic Security.',
|
||||
}
|
||||
);
|
||||
|
||||
export const IF_YOU_DONT_KNOW_THE_ANSWER = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.content.prompts.system.ifYouDontKnowTheAnswer',
|
||||
{
|
||||
defaultMessage: 'Do not answer questions unrelated to Elastic Security.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SUPERHERO_PERSONALITY = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.content.prompts.system.superheroPersonality',
|
||||
{
|
||||
defaultMessage:
|
||||
'Provide the most detailed and relevant answer possible, as if you were relaying this information back to a cyber security expert.',
|
||||
}
|
||||
);
|
||||
|
||||
export const DEFAULT_SYSTEM_PROMPT_NON_I18N = `${YOU_ARE_A_HELPFUL_EXPERT_ASSISTANT} ${IF_YOU_DONT_KNOW_THE_ANSWER}`;
|
||||
|
||||
export const DEFAULT_SYSTEM_PROMPT_NAME = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.content.prompts.system.defaultSystemPromptName',
|
||||
{
|
||||
|
@ -39,30 +14,6 @@ export const DEFAULT_SYSTEM_PROMPT_NAME = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const DEFAULT_SYSTEM_PROMPT_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.content.prompts.system.defaultSystemPromptLabel',
|
||||
{
|
||||
defaultMessage: 'Default',
|
||||
}
|
||||
);
|
||||
|
||||
export const SUPERHERO_SYSTEM_PROMPT_NON_I18N = `${YOU_ARE_A_HELPFUL_EXPERT_ASSISTANT} ${IF_YOU_DONT_KNOW_THE_ANSWER}
|
||||
${SUPERHERO_PERSONALITY}`;
|
||||
|
||||
export const SUPERHERO_SYSTEM_PROMPT_NAME = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptName',
|
||||
{
|
||||
defaultMessage: 'Enhanced system prompt',
|
||||
}
|
||||
);
|
||||
|
||||
export const SUPERHERO_SYSTEM_PROMPT_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptLabel',
|
||||
{
|
||||
defaultMessage: 'Enhanced',
|
||||
}
|
||||
);
|
||||
|
||||
export const SYSTEM_PROMPT_CONTEXT_NON_I18N = (context: string) => {
|
||||
return `CONTEXT:\n"""\n${context}\n"""`;
|
||||
};
|
||||
|
|
|
@ -7,27 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const CALLOUT_PARAGRAPH1 = i18n.translate(
|
||||
'xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutParagraph1',
|
||||
{
|
||||
defaultMessage: 'The fields below are allowed by default',
|
||||
}
|
||||
);
|
||||
|
||||
export const CALLOUT_PARAGRAPH2 = i18n.translate(
|
||||
'xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutParagraph2',
|
||||
{
|
||||
defaultMessage: 'Optionally enable anonymization for these fields',
|
||||
}
|
||||
);
|
||||
|
||||
export const CALLOUT_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutTitle',
|
||||
{
|
||||
defaultMessage: 'Anonymization defaults',
|
||||
}
|
||||
);
|
||||
|
||||
export const SETTINGS_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.settingsTitle',
|
||||
{
|
||||
|
|
|
@ -21,13 +21,6 @@ export const ALLOW = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const ALLOW_BY_DEFAULT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowByDefaultAction',
|
||||
{
|
||||
defaultMessage: 'Allow by default',
|
||||
}
|
||||
);
|
||||
|
||||
export const ALLOWED = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowedColumnTitle',
|
||||
{
|
||||
|
@ -48,14 +41,6 @@ export const ANONYMIZE = i18n.translate(
|
|||
defaultMessage: 'Anonymize',
|
||||
}
|
||||
);
|
||||
|
||||
export const ANONYMIZE_BY_DEFAULT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizeByDefaultAction',
|
||||
{
|
||||
defaultMessage: 'Anonymize by default',
|
||||
}
|
||||
);
|
||||
|
||||
export const ANONYMIZED = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizedColumnTitle',
|
||||
{
|
||||
|
@ -83,14 +68,6 @@ export const DENY = i18n.translate(
|
|||
defaultMessage: 'Deny',
|
||||
}
|
||||
);
|
||||
|
||||
export const DENY_BY_DEFAULT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.denyByDefaultAction',
|
||||
{
|
||||
defaultMessage: 'Deny by default',
|
||||
}
|
||||
);
|
||||
|
||||
export const FIELD = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.fieldColumnTitle',
|
||||
{
|
||||
|
@ -130,13 +107,6 @@ export const UNANONYMIZE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const UNANONYMIZE_BY_DEFAULT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.unanonymizeByDefaultAction',
|
||||
{
|
||||
defaultMessage: 'Unanonymize by default',
|
||||
}
|
||||
);
|
||||
|
||||
export const VALUES = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.valuesColumnTitle',
|
||||
{
|
||||
|
|
|
@ -6,4 +6,3 @@
|
|||
*/
|
||||
|
||||
export const TITLE_SIZE = 'xs';
|
||||
export const STAT_TITLE_SIZE = 'm';
|
||||
|
|
|
@ -13,14 +13,6 @@ export const ALERTS_LABEL = i18n.translate(
|
|||
defaultMessage: 'Alerts',
|
||||
}
|
||||
);
|
||||
|
||||
export const ASK_QUESTIONS_ABOUT = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.askQuestionsAboutLabel',
|
||||
{
|
||||
defaultMessage: 'Ask questions about the',
|
||||
}
|
||||
);
|
||||
|
||||
export const LATEST_AND_RISKIEST_OPEN_ALERTS = (alertsCount: number) =>
|
||||
i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.latestAndRiskiestOpenAlertsLabel',
|
||||
|
@ -93,14 +85,6 @@ export const SETUP_KNOWLEDGE_BASE_BUTTON_TOOLTIP = i18n.translate(
|
|||
defaultMessage: 'Knowledge Base unavailable, please see documentation for more details.',
|
||||
}
|
||||
);
|
||||
|
||||
export const KNOWLEDGE_BASE_TOOLTIP = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseTooltip',
|
||||
{
|
||||
defaultMessage: 'ELSER must be configured to enable the Knowledge Base',
|
||||
}
|
||||
);
|
||||
|
||||
export const KNOWLEDGE_BASE_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseDescription',
|
||||
{
|
||||
|
@ -117,20 +101,6 @@ export const KNOWLEDGE_BASE_DESCRIPTION_INSTALLED = (kbIndexPattern: string) =>
|
|||
}
|
||||
);
|
||||
|
||||
export const KNOWLEDGE_BASE_INIT_BUTTON = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.initializeKnowledgeBaseButton',
|
||||
{
|
||||
defaultMessage: 'Initialize',
|
||||
}
|
||||
);
|
||||
|
||||
export const KNOWLEDGE_BASE_DELETE_BUTTON = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.deleteKnowledgeBaseButton',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
);
|
||||
|
||||
export const KNOWLEDGE_BASE_ELSER_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserLabel',
|
||||
{
|
||||
|
@ -138,20 +108,6 @@ export const KNOWLEDGE_BASE_ELSER_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const KNOWLEDGE_BASE_ELSER_MACHINE_LEARNING = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserMachineLearningDescription',
|
||||
{
|
||||
defaultMessage: 'Machine Learning',
|
||||
}
|
||||
);
|
||||
|
||||
export const KNOWLEDGE_BASE_ELSER_SEE_DOCS = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserSeeDocsDescription',
|
||||
{
|
||||
defaultMessage: 'See docs',
|
||||
}
|
||||
);
|
||||
|
||||
export const ESQL_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlLabel',
|
||||
{
|
||||
|
|
|
@ -1,16 +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 { PromptResponse } from '@kbn/elastic-assistant-common';
|
||||
|
||||
export const mockUserPrompt: PromptResponse = {
|
||||
id: 'mock-user-prompt-1',
|
||||
content: `Explain the meaning from the context above, then summarize a list of suggested Elasticsearch KQL and EQL queries.
|
||||
Finally, suggest an investigation guide, and format it as markdown.`,
|
||||
name: 'Mock user prompt',
|
||||
promptType: 'quick',
|
||||
};
|
|
@ -22,7 +22,6 @@
|
|||
"@kbn/stack-connectors-plugin",
|
||||
"@kbn/triggers-actions-ui-plugin",
|
||||
"@kbn/core-http-browser-mocks",
|
||||
"@kbn/security-plugin",
|
||||
"@kbn/cases-plugin",
|
||||
"@kbn/actions-plugin",
|
||||
"@kbn/core-notifications-browser",
|
||||
|
|
|
@ -15084,7 +15084,6 @@
|
|||
"xpack.ecsDataQualityDashboard.getIndexStats.dateRangeRequiredErrorMessage": "\"startDate\" et \"endDate\" sont requis",
|
||||
"xpack.elasticAssistant.anonymizationFields.bulkActionsAnonymizationFieldsError": "Erreur lors de la mise à jour des champs d'anonymisation {error}",
|
||||
"xpack.elasticAssistant.assistant.apiErrorTitle": "Une erreur s’est produite lors de l’envoi de votre message.",
|
||||
"xpack.elasticAssistant.assistant.clearChat": "Effacer le chat",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.description": "Configurez un connecteur pour continuer la conversation",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.missingPrivilegesDescription": "Veuillez contacter votre administrateur pour activer le connecteur d'intelligence artificielle générative.",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.missingPrivilegesTitle": "Connecteur d'intelligence artificielle générative requis",
|
||||
|
@ -15096,20 +15095,8 @@
|
|||
"xpack.elasticAssistant.assistant.connectors.connectorSelector.ariaLabel": "Sélecteur de connecteur",
|
||||
"xpack.elasticAssistant.assistant.connectors.connectorSelector.newConnectorOptions": "Ajouter un nouveau connecteur...",
|
||||
"xpack.elasticAssistant.assistant.connectors.connectorSelectorInline.connectorPlaceholder": "Sélectionner un connecteur",
|
||||
"xpack.elasticAssistant.assistant.connectors.createConnectorButton": "Connecteur",
|
||||
"xpack.elasticAssistant.assistant.connectors.preconfiguredTitle": "Préconfiguré",
|
||||
"xpack.elasticAssistant.assistant.connectors.refreshConnectorsButton": "Actualiser",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.skipTitle": "Cliquez pour ignorer...",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.timestampAtTitle": "à",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.userAssistantTitle": "Assistant",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.userYouTitle": "Vous",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.defaultSystemPromptLabel": "Par défaut",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.defaultSystemPromptName": "Invite de système par défaut",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.ifYouDontKnowTheAnswer": "Ne répondez pas aux questions qui ne sont pas liées à Elastic Security.",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroPersonality": "Donnez la réponse la plus pertinente et détaillée possible, comme si vous deviez communiquer ces informations à un expert en cybersécurité.",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptLabel": "Amélioré",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptName": "Invite système améliorée",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.youAreAHelpfulExpertAssistant": "Vous êtes un assistant expert et serviable qui répond à des questions au sujet d’Elastic Security.",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.connectorTitle": "Connecteur",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptHelpTextTitle": "Contexte fourni dans le cadre de chaque conversation.",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptTitle": "Invite système",
|
||||
|
@ -15127,7 +15114,6 @@
|
|||
"xpack.elasticAssistant.assistant.conversationSelector.nextConversationTitle": "Conversation suivante",
|
||||
"xpack.elasticAssistant.assistant.conversationSelector.placeholderTitle": "Sélectionnez ou saisissez pour créer une nouvelle...",
|
||||
"xpack.elasticAssistant.assistant.conversationSelector.previousConversationTitle": "Conversation précédente",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.actions": "Actions",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.connector": "Connecteur",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.systemPrompt": "Invite système",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.Title": "Titre",
|
||||
|
@ -15140,21 +15126,17 @@
|
|||
"xpack.elasticAssistant.assistant.conversationSettings.title": "Paramètres",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allActionsTooltip": "Toutes les actions",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowAction": "Autoriser",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowByDefaultAction": "Permettre par défaut",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowedColumnTitle": "Permis",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.alwaysSubmenu": "Toujours",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizeAction": "Anonymiser",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizeByDefaultAction": "Anonymiser par défaut",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizedColumnTitle": "Anonymisé",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.bulkActions": "Actions groupées",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.defaultsSubmenu": "Par défaut",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.denyAction": "Refuser",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.denyByDefaultAction": "Refuser par défaut",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.fieldColumnTitle": "Champ",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.noButtonLabel": "Non",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.resetButton": "Réinitialiser",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.unanonymizeAction": "Désanonymiser",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.unanonymizeByDefaultAction": "Désanonymiser par défaut",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.valuesColumnTitle": "Valeurs",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.yesButtonLabel": "Oui",
|
||||
"xpack.elasticAssistant.assistant.defaultAssistantTitle": "Assistant d’intelligence artificielle d’Elastic",
|
||||
|
@ -15165,13 +15147,9 @@
|
|||
"xpack.elasticAssistant.assistant.emptyScreen.description": "Demandez-moi tout ce que vous voulez, de \"Résumez cette alerte\" à \"Aidez-moi à construire une requête\" en utilisant l'invite suivante du système :",
|
||||
"xpack.elasticAssistant.assistant.emptyScreen.title": "Comment puis-je vous aider ?",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.addNewSystemPrompt": "Ajouter une nouvelle invite système...",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.addSystemPromptTooltip": "Ajouter une invite système",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.clearSystemPrompt": "Effacer une invite système",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.commentsListAriaLabel": "Liste de commentaires",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.editingPromptLabel": "modification d’invite",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.emptyPrompt": "(invite vide)",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.selectASystemPromptPlaceholder": "Sélectionner une invite système",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.youLabel": "Vous",
|
||||
"xpack.elasticAssistant.assistant.newChat.newChatButton": "Chat",
|
||||
"xpack.elasticAssistant.assistant.newChatByTitle.newChatByTitleButton": "Chat",
|
||||
"xpack.elasticAssistant.assistant.overlay.CancelButton": "Annuler",
|
||||
|
@ -15183,7 +15161,6 @@
|
|||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultConversationsHelpText": "Les conversations devant utiliser cette invite système par défaut.",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultConversationsLabel": "Conversations par défaut",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultNewConversationTitle": "Utiliser par défaut pour toutes les nouvelles conversations",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.modalTitle": "Invites système",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.nameLabel": "Nom",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.promptLabel": "Invite",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.promptPlaceholder": "Saisir une invite système",
|
||||
|
@ -15197,7 +15174,6 @@
|
|||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.systemPromptModal.systemPromptSelector.deletePromptTitle": "Supprimer une invite système",
|
||||
"xpack.elasticAssistant.assistant.promptPlaceholder": "Demandez-moi tout ce que vous voulez, de \"résume cette alerte\" à \"aide-moi à créer une recherche...\"",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.settingsDescription": "Créer et gérer des invites système. Les invites système sont des morceaux de contexte configurables systématiquement envoyés pour une conversation donnée.",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnActions": "Actions",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnDateUpdated": "Date de mise à jour",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnDefaultConversations": "Conversations par défaut",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnName": "Nom",
|
||||
|
@ -15215,7 +15191,6 @@
|
|||
"xpack.elasticAssistant.assistant.quickPrompts.settings.badgeColorLabel": "Couleur de badge",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.contextsHelpText": "Sélectionnez l'emplacement où cette invite rapide apparaîtra. Cette invite apparaîtra partout si vous n’en sélectionnez aucun.",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.contextsLabel": "Contextes",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.modalTitle": "Invites rapides",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.nameLabel": "Nom",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.promptLabel": "Invite",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.promptPlaceholder": "Saisir une invite rapide",
|
||||
|
@ -15226,7 +15201,6 @@
|
|||
"xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationDefaultTitle": "Supprimer l’invite rapide ?",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationMessage": "Vous ne pouvez pas récupérer une invite supprimée",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationTitle": "Supprimer \"{prompt}\" ?",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnActions": "Actions",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnContexts": "Contextes",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnDateUpdated": "Date de mise à jour",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnName": "Nom",
|
||||
|
@ -15234,9 +15208,7 @@
|
|||
"xpack.elasticAssistant.assistant.resetConversationModal.clearChatConfirmation": "Êtes-vous sûr de vouloir effacer le chat actuel ? Toutes les données de conversation seront perdues.",
|
||||
"xpack.elasticAssistant.assistant.resetConversationModal.resetButtonText": "Réinitialiser",
|
||||
"xpack.elasticAssistant.assistant.settings.actionsButtonTitle": "Actions",
|
||||
"xpack.elasticAssistant.assistant.settings.anonymizedValues": "Valeurs anonymisées",
|
||||
"xpack.elasticAssistant.assistant.settings.connectorHelpTextTitle": "Le connecteur LLM par défaut pour ce type de conversation.",
|
||||
"xpack.elasticAssistant.assistant.settings.connectorTitle": "Connecteur",
|
||||
"xpack.elasticAssistant.assistant.settings.deleteButtonTitle": "Supprimer",
|
||||
"xpack.elasticAssistant.assistant.settings.editButtonTitle": "Modifier",
|
||||
"xpack.elasticAssistant.assistant.settings.evaluationSettings.apmUrlDescription": "URL pour l'application APM de Kibana. Utilisé pour établir un lien avec les traces APM pour les résultats de l'évaluation. La valeur par défaut est \"{defaultUrlPath}\".",
|
||||
|
@ -15269,26 +15241,19 @@
|
|||
"xpack.elasticAssistant.assistant.settings.knowledgeBasedSettings.knowledgeBaseDescription": "Pour commencer, configurez ELSER dans {machineLearning}. {seeDocs}",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.alertsLabel": "Alertes",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.alertsRangeSliderLabel": "Plage d'alertes",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.askQuestionsAboutLabel": "Poser une question à propos de",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.deleteKnowledgeBaseButton": "Supprimer",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserLabel": "ELSER configuré",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserMachineLearningDescription": "Machine Learning",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserSeeDocsDescription": "Voir les documents",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlDescription": "Documents de la base de connaissances pour générer des requêtes ES|QL",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlInstalledDescription": "Documents de la base de connaissances ES|QL chargés",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlLabel": "Documents de la base de connaissances ES|QL",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.initializeKnowledgeBaseButton": "Initialiser",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseDescription": "Index où sont stockés les documents de la base de connaissances",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseInstalledDescription": "Initialisé sur `{kbIndexPattern}`",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseLabel": "Base de connaissances",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseTooltip": "ELSER doit être configuré pour activer la base de connaissances",
|
||||
"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.settings.knowledgeBaseSettings.selectFewerAlertsLabel": "Envoyez moins d'alertes si la fenêtre contextuelle du modèle est trop petite.",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsBadgeTitle": "Expérimental",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsDescription": "documentation",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsTitle": "Base de connaissances",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.yourAnonymizationSettingsLabel": "Vos paramètres d'anonymisation seront appliqués à ces alertes.",
|
||||
"xpack.elasticAssistant.assistant.settings.modalTitle": "Invites système",
|
||||
"xpack.elasticAssistant.assistant.settings.resetConversation": "Réinitialiser la conversation",
|
||||
"xpack.elasticAssistant.assistant.settings.securityAiSettingsTitle": "Paramètres d’IA de sécurité",
|
||||
"xpack.elasticAssistant.assistant.settings.settingsAnonymizationMenuItemTitle": "Anonymisation",
|
||||
|
@ -15303,11 +15268,9 @@
|
|||
"xpack.elasticAssistant.assistant.settings.settingsUpdatedToastTitle": "Paramètres mis à jour",
|
||||
"xpack.elasticAssistant.assistant.settings.showAnonymizedToggleLabel": "Afficher les anonymisés",
|
||||
"xpack.elasticAssistant.assistant.settings.showAnonymizedToggleRealValuesLabel": "Afficher les valeurs réelles",
|
||||
"xpack.elasticAssistant.assistant.settings.showAnonymizedTooltip": "Afficher les valeurs anonymisées envoyées vers et depuis l’assistant",
|
||||
"xpack.elasticAssistant.assistant.sidePanel.deleteConversationAriaLabel": "Supprimer la conversation",
|
||||
"xpack.elasticAssistant.assistant.submitMessage": "Envoyer un message",
|
||||
"xpack.elasticAssistant.assistant.useConversation.defaultConversationTitle": "Par défaut",
|
||||
"xpack.elasticAssistant.assistant.useConversation.elasticAiAssistantName": "Assistant",
|
||||
"xpack.elasticAssistant.assistant.useConversation.elasticAiAssistantTitle": "Assistant d’intelligence artificielle d’Elastic",
|
||||
"xpack.elasticAssistant.assistant.useConversation.welcomeConversationTitle": "Bienvenue",
|
||||
"xpack.elasticAssistant.assistant.welcomeScreen.description": "Avant toute chose, il faut configurer un Connecteur d'intelligence artificielle générative pour lancer cette expérience de chat !",
|
||||
|
@ -15333,12 +15296,6 @@
|
|||
"xpack.elasticAssistant.conversations.getConversationError": "Erreur lors de la recherche d'une conversation par l'identifiant {id}",
|
||||
"xpack.elasticAssistant.conversations.getUserConversationsError": "Erreur lors de la recherche de conversations",
|
||||
"xpack.elasticAssistant.conversations.updateConversationError": "Erreur lors de la mise à jour d'une conversation par l'identifiant {conversationId}",
|
||||
"xpack.elasticAssistant.conversationSidepanel.titleField.titleIsRequired": "Un titre est requis",
|
||||
"xpack.elasticAssistant.conversationSidepanel.titleField.uniqueTitle": "Le titre doit être unique",
|
||||
"xpack.elasticAssistant.conversationSidepanel.titleFieldLabel": "Titre",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutParagraph1": "Les champs ci-dessous sont permis par défaut",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutParagraph2": "Activer optionnellement l’anonymisation pour ces champs",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutTitle": "Valeur par défaut de l'anonymisation",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.settingsDescription": "Définissez les paramètres de confidentialité pour les données d'événements envoyées à des fournisseurs de LLM tiers. Vous pouvez choisir les champs à inclure et ceux à anonymiser en remplaçant leurs valeurs par des chaînes aléatoires. Les valeurs par défaut sont fournies ci-dessous.",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.settingsTitle": "Anonymisation",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.contextEditor.selectAllFields": "Sélectionnez l'ensemble des {totalFields} champs",
|
||||
|
@ -15350,15 +15307,12 @@
|
|||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.anonymizedStat.noneOfTheDataWillBeAnonymizedTooltip": "{isDataAnonymizable, select, true {Sélectionnez les champs à remplacer par des valeurs aléatoires. Les réponses sont automatiquement ramenées à leur valeur d'origine.} other {Ce contexte ne peut pas être anonymisé}}",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.availableStat.availableDescription": "Disponible",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.availableStat.availableTooltip": "{total} champs dans ce contexte sont disponibles pour être inclus dans la conversation",
|
||||
"xpack.elasticAssistant.documentationLinks.ariaLabel": "Cliquez pour ouvrir la documentation de l'assistant d'Elastic dans un nouvel onglet",
|
||||
"xpack.elasticAssistant.documentationLinks.documentation": "documentation",
|
||||
"xpack.elasticAssistant.evaluation.evaluationError": "Erreur lors de la réalisation de l'évaluation...",
|
||||
"xpack.elasticAssistant.evaluation.fetchEvaluationDataError": "Erreur lors de la récupération des données d'évaluation…",
|
||||
"xpack.elasticAssistant.flyout.right.header.collapseDetailButtonAriaLabel": "Masquer les chats",
|
||||
"xpack.elasticAssistant.flyout.right.header.expandDetailButtonAriaLabel": "Afficher les chats",
|
||||
"xpack.elasticAssistant.knowledgeBase.deleteError": "Erreur lors de la suppression de la base de connaissances",
|
||||
"xpack.elasticAssistant.knowledgeBase.entries.createErrorTitle": "Erreur lors de la création d’une entrée de la base de connaissances",
|
||||
"xpack.elasticAssistant.knowledgeBase.entries.deleteErrorTitle": "Erreur lors de la suppression d’entrées de la base de connaissances",
|
||||
"xpack.elasticAssistant.knowledgeBase.installKnowledgeBaseButton": "Installer la base de connaissances",
|
||||
"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",
|
||||
|
|
|
@ -15070,7 +15070,6 @@
|
|||
"xpack.ecsDataQualityDashboard.getIndexStats.dateRangeRequiredErrorMessage": "startDateとendDateは必須です",
|
||||
"xpack.elasticAssistant.anonymizationFields.bulkActionsAnonymizationFieldsError": "匿名化フィールドの更新エラー{error}",
|
||||
"xpack.elasticAssistant.assistant.apiErrorTitle": "メッセージの送信中にエラーが発生しました。",
|
||||
"xpack.elasticAssistant.assistant.clearChat": "チャットを消去",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.description": "会話を続行するようにコネクターを構成",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.missingPrivilegesDescription": "生成AIコネクターを有効にするには、管理者に連絡してください。",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.missingPrivilegesTitle": "生成AIコネクターが必要です",
|
||||
|
@ -15082,20 +15081,8 @@
|
|||
"xpack.elasticAssistant.assistant.connectors.connectorSelector.ariaLabel": "コネクターセレクター",
|
||||
"xpack.elasticAssistant.assistant.connectors.connectorSelector.newConnectorOptions": "新しいコネクターを追加...",
|
||||
"xpack.elasticAssistant.assistant.connectors.connectorSelectorInline.connectorPlaceholder": "コネクターを選択",
|
||||
"xpack.elasticAssistant.assistant.connectors.createConnectorButton": "コネクター",
|
||||
"xpack.elasticAssistant.assistant.connectors.preconfiguredTitle": "構成済み",
|
||||
"xpack.elasticAssistant.assistant.connectors.refreshConnectorsButton": "更新",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.skipTitle": "クリックしてスキップ....",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.timestampAtTitle": "に",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.userAssistantTitle": "アシスタント",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.userYouTitle": "あなた",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.defaultSystemPromptLabel": "デフォルト",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.defaultSystemPromptName": "デフォルトシステムプロンプト",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.ifYouDontKnowTheAnswer": "Elasticセキュリティに関連していない質問には回答しないでください。",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroPersonality": "サイバーセキュリティの専門家に情報を伝えるつもりで、できるだけ詳細で関連性のある回答を入力してください。",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptLabel": "拡張",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptName": "拡張システムプロンプト",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.youAreAHelpfulExpertAssistant": "あなたはElasticセキュリティに関する質問に答える、親切で専門的なアシスタントです。",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.connectorTitle": "コネクター",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptHelpTextTitle": "すべての会話の一部として提供されたコンテキスト。",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptTitle": "システムプロンプト",
|
||||
|
@ -15113,7 +15100,6 @@
|
|||
"xpack.elasticAssistant.assistant.conversationSelector.nextConversationTitle": "次の会話",
|
||||
"xpack.elasticAssistant.assistant.conversationSelector.placeholderTitle": "選択するか、入力して新規作成...",
|
||||
"xpack.elasticAssistant.assistant.conversationSelector.previousConversationTitle": "前の会話",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.actions": "アクション",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.connector": "コネクター",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.systemPrompt": "システムプロンプト",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.Title": "タイトル",
|
||||
|
@ -15126,21 +15112,17 @@
|
|||
"xpack.elasticAssistant.assistant.conversationSettings.title": "設定",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allActionsTooltip": "すべてのアクション",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowAction": "許可",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowByDefaultAction": "デフォルトで許可",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowedColumnTitle": "許可",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.alwaysSubmenu": "常に実行",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizeAction": "匿名化",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizeByDefaultAction": "デフォルトで匿名化",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizedColumnTitle": "匿名化",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.bulkActions": "一斉アクション",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.defaultsSubmenu": "デフォルト",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.denyAction": "拒否",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.denyByDefaultAction": "デフォルトで拒否",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.fieldColumnTitle": "フィールド",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.noButtonLabel": "いいえ",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.resetButton": "リセット",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.unanonymizeAction": "非匿名化",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.unanonymizeByDefaultAction": "デフォルトで非匿名化",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.valuesColumnTitle": "値",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.yesButtonLabel": "はい",
|
||||
"xpack.elasticAssistant.assistant.defaultAssistantTitle": "Elastic AI Assistant",
|
||||
|
@ -15151,13 +15133,9 @@
|
|||
"xpack.elasticAssistant.assistant.emptyScreen.description": "次のシステムプロンプトを使用して、「このアラートを要約してください」から「クエリの作成を手伝ってください」まで、何でも依頼してください。",
|
||||
"xpack.elasticAssistant.assistant.emptyScreen.title": "お手伝いできることはありますか?",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.addNewSystemPrompt": "新しいシステムプロンプトを追加...",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.addSystemPromptTooltip": "システムプロンプトを追加",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.clearSystemPrompt": "システムプロンプトを消去",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.commentsListAriaLabel": "コメントのリスト",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.editingPromptLabel": "プロンプトを編集中",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.emptyPrompt": "(空のプロンプト)",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.selectASystemPromptPlaceholder": "システムプロンプトを選択",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.youLabel": "あなた",
|
||||
"xpack.elasticAssistant.assistant.newChat.newChatButton": "チャット",
|
||||
"xpack.elasticAssistant.assistant.newChatByTitle.newChatByTitleButton": "チャット",
|
||||
"xpack.elasticAssistant.assistant.overlay.CancelButton": "キャンセル",
|
||||
|
@ -15169,7 +15147,6 @@
|
|||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultConversationsHelpText": "デフォルトでこのシステムプロンプトを使用する会話。",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultConversationsLabel": "デフォルトの会話",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultNewConversationTitle": "すべての新しい会話でデフォルトとして使用",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.modalTitle": "システムプロンプト",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.nameLabel": "名前",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.promptLabel": "プロンプト",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.promptPlaceholder": "システムプロンプトを入力",
|
||||
|
@ -15183,7 +15160,6 @@
|
|||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.systemPromptModal.systemPromptSelector.deletePromptTitle": "システムプロンプトを削除",
|
||||
"xpack.elasticAssistant.assistant.promptPlaceholder": "「このアラートを要約してください」から「クエリーの作成を手伝ってください」まで、何でも依頼してください。",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.settingsDescription": "システムプロンプトを作成して管理できます。システムプロンプトは、会話の一部に対して常に送信される、構成可能なコンテキストのチャンクです。",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnActions": "アクション",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnDateUpdated": "更新日",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnDefaultConversations": "デフォルトの会話",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnName": "名前",
|
||||
|
@ -15201,7 +15177,6 @@
|
|||
"xpack.elasticAssistant.assistant.quickPrompts.settings.badgeColorLabel": "バッジの色",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.contextsHelpText": "このクイックプロンプトが表示される場所を選択します。ノードを選択すると、このプロンプトがすべての場所に表示されます。",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.contextsLabel": "コンテキスト",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.modalTitle": "クイックプロンプト",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.nameLabel": "名前",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.promptLabel": "プロンプト",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.promptPlaceholder": "クイックプロンプトを入力",
|
||||
|
@ -15212,7 +15187,6 @@
|
|||
"xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationDefaultTitle": "クイックプロンプトを削除しますか?",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationMessage": "一度削除したプロンプトは回復できません",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationTitle": "\"{prompt}\"を削除しますか?",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnActions": "アクション",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnContexts": "コンテキスト",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnDateUpdated": "更新日",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnName": "名前",
|
||||
|
@ -15220,9 +15194,7 @@
|
|||
"xpack.elasticAssistant.assistant.resetConversationModal.clearChatConfirmation": "現在のチャットを消去しますか?すべての会話データは失われます。",
|
||||
"xpack.elasticAssistant.assistant.resetConversationModal.resetButtonText": "リセット",
|
||||
"xpack.elasticAssistant.assistant.settings.actionsButtonTitle": "アクション",
|
||||
"xpack.elasticAssistant.assistant.settings.anonymizedValues": "匿名化された値",
|
||||
"xpack.elasticAssistant.assistant.settings.connectorHelpTextTitle": "この会話タイプのデフォルトLLMコネクター。",
|
||||
"xpack.elasticAssistant.assistant.settings.connectorTitle": "コネクター",
|
||||
"xpack.elasticAssistant.assistant.settings.deleteButtonTitle": "削除",
|
||||
"xpack.elasticAssistant.assistant.settings.editButtonTitle": "編集",
|
||||
"xpack.elasticAssistant.assistant.settings.evaluationSettings.apmUrlDescription": "Kibana APMアプリのURL。評価結果のAPMトレースにリンクするために使用されます。デフォルトは\"{defaultUrlPath}\"です。",
|
||||
|
@ -15255,26 +15227,19 @@
|
|||
"xpack.elasticAssistant.assistant.settings.knowledgeBasedSettings.knowledgeBaseDescription": "{machineLearning}内でELSERを構成して開始します。{seeDocs}",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.alertsLabel": "アラート",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.alertsRangeSliderLabel": "アラート範囲",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.askQuestionsAboutLabel": "質問をする",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.deleteKnowledgeBaseButton": "削除",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserLabel": "ELSERが構成されました",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserMachineLearningDescription": "機械学習",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserSeeDocsDescription": "ドキュメントを参照",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlDescription": "ES|SQLクエリーを生成するためのナレッジベースドキュメント",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlInstalledDescription": "ES|QLナレッジベースドキュメントが読み込まれました",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlLabel": "ES|QLナレッジベースドキュメント",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.initializeKnowledgeBaseButton": "初期化",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseDescription": "ナレッジベースドキュメントが保存されているインデックス",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseInstalledDescription": "`{kbIndexPattern}`に初期化されました",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseLabel": "ナレッジベース",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseTooltip": "ナレッジベースを有効にするには、ELSERを構成する必要があります",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.latestAndRiskiestOpenAlertsLabel": "{alertsCount}件の最新の最もリスクが高い未解決または確認済みのアラートに関する情報をAI Assistantに送信します。",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.selectFewerAlertsLabel": "モデルのコンテキストウィンドウが小さすぎるため、少ないアラートが送信されます。",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsBadgeTitle": "実験的",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsDescription": "ドキュメンテーション",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsTitle": "ナレッジベース",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.yourAnonymizationSettingsLabel": "匿名化設定がこれらのアラートに適用されます。",
|
||||
"xpack.elasticAssistant.assistant.settings.modalTitle": "システムプロンプト",
|
||||
"xpack.elasticAssistant.assistant.settings.resetConversation": "会話をリセット",
|
||||
"xpack.elasticAssistant.assistant.settings.securityAiSettingsTitle": "セキュリティAI設定",
|
||||
"xpack.elasticAssistant.assistant.settings.settingsAnonymizationMenuItemTitle": "匿名化",
|
||||
|
@ -15289,11 +15254,9 @@
|
|||
"xpack.elasticAssistant.assistant.settings.settingsUpdatedToastTitle": "設定が更新されました",
|
||||
"xpack.elasticAssistant.assistant.settings.showAnonymizedToggleLabel": "匿名化して表示",
|
||||
"xpack.elasticAssistant.assistant.settings.showAnonymizedToggleRealValuesLabel": "実際の値を表示",
|
||||
"xpack.elasticAssistant.assistant.settings.showAnonymizedTooltip": "アシスタントの間で送信された匿名化された値を表示",
|
||||
"xpack.elasticAssistant.assistant.sidePanel.deleteConversationAriaLabel": "会話を削除",
|
||||
"xpack.elasticAssistant.assistant.submitMessage": "メッセージを送信",
|
||||
"xpack.elasticAssistant.assistant.useConversation.defaultConversationTitle": "デフォルト",
|
||||
"xpack.elasticAssistant.assistant.useConversation.elasticAiAssistantName": "アシスタント",
|
||||
"xpack.elasticAssistant.assistant.useConversation.elasticAiAssistantTitle": "Elastic AI Assistant",
|
||||
"xpack.elasticAssistant.assistant.useConversation.welcomeConversationTitle": "ようこそ",
|
||||
"xpack.elasticAssistant.assistant.welcomeScreen.description": "まず最初に、このチャットエクスペリエンスを開始するために生成AIコネクターを設定する必要があります。",
|
||||
|
@ -15319,12 +15282,6 @@
|
|||
"xpack.elasticAssistant.conversations.getConversationError": "id {id}による会話の取得エラー",
|
||||
"xpack.elasticAssistant.conversations.getUserConversationsError": "会話の取得エラー",
|
||||
"xpack.elasticAssistant.conversations.updateConversationError": "id {conversationId}による会話の更新エラー",
|
||||
"xpack.elasticAssistant.conversationSidepanel.titleField.titleIsRequired": "タイトルは必須です",
|
||||
"xpack.elasticAssistant.conversationSidepanel.titleField.uniqueTitle": "タイトルは一意でなければなりません",
|
||||
"xpack.elasticAssistant.conversationSidepanel.titleFieldLabel": "タイトル",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutParagraph1": "このフィールドはデフォルトで許可されています",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutParagraph2": "任意で、これらのフィールドの匿名化を有効にします",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutTitle": "匿名化デフォルト",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.settingsDescription": "サードパーティLLMプロバイダーに送信されたイベントデータのプライバシー設定を定義します。含めるフィールド、および値をランダム文字列で置換することで匿名化するフィールドを選択できます。参考になるデフォルト値を以下に示します。",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.settingsTitle": "匿名化",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.contextEditor.selectAllFields": "すべての{totalFields}フィールドを選択",
|
||||
|
@ -15336,15 +15293,12 @@
|
|||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.anonymizedStat.noneOfTheDataWillBeAnonymizedTooltip": "{isDataAnonymizable, select, true {ランダムな値に置き換えるフィールドを選択します。応答は自動的に元の値に変換されます。} other {このコンテキストは匿名化できません}}",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.availableStat.availableDescription": "利用可能",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.availableStat.availableTooltip": "このコンテキストの{total}個のフィールドを会話に含めることができます",
|
||||
"xpack.elasticAssistant.documentationLinks.ariaLabel": "クリックすると、新しいタブでElastic Assistantドキュメントを開きます",
|
||||
"xpack.elasticAssistant.documentationLinks.documentation": "ドキュメンテーション",
|
||||
"xpack.elasticAssistant.evaluation.evaluationError": "評価の実行エラー...",
|
||||
"xpack.elasticAssistant.evaluation.fetchEvaluationDataError": "評価データの取得エラー...",
|
||||
"xpack.elasticAssistant.flyout.right.header.collapseDetailButtonAriaLabel": "チャットを非表示",
|
||||
"xpack.elasticAssistant.flyout.right.header.expandDetailButtonAriaLabel": "チャットを表示",
|
||||
"xpack.elasticAssistant.knowledgeBase.deleteError": "ナレッジベースの削除エラー",
|
||||
"xpack.elasticAssistant.knowledgeBase.entries.createErrorTitle": "ナレッジベースエントリの作成エラー",
|
||||
"xpack.elasticAssistant.knowledgeBase.entries.deleteErrorTitle": "ナレッジベースエントリの削除エラー",
|
||||
"xpack.elasticAssistant.knowledgeBase.installKnowledgeBaseButton": "ナレッジベースをインストール",
|
||||
"xpack.elasticAssistant.knowledgeBase.setupError": "ナレッジベースの設定エラー",
|
||||
"xpack.elasticAssistant.knowledgeBase.statusError": "ナレッジベースステータスの取得エラー",
|
||||
|
|
|
@ -15095,7 +15095,6 @@
|
|||
"xpack.ecsDataQualityDashboard.getIndexStats.dateRangeRequiredErrorMessage": "“开始日期”和“结束日期”必填",
|
||||
"xpack.elasticAssistant.anonymizationFields.bulkActionsAnonymizationFieldsError": "更新匿名处理字段时出错 {error}",
|
||||
"xpack.elasticAssistant.assistant.apiErrorTitle": "发送消息时出错。",
|
||||
"xpack.elasticAssistant.assistant.clearChat": "清除聊天",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.description": "配置连接器以继续对话",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.missingPrivilegesDescription": "请联系管理员启用生成式 AI 连接器。",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.missingPrivilegesTitle": "需要生成式 AI 连接器",
|
||||
|
@ -15107,20 +15106,8 @@
|
|||
"xpack.elasticAssistant.assistant.connectors.connectorSelector.ariaLabel": "连接器选择器",
|
||||
"xpack.elasticAssistant.assistant.connectors.connectorSelector.newConnectorOptions": "添加新连接器……",
|
||||
"xpack.elasticAssistant.assistant.connectors.connectorSelectorInline.connectorPlaceholder": "选择连接器",
|
||||
"xpack.elasticAssistant.assistant.connectors.createConnectorButton": "连接器",
|
||||
"xpack.elasticAssistant.assistant.connectors.preconfiguredTitle": "预配置",
|
||||
"xpack.elasticAssistant.assistant.connectors.refreshConnectorsButton": "刷新",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.skipTitle": "单击以跳过……",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.timestampAtTitle": "处于",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.userAssistantTitle": "助手",
|
||||
"xpack.elasticAssistant.assistant.connectors.setup.userYouTitle": "您",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.defaultSystemPromptLabel": "默认",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.defaultSystemPromptName": "默认系统提示",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.ifYouDontKnowTheAnswer": "不回答与 Elastic Security 无关的问题。",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroPersonality": "提供可能的最详细、最相关的答案,就好像您正将此信息转发给网络安全专家一样。",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptLabel": "已增强",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.superheroSystemPromptName": "已增强系统提示",
|
||||
"xpack.elasticAssistant.assistant.content.prompts.system.youAreAHelpfulExpertAssistant": "您是一位可帮助回答 Elastic Security 相关问题的专家助手。",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.connectorTitle": "连接器",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptHelpTextTitle": "已作为每个对话的一部分提供上下文。",
|
||||
"xpack.elasticAssistant.assistant.conversations.settings.promptTitle": "系统提示",
|
||||
|
@ -15138,7 +15125,6 @@
|
|||
"xpack.elasticAssistant.assistant.conversationSelector.nextConversationTitle": "下一个对话",
|
||||
"xpack.elasticAssistant.assistant.conversationSelector.placeholderTitle": "选择或键入以新建……",
|
||||
"xpack.elasticAssistant.assistant.conversationSelector.previousConversationTitle": "上一个对话",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.actions": "操作",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.connector": "连接器",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.systemPrompt": "系统提示",
|
||||
"xpack.elasticAssistant.assistant.conversationSettings.column.Title": "标题",
|
||||
|
@ -15151,21 +15137,17 @@
|
|||
"xpack.elasticAssistant.assistant.conversationSettings.title": "设置",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allActionsTooltip": "所有操作",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowAction": "允许",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowByDefaultAction": "默认允许",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.allowedColumnTitle": "已允许",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.alwaysSubmenu": "始终",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizeAction": "匿名处理",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizeByDefaultAction": "默认匿名处理",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.anonymizedColumnTitle": "已匿名处理",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.bulkActions": "批处理操作",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.defaultsSubmenu": "默认值",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.denyAction": "拒绝",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.denyByDefaultAction": "默认拒绝",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.fieldColumnTitle": "字段",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.noButtonLabel": "否",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.resetButton": "重置",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.unanonymizeAction": "取消匿名处理",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.unanonymizeByDefaultAction": "默认取消匿名处理",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.valuesColumnTitle": "值",
|
||||
"xpack.elasticAssistant.assistant.dataAnonymizationEditor.contextEditor.yesButtonLabel": "是",
|
||||
"xpack.elasticAssistant.assistant.defaultAssistantTitle": "Elastic AI 助手",
|
||||
|
@ -15176,13 +15158,9 @@
|
|||
"xpack.elasticAssistant.assistant.emptyScreen.description": "使用以下系统提示向我提出任何要求,从“汇总此告警”到“帮助我构建查询”:",
|
||||
"xpack.elasticAssistant.assistant.emptyScreen.title": "我如何帮助您?",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.addNewSystemPrompt": "添加新系统提示……",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.addSystemPromptTooltip": "添加系统提示",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.clearSystemPrompt": "清除系统提示",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.commentsListAriaLabel": "注释列表",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.editingPromptLabel": "正在编辑提示",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.emptyPrompt": "(空提示)",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.selectASystemPromptPlaceholder": "选择系统提示",
|
||||
"xpack.elasticAssistant.assistant.firstPromptEditor.youLabel": "您",
|
||||
"xpack.elasticAssistant.assistant.newChat.newChatButton": "聊天",
|
||||
"xpack.elasticAssistant.assistant.newChatByTitle.newChatByTitleButton": "聊天",
|
||||
"xpack.elasticAssistant.assistant.overlay.CancelButton": "取消",
|
||||
|
@ -15194,7 +15172,6 @@
|
|||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultConversationsHelpText": "应默认使用此系统提示的对话。",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultConversationsLabel": "默认对话",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.defaultNewConversationTitle": "用作所有新对话的默认设置",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.modalTitle": "系统提示",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.nameLabel": "名称",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.promptLabel": "提示",
|
||||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.promptPlaceholder": "输入系统提示",
|
||||
|
@ -15208,7 +15185,6 @@
|
|||
"xpack.elasticAssistant.assistant.promptEditor.systemPrompt.systemPromptModal.systemPromptSelector.deletePromptTitle": "删除系统提示",
|
||||
"xpack.elasticAssistant.assistant.promptPlaceholder": "向我提出任何要求,从“汇总此告警”到“帮助我构建查询……”",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.settingsDescription": "创建和管理系统提示。系统提示是始终作为对话的一部分发送的可配置上下文块。",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnActions": "操作",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnDateUpdated": "更新日期",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnDefaultConversations": "默认对话",
|
||||
"xpack.elasticAssistant.assistant.promptsTable.systemPromptsTableColumnName": "名称",
|
||||
|
@ -15226,7 +15202,6 @@
|
|||
"xpack.elasticAssistant.assistant.quickPrompts.settings.badgeColorLabel": "徽章颜色",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.contextsHelpText": "选择将在什么地方显示此快速提示。选择无将随处显示此提示。",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.contextsLabel": "上下文",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.modalTitle": "快速提示",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.nameLabel": "名称",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.promptLabel": "提示",
|
||||
"xpack.elasticAssistant.assistant.quickPrompts.settings.promptPlaceholder": "输入快速提示",
|
||||
|
@ -15237,7 +15212,6 @@
|
|||
"xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationDefaultTitle": "删除快速提示?",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationMessage": "提示一旦删除,将无法恢复",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.modal.deleteQuickPromptConfirmationTitle": "删除“{prompt}”?",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnActions": "操作",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnContexts": "上下文",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnDateUpdated": "更新日期",
|
||||
"xpack.elasticAssistant.assistant.quickPromptsTable.quickPromptsTableColumnName": "名称",
|
||||
|
@ -15245,9 +15219,7 @@
|
|||
"xpack.elasticAssistant.assistant.resetConversationModal.clearChatConfirmation": "是否确定要清除当前聊天?所有对话数据将会丢失。",
|
||||
"xpack.elasticAssistant.assistant.resetConversationModal.resetButtonText": "重置",
|
||||
"xpack.elasticAssistant.assistant.settings.actionsButtonTitle": "操作",
|
||||
"xpack.elasticAssistant.assistant.settings.anonymizedValues": "已匿名处理值",
|
||||
"xpack.elasticAssistant.assistant.settings.connectorHelpTextTitle": "此对话类型的默认 LLM 连接器。",
|
||||
"xpack.elasticAssistant.assistant.settings.connectorTitle": "连接器",
|
||||
"xpack.elasticAssistant.assistant.settings.deleteButtonTitle": "删除",
|
||||
"xpack.elasticAssistant.assistant.settings.editButtonTitle": "编辑",
|
||||
"xpack.elasticAssistant.assistant.settings.evaluationSettings.apmUrlDescription": "Kibana APM 应用的 URL。用于链接到 APM 跟踪以获取评估结果。默认为“{defaultUrlPath}”。",
|
||||
|
@ -15280,26 +15252,19 @@
|
|||
"xpack.elasticAssistant.assistant.settings.knowledgeBasedSettings.knowledgeBaseDescription": "在 {machineLearning} 中配置 ELSER 以开始。{seeDocs}",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.alertsLabel": "告警",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.alertsRangeSliderLabel": "告警范围",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.askQuestionsAboutLabel": "提出有关以下项的问题",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.deleteKnowledgeBaseButton": "删除",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserLabel": "ELSER 已配置",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserMachineLearningDescription": "Machine Learning",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserSeeDocsDescription": "参阅文档",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlDescription": "用于生成 ES|QL 查询的知识库文档",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlInstalledDescription": "已加载 ES|QL 知识库文档",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlLabel": "ES|QL 知识库文档",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.initializeKnowledgeBaseButton": "初始化",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseDescription": "存储知识库文档的索引",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseInstalledDescription": "已初始化为 `{kbIndexPattern}`",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseLabel": "知识库",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseTooltip": "必须配置 ELSER 才能启用知识库",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.latestAndRiskiestOpenAlertsLabel": "发送有关 {alertsCount} 个最新和风险最高的未决或已确认告警的 AI 助手信息。",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.selectFewerAlertsLabel": "如果此模型的上下文窗口太小,则发送更少告警。",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsBadgeTitle": "实验性",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsDescription": "文档",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsTitle": "知识库",
|
||||
"xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.yourAnonymizationSettingsLabel": "您的匿名处理设置将应用于这些告警。",
|
||||
"xpack.elasticAssistant.assistant.settings.modalTitle": "系统提示",
|
||||
"xpack.elasticAssistant.assistant.settings.resetConversation": "重置对话",
|
||||
"xpack.elasticAssistant.assistant.settings.securityAiSettingsTitle": "安全性 AI 设置",
|
||||
"xpack.elasticAssistant.assistant.settings.settingsAnonymizationMenuItemTitle": "匿名处理",
|
||||
|
@ -15314,11 +15279,9 @@
|
|||
"xpack.elasticAssistant.assistant.settings.settingsUpdatedToastTitle": "设置已更新",
|
||||
"xpack.elasticAssistant.assistant.settings.showAnonymizedToggleLabel": "显示已匿名处理项",
|
||||
"xpack.elasticAssistant.assistant.settings.showAnonymizedToggleRealValuesLabel": "显示实际值",
|
||||
"xpack.elasticAssistant.assistant.settings.showAnonymizedTooltip": "显示该助手接收和发送的已匿名处理值",
|
||||
"xpack.elasticAssistant.assistant.sidePanel.deleteConversationAriaLabel": "删除对话",
|
||||
"xpack.elasticAssistant.assistant.submitMessage": "提交消息",
|
||||
"xpack.elasticAssistant.assistant.useConversation.defaultConversationTitle": "默认",
|
||||
"xpack.elasticAssistant.assistant.useConversation.elasticAiAssistantName": "助手",
|
||||
"xpack.elasticAssistant.assistant.useConversation.elasticAiAssistantTitle": "Elastic AI 助手",
|
||||
"xpack.elasticAssistant.assistant.useConversation.welcomeConversationTitle": "欢迎",
|
||||
"xpack.elasticAssistant.assistant.welcomeScreen.description": "首先,我们需要设置生成式 AI 连接器以继续这种聊天体验!",
|
||||
|
@ -15344,12 +15307,6 @@
|
|||
"xpack.elasticAssistant.conversations.getConversationError": "按 ID {id} 提取对话时出错",
|
||||
"xpack.elasticAssistant.conversations.getUserConversationsError": "提取对话时出错",
|
||||
"xpack.elasticAssistant.conversations.updateConversationError": "按 ID {conversationId} 更新对话时出错",
|
||||
"xpack.elasticAssistant.conversationSidepanel.titleField.titleIsRequired": "“标题”必填",
|
||||
"xpack.elasticAssistant.conversationSidepanel.titleField.uniqueTitle": "标题必须唯一",
|
||||
"xpack.elasticAssistant.conversationSidepanel.titleFieldLabel": "标题",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutParagraph1": "默认允许使用以下字段",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutParagraph2": "(可选)对这些字段启用匿名处理",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.calloutTitle": "匿名处理默认设置",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.settingsDescription": "为发送到第三方 LLM 提供商的事件数据定义隐私设置。您可以选择要包括哪些字段,并通过将其值替换为随机字符串来选择要匿名处理的字段。下面提供了有用的默认值。",
|
||||
"xpack.elasticAssistant.dataAnonymization.settings.anonymizationSettings.settingsTitle": "匿名处理",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.contextEditor.selectAllFields": "选择所有 {totalFields} 个字段",
|
||||
|
@ -15361,15 +15318,12 @@
|
|||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.anonymizedStat.noneOfTheDataWillBeAnonymizedTooltip": "{isDataAnonymizable, select, true {选择要用随机值替换的字段。会自动将响应重新转换为原始值。} other {此上下文无法进行匿名处理}}",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.availableStat.availableDescription": "可用",
|
||||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.availableStat.availableTooltip": "可在对话中包含此上下文中的 {total} 个字段",
|
||||
"xpack.elasticAssistant.documentationLinks.ariaLabel": "单击以在新选项卡中打开 Elastic 助手文档",
|
||||
"xpack.elasticAssistant.documentationLinks.documentation": "文档",
|
||||
"xpack.elasticAssistant.evaluation.evaluationError": "执行评估时出错......",
|
||||
"xpack.elasticAssistant.evaluation.fetchEvaluationDataError": "提取评估数据时出错......",
|
||||
"xpack.elasticAssistant.flyout.right.header.collapseDetailButtonAriaLabel": "隐藏聊天",
|
||||
"xpack.elasticAssistant.flyout.right.header.expandDetailButtonAriaLabel": "显示聊天",
|
||||
"xpack.elasticAssistant.knowledgeBase.deleteError": "删除知识库时出错",
|
||||
"xpack.elasticAssistant.knowledgeBase.entries.createErrorTitle": "创建知识库条目时出错",
|
||||
"xpack.elasticAssistant.knowledgeBase.entries.deleteErrorTitle": "删除知识库条目时出错",
|
||||
"xpack.elasticAssistant.knowledgeBase.installKnowledgeBaseButton": "安装知识库",
|
||||
"xpack.elasticAssistant.knowledgeBase.setupError": "设置知识库时出错",
|
||||
"xpack.elasticAssistant.knowledgeBase.statusError": "提取知识库状态时出错",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue