Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -87,31 +83,24 @@ 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`
min-width: 0;
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({
Expand Down Expand Up @@ -462,33 +451,23 @@ export function ChatBody({
}

let footer: React.ReactNode;

if (!hasCorrectLicense && !initialConversationId) {
footer = (
<>
<EuiFlexItem grow className={incorrectLicenseContainer(euiTheme)}>
<IncorrectLicensePanel />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiHorizontalRule margin="none" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
<PromptEditor
hidden={connectors.loading || connectors.connectors?.length === 0}
loading={isLoading}
disabled
onChangeHeight={setPromptEditorHeight}
onSubmit={(message) => {
next(messages.concat(message));
}}
onSendTelemetry={(eventWithPayload) =>
chatService.sendAnalyticsEvent(eventWithPayload)
}
/>
<EuiSpacer size="s" />
</EuiPanel>
</EuiFlexItem>
</>
<EuiFlexItem
grow
css={css`
justify-content: center;
`}
>
<IncorrectLicensePanel />
</EuiFlexItem>
);
} else if (connectors.loading) {
footer = (
<div className={loadingClassname}>
<AssistantBeacon size="xxl" backgroundColor="backgroundBasePlain" />
</div>
);
} else if (!conversation.value && conversation.loading) {
footer = null;
Expand All @@ -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 ? (
<WelcomeMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export function ChatFlyout({
refreshConversations={refreshConversations}
updateDisplayedConversation={updateDisplayedConversation}
/>
) : (
) : connectors.connectors?.length ? (
<EuiPopover
anchorPosition="downLeft"
button={
Expand All @@ -271,7 +271,7 @@ export function ChatFlyout({
}
className={newChatButtonClassName}
/>
)}
) : null}
</EuiFlexItem>
) : null}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export function ChatTimeline({
<EuiCommentList
className={css`
padding-bottom: 32px;
flex-grow: 1;
`}
>
{items.map((item, index) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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: [
Expand Down Expand Up @@ -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(<ConversationList {...defaultProps} />);
expect(screen.queryByTestId('observabilityAiAssistantNewChatButton')).not.toBeInTheDocument();
});

it('defaults to archived section open if selected conversation is archived', () => {
const archivedConversation = {
...mockConversations.value!.conversations[0],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 ?? [];
Expand Down Expand Up @@ -325,18 +331,20 @@ export function ConversationList({
</>
) : null}

<EuiFlexItem grow={false}>
<EuiPanel paddingSize="s" hasBorder={false} hasShadow={false}>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow className={newChatButtonWrapperClassName}>
<NewChatButton
href={newConversationHref}
onClick={(event) => onClickConversation(event)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
{connectors?.length ? (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don’t we allow navigating to a new conversation when there are no connectors?

Currently, if there is no connector, we show a button to add one directly from the AI Assistant, without requiring navigation to the connector settings page. However, if a user previously had a connector, started a conversation, and then deleted the connector, they can still see the list of past conversations.

In this scenario, we should allow the user to navigate back to the “New Conversation” page from the “Add Connector” button.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be raised with @isaclfreire and others involved in the unified design. The designs make no allowances for "New Conversation" to be available.

<EuiFlexItem grow={false}>
<EuiPanel paddingSize="s" hasBorder={false} hasShadow={false}>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow className={newChatButtonWrapperClassName}>
<NewChatButton
href={newConversationHref}
onClick={(event) => onClickConversation(event)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</EuiPanel>
{confirmDeleteElement}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<EuiPanel hasBorder hasShadow={false}>
<EuiFlexGroup
direction="column"
alignItems="center"
justifyContent="center"
className={incorrectLicenseContainer}
>
<EuiImage src={elasticAiAssistantImage} alt="Elastic AI Assistant" size="m" />
<EuiTitle>
<h2>{UPGRADE_LICENSE_TITLE}</h2>
</EuiTitle>
<EuiText color="subdued">
{i18n.translate('xpack.aiAssistant.incorrectLicense.body', {
defaultMessage: 'You need an Enterprise license to use the Elastic AI Assistant.',
})}
</EuiText>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
data-test-subj="observabilityAiAssistantIncorrectLicensePanelSubscriptionPlansButton"
fill
href="https://www.elastic.co/subscriptions"
target="_blank"
>
{i18n.translate('xpack.aiAssistant.incorrectLicense.subscriptionPlansButton', {
defaultMessage: 'Subscription plans',
})}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
<EuiButtonEmpty
data-test-subj="observabilityAiAssistantIncorrectLicensePanelManageLicenseButton"
onClick={handleNavigateToLicenseManagement}
>
{i18n.translate('xpack.aiAssistant.incorrectLicense.manageLicense', {
defaultMessage: 'Manage license',
})}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
return <NeedLicenseUpgrade onManageLicense={onManageLicense} />;
}
Loading