[8.12] Onboarding Polish (#172974) (#173271)

# Backport

This will backport the following commits from `main` to `8.12`:
- [Onboarding Polish
(#172974)](https://github.com/elastic/kibana/pull/172974)

<!--- 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-12-13T13:00:40Z","message":"Onboarding
Polish
(#172974)","sha":"d28da807121b43f4c4b8a5a61b170e8f740f076b","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:prev-minor","v8.12.0","v8.13.0"],"number":172974,"url":"https://github.com/elastic/kibana/pull/172974","mergeCommit":{"message":"Onboarding
Polish
(#172974)","sha":"d28da807121b43f4c4b8a5a61b170e8f740f076b"}},"sourceBranch":"main","suggestedTargetBranches":["8.12"],"targetPullRequestStates":[{"branch":"8.12","label":"v8.12.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.13.0","labelRegex":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/172974","number":172974,"mergeCommit":{"message":"Onboarding
Polish (#172974)","sha":"d28da807121b43f4c4b8a5a61b170e8f740f076b"}}]}]
BACKPORT-->

Co-authored-by: Coen Warmer <coen.warmer@gmail.com>
This commit is contained in:
Kibana Machine 2023-12-13 09:26:44 -05:00 committed by GitHub
parent 5889e7dd07
commit 08a925ad85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1115 additions and 419 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before After
Before After

View file

@ -48,7 +48,7 @@ export function ObservabilityAIAssistantActionMenuItem() {
{!isOpen || chatService.value ? (
<AssistantAvatar size="xs" />
) : (
<EuiLoadingSpinner size="s" />
<EuiLoadingSpinner size="m" />
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>

View file

@ -85,7 +85,7 @@ export function AskAssistantButton({
<EuiToolTip
position="top"
title={i18n.translate('xpack.observabilityAiAssistant.askAssistantButton.popoverTitle', {
defaultMessage: 'Elastic Assistant',
defaultMessage: 'AI Assistant for Observability',
})}
content={i18n.translate(
'xpack.observabilityAiAssistant.askAssistantButton.popoverContent',
@ -98,7 +98,7 @@ export function AskAssistantButton({
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.askAssistantButton.popoverTitle',
{
defaultMessage: 'Elastic Assistant',
defaultMessage: 'AI Assistant for Observability',
}
)}
data-test-subj="observabilityAiAssistantAskAssistantButtonButtonIcon"

View file

@ -70,6 +70,15 @@ export function ChatActionsMenu({
defaultMessage: 'Actions',
}),
items: [
{
name: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.settings', {
defaultMessage: 'AI Assistant Settings',
}),
onClick: () => {
toggleActionsMenu();
handleNavigateToSettings();
},
},
{
name: (
<div className="eui-textTruncate">
@ -86,15 +95,6 @@ export function ChatActionsMenu({
),
panel: 1,
},
{
name: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.settings', {
defaultMessage: 'AI Assistant Settings',
}),
onClick: () => {
toggleActionsMenu();
handleNavigateToSettings();
},
},
{
name: i18n.translate(
'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase',

View file

@ -5,44 +5,48 @@
* 2.0.
*/
import React, { useEffect, useRef, useState } from 'react';
import { css, keyframes } from '@emotion/css';
import {
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiLoadingSpinner,
EuiPanel,
EuiSpacer,
} from '@elastic/eui';
import { css } from '@emotion/css';
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
import { euiThemeVars } from '@kbn/ui-theme';
import React, { useEffect, useRef, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { Conversation, Message, MessageRole } from '../../../common/types';
import { ChatState } from '../../hooks/use_chat';
import { useConversation } from '../../hooks/use_conversation';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import { useLicense } from '../../hooks/use_license';
import { useObservabilityAIAssistantChatService } from '../../hooks/use_observability_ai_assistant_chat_service';
import { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import { type Conversation, type Message, MessageRole } from '../../../common/types';
import { ChatHeader } from './chat_header';
import { ChatPromptEditor } from './chat_prompt_editor';
import { ChatTimeline } from './chat_timeline';
import { IncorrectLicensePanel } from './incorrect_license_panel';
import { InitialSetupPanel } from './initial_setup_panel';
import { ChatActionClickType } from './types';
import { EMPTY_CONVERSATION_TITLE } from '../../i18n';
import { Feedback } from '../feedback_buttons';
import { IncorrectLicensePanel } from './incorrect_license_panel';
import { WelcomeMessage } from './welcome_message';
import { EMPTY_CONVERSATION_TITLE } from '../../i18n';
import { MESSAGE_FEEDBACK } from '../../analytics/schema';
import { ChatActionClickType } from './types';
import type { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
const fullHeightClassName = css`
height: 100%;
`;
const timelineClassName = css`
overflow-y: auto;
`;
const loadingSpinnerContainerClassName = css`
align-self: center;
const promptEditorClassname = css`
overflow: hidden;
transition: height ${euiThemeVars.euiAnimSpeedFast} ${euiThemeVars.euiAnimSlightResistance};
`;
const incorrectLicenseContainer = css`
@ -54,6 +58,29 @@ const chatBodyContainerClassNameWithError = css`
align-self: center;
`;
const promptEditorContainerClassName = css`
padding-top: 12px;
padding-bottom: 8px;
`;
const fadeInAnimation = keyframes`
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
`;
const animClassName = css`
height: 100%;
opacity: 0;
animation: ${fadeInAnimation} ${euiThemeVars.euiAnimSpeedNormal}
${euiThemeVars.euiAnimSlightBounce} ${euiThemeVars.euiAnimSpeedNormal} forwards;
`;
export function ChatBody({
initialTitle,
initialMessages,
@ -175,83 +202,86 @@ export function ChatBody({
</EuiFlexItem>
</>
);
} else if (
connectors.loading ||
knowledgeBase.status.loading ||
(!conversation.value && conversation.loading)
) {
footer = (
<EuiFlexItem className={loadingSpinnerContainerClassName}>
<EuiLoadingSpinner />
</EuiFlexItem>
);
} else if (connectors.connectors?.length === 0 && !initialConversationId) {
footer = (
<InitialSetupPanel
connectors={connectors}
connectorsManagementHref={connectorsManagementHref}
knowledgeBase={knowledgeBase}
startedFrom={startedFrom}
/>
);
} else if (!conversation.value && conversation.loading) {
footer = null;
} else {
footer = (
<>
<EuiFlexItem grow className={timelineClassName}>
<div ref={timelineContainerRef}>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
<ChatTimeline
startedFrom={startedFrom}
messages={messages}
knowledgeBase={knowledgeBase}
chatService={chatService}
currentUser={currentUser}
chatState={state}
hasConnector={!!connectors.connectors?.length}
onEdit={(editedMessage, newMessage) => {
const indexOf = messages.indexOf(editedMessage);
next(messages.slice(0, indexOf).concat(newMessage));
}}
onFeedback={handleFeedback}
onRegenerate={(message) => {
const indexOf = messages.indexOf(message);
next(messages.slice(0, indexOf));
}}
onStopGenerating={() => {
stop();
}}
onActionClick={(payload) => {
setStickToBottom(true);
switch (payload.type) {
case ChatActionClickType.executeEsqlQuery:
next(
messages.concat({
'@timestamp': new Date().toISOString(),
message: {
role: MessageRole.Assistant,
content: '',
function_call: {
name: 'execute_query',
arguments: JSON.stringify({
query: payload.query,
}),
trigger: MessageRole.User,
<div ref={timelineContainerRef} className={fullHeightClassName}>
<EuiPanel
grow
hasBorder={false}
hasShadow={false}
paddingSize="m"
className={animClassName}
>
{connectors.connectors?.length === 0 || messages.length === 1 ? (
<WelcomeMessage connectors={connectors} knowledgeBase={knowledgeBase} />
) : (
<ChatTimeline
startedFrom={startedFrom}
messages={messages}
knowledgeBase={knowledgeBase}
chatService={chatService}
currentUser={currentUser}
chatState={state}
hasConnector={!!connectors.connectors?.length}
onEdit={(editedMessage, newMessage) => {
const indexOf = messages.indexOf(editedMessage);
next(messages.slice(0, indexOf).concat(newMessage));
}}
onFeedback={handleFeedback}
onRegenerate={(message) => {
const indexOf = messages.indexOf(message);
next(messages.slice(0, indexOf));
}}
onStopGenerating={() => {
stop();
}}
onActionClick={(payload) => {
setStickToBottom(true);
switch (payload.type) {
case ChatActionClickType.executeEsqlQuery:
next(
messages.concat({
'@timestamp': new Date().toISOString(),
message: {
role: MessageRole.Assistant,
content: '',
function_call: {
name: 'execute_query',
arguments: JSON.stringify({
query: payload.query,
}),
trigger: MessageRole.User,
},
},
},
})
);
break;
}
}}
/>
})
);
break;
}
}}
/>
)}
</EuiPanel>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexItem
grow={false}
className={promptEditorClassname}
style={{
height: !connectors.loading && connectors.connectors?.length !== 0 ? 110 : 0,
}}
>
<EuiHorizontalRule margin="none" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
<EuiPanel
hasBorder={false}
hasShadow={false}
paddingSize="m"
className={promptEditorContainerClassName}
>
<ChatPromptEditor
loading={isLoading}
disabled={!connectors.selectedConnector || !hasCorrectLicense}

View file

@ -39,11 +39,12 @@ export function ChatFlyout({
startedFrom: StartedFrom;
onClose: () => void;
}) {
const { euiTheme } = useEuiTheme();
const {
services: { http },
} = useKibana();
const { euiTheme } = useEuiTheme();
const currentUser = useCurrentUser();
const connectors = useGenAIConnectors();
@ -54,6 +55,12 @@ export function ChatFlyout({
const [conversationId, setConversationId] = useState<string | undefined>(undefined);
const conversationsHeaderClassName = css`
padding-top: 12px;
padding-bottom: 12px;
border-bottom: solid 1px ${euiTheme.border.color};
`;
return isOpen ? (
<EuiFlyout onClose={onClose}>
<EuiFlexGroup
@ -67,7 +74,7 @@ export function ChatFlyout({
hasShadow={false}
hasBorder={false}
borderRadius="none"
css={{ borderBottom: `solid 1px ${euiTheme.border.color}` }}
className={conversationsHeaderClassName}
>
{conversationId ? (
<EuiLink

View file

@ -28,6 +28,11 @@ const minWidthClassName = css`
min-width: 0;
`;
const chatHeaderClassName = css`
padding-top: 12px;
padding-bottom: 12px;
`;
export function ChatHeader({
title,
loading,
@ -68,7 +73,13 @@ export function ChatHeader({
const inputRef = useRef<HTMLInputElement>(null);
return (
<EuiPanel paddingSize="m" hasBorder={false} hasShadow={false} borderRadius="none">
<EuiPanel
borderRadius="none"
hasBorder={false}
hasShadow={false}
paddingSize="m"
className={chatHeaderClassName}
>
<EuiFlexGroup gutterSize="m" responsive={false} alignItems="center">
<EuiFlexItem grow={false}>
{loading ? <EuiLoadingSpinner size="l" /> : <AssistantAvatar size="s" />}

View file

@ -9,19 +9,18 @@ import React, { ReactNode, useMemo } from 'react';
import { css } from '@emotion/css';
import { EuiCommentList } from '@elastic/eui';
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
import { ChatItem } from './chat_item';
import { ChatWelcomePanel } from './chat_welcome_panel';
import { ChatConsolidatedItems } from './chat_consolidated_items';
import type { Feedback } from '../feedback_buttons';
import { type Message } from '../../../common';
import type { Message } from '../../../common';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import type { ChatActionClickHandler } from './types';
import type { ObservabilityAIAssistantChatService } from '../../types';
import { ChatItem } from './chat_item';
import { ChatConsolidatedItems } from './chat_consolidated_items';
import { ChatState } from '../../hooks/use_chat';
import {
getTimelineItemsfromConversation,
StartedFrom,
} from '../../utils/get_timeline_items_from_conversation';
import { ObservabilityAIAssistantChatService } from '../../types';
import { ChatState } from '../../hooks/use_chat';
export interface ChatTimelineItem
extends Pick<Message['message'], 'role' | 'content' | 'function_call'> {
@ -107,44 +106,40 @@ export function ChatTimeline({
return (
<EuiCommentList
css={css`
className={css`
padding-bottom: 32px;
`}
>
{items.length <= 1 ? (
<ChatWelcomePanel knowledgeBase={knowledgeBase} />
) : (
items.map((item, index) => {
return Array.isArray(item) ? (
<ChatConsolidatedItems
key={index}
consolidatedItem={item}
onFeedback={onFeedback}
onRegenerate={onRegenerate}
onEditSubmit={onEdit}
onStopGenerating={onStopGenerating}
onActionClick={onActionClick}
/>
) : (
<ChatItem
// use index, not id to prevent unmounting of component when message is persisted
key={index}
{...item}
onFeedbackClick={(feedback) => {
onFeedback(item.message, feedback);
}}
onRegenerateClick={() => {
onRegenerate(item.message);
}}
onEditSubmit={(message) => {
onEdit(item.message, message);
}}
onStopGeneratingClick={onStopGenerating}
onActionClick={onActionClick}
/>
);
})
)}
{items.map((item, index) => {
return Array.isArray(item) ? (
<ChatConsolidatedItems
key={index}
consolidatedItem={item}
onFeedback={onFeedback}
onRegenerate={onRegenerate}
onEditSubmit={onEdit}
onStopGenerating={onStopGenerating}
onActionClick={onActionClick}
/>
) : (
<ChatItem
// use index, not id to prevent unmounting of component when message is persisted
key={index}
{...item}
onFeedbackClick={(feedback) => {
onFeedback(item.message, feedback);
}}
onRegenerateClick={() => {
onRegenerate(item.message);
}}
onEditSubmit={(message) => {
onEdit(item.message, message);
}}
onStopGeneratingClick={onStopGenerating}
onActionClick={onActionClick}
/>
);
})}
</EuiCommentList>
);
}

View file

@ -1,52 +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 { EuiButton, EuiFlexGroup, EuiPanel } from '@elastic/eui';
import { css } from '@emotion/css';
import { i18n } from '@kbn/i18n';
import { euiThemeVars } from '@kbn/ui-theme';
import { Disclaimer } from './disclaimer';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
const incorrectLicenseContainer = css`
height: 100%;
padding: ${euiThemeVars.euiPanelPaddingModifiers.paddingMedium};
`;
export function ChatWelcomePanel({ knowledgeBase }: { knowledgeBase: UseKnowledgeBaseResult }) {
return (
<EuiPanel hasBorder={false} hasShadow={false}>
<EuiFlexGroup
direction="column"
alignItems="center"
justifyContent="center"
className={incorrectLicenseContainer}
>
<Disclaimer />
{!knowledgeBase.status.value?.ready ? (
<EuiButton
data-test-subj="observabilityAiAssistantChatWelcomePanelSetUpKnowledgeBaseButton"
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

@ -6,27 +6,21 @@
*/
import React from 'react';
import { EuiImage, EuiText, EuiTitle } from '@elastic/eui';
import { EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import ctaImage from '../../assets/elastic_ai_assistant.png';
export function Disclaimer() {
return (
<>
<EuiImage src={ctaImage} alt="Elastic AI Assistant" size="m" />
<EuiTitle>
<h2>
{i18n.translate('xpack.observabilityAiAssistant.disclaimer.title', {
defaultMessage: 'Welcome to the Elastic AI Assistant for Observability',
})}
</h2>
</EuiTitle>
<EuiText color="subdued" textAlign="center">
{i18n.translate('xpack.observabilityAiAssistant.disclaimer.thisChatIsPoweredTextLabel', {
defaultMessage:
'This chat is powered by an integration with your LLM provider. LLMs are known to produce hallucinations. Elastic supports the configuration and connection to the LLM provider and to your Knowledge base, but is not responsible for the LLM responses.',
})}
</EuiText>
</>
<EuiText
color="subdued"
size="xs"
textAlign="center"
data-test-subj="observabilityAiAssistantDisclaimer"
>
{i18n.translate('xpack.observabilityAiAssistant.disclaimer.disclaimerLabel', {
defaultMessage:
"This chat is powered by an integration with your LLM provider. LLMs are known to sometimes present incorrect information as if it's correct. Elastic supports configuration and connection to the LLM provider and your knowledge base, but is not responsible for the LLM's responses.",
})}
</EuiText>
);
}

View file

@ -1,175 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useState } from 'react';
import {
EuiBetaBadge,
EuiButton,
EuiCard,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiPanel,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public';
import { ConnectorSelectorBase } from '../connector_selector/connector_selector_base';
import { Disclaimer } from './disclaimer';
import { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
import { useKibana } from '../../hooks/use_kibana';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
export function InitialSetupPanel({
connectors,
startedFrom,
}: {
connectors: UseGenAIConnectorsResult;
connectorsManagementHref: string;
knowledgeBase: UseKnowledgeBaseResult;
startedFrom?: StartedFrom;
}) {
const [connectorFlyoutOpen, setConnectorFlyoutOpen] = useState(false);
const {
application: { navigateToApp, capabilities },
triggersActionsUi: { getAddConnectorFlyout: ConnectorFlyout },
} = useKibana().services;
const handleConnectorClick = () => {
if (capabilities.management?.insightsAndAlerting?.triggersActions) {
setConnectorFlyoutOpen(true);
} else {
navigateToApp('management', {
path: '/insightsAndAlerting/triggersActionsConnectors/connectors',
});
}
};
const onConnectorCreated = (createdConnector: ActionConnector) => {
setConnectorFlyoutOpen(false);
if (createdConnector.actionTypeId === '.gen-ai') {
connectors.reloadConnectors();
}
};
return (
<>
<Disclaimer />
<EuiPanel paddingSize="m" style={{ overflowY: 'auto' }}>
<EuiSpacer size="s" />
<EuiText color="subdued" size="s">
{i18n.translate('xpack.observabilityAiAssistant.initialSetupPanel.title', {
defaultMessage: 'Start your Al experience with Elastic by completing the steps below.',
})}
</EuiText>
<EuiSpacer size="l" />
<EuiFlexGroup direction={startedFrom === 'conversationView' ? 'row' : 'column'}>
<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">
{i18n.translate(
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description1',
{
defaultMessage: 'Set up an OpenAI connector with your AI provider.',
}
)}
</EuiText>
<EuiText size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2',
{
defaultMessage:
'The OpenAI 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"
/>
</EuiText>
</>
) : connectors.connectors.length && !connectors.selectedConnector ? (
<EuiText size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description',
{
defaultMessage: 'Please select a provider.',
}
)}
</EuiText>
) : undefined
}
footer={
!connectors.connectors?.length ? (
<EuiButton
data-test-subj="observabilityAiAssistantInitialSetupPanelSetUpGenerativeAiConnectorButton"
fill
color="primary"
onClick={handleConnectorClick}
>
{i18n.translate(
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel',
{
defaultMessage: 'Set up OpenAI connector',
}
)}
</EuiButton>
) : connectors.connectors.length && !connectors.selectedConnector ? (
<ConnectorSelectorBase {...connectors} />
) : null
}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="xxl" />
<EuiText color="subdued" size="s">
{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.',
})}
</EuiText>
</EuiPanel>
{connectorFlyoutOpen ? (
<ConnectorFlyout
onClose={() => setConnectorFlyoutOpen(false)}
onConnectorCreated={onConnectorCreated}
/>
) : null}
</>
);
}

View file

@ -0,0 +1,355 @@
/*
* 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 { act, fireEvent, render as rtlRender } from '@testing-library/react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { waitForEuiPopoverClose, waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
import { WelcomeMessage } from './welcome_message';
import { useKibana } from '../../hooks/use_kibana';
import type {
MlDeploymentAllocationState,
MlDeploymentState,
} from '@elastic/elasticsearch/lib/api/types';
jest.mock('../../hooks/use_kibana');
const useKibanaMock = useKibana as jest.Mock;
const navigateToApp = jest.fn();
const reloadConnectors = jest.fn();
const selectConnector = jest.fn();
const install = jest.fn().mockResolvedValue(undefined);
const refresh = jest.fn();
const baseKnowledgeBase = {
install,
};
const emptyKnowledgeBase = {
...baseKnowledgeBase,
status: { value: { ready: false }, loading: false, refresh },
isInstalling: false,
installError: undefined,
};
const installingKnowledgeBase = {
...baseKnowledgeBase,
status: { value: { ready: false }, loading: false, refresh },
isInstalling: true,
installError: undefined,
};
const loadingKnowledgeBase = {
...baseKnowledgeBase,
status: { value: { ready: false }, loading: true, refresh },
isInstalling: false,
installError: undefined,
};
const installedKnowledgeBase = {
...baseKnowledgeBase,
status: {
value: {
ready: true,
model_name: 'foo',
deployment_state: 'started' as MlDeploymentState,
allocation_state: 'allocated' as MlDeploymentAllocationState,
},
loading: false,
refresh,
},
isInstalling: false,
installError: undefined,
};
const baseConnectors = {
reloadConnectors,
selectConnector,
loading: false,
};
const emptyConnectors = {
...baseConnectors,
connectors: [],
};
const filledConnectors = {
...baseConnectors,
connectors: [
{
id: 'test',
actionTypeId: 'test',
name: 'test',
referencedByCount: 0,
isPreconfigured: false,
isDeprecated: false,
isSystemAction: false,
},
],
};
const render = (component: React.ReactElement) => {
return rtlRender(<IntlProvider locale="en">{component}</IntlProvider>);
};
describe('Welcome Message', () => {
beforeEach(() => {
useKibanaMock.mockReturnValue({
services: {
application: { navigateToApp, capabilities: {} },
http: { basePath: { prepend: jest.fn((path: string) => `/${path}`) } },
triggersActionsUi: {
getAddConnectorFlyout: () => <button data-test-subj="connectorFlyout">hello</button>,
},
},
});
});
describe('when no connectors are available', () => {
it('should show a disclaimer', () => {
const { getByTestId } = render(
<WelcomeMessage connectors={emptyConnectors} knowledgeBase={emptyKnowledgeBase} />
);
expect(getByTestId('observabilityAiAssistantDisclaimer')).toBeInTheDocument();
});
it('should show a set up connector button', () => {
const { getByTestId } = render(
<WelcomeMessage connectors={emptyConnectors} knowledgeBase={emptyKnowledgeBase} />
);
expect(
getByTestId('observabilityAiAssistantInitialSetupPanelSetUpGenerativeAiConnectorButton')
).toBeInTheDocument();
});
describe('when no triggersactionsUi capabilities are available', () => {
it('should navigate to stack management', () => {
const { getByTestId } = render(
<WelcomeMessage connectors={emptyConnectors} knowledgeBase={emptyKnowledgeBase} />
);
fireEvent.click(
getByTestId('observabilityAiAssistantInitialSetupPanelSetUpGenerativeAiConnectorButton')
);
expect(navigateToApp).toHaveBeenCalledWith('management', {
path: '/insightsAndAlerting/triggersActionsConnectors/connectors',
});
});
});
describe('when triggersactionsUi capabilities are available', () => {
beforeEach(() => {
useKibanaMock.mockReturnValue({
services: {
application: {
navigateToApp,
capabilities: {
management: {
insightsAndAlerting: {
triggersActions: true,
},
},
},
},
triggersActionsUi: {
getAddConnectorFlyout: () => <button data-test-subj="connectorFlyout">hello</button>,
},
},
});
});
it('should render a connector flyout when clicking the set up connector button', () => {
const { getByTestId } = render(
<WelcomeMessage connectors={emptyConnectors} knowledgeBase={emptyKnowledgeBase} />
);
fireEvent.click(
getByTestId('observabilityAiAssistantInitialSetupPanelSetUpGenerativeAiConnectorButton')
);
expect(getByTestId('connectorFlyout')).toBeInTheDocument();
});
describe('when creating a new connector', () => {
// it('should call reloadConnectors and install knowledge base', () => {
// const { getByTestId } = render(
// <WelcomeMessage connectors={emptyConnectors} knowledgeBase={emptyKnowledgeBase} />
// );
// fireEvent.click(
// getByTestId('observabilityAiAssistantInitialSetupPanelSetUpGenerativeAiConnectorButton')
// );
// fireEvent.click(getByTestId('connectorFlyout'));
// expect(reloadConnectors).toHaveBeenCalled();
// expect(install).toHaveBeenCalled();
// });
// it('should install the knowledge base', () => {
// const { getByTestId } = render(
// <WelcomeMessage connectors={emptyConnectors} knowledgeBase={emptyKnowledgeBase} />
// );
// fireEvent.click(
// getByTestId('observabilityAiAssistantInitialSetupPanelSetUpGenerativeAiConnectorButton')
// );
// fireEvent.click(getByTestId('connectorFlyout'));
// expect(install).toHaveBeenCalled();
// });
});
});
});
describe('when connectors are available', () => {
it('should show a disclaimer', () => {
const { getByTestId } = render(
<WelcomeMessage connectors={filledConnectors} knowledgeBase={emptyKnowledgeBase} />
);
expect(getByTestId('observabilityAiAssistantDisclaimer')).toBeInTheDocument();
});
describe('when knowledge base is not installed', () => {
it('should render the retry and inspect errors buttons', () => {
const { getByTestId } = render(
<WelcomeMessage connectors={filledConnectors} knowledgeBase={emptyKnowledgeBase} />
);
expect(
getByTestId('observabilityAiAssistantWelcomeMessageSetUpKnowledgeBaseButton')
).toBeInTheDocument();
expect(
getByTestId('observabilityAiAssistantWelcomeMessageInspectErrorsButton')
).toBeInTheDocument();
});
it('should call kb install when clicking retry', async () => {
const { getByTestId } = render(
<WelcomeMessage connectors={filledConnectors} knowledgeBase={emptyKnowledgeBase} />
);
await act(async () => {
fireEvent.click(
getByTestId('observabilityAiAssistantWelcomeMessageSetUpKnowledgeBaseButton')
);
expect(install).toBeCalled();
});
});
it('should render a popover with installation errors when clicking inspect', async () => {
const { getByTestId } = render(
<WelcomeMessage connectors={filledConnectors} knowledgeBase={emptyKnowledgeBase} />
);
fireEvent.click(getByTestId('observabilityAiAssistantWelcomeMessageInspectErrorsButton'));
await waitForEuiPopoverOpen();
expect(
getByTestId('observabilityAiAssistantWelcomeMessageKnowledgeBaseSetupErrorPanel')
).toBeInTheDocument();
fireEvent.click(
getByTestId(
'observabilityAiAssistantWelcomeMessageKnowledgeBaseSetupErrorPanelRetryInstallingLink'
)
);
await waitForEuiPopoverClose();
expect(install).toBeCalled();
});
it('should navigate to ML when clicking the link in the error popover', async () => {
const { getByTestId } = render(
<WelcomeMessage connectors={filledConnectors} knowledgeBase={emptyKnowledgeBase} />
);
fireEvent.click(getByTestId('observabilityAiAssistantWelcomeMessageInspectErrorsButton'));
await waitForEuiPopoverOpen();
const link = getByTestId('observabilityAiAssistantWelcomeMessageTrainedModelsLink');
expect(link).toHaveProperty('href', 'http://app/ml/trained_models');
});
});
});
describe('when knowledge base is installing', () => {
it('should not show a failure message', () => {
const { queryByTestId } = render(
<WelcomeMessage connectors={filledConnectors} knowledgeBase={installingKnowledgeBase} />
);
expect(
queryByTestId('observabilityAiAssistantWelcomeMessageSetUpKnowledgeBaseButton')
).not.toBeInTheDocument();
expect(
queryByTestId('observabilityAiAssistantWelcomeMessageInspectErrorsButton')
).not.toBeInTheDocument();
});
it('should show a disclaimer', () => {
const { getByTestId } = render(
<WelcomeMessage connectors={emptyConnectors} knowledgeBase={emptyKnowledgeBase} />
);
expect(getByTestId('observabilityAiAssistantDisclaimer')).toBeInTheDocument();
});
});
describe('when knowledge base is loading', () => {
it('should not show a failure message', () => {
const { queryByTestId } = render(
<WelcomeMessage connectors={filledConnectors} knowledgeBase={loadingKnowledgeBase} />
);
expect(
queryByTestId('observabilityAiAssistantWelcomeMessageSetUpKnowledgeBaseButton')
).not.toBeInTheDocument();
expect(
queryByTestId('observabilityAiAssistantWelcomeMessageInspectErrorsButton')
).not.toBeInTheDocument();
});
it('should show a disclaimer', () => {
const { getByTestId } = render(
<WelcomeMessage connectors={emptyConnectors} knowledgeBase={emptyKnowledgeBase} />
);
expect(getByTestId('observabilityAiAssistantDisclaimer')).toBeInTheDocument();
});
});
describe('when knowledge base is installed', () => {
it('should not show a failure message', () => {
const { queryByTestId } = render(
<WelcomeMessage connectors={filledConnectors} knowledgeBase={installedKnowledgeBase} />
);
expect(
queryByTestId('observabilityAiAssistantWelcomeMessageSetUpKnowledgeBaseButton')
).not.toBeInTheDocument();
expect(
queryByTestId('observabilityAiAssistantWelcomeMessageInspectErrorsButton')
).not.toBeInTheDocument();
});
it('should show a disclaimer', () => {
const { getByTestId } = render(
<WelcomeMessage connectors={emptyConnectors} knowledgeBase={emptyKnowledgeBase} />
);
expect(getByTestId('observabilityAiAssistantDisclaimer')).toBeInTheDocument();
});
});
});

View file

@ -0,0 +1,127 @@
/*
* 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 { css } from '@emotion/css';
import {
EuiFlexGroup,
EuiFlexItem,
EuiImage,
EuiSpacer,
EuiTitle,
useCurrentEuiBreakpoint,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
import ctaImage from '../../assets/elastic_ai_assistant.png';
import { Disclaimer } from './disclaimer';
import { WelcomeMessageConnectors } from './welcome_message_connectors';
import { WelcomeMessageKnowledgeBase } from './welcome_message_knowledge_base';
import { useKibana } from '../../hooks/use_kibana';
const fullHeightClassName = css`
height: 100%;
`;
const centerMaxWidthClassName = css`
max-width: 600px;
text-align: center;
`;
export function WelcomeMessage({
connectors,
knowledgeBase,
}: {
connectors: UseGenAIConnectorsResult;
knowledgeBase: UseKnowledgeBaseResult;
}) {
const breakpoint = useCurrentEuiBreakpoint();
const {
application: { navigateToApp, capabilities },
triggersActionsUi: { getAddConnectorFlyout: ConnectorFlyout },
} = useKibana().services;
const [connectorFlyoutOpen, setConnectorFlyoutOpen] = useState(false);
const handleConnectorClick = () => {
if (capabilities.management?.insightsAndAlerting?.triggersActions) {
setConnectorFlyoutOpen(true);
} else {
navigateToApp('management', {
path: '/insightsAndAlerting/triggersActionsConnectors/connectors',
});
}
};
const onConnectorCreated = (createdConnector: ActionConnector) => {
setConnectorFlyoutOpen(false);
if (createdConnector.actionTypeId === '.gen-ai') {
connectors.reloadConnectors();
}
if (!knowledgeBase.status.value || knowledgeBase.status.value?.ready === false) {
knowledgeBase.install();
}
};
return (
<>
<EuiFlexGroup
alignItems="center"
direction="column"
gutterSize="none"
className={fullHeightClassName}
>
<EuiFlexItem grow className={centerMaxWidthClassName}>
<EuiSpacer size={breakpoint === 'xl' ? 'l' : 'l' ? 'l' : 's'} />
<EuiImage
src={ctaImage}
alt="Elastic AI Assistant"
size={breakpoint === 'xl' ? 300 : 'm'}
/>
<EuiSpacer size="m" />
<EuiTitle size={breakpoint === 'xl' ? 'm' : 'l' ? 'm' : 's'}>
<h2>
{i18n.translate('xpack.observabilityAiAssistant.disclaimer.title', {
defaultMessage: 'Welcome to the AI Assistant for Observability',
})}
</h2>
</EuiTitle>
<EuiSpacer size="m" />
<WelcomeMessageConnectors
connectors={connectors}
onSetupConnectorClick={handleConnectorClick}
/>
<WelcomeMessageKnowledgeBase connectors={connectors} knowledgeBase={knowledgeBase} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSpacer size="m" />
<Disclaimer />
</EuiFlexItem>
</EuiFlexGroup>
{connectorFlyoutOpen ? (
<ConnectorFlyout
featureId="generativeAI"
onConnectorCreated={onConnectorCreated}
onClose={() => setConnectorFlyoutOpen(false)}
/>
) : null}
</>
);
}

View file

@ -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 from 'react';
import { css, keyframes } from '@emotion/css';
import { EuiBetaBadge, EuiButton, EuiSpacer, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { euiThemeVars } from '@kbn/ui-theme';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
const fadeInAnimation = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
const fadeInClassName = css`
animation: ${fadeInAnimation} ${euiThemeVars.euiAnimSpeedNormal} ease-in-out;
`;
export function WelcomeMessageConnectors({
connectors,
onSetupConnectorClick,
}: {
connectors: UseGenAIConnectorsResult;
onSetupConnectorClick?: () => void;
}) {
return !connectors.loading && connectors.connectors?.length === 0 && onSetupConnectorClick ? (
<div className={fadeInClassName}>
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2',
{
defaultMessage:
'Start working with the Elastic AI Assistant by setting up a connector for your AI provider. The OpenAI model needs to support function calls. We strongly recommend using GPT4.',
}
)}
<EuiBetaBadge
label=""
css={{ boxShadow: 'none', inlineSize: 'unset', lineHeight: 'initial' }}
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"
/>
</EuiText>
<EuiSpacer size="m" />
<div>
<EuiButton
data-test-subj="observabilityAiAssistantInitialSetupPanelSetUpGenerativeAiConnectorButton"
fill
color="primary"
onClick={onSetupConnectorClick}
>
{i18n.translate(
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel',
{
defaultMessage: 'Set up GenAI connector',
}
)}
</EuiButton>
</div>
</div>
) : null;
}

View file

@ -0,0 +1,193 @@
/*
* 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, { useEffect, useState } from 'react';
import { noop } from 'lodash';
import { i18n } from '@kbn/i18n';
import {
EuiButton,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiPopover,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import usePrevious from 'react-use/lib/usePrevious';
import useTimeoutFn from 'react-use/lib/useTimeoutFn';
import useInterval from 'react-use/lib/useInterval';
import { WelcomeMessageKnowledgeBaseSetupErrorPanel } from './welcome_message_knowledge_base_setup_error_panel';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
export function WelcomeMessageKnowledgeBase({
connectors,
knowledgeBase,
}: {
connectors: UseGenAIConnectorsResult;
knowledgeBase: UseKnowledgeBaseResult;
}) {
const previouslyNotInstalled = usePrevious(knowledgeBase.status.value?.ready === false);
const [showHasBeenInstalled, setShowHasBeenInstalled] = useState(false);
const [timeoutTime, setTimeoutTime] = useState(0);
const [, , reset] = useTimeoutFn(() => setShowHasBeenInstalled(false), timeoutTime);
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const handleClosePopover = () => setIsPopoverOpen(false);
const [checkForInstallStatus, setCheckForInstallStatus] = useState(false);
// When the knowledge base is installed, show a success message for 3 seconds
useEffect(() => {
if (previouslyNotInstalled && knowledgeBase.status.value?.ready) {
setTimeoutTime(3000);
reset();
setShowHasBeenInstalled(true);
}
}, [knowledgeBase.status.value?.ready, previouslyNotInstalled, reset]);
// When the knowledge base is installed, stop checking for install status
useEffect(() => {
if (!checkForInstallStatus && knowledgeBase.status.value?.ready) {
setCheckForInstallStatus(false);
}
}, [checkForInstallStatus, knowledgeBase.status.value?.ready]);
// Check for install status every 5 seconds
useInterval(
() => {
knowledgeBase.status.refresh();
},
checkForInstallStatus ? 5000 : null
);
const handleRetryInstall = async () => {
setCheckForInstallStatus(true);
setIsPopoverOpen(false);
await knowledgeBase.install().then(() => {
setCheckForInstallStatus(false);
});
};
return knowledgeBase.status.value?.ready !== undefined ? (
<>
{knowledgeBase.isInstalling ? (
<>
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.weAreSettingUpTextLabel',
{
defaultMessage:
'We are setting up your knowledge base. This may take a few minutes. You can continue to use the Assistant while this process is underway.',
}
)}
</EuiText>
<EuiSpacer size="m" />
<EuiButtonEmpty
data-test-subj="observabilityAiAssistantWelcomeMessageSettingUpKnowledgeBaseButton"
isLoading
onClick={noop}
>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel',
{ defaultMessage: 'Setting up Knowledge base' }
)}
</EuiButtonEmpty>
</>
) : null}
{connectors.connectors?.length ? (
(!knowledgeBase.isInstalling && knowledgeBase.installError) ||
(!knowledgeBase.isInstalling &&
knowledgeBase.status.loading === false &&
knowledgeBase.status.value.ready === false) ? (
<>
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel',
{ defaultMessage: 'Your Knowledge base is not set up correctly' }
)}
</EuiText>
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false}>
<div>
<EuiButton
color="primary"
data-test-subj="observabilityAiAssistantWelcomeMessageSetUpKnowledgeBaseButton"
fill
isLoading={checkForInstallStatus}
iconType="refresh"
onClick={handleRetryInstall}
>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.retryButtonLabel',
{
defaultMessage: 'Retry install',
}
)}
</EuiButton>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
button={
<EuiButtonEmpty
data-test-subj="observabilityAiAssistantWelcomeMessageInspectErrorsButton"
iconType="inspect"
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel',
{ defaultMessage: 'Inspect issues' }
)}
</EuiButtonEmpty>
}
isOpen={isPopoverOpen}
panelPaddingSize="none"
closePopover={handleClosePopover}
>
<WelcomeMessageKnowledgeBaseSetupErrorPanel
knowledgeBase={knowledgeBase}
onRetryInstall={handleRetryInstall}
/>
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
</>
) : null
) : null}
{showHasBeenInstalled ? (
<div>
<EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="center">
<EuiFlexItem grow={false}>
<EuiIcon type="checkInCircleFilled" color="success" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel',
{ defaultMessage: 'Knowledge base successfully installed' }
)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</div>
) : null}
</>
) : null;
}

View file

@ -0,0 +1,151 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiCode,
EuiDescriptionList,
EuiDescriptionListTitle,
EuiDescriptionListDescription,
EuiLink,
EuiSpacer,
EuiIcon,
EuiText,
EuiHorizontalRule,
EuiPanel,
} from '@elastic/eui';
import { css } from '@emotion/css';
import { useKibana } from '../../hooks/use_kibana';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
const panelContainerClassName = css`
width: 330px;
`;
export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
knowledgeBase,
onRetryInstall,
}: {
knowledgeBase: UseKnowledgeBaseResult;
onRetryInstall: () => void;
}) {
const { http } = useKibana().services;
const modelName = knowledgeBase.status.value?.model_name;
return (
<div
className={panelContainerClassName}
data-test-subj="observabilityAiAssistantWelcomeMessageKnowledgeBaseSetupErrorPanel"
>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
<EuiDescriptionList>
<EuiDescriptionListTitle>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.issuesDescriptionListTitleLabel',
{ defaultMessage: 'Issues' }
)}
</EuiDescriptionListTitle>
<EuiSpacer size="s" />
<EuiDescriptionListDescription>
<ul>
{!knowledgeBase.status.value?.deployment_state ? (
<li>
<EuiIcon type="alert" color="subdued" />{' '}
<FormattedMessage
id="xpack.observabilityAiAssistant.welcomeMessage.modelIsNotDeployedLabel"
defaultMessage="Model {modelName} is not deployed"
values={{
modelName: <EuiCode>{modelName}</EuiCode>,
}}
/>
</li>
) : null}
{knowledgeBase.status.value?.deployment_state &&
knowledgeBase.status.value.deployment_state !== 'started' ? (
<li>
<EuiIcon type="alert" color="subdued" />{' '}
<FormattedMessage
id="xpack.observabilityAiAssistant.welcomeMessage.modelIsNotStartedLabel"
defaultMessage="Deployment state of {modelName} is {deploymentState}"
values={{
modelName: <EuiCode>{modelName}</EuiCode>,
deploymentState: (
<EuiCode>{knowledgeBase.status.value?.deployment_state}</EuiCode>
),
}}
/>
</li>
) : null}
{knowledgeBase.status.value?.allocation_state &&
knowledgeBase.status.value.allocation_state !== 'fully_allocated' ? (
<li>
<EuiIcon type="alert" color="subdued" />{' '}
<FormattedMessage
id="xpack.observabilityAiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel"
defaultMessage="Allocation state of {modelName} is {allocationState}"
values={{
modelName: <EuiCode>{modelName}</EuiCode>,
allocationState: (
<EuiCode>{knowledgeBase.status.value?.allocation_state}</EuiCode>
),
}}
/>
</li>
) : null}
</ul>
</EuiDescriptionListDescription>
</EuiDescriptionList>
</EuiPanel>
<EuiHorizontalRule margin="none" />
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
<EuiText color="subdued" size="xs">
<FormattedMessage
id="xpack.observabilityAiAssistant.welcomeMessage.div.checkTrainedModelsToLabel"
defaultMessage="
{retryInstallingLink} or check {trainedModelsLink} to ensure {modelName} is deployed and running."
values={{
modelName,
retryInstallingLink: (
<EuiLink
data-test-subj="observabilityAiAssistantWelcomeMessageKnowledgeBaseSetupErrorPanelRetryInstallingLink"
onClick={onRetryInstall}
>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel',
{ defaultMessage: 'Retry install' }
)}
</EuiLink>
),
trainedModelsLink: (
<EuiLink
data-test-subj="observabilityAiAssistantWelcomeMessageTrainedModelsLink"
external
href={http.basePath.prepend('/app/ml/trained_models')}
target="_blank"
>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.trainedModelsLinkLabel',
{ defaultMessage: 'Trained Models' }
)}
</EuiLink>
),
}}
/>
</EuiText>
</EuiPanel>
</div>
);
}

View file

@ -5,6 +5,10 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type {
MlDeploymentAllocationState,
MlDeploymentState,
} from '@elastic/elasticsearch/lib/api/types';
import { useMemo, useState } from 'react';
import { AbortableAsyncState, useAbortableAsync } from './use_abortable_async';
import { useKibana } from './use_kibana';
@ -14,8 +18,9 @@ export interface UseKnowledgeBaseResult {
status: AbortableAsyncState<{
ready: boolean;
error?: any;
deployment_state?: string;
allocation_state?: string;
deployment_state?: MlDeploymentState;
allocation_state?: MlDeploymentAllocationState;
model_name?: string;
}>;
isInstalling: boolean;
installError?: Error;
@ -50,15 +55,6 @@ export function useKnowledgeBase(): UseKnowledgeBaseResult {
})
.then(() => {
status.refresh();
toasts.addSuccess({
title: i18n.translate('xpack.observabilityAiAssistant.knowledgeBaseReadyTitle', {
defaultMessage: 'Knowledge base is ready',
}),
text: i18n.translate('xpack.observabilityAiAssistant.knowledgeBaseReadyContentReload', {
defaultMessage: 'A page reload is needed to be able to use it.',
}),
toastLifeTimeMs: Number.MAX_VALUE,
});
})
.catch((error) => {
if (

View file

@ -4,6 +4,11 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type {
MlDeploymentAllocationState,
MlDeploymentState,
} from '@elastic/elasticsearch/lib/api/types';
import { notImplemented } from '@hapi/boom';
import { nonEmptyStringRt, toBooleanRt } from '@kbn/io-ts-utils';
import * as t from 'io-ts';
@ -20,8 +25,9 @@ const getKnowledgeBaseStatus = createObservabilityAIAssistantServerRoute({
): Promise<{
ready: boolean;
error?: any;
deployment_state?: string;
allocation_state?: string;
deployment_state?: MlDeploymentState;
allocation_state?: MlDeploymentAllocationState;
model_name?: string;
}> => {
const client = await resources.service.getClient({ request: resources.request });

View file

@ -275,15 +275,18 @@ export class KnowledgeBaseService {
const elserModelStats = modelStats.trained_model_stats[0];
const deploymentState = elserModelStats.deployment_stats?.state;
const allocationState = elserModelStats.deployment_stats?.allocation_status.state;
return {
ready: deploymentState === 'started' && allocationState === 'fully_allocated',
deployment_state: deploymentState,
allocation_state: allocationState,
model_name: ELSER_MODEL_ID,
};
} catch (error) {
return {
error: error instanceof errors.ResponseError ? error.body.error : String(error),
ready: false,
model_name: ELSER_MODEL_ID,
};
}
};

View file

@ -29629,7 +29629,6 @@
"xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label": "Assistant d'Elastic",
"xpack.observabilityAiAssistant.chatTimeline.messages.system.label": "Système",
"xpack.observabilityAiAssistant.chatTimeline.messages.user.label": "Vous",
"xpack.observabilityAiAssistant.chatWelcomePanel.knowledgeBase.buttonLabel.notInstalledYet": "Configurer la base de connaissances",
"xpack.observabilityAiAssistant.checkingKbAvailability": "Vérification de la disponibilité de la base de connaissances",
"xpack.observabilityAiAssistant.confirmDeleteButtonText": "Supprimer la conversation",
"xpack.observabilityAiAssistant.confirmDeleteConversationContent": "Cette action ne peut pas être annulée.",
@ -29662,13 +29661,8 @@
"xpack.observabilityAiAssistant.incorrectLicense.manageLicense": "Gérer la licence",
"xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton": "Plans d'abonnement",
"xpack.observabilityAiAssistant.incorrectLicense.title": "Mettez votre licence à niveau",
"xpack.observabilityAiAssistant.initialSetupPanel.disclaimer": "Le fournisseur d'intelligence artificielle configuré peut collecter des données télémétriques lors de l'utilisation de l'assistant d'intelligence artificielle d'Elastic. Contactez votre fournisseur d'intelligence artificielle pour obtenir des informations sur le mode de collecte des données.",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel": "Configurez un connecteur OpenAI",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description": "Veuillez sélectionner un fournisseur.",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description1": "Configurez un connecteur OpenAI avec votre fournisseur d'intelligence artificielle.",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2": "Le modèle OpenAi doit être compatible avec l'appel de fonctions. Nous recommandons fortement d'utiliser GPT4.",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.title": "Configuration du connecteur",
"xpack.observabilityAiAssistant.initialSetupPanel.title": "Embarquez aux côtés de l'intelligence artificielle grâce à Elastic une fois les étapes ci-dessous effectuées.",
"xpack.observabilityAiAssistant.insight.actions": "Actions",
"xpack.observabilityAiAssistant.insight.defaultDescription": "Obtenez des informations utiles grâce à l'assistant d'intelligence artificielle d'Elastic.",
"xpack.observabilityAiAssistant.insight.error.buttonLabel": "Régénérer",
@ -29679,8 +29673,6 @@
"xpack.observabilityAiAssistant.insight.feedbackButtons.title": "Cela a-t-il été utile ?",
"xpack.observabilityAiAssistant.insight.response.startChat": "Lancer le chat",
"xpack.observabilityAiAssistant.installingKb": "Configuration de la base de connaissances",
"xpack.observabilityAiAssistant.knowledgeBaseReadyContentReload": "Il faut recharger la page pour pouvoir l'utiliser.",
"xpack.observabilityAiAssistant.knowledgeBaseReadyTitle": "La base de connaissances est prête",
"xpack.observabilityAiAssistant.lensFunction.openInLens": "Ouvrir dans Lens",
"xpack.observabilityAiAssistant.lensFunction.save": "Enregistrer",
"xpack.observabilityAiAssistant.missingCredentialsCallout.buttonLabel": "Connecter l'assistant",

View file

@ -29629,7 +29629,6 @@
"xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic Assistant",
"xpack.observabilityAiAssistant.chatTimeline.messages.system.label": "システム",
"xpack.observabilityAiAssistant.chatTimeline.messages.user.label": "あなた",
"xpack.observabilityAiAssistant.chatWelcomePanel.knowledgeBase.buttonLabel.notInstalledYet": "ナレッジベースをセットアップ",
"xpack.observabilityAiAssistant.checkingKbAvailability": "ナレッジベースの利用可能性を確認中",
"xpack.observabilityAiAssistant.confirmDeleteButtonText": "会話を削除",
"xpack.observabilityAiAssistant.confirmDeleteConversationContent": "この操作は元に戻すことができません。",
@ -29662,13 +29661,8 @@
"xpack.observabilityAiAssistant.incorrectLicense.manageLicense": "ライセンスの管理",
"xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton": "サブスクリプションオプション",
"xpack.observabilityAiAssistant.incorrectLicense.title": "ライセンスをアップグレード",
"xpack.observabilityAiAssistant.initialSetupPanel.disclaimer": "構成されたAIプロバイダーは、Elastic AI Assistantの使用時にテレメトリを収集することがあります。データの収集方法については、AIプロバイダーにお問い合わせください。",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel": "OpenAIコネクターをセットアップ",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description": "プロバイダーを選択してください。",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description1": "AIプロバイダーとOpenAIコネクターを設定します。",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2": "OpenAIモデルは関数呼び出しをサポートしている必要があります。GPT4の使用を強くお勧めします。",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.title": "コネクターセットアップ",
"xpack.observabilityAiAssistant.initialSetupPanel.title": "以下のステップを完了することで、ElasticでAIエクスペリエンスを開始できます。",
"xpack.observabilityAiAssistant.insight.actions": "アクション",
"xpack.observabilityAiAssistant.insight.defaultDescription": "Elastic AI Assistantから役立つインサイトを得ることができます。",
"xpack.observabilityAiAssistant.insight.error.buttonLabel": "再生成",
@ -29679,8 +29673,6 @@
"xpack.observabilityAiAssistant.insight.feedbackButtons.title": "役に立ちましたか。",
"xpack.observabilityAiAssistant.insight.response.startChat": "チャットを開始",
"xpack.observabilityAiAssistant.installingKb": "ナレッジベースをセットアップ中",
"xpack.observabilityAiAssistant.knowledgeBaseReadyContentReload": "使用するにはページの再読み込みが必要です。",
"xpack.observabilityAiAssistant.knowledgeBaseReadyTitle": "ナレッジベースを使用できます",
"xpack.observabilityAiAssistant.lensFunction.openInLens": "Lensで開く",
"xpack.observabilityAiAssistant.lensFunction.save": "保存",
"xpack.observabilityAiAssistant.missingCredentialsCallout.buttonLabel": "アシスタントを接続",

View file

@ -29626,7 +29626,6 @@
"xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic 助手",
"xpack.observabilityAiAssistant.chatTimeline.messages.system.label": "系统",
"xpack.observabilityAiAssistant.chatTimeline.messages.user.label": "您",
"xpack.observabilityAiAssistant.chatWelcomePanel.knowledgeBase.buttonLabel.notInstalledYet": "设置知识库",
"xpack.observabilityAiAssistant.checkingKbAvailability": "正在检查知识库的可用性",
"xpack.observabilityAiAssistant.confirmDeleteButtonText": "删除对话",
"xpack.observabilityAiAssistant.confirmDeleteConversationContent": "此操作无法撤消。",
@ -29659,13 +29658,8 @@
"xpack.observabilityAiAssistant.incorrectLicense.manageLicense": "管理许可证",
"xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton": "订阅计划",
"xpack.observabilityAiAssistant.incorrectLicense.title": "升级您的许可证",
"xpack.observabilityAiAssistant.initialSetupPanel.disclaimer": "配置的 AI 提供商可能会在使用 Elastic AI 助手时收集遥测数据。请联系您的 AI 提供商了解有关如何收集数据的信息。",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel": "设置 OpenAI 连接器",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description": "请选择提供商。",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description1": "通过 AI 提供商设置 OpenAI 连接器。",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2": "OpenAI 模型需要支持函数调用。强烈建议使用 GPT4。",
"xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.title": "连接器设置",
"xpack.observabilityAiAssistant.initialSetupPanel.title": "通过完成以下步骤,利用 Elastic 开始您的 AI 体验。",
"xpack.observabilityAiAssistant.insight.actions": "操作",
"xpack.observabilityAiAssistant.insight.defaultDescription": "从 Elastic AI 助手获取有用的洞见。",
"xpack.observabilityAiAssistant.insight.error.buttonLabel": "重新生成",
@ -29676,8 +29670,6 @@
"xpack.observabilityAiAssistant.insight.feedbackButtons.title": "这是否有帮助?",
"xpack.observabilityAiAssistant.insight.response.startChat": "开始聊天",
"xpack.observabilityAiAssistant.installingKb": "正在设置知识库",
"xpack.observabilityAiAssistant.knowledgeBaseReadyContentReload": "需要重新加载页面才能使用它。",
"xpack.observabilityAiAssistant.knowledgeBaseReadyTitle": "知识库已准备就绪",
"xpack.observabilityAiAssistant.lensFunction.openInLens": "在 Lens 中打开",
"xpack.observabilityAiAssistant.lensFunction.save": "保存",
"xpack.observabilityAiAssistant.missingCredentialsCallout.buttonLabel": "连接助手",