Skip to content

Commit

Permalink
Merge pull request #9 from CoenWarmer/storybook
Browse files Browse the repository at this point in the history
  • Loading branch information
dgieselaar committed Jul 25, 2023
2 parents 807fd10 + d4d2ad3 commit d01bbef
Show file tree
Hide file tree
Showing 15 changed files with 490 additions and 22 deletions.
15 changes: 3 additions & 12 deletions x-pack/plugins/observability_ai_assistant/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,9 @@
"id": "observabilityAIAssistant",
"server": true,
"browser": true,
"configPath": [
"xpack",
"observabilityAIAssistant"
],
"requiredPlugins": [
"triggersActionsUi",
"actions",
"security"
],
"requiredBundles": [
"kibanaReact"
],
"configPath": ["xpack", "observabilityAIAssistant"],
"requiredPlugins": ["triggersActionsUi", "actions", "security"],
"requiredBundles": ["kibanaReact"],
"optionalPlugins": [],
"extraPublicDirs": []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import React, { ReactNode } from 'react';

export interface AssistantAvatarProps {
size: keyof typeof sizeMap;
size?: keyof typeof sizeMap;
children?: ReactNode;
}

export const sizeMap = {
Expand All @@ -18,7 +19,7 @@ export const sizeMap = {
xs: 16,
};

export function AssistantAvatar({ size }: AssistantAvatarProps) {
export function AssistantAvatar({ size = 's' }: AssistantAvatarProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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 { css } from '@emotion/react';
import { EuiText, EuiComment } from '@elastic/eui';
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
import { MessageRole, Message } from '../../../common/types';
import { ChatItemAvatar } from './chat_item_avatar';
import { ChatItemTitle } from './chat_item_title';
import { MessagePanel } from '../message_panel/message_panel';
import { FeedbackButtons, Feedback } from '../feedback_buttons';

const roleMap = {
[MessageRole.User]: i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.userLabel',
{ defaultMessage: 'You' }
),
[MessageRole.System]: i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.systemLabel',
{ defaultMessage: 'System' }
),
[MessageRole.Assistant]: i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.assistantLabel',
{ defaultMessage: 'Elastic Assistant' }
),
[MessageRole.Function]: i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.functionLabel',
{ defaultMessage: 'Elastic Assistant' }
),
[MessageRole.Event]: i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.functionLabel',
{ defaultMessage: 'Elastic Assistant' }
),
[MessageRole.Elastic]: i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.functionLabel',
{ defaultMessage: 'Elastic Assistant' }
),
};

export interface ChatItemProps {
currentUser: AuthenticatedUser | undefined;
dateFormat: string;
index: number;
message: Message;
onFeedbackClick: (feedback: Feedback) => void;
}

export function ChatItem({
currentUser,
dateFormat,
index,
message,
onFeedbackClick,
}: ChatItemProps) {
return (
<EuiComment
key={message['@timestamp']}
event={<ChatItemTitle message={message} index={index} dateFormat={dateFormat} />}
timelineAvatar={<ChatItemAvatar currentUser={currentUser} role={message.message.role} />}
username={roleMap[message.message.role]}
css={
message.message.role !== MessageRole.User
? css`
.euiCommentEvent__body {
padding: 0;
}
`
: ''
}
>
{message.message.content ? (
<>
{message.message.role === MessageRole.User ? (
<EuiText size="s">
<p>{message.message.content}</p>
</EuiText>
) : (
<MessagePanel
body={message.message.content}
controls={<FeedbackButtons onClickFeedback={onFeedbackClick} />}
/>
)}
</>
) : null}
</EuiComment>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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 { UserAvatar } from '@kbn/user-profile-components';
import { EuiAvatar, EuiLoadingSpinner } from '@elastic/eui';
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
import { AssistantAvatar } from '../assistant_avatar';
import { MessageRole } from '../../../common/types';

interface ChatAvatarProps {
currentUser?: AuthenticatedUser | undefined;
role: MessageRole;
}

export function ChatItemAvatar({ currentUser, role }: ChatAvatarProps) {
switch (role) {
case MessageRole.User:
return currentUser ? (
<UserAvatar user={currentUser} size="m" data-test-subj="userMenuAvatar" />
) : (
<EuiLoadingSpinner size="xl" />
);

case MessageRole.Assistant:
case MessageRole.Elastic:
case MessageRole.Function:
return <EuiAvatar name="Elastic Assistant" iconType={AssistantAvatar} color="subdued" />;

case MessageRole.System:
return <EuiAvatar name="system" iconType="dot" color="subdued" />;

default:
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 moment from 'moment';
import { i18n } from '@kbn/i18n';
import { Message, MessageRole } from '../../../common/types';

interface ChatItemTitleProps {
dateFormat: string;
index: number;
message: Message;
}

export function ChatItemTitle({ dateFormat, index, message }: ChatItemTitleProps) {
switch (message.message.role) {
case MessageRole.User:
if (index === 0) {
return i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.user.createdNewConversation',
{
defaultMessage: 'created a new conversation on {date}',
values: {
date: moment(message['@timestamp']).format(dateFormat),
},
}
);
} else {
return i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.user.addedPrompt',
{
defaultMessage: 'added a prompt on {date}',
values: {
date: moment(message['@timestamp']).format(dateFormat),
},
}
);
}

case MessageRole.Assistant:
case MessageRole.Elastic:
case MessageRole.Function:
return i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.responded',
{
defaultMessage: 'responded on {date}',
values: {
date: moment(message['@timestamp']).format(dateFormat),
},
}
);

case MessageRole.System:
return i18n.translate('xpack.observabilityAiAssistant.chatTimeline.messages.system.added', {
defaultMessage: 'added {thing} on {date}',
values: {
date: moment(message['@timestamp']).format(dateFormat),
thing: message.message.content,
},
});

default:
return '';
}
}
Original file line number Diff line number Diff line change
@@ -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, { useState } from 'react';
import { ComponentStory } from '@storybook/react';
import { EuiButton, EuiSpacer } from '@elastic/eui';

import { ChatTimeline as Component, ChatTimelineProps } from './chat_timeline';
import {
buildAssistantInnerMessage,
buildElasticInnerMessage,
buildMessage,
buildSystemInnerMessage,
buildUserInnerMessage,
} from '../../utils/builders';

export default {
component: Component,
title: 'app/Molecules/ChatTimeline',
parameters: {
backgrounds: {
default: 'white',
values: [{ name: 'white', value: '#fff' }],
},
},
argTypes: {},
};

const Template: ComponentStory<typeof Component> = (props: ChatTimelineProps) => {
const [count, setCount] = useState(0);

return (
<>
<Component {...props} messages={props.messages.filter((_, index) => index <= count)} />

<EuiSpacer />

<EuiButton
onClick={() => setCount(count >= 0 && count < props.messages.length - 1 ? count + 1 : 0)}
>
Add message
</EuiButton>
</>
);
};

const currentDate = new Date();

const defaultProps = {
messages: [
buildMessage({
'@timestamp': String(new Date(currentDate.getTime())),
message: buildSystemInnerMessage(),
}),
buildMessage({
'@timestamp': String(new Date(currentDate.getTime() + 1000)),
message: buildUserInnerMessage(),
}),
buildMessage({
'@timestamp': String(new Date(currentDate.getTime() + 2000)),
message: buildAssistantInnerMessage(),
}),
buildMessage({
'@timestamp': String(new Date(currentDate.getTime() + 3000)),
message: buildUserInnerMessage({ content: 'How does it work?' }),
}),
buildMessage({
'@timestamp': String(new Date(currentDate.getTime() + 4000)),
message: buildElasticInnerMessage({ content: 'Here you go.' }),
}),
],
};

export const ChatTimeline = Template.bind({});
ChatTimeline.args = defaultProps;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { EuiCommentList } from '@elastic/eui';
import { useKibana } from '../../hooks/use_kibana';
import { useCurrentUser } from '../../hooks/use_current_user';
import { Message } from '../../../common/types';
import { ChatItem } from './chat_item';

export interface ChatTimelineProps {
messages: Message[];
}

export function ChatTimeline({ messages = [] }: ChatTimelineProps) {
const { uiSettings } = useKibana().services;
const currentUser = useCurrentUser();

const dateFormat = uiSettings?.get('dateFormat');

return (
<EuiCommentList>
{messages.map((message, index) => (
<ChatItem
currentUser={currentUser}
dateFormat={dateFormat}
index={index}
message={message}
onFeedbackClick={() => {}}
/>
))}
</EuiCommentList>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
EuiFlexItem,
EuiHorizontalRule,
EuiIcon,
EuiPanel,
EuiSpacer,
EuiText,
} from '@elastic/eui';
Expand All @@ -24,7 +23,7 @@ interface Props {

export function MessagePanel(props: Props) {
return (
<EuiPanel color="subdued" hasShadow={false}>
<>
{props.body}
{props.error ? (
<>
Expand All @@ -51,6 +50,6 @@ export function MessagePanel(props: Props) {
{props.controls}
</>
) : null}
</EuiPanel>
</>
);
}
Loading

0 comments on commit d01bbef

Please sign in to comment.