mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.10`: - [[Observability Ai Assistant] Onboarding & Action Menu Improvements (#165459)](https://github.com/elastic/kibana/pull/165459) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Coen Warmer","email":"coen.warmer@gmail.com"},"sourceCommit":{"committedDate":"2023-09-01T16:25:16Z","message":"[Observability Ai Assistant] Onboarding & Action Menu Improvements (#165459)","sha":"9090ceaf491a120d21b65ced5596f9fe07e0e196","branchLabelMapping":{"^v8.11.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:prev-minor","v8.10.0","v8.11.0"],"number":165459,"url":"https://github.com/elastic/kibana/pull/165459","mergeCommit":{"message":"[Observability Ai Assistant] Onboarding & Action Menu Improvements (#165459)","sha":"9090ceaf491a120d21b65ced5596f9fe07e0e196"}},"sourceBranch":"main","suggestedTargetBranches":["8.10"],"targetPullRequestStates":[{"branch":"8.10","label":"v8.10.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.11.0","labelRegex":"^v8.11.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/165459","number":165459,"mergeCommit":{"message":"[Observability Ai Assistant] Onboarding & Action Menu Improvements (#165459)","sha":"9090ceaf491a120d21b65ced5596f9fe07e0e196"}}]}] BACKPORT--> Co-authored-by: Coen Warmer <coen.warmer@gmail.com>
This commit is contained in:
parent
de6c207632
commit
1e19e16673
13 changed files with 621 additions and 65 deletions
|
@ -83,6 +83,12 @@ export function AskAssistantButton({
|
|||
)}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.translate(
|
||||
'xpack.observabilityAiAssistant.askAssistantButton.popoverTitle',
|
||||
{
|
||||
defaultMessage: 'Elastic Assistant',
|
||||
}
|
||||
)}
|
||||
iconType="sparkles"
|
||||
display={fill ? 'fill' : 'base'}
|
||||
size={size}
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonIcon,
|
||||
EuiContextMenu,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiLoadingSpinner,
|
||||
EuiPanel,
|
||||
EuiPopover,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { ConnectorSelectorBase } from '../connector_selector/connector_selector_base';
|
||||
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
|
||||
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
|
||||
import type { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
|
||||
|
||||
export function ChatActionsMenu({
|
||||
connectors,
|
||||
connectorsManagementHref,
|
||||
conversationId,
|
||||
knowledgeBase,
|
||||
modelsManagementHref,
|
||||
startedFrom,
|
||||
onCopyConversationClick,
|
||||
}: {
|
||||
connectors: UseGenAIConnectorsResult;
|
||||
connectorsManagementHref: string;
|
||||
conversationId?: string;
|
||||
knowledgeBase: UseKnowledgeBaseResult;
|
||||
modelsManagementHref: string;
|
||||
startedFrom?: StartedFrom;
|
||||
onCopyConversationClick: () => void;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const toggleActionsMenu = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
isOpen={isOpen}
|
||||
button={
|
||||
<EuiButtonIcon iconType="boxesVertical" onClick={toggleActionsMenu} aria-label="Menu" />
|
||||
}
|
||||
panelPaddingSize="none"
|
||||
closePopover={toggleActionsMenu}
|
||||
>
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={[
|
||||
{
|
||||
id: 0,
|
||||
title: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.title', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
items: [
|
||||
{
|
||||
name: (
|
||||
<>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.connector', {
|
||||
defaultMessage: 'Connector',
|
||||
})}{' '}
|
||||
<strong>
|
||||
{
|
||||
connectors.connectors?.find(({ id }) => id === connectors.selectedConnector)
|
||||
?.name
|
||||
}
|
||||
</strong>
|
||||
</>
|
||||
),
|
||||
panel: 1,
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase',
|
||||
{
|
||||
defaultMessage: 'Knowledge base',
|
||||
}
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ paddingRight: 4 }}>
|
||||
{knowledgeBase.status.loading || knowledgeBase.isInstalling ? (
|
||||
<EuiLoadingSpinner size="s" />
|
||||
) : knowledgeBase.status.value?.ready ? (
|
||||
<EuiIcon type="checkInCircleFilled" />
|
||||
) : (
|
||||
<EuiIcon type="dotInCircle" />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
panel: 2,
|
||||
},
|
||||
{
|
||||
name: i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.actions.copyConversation',
|
||||
{
|
||||
defaultMessage: 'Copy conversation',
|
||||
}
|
||||
),
|
||||
disabled: !conversationId,
|
||||
onClick: () => {
|
||||
toggleActionsMenu();
|
||||
onCopyConversationClick();
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
width: 256,
|
||||
title: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.connector', {
|
||||
defaultMessage: 'Connector',
|
||||
}),
|
||||
content: (
|
||||
<EuiPanel>
|
||||
<ConnectorSelectorBase {...connectors} />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton
|
||||
href={connectorsManagementHref}
|
||||
iconSide="right"
|
||||
iconType="arrowRight"
|
||||
size="s"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.actions.connectorManagement.button',
|
||||
{
|
||||
defaultMessage: 'Manage connectors',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiPanel>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
width: 256,
|
||||
title: i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.title',
|
||||
{
|
||||
defaultMessage: 'Knowledge base',
|
||||
}
|
||||
),
|
||||
content: (
|
||||
<EuiPanel>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.description.paragraph',
|
||||
{
|
||||
defaultMessage:
|
||||
'Using a knowledge base is optional but improves the experience of using the Assistant significantly.',
|
||||
}
|
||||
)}{' '}
|
||||
<EuiLink
|
||||
external
|
||||
target="_blank"
|
||||
href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.elser.learnMore',
|
||||
{
|
||||
defaultMessage: 'Learn more',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
{knowledgeBase.isInstalling || knowledgeBase.status.loading ? (
|
||||
<EuiLoadingSpinner size="m" />
|
||||
) : (
|
||||
<>
|
||||
<EuiSwitch
|
||||
label={
|
||||
knowledgeBase.isInstalling
|
||||
? i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.switchLabel.installing',
|
||||
{
|
||||
defaultMessage: 'Setting up knowledge base',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase.switchLabel.enable',
|
||||
{
|
||||
defaultMessage: 'Knowledge base installed',
|
||||
}
|
||||
)
|
||||
}
|
||||
checked={Boolean(knowledgeBase.status.value?.ready)}
|
||||
disabled={
|
||||
Boolean(knowledgeBase.status.value?.ready) || knowledgeBase.isInstalling
|
||||
}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
knowledgeBase.install();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiButton href={modelsManagementHref} fullWidth size="s">
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.actions.connectorManagement',
|
||||
{
|
||||
defaultMessage: 'Go to Machine Learning',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</>
|
||||
)}
|
||||
</EuiPanel>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
|
@ -24,19 +24,14 @@ import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
|
|||
import { useTimeline } from '../../hooks/use_timeline';
|
||||
import { useLicense } from '../../hooks/use_license';
|
||||
import { useObservabilityAIAssistantChatService } from '../../hooks/use_observability_ai_assistant_chat_service';
|
||||
import { MissingCredentialsCallout } from '../missing_credentials_callout';
|
||||
import { ExperimentalFeatureBanner } from './experimental_feature_banner';
|
||||
import { InitialSetupPanel } from './initial_setup_panel';
|
||||
import { IncorrectLicensePanel } from './incorrect_license_panel';
|
||||
import { ChatHeader } from './chat_header';
|
||||
import { ChatPromptEditor } from './chat_prompt_editor';
|
||||
import { ChatTimeline } from './chat_timeline';
|
||||
import { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
|
||||
|
||||
const containerClassName = css`
|
||||
max-height: 100%;
|
||||
max-width: ${1200 - 250}px; // page template max width - conversation list width.
|
||||
`;
|
||||
|
||||
const timelineClassName = css`
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
@ -57,6 +52,7 @@ export function ChatBody({
|
|||
connectors,
|
||||
knowledgeBase,
|
||||
connectorsManagementHref,
|
||||
modelsManagementHref,
|
||||
conversationId,
|
||||
currentUser,
|
||||
startedFrom,
|
||||
|
@ -70,6 +66,7 @@ export function ChatBody({
|
|||
connectors: UseGenAIConnectorsResult;
|
||||
knowledgeBase: UseKnowledgeBaseResult;
|
||||
connectorsManagementHref: string;
|
||||
modelsManagementHref: string;
|
||||
conversationId?: string;
|
||||
currentUser?: Pick<AuthenticatedUser, 'full_name' | 'username'>;
|
||||
startedFrom?: StartedFrom;
|
||||
|
@ -83,10 +80,10 @@ export function ChatBody({
|
|||
const chatService = useObservabilityAIAssistantChatService();
|
||||
|
||||
const timeline = useTimeline({
|
||||
messages,
|
||||
chatService,
|
||||
connectors,
|
||||
currentUser,
|
||||
chatService,
|
||||
messages,
|
||||
startedFrom,
|
||||
onChatUpdate,
|
||||
onChatComplete,
|
||||
|
@ -100,6 +97,13 @@ export function ChatBody({
|
|||
connectors.loading || knowledgeBase.status.loading || last(timeline.items)?.loading
|
||||
);
|
||||
|
||||
const containerClassName = css`
|
||||
max-height: 100%;
|
||||
max-width: ${startedFrom === 'conversationView'
|
||||
? 1200 - 250 + 'px' // page template max width - conversation list width.
|
||||
: '100%'};
|
||||
`;
|
||||
|
||||
useEffect(() => {
|
||||
const parent = timelineContainerRef.current?.parentElement;
|
||||
if (!parent) {
|
||||
|
@ -143,6 +147,12 @@ export function ChatBody({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [timelineContainerRef.current]);
|
||||
|
||||
const handleCopyConversation = () => {
|
||||
const content = JSON.stringify({ title, messages });
|
||||
|
||||
navigator.clipboard?.writeText(content || '');
|
||||
};
|
||||
|
||||
if (!hasCorrectLicense && !conversationId) {
|
||||
footer = (
|
||||
<>
|
||||
|
@ -166,12 +176,14 @@ export function ChatBody({
|
|||
<EuiLoadingSpinner />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
} else if (connectors.connectors?.length === 0) {
|
||||
} else if (connectors.connectors?.length === 0 && !conversationId) {
|
||||
footer = (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<MissingCredentialsCallout connectorsManagementHref={connectorsManagementHref} />
|
||||
</>
|
||||
<InitialSetupPanel
|
||||
connectors={connectors}
|
||||
connectorsManagementHref={connectorsManagementHref}
|
||||
knowledgeBase={knowledgeBase}
|
||||
startedFrom={startedFrom}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
footer = (
|
||||
|
@ -181,6 +193,7 @@ export function ChatBody({
|
|||
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
|
||||
<ChatTimeline
|
||||
items={timeline.items}
|
||||
knowledgeBase={knowledgeBase}
|
||||
onEdit={timeline.onEdit}
|
||||
onFeedback={timeline.onFeedback}
|
||||
onRegenerate={timeline.onRegenerate}
|
||||
|
@ -208,16 +221,24 @@ export function ChatBody({
|
|||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none" className={containerClassName}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ExperimentalFeatureBanner />
|
||||
</EuiFlexItem>
|
||||
{connectors.selectedConnector ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<ExperimentalFeatureBanner />
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<ChatHeader
|
||||
connectors={connectors}
|
||||
conversationId={conversationId}
|
||||
connectorsManagementHref={connectorsManagementHref}
|
||||
modelsManagementHref={modelsManagementHref}
|
||||
knowledgeBase={knowledgeBase}
|
||||
licenseInvalid={!hasCorrectLicense && !conversationId}
|
||||
loading={loading}
|
||||
startedFrom={startedFrom}
|
||||
title={title}
|
||||
onCopyConversation={handleCopyConversation}
|
||||
onSaveTitle={onSaveTitle}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -15,6 +15,7 @@ import { useKibana } from '../../hooks/use_kibana';
|
|||
import { useKnowledgeBase } from '../../hooks/use_knowledge_base';
|
||||
import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router';
|
||||
import { getConnectorsManagementHref } from '../../utils/get_connectors_management_href';
|
||||
import { getModelsManagementHref } from '../../utils/get_models_management_href';
|
||||
import { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
|
||||
import { ChatBody } from './chat_body';
|
||||
|
||||
|
@ -102,6 +103,8 @@ export function ChatFlyout({
|
|||
messages={messages}
|
||||
currentUser={currentUser}
|
||||
connectorsManagementHref={getConnectorsManagementHref(http)}
|
||||
modelsManagementHref={getModelsManagementHref(http)}
|
||||
conversationId={conversationId}
|
||||
knowledgeBase={knowledgeBase}
|
||||
startedFrom={startedFrom}
|
||||
onChatUpdate={(nextMessages) => {
|
||||
|
|
|
@ -16,12 +16,12 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/css';
|
||||
import { AssistantAvatar } from '../assistant_avatar';
|
||||
import { ConnectorSelectorBase } from '../connector_selector/connector_selector_base';
|
||||
import { EMPTY_CONVERSATION_TITLE, UPGRADE_LICENSE_TITLE } from '../../i18n';
|
||||
import { KnowledgeBaseCallout } from './knowledge_base_callout';
|
||||
import { ChatActionsMenu } from './chat_actions_menu';
|
||||
import { ASSISTANT_SETUP_TITLE, EMPTY_CONVERSATION_TITLE, UPGRADE_LICENSE_TITLE } from '../../i18n';
|
||||
import { useUnmountAndRemountWhenPropChanges } from '../../hooks/use_unmount_and_remount_when_prop_changes';
|
||||
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
|
||||
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
|
||||
import { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
|
||||
|
||||
// needed to prevent InlineTextEdit component from expanding container
|
||||
const minWidthClassName = css`
|
||||
|
@ -33,19 +33,33 @@ export function ChatHeader({
|
|||
loading,
|
||||
licenseInvalid,
|
||||
connectors,
|
||||
connectorsManagementHref,
|
||||
modelsManagementHref,
|
||||
conversationId,
|
||||
knowledgeBase,
|
||||
startedFrom,
|
||||
onSaveTitle,
|
||||
onCopyConversation,
|
||||
}: {
|
||||
title: string;
|
||||
loading: boolean;
|
||||
licenseInvalid: boolean;
|
||||
connectors: UseGenAIConnectorsResult;
|
||||
connectorsManagementHref: string;
|
||||
modelsManagementHref: string;
|
||||
conversationId?: string;
|
||||
knowledgeBase: UseKnowledgeBaseResult;
|
||||
startedFrom?: StartedFrom;
|
||||
onCopyConversation: () => void;
|
||||
onSaveTitle?: (title: string) => void;
|
||||
}) {
|
||||
const hasTitle = !!title;
|
||||
|
||||
const displayedTitle = licenseInvalid ? UPGRADE_LICENSE_TITLE : title || EMPTY_CONVERSATION_TITLE;
|
||||
const displayedTitle = !connectors.selectedConnector
|
||||
? ASSISTANT_SETUP_TITLE
|
||||
: licenseInvalid
|
||||
? UPGRADE_LICENSE_TITLE
|
||||
: title || EMPTY_CONVERSATION_TITLE;
|
||||
|
||||
const theme = useEuiTheme();
|
||||
|
||||
|
@ -57,45 +71,40 @@ export function ChatHeader({
|
|||
|
||||
return (
|
||||
<EuiPanel paddingSize="m" hasBorder={false} hasShadow={false} borderRadius="none">
|
||||
<EuiFlexGroup gutterSize="m" responsive={false}>
|
||||
<EuiFlexGroup gutterSize="m" responsive={false} alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{loading ? <EuiLoadingSpinner size="l" /> : <AssistantAvatar size="s" />}
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow className={minWidthClassName}>
|
||||
<EuiFlexGroup direction="column" gutterSize="none" className={minWidthClassName}>
|
||||
<EuiFlexItem className={minWidthClassName}>
|
||||
{shouldRender ? (
|
||||
<EuiInlineEditTitle
|
||||
heading="h2"
|
||||
size="s"
|
||||
defaultValue={displayedTitle}
|
||||
className={css`
|
||||
color: ${hasTitle
|
||||
? theme.euiTheme.colors.text
|
||||
: theme.euiTheme.colors.subduedText};
|
||||
`}
|
||||
inputAriaLabel={i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.editConversationInput',
|
||||
{ defaultMessage: 'Edit conversation' }
|
||||
)}
|
||||
editModeProps={{ inputProps: { inputRef } }}
|
||||
isReadOnly={licenseInvalid || !Boolean(onSaveTitle)}
|
||||
onSave={onSaveTitle}
|
||||
/>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow>
|
||||
<ConnectorSelectorBase {...connectors} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{!licenseInvalid ? <KnowledgeBaseCallout knowledgeBase={knowledgeBase} /> : null}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{shouldRender ? (
|
||||
<EuiInlineEditTitle
|
||||
heading="h2"
|
||||
size="s"
|
||||
defaultValue={displayedTitle}
|
||||
className={css`
|
||||
color: ${hasTitle ? theme.euiTheme.colors.text : theme.euiTheme.colors.subduedText};
|
||||
`}
|
||||
inputAriaLabel={i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatHeader.editConversationInput',
|
||||
{ defaultMessage: 'Edit conversation' }
|
||||
)}
|
||||
editModeProps={{ inputProps: { inputRef } }}
|
||||
isReadOnly={!connectors.selectedConnector || licenseInvalid || !Boolean(onSaveTitle)}
|
||||
onSave={onSaveTitle}
|
||||
/>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ChatActionsMenu
|
||||
connectors={connectors}
|
||||
connectorsManagementHref={connectorsManagementHref}
|
||||
modelsManagementHref={modelsManagementHref}
|
||||
conversationId={conversationId}
|
||||
knowledgeBase={knowledgeBase}
|
||||
startedFrom={startedFrom}
|
||||
onCopyConversationClick={onCopyConversation}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
|
|
|
@ -48,6 +48,18 @@ const Template: ComponentStory<typeof Component> = (props: ChatTimelineProps) =>
|
|||
};
|
||||
|
||||
const defaultProps: ComponentProps<typeof Component> = {
|
||||
knowledgeBase: {
|
||||
status: {
|
||||
loading: false,
|
||||
value: {
|
||||
ready: true,
|
||||
},
|
||||
refresh: () => {},
|
||||
},
|
||||
isInstalling: false,
|
||||
installError: undefined,
|
||||
install: async () => {},
|
||||
},
|
||||
items: [
|
||||
buildChatInitItem(),
|
||||
buildUserChatItem(),
|
||||
|
|
|
@ -14,6 +14,7 @@ import { ChatItem } from './chat_item';
|
|||
import { ChatWelcomePanel } from './chat_welcome_panel';
|
||||
import type { Feedback } from '../feedback_buttons';
|
||||
import type { Message } from '../../../common';
|
||||
import { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
|
||||
|
||||
export interface ChatTimelineItem
|
||||
extends Pick<Message['message'], 'role' | 'content' | 'function_call'> {
|
||||
|
@ -37,6 +38,7 @@ export interface ChatTimelineItem
|
|||
|
||||
export interface ChatTimelineProps {
|
||||
items: ChatTimelineItem[];
|
||||
knowledgeBase: UseKnowledgeBaseResult;
|
||||
onEdit: (item: ChatTimelineItem, message: Message) => Promise<void>;
|
||||
onFeedback: (item: ChatTimelineItem, feedback: Feedback) => void;
|
||||
onRegenerate: (item: ChatTimelineItem) => void;
|
||||
|
@ -45,6 +47,7 @@ export interface ChatTimelineProps {
|
|||
|
||||
export function ChatTimeline({
|
||||
items = [],
|
||||
knowledgeBase,
|
||||
onEdit,
|
||||
onFeedback,
|
||||
onRegenerate,
|
||||
|
@ -77,7 +80,7 @@ export function ChatTimeline({
|
|||
/>
|
||||
))
|
||||
)}
|
||||
{filteredItems.length === 1 ? <ChatWelcomePanel /> : null}
|
||||
{filteredItems.length === 1 ? <ChatWelcomePanel knowledgeBase={knowledgeBase} /> : null}
|
||||
</EuiCommentList>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,18 +6,19 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiImage, EuiPanel, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { EuiButton, EuiFlexGroup, EuiImage, EuiPanel, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import ctaImage from '../../assets/elastic_ai_assistant.png';
|
||||
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
|
||||
|
||||
const incorrectLicenseContainer = css`
|
||||
height: 100%;
|
||||
padding: ${euiThemeVars.euiPanelPaddingModifiers.paddingMedium};
|
||||
`;
|
||||
|
||||
export function ChatWelcomePanel() {
|
||||
export function ChatWelcomePanel({ knowledgeBase }: { knowledgeBase: UseKnowledgeBaseResult }) {
|
||||
return (
|
||||
<EuiPanel hasBorder={false} hasShadow={false}>
|
||||
<EuiFlexGroup
|
||||
|
@ -26,7 +27,7 @@ export function ChatWelcomePanel() {
|
|||
justifyContent="center"
|
||||
className={incorrectLicenseContainer}
|
||||
>
|
||||
<EuiImage src={ctaImage} alt="Elastic AI Assistant" size="l" />
|
||||
<EuiImage src={ctaImage} alt="Elastic AI Assistant" size="m" />
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.title', {
|
||||
|
@ -36,12 +37,34 @@ export function ChatWelcomePanel() {
|
|||
</EuiTitle>
|
||||
<EuiText color="subdued" textAlign="center">
|
||||
<p>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.body', {
|
||||
defaultMessage:
|
||||
'Elastic AI Assistant is an experimental feature. Make sure to provide feedback when you interact with it.',
|
||||
})}
|
||||
{knowledgeBase.status.value?.ready
|
||||
? i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.body.kbReady', {
|
||||
defaultMessage:
|
||||
'Keep in mind that Elastic AI Assistant is a technical preview feature. Please provide feedback at any time.',
|
||||
})
|
||||
: i18n.translate('xpack.observabilityAiAssistant.chatWelcomePanel.body.kbNotReady', {
|
||||
defaultMessage:
|
||||
'We recommend you enable the knowledge base for a better experience. It will provide the assistant with the ability to learn from your interaction with it. Keep in mind that Elastic AI Assistant is a technical preview feature. Please provide feedback at any time.',
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
{!knowledgeBase.status.value?.ready ? (
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
iconType={knowledgeBase.status.value?.ready ? 'checkInCircleFilled' : 'dotInCircle'}
|
||||
isLoading={knowledgeBase.isInstalling || knowledgeBase.status.loading}
|
||||
onClick={knowledgeBase.install}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.chatWelcomePanel.knowledgeBase.buttonLabel.notInstalledYet',
|
||||
{
|
||||
defaultMessage: 'Set up knowledge base',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
|
|
@ -20,7 +20,7 @@ import illustration from '../../assets/illustration.svg';
|
|||
|
||||
export function ExperimentalFeatureBanner() {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<EuiPanel color="warning" paddingSize="s" hasBorder={false}>
|
||||
<EuiFlexGroup direction="row" alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -33,7 +33,7 @@ export function ExperimentalFeatureBanner() {
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow>
|
||||
<EuiFlexGroup direction="row" gutterSize="xs" alignItems="center">
|
||||
<EuiFlexGroup direction="row" gutterSize="xs" alignItems="center" wrap>
|
||||
<FormattedMessage
|
||||
id="xpack.observabilityAiAssistant.experimentalFunctionBanner.title"
|
||||
defaultMessage="This feature is currently in {techPreview} and may contain issues."
|
||||
|
@ -52,6 +52,6 @@ export function ExperimentalFeatureBanner() {
|
|||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiBetaBadge,
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiCard,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ConnectorSelectorBase } from '../connector_selector/connector_selector_base';
|
||||
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
|
||||
import { ExperimentalFeatureBanner } from './experimental_feature_banner';
|
||||
import { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
|
||||
import { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
|
||||
|
||||
export function InitialSetupPanel({
|
||||
connectors,
|
||||
connectorsManagementHref,
|
||||
knowledgeBase,
|
||||
startedFrom,
|
||||
}: {
|
||||
connectors: UseGenAIConnectorsResult;
|
||||
connectorsManagementHref: string;
|
||||
knowledgeBase: UseKnowledgeBaseResult;
|
||||
startedFrom?: StartedFrom;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<ExperimentalFeatureBanner />
|
||||
|
||||
<EuiPanel paddingSize="m" style={{ overflowY: 'auto' }}>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.initialSetupPanel.title', {
|
||||
defaultMessage:
|
||||
'Start your Al experience with Elastic by completing the steps below.',
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGroup direction={startedFrom === 'conversationView' ? 'row' : 'column'}>
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
icon={<EuiIcon type="machineLearningApp" size="xl" />}
|
||||
title={i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.title',
|
||||
{
|
||||
defaultMessage: 'Knowledge Base',
|
||||
}
|
||||
)}
|
||||
description={
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.description.paragraph1',
|
||||
{
|
||||
defaultMessage:
|
||||
'We recommend you enable the knowledge base for a better experience. It will provide the assistant with the ability to learn from your interaction with it.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.description.paragraph2',
|
||||
{
|
||||
defaultMessage: 'This step is optional, you can always do it later.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
}
|
||||
footer={
|
||||
knowledgeBase.status.value?.ready ? (
|
||||
<EuiCallOut
|
||||
color="success"
|
||||
iconType="checkInCircleFilled"
|
||||
size="s"
|
||||
style={{ padding: '10px 14px', display: 'inline-flex', borderRadius: '6px' }}
|
||||
title={i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.buttonLabel.alreadyInstalled',
|
||||
{
|
||||
defaultMessage: 'Knowledge base installed',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<EuiButton
|
||||
color={knowledgeBase.status.value?.ready ? 'success' : 'primary'}
|
||||
fill
|
||||
isLoading={knowledgeBase.isInstalling || knowledgeBase.status.loading}
|
||||
onClick={knowledgeBase.install}
|
||||
iconType="dotInCircle"
|
||||
>
|
||||
{knowledgeBase.isInstalling || knowledgeBase.status.loading
|
||||
? i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.buttonLabel.installingKb',
|
||||
{
|
||||
defaultMessage: 'Installing knowledge base',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.knowledgeBase.buttonLabel.kbNotInstalledYet',
|
||||
{
|
||||
defaultMessage: 'Set up knowledge base',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
icon={<EuiIcon type="devToolsApp" size="xl" />}
|
||||
title={i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.title',
|
||||
{
|
||||
defaultMessage: 'Connector setup',
|
||||
}
|
||||
)}
|
||||
description={
|
||||
!connectors.connectors?.length ? (
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description1',
|
||||
{
|
||||
defaultMessage: 'Set up a Generative AI connector with your AI provider.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2',
|
||||
{
|
||||
defaultMessage:
|
||||
'The Generative AI model needs to support function calls. We strongly recommend using GPT4.',
|
||||
}
|
||||
)}
|
||||
<EuiBetaBadge
|
||||
label=""
|
||||
css={{ boxShadow: 'none' }}
|
||||
tooltipContent={i18n.translate(
|
||||
'xpack.observabilityAiAssistant.technicalPreviewBadgeDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
"GPT4 is required for a more consistent experience when using function calls (for example when performing root cause analysis, visualizing data and more). GPT3.5 can work for some of the simpler workflows, such as explaining errors or for a ChatGPT like experience within Kibana which don't require the use of frequent function calls.",
|
||||
}
|
||||
)}
|
||||
iconType="iInCircle"
|
||||
size="s"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
) : connectors.connectors.length && !connectors.selectedConnector ? (
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description',
|
||||
{
|
||||
defaultMessage: 'Please select a provider.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
footer={
|
||||
!connectors.connectors?.length ? (
|
||||
<EuiButton fill color="primary" href={connectorsManagementHref}>
|
||||
{i18n.translate(
|
||||
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel',
|
||||
{
|
||||
defaultMessage: 'Set up Generative AI connector',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
) : connectors.connectors.length && !connectors.selectedConnector ? (
|
||||
<ConnectorSelectorBase {...connectors} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="xxl" />
|
||||
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.initialSetupPanel.disclaimer', {
|
||||
defaultMessage:
|
||||
'The AI provider that is configured may collect telemetry when using the Elastic AI Assistant. Contact your AI provider for information on how data is collected.',
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -7,6 +7,13 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ASSISTANT_SETUP_TITLE = i18n.translate(
|
||||
'xpack.observabilityAiAssistant.assistantSetup.title',
|
||||
{
|
||||
defaultMessage: 'Welcome to Elastic AI Assistant',
|
||||
}
|
||||
);
|
||||
|
||||
export const EMPTY_CONVERSATION_TITLE = i18n.translate(
|
||||
'xpack.observabilityAiAssistant.emptyConversationTitle',
|
||||
{ defaultMessage: 'New conversation' }
|
||||
|
|
|
@ -23,6 +23,7 @@ import { useObservabilityAIAssistant } from '../../hooks/use_observability_ai_as
|
|||
import { useObservabilityAIAssistantParams } from '../../hooks/use_observability_ai_assistant_params';
|
||||
import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router';
|
||||
import { getConnectorsManagementHref } from '../../utils/get_connectors_management_href';
|
||||
import { getModelsManagementHref } from '../../utils/get_models_management_href';
|
||||
import { EMPTY_CONVERSATION_TITLE } from '../../i18n';
|
||||
|
||||
const containerClassName = css`
|
||||
|
@ -230,10 +231,12 @@ export function ConversationView() {
|
|||
currentUser={currentUser}
|
||||
connectors={connectors}
|
||||
connectorsManagementHref={getConnectorsManagementHref(http)}
|
||||
modelsManagementHref={getModelsManagementHref(http)}
|
||||
conversationId={conversationId}
|
||||
knowledgeBase={knowledgeBase}
|
||||
messages={displayedMessages}
|
||||
title={conversation.value.conversation.title}
|
||||
startedFrom="conversationView"
|
||||
onChatUpdate={(messages) => {
|
||||
setDisplayedMessages(messages);
|
||||
}}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { HttpStart } from '@kbn/core/public';
|
||||
|
||||
export function getModelsManagementHref(http: HttpStart) {
|
||||
return http!.basePath.prepend(`/app/ml/trained_models`);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue