Skip to content

Commit

Permalink
Add ConversationList
Browse files Browse the repository at this point in the history
  • Loading branch information
CoenWarmer committed Jul 31, 2023
1 parent 59ebcdf commit 6cf0b70
Show file tree
Hide file tree
Showing 24 changed files with 429 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 { EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

export function HideExpandConversationListButton(
props: React.ComponentProps<typeof EuiButtonEmpty> & { isExpanded: boolean }
) {
return (
<EuiButtonEmpty iconType={props.isExpanded ? 'menuLeft' : 'menuRight'} size="xs" {...props}>
{props.isExpanded
? i18n.translate('xpack.observabilityAiAssistant.hideExpandConversationButton.hide', {
defaultMessage: 'Hide chats',
})
: i18n.translate('xpack.observabilityAiAssistant.hideExpandConversationButton.show', {
defaultMessage: 'Show chats',
})}
</EuiButtonEmpty>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { NewChatButton as Component } from './new_chat_button';

const meta: ComponentMeta<typeof Component> = {
component: Component,
title: 'app/Atoms/NewChatButton',
};

export default meta;

export const NewChatButton: ComponentStoryObj<typeof Component> = {
args: {},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

export function NewChatButton(props: React.ComponentProps<typeof EuiButton>) {
return (
<EuiButton {...props} fill iconType="discuss">
{i18n.translate('xpack.observabilityAiAssistant.newChatButton', {
defaultMessage: 'New chat',
})}
</EuiButton>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { RegenerateResponseButton as Component } from './regenerate_response_button';

const meta: ComponentMeta<typeof Component> = {
component: Component,
title: 'app/Atoms/RegenerateResponseButton',
};

export default meta;

export const RegenerateResponseButton: ComponentStoryObj<typeof Component> = {
args: {},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { StartChatButton as Component } from './start_chat_button';

const meta: ComponentMeta<typeof Component> = {
component: Component,
title: 'app/Atoms/StartChatButton',
};

export default meta;

export const StartChatButton: ComponentStoryObj<typeof Component> = {
args: {},
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
* 2.0.
*/

import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/css';
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
import React from 'react';
import { type ConversationCreateRequest } from '../../../common/types';
import type { UseChatResult } from '../../hooks/use_chat';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
import { useTimeline } from '../../hooks/use_timeline';
import { HideExpandConversationListButton } from '../buttons/hide_expand_conversation_list_button';
import { ChatHeader } from './chat_header';
import { ChatPromptEditor } from './chat_prompt_editor';
import { ChatTimeline } from './chat_timeline';
Expand All @@ -30,12 +31,18 @@ export function ChatBody({
connectors,
currentUser,
chat,
isConversationListExpanded,
onToggleExpandConversationList,
}: {
initialConversation?: ConversationCreateRequest;
connectors: UseGenAIConnectorsResult;
currentUser?: Pick<AuthenticatedUser, 'full_name' | 'username'>;
chat: UseChatResult;
isConversationListExpanded?: boolean;
onToggleExpandConversationList?: () => void;
}) {
const { euiTheme } = useEuiTheme();

const timeline = useTimeline({
initialConversation,
connectors,
Expand All @@ -46,15 +53,31 @@ export function ChatBody({
return (
<EuiFlexGroup direction="column" gutterSize="none" className={containerClassName}>
<EuiFlexItem grow={false}>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="l">
<ChatHeader title="foo" connectors={connectors} />
<EuiPanel
hasShadow={false}
hasBorder={false}
borderRadius="none"
css={{ borderBottom: `solid 1px ${euiTheme.border.color}` }}
>
<HideExpandConversationListButton
isExpanded={Boolean(isConversationListExpanded)}
onClick={onToggleExpandConversationList}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
<ChatHeader
title={initialConversation?.conversation.title ?? ''}
connectors={connectors}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiHorizontalRule margin="none" />
</EuiFlexItem>
<EuiFlexItem grow className={timelineClassName}>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="l">
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
<ChatTimeline
items={timeline.items}
onEdit={timeline.onEdit}
Expand All @@ -68,7 +91,7 @@ export function ChatBody({
<EuiHorizontalRule margin="none" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="l">
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
<ChatPromptEditor
loading={chat.loading}
disabled={!connectors.selectedConnector}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { ComponentStory } from '@storybook/react';
import React from 'react';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { ChatFlyout as Component } from './chat_flyout';

export default {
component: Component,
title: 'app/Organisms/ChatFlyout',
decorators: [KibanaReactStorybookDecorator],
};

type ChatFlyoutProps = React.ComponentProps<typeof Component>;

const Template: ComponentStory<typeof Component> = (props: ChatFlyoutProps) => {
return (
<div style={{ display: 'flex', minHeight: 800 }}>
<Component {...props} />
</div>
);
};

const defaultProps: ChatFlyoutProps = {
isOpen: true,
initialConversation: {
'@timestamp': '',
conversation: {
title: 'How is this working',
},
messages: [],
labels: {},
numeric_labels: {},
},
onClose: () => {},
};

export const ChatFlyout = Template.bind({});
ChatFlyout.args = defaultProps;
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiFlyout } from '@elastic/eui';
import React from 'react';
import React, { useState } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiFlyout, useEuiTheme } from '@elastic/eui';
import type { ConversationCreateRequest } from '../../../common/types';
import { useChat } from '../../hooks/use_chat';
import { useCurrentUser } from '../../hooks/use_current_user';
import { useGenAIConnectors } from '../../hooks/use_genai_connectors';
import { ChatBody } from './chat_body';
import { ConversationList } from './conversation_list';

export function ChatFlyout({
initialConversation,
Expand All @@ -21,20 +22,46 @@ export function ChatFlyout({
isOpen: boolean;
onClose: () => void;
}) {
const currentUser = useCurrentUser();
const connectors = useGenAIConnectors();
const { euiTheme } = useEuiTheme();

const chat = useChat();

const connectors = useGenAIConnectors();
const [isConversationListExpanded, setIsConversationListExpanded] = useState(false);

const currentUser = useCurrentUser();
const handleClickConversation = (id: string) => {};
const handleClickNewChat = () => {};
const handleClickSettings = () => {};

return isOpen ? (
<EuiFlyout onClose={onClose}>
<ChatBody
chat={chat}
connectors={connectors}
initialConversation={initialConversation}
currentUser={currentUser}
/>
<EuiFlexGroup responsive={false} gutterSize="none">
{isConversationListExpanded ? (
<EuiFlexItem
grow={false}
css={{ minWidth: 200, borderRight: `solid 1px ${euiTheme.border.color}` }}
>
<ConversationList
onClickConversation={handleClickConversation}
onClickNewChat={handleClickNewChat}
onClickSettings={handleClickSettings}
/>
</EuiFlexItem>
) : null}
<EuiFlexItem>
<ChatBody
chat={chat}
connectors={connectors}
initialConversation={initialConversation}
currentUser={currentUser}
isConversationListExpanded={isConversationListExpanded}
onToggleExpandConversationList={() =>
setIsConversationListExpanded(!isConversationListExpanded)
}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyout>
) : null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ 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 '../regenerate_response_button';
import { StopGeneratingButton } from '../stop_generating_button';
import { RegenerateResponseButton } from '../buttons/regenerate_response_button';
import { StopGeneratingButton } from '../buttons/stop_generating_button';
import { ChatItemAvatar } from './chat_item_avatar';
import { ChatItemTitle } from './chat_item_title';
import { ChatTimelineItem } from './chat_timeline';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
* 2.0.
*/

import React from 'react';
import { EuiCommentList } from '@elastic/eui';
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
import React from 'react';
import { MessageRole } from '../../../common/types';
import type { Feedback } from '../feedback_buttons';
import { ChatItem } from './chat_item';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 { ComponentStory } from '@storybook/react';
import React from 'react';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { ConversationList as Component } from './conversation_list';

export default {
component: Component,
title: 'app/Organisms/ConversationList',
decorators: [KibanaReactStorybookDecorator],
};

type ConversationListProps = React.ComponentProps<typeof Component>;

const Template: ComponentStory<typeof Component> = (props: ConversationListProps) => {
return (
<div style={{ minHeight: 800, display: 'flex' }}>
<Component {...props} />
</div>
);
};

const defaultProps: ConversationListProps = {
onClickConversation: (conversationId: string) => {},
onClickNewChat: () => {},
onClickSettings: () => {},
};

export const ConversationList = Template.bind({});
ConversationList.args = defaultProps;
Loading

0 comments on commit 6cf0b70

Please sign in to comment.