Skip to content

Commit

Permalink
[Observability AI Assistant] Chat tweaks + keyboard shortcut (elastic…
Browse files Browse the repository at this point in the history
  • Loading branch information
CoenWarmer authored Feb 8, 2024
1 parent b1b36bd commit 52f35fc
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiHeaderLink, EuiLoadingSpinner } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ObservabilityAIAssistantChatServiceProvider } from '../../context/observability_ai_assistant_chat_service_provider';
Expand All @@ -30,6 +30,20 @@ export function ObservabilityAIAssistantActionMenuItem() {

const initialMessages = useMemo(() => [], []);

useEffect(() => {
const keyboardListener = (event: KeyboardEvent) => {
if (event.ctrlKey && event.code === 'Semicolon') {
setIsOpen(true);
}
};

window.addEventListener('keypress', keyboardListener);

return () => {
window.removeEventListener('keypress', keyboardListener);
};
}, []);

if (!service.isEnabled()) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonIcon, EuiContextMenu, EuiPanel, EuiPopover } from '@elastic/eui';
import { EuiButtonIcon, EuiContextMenu, EuiPanel, EuiPopover, EuiToolTip } from '@elastic/eui';
import { useKibana } from '../../hooks/use_kibana';
import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router';
import { getSettingsHref } from '../../utils/get_settings_href';
Expand Down Expand Up @@ -52,16 +52,24 @@ export function ChatActionsMenu({
<EuiPopover
isOpen={isOpen}
button={
<EuiButtonIcon
data-test-subj="observabilityAiAssistantChatActionsMenuButtonIcon"
disabled={disabled}
iconType="boxesVertical"
onClick={toggleActionsMenu}
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatActionsMenu.euiButtonIcon.menuLabel',
{ defaultMessage: 'Menu' }
<EuiToolTip
content={i18n.translate(
'xpack.observabilityAiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel',
{ defaultMessage: 'More actions' }
)}
/>
display="block"
>
<EuiButtonIcon
data-test-subj="observabilityAiAssistantChatActionsMenuButtonIcon"
disabled={disabled}
iconType="boxesVertical"
onClick={toggleActionsMenu}
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatActionsMenu.euiButtonIcon.menuLabel',
{ defaultMessage: 'Menu' }
)}
/>
</EuiToolTip>
}
panelPaddingSize="none"
closePopover={toggleActionsMenu}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
import { ASSISTANT_SETUP_TITLE, EMPTY_CONVERSATION_TITLE, UPGRADE_LICENSE_TITLE } from '../../i18n';
import type { StartedFrom } from '../../utils/get_timeline_items_from_conversation';
import { TELEMETRY, sendEvent } from '../../analytics';
import { FlyoutWidthMode } from './chat_flyout';

const fullHeightClassName = css`
height: 100%;
Expand Down Expand Up @@ -93,27 +94,31 @@ const animClassName = css`
const PADDING_AND_BORDER = 32;

export function ChatBody({
initialTitle,
initialMessages,
initialConversationId,
chatFlyoutSecondSlotHandler,
connectors,
knowledgeBase,
currentUser,
flyoutWidthMode,
initialConversationId,
initialMessages,
initialTitle,
knowledgeBase,
showLinkToConversationsApp,
startedFrom,
chatFlyoutSecondSlotHandler,
onConversationUpdate,
onToggleFlyoutWidthMode,
}: {
chatFlyoutSecondSlotHandler?: ChatFlyoutSecondSlotHandler;
connectors: UseGenAIConnectorsResult;
currentUser?: Pick<AuthenticatedUser, 'full_name' | 'username'>;
flyoutWidthMode?: FlyoutWidthMode;
initialTitle?: string;
initialMessages?: Message[];
initialConversationId?: string;
connectors: UseGenAIConnectorsResult;
knowledgeBase: UseKnowledgeBaseResult;
currentUser?: Pick<AuthenticatedUser, 'full_name' | 'username'>;
showLinkToConversationsApp: boolean;
startedFrom?: StartedFrom;
chatFlyoutSecondSlotHandler?: ChatFlyoutSecondSlotHandler;
onConversationUpdate: (conversation: { conversation: Conversation['conversation'] }) => void;
onToggleFlyoutWidthMode?: (flyoutWidthMode: FlyoutWidthMode) => void;
}) {
const license = useLicense();
const hasCorrectLicense = license?.hasAtLeast('enterprise');
Expand Down Expand Up @@ -455,6 +460,7 @@ export function ChatBody({
? conversation.value.conversation.id
: undefined
}
flyoutWidthMode={flyoutWidthMode}
licenseInvalid={!hasCorrectLicense && !initialConversationId}
loading={isLoading}
showLinkToConversationsApp={showLinkToConversationsApp}
Expand All @@ -463,6 +469,7 @@ export function ChatBody({
onSaveTitle={(newTitle) => {
saveTitle(newTitle);
}}
onToggleFlyoutWidthMode={onToggleFlyoutWidthMode}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ import ReactDOM from 'react-dom';
import { i18n } from '@kbn/i18n';
import { v4 } from 'uuid';
import { css } from '@emotion/css';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiFlyout, useEuiTheme } from '@elastic/eui';
import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiFlyout,
EuiPopover,
EuiToolTip,
useEuiTheme,
} from '@elastic/eui';
import { useForceUpdate } from '../../hooks/use_force_update';
import { useCurrentUser } from '../../hooks/use_current_user';
import { useGenAIConnectors } from '../../hooks/use_genai_connectors';
Expand All @@ -25,6 +33,8 @@ const CONVERSATIONS_SIDEBAR_WIDTH_COLLAPSED = 34;

const SIDEBAR_WIDTH = 400;

export type FlyoutWidthMode = 'side' | 'full';

export function ChatFlyout({
initialTitle,
initialMessages,
Expand All @@ -48,26 +58,36 @@ export function ChatFlyout({

const [conversationId, setConversationId] = useState<string | undefined>(undefined);

const [expanded, setExpanded] = useState(false);
const [flyoutWidthMode, setFlyoutWidthMode] = useState<FlyoutWidthMode>('side');

const [conversationsExpanded, setConversationsExpanded] = useState(false);

const [secondSlotContainer, setSecondSlotContainer] = useState<HTMLDivElement | null>(null);
const [isSecondSlotVisible, setIsSecondSlotVisible] = useState(false);

const sidebarClass = css`
max-width: ${expanded ? CONVERSATIONS_SIDEBAR_WIDTH : CONVERSATIONS_SIDEBAR_WIDTH_COLLAPSED}px;
min-width: ${expanded ? CONVERSATIONS_SIDEBAR_WIDTH : CONVERSATIONS_SIDEBAR_WIDTH_COLLAPSED}px;
max-width: ${conversationsExpanded
? CONVERSATIONS_SIDEBAR_WIDTH
: CONVERSATIONS_SIDEBAR_WIDTH_COLLAPSED}px;
min-width: ${conversationsExpanded
? CONVERSATIONS_SIDEBAR_WIDTH
: CONVERSATIONS_SIDEBAR_WIDTH_COLLAPSED}px;
border-right: solid 1px ${euiTheme.border.color};
`;

const expandButtonClassName = css`
const expandButtonContainerClassName = css`
position: absolute;
margin-top: 16px;
margin-left: ${expanded
margin-left: ${conversationsExpanded
? CONVERSATIONS_SIDEBAR_WIDTH - CONVERSATIONS_SIDEBAR_WIDTH_COLLAPSED
: 5}px;
padding: ${euiTheme.size.s};
z-index: 1;
`;

const expandButtonClassName = css`
color: ${euiTheme.colors.primary};
`;

const containerClassName = css`
height: 100%;
`;
Expand All @@ -79,10 +99,9 @@ export function ChatFlyout({
const newChatButtonClassName = css`
position: absolute;
bottom: 31px;
margin-left: ${expanded
margin-left: ${conversationsExpanded
? CONVERSATIONS_SIDEBAR_WIDTH - CONVERSATIONS_SIDEBAR_WIDTH_COLLAPSED
: 5}px;
padding: ${euiTheme.size.s};
z-index: 1;
`;

Expand Down Expand Up @@ -110,12 +129,20 @@ export function ChatFlyout({
}
};

const handleToggleFlyoutWidthMode = (newFlyoutWidthMode: FlyoutWidthMode) => {
setFlyoutWidthMode(newFlyoutWidthMode);
};

return isOpen ? (
<EuiFlyout
closeButtonProps={{
css: { marginRight: `${euiTheme.size.s}`, marginTop: `${euiTheme.size.s}` },
}}
size={getFlyoutWidth({ expanded, isSecondSlotVisible })}
size={getFlyoutWidth({
expanded: conversationsExpanded,
isSecondSlotVisible,
flyoutWidthMode,
})}
paddingSize="m"
onClose={() => {
onClose();
Expand All @@ -127,57 +154,93 @@ export function ChatFlyout({
>
<EuiFlexGroup gutterSize="none" className={containerClassName}>
<EuiFlexItem className={sidebarClass}>
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel',
{ defaultMessage: 'Expand conversation list' }
)}
className={expandButtonClassName}
color="text"
data-test-subj="observabilityAiAssistantChatFlyoutButton"
iconType={expanded ? 'transitionLeftIn' : 'transitionLeftOut'}
onClick={() => setExpanded(!expanded)}
<EuiPopover
anchorPosition="downLeft"
className={expandButtonContainerClassName}
button={
<EuiToolTip
content={
conversationsExpanded
? i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel',
{ defaultMessage: 'Collapse conversation list' }
)
: i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiToolTip.expandConversationListLabel',
{ defaultMessage: 'Expand conversation list' }
)
}
display="block"
>
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel',
{ defaultMessage: 'Expand conversation list' }
)}
className={expandButtonClassName}
color="text"
data-test-subj="observabilityAiAssistantChatFlyoutButton"
iconType={conversationsExpanded ? 'transitionLeftIn' : 'transitionLeftOut'}
onClick={() => setConversationsExpanded(!conversationsExpanded)}
/>
</EuiToolTip>
}
/>

{expanded ? (
{conversationsExpanded ? (
<ConversationList
selected={conversationId ?? ''}
onClickDeleteConversation={handleClickDeleteConversation}
onClickChat={handleClickChat}
onClickNewChat={handleClickNewChat}
/>
) : (
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.newChatLabel',
{ defaultMessage: 'New chat' }
)}
<EuiPopover
anchorPosition="downLeft"
button={
<EuiToolTip
content={i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiToolTip.newChatLabel',
{ defaultMessage: 'New chat' }
)}
display="block"
>
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.newChatLabel',
{ defaultMessage: 'New chat' }
)}
data-test-subj="observabilityAiAssistantNewChatFlyoutButton"
iconType="plusInCircle"
onClick={handleClickNewChat}
/>
</EuiToolTip>
}
className={newChatButtonClassName}
data-test-subj="observabilityAiAssistantNewChatFlyoutButton"
iconType="plusInCircle"
onClick={handleClickNewChat}
/>
)}
</EuiFlexItem>

<EuiFlexItem className={chatBodyContainerClassName}>
<ChatBody
key={chatBodyKeyRef.current}
chatFlyoutSecondSlotHandler={{
container: secondSlotContainer,
setVisibility: setIsSecondSlotVisible,
}}
connectors={connectors}
currentUser={currentUser}
flyoutWidthMode={flyoutWidthMode}
initialTitle={initialTitle}
initialMessages={initialMessages}
initialConversationId={conversationId}
currentUser={currentUser}
knowledgeBase={knowledgeBase}
showLinkToConversationsApp
startedFrom={startedFrom}
onConversationUpdate={(conversation) => {
setConversationId(conversation.conversation.id);
}}
chatFlyoutSecondSlotHandler={{
container: secondSlotContainer,
setVisibility: setIsSecondSlotVisible,
}}
showLinkToConversationsApp
onToggleFlyoutWidthMode={handleToggleFlyoutWidthMode}
/>
</EuiFlexItem>

Expand All @@ -204,10 +267,15 @@ export function ChatFlyout({
const getFlyoutWidth = ({
expanded,
isSecondSlotVisible,
flyoutWidthMode,
}: {
expanded: boolean;
isSecondSlotVisible: boolean;
flyoutWidthMode?: FlyoutWidthMode;
}) => {
if (flyoutWidthMode === 'full') {
return '100%';
}
if (!expanded && !isSecondSlotVisible) {
return '40vw';
}
Expand Down
Loading

0 comments on commit 52f35fc

Please sign in to comment.