import { useEffect, useCallback, useRef } from 'react'; import { useRecoilValue } from 'recoil'; import { useSearchParams } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; import { QueryKeys, EModelEndpoint, isAgentsEndpoint, tQueryParamsSchema, isAssistantsEndpoint, } from 'librechat-data-provider'; import type { TPreset, TEndpointsConfig } from 'librechat-data-provider'; import type { ZodAny } from 'zod'; import { getConvoSwitchLogic, removeUnavailableTools } from '~/utils'; import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo'; import { useChatContext, useChatFormContext } from '~/Providers'; import store from '~/store'; const parseQueryValue = (value: string) => { if (value === 'true') { return true; } if (value === 'false') { return false; } if (!isNaN(Number(value))) { return Number(value); } return value; }; const processValidSettings = (queryParams: Record) => { const validSettings = {} as TPreset; Object.entries(queryParams).forEach(([key, value]) => { try { const schema = tQueryParamsSchema.shape[key] as ZodAny | undefined; if (schema) { const parsedValue = parseQueryValue(value); const validValue = schema.parse(parsedValue); validSettings[key] = validValue; } } catch (error) { console.warn(`Invalid value for setting ${key}:`, error); } }); if ( validSettings.assistant_id != null && validSettings.assistant_id && !isAssistantsEndpoint(validSettings.endpoint) ) { validSettings.endpoint = EModelEndpoint.assistants; } if ( validSettings.agent_id != null && validSettings.agent_id && !isAgentsEndpoint(validSettings.endpoint) ) { validSettings.endpoint = EModelEndpoint.agents; } return validSettings; }; export default function useQueryParams({ textAreaRef, }: { textAreaRef: React.RefObject; }) { const maxAttempts = 50; const attemptsRef = useRef(0); const processedRef = useRef(false); const methods = useChatFormContext(); const [searchParams] = useSearchParams(); const getDefaultConversation = useDefaultConvo(); const modularChat = useRecoilValue(store.modularChat); const availableTools = useRecoilValue(store.availableTools); const queryClient = useQueryClient(); const { conversation, newConversation } = useChatContext(); const newQueryConvo = useCallback( (_newPreset?: TPreset) => { if (!_newPreset) { return; } const newPreset = removeUnavailableTools(_newPreset, availableTools); let newEndpoint = newPreset.endpoint ?? ''; const endpointsConfig = queryClient.getQueryData([QueryKeys.endpoints]); if (newEndpoint && endpointsConfig && !endpointsConfig[newEndpoint]) { const normalizedNewEndpoint = newEndpoint.toLowerCase(); for (const [key, value] of Object.entries(endpointsConfig)) { if ( value && value.type === EModelEndpoint.custom && key.toLowerCase() === normalizedNewEndpoint ) { newEndpoint = key; newPreset.endpoint = key; newPreset.endpointType = EModelEndpoint.custom; break; } } } const { template, shouldSwitch, isNewModular, newEndpointType, isCurrentModular, isExistingConversation, } = getConvoSwitchLogic({ newEndpoint, modularChat, conversation, endpointsConfig, }); const isModular = isCurrentModular && isNewModular && shouldSwitch; if (isExistingConversation && isModular) { template.endpointType = newEndpointType as EModelEndpoint | undefined; const currentConvo = getDefaultConversation({ /* target endpointType is necessary to avoid endpoint mixing */ conversation: { ...(conversation ?? {}), endpointType: template.endpointType }, preset: template, }); /* We don't reset the latest message, only when changing settings mid-converstion */ newConversation({ template: currentConvo, preset: newPreset, keepLatestMessage: true, keepAddedConvos: true, }); return; } newConversation({ preset: newPreset, keepAddedConvos: true }); }, [ queryClient, modularChat, conversation, availableTools, newConversation, getDefaultConversation, ], ); useEffect(() => { const processQueryParams = () => { const queryParams: Record = {}; searchParams.forEach((value, key) => { queryParams[key] = value; }); const decodedPrompt = queryParams.prompt || ''; delete queryParams.prompt; const validSettings = processValidSettings(queryParams); return { decodedPrompt, validSettings }; }; const intervalId = setInterval(() => { if (processedRef.current || attemptsRef.current >= maxAttempts) { clearInterval(intervalId); if (attemptsRef.current >= maxAttempts) { console.warn('Max attempts reached, failed to process parameters'); } return; } attemptsRef.current += 1; if (!textAreaRef.current) { return; } const { decodedPrompt, validSettings } = processQueryParams(); const currentText = methods.getValues('text'); /** Clean up URL parameters after successful processing */ const success = () => { const newUrl = window.location.pathname; window.history.replaceState({}, '', newUrl); processedRef.current = true; console.log('Parameters processed successfully'); clearInterval(intervalId); }; if (!currentText && decodedPrompt) { methods.setValue('text', decodedPrompt, { shouldValidate: true }); textAreaRef.current.focus(); textAreaRef.current.setSelectionRange(decodedPrompt.length, decodedPrompt.length); } if (Object.keys(validSettings).length > 0) { newQueryConvo(validSettings); } success(); }, 100); return () => { clearInterval(intervalId); }; }, [searchParams, methods, textAreaRef, newQueryConvo, newConversation]); }