From c48cafd73ba2e30bf211e66c613adbce81abb010 Mon Sep 17 00:00:00 2001 From: Mark Waddle Date: Tue, 5 Nov 2024 14:23:21 -0800 Subject: [PATCH] Avoid querying capabilities from offline assistants (#213) To avoid the continuous reload loop --- .../src/components/FrontDoor/Chat/Chat.tsx | 6 +- .../src/components/FrontDoor/MainContent.tsx | 62 ++++++++----------- .../src/libs/useAssistantCapabilities.ts | 51 ++++++++------- workbench-app/src/libs/useWorkbenchService.ts | 45 +++++++++++++- workbench-app/src/routes/Interact.tsx | 1 + 5 files changed, 99 insertions(+), 66 deletions(-) diff --git a/workbench-app/src/components/FrontDoor/Chat/Chat.tsx b/workbench-app/src/components/FrontDoor/Chat/Chat.tsx index 936a1567..a7f01f07 100644 --- a/workbench-app/src/components/FrontDoor/Chat/Chat.tsx +++ b/workbench-app/src/components/FrontDoor/Chat/Chat.tsx @@ -148,8 +148,10 @@ export const Chat: React.FC = (props) => { isLoading: conversationFilesIsLoading, } = useGetConversationFilesQuery(conversationId); - const { data: assistantCapabilities, isFetching: assistantCapabilitiesIsFetching } = - useGetAssistantCapabilities(assistants); + const { data: assistantCapabilities, isFetching: assistantCapabilitiesIsFetching } = useGetAssistantCapabilities( + assistants ?? [], + { skip: assistantsIsLoading || assistantsError !== undefined }, + ); if (conversationError) { const errorMessage = JSON.stringify(conversationError); diff --git a/workbench-app/src/components/FrontDoor/MainContent.tsx b/workbench-app/src/components/FrontDoor/MainContent.tsx index 136d8b7e..38eebe19 100644 --- a/workbench-app/src/components/FrontDoor/MainContent.tsx +++ b/workbench-app/src/components/FrontDoor/MainContent.tsx @@ -107,43 +107,35 @@ export const MainContent: React.FC = (props) => { return (
- {activeConversationId ? ( - - ) : ( - <> -
- {headerBefore} - - {headerAfter} -
-
-
- Create a new conversation with an assistant - { - setIsValid(isValid); - setTitle(data.title); - setAssistantId(data.assistantId); - setAssistantServiceId(data.assistantServiceId); - setName(data.name); - }} - disabled={submitted} - /> -
- - -
+ <> +
+ {headerBefore} + + {headerAfter} +
+
+
+ Create a new conversation with an assistant + { + setIsValid(isValid); + setTitle(data.title); + setAssistantId(data.assistantId); + setAssistantServiceId(data.assistantServiceId); + setName(data.name); + }} + disabled={submitted} + /> +
+ +
- - )} +
+
); }; diff --git a/workbench-app/src/libs/useAssistantCapabilities.ts b/workbench-app/src/libs/useAssistantCapabilities.ts index a9b0b616..cab9486b 100644 --- a/workbench-app/src/libs/useAssistantCapabilities.ts +++ b/workbench-app/src/libs/useAssistantCapabilities.ts @@ -3,10 +3,8 @@ import { Assistant } from '../models/Assistant'; import { AssistantCapability } from '../models/AssistantCapability'; import { useWorkbenchService } from './useWorkbenchService'; -export function useGetAssistantCapabilities(assistants?: Assistant[]) { - const [isFetching, setIsFetching] = React.useState(false); - const [assistantCapabilities, setAssistantCapabilities] = React.useState(new Set()); - const workbenchService = useWorkbenchService(); +export function useGetAssistantCapabilities(assistants: Assistant[], skipToken: { skip: boolean } = { skip: false }) { + const [isFetching, setIsFetching] = React.useState(true); // Build a memoized set of all capabilities to be used as a default for assistants that do not // specify capabilities @@ -19,32 +17,35 @@ export function useGetAssistantCapabilities(assistants?: Assistant[]) { [], ); + const [assistantCapabilities, setAssistantCapabilities] = React.useState>(allCapabilities); + const workbenchService = useWorkbenchService(); + // Load the capabilities for all assistants and update the state with the result React.useEffect(() => { - let ignore = false; + if (skipToken.skip) { + return; + } - if (!assistants || assistants?.length === 0) { - if (ignore) { - return; - } - // only update the state if the capabilities have changed - if (!assistantCapabilities || allCapabilities.symmetricDifference(assistantCapabilities).size > 0) { + let active = true; + + if (assistants.length === 0) { + if (assistantCapabilities.symmetricDifference(allCapabilities).size > 0) { setAssistantCapabilities(allCapabilities); } + setIsFetching(false); return; } (async () => { - setIsFetching(true); + if (active) { + setIsFetching(true); + } // Get the service info for each assistant - const serviceInfos = ( - await Promise.all( - assistants.map(async (assistant) => { - return await workbenchService.getAssistantServiceInfoAsync(assistant.assistantServiceId); - }), - ) - ).filter((info) => info !== undefined); + const infosResponse = await workbenchService.getAssistantServiceInfosAsync( + assistants.map((assistant) => assistant.assistantServiceId), + ); + const serviceInfos = infosResponse.filter((info) => info !== undefined); // Combine all capabilities from all assistants into a single set const capabilities = serviceInfos.reduce>((acc, info) => { @@ -65,20 +66,18 @@ export function useGetAssistantCapabilities(assistants?: Assistant[]) { return acc; }, new Set()); - if (!ignore) { - // only update the state if the capabilities have changed - if (!assistantCapabilities || capabilities.symmetricDifference(assistantCapabilities).size > 0) { + if (active) { + if (assistantCapabilities.symmetricDifference(capabilities).size > 0) { setAssistantCapabilities(capabilities); } + setIsFetching(false); } - - setIsFetching(false); })(); return () => { - ignore = true; + active = false; }; - }, [allCapabilities, assistantCapabilities, assistants, workbenchService]); + }, [allCapabilities, assistants, assistantCapabilities, skipToken.skip, workbenchService]); return { data: assistantCapabilities, diff --git a/workbench-app/src/libs/useWorkbenchService.ts b/workbench-app/src/libs/useWorkbenchService.ts index a053c4a4..46f0157e 100644 --- a/workbench-app/src/libs/useWorkbenchService.ts +++ b/workbench-app/src/libs/useWorkbenchService.ts @@ -4,10 +4,11 @@ import { InteractionRequiredAuthError } from '@azure/msal-browser'; import { useAccount, useMsal } from '@azure/msal-react'; import React from 'react'; import { AssistantServiceInfo } from '../models/AssistantServiceInfo'; +import { AssistantServiceRegistration } from '../models/AssistantServiceRegistration'; import { ConversationFile } from '../models/ConversationFile'; import { useAppDispatch } from '../redux/app/hooks'; import { addError } from '../redux/features/app/appSlice'; -import { assistantApi, workbenchApi, workflowApi } from '../services/workbench'; +import { assistantApi, assistantServiceRegistrationApi, workbenchApi, workflowApi } from '../services/workbench'; import { AuthHelper } from './AuthHelper'; import { Utility } from './Utility'; import { useEnvironment } from './useEnvironment'; @@ -225,15 +226,52 @@ export const useWorkbenchService = () => { ); const getAssistantServiceInfoAsync = React.useCallback( - async (assistant_service_id: string): Promise => { + async (assistantServiceId: string): Promise => { + const results = await dispatch(assistantApi.endpoints.getAssistantServiceInfo.initiate(assistantServiceId)); + if (results.isError) { + throw results.error; + } + return results.data; + }, + [dispatch], + ); + + const getAssistantServiceRegistrationAsync = React.useCallback( + async (assistantServiceId: string): Promise => { const results = await dispatch( - assistantApi.endpoints.getAssistantServiceInfo.initiate(assistant_service_id), + assistantServiceRegistrationApi.endpoints.getAssistantServiceRegistration.initiate(assistantServiceId), ); + if (results.isError) { + throw results.error; + } return results.data; }, [dispatch], ); + const getAssistantServiceInfosAsync = React.useCallback( + async (assistantServiceIds: string[]): Promise> => { + return await Promise.all( + assistantServiceIds.map(async (assistantServiceId) => { + try { + const registration = await getAssistantServiceRegistrationAsync(assistantServiceId); + if (!registration?.assistantServiceOnline) { + return undefined; + } + } catch (error) { + return undefined; + } + try { + return await getAssistantServiceInfoAsync(assistantServiceId); + } catch (error) { + return undefined; + } + }), + ); + }, + [getAssistantServiceInfoAsync, getAssistantServiceRegistrationAsync], + ); + const exportWorkflowDefinitionAsync = React.useCallback( async (workflowId: string) => { const results = await dispatch(workflowApi.endpoints.getWorkflowDefinition.initiate(workflowId)); @@ -273,6 +311,7 @@ export const useWorkbenchService = () => { exportAssistantAsync, duplicateAssistantAsync, getAssistantServiceInfoAsync, + getAssistantServiceInfosAsync, exportWorkflowDefinitionAsync, getWorkflowDefinitionDefaultsAsync, }; diff --git a/workbench-app/src/routes/Interact.tsx b/workbench-app/src/routes/Interact.tsx index 8b1484e1..21b808df 100644 --- a/workbench-app/src/routes/Interact.tsx +++ b/workbench-app/src/routes/Interact.tsx @@ -100,6 +100,7 @@ export const Interact: React.FC = () => { const localUserId = useAppSelector((state) => state.localUser.id); const { data: assistantCapabilities, isFetching: isFetchingAssistantCapabilities } = useGetAssistantCapabilities( assistants ?? [], + { skip: isLoadingAssistants || assistantsError !== undefined }, ); const siteUtility = useSiteUtility();