[8.10] [Observability Ai Assistant] Onboarding & Action Menu Improvements (#165459) (#165498)

# 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:
Kibana Machine 2023-09-01 13:42:09 -04:00 committed by GitHub
parent de6c207632
commit 1e19e16673
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 621 additions and 65 deletions

View file

@ -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}

View file

@ -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>
);
}

View file

@ -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>

View file

@ -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) => {

View file

@ -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>

View file

@ -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(),

View file

@ -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>
);
}

View file

@ -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>
);

View file

@ -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>
);
}

View file

@ -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>
</>
);
}

View file

@ -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' }

View file

@ -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);
}}

View file

@ -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`);
}