import { QueryKeys, dataService, EModelEndpoint, isAgentsEndpoint, defaultOrderQuery, defaultAssistantsVersion, } from 'librechat-data-provider'; import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query'; import type { UseInfiniteQueryOptions, QueryObserverResult, UseQueryOptions, InfiniteData, } from '@tanstack/react-query'; import type t from 'librechat-data-provider'; import type { Action, TPreset, ConversationListResponse, ConversationListParams, MessagesListParams, MessagesListResponse, Assistant, AssistantListParams, AssistantListResponse, AssistantDocument, TEndpointsConfig, TCheckUserKeyResponse, SharedLinksListParams, SharedLinksResponse, } from 'librechat-data-provider'; import type { ConversationCursorData } from '~/utils/convos'; import { findConversationInInfinite } from '~/utils'; export const useGetPresetsQuery = ( config?: UseQueryOptions, ): QueryObserverResult => { return useQuery([QueryKeys.presets], () => dataService.getPresets(), { staleTime: 1000 * 10, refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, ...config, }); }; export const useGetConvoIdQuery = ( id: string, config?: UseQueryOptions, ): QueryObserverResult => { const queryClient = useQueryClient(); return useQuery( [QueryKeys.conversation, id], () => { // Try to find in all fetched infinite pages const convosQuery = queryClient.getQueryData>( [QueryKeys.allConversations], { exact: false }, ); const found = findConversationInInfinite(convosQuery, id); if (found && found.messages != null) { return found; } // Otherwise, fetch from API return dataService.getConversationById(id); }, { refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, ...config, }, ); }; export const useConversationsInfiniteQuery = ( params: ConversationListParams, config?: UseInfiniteQueryOptions, ) => { const { isArchived, sortBy, sortDirection, tags, search } = params; return useInfiniteQuery({ queryKey: [ isArchived ? QueryKeys.archivedConversations : QueryKeys.allConversations, { isArchived, sortBy, sortDirection, tags, search }, ], queryFn: ({ pageParam }) => dataService.listConversations({ isArchived, sortBy, sortDirection, tags, search, cursor: pageParam?.toString(), }), getNextPageParam: (lastPage) => lastPage?.nextCursor ?? undefined, keepPreviousData: true, staleTime: 5 * 60 * 1000, // 5 minutes cacheTime: 30 * 60 * 1000, // 30 minutes ...config, }); }; export const useMessagesInfiniteQuery = ( params: MessagesListParams, config?: UseInfiniteQueryOptions, ) => { const { sortBy, sortDirection, pageSize, conversationId, messageId, search } = params; return useInfiniteQuery({ queryKey: [ QueryKeys.messages, { sortBy, sortDirection, pageSize, conversationId, messageId, search }, ], queryFn: ({ pageParam }) => dataService.listMessages({ sortBy, sortDirection, pageSize, conversationId, messageId, search, cursor: pageParam?.toString(), }), getNextPageParam: (lastPage) => lastPage?.nextCursor ?? undefined, keepPreviousData: true, staleTime: 5 * 60 * 1000, // 5 minutes cacheTime: 30 * 60 * 1000, // 30 minutes ...config, }); }; export const useSharedLinksQuery = ( params: SharedLinksListParams, config?: UseInfiniteQueryOptions, ) => { const { pageSize, isPublic, search, sortBy, sortDirection } = params; return useInfiniteQuery({ 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, ): QueryObserverResult => { return useQuery( [QueryKeys.conversationTags], () => dataService.getConversationTags(), { refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, ...config, }, ); }; /** * ASSISTANTS */ /** * Hook for getting available LibreChat tools (excludes MCP tools) * For MCP tools, use `useMCPToolsQuery` from mcp-queries.ts */ export const useAvailableToolsQuery = ( endpoint: t.AssistantsEndpoint | EModelEndpoint.agents, config?: UseQueryOptions, ): QueryObserverResult => { const queryClient = useQueryClient(); const endpointsConfig = queryClient.getQueryData([QueryKeys.endpoints]); const keyExpiry = queryClient.getQueryData([QueryKeys.name, endpoint]); const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide; const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true; const enabled = isAgentsEndpoint(endpoint) ? true : !!endpointsConfig?.[endpoint] && keyProvided; const version: string | number | undefined = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint]; return useQuery( [QueryKeys.tools], () => dataService.getAvailableTools(endpoint, version), { refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, enabled, ...config, }, ); }; /** * Hook for listing all assistants, with optional parameters provided for pagination and sorting */ export const useListAssistantsQuery = ( endpoint: t.AssistantsEndpoint, params: Omit = defaultOrderQuery, config?: UseQueryOptions, ): QueryObserverResult => { const queryClient = useQueryClient(); const endpointsConfig = queryClient.getQueryData([QueryKeys.endpoints]); const keyExpiry = queryClient.getQueryData([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( [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, ) => { const queryClient = useQueryClient(); const endpointsConfig = queryClient.getQueryData([QueryKeys.endpoints]); const keyExpiry = queryClient.getQueryData([ QueryKeys.name, EModelEndpoint.assistants, ]); const userProvidesKey = !!endpointsConfig?.[EModelEndpoint.assistants]?.userProvide; const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true; const enabled = !!endpointsConfig?.[EModelEndpoint.assistants] && keyProvided; return useInfiniteQuery( ['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, ): QueryObserverResult => { const queryClient = useQueryClient(); const endpointsConfig = queryClient.getQueryData([QueryKeys.endpoints]); const keyExpiry = queryClient.getQueryData([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( [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 = ( endpoint: t.AssistantsEndpoint | EModelEndpoint.agents, config?: UseQueryOptions, ): QueryObserverResult => { const queryClient = useQueryClient(); const endpointsConfig = queryClient.getQueryData([QueryKeys.endpoints]); const keyExpiry = queryClient.getQueryData([QueryKeys.name, endpoint]); const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide; const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true; const enabled = (!!endpointsConfig?.[endpoint] && keyProvided) || endpoint === EModelEndpoint.agents; return useQuery([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 = ( endpoint: t.AssistantsEndpoint | string, config?: UseQueryOptions, ): QueryObserverResult => { const queryClient = useQueryClient(); const endpointsConfig = queryClient.getQueryData([QueryKeys.endpoints]); const keyExpiry = queryClient.getQueryData([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( [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, ): QueryObserverResult => { return useQuery([QueryKeys.voices], () => dataService.getVoices(), { refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, retry: false, ...config, }); }; /* Custom config speech */ export const useCustomConfigSpeechQuery = ( config?: UseQueryOptions, ): QueryObserverResult => { return useQuery( [QueryKeys.customConfigSpeech], () => dataService.getCustomConfigSpeech(), { refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, retry: false, ...config, }, ); }; /** Prompt */ export const usePromptGroupsInfiniteQuery = ( params?: t.TPromptGroupsWithFilterRequest, config?: UseInfiniteQueryOptions, ) => { const { name, pageSize, category } = params || {}; return useInfiniteQuery( [QueryKeys.promptGroups, name, category, pageSize], ({ pageParam }) => { const queryParams: t.TPromptGroupsWithFilterRequest = { name, category: category || '', limit: (pageSize || 10).toString(), }; // Only add cursor if it's a valid string if (pageParam && typeof pageParam === 'string') { queryParams.cursor = pageParam; } return dataService.getPromptGroups(queryParams); }, { getNextPageParam: (lastPage) => { // Use cursor-based pagination - ensure we return a valid cursor or undefined return lastPage.has_more && lastPage.after ? lastPage.after : undefined; }, refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, ...config, }, ); }; export const useGetPromptGroup = ( id: string, config?: UseQueryOptions, ): QueryObserverResult => { return useQuery( [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, ): QueryObserverResult => { return useQuery( [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 = ( filter?: t.AllPromptGroupsFilterRequest, config?: UseQueryOptions, ): QueryObserverResult => { return useQuery( [QueryKeys.allPromptGroups], () => dataService.getAllPromptGroups(), { refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, retry: false, ...config, }, ); }; export const useGetCategories = ( config?: UseQueryOptions, ): QueryObserverResult => { return useQuery( [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, ): QueryObserverResult => { return useQuery( [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, ): QueryObserverResult => { return useQuery([QueryKeys.userTerms], () => dataService.getUserTerms(), { refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, ...config, }); };