From d9412341565a2c408ed67c0e1ae561b11dbff6da Mon Sep 17 00:00:00 2001 From: Lin Wang Date: Mon, 20 Nov 2023 11:11:39 +0800 Subject: [PATCH] refactor: update get session to manual call (#26) Signed-off-by: Lin Wang --- public/contexts/core_context.tsx | 2 ++ public/hooks/use_chat_actions.tsx | 12 ++++++-- public/hooks/use_sessions.ts | 38 +------------------------ public/plugin.tsx | 2 ++ public/services/session_load_service.ts | 33 +++++++++++++++++++++ public/tabs/chat/chat_page.tsx | 26 ++++++++++------- 6 files changed, 63 insertions(+), 50 deletions(-) create mode 100644 public/services/session_load_service.ts diff --git a/public/contexts/core_context.tsx b/public/contexts/core_context.tsx index 0691aeca..848f4ad8 100644 --- a/public/contexts/core_context.tsx +++ b/public/contexts/core_context.tsx @@ -8,10 +8,12 @@ import { useOpenSearchDashboards, } from '../../../../src/plugins/opensearch_dashboards_react/public'; import { AppPluginStartDependencies, SetupDependencies } from '../types'; +import { SessionLoadService } from '../services/session_load_service'; export interface AssistantServices extends Required { setupDeps: SetupDependencies; startDeps: AppPluginStartDependencies; + sessionLoad: SessionLoadService; } export const useCore: () => OpenSearchDashboardsReactContextValue< diff --git a/public/hooks/use_chat_actions.tsx b/public/hooks/use_chat_actions.tsx index 50a1e9fc..c71836b3 100644 --- a/public/hooks/use_chat_actions.tsx +++ b/public/hooks/use_chat_actions.tsx @@ -53,8 +53,9 @@ export const useChatActions = (): AssistantActions => { } }; - const loadChat = (sessionId?: string, title?: string) => { + const loadChat = async (sessionId?: string, title?: string) => { abortControllerRef?.abort(); + core.services.sessionLoad.abortController?.abort(); chatContext.setSessionId(sessionId); chatContext.setTitle(title); // Chat page will always visible in fullscreen mode, we don't need to change the tab anymore @@ -62,7 +63,14 @@ export const useChatActions = (): AssistantActions => { chatContext.setSelectedTabId('chat'); } chatContext.setFlyoutComponent(null); - if (!sessionId) chatStateDispatch({ type: 'reset' }); + if (!sessionId) { + chatStateDispatch({ type: 'reset' }); + return; + } + const session = await core.services.sessionLoad.load(sessionId); + if (session) { + chatStateDispatch({ type: 'receive', payload: session.messages }); + } }; const openChatUI = () => { diff --git a/public/hooks/use_sessions.ts b/public/hooks/use_sessions.ts index da2ced11..690dee1c 100644 --- a/public/hooks/use_sessions.ts +++ b/public/hooks/use_sessions.ts @@ -6,46 +6,10 @@ import { useCallback, useEffect, useReducer, useState } from 'react'; import { HttpFetchQuery, SavedObjectsFindOptions } from '../../../../src/core/public'; import { ASSISTANT_API } from '../../common/constants/llm'; -import { ISession, ISessionFindResponse } from '../../common/types/chat_saved_object_attributes'; -import { useChatContext } from '../contexts/chat_context'; +import { ISessionFindResponse } from '../../common/types/chat_saved_object_attributes'; import { useCore } from '../contexts/core_context'; import { GenericReducer, genericReducer, genericReducerWithAbortController } from './fetch_reducer'; -export const useGetSession = () => { - const chatContext = useChatContext(); - const core = useCore(); - const reducer: GenericReducer = genericReducer; - const [state, dispatch] = useReducer(reducer, { loading: false }); - const [refreshToggle, setRefreshToggle] = useState(false); - - const refresh = useCallback(() => { - setRefreshToggle((flag) => !flag); - }, []); - - useEffect(() => { - const abortController = new AbortController(); - dispatch({ type: 'request' }); - if (!chatContext.sessionId) { - dispatch({ type: 'success', payload: undefined }); - return; - } - - core.services.http - .get(`${ASSISTANT_API.SESSION}/${chatContext.sessionId}`, { - signal: abortController.signal, - }) - .then((payload) => dispatch({ type: 'success', payload })) - .catch((error) => dispatch({ type: 'failure', error })); - - return () => { - abortController.abort(); - }; - // refreshToggle is used to force refresh session to get latest data - }, [chatContext.sessionId, refreshToggle]); - - return { ...state, refresh }; -}; - export const useGetSessions = (options: Partial = {}) => { const core = useCore(); const reducer: GenericReducer = genericReducer; diff --git a/public/plugin.tsx b/public/plugin.tsx index 3fda4bf5..06db1ddc 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -21,6 +21,7 @@ import { ContentRenderer, SetupDependencies, } from './types'; +import { SessionLoadService } from './services/session_load_service'; export const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); @@ -56,6 +57,7 @@ export class AssistantPlugin ...coreStart, setupDeps, startDeps, + sessionLoad: new SessionLoadService(coreStart.http), }); const account = await getAccount(); const username = account.data.user_name; diff --git a/public/services/session_load_service.ts b/public/services/session_load_service.ts new file mode 100644 index 00000000..1498580f --- /dev/null +++ b/public/services/session_load_service.ts @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BehaviorSubject, from } from 'rxjs'; +import { HttpStart } from '../../../../src/core/public'; +import { ISession } from '../../common/types/chat_saved_object_attributes'; +import { ASSISTANT_API } from '../../common/constants/llm'; + +export class SessionLoadService { + status$: BehaviorSubject< + 'idle' | 'loading' | { status: 'error'; error: Error } + > = new BehaviorSubject<'idle' | 'loading' | { status: 'error'; error: Error }>('idle'); + abortController?: AbortController; + + constructor(private _http: HttpStart) {} + + load = async (sessionId: string) => { + this.abortController?.abort(); + this.status$.next('loading'); + this.abortController = new AbortController(); + try { + return await this._http.get(`${ASSISTANT_API.SESSION}/${sessionId}`, { + signal: this.abortController.signal, + }); + } catch (error) { + this.status$.next({ status: 'error', error }); + } finally { + this.status$.next('idle'); + } + }; +} diff --git a/public/tabs/chat/chat_page.tsx b/public/tabs/chat/chat_page.tsx index 3e27281c..6ca8893f 100644 --- a/public/tabs/chat/chat_page.tsx +++ b/public/tabs/chat/chat_page.tsx @@ -4,34 +4,36 @@ */ import { EuiFlyoutBody, EuiFlyoutFooter, EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import cs from 'classnames'; import { useChatContext } from '../../contexts/chat_context'; import { useChatState } from '../../hooks/use_chat_state'; -import { useGetSession } from '../../hooks/use_sessions'; import { ChatPageContent } from './chat_page_content'; import { ChatInputControls } from './controls/chat_input_controls'; +import { useObservable } from 'react-use'; +import { useCore } from '../../contexts/core_context'; interface ChatPageProps { className?: string; } export const ChatPage: React.FC = (props) => { + const core = useCore(); const chatContext = useChatContext(); const { chatState, chatStateDispatch } = useChatState(); const [showGreetings, setShowGreetings] = useState(false); - const { - data: session, - loading: messagesLoading, - error: messagesLoadingError, - refresh, - } = useGetSession(); + const sessionLoadStatus = useObservable(core.services.sessionLoad.status$); + const messagesLoading = sessionLoadStatus === 'loading'; - useEffect(() => { + const refresh = useCallback(async () => { + if (!chatContext.sessionId) { + return; + } + const session = await core.services.sessionLoad.load(chatContext.sessionId); if (session) { chatStateDispatch({ type: 'receive', payload: session.messages }); } - }, [session]); + }, [chatContext.sessionId, chatStateDispatch]); return ( <> @@ -42,7 +44,9 @@ export const ChatPage: React.FC = (props) => { showGreetings={showGreetings} setShowGreetings={setShowGreetings} messagesLoading={messagesLoading} - messagesLoadingError={messagesLoadingError} + messagesLoadingError={ + typeof sessionLoadStatus !== 'string' ? sessionLoadStatus?.error : undefined + } onRefresh={refresh} />