diff --git a/client/src/components/Endpoints/EditPresetDialog.tsx b/client/src/components/Endpoints/EditPresetDialog.tsx index ed87f981f..1a13239ae 100644 --- a/client/src/components/Endpoints/EditPresetDialog.tsx +++ b/client/src/components/Endpoints/EditPresetDialog.tsx @@ -2,20 +2,23 @@ import axios from 'axios'; import { useEffect } from 'react'; import filenamify from 'filenamify'; import exportFromJSON from 'export-from-json'; -import { useSetRecoilState, useRecoilState, useRecoilValue } from 'recoil'; +import { useSetRecoilState, useRecoilState } from 'recoil'; +import { useGetEndpointsQuery } from 'librechat-data-provider'; import type { TEditPresetProps } from '~/common'; import { useSetOptions, useLocalize } from '~/hooks'; import { Input, Label, Dropdown, Dialog, DialogClose, DialogButton } from '~/components/'; import DialogTemplate from '~/components/ui/DialogTemplate'; import PopoverButtons from './PopoverButtons'; import EndpointSettings from './EndpointSettings'; -import { cn, defaultTextProps, removeFocusOutlines, cleanupPreset } from '~/utils/'; +import { cn, defaultTextProps, removeFocusOutlines, cleanupPreset, mapEndpoints } from '~/utils/'; import store from '~/store'; const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }: TEditPresetProps) => { const [preset, setPreset] = useRecoilState(store.preset); const setPresets = useSetRecoilState(store.presets); - const availableEndpoints = useRecoilValue(store.availableEndpoints); + const { data: availableEndpoints } = useGetEndpointsQuery({ + select: mapEndpoints, + }); const { setOption } = useSetOptions(_preset); const localize = useLocalize(); diff --git a/client/src/components/Input/EndpointMenu/EndpointItem.tsx b/client/src/components/Input/EndpointMenu/EndpointItem.tsx index f21917f27..2c7457498 100644 --- a/client/src/components/Input/EndpointMenu/EndpointItem.tsx +++ b/client/src/components/Input/EndpointMenu/EndpointItem.tsx @@ -1,12 +1,11 @@ import { useState } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useGetEndpointsQuery } from 'librechat-data-provider'; import { Settings } from 'lucide-react'; import { DropdownMenuRadioItem } from '~/components'; import { Icon } from '~/components/Endpoints'; import { SetKeyDialog } from '../SetKeyDialog'; import { useLocalize } from '~/hooks'; -import store from '~/store'; import { cn, alternateName } from '~/utils'; export default function ModelItem({ @@ -19,7 +18,7 @@ export default function ModelItem({ isSelected: boolean; }) { const [isDialogOpen, setDialogOpen] = useState(false); - const endpointsConfig = useRecoilValue(store.endpointsConfig); + const { data: endpointsConfig } = useGetEndpointsQuery(); const icon = Icon({ size: 20, diff --git a/client/src/components/Input/EndpointMenu/EndpointMenu.jsx b/client/src/components/Input/EndpointMenu/EndpointMenu.jsx index 2372e3b4c..1772f02c7 100644 --- a/client/src/components/Input/EndpointMenu/EndpointMenu.jsx +++ b/client/src/components/Input/EndpointMenu/EndpointMenu.jsx @@ -1,8 +1,12 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { Trash2 } from 'lucide-react'; import { useState, useEffect } from 'react'; -import { useRecoilValue, useRecoilState } from 'recoil'; -import { useDeletePresetMutation, useCreatePresetMutation } from 'librechat-data-provider'; +import { useRecoilState } from 'recoil'; +import { + useDeletePresetMutation, + useCreatePresetMutation, + useGetEndpointsQuery, +} from 'librechat-data-provider'; import { Icon, EditPresetDialog } from '~/components/Endpoints'; import EndpointItems from './EndpointItems'; import PresetItems from './PresetItems'; @@ -23,7 +27,7 @@ import { TooltipContent, } from '~/components/ui/'; import DialogTemplate from '~/components/ui/DialogTemplate'; -import { cn, cleanupPreset } from '~/utils'; +import { cn, cleanupPreset, mapEndpoints } from '~/utils'; import { useLocalize, useLocalStorage, useConversation, useDefaultConvo } from '~/hooks'; import store from '~/store'; @@ -37,7 +41,10 @@ export default function NewConversationMenu() { const [preset, setPreset] = useState(false); const [conversation, setConversation] = useRecoilState(store.conversation) ?? {}; const [messages, setMessages] = useRecoilState(store.messages); - const availableEndpoints = useRecoilValue(store.availableEndpoints); + + const { data: availableEndpoints } = useGetEndpointsQuery({ + select: mapEndpoints, + }); const [presets, setPresets] = useRecoilState(store.presets); const modularEndpoints = new Set(['gptPlugins', 'anthropic', 'google', 'openAI']); diff --git a/client/src/components/Messages/Content/Plugin.tsx b/client/src/components/Messages/Content/Plugin.tsx index f5a34bb79..3e8c24784 100644 --- a/client/src/components/Messages/Content/Plugin.tsx +++ b/client/src/components/Messages/Content/Plugin.tsx @@ -1,16 +1,11 @@ -import { useRecoilValue } from 'recoil'; import { Disclosure } from '@headlessui/react'; import { useCallback, memo, ReactNode } from 'react'; +import { useGetEndpointsQuery } from 'librechat-data-provider'; import type { TResPlugin, TInput } from 'librechat-data-provider'; import { ChevronDownIcon, LucideProps } from 'lucide-react'; import { cn, formatJSON } from '~/utils'; import { Spinner } from '~/components'; import CodeBlock from './CodeBlock'; -import store from '~/store'; - -type PluginsMap = { - [pluginKey: string]: string; -}; type PluginIconProps = LucideProps & { className?: string; @@ -36,7 +31,9 @@ type PluginProps = { }; const Plugin: React.FC = ({ plugin }) => { - const plugins: PluginsMap = useRecoilValue(store.plugins); + const { data: plugins } = useGetEndpointsQuery({ + select: (data) => data?.gptPlugins?.plugins, + }); const getPluginName = useCallback( (pluginKey: string) => { @@ -47,7 +44,7 @@ const Plugin: React.FC = ({ plugin }) => { if (pluginKey === 'n/a' || pluginKey === 'self reflection') { return pluginKey; } - return plugins[pluginKey] ?? 'self reflection'; + return plugins?.[pluginKey] ?? 'self reflection'; }, [plugins], ); diff --git a/client/src/hooks/useConversation.ts b/client/src/hooks/useConversation.ts index 6e23ebe3b..4a1f64341 100644 --- a/client/src/hooks/useConversation.ts +++ b/client/src/hooks/useConversation.ts @@ -1,5 +1,6 @@ import { useCallback } from 'react'; -import { useSetRecoilState, useResetRecoilState, useRecoilCallback, useRecoilValue } from 'recoil'; +import { useSetRecoilState, useResetRecoilState, useRecoilCallback } from 'recoil'; +import { useGetEndpointsQuery } from 'librechat-data-provider'; import type { TConversation, TMessagesAtom, @@ -15,7 +16,7 @@ const useConversation = () => { const setMessages = useSetRecoilState(store.messages); const setSubmission = useSetRecoilState(store.submission); const resetLatestMessage = useResetRecoilState(store.latestMessage); - const endpointsConfig = useRecoilValue(store.endpointsConfig); + const { data: endpointsConfig = {} } = useGetEndpointsQuery(); const switchToConversation = useRecoilCallback( ({ snapshot }) => diff --git a/client/src/hooks/useDefaultConvo.ts b/client/src/hooks/useDefaultConvo.ts index c7ef1fd11..0d8707763 100644 --- a/client/src/hooks/useDefaultConvo.ts +++ b/client/src/hooks/useDefaultConvo.ts @@ -1,4 +1,5 @@ import { useRecoilValue } from 'recoil'; +import { useGetEndpointsQuery } from 'librechat-data-provider'; import type { TConversation, TPreset } from 'librechat-data-provider'; import { getDefaultEndpoint, buildDefaultConvo } from '~/utils'; import store from '~/store'; @@ -6,7 +7,7 @@ import store from '~/store'; type TDefaultConvo = { conversation: Partial; preset?: Partial | null }; const useDefaultConvo = () => { - const endpointsConfig = useRecoilValue(store.endpointsConfig); + const { data: endpointsConfig = {} } = useGetEndpointsQuery(); const modelsConfig = useRecoilValue(store.modelsConfig); const getDefaultConversation = ({ conversation, preset }: TDefaultConvo) => { diff --git a/client/src/hooks/useMessageHandler.ts b/client/src/hooks/useMessageHandler.ts index d3915b929..d580d1558 100644 --- a/client/src/hooks/useMessageHandler.ts +++ b/client/src/hooks/useMessageHandler.ts @@ -1,6 +1,6 @@ import { v4 } from 'uuid'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; -import { parseConvo, getResponseSender } from 'librechat-data-provider'; +import { parseConvo, getResponseSender, useGetEndpointsQuery } from 'librechat-data-provider'; import type { TMessage, TSubmission, TEndpointOption } from 'librechat-data-provider'; import type { TAskFunction } from '~/common'; import useUserKey from './useUserKey'; @@ -14,7 +14,7 @@ const useMessageHandler = () => { const currentConversation = useRecoilValue(store.conversation) || { endpoint: null }; const setSubmission = useSetRecoilState(store.submission); const isSubmitting = useRecoilValue(store.isSubmitting); - const endpointsConfig = useRecoilValue(store.endpointsConfig); + const { data: endpointsConfig } = useGetEndpointsQuery(); const [messages, setMessages] = useRecoilState(store.messages); const { endpoint } = currentConversation; const { getExpiry } = useUserKey(endpoint ?? ''); diff --git a/client/src/hooks/usePresetOptions.ts b/client/src/hooks/usePresetOptions.ts index a52618553..190200967 100644 --- a/client/src/hooks/usePresetOptions.ts +++ b/client/src/hooks/usePresetOptions.ts @@ -1,6 +1,6 @@ import { TPreset } from 'librechat-data-provider'; import type { TSetOptionsPayload, TSetExample, TSetOption } from '~/common'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilState } from 'recoil'; import { cleanupPreset } from '~/utils'; import store from '~/store'; @@ -8,7 +8,7 @@ type TUsePresetOptions = (preset?: TPreset | boolean | null) => TSetOptionsPaylo const usePresetOptions: TUsePresetOptions = (_preset) => { const [preset, setPreset] = useRecoilState(store.preset); - const endpointsConfig = useRecoilValue(store.endpointsConfig); + if (!_preset) { return false; } diff --git a/client/src/hooks/useUserKey.ts b/client/src/hooks/useUserKey.ts index d718926fa..bc81f983c 100644 --- a/client/src/hooks/useUserKey.ts +++ b/client/src/hooks/useUserKey.ts @@ -1,11 +1,13 @@ -import { useRecoilValue } from 'recoil'; import { useMemo, useCallback } from 'react'; -import { useUpdateUserKeysMutation, useUserKeyQuery } from 'librechat-data-provider'; -import store from '~/store'; +import { + useUpdateUserKeysMutation, + useUserKeyQuery, + useGetEndpointsQuery, +} from 'librechat-data-provider'; const useUserKey = (endpoint: string) => { - const endpointsConfig = useRecoilValue(store.endpointsConfig); - const config = endpointsConfig[endpoint]; + const { data: endpointsConfig } = useGetEndpointsQuery(); + const config = endpointsConfig?.[endpoint]; const { azure } = config ?? {}; let keyEndpoint = endpoint; diff --git a/client/src/routes/Root.tsx b/client/src/routes/Root.tsx index c4711fb02..cfd3e5a6b 100644 --- a/client/src/routes/Root.tsx +++ b/client/src/routes/Root.tsx @@ -3,7 +3,6 @@ import { useEffect, useState } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { Outlet } from 'react-router-dom'; import { - useGetEndpointsQuery, useGetModelsQuery, useGetPresetsQuery, useGetSearchEnabledQuery, @@ -26,10 +25,8 @@ export default function Root() { const setPresets = useSetRecoilState(store.presets); const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled); - const setEndpointsConfig = useSetRecoilState(store.endpointsConfig); const setModelsConfig = useSetRecoilState(store.modelsConfig); - const endpointsQuery = useGetEndpointsQuery(); const searchEnabledQuery = useGetSearchEnabledQuery({ enabled: isAuthenticated }); const modelsQuery = useGetModelsQuery({ enabled: isAuthenticated }); const presetsQuery = useGetPresetsQuery({ enabled: !!user }); @@ -38,14 +35,6 @@ export default function Root() { localStorage.setItem('navVisible', JSON.stringify(navVisible)); }, [navVisible]); - useEffect(() => { - if (endpointsQuery.data) { - setEndpointsConfig(endpointsQuery.data); - } else if (endpointsQuery.isError) { - console.error('Failed to get endpoints', endpointsQuery.error); - } - }, [endpointsQuery.data, endpointsQuery.isError]); - useEffect(() => { if (modelsQuery.data) { setModelsConfig(modelsQuery.data); diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts index 60b4d3c8f..69cf2dec6 100644 --- a/client/src/utils/index.ts +++ b/client/src/utils/index.ts @@ -2,6 +2,7 @@ export * from './json'; export * from './languages'; export { default as cn } from './cn'; export { default as buildTree } from './buildTree'; +export { default as mapEndpoints } from './mapEndpoints'; export { default as getLoginError } from './getLoginError'; export { default as cleanupPreset } from './cleanupPreset'; export { default as validateIframe } from './validateIframe'; diff --git a/client/src/utils/mapEndpoints.ts b/client/src/utils/mapEndpoints.ts new file mode 100644 index 000000000..e7a2c3547 --- /dev/null +++ b/client/src/utils/mapEndpoints.ts @@ -0,0 +1,27 @@ +import type { TEndpointsConfig } from 'librechat-data-provider'; + +const getEndpointsFilter = (config: TEndpointsConfig) => { + const filter: Record = {}; + for (const key of Object.keys(config)) { + filter[key] = !!config[key]; + } + return filter; +}; + +const getAvailableEndpoints = (filter: Record) => { + const endpoints = [ + 'azureOpenAI', + 'openAI', + 'chatGPTBrowser', + 'gptPlugins', + 'bingAI', + 'google', + 'anthropic', + ]; + return endpoints.filter((endpoint) => filter[endpoint]); +}; + +export default function mapEndpoints(config: TEndpointsConfig) { + const filter = getEndpointsFilter(config); + return getAvailableEndpoints(filter); +} diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts index 2a3739e96..71b12b483 100644 --- a/packages/data-provider/src/data-service.ts +++ b/packages/data-provider/src/data-service.ts @@ -101,7 +101,7 @@ export const searchConversations = async ( return request.get(endpoints.search(q, pageNumber)); }; -export const getAIEndpoints = () => { +export const getAIEndpoints = (): Promise => { return request.get(endpoints.aiEndpoints()); }; diff --git a/packages/data-provider/src/react-query-service.ts b/packages/data-provider/src/react-query-service.ts index bb87e3940..57770e4b9 100644 --- a/packages/data-provider/src/react-query-service.ts +++ b/packages/data-provider/src/react-query-service.ts @@ -230,12 +230,19 @@ export const useGetSearchEnabledQuery = ( }); }; -export const useGetEndpointsQuery = (): QueryObserverResult => { - return useQuery([QueryKeys.endpoints], () => dataService.getAIEndpoints(), { - refetchOnWindowFocus: false, - refetchOnReconnect: false, - refetchOnMount: false, - }); +export const useGetEndpointsQuery = ( + config?: UseQueryOptions, +): QueryObserverResult => { + return useQuery( + [QueryKeys.endpoints], + () => dataService.getAIEndpoints(), + { + refetchOnWindowFocus: false, + refetchOnReconnect: false, + refetchOnMount: false, + ...config, + }, + ); }; export const useGetModelsQuery = ( diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 508e21333..a5e6f25be 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -120,7 +120,7 @@ export type TConfig = { availableModels?: []; userProvide?: boolean | null; availableTools?: []; - plugins?: []; + plugins?: Record; azure?: boolean; };