Skip to content

Commit

Permalink
Avoid querying capabilities from offline assistants (#213)
Browse files Browse the repository at this point in the history
To avoid the continuous reload loop
  • Loading branch information
markwaddle authored Nov 5, 2024
1 parent f237727 commit c48cafd
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 66 deletions.
6 changes: 4 additions & 2 deletions workbench-app/src/components/FrontDoor/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,10 @@ export const Chat: React.FC<ChatProps> = (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);
Expand Down
62 changes: 27 additions & 35 deletions workbench-app/src/components/FrontDoor/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,43 +107,35 @@ export const MainContent: React.FC<MainContentProps> = (props) => {

return (
<div className={classes.root}>
{activeConversationId ? (
<Chat conversationId={activeConversationId} headerBefore={headerBefore} headerAfter={headerAfter} />
) : (
<>
<div className={classes.header}>
{headerBefore}
<ExperimentalNotice />
{headerAfter}
</div>
<div className={classes.body}>
<div className={classes.content}>
<Title3>Create a new conversation with an assistant</Title3>
<NewConversationForm
onSubmit={handleCreate}
onChange={(isValid, data) => {
setIsValid(isValid);
setTitle(data.title);
setAssistantId(data.assistantId);
setAssistantServiceId(data.assistantServiceId);
setName(data.name);
}}
disabled={submitted}
/>
<div className={classes.actions}>
<ConversationsImport
appearance="outline"
onImport={handleImport}
disabled={submitted}
/>
<Button appearance="primary" onClick={handleCreate} disabled={!isValid || submitted}>
Create
</Button>
</div>
<>
<div className={classes.header}>
{headerBefore}
<ExperimentalNotice />
{headerAfter}
</div>
<div className={classes.body}>
<div className={classes.content}>
<Title3>Create a new conversation with an assistant</Title3>
<NewConversationForm
onSubmit={handleCreate}
onChange={(isValid, data) => {
setIsValid(isValid);
setTitle(data.title);
setAssistantId(data.assistantId);
setAssistantServiceId(data.assistantServiceId);
setName(data.name);
}}
disabled={submitted}
/>
<div className={classes.actions}>
<ConversationsImport appearance="outline" onImport={handleImport} disabled={submitted} />
<Button appearance="primary" onClick={handleCreate} disabled={!isValid || submitted}>
Create
</Button>
</div>
</div>
</>
)}
</div>
</>
</div>
);
};
51 changes: 25 additions & 26 deletions workbench-app/src/libs/useAssistantCapabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>(false);
const [assistantCapabilities, setAssistantCapabilities] = React.useState(new Set<AssistantCapability>());
const workbenchService = useWorkbenchService();
export function useGetAssistantCapabilities(assistants: Assistant[], skipToken: { skip: boolean } = { skip: false }) {
const [isFetching, setIsFetching] = React.useState<boolean>(true);

// Build a memoized set of all capabilities to be used as a default for assistants that do not
// specify capabilities
Expand All @@ -19,32 +17,35 @@ export function useGetAssistantCapabilities(assistants?: Assistant[]) {
[],
);

const [assistantCapabilities, setAssistantCapabilities] = React.useState<Set<AssistantCapability>>(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<Set<AssistantCapability>>((acc, info) => {
Expand All @@ -65,20 +66,18 @@ export function useGetAssistantCapabilities(assistants?: Assistant[]) {
return acc;
}, new Set<AssistantCapability>());

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,
Expand Down
45 changes: 42 additions & 3 deletions workbench-app/src/libs/useWorkbenchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -225,15 +226,52 @@ export const useWorkbenchService = () => {
);

const getAssistantServiceInfoAsync = React.useCallback(
async (assistant_service_id: string): Promise<AssistantServiceInfo | undefined> => {
async (assistantServiceId: string): Promise<AssistantServiceInfo | undefined> => {
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<AssistantServiceRegistration | undefined> => {
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<Array<AssistantServiceInfo | undefined>> => {
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));
Expand Down Expand Up @@ -273,6 +311,7 @@ export const useWorkbenchService = () => {
exportAssistantAsync,
duplicateAssistantAsync,
getAssistantServiceInfoAsync,
getAssistantServiceInfosAsync,
exportWorkflowDefinitionAsync,
getWorkflowDefinitionDefaultsAsync,
};
Expand Down
1 change: 1 addition & 0 deletions workbench-app/src/routes/Interact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit c48cafd

Please sign in to comment.