diff --git a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_body.tsx b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_body.tsx index 137f1166fb1fd..04c0fe89b3bbd 100644 --- a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_body.tsx +++ b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_body.tsx @@ -18,7 +18,7 @@ import { useEuiTheme, UseEuiTheme, } from '@elastic/eui'; -import { css, keyframes } from '@emotion/css'; +import { css } from '@emotion/css'; import { i18n } from '@kbn/i18n'; import type { Conversation, @@ -39,6 +39,7 @@ import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import { findLastIndex } from 'lodash'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { ChatFeedback } from '@kbn/observability-ai-assistant-plugin/public/analytics/schemas/chat_feedback'; +import { AssistantBeacon } from '@kbn/ai-assistant-icon'; import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base'; import { ASSISTANT_SETUP_TITLE, EMPTY_CONVERSATION_TITLE, UPGRADE_LICENSE_TITLE } from '../i18n'; import { useAIAssistantChatService } from '../hooks/use_ai_assistant_chat_service'; @@ -72,11 +73,6 @@ const promptEditorClassname = (euiTheme: UseEuiTheme['euiTheme']) => css` } `; -const incorrectLicenseContainer = (euiTheme: UseEuiTheme['euiTheme']) => css` - height: 100%; - padding: ${euiTheme.size.base}; -`; - const chatBodyContainerClassNameWithError = css` align-self: center; margin: 12px; @@ -87,24 +83,9 @@ const promptEditorContainerClassName = css` padding-bottom: 8px; `; -const fadeInAnimation = keyframes` - from { - opacity: 0; - transform: scale(0.9); - } - to { - opacity: 1; - transform: scale(1); - } -`; - -const animClassName = (euiTheme: UseEuiTheme['euiTheme']) => css` +const panelClassName = css` + display: flex; height: 100%; - opacity: 0; - ${euiCanAnimate} { - animation: ${fadeInAnimation} ${euiTheme.animation.normal} ${euiTheme.animation.bounce} - ${euiTheme.animation.normal} forwards; - } `; const containerClassName = css` @@ -112,6 +93,14 @@ const containerClassName = css` max-height: 100%; `; +const loadingClassname = css` + height: 100%; + width: 100%; + display: flex; + align-items: center; + justify-content: center; +}`; + const PADDING_AND_BORDER = 32; export function ChatBody({ @@ -462,33 +451,23 @@ export function ChatBody({ } let footer: React.ReactNode; + if (!hasCorrectLicense && !initialConversationId) { footer = ( - <> - - - - - - - - - - - + + + + ); + } else if (connectors.loading) { + footer = ( +
+ +
); } else if (!conversation.value && conversation.loading) { footer = null; @@ -502,7 +481,7 @@ export function ChatBody({ hasBorder={false} hasShadow={false} paddingSize="m" - className={animClassName(euiTheme)} + className={panelClassName} > {connectors.connectors?.length === 0 || messages.length === 0 ? ( - ) : ( + ) : connectors.connectors?.length ? ( - )} + ) : null} ) : null} diff --git a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_timeline.tsx b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_timeline.tsx index 119a57e69f337..83ceb403fa9d7 100644 --- a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_timeline.tsx +++ b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_timeline.tsx @@ -134,6 +134,7 @@ export function ChatTimeline({ {items.map((item, index) => { diff --git a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/conversation_list.test.tsx b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/conversation_list.test.tsx index d82d994e1742a..468bd1f99a16a 100644 --- a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/conversation_list.test.tsx +++ b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/conversation_list.test.tsx @@ -11,7 +11,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { DATE_CATEGORY_LABELS } from '../i18n'; import { ConversationList } from './conversation_list'; import { UseConversationListResult } from '../hooks/use_conversation_list'; -import { useConversationsByDate, useConversationContextMenu } from '../hooks'; +import { useConversationsByDate, useConversationContextMenu, useGenAIConnectors } from '../hooks'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import { getDisplayedConversation } from '../hooks/use_conversations_by_date.test'; @@ -32,6 +32,12 @@ jest.mock('../hooks/use_conversation_context_menu', () => ({ }), })); +jest.mock('../hooks/use_genai_connectors', () => ({ + useGenAIConnectors: jest.fn().mockReturnValue({ + connectors: ['connector_1', 'connector_2'], + }), +})); + const mockConversations: UseConversationListResult['conversations'] = { value: { conversations: [ @@ -239,6 +245,12 @@ describe('ConversationList', () => { expect(defaultProps.onConversationSelect).toHaveBeenCalledWith(undefined); }); + it('does not render a new chat button if no connectors are configured', () => { + (useGenAIConnectors as jest.Mock).mockReturnValue({ connectors: [] }); + render(); + expect(screen.queryByTestId('observabilityAiAssistantNewChatButton')).not.toBeInTheDocument(); + }); + it('defaults to archived section open if selected conversation is archived', () => { const archivedConversation = { ...mockConversations.value!.conversations[0], diff --git a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/conversation_list.tsx b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/conversation_list.tsx index 9b64b038e94d8..fe536409af234 100644 --- a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/conversation_list.tsx +++ b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/conversation_list.tsx @@ -25,7 +25,12 @@ import { i18n } from '@kbn/i18n'; import React, { MouseEvent, useEffect, useMemo, useState } from 'react'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import type { UseConversationListResult } from '../hooks/use_conversation_list'; -import { useConfirmModal, useConversationsByDate, useConversationContextMenu } from '../hooks'; +import { + useConfirmModal, + useConversationsByDate, + useConversationContextMenu, + useGenAIConnectors, +} from '../hooks'; import { DATE_CATEGORY_LABELS } from '../i18n'; import { NewChatButton } from '../buttons/new_chat_button'; import { ConversationListItemLabel } from './conversation_list_item_label'; @@ -88,6 +93,7 @@ export function ConversationList({ }) { const euiTheme = useEuiTheme(); const scrollBarStyles = euiScrollBarStyles(euiTheme); + const { connectors } = useGenAIConnectors(); const [allConversations, activeConversations, archivedConversations] = useMemo(() => { const conversationList = conversations.value?.conversations ?? []; @@ -325,18 +331,20 @@ export function ConversationList({ ) : null} - - - - - onClickConversation(event)} - /> - - - - + {connectors?.length ? ( + + + + + onClickConversation(event)} + /> + + + + + ) : null} {confirmDeleteElement} diff --git a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/incorrect_license_panel.tsx b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/incorrect_license_panel.tsx index 136f8d5918fb7..c3df76d3be743 100644 --- a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/incorrect_license_panel.tsx +++ b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/incorrect_license_panel.tsx @@ -6,76 +6,18 @@ */ import React from 'react'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiImage, - EuiPanel, - EuiText, - EuiTitle, - useEuiTheme, -} from '@elastic/eui'; -import { css } from '@emotion/css'; -import { i18n } from '@kbn/i18n'; -import { elasticAiAssistantImage } from '@kbn/observability-ai-assistant-plugin/public'; -import { UPGRADE_LICENSE_TITLE } from '../i18n'; +import { NeedLicenseUpgrade } from '@kbn/ai-assistant-cta'; import { useLicenseManagementLocator } from '../hooks/use_license_management_locator'; +// TODO - onManageLicense does not work in serverless. export function IncorrectLicensePanel() { - const handleNavigateToLicenseManagement = useLicenseManagementLocator(); - const { euiTheme } = useEuiTheme(); + const handler = useLicenseManagementLocator(); - const incorrectLicenseContainer = css` - height: 100%; - padding: ${euiTheme.size.base}; - `; + const onManageLicense = () => { + if (handler) { + handler(); + } + }; - return ( - - - - -

{UPGRADE_LICENSE_TITLE}

-
- - {i18n.translate('xpack.aiAssistant.incorrectLicense.body', { - defaultMessage: 'You need an Enterprise license to use the Elastic AI Assistant.', - })} - - - - - - {i18n.translate('xpack.aiAssistant.incorrectLicense.subscriptionPlansButton', { - defaultMessage: 'Subscription plans', - })} - - - - - {i18n.translate('xpack.aiAssistant.incorrectLicense.manageLicense', { - defaultMessage: 'Manage license', - })} - - - - -
-
- ); + return ; } diff --git a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/welcome_message.tsx b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/welcome_message.tsx index 5876269d8e03a..a7131f1123ce4 100644 --- a/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/welcome_message.tsx +++ b/x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/welcome_message.tsx @@ -7,29 +7,30 @@ import React, { useMemo, useState } from 'react'; import { css } from '@emotion/css'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, useCurrentEuiBreakpoint } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { GenerativeAIForObservabilityConnectorFeatureId } from '@kbn/actions-plugin/common'; import { isSupportedConnectorType } from '@kbn/inference-common'; -import { AssistantBeacon } from '@kbn/ai-assistant-icon'; import { KnowledgeBaseState } from '@kbn/observability-ai-assistant-plugin/public'; + +import { + AddConnector, + ReadyToHelp, + ReadyToHelpProps, + NoConnectorAccess, +} from '@kbn/ai-assistant-cta'; + import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base'; import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors'; import { Disclaimer } from './disclaimer'; -import { WelcomeMessageConnectors } from './welcome_message_connectors'; -import { WelcomeMessageKnowledgeBase } from './welcome_message_knowledge_base'; import { StarterPrompts } from './starter_prompts'; import { useKibana } from '../hooks/use_kibana'; +import { WelcomeMessageKnowledgeBase } from './welcome_message_knowledge_base'; const fullHeightClassName = css` height: 100%; `; -const centerMaxWidthClassName = css` - max-width: 600px; - text-align: center; -`; - export function WelcomeMessage({ connectors, knowledgeBase, @@ -39,9 +40,10 @@ export function WelcomeMessage({ knowledgeBase: UseKnowledgeBaseResult; onSelectPrompt: (prompt: string) => void; }) { - const breakpoint = useCurrentEuiBreakpoint(); - - const { application, triggersActionsUi } = useKibana().services; + const { application, triggersActionsUi, observabilityAIAssistant } = useKibana().services; + const { + service: { getScopes }, + } = observabilityAIAssistant; const [connectorFlyoutOpen, setConnectorFlyoutOpen] = useState(false); @@ -61,13 +63,6 @@ export function WelcomeMessage({ if (isSupportedConnectorType(createdConnector.actionTypeId)) { connectors.reloadConnectors(); } - - if ( - !knowledgeBase.status.value || - knowledgeBase.status.value?.kbState === KnowledgeBaseState.NOT_INSTALLED - ) { - knowledgeBase.install(); - } }; const ConnectorFlyout = useMemo( @@ -75,32 +70,78 @@ export function WelcomeMessage({ [triggersActionsUi] ); + const isKnowledgeBaseReady = useMemo( + () => + knowledgeBase.status.value?.kbState === KnowledgeBaseState.READY && + !knowledgeBase.isInstalling && + !knowledgeBase.status.value?.errorMessage, + [knowledgeBase] + ); + + const isConnectorReady = useMemo( + () => !connectors.error && connectors.connectors?.length && connectors.connectors.length > 0, + [connectors] + ); + + const CallToAction = () => { + if (!isConnectorReady) { + if (connectors.error) { + return ; + } + return ; + } + + if (knowledgeBase.isInstalling || !isKnowledgeBaseReady || knowledgeBase.isPolling) { + return ; + } + + const scopes = getScopes(); + + let type: ReadyToHelpProps['type'] = 'stack'; + + if (scopes.length === 1) { + if (scopes[0] === 'observability') { + type = 'oblt'; + } + if (scopes[0] === 'search') { + type = 'search'; + } + } + return ; + }; + + const Footer = () => { + if (!isConnectorReady || !isKnowledgeBaseReady) { + return null; + } + + return ( + + + + + + ); + }; + return ( <> - - - - - - - {knowledgeBase.status.value?.enabled && connectors.connectors?.length ? ( - - ) : null} - - - - - + + +