2025-01-04 20:36:12 -05:00
|
|
|
import { useEffect, useCallback, useRef } from 'react';
|
|
|
|
|
import { useRecoilValue } from 'recoil';
|
2024-08-26 15:34:46 -04:00
|
|
|
import { useSearchParams } from 'react-router-dom';
|
2025-01-04 20:36:12 -05:00
|
|
|
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<string, string>) => {
|
|
|
|
|
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;
|
|
|
|
|
};
|
2024-08-26 15:34:46 -04:00
|
|
|
|
|
|
|
|
export default function useQueryParams({
|
|
|
|
|
textAreaRef,
|
|
|
|
|
}: {
|
|
|
|
|
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
|
|
|
|
}) {
|
2025-01-04 20:36:12 -05:00
|
|
|
const maxAttempts = 50;
|
2024-08-26 15:34:46 -04:00
|
|
|
const attemptsRef = useRef(0);
|
|
|
|
|
const processedRef = useRef(false);
|
2025-01-04 20:36:12 -05:00
|
|
|
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<TEndpointsConfig>([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,
|
|
|
|
|
],
|
|
|
|
|
);
|
2024-08-26 15:34:46 -04:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-01-04 20:36:12 -05:00
|
|
|
const processQueryParams = () => {
|
|
|
|
|
const queryParams: Record<string, string> = {};
|
|
|
|
|
searchParams.forEach((value, key) => {
|
|
|
|
|
queryParams[key] = value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const decodedPrompt = queryParams.prompt || '';
|
|
|
|
|
delete queryParams.prompt;
|
|
|
|
|
const validSettings = processValidSettings(queryParams);
|
|
|
|
|
|
|
|
|
|
return { decodedPrompt, validSettings };
|
|
|
|
|
};
|
2024-08-26 15:34:46 -04:00
|
|
|
|
|
|
|
|
const intervalId = setInterval(() => {
|
|
|
|
|
if (processedRef.current || attemptsRef.current >= maxAttempts) {
|
|
|
|
|
clearInterval(intervalId);
|
|
|
|
|
if (attemptsRef.current >= maxAttempts) {
|
2025-01-04 20:36:12 -05:00
|
|
|
console.warn('Max attempts reached, failed to process parameters');
|
2024-08-26 15:34:46 -04:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attemptsRef.current += 1;
|
|
|
|
|
|
2025-01-04 20:36:12 -05:00
|
|
|
if (!textAreaRef.current) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const { decodedPrompt, validSettings } = processQueryParams();
|
|
|
|
|
const currentText = methods.getValues('text');
|
2024-08-26 15:34:46 -04:00
|
|
|
|
2025-01-04 20:36:12 -05:00
|
|
|
/** 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);
|
|
|
|
|
};
|
2024-08-26 15:34:46 -04:00
|
|
|
|
2025-01-04 20:36:12 -05:00
|
|
|
if (!currentText && decodedPrompt) {
|
|
|
|
|
methods.setValue('text', decodedPrompt, { shouldValidate: true });
|
|
|
|
|
textAreaRef.current.focus();
|
|
|
|
|
textAreaRef.current.setSelectionRange(decodedPrompt.length, decodedPrompt.length);
|
|
|
|
|
}
|
2024-08-26 15:34:46 -04:00
|
|
|
|
2025-01-04 20:36:12 -05:00
|
|
|
if (Object.keys(validSettings).length > 0) {
|
|
|
|
|
newQueryConvo(validSettings);
|
2024-08-26 15:34:46 -04:00
|
|
|
}
|
|
|
|
|
|
2025-01-04 20:36:12 -05:00
|
|
|
success();
|
|
|
|
|
}, 100);
|
|
|
|
|
|
2024-08-26 15:34:46 -04:00
|
|
|
return () => {
|
|
|
|
|
clearInterval(intervalId);
|
|
|
|
|
};
|
2025-01-04 20:36:12 -05:00
|
|
|
}, [searchParams, methods, textAreaRef, newQueryConvo, newConversation]);
|
2024-08-26 15:34:46 -04:00
|
|
|
}
|