Refactor: replace endpointsConfig recoil store with react query (#1085)

This commit is contained in:
Danny Avila 2023-10-21 13:50:29 -04:00 committed by GitHub
parent 7d6a1d260f
commit 4073b7d05d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 83 additions and 49 deletions

View file

@ -2,20 +2,23 @@ import axios from 'axios';
import { useEffect } from 'react'; import { useEffect } from 'react';
import filenamify from 'filenamify'; import filenamify from 'filenamify';
import exportFromJSON from 'export-from-json'; 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 type { TEditPresetProps } from '~/common';
import { useSetOptions, useLocalize } from '~/hooks'; import { useSetOptions, useLocalize } from '~/hooks';
import { Input, Label, Dropdown, Dialog, DialogClose, DialogButton } from '~/components/'; import { Input, Label, Dropdown, Dialog, DialogClose, DialogButton } from '~/components/';
import DialogTemplate from '~/components/ui/DialogTemplate'; import DialogTemplate from '~/components/ui/DialogTemplate';
import PopoverButtons from './PopoverButtons'; import PopoverButtons from './PopoverButtons';
import EndpointSettings from './EndpointSettings'; import EndpointSettings from './EndpointSettings';
import { cn, defaultTextProps, removeFocusOutlines, cleanupPreset } from '~/utils/'; import { cn, defaultTextProps, removeFocusOutlines, cleanupPreset, mapEndpoints } from '~/utils/';
import store from '~/store'; import store from '~/store';
const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }: TEditPresetProps) => { const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }: TEditPresetProps) => {
const [preset, setPreset] = useRecoilState(store.preset); const [preset, setPreset] = useRecoilState(store.preset);
const setPresets = useSetRecoilState(store.presets); const setPresets = useSetRecoilState(store.presets);
const availableEndpoints = useRecoilValue(store.availableEndpoints); const { data: availableEndpoints } = useGetEndpointsQuery({
select: mapEndpoints,
});
const { setOption } = useSetOptions(_preset); const { setOption } = useSetOptions(_preset);
const localize = useLocalize(); const localize = useLocalize();

View file

@ -1,12 +1,11 @@
import { useState } from 'react'; import { useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useGetEndpointsQuery } from 'librechat-data-provider';
import { Settings } from 'lucide-react'; import { Settings } from 'lucide-react';
import { DropdownMenuRadioItem } from '~/components'; import { DropdownMenuRadioItem } from '~/components';
import { Icon } from '~/components/Endpoints'; import { Icon } from '~/components/Endpoints';
import { SetKeyDialog } from '../SetKeyDialog'; import { SetKeyDialog } from '../SetKeyDialog';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import store from '~/store';
import { cn, alternateName } from '~/utils'; import { cn, alternateName } from '~/utils';
export default function ModelItem({ export default function ModelItem({
@ -19,7 +18,7 @@ export default function ModelItem({
isSelected: boolean; isSelected: boolean;
}) { }) {
const [isDialogOpen, setDialogOpen] = useState(false); const [isDialogOpen, setDialogOpen] = useState(false);
const endpointsConfig = useRecoilValue(store.endpointsConfig); const { data: endpointsConfig } = useGetEndpointsQuery();
const icon = Icon({ const icon = Icon({
size: 20, size: 20,

View file

@ -1,8 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { Trash2 } from 'lucide-react'; import { Trash2 } from 'lucide-react';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useRecoilValue, useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { useDeletePresetMutation, useCreatePresetMutation } from 'librechat-data-provider'; import {
useDeletePresetMutation,
useCreatePresetMutation,
useGetEndpointsQuery,
} from 'librechat-data-provider';
import { Icon, EditPresetDialog } from '~/components/Endpoints'; import { Icon, EditPresetDialog } from '~/components/Endpoints';
import EndpointItems from './EndpointItems'; import EndpointItems from './EndpointItems';
import PresetItems from './PresetItems'; import PresetItems from './PresetItems';
@ -23,7 +27,7 @@ import {
TooltipContent, TooltipContent,
} from '~/components/ui/'; } from '~/components/ui/';
import DialogTemplate from '~/components/ui/DialogTemplate'; 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 { useLocalize, useLocalStorage, useConversation, useDefaultConvo } from '~/hooks';
import store from '~/store'; import store from '~/store';
@ -37,7 +41,10 @@ export default function NewConversationMenu() {
const [preset, setPreset] = useState(false); const [preset, setPreset] = useState(false);
const [conversation, setConversation] = useRecoilState(store.conversation) ?? {}; const [conversation, setConversation] = useRecoilState(store.conversation) ?? {};
const [messages, setMessages] = useRecoilState(store.messages); const [messages, setMessages] = useRecoilState(store.messages);
const availableEndpoints = useRecoilValue(store.availableEndpoints);
const { data: availableEndpoints } = useGetEndpointsQuery({
select: mapEndpoints,
});
const [presets, setPresets] = useRecoilState(store.presets); const [presets, setPresets] = useRecoilState(store.presets);
const modularEndpoints = new Set(['gptPlugins', 'anthropic', 'google', 'openAI']); const modularEndpoints = new Set(['gptPlugins', 'anthropic', 'google', 'openAI']);

View file

@ -1,16 +1,11 @@
import { useRecoilValue } from 'recoil';
import { Disclosure } from '@headlessui/react'; import { Disclosure } from '@headlessui/react';
import { useCallback, memo, ReactNode } from 'react'; import { useCallback, memo, ReactNode } from 'react';
import { useGetEndpointsQuery } from 'librechat-data-provider';
import type { TResPlugin, TInput } from 'librechat-data-provider'; import type { TResPlugin, TInput } from 'librechat-data-provider';
import { ChevronDownIcon, LucideProps } from 'lucide-react'; import { ChevronDownIcon, LucideProps } from 'lucide-react';
import { cn, formatJSON } from '~/utils'; import { cn, formatJSON } from '~/utils';
import { Spinner } from '~/components'; import { Spinner } from '~/components';
import CodeBlock from './CodeBlock'; import CodeBlock from './CodeBlock';
import store from '~/store';
type PluginsMap = {
[pluginKey: string]: string;
};
type PluginIconProps = LucideProps & { type PluginIconProps = LucideProps & {
className?: string; className?: string;
@ -36,7 +31,9 @@ type PluginProps = {
}; };
const Plugin: React.FC<PluginProps> = ({ plugin }) => { const Plugin: React.FC<PluginProps> = ({ plugin }) => {
const plugins: PluginsMap = useRecoilValue(store.plugins); const { data: plugins } = useGetEndpointsQuery({
select: (data) => data?.gptPlugins?.plugins,
});
const getPluginName = useCallback( const getPluginName = useCallback(
(pluginKey: string) => { (pluginKey: string) => {
@ -47,7 +44,7 @@ const Plugin: React.FC<PluginProps> = ({ plugin }) => {
if (pluginKey === 'n/a' || pluginKey === 'self reflection') { if (pluginKey === 'n/a' || pluginKey === 'self reflection') {
return pluginKey; return pluginKey;
} }
return plugins[pluginKey] ?? 'self reflection'; return plugins?.[pluginKey] ?? 'self reflection';
}, },
[plugins], [plugins],
); );

View file

@ -1,5 +1,6 @@
import { useCallback } from 'react'; 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 { import type {
TConversation, TConversation,
TMessagesAtom, TMessagesAtom,
@ -15,7 +16,7 @@ const useConversation = () => {
const setMessages = useSetRecoilState<TMessagesAtom>(store.messages); const setMessages = useSetRecoilState<TMessagesAtom>(store.messages);
const setSubmission = useSetRecoilState<TSubmission | null>(store.submission); const setSubmission = useSetRecoilState<TSubmission | null>(store.submission);
const resetLatestMessage = useResetRecoilState(store.latestMessage); const resetLatestMessage = useResetRecoilState(store.latestMessage);
const endpointsConfig = useRecoilValue(store.endpointsConfig); const { data: endpointsConfig = {} } = useGetEndpointsQuery();
const switchToConversation = useRecoilCallback( const switchToConversation = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>

View file

@ -1,4 +1,5 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useGetEndpointsQuery } from 'librechat-data-provider';
import type { TConversation, TPreset } from 'librechat-data-provider'; import type { TConversation, TPreset } from 'librechat-data-provider';
import { getDefaultEndpoint, buildDefaultConvo } from '~/utils'; import { getDefaultEndpoint, buildDefaultConvo } from '~/utils';
import store from '~/store'; import store from '~/store';
@ -6,7 +7,7 @@ import store from '~/store';
type TDefaultConvo = { conversation: Partial<TConversation>; preset?: Partial<TPreset> | null }; type TDefaultConvo = { conversation: Partial<TConversation>; preset?: Partial<TPreset> | null };
const useDefaultConvo = () => { const useDefaultConvo = () => {
const endpointsConfig = useRecoilValue(store.endpointsConfig); const { data: endpointsConfig = {} } = useGetEndpointsQuery();
const modelsConfig = useRecoilValue(store.modelsConfig); const modelsConfig = useRecoilValue(store.modelsConfig);
const getDefaultConversation = ({ conversation, preset }: TDefaultConvo) => { const getDefaultConversation = ({ conversation, preset }: TDefaultConvo) => {

View file

@ -1,6 +1,6 @@
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; 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 { TMessage, TSubmission, TEndpointOption } from 'librechat-data-provider';
import type { TAskFunction } from '~/common'; import type { TAskFunction } from '~/common';
import useUserKey from './useUserKey'; import useUserKey from './useUserKey';
@ -14,7 +14,7 @@ const useMessageHandler = () => {
const currentConversation = useRecoilValue(store.conversation) || { endpoint: null }; const currentConversation = useRecoilValue(store.conversation) || { endpoint: null };
const setSubmission = useSetRecoilState(store.submission); const setSubmission = useSetRecoilState(store.submission);
const isSubmitting = useRecoilValue(store.isSubmitting); const isSubmitting = useRecoilValue(store.isSubmitting);
const endpointsConfig = useRecoilValue(store.endpointsConfig); const { data: endpointsConfig } = useGetEndpointsQuery();
const [messages, setMessages] = useRecoilState(store.messages); const [messages, setMessages] = useRecoilState(store.messages);
const { endpoint } = currentConversation; const { endpoint } = currentConversation;
const { getExpiry } = useUserKey(endpoint ?? ''); const { getExpiry } = useUserKey(endpoint ?? '');

View file

@ -1,6 +1,6 @@
import { TPreset } from 'librechat-data-provider'; import { TPreset } from 'librechat-data-provider';
import type { TSetOptionsPayload, TSetExample, TSetOption } from '~/common'; import type { TSetOptionsPayload, TSetExample, TSetOption } from '~/common';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState } from 'recoil';
import { cleanupPreset } from '~/utils'; import { cleanupPreset } from '~/utils';
import store from '~/store'; import store from '~/store';
@ -8,7 +8,7 @@ type TUsePresetOptions = (preset?: TPreset | boolean | null) => TSetOptionsPaylo
const usePresetOptions: TUsePresetOptions = (_preset) => { const usePresetOptions: TUsePresetOptions = (_preset) => {
const [preset, setPreset] = useRecoilState(store.preset); const [preset, setPreset] = useRecoilState(store.preset);
const endpointsConfig = useRecoilValue(store.endpointsConfig);
if (!_preset) { if (!_preset) {
return false; return false;
} }

View file

@ -1,11 +1,13 @@
import { useRecoilValue } from 'recoil';
import { useMemo, useCallback } from 'react'; import { useMemo, useCallback } from 'react';
import { useUpdateUserKeysMutation, useUserKeyQuery } from 'librechat-data-provider'; import {
import store from '~/store'; useUpdateUserKeysMutation,
useUserKeyQuery,
useGetEndpointsQuery,
} from 'librechat-data-provider';
const useUserKey = (endpoint: string) => { const useUserKey = (endpoint: string) => {
const endpointsConfig = useRecoilValue(store.endpointsConfig); const { data: endpointsConfig } = useGetEndpointsQuery();
const config = endpointsConfig[endpoint]; const config = endpointsConfig?.[endpoint];
const { azure } = config ?? {}; const { azure } = config ?? {};
let keyEndpoint = endpoint; let keyEndpoint = endpoint;

View file

@ -3,7 +3,6 @@ import { useEffect, useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { Outlet } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import { import {
useGetEndpointsQuery,
useGetModelsQuery, useGetModelsQuery,
useGetPresetsQuery, useGetPresetsQuery,
useGetSearchEnabledQuery, useGetSearchEnabledQuery,
@ -26,10 +25,8 @@ export default function Root() {
const setPresets = useSetRecoilState(store.presets); const setPresets = useSetRecoilState(store.presets);
const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled); const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled);
const setEndpointsConfig = useSetRecoilState(store.endpointsConfig);
const setModelsConfig = useSetRecoilState(store.modelsConfig); const setModelsConfig = useSetRecoilState(store.modelsConfig);
const endpointsQuery = useGetEndpointsQuery();
const searchEnabledQuery = useGetSearchEnabledQuery({ enabled: isAuthenticated }); const searchEnabledQuery = useGetSearchEnabledQuery({ enabled: isAuthenticated });
const modelsQuery = useGetModelsQuery({ enabled: isAuthenticated }); const modelsQuery = useGetModelsQuery({ enabled: isAuthenticated });
const presetsQuery = useGetPresetsQuery({ enabled: !!user }); const presetsQuery = useGetPresetsQuery({ enabled: !!user });
@ -38,14 +35,6 @@ export default function Root() {
localStorage.setItem('navVisible', JSON.stringify(navVisible)); localStorage.setItem('navVisible', JSON.stringify(navVisible));
}, [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(() => { useEffect(() => {
if (modelsQuery.data) { if (modelsQuery.data) {
setModelsConfig(modelsQuery.data); setModelsConfig(modelsQuery.data);

View file

@ -2,6 +2,7 @@ export * from './json';
export * from './languages'; export * from './languages';
export { default as cn } from './cn'; export { default as cn } from './cn';
export { default as buildTree } from './buildTree'; export { default as buildTree } from './buildTree';
export { default as mapEndpoints } from './mapEndpoints';
export { default as getLoginError } from './getLoginError'; export { default as getLoginError } from './getLoginError';
export { default as cleanupPreset } from './cleanupPreset'; export { default as cleanupPreset } from './cleanupPreset';
export { default as validateIframe } from './validateIframe'; export { default as validateIframe } from './validateIframe';

View file

@ -0,0 +1,27 @@
import type { TEndpointsConfig } from 'librechat-data-provider';
const getEndpointsFilter = (config: TEndpointsConfig) => {
const filter: Record<string, boolean> = {};
for (const key of Object.keys(config)) {
filter[key] = !!config[key];
}
return filter;
};
const getAvailableEndpoints = (filter: Record<string, boolean>) => {
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);
}

View file

@ -101,7 +101,7 @@ export const searchConversations = async (
return request.get(endpoints.search(q, pageNumber)); return request.get(endpoints.search(q, pageNumber));
}; };
export const getAIEndpoints = () => { export const getAIEndpoints = (): Promise<t.TEndpointsConfig> => {
return request.get(endpoints.aiEndpoints()); return request.get(endpoints.aiEndpoints());
}; };

View file

@ -230,12 +230,19 @@ export const useGetSearchEnabledQuery = (
}); });
}; };
export const useGetEndpointsQuery = (): QueryObserverResult<t.TEndpointsConfig> => { export const useGetEndpointsQuery = <TData = t.TEndpointsConfig>(
return useQuery([QueryKeys.endpoints], () => dataService.getAIEndpoints(), { config?: UseQueryOptions<t.TEndpointsConfig, unknown, TData>,
refetchOnWindowFocus: false, ): QueryObserverResult<TData> => {
refetchOnReconnect: false, return useQuery<t.TEndpointsConfig, unknown, TData>(
refetchOnMount: false, [QueryKeys.endpoints],
}); () => dataService.getAIEndpoints(),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
},
);
}; };
export const useGetModelsQuery = ( export const useGetModelsQuery = (

View file

@ -120,7 +120,7 @@ export type TConfig = {
availableModels?: []; availableModels?: [];
userProvide?: boolean | null; userProvide?: boolean | null;
availableTools?: []; availableTools?: [];
plugins?: []; plugins?: Record<string, string>;
azure?: boolean; azure?: boolean;
}; };