Skip to content

Commit

Permalink
Chat window header (#3)
Browse files Browse the repository at this point in the history
* Chat window header

Signed-off-by: Hailong Cui <ihailong@amazon.com>

* support chat title

Signed-off-by: Hailong Cui <ihailong@amazon.com>

* dock bottom and right icon update

Signed-off-by: Hailong Cui <ihailong@amazon.com>

* chat window style

Signed-off-by: Hailong Cui <ihailong@amazon.com>

* fix new conversation don't show when in trace page

Signed-off-by: Hailong Cui <ihailong@amazon.com>

---------

Signed-off-by: Hailong Cui <ihailong@amazon.com>
  • Loading branch information
Hailong-am authored and ruanyl committed Nov 20, 2023
1 parent a50f637 commit 3fa5f53
Show file tree
Hide file tree
Showing 12 changed files with 187 additions and 104 deletions.
13 changes: 7 additions & 6 deletions public/chat_flyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import cs from 'classnames';
import React from 'react';
import { useChatContext } from './contexts/chat_context';
import { ChatPage } from './tabs/chat/chat_page';
import { ChatTabBar } from './tabs/chat_tab_bar';
import { ChatWindowHeader } from './tabs/chat_window_header';
import { ChatHistoryPage } from './tabs/history/chat_history_page';

let chatHistoryPageLoaded = false;
Expand Down Expand Up @@ -59,16 +59,17 @@ export const ChatFlyout: React.FC<ChatFlyoutProps> = (props) => {
{...props.flyoutProps}
>
<>
{props.overrideComponent}
<EuiFlyoutHeader
className={cs('llm-chat-flyout-header', { 'llm-chat-hidden': props.overrideComponent })}
>
<ChatTabBar
<EuiFlyoutHeader className={cs('llm-chat-flyout-header')}>
<ChatWindowHeader
flyoutFullScreen={props.flyoutFullScreen}
toggleFlyoutFullScreen={props.toggleFlyoutFullScreen}
/>
</EuiFlyoutHeader>

{props.overrideComponent}

<ChatPage className={cs({ 'llm-chat-hidden': !chatPageVisible })} />

{chatHistoryPageLoaded && (
<ChatHistoryPage
className={cs({ 'llm-chat-hidden': !chatHistoryPageVisible })}
Expand Down
8 changes: 6 additions & 2 deletions public/chat_header_button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { ChatContext, IChatContext } from './contexts/chat_context';
import { SetContext } from './contexts/set_context';
import { ChatStateProvider } from './hooks/use_chat_state';
import './index.scss';
import { TabId } from './tabs/chat_tab_bar';
import { ActionExecutor, AssistantActions, ContentRenderer, UserAccount } from './types';
import { ActionExecutor, AssistantActions, ContentRenderer, UserAccount, TabId } from './types';

interface HeaderChatButtonProps {
application: ApplicationStart;
Expand All @@ -30,6 +29,7 @@ let flyoutLoaded = false;
export const HeaderChatButton: React.FC<HeaderChatButtonProps> = (props) => {
const [appId, setAppId] = useState<string>();
const [sessionId, setSessionId] = useState<string>();
const [title, setTitle] = useState<string>();
const [flyoutVisible, setFlyoutVisible] = useState(false);
const [flyoutComponent, setFlyoutComponent] = useState<React.ReactNode | null>(null);
const [flyoutProps, setFlyoutProps] = useState<Partial<React.ComponentProps<typeof EuiFlyout>>>(
Expand Down Expand Up @@ -62,6 +62,8 @@ export const HeaderChatButton: React.FC<HeaderChatButtonProps> = (props) => {
contentRenderers: props.contentRenderers,
actionExecutors: props.actionExecutors,
currentAccount: props.currentAccount,
title,
setTitle,
}),
[
appId,
Expand All @@ -72,6 +74,8 @@ export const HeaderChatButton: React.FC<HeaderChatButtonProps> = (props) => {
props.contentRenderers,
props.actionExecutors,
props.currentAccount,
title,
setTitle,
]
);

Expand Down
5 changes: 3 additions & 2 deletions public/contexts/chat_context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
*/

import React, { useContext } from 'react';
import { TabId } from '../tabs/chat_tab_bar';
import { ActionExecutor, ContentRenderer, UserAccount } from '../types';
import { ActionExecutor, ContentRenderer, UserAccount, TabId } from '../types';

export interface IChatContext {
appId?: string;
Expand All @@ -20,6 +19,8 @@ export interface IChatContext {
contentRenderers: Record<string, ContentRenderer>;
actionExecutors: Record<string, ActionExecutor>;
currentAccount: UserAccount;
title?: string;
setTitle: React.Dispatch<React.SetStateAction<string | undefined>>;
}
export const ChatContext = React.createContext<IChatContext | null>(null);

Expand Down
6 changes: 5 additions & 1 deletion public/hooks/use_chat_actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useChatState } from './use_chat_state';

interface SendResponse {
sessionId: string;
title: string;
messages: IMessage[];
}

Expand All @@ -37,17 +38,20 @@ export const useChatActions = (): AssistantActions => {
});
if (abortController.signal.aborted) return;
chatContext.setSessionId(response.sessionId);
chatContext.setTitle(response.title);
chatStateDispatch({ type: 'receive', payload: response.messages });
} catch (error) {
if (abortController.signal.aborted) return;
chatStateDispatch({ type: 'error', payload: error });
}
};

const loadChat = (sessionId?: string) => {
const loadChat = (sessionId?: string, title?: string) => {
abortControllerRef?.abort();
chatContext.setSessionId(sessionId);
chatContext.setTitle(title);
chatContext.setSelectedTabId('chat');
chatContext.setFlyoutComponent(null);
if (!sessionId) chatStateDispatch({ type: 'reset' });
};

Expand Down
21 changes: 3 additions & 18 deletions public/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,9 @@
}
}

.llm-chat-flyout-header {
background: #e6f0f8;
}

.llm-chat-tabs {
justify-content: left;

.euiTab-isSelected {
font-weight: 700;
}

.euiTab {
color: $ouiLinkColor;
&:hover,
&:focus {
text-decoration: none;
}
}
.llm-chat-flyout-body {
background-color: $euiPageBackgroundColor;
margin: 0px 20px;
}

.euiPanel {
Expand Down
3 changes: 2 additions & 1 deletion public/tabs/chat/chat_page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { EuiFlyoutBody, EuiFlyoutFooter, EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui';
import React, { useEffect, useState } from 'react';
import cs from 'classnames';
import { useObservable } from 'react-use';
import { useChatContext } from '../../contexts/chat_context';
import { useChatState } from '../../hooks/use_chat_state';
Expand Down Expand Up @@ -41,7 +42,7 @@ export const ChatPage: React.FC<ChatPageProps> = (props) => {

return (
<>
<EuiFlyoutBody className={props.className}>
<EuiFlyoutBody className={cs(props.className, 'llm-chat-flyout-body')}>
<EuiPage style={{ background: 'transparent' }}>
<EuiPageBody component="div">
<ChatPageContent
Expand Down
2 changes: 1 addition & 1 deletion public/tabs/chat/chat_page_content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const ChatPageContent: React.FC<ChatPageContentProps> = React.memo((props
<MessageBubble type="output" contentType="markdown">
<TermsAndConditions username={chatContext.currentAccount.username} />
</MessageBubble>
<EuiSpacer />,
<EuiSpacer />
{props.showGreetings && <ChatPageGreetings dismiss={() => props.setShowGreetings(false)} />}
{chatState.messages
.flatMap((message, i, array) => [
Expand Down
65 changes: 0 additions & 65 deletions public/tabs/chat_tab_bar.tsx

This file was deleted.

145 changes: 145 additions & 0 deletions public/tabs/chat_window_header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
EuiButtonEmpty,
EuiButtonIcon,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
} from '@elastic/eui';
import React, { useState } from 'react';
import { useChatContext } from '../contexts/chat_context';
import { useChatActions } from '../hooks/use_chat_actions';

interface ChatWindowHeaderProps {
flyoutFullScreen: boolean;
toggleFlyoutFullScreen: () => void;
}

export const ChatWindowHeader: React.FC<ChatWindowHeaderProps> = React.memo((props) => {
const chatContext = useChatContext();
const { loadChat } = useChatActions();
const [isPopoverOpen, setPopover] = useState(false);

const onButtonClick = () => {
setPopover(!isPopoverOpen);
};

const closePopover = () => {
setPopover(false);
};

const dockBottom = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="currentColor">
<path d="M0 1.99406C0 0.892771 0.894514 0 1.99406 0H14.0059C15.1072 0 16 0.894514 16 1.99406V14.0059C16 15.1072 15.1055 16 14.0059 16H1.99406C0.892771 16 0 15.1055 0 14.0059V1.99406ZM1 1.99406V14.0059C1 14.5539 1.44579 15 1.99406 15H14.0059C14.5539 15 15 14.5542 15 14.0059V1.99406C15 1.44606 14.5542 1 14.0059 1H1.99406C1.44606 1 1 1.44579 1 1.99406Z" />
<rect x="0.5" y="15" width="9.5" height="15" transform="rotate(-90 0.5 15)" />
</g>
</svg>
);

const dockRight = () => (
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path d="M0 1.99406C0 0.892771 0.894514 0 1.99406 0H14.0059C15.1072 0 16 0.894514 16 1.99406V14.0059C16 15.1072 15.1055 16 14.0059 16H1.99406C0.892771 16 0 15.1055 0 14.0059V1.99406ZM1 1.99406V14.0059C1 14.5539 1.44579 15 1.99406 15H14.0059C14.5539 15 15 14.5542 15 14.0059V1.99406C15 1.44606 14.5542 1 14.0059 1H1.99406C1.44606 1 1 1.44579 1 1.99406Z" />
<rect x="9" y="0.5" width="6" height="14.5" />
</g>
</svg>
);

const button = (
<EuiButtonEmpty
style={{ maxWidth: '300px' }}
size="s"
iconType="arrowDown"
iconSide="right"
onClick={onButtonClick}
>
<span className="eui-textTruncate">{chatContext.title || 'OpenSearch Assistant'}</span>
</EuiButtonEmpty>
);

const items = [
<EuiContextMenuItem
key="rename-conversation"
onClick={() => {
closePopover();
}}
>
Rename conversation
</EuiContextMenuItem>,
<EuiContextMenuItem
key="new-conversation"
onClick={() => {
closePopover();
loadChat(undefined);
}}
>
New conversation
</EuiContextMenuItem>,
<EuiContextMenuItem
key="save-as-notebook"
onClick={() => {
closePopover();
}}
>
Save as notebook
</EuiContextMenuItem>,
];

return (
<EuiFlexGroup gutterSize="s" justifyContent="spaceAround" alignItems="center">
<EuiFlexItem>
<EuiFlexGroup gutterSize="none" alignItems="center">
<EuiFlexItem grow={false}>
<EuiPopover
id="conversationTitle"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downRight"
>
<EuiContextMenuPanel size="m" items={items} />
</EuiPopover>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label="history"
iconType="clock"
size="m"
onClick={() => {
chatContext.setSelectedTabId('history');
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label="fullScreen"
size="s"
// TODO replace svg with built-in icon
iconType={props.flyoutFullScreen ? dockRight : dockBottom}
onClick={props.toggleFlyoutFullScreen}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label="close"
size="s"
iconType="cross"
onClick={() => {
chatContext.setFlyoutVisible(false);
}}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} />
</EuiFlexGroup>
);
});
4 changes: 3 additions & 1 deletion public/tabs/history/chat_history_page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ export const ChatHistoryPage: React.FC<ChatHistoryPageProps> = (props) => {
{
field: 'id',
name: 'Chat',
render: (id: string, item) => <EuiLink onClick={() => loadChat(id)}>{item.title}</EuiLink>,
render: (id: string, item) => (
<EuiLink onClick={() => loadChat(id, item.title)}>{item.title}</EuiLink>
),
},
{
field: 'updatedTimeMs',
Expand Down
Loading

0 comments on commit 3fa5f53

Please sign in to comment.