Skip to content

Commit

Permalink
Merge pull request elastic#23 from CoenWarmer/storybook
Browse files Browse the repository at this point in the history
  • Loading branch information
CoenWarmer authored Aug 7, 2023
2 parents c06351f + 4f1d5cc commit bcab778
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 117 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/observability_ai_assistant/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface Message {
trigger: MessageRole.Assistant | MessageRole.User | MessageRole.Elastic;
};
data?: string;
isAssistantSetupMessage?: boolean;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import { ComponentStory } from '@storybook/react';
import React from 'react';
import { Observable } from 'rxjs';
import { MessageRole } from '../../../common';
import { getSystemMessage } from '../../service/get_system_message';
import { getAssistantSetupMessage } from '../../service/get_assistant_setup_message';
import { ObservabilityAIAssistantService } from '../../types';
import { MessageRole } from '../../../common';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { ChatBody as Component } from './chat_body';

Expand All @@ -33,7 +33,7 @@ const Template: ComponentStory<typeof Component> = (props: ChatBodyProps) => {
const defaultProps: ChatBodyProps = {
title: 'My Conversation',
messages: [
getSystemMessage(),
getAssistantSetupMessage(),
{
'@timestamp': new Date().toISOString(),
message: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { ChatTimeline } from './chat_timeline';
const containerClassName = css`
max-height: 100%;
max-width: 100%;
height: 1px;
`;

const timelineClassName = css`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { ComponentStory } from '@storybook/react';
import React from 'react';
import { getSystemMessage } from '../../service/get_system_message';
import { getAssistantSetupMessage } from '../../service/get_assistant_setup_message';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { ChatFlyout as Component } from './chat_flyout';

Expand All @@ -30,7 +30,7 @@ const Template: ComponentStory<typeof Component> = (props: ChatFlyoutProps) => {
const defaultProps: ChatFlyoutProps = {
isOpen: true,
title: 'How is this working',
messages: [getSystemMessage()],
messages: [getAssistantSetupMessage()],
onClose: () => {},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@
*/

import {
EuiAccordion,
EuiButtonIcon,
EuiComment,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiErrorBoundary,
EuiFlexGroup,
EuiErrorBoundary,
EuiFlexItem,
EuiPopover,
EuiSpacer,
EuiText,
useGeneratedHtmlId,
} from '@elastic/eui';
import { css } from '@emotion/css';
import { i18n } from '@kbn/i18n';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { MessageRole } from '../../../common/types';
import { Feedback, FeedbackButtons } from '../feedback_buttons';
import { MessagePanel } from '../message_panel/message_panel';
import { MessageText } from '../message_panel/message_text';
import { RegenerateResponseButton } from '../buttons/regenerate_response_button';
import { StopGeneratingButton } from '../buttons/stop_generating_button';
Expand All @@ -31,7 +32,7 @@ import { ChatTimelineItem } from './chat_timeline';
export interface ChatItemAction {
id: string;
label: string;
icon?: string;
icon: string;
handler: () => void;
}

Expand All @@ -52,11 +53,30 @@ const euiCommentClassName = css`
}
`;

const expandableMessageClassName = css`
.euiCommentEvent__header {
background: transparent;
border-block-end: none;
}
.euiCommentEvent {
border: none;
}
`;

const accordionButtonClassName = css`
.euiAccordion__iconButton {
display: none;
}
`;

export function ChatItem({
title,
content,
element,
canCopy,
canEdit,
canExpand,
canGiveFeedback,
canRegenerate,
role,
Expand All @@ -68,28 +88,77 @@ export function ChatItem({
onStopGeneratingClick,
onFeedbackClick,
}: ChatItemProps) {
const [isActionsPopoverOpen, setIsActionsPopover] = useState(false);
const accordionId = useGeneratedHtmlId({ prefix: 'chat' });

const handleClickActions = () => {
setIsActionsPopover(!isActionsPopoverOpen);
const [isPopoverOpen, setIsPopoverOpen] = useState<string | undefined>();
const [isAccordionOpen, setIsAccordionOpen] = useState<boolean>(false);

const handleAccordionToggle = () => {
setIsAccordionOpen(!isAccordionOpen);
};

const [_, setEditing] = useState(false);

const actions: ChatItemAction[] = canEdit
? [
{
id: 'edit',
label: i18n.translate('xpack.observabilityAiAssistant.chatTimeline.actions.editMessage', {
defaultMessage: 'Edit message',
}),
handler: () => {
setEditing(false);
setIsActionsPopover(false);
useEffect(() => {
const timeout = setTimeout(() => {
if (isPopoverOpen) {
setIsPopoverOpen(undefined);
}
}, 800);

return () => {
clearTimeout(timeout);
};
}, [isPopoverOpen]);

const actions: ChatItemAction[] = [
...(canEdit
? [
{
id: 'edit',
icon: 'documentEdit',
label: i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.actions.editMessage',
{
defaultMessage: 'Edit message',
}
),
handler: () => {
onEditSubmit(content || '');
},
},
]
: []),
...(canExpand
? [
{
id: 'expand',
icon: isAccordionOpen ? 'eyeClosed' : 'eye',
label: i18n.translate('xpack.observabilityAiAssistant.chatTimeline.actions.inspect', {
defaultMessage: 'Inspect message',
}),
handler: () => {
handleAccordionToggle();
},
},
},
]
: [];
]
: []),
...(canCopy
? [
{
id: 'copy',
icon: 'copyClipboard',
label: i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.actions.copyMessage',
{
defaultMessage: 'Copied message',
}
),
handler: () => {
navigator.clipboard.writeText(content || '');
},
},
]
: []),
];

let controls: React.ReactNode;

Expand Down Expand Up @@ -118,60 +187,77 @@ export function ChatItem({
if (!element) {
element =
content || error || controls ? (
<MessagePanel
body={
content || loading ? <MessageText content={content || ''} loading={loading} /> : null
}
error={error}
controls={controls}
/>
canExpand ? (
<EuiAccordion
id={accordionId}
className={accordionButtonClassName}
forceState={isAccordionOpen ? 'open' : 'closed'}
onToggle={handleAccordionToggle}
>
<EuiSpacer size="s" />
<MessageText content={content || ''} loading={loading} />
</EuiAccordion>
) : (
<MessageText content={content || ''} loading={loading} />
)
) : null;
} else {
element = <EuiErrorBoundary>{element}</EuiErrorBoundary>;
element = (
<EuiAccordion
id={accordionId}
className={accordionButtonClassName}
forceState={isAccordionOpen ? 'open' : 'closed'}
onToggle={handleAccordionToggle}
>
<EuiSpacer size="s" />
<EuiErrorBoundary>{element}</EuiErrorBoundary>
</EuiAccordion>
);
}

return (
<EuiComment
event={
<ChatItemTitle
actionsTrigger={
actions.length ? (
actions={actions.map(({ id, icon, label, handler }) =>
label ? (
<EuiPopover
anchorPosition="downLeft"
button={
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.actions',
{
defaultMessage: 'Actions',
}
)}
aria-label={label}
key={id}
iconType={icon}
onClick={() => {
setIsPopoverOpen(id);
handler();
}}
color="text"
display="empty"
iconType="boxesHorizontal"
size="s"
onClick={handleClickActions}
/>
}
isOpen={isPopoverOpen === id}
closePopover={() => setIsPopoverOpen(undefined)}
panelPaddingSize="s"
closePopover={handleClickActions}
isOpen={isActionsPopoverOpen}
>
<EuiContextMenuPanel
size="s"
items={actions.map(({ id, icon, label, handler }) => (
<EuiContextMenuItem key={id} icon={icon} onClick={handler}>
{label}
</EuiContextMenuItem>
))}
/>
<EuiText size="s">
<p>{label}</p>
</EuiText>
</EuiPopover>
) : null
}
) : (
<EuiButtonIcon
aria-label={label}
key={id}
iconType={icon}
onClick={handler}
color="text"
/>
)
)}
title={title}
/>
}
className={euiCommentClassName}
className={
role === MessageRole.User && canExpand ? expandableMessageClassName : euiCommentClassName
}
timelineAvatar={
<ChatItemAvatar loading={loading && !content} currentUser={currentUser} role={role} />
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ import { euiThemeVars } from '@kbn/ui-theme';
import React, { ReactNode } from 'react';

interface ChatItemTitleProps {
actionsTrigger?: ReactNode;
actions?: ReactNode;
title: string;
}

export function ChatItemTitle({ actionsTrigger, title }: ChatItemTitleProps) {
export function ChatItemTitle({ actions, title }: ChatItemTitleProps) {
return (
<>
{title}
{actionsTrigger ? (
<div css={{ position: 'absolute', top: 2, right: euiThemeVars.euiSizeS }}>
{actionsTrigger}
</div>
{actions ? (
<div css={{ position: 'absolute', top: 4, right: euiThemeVars.euiSizeS }}>{actions}</div>
) : null}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ export interface ChatPromptEditorProps {
onSubmit: (message: Message) => Promise<void>;
}

export function ChatPromptEditor({ onSubmit, disabled, loading }: ChatPromptEditorProps) {
export function ChatPromptEditor({ disabled, loading, onSubmit }: ChatPromptEditorProps) {
const { getFunctions } = useObservabilityAIAssistant();
const functions = getFunctions();

const [prompt, setPrompt] = useState('');

const [functionPayload, setFunctionPayload] = useState<string | undefined>('');
const [selectedFunction, setSelectedFunction] = useState<FunctionDefinition | undefined>();

Expand Down
Loading

0 comments on commit bcab778

Please sign in to comment.