Skip to content

Commit

Permalink
integrate sidecar poc
Browse files Browse the repository at this point in the history
Signed-off-by: tygao <[email protected]>
  • Loading branch information
raintygao committed Jan 18, 2024
1 parent 4387510 commit 63ac8b3
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 63 deletions.
13 changes: 3 additions & 10 deletions public/chat_flyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface ChatFlyoutProps {
overrideComponent: React.ReactNode | null;
flyoutProps: Partial<React.ComponentProps<typeof EuiFlyout>>;
flyoutFullScreen: boolean;
toggleFlyoutFullScreen: () => void;
toggleFlyoutFullScreen: (direction: string) => void;
}

export const ChatFlyout = (props: ChatFlyoutProps) => {
Expand Down Expand Up @@ -81,17 +81,10 @@ export const ChatFlyout = (props: ChatFlyoutProps) => {
const rightPanelSize = getRightPanelSize();

return (
<EuiFlyout
<div
className={cs('llm-chat-flyout', {
'llm-chat-fullscreen': props.flyoutFullScreen,
'llm-chat-hidden': !props.flyoutVisible,
})}
type="push"
paddingSize="none"
size="460px"
ownFocus={false}
hideCloseButton
onClose={() => chatContext.setFlyoutVisible(false)}
{...props.flyoutProps}
>
<>
Expand Down Expand Up @@ -146,6 +139,6 @@ export const ChatFlyout = (props: ChatFlyoutProps) => {
)}
</EuiResizableContainer>
</>
</EuiFlyout>
</div>
);
};
97 changes: 76 additions & 21 deletions public/chat_header_button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import './index.scss';
import chatIcon from './assets/chat.svg';
import { ActionExecutor, AssistantActions, MessageRenderer, UserAccount, TabId } from './types';
import { TAB_ID } from './utils/constants';
import { useCore } from './contexts/core_context';
import { toMountPoint } from '../../../src/plugins/opensearch_dashboards_react/public';
import { OpenSearchDashboardsReactContext } from '../../../src/plugins/opensearch_dashboards_react/public';
import { AssistantServices } from './contexts/core_context';
import { ISidecarConfig } from '../../../src/core/public';

interface HeaderChatButtonProps {
application: ApplicationStart;
Expand All @@ -24,6 +29,7 @@ interface HeaderChatButtonProps {
actionExecutors: Record<string, ActionExecutor>;
assistantActions: AssistantActions;
currentAccount: UserAccount;
coreContext: OpenSearchDashboardsReactContext<AssistantServices>;
}

let flyoutLoaded = false;
Expand All @@ -37,22 +43,45 @@ export const HeaderChatButton = (props: HeaderChatButtonProps) => {
const [selectedTabId, setSelectedTabId] = useState<TabId>(TAB_ID.CHAT);
const [preSelectedTabId, setPreSelectedTabId] = useState<TabId | undefined>(undefined);
const [interactionId, setInteractionId] = useState<string | undefined>(undefined);
const [chatSize, setChatSize] = useState<number | 'fullscreen' | 'dock-right'>('dock-right');
const [dockedDirection, setDockedDirection] = useState<ISidecarConfig['dockedDirection']>(
'right'
);
const [query, setQuery] = useState('');
const [inputFocus, setInputFocus] = useState(false);
const flyoutFullScreen = chatSize === 'fullscreen';
const flyoutFullScreen = dockedDirection === 'bottom';
const inputRef = useRef<HTMLInputElement>(null);

if (!flyoutLoaded && flyoutVisible) flyoutLoaded = true;
// const [flyoutLoaded, setFlyoutLoaded] = useState(false);
const core = useCore();
// if (!flyoutLoaded && flyoutVisible) flyoutLoaded = true;

useEffectOnce(() => {
const subscription = props.application.currentAppId$.subscribe((id) => setAppId(id));
return () => subscription.unsubscribe();
});

const toggleFlyoutFullScreen = useCallback(() => {
setChatSize(flyoutFullScreen ? 'dock-right' : 'fullscreen');
}, [flyoutFullScreen, setChatSize]);
const toggleFlyoutFullScreen = useCallback(
(direction: ISidecarConfig['dockedDirection']) => {
setDockedDirection((prevDirection) => {
if (prevDirection === direction) {
return prevDirection;
} else {
if (direction === 'bottom') {
core.overlays.sidecar().setSidecarConfig({
dockedDirection: 'bottom',
paddingSize: window.innerHeight - 136,
});
} else {
core.overlays.sidecar().setSidecarConfig({
dockedDirection: direction,
paddingSize: 460,
});
}
return direction;
}
});
},
[flyoutFullScreen]
);

const chatContextValue: IChatContext = useMemo(
() => ({
Expand Down Expand Up @@ -117,6 +146,46 @@ export const HeaderChatButton = (props: HeaderChatButtonProps) => {
}
};

const Chat = (
<props.coreContext.Provider>
<ChatContext.Provider value={chatContextValue}>
<ChatStateProvider>
<SetContext assistantActions={props.assistantActions} />
<ChatFlyout
flyoutVisible={flyoutVisible}
overrideComponent={flyoutComponent}
flyoutProps={flyoutFullScreen ? { size: '100%' } : {}}
flyoutFullScreen={flyoutFullScreen}
toggleFlyoutFullScreen={toggleFlyoutFullScreen}
/>
</ChatStateProvider>
</ChatContext.Provider>
</props.coreContext.Provider>
);

useEffect(() => {
console.log('visa', flyoutVisible, flyoutLoaded);
if (!flyoutLoaded && flyoutVisible) {
console.log('mounut');
core.overlays.sidecar().open(toMountPoint(Chat), {
className: 'chatbot-sidecar',
config: {
dockedDirection: 'right',
paddingSize: 460,
},
});
flyoutLoaded = true;
} else if (flyoutLoaded && flyoutVisible) {
console.log('show');

core.overlays.sidecar().show();
} else if (flyoutLoaded && !flyoutVisible) {
console.log('hide');

core.overlays.sidecar().hide();
}
}, [flyoutVisible, flyoutLoaded]);

const onKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Escape') {
inputRef.current?.blur();
Expand Down Expand Up @@ -185,20 +254,6 @@ export const HeaderChatButton = (props: HeaderChatButtonProps) => {
disabled={!props.userHasAccess}
/>
</div>
<ChatContext.Provider value={chatContextValue}>
<ChatStateProvider>
<SetContext assistantActions={props.assistantActions} />
{flyoutLoaded ? (
<ChatFlyout
flyoutVisible={flyoutVisible}
overrideComponent={flyoutComponent}
flyoutProps={flyoutFullScreen ? { size: '100%' } : {}}
flyoutFullScreen={flyoutFullScreen}
toggleFlyoutFullScreen={toggleFlyoutFullScreen}
/>
) : null}
</ChatStateProvider>
</ChatContext.Provider>
</>
);
};
104 changes: 104 additions & 0 deletions public/components/sidecar_icon_menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
EuiContextMenuItem,
EuiContextMenuPanel,
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
EuiButtonIcon,
} from '@elastic/eui';
import React, { useCallback, useState } from 'react';

const dockBottom = () => (
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path d="M3 1H13C14.1046 1 15 1.89543 15 3V13C15 14.1046 14.1046 15 13 15H3C1.89543 15 1 14.1046 1 13V3C1 1.89543 1.89543 1 3 1ZM3 2C2.44772 2 2 2.44772 2 3V13C2 13.5523 2.44772 14 3 14H13C13.5523 14 14 13.5523 14 13V3C14 2.44772 13.5523 2 13 2H3Z" />
<path d="M3 9.5C3 9.22386 3.22386 9 3.5 9H12.5C12.7761 9 13 9.22386 13 9.5V12.5C13 12.7761 12.7761 13 12.5 13H3.5C3.22386 13 3 12.7761 3 12.5V9.5Z" />
</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="M3 1H13C14.1046 1 15 1.89543 15 3V13C15 14.1046 14.1046 15 13 15H3C1.89543 15 1 14.1046 1 13V3C1 1.89543 1.89543 1 3 1ZM3 2C2.44772 2 2 2.44772 2 3V13C2 13.5523 2.44772 14 3 14H13C13.5523 14 14 13.5523 14 13V3C14 2.44772 13.5523 2 13 2H3Z" />
<path d="M9 3.5C9 3.22386 9.22386 3 9.5 3H12.5C12.7761 3 13 3.22386 13 3.5V12.5C13 12.7761 12.7761 13 12.5 13H9.5C9.22386 13 9 12.7761 9 12.5V3.5Z" />
</g>
</svg>
);

interface Props {
setDockedDirection: (direction: string) => void;
}

export const SidecarIconMenu = ({ setDockedDirection }) => {
const [isPopoverOpen, setPopoverOpen] = useState(false);

const onButtonClick = useCallback(() => {
setPopoverOpen((flag) => !flag);
}, []);

const closePopover = useCallback(() => {
setPopoverOpen(false);
}, []);

const button = (
<EuiButtonIcon
aria-label="fullScreen"
color="text"
size="xs"
iconType={dockRight}
onClick={onButtonClick}
/>
);

const items = [
<EuiContextMenuItem
key="rename-conversation"
prefix="a"
onClick={() => {
closePopover();
setDockedDirection('right');
}}
>
Dock Right
</EuiContextMenuItem>,
<EuiContextMenuItem
key="new-conversation"
onClick={() => {
closePopover();
setDockedDirection('left');
}}
>
Dock Left
</EuiContextMenuItem>,
<EuiContextMenuItem
key="save-as-notebook"
onClick={() => {
closePopover();
setDockedDirection('bottom');
}}
>
Dock Full Width
</EuiContextMenuItem>,
];

return (
<>
<EuiPopover
id="conversationTitle"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downRight"
>
<EuiContextMenuPanel size="m" items={items} />
</EuiPopover>
</>
);
};
5 changes: 5 additions & 0 deletions public/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
}

.llm-chat-flyout {
height: 100%;
.euiFlyoutFooter {
background: transparent;
}
Expand All @@ -88,6 +89,10 @@
border-radius: 8px;
}

.llm-chat-flyout-footer {
padding-bottom: 24px;
}

.llm-chat-bubble-wrapper {
.llm-chat-action-buttons-hidden {
opacity: 0;
Expand Down
58 changes: 30 additions & 28 deletions public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,36 +75,38 @@ export class AssistantPlugin
const checkAccess = (account: Awaited<ReturnType<typeof getAccount>>) =>
account.data.roles.some((role) => ['all_access', 'assistant_user'].includes(role));

if (this.config.chat.enabled) {
core.getStartServices().then(async ([coreStart, startDeps]) => {
const CoreContext = createOpenSearchDashboardsReactContext<AssistantServices>({
...coreStart,
setupDeps,
startDeps,
conversationLoad: new ConversationLoadService(coreStart.http),
conversations: new ConversationsService(coreStart.http),
});
const account = await getAccount();
const username = account.data.user_name;
const tenant = account.data.user_requested_tenant ?? '';
// if (this.config.chat.enabled) {
core.getStartServices().then(async ([coreStart, startDeps]) => {
const CoreContext = createOpenSearchDashboardsReactContext<AssistantServices>({
...coreStart,
setupDeps,
startDeps,
conversationLoad: new ConversationLoadService(coreStart.http),
conversations: new ConversationsService(coreStart.http),
});
const account = await getAccount();
const username = account.data.user_name;
const tenant = account.data.user_requested_tenant ?? '';

coreStart.chrome.navControls.registerRight({
order: 10000,
mount: toMountPoint(
<CoreContext.Provider>
<HeaderChatButton
application={coreStart.application}
userHasAccess={checkAccess(account)}
messageRenderers={messageRenderers}
actionExecutors={actionExecutors}
assistantActions={assistantActions}
currentAccount={{ username, tenant }}
/>
</CoreContext.Provider>
),
});
coreStart.chrome.navControls.registerRight({
order: 10000,
mount: toMountPoint(
<CoreContext.Provider>
<HeaderChatButton
application={coreStart.application}
userHasAccess={checkAccess(account)}
messageRenderers={messageRenderers}
actionExecutors={actionExecutors}
assistantActions={assistantActions}
currentAccount={{ username, tenant }}
coreContext={CoreContext}
/>
</CoreContext.Provider>
),
});
}
});
// });
// }

return {
registerMessageRenderer: (contentType, render) => {
Expand Down
2 changes: 1 addition & 1 deletion public/tabs/chat/chat_page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const ChatPage: React.FC<ChatPageProps> = (props) => {
</EuiPageBody>
</EuiPage>
</EuiFlyoutBody>
<EuiFlyoutFooter className={props.className}>
<EuiFlyoutFooter className={(props.className, 'llm-chat-flyout-footer')}>
<EuiSpacer size="xs" />
<ChatInputControls
loading={chatState.llmResponding}
Expand Down
Loading

0 comments on commit 63ac8b3

Please sign in to comment.