LibreChat/client/src/data-provider/queries.ts
Marco Beretta 7f29f2f676
🎨 feat: UI Refresh for Enhanced UX (#6346)
*  feat: Add Expand Chat functionality and improve UI components

*  feat: Introduce Chat Badges feature with editing capabilities and UI enhancements

*  feat: re-implement file attachment functionality with new components and improved UI

*  feat: Enhance BadgeRow component with drag-and-drop functionality and add animations for better user experience

*  feat: Add useChatBadges hook and enhance Badge component with animations and toggle functionality

* feat: Improve Add/Delete Badges + style and bug fixes

*  feat: Refactor EditBadges component and optimize useChatBadges hook for improved performance and readability

*  feat: Add type definition for LucideIcon in EditBadges component

* refactor: Clean up BadgeRow component by removing outdated comment and improving code readability

* refactor: Rename app-icon class to badge-icon for consistency and improve badge styling

* feat: Add Center Chat Input toggle and update related components for improved UI/UX

* refactor: Simplify ChatView and MessagesView components for improved readability and performance

* refactor: Improve layout and positioning of scroll button in MessagesView component

* refactor: Adjust scroll button position in MessagesView component for better visibility

* refactor: Remove redundant background class from Badge component for cleaner styling

* feat: disable chat badges

* refactor: adjust positioning of scroll button and popover for improved layout

* refactor: simplify class names in ChatForm and RemoveFile components for cleaner code

* refactor: move Switcher to HeaderOptions from SidePanel

* fix(Landing): duplicate description

* feat: add SplitText component for animated text display and update Landing component to use it

* feat(Chat): add ConversationStarters component and integrate it into ChatView; remove ConvoStarter component

* feat(Chat): enhance Message component layout and styling for improved readability

* feat(ControlCombobox, Select): enhance styling and add animation for improved UI experience

* feat(Chat): update Header and HeaderNewChat components for improved layout and styling

* feat(Chat): add ModelDropdown (now includes both endpoint and model) and refactor Menu components for improved UI

* feat(ModelDropdown): add Agent Select; removed old AgentSwitcher components

* feat(ModelDropdown): add settings button for user key configuration

* fix(ModelDropdown): the model dropdown wasn't opening automatically when opening the endpoint one

* refactor(Chat): remove unused EndpointsMenu and related components to streamline codebase

* feat: enhance greeting message and improve accessibility fro ModelDropdown

* refactor(Endpoints): add new hooks and components for endpoint management

* feat(Endpoint): add support for modelSpecs

* feat(Endpoints): add mobile support

* fix: type issues

* fix(modelSpec): type issue

* fix(EndpointMenuDropdown): double overflow scroller in mobile model list

* fix: search model on mobile

* refactor: Endpoint/Model/modelSpec dropdown

* refactor: reorganize imports in Endpoint components

* refactor: remove unused translation keys from English locale

* BREAKING: moving to ariakit with new CustomMenu

* refactor: remove unnecessary comments

* refactor: remove EndpointItem, ModelDropdownButton, SpecIcon, and SpecItem components

* 🔧 fix: AI Icon bump when regenerating message

* wip: chat UI refactoring, fix issues

* chore: add recent update to useAutoSave

* feat: add access control for agent permissions in useMentions hook

* refactor: streamline ModelSelector by removing unused endpoints logic

* refactor: enhance ModelSelector and context by integrating endpointsConfig and improving type usage

* feat: update ModelSelectorContext to utilize conversation data for initial state

* feat: add selector effects for synced endpoint handling

* feat: add guard clause for conversation endpoint in useSelectorEffects hook

* fix: safely call onSelectMention and add autofocus to mention input

* chore: typing

* refactor: ModelSelector to streamline key dialog handling and improve endpoint rendering

* refactor: extract SettingsButton component for cleaner endpoint item rendering

* wip: first pass, expand set api key

* wip: first pass, expanding set key

* refactor: update EndpointItem styles for improved layout and hover effects

* refactor: adjust padding in EndpointItem for improved layout consistency

* refactor: update preset structure in useSelectMention to include spec as null

* refactor: rename setKeyDialogOpen to onOpenChange for clarity and consistency, bring focus back to button that opened dialog

* feat: add SpecIcon component for dynamic model spec icons in menu, adjust icon styling

* refactor: update getSelectedIcon to accept additional parameters and improve icon rendering logic

* fix: adjust padding in MessageRender for improved layout

* refactor: remove inline style for menu width in CustomMenu component

* refactor: enhance layout and styling in ModelSpecItem component for better responsiveness

* refactor: update getDefaultModelSpec to accept startupConfig and improve model spec retrieval logic

* refactor: improve key management and default values in ModelSelector and related components

* refactor: adjust menu width and improve responsiveness in CustomMenu and EndpointItem components

* refactor: enhance focus styles and responsiveness in EndpointItem component

* refactor: improve layout and spacing in Header and ModelSelector components for better responsiveness

* refactor: adjust button styles for consistency and improved layout in AddMultiConvo and PresetsMenu components

* fix: initial fix of assistant names

* fix: assistants handling

* chore: update version of librechat-data-provider to 0.7.75 and add 'spec' to excludedKeys

* fix: improve endpoint filtering logic based on interface configuration and access rights

* fix: remove unused HeaderOptions import and set spec to null in presets and mentions

* fix: ensure currentExample is always an object when updating examples

* fix: update interfaceConfig checks to ensure modelSelect is considered for rendering components

* fix: update model selection logic to consider interface configuration when prioritizing model specs

* fix: add missing localizations

* fix: remove unused agent and assistant selection translations

* fix: implement debounced state updates for selected values in useSelectorEffects

* style: minor style changes related to the ModelSelector

* fix: adjust maximum height for popover and set fixed height for model item

* fix: update placeholders for model and endpoint search inputs

* fix: refactor MessageRender and ContentRender components to better match each other

* fix: remove convo fallback for iconURL in MessageRender and ContentRender components

* fix: update handling of spec, iconURL, and modelLabel in conversation presets, to allow better interchangeability

* fix: replace chatGptLabel with modelLabel in OpenAI settings configuration (fully deprecate chatGptLabel)

* fix: remove console log for assistantNames in useEndpoints hook

* refactor: add cleanInput and cleanOutput options to default conversation handling

* chore: update bun.lockb

* fix: set default value for showIconInHeader in getSelectedIcon function

* refactor: enhance error handling in message processing when latest message has existing content blocks

* chore: allow import/no-cycle for messages

* fix: adjust flex properties in BookmarkMenu for better layout

* feat: support both 'prompt' and 'q' as query parameters in useQueryParams hook

* feat: re-enable Badges components

* refactor: disable edit badge component

* chore: rename assistantMap to assistantsMap for consistency

* chore: rename assistantMap to assistantsMap for consistency in Mention component

* feat: set staleTime for various queries to improve data freshness

* feat: add spec field to tQueryParamsSchema for model specification

* feat: enhance useQueryParams to handle model specs

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2025-03-25 18:50:58 -04:00

534 lines
17 KiB
TypeScript

import {
QueryKeys,
dataService,
EModelEndpoint,
defaultOrderQuery,
defaultAssistantsVersion,
} from 'librechat-data-provider';
import { useRecoilValue } from 'recoil';
import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
import type {
UseInfiniteQueryOptions,
QueryObserverResult,
UseQueryOptions,
} from '@tanstack/react-query';
import type t from 'librechat-data-provider';
import type {
Action,
TPreset,
TPlugin,
ConversationListResponse,
ConversationListParams,
Assistant,
AssistantListParams,
AssistantListResponse,
AssistantDocument,
TEndpointsConfig,
TCheckUserKeyResponse,
SharedLinksListParams,
SharedLinksResponse,
} from 'librechat-data-provider';
import { findPageForConversation } from '~/utils';
import store from '~/store';
export const useGetPresetsQuery = (
config?: UseQueryOptions<TPreset[]>,
): QueryObserverResult<TPreset[], unknown> => {
return useQuery<TPreset[]>([QueryKeys.presets], () => dataService.getPresets(), {
staleTime: 1000 * 10,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
});
};
export const useGetEndpointsConfigOverride = <TData = unknown | boolean>(
config?: UseQueryOptions<unknown | boolean, unknown, TData>,
): QueryObserverResult<TData> => {
return useQuery<unknown | boolean, unknown, TData>(
[QueryKeys.endpointsConfigOverride],
() => dataService.getEndpointsConfigOverride(),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
},
);
};
export const useGetConvoIdQuery = (
id: string,
config?: UseQueryOptions<t.TConversation>,
): QueryObserverResult<t.TConversation> => {
const queryClient = useQueryClient();
return useQuery<t.TConversation>(
[QueryKeys.conversation, id],
() => {
const defaultQuery = () => dataService.getConversationById(id);
const convosQuery = queryClient.getQueryData<t.ConversationData>([
QueryKeys.allConversations,
]);
if (!convosQuery) {
return defaultQuery();
}
const { pageIndex, index } = findPageForConversation(convosQuery, { conversationId: id });
if (pageIndex > -1 && index > -1) {
return convosQuery.pages[pageIndex].conversations[index];
}
return defaultQuery();
},
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
},
);
};
export const useSearchInfiniteQuery = (
params?: ConversationListParams & { searchQuery?: string },
config?: UseInfiniteQueryOptions<ConversationListResponse, unknown>,
) => {
return useInfiniteQuery<ConversationListResponse, unknown>(
[QueryKeys.searchConversations, params], // Include the searchQuery in the query key
({ pageParam = '1' }) =>
dataService.listConversationsByQuery({ ...params, pageNumber: pageParam }),
{
getNextPageParam: (lastPage) => {
const currentPageNumber = Number(lastPage.pageNumber);
const totalPages = Number(lastPage.pages);
return currentPageNumber < totalPages ? currentPageNumber + 1 : undefined;
},
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
},
);
};
export const useConversationsInfiniteQuery = (
params?: ConversationListParams,
config?: UseInfiniteQueryOptions<ConversationListResponse, unknown>,
) => {
const queriesEnabled = useRecoilValue<boolean>(store.queriesEnabled);
return useInfiniteQuery<ConversationListResponse, unknown>(
params?.isArchived === true ? [QueryKeys.archivedConversations] : [QueryKeys.allConversations],
({ pageParam = '' }) =>
dataService.listConversations({
...params,
pageNumber: pageParam?.toString(),
isArchived: params?.isArchived ?? false,
tags: params?.tags || [],
}),
{
getNextPageParam: (lastPage) => {
const currentPageNumber = Number(lastPage.pageNumber);
const totalPages = Number(lastPage.pages); // Convert totalPages to a number
// If the current page number is less than total pages, return the next page number
return currentPageNumber < totalPages ? currentPageNumber + 1 : undefined;
},
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
enabled: (config?.enabled ?? true) === true && queriesEnabled,
},
);
};
export const useSharedLinksQuery = (
params: SharedLinksListParams,
config?: UseInfiniteQueryOptions<SharedLinksResponse, unknown>,
) => {
const { pageSize, isPublic, search, sortBy, sortDirection } = params;
return useInfiniteQuery<SharedLinksResponse>({
queryKey: [QueryKeys.sharedLinks, { pageSize, isPublic, search, sortBy, sortDirection }],
queryFn: ({ pageParam }) =>
dataService.listSharedLinks({
cursor: pageParam?.toString(),
pageSize,
isPublic,
search,
sortBy,
sortDirection,
}),
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
keepPreviousData: true,
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 30 * 60 * 1000, // 30 minutes
...config,
});
};
export const useConversationTagsQuery = (
config?: UseQueryOptions<t.TConversationTagsResponse>,
): QueryObserverResult<t.TConversationTagsResponse> => {
return useQuery<t.TConversationTag[]>(
[QueryKeys.conversationTags],
() => dataService.getConversationTags(),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
},
);
};
/**
* ASSISTANTS
*/
/**
* Hook for getting all available tools for Assistants
*/
export const useAvailableToolsQuery = (
endpoint: t.AssistantsEndpoint | EModelEndpoint.agents,
): QueryObserverResult<TPlugin[]> => {
const queryClient = useQueryClient();
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide;
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
const version: string | number | undefined =
endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
return useQuery<TPlugin[]>(
[QueryKeys.tools],
() => dataService.getAvailableTools(endpoint, version),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
enabled,
},
);
};
/**
* Hook for listing all assistants, with optional parameters provided for pagination and sorting
*/
export const useListAssistantsQuery = <TData = AssistantListResponse>(
endpoint: t.AssistantsEndpoint,
params: Omit<AssistantListParams, 'endpoint'> = defaultOrderQuery,
config?: UseQueryOptions<AssistantListResponse, unknown, TData>,
): QueryObserverResult<TData> => {
const queryClient = useQueryClient();
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
const userProvidesKey = !!(endpointsConfig?.[endpoint]?.userProvide ?? false);
const keyProvided = userProvidesKey ? !!(keyExpiry?.expiresAt ?? '') : true;
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
return useQuery<AssistantListResponse, unknown, TData>(
[QueryKeys.assistants, endpoint, params],
() => dataService.listAssistants({ ...params, endpoint }, version),
{
// Example selector to sort them by created_at
// select: (res) => {
// return res.data.sort((a, b) => a.created_at - b.created_at);
// },
staleTime: 1000 * 5,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
retry: false,
...config,
enabled: config?.enabled !== undefined ? config.enabled && enabled : enabled,
},
);
};
/*
export const useListAssistantsInfiniteQuery = (
params?: AssistantListParams,
config?: UseInfiniteQueryOptions<AssistantListResponse, Error>,
) => {
const queryClient = useQueryClient();
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([
QueryKeys.name,
EModelEndpoint.assistants,
]);
const userProvidesKey = !!endpointsConfig?.[EModelEndpoint.assistants]?.userProvide;
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
const enabled = !!endpointsConfig?.[EModelEndpoint.assistants] && keyProvided;
return useInfiniteQuery<AssistantListResponse, Error>(
['assistantsList', params],
({ pageParam = '' }) => dataService.listAssistants({ ...params, after: pageParam }),
{
getNextPageParam: (lastPage) => {
// lastPage is of type AssistantListResponse, you can use the has_more and last_id from it directly
if (lastPage.has_more) {
return lastPage.last_id;
}
return undefined;
},
...config,
enabled: config?.enabled !== undefined ? config?.enabled && enabled : enabled,
},
);
};
*/
/**
* Hook for retrieving details about a single assistant
*/
export const useGetAssistantByIdQuery = (
endpoint: t.AssistantsEndpoint,
assistant_id: string,
config?: UseQueryOptions<Assistant>,
): QueryObserverResult<Assistant> => {
const queryClient = useQueryClient();
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
const userProvidesKey = endpointsConfig?.[endpoint]?.userProvide ?? false;
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
return useQuery<Assistant>(
[QueryKeys.assistant, assistant_id],
() =>
dataService.getAssistantById({
endpoint,
assistant_id,
version,
}),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
retry: false,
...config,
// Query will not execute until the assistant_id exists
enabled: config?.enabled !== undefined ? config.enabled && enabled : enabled,
},
);
};
/**
* Hook for retrieving user's saved Assistant Actions
*/
export const useGetActionsQuery = <TData = Action[]>(
endpoint: t.AssistantsEndpoint | EModelEndpoint.agents,
config?: UseQueryOptions<Action[], unknown, TData>,
): QueryObserverResult<TData> => {
const queryClient = useQueryClient();
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide;
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
const enabled =
(!!endpointsConfig?.[endpoint] && keyProvided) || endpoint === EModelEndpoint.agents;
return useQuery<Action[], unknown, TData>([QueryKeys.actions], () => dataService.getActions(), {
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
enabled: config?.enabled !== undefined ? config.enabled && enabled : enabled,
});
};
/**
* Hook for retrieving user's saved Assistant Documents (metadata saved to Database)
*/
export const useGetAssistantDocsQuery = <TData = AssistantDocument[]>(
endpoint: t.AssistantsEndpoint | string,
config?: UseQueryOptions<AssistantDocument[], unknown, TData>,
): QueryObserverResult<TData> => {
const queryClient = useQueryClient();
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
const userProvidesKey = !!(endpointsConfig?.[endpoint]?.userProvide ?? false);
const keyProvided = userProvidesKey ? !!(keyExpiry?.expiresAt ?? '') : true;
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
return useQuery<AssistantDocument[], unknown, TData>(
[QueryKeys.assistantDocs, endpoint],
() =>
dataService.getAssistantDocs({
endpoint,
version,
}),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
enabled: config?.enabled !== undefined ? config.enabled && enabled : enabled,
},
);
};
/** STT/TTS */
/* Text to speech voices */
export const useVoicesQuery = (
config?: UseQueryOptions<t.VoiceResponse>,
): QueryObserverResult<t.VoiceResponse> => {
return useQuery<t.VoiceResponse>([QueryKeys.voices], () => dataService.getVoices(), {
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
retry: false,
...config,
});
};
/* Custom config speech */
export const useCustomConfigSpeechQuery = (
config?: UseQueryOptions<t.TCustomConfigSpeechResponse>,
): QueryObserverResult<t.TCustomConfigSpeechResponse> => {
return useQuery<t.TCustomConfigSpeechResponse>(
[QueryKeys.customConfigSpeech],
() => dataService.getCustomConfigSpeech(),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
retry: false,
...config,
},
);
};
/** Prompt */
export const usePromptGroupsInfiniteQuery = (
params?: t.TPromptGroupsWithFilterRequest,
config?: UseInfiniteQueryOptions<t.PromptGroupListResponse, unknown>,
) => {
const { name, pageSize, category, ...rest } = params || {};
return useInfiniteQuery<t.PromptGroupListResponse, unknown>(
[QueryKeys.promptGroups, name, category, pageSize],
({ pageParam = '1' }) =>
dataService.getPromptGroups({
...rest,
name,
category: category || '',
pageNumber: pageParam?.toString(),
pageSize: (pageSize || 10).toString(),
}),
{
getNextPageParam: (lastPage) => {
const currentPageNumber = Number(lastPage.pageNumber);
const totalPages = Number(lastPage.pages);
return currentPageNumber < totalPages ? currentPageNumber + 1 : undefined;
},
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
},
);
};
export const useGetPromptGroup = (
id: string,
config?: UseQueryOptions<t.TPromptGroup>,
): QueryObserverResult<t.TPromptGroup> => {
return useQuery<t.TPromptGroup>(
[QueryKeys.promptGroup, id],
() => dataService.getPromptGroup(id),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
retry: false,
...config,
enabled: config?.enabled !== undefined ? config.enabled : true,
},
);
};
export const useGetPrompts = (
filter: t.TPromptsWithFilterRequest,
config?: UseQueryOptions<t.TPrompt[]>,
): QueryObserverResult<t.TPrompt[]> => {
return useQuery<t.TPrompt[]>(
[QueryKeys.prompts, filter.groupId ?? ''],
() => dataService.getPrompts(filter),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
retry: false,
...config,
enabled: config?.enabled !== undefined ? config.enabled : true,
},
);
};
export const useGetAllPromptGroups = <TData = t.AllPromptGroupsResponse>(
filter?: t.AllPromptGroupsFilterRequest,
config?: UseQueryOptions<t.AllPromptGroupsResponse, unknown, TData>,
): QueryObserverResult<TData> => {
return useQuery<t.AllPromptGroupsResponse, unknown, TData>(
[QueryKeys.allPromptGroups],
() => dataService.getAllPromptGroups(),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
retry: false,
...config,
},
);
};
export const useGetCategories = <TData = t.TGetCategoriesResponse>(
config?: UseQueryOptions<t.TGetCategoriesResponse, unknown, TData>,
): QueryObserverResult<TData> => {
return useQuery<t.TGetCategoriesResponse, unknown, TData>(
[QueryKeys.categories],
() => dataService.getCategories(),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
retry: false,
...config,
enabled: config?.enabled !== undefined ? config.enabled : true,
},
);
};
export const useGetRandomPrompts = (
filter: t.TGetRandomPromptsRequest,
config?: UseQueryOptions<t.TGetRandomPromptsResponse>,
): QueryObserverResult<t.TGetRandomPromptsResponse> => {
return useQuery<t.TGetRandomPromptsResponse>(
[QueryKeys.randomPrompts],
() => dataService.getRandomPrompts(filter),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
retry: false,
...config,
enabled: config?.enabled !== undefined ? config.enabled : true,
},
);
};
export const useUserTermsQuery = (
config?: UseQueryOptions<t.TUserTermsResponse>,
): QueryObserverResult<t.TUserTermsResponse> => {
return useQuery<t.TUserTermsResponse>([QueryKeys.userTerms], () => dataService.getUserTerms(), {
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
});
};