Skip to content

Commit

Permalink
Add chat timeline
Browse files Browse the repository at this point in the history
  • Loading branch information
CoenWarmer committed Jul 25, 2023
1 parent fa10766 commit 0b66523
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 139 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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 { 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';

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;
}

export function ChatItem({ currentUser, dateFormat, index, message }: 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]}
>
{message.message.content ? (
<EuiText size="s">
<p>{message.message.content}</p>
</EuiText>
) : null}
</EuiComment>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,26 @@ import { AssistantAvatar } from '../assistant_avatar';
import { MessageRole } from '../../../common/types';

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

export function ChatAvatar({ user, role }: ChatAvatarProps) {
export function ChatItemAvatar({ currentUser, role }: ChatAvatarProps) {
switch (role) {
case MessageRole.User:
return user ? (
<UserAvatar user={user} size="m" data-test-subj="userMenuAvatar" />
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="#fff" />;
return <EuiAvatar name="Elastic Assistant" iconType={AssistantAvatar} color="subdued" />;

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

default:
return null;
Expand Down
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
Expand Up @@ -5,28 +5,73 @@
* 2.0.
*/

import React from 'react';
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 { buildAssistantMessage, buildElasticMessage, buildUserMessage } from '../../utils/builders';
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) => (
<Component {...props} />
);
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: [
buildUserMessage(),
buildAssistantMessage(),
buildUserMessage(),
buildElasticMessage(),
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.' }),
}),
],
};

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

import React from 'react';
import { EuiText, EuiCommentList, EuiComment, EuiCode, EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Message, MessageRole } from '../../../common/types';
import { EuiCommentList } from '@elastic/eui';
import { useKibana } from '../../hooks/use_kibana';
import { useCurrentUser } from '../../hooks/use_current_user';
import { ChatAvatar } from './chat_avatar';
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) => (
<EuiComment
username={i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.userInitiatedTitle.you',
{ defaultMessage: 'You' }
)}
event={
index === 0 && message.message.role === MessageRole.User
? i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.userInitiatedTitle.createdNewConversation',
{
defaultMessage: 'created a new conversation',
}
)
: null
}
timelineAvatar={<ChatAvatar user={currentUser} role={message.message.role} />}
>
{message.message.role === MessageRole.User && index === 0 ? (
<EuiText size="s">
<p>{message.message.content}</p>
</EuiText>
) : (
<EuiPanel
hasBorder
css={{
backgroundColor: message.message.role !== MessageRole.User ? '#F1F4FA' : '#fff',
}}
paddingSize="s"
>
<p>{message.message.content}</p>
</EuiPanel>
)}
</EuiComment>
<ChatItem
currentUser={currentUser}
dateFormat={dateFormat}
index={index}
message={message}
/>
))}
</EuiCommentList>
);
}

// <EuiTimeline>
// {messages.map((message, index) => (
// <EuiTimelineItem
// key={index}
// verticalAlign="top"
// icon={<EuiAvatar name="Checked" iconType="check" />}
// >
// <EuiSplitPanel.Outer color="transparent" hasBorder grow>
// {message.message.role === MessageRole.User && index === 0 ? (
// <>
// <EuiSplitPanel.Inner css={{ backgroundColor: lightGreyBgColor }} paddingSize="s">
// <EuiText size="s">
// <p>
// <strong>
// {i18n.translate(
// 'xpack.observabilityAiAssistant.chatTimeline.messages.userInitiatedTitle.you',
// { defaultMessage: 'You' }
// )}{' '}
// </strong>
// {i18n.translate(
// 'xpack.observabilityAiAssistant.chatTimeline.messages.userInitiatedTitle.createdNewConversation',
// {
// defaultMessage: 'created a new conversation',
// }
// )}
// </p>
// </EuiText>
// </EuiSplitPanel.Inner>
// <EuiHorizontalRule margin="none" />
// </>
// ) : null}

// <EuiSplitPanel.Inner
// css={{
// backgroundColor:
// message.message.role === MessageRole.User ? 'white' : lightGreyBgColor,
// }}
// paddingSize="s"
// >
// <EuiText grow={false} size="s">
// <p>{message.message.content}</p>
// </EuiText>
// </EuiSplitPanel.Inner>
// </EuiSplitPanel.Outer>
// </EuiTimelineItem>
// ))}
// </EuiTimeline>
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,6 @@
* 2.0.
*/

interface User {
username: string;
email?: string;
full_name?: string;
roles: readonly string[];
enabled: boolean;
metadata?: {
_reserved: boolean;
_deprecated?: boolean;
_deprecated_reason?: string;
};
}

export function useCurrentUser() {
return {
username: 'john_doe',
Expand Down
Loading

0 comments on commit 0b66523

Please sign in to comment.