🛡️ fix: Preset and Validation Logic for URL Query Params (#7407)

* chore(store/families): linting

* refactor: Update `createChatSearchParams` to use `tQueryParamsSchema` for allowed parameters and add `modelLabel` to schema

* refactor: Enhance `useQueryParams` to streamline parameter processing and improve submission handling

* chore: linting

* fix: Add `disableParams` option to conversation handling and related schemas to prevent search params from updating due to use of default preset

* fix: Update `createChatSearchParams` to correctly ignore `agent_id` when it matches `EPHEMERAL_AGENT_ID`

* chore: revert modelLabel addition to query params, as no longer necessary due to `disableParams`

* fix: Refine logic for `disableParams` to ensure correct handling of active preset comparison

* fix: Add `disableParams` option to `NewConversationParams` and update related hooks for preset handling

* fix: Refactor validation logic in `validateSettingDefinitions` to improve handling of `includeInput` and update conversation schema

* fix: Bump version of `librechat-data-provider` to 0.7.83
This commit is contained in:
Danny Avila 2025-05-15 17:46:48 -04:00 committed by GitHub
parent 7a91f6ca62
commit 2f4a03b581
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 126 additions and 112 deletions

View file

@ -58,10 +58,11 @@ export default function usePresets() {
}
setDefaultPreset(defaultPreset);
if (!conversation?.conversationId || conversation.conversationId === 'new') {
newConversation({ preset: defaultPreset, modelsData });
newConversation({ preset: defaultPreset, modelsData, disableParams: true });
}
hasLoaded.current = true;
// dependencies are stable and only needed once
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [presetsQuery.data, user, modelsData]);
const setPresets = useCallback(
@ -102,7 +103,7 @@ export default function usePresets() {
if (data.defaultPreset && data.presetId !== _defaultPreset?.presetId) {
message = `${toastTitle} ${localize('com_endpoint_preset_default')}`;
setDefaultPreset(data);
newConversation({ preset: data });
newConversation({ preset: data, disableParams: true });
} else if (preset.defaultPreset === false) {
setDefaultPreset(null);
message = `${toastTitle} ${localize('com_endpoint_preset_default_removed')}`;
@ -185,6 +186,7 @@ export default function usePresets() {
newPreset.iconURL = newPreset.iconURL ?? null;
newPreset.modelLabel = newPreset.modelLabel ?? null;
const isModular = isCurrentModular && isNewModular && shouldSwitch;
const disableParams = newPreset.defaultPreset === true;
if (isExistingConversation && isModular) {
const currentConvo = getDefaultConversation({
/* target endpointType is necessary to avoid endpoint mixing */
@ -205,11 +207,12 @@ export default function usePresets() {
preset: currentConvo,
keepLatestMessage: true,
keepAddedConvos: true,
disableParams,
});
return;
}
newConversation({ preset: newPreset, keepAddedConvos: isModular });
newConversation({ preset: newPreset, keepAddedConvos: isModular, disableParams });
};
const onChangePreset = (preset: TPreset) => {

View file

@ -258,35 +258,6 @@ export default function useQueryParams({
})();
}, [methods, submitMessage, conversation]);
useEffect(() => {
// Only proceed if we've already processed URL parameters but haven't yet handled submission
if (
!processedRef.current ||
submissionHandledRef.current ||
settingsAppliedRef.current ||
!validSettingsRef.current ||
!conversation
) {
return;
}
const allSettingsApplied = areSettingsApplied();
if (allSettingsApplied) {
settingsAppliedRef.current = true;
if (pendingSubmitRef.current) {
if (settingsTimeoutRef.current) {
clearTimeout(settingsTimeoutRef.current);
settingsTimeoutRef.current = null;
}
console.log('Settings fully applied, processing submission');
processSubmission();
}
}
}, [conversation, processSubmission, areSettingsApplied]);
useEffect(() => {
const processQueryParams = () => {
const queryParams: Record<string, string> = {};
@ -332,14 +303,15 @@ export default function useQueryParams({
/** Mark processing as complete and clean up as needed */
const success = () => {
const currentParams = new URLSearchParams(searchParams.toString());
const paramString = searchParams.toString();
const currentParams = new URLSearchParams(paramString);
currentParams.delete('prompt');
currentParams.delete('q');
currentParams.delete('submit');
setSearchParams(currentParams, { replace: true });
processedRef.current = true;
console.log('Parameters processed successfully');
console.log('Parameters processed successfully', paramString);
clearInterval(intervalId);
// Only clean URL if there's no pending submission
@ -417,4 +389,33 @@ export default function useQueryParams({
queryClient,
processSubmission,
]);
useEffect(() => {
// Only proceed if we've already processed URL parameters but haven't yet handled submission
if (
!processedRef.current ||
submissionHandledRef.current ||
settingsAppliedRef.current ||
!validSettingsRef.current ||
!conversation
) {
return;
}
const allSettingsApplied = areSettingsApplied();
if (allSettingsApplied) {
settingsAppliedRef.current = true;
if (pendingSubmitRef.current) {
if (settingsTimeoutRef.current) {
clearTimeout(settingsTimeoutRef.current);
settingsTimeoutRef.current = null;
}
console.log('Settings fully applied, processing submission');
processSubmission();
}
}
}, [conversation, processSubmission, areSettingsApplied]);
}

View file

@ -225,6 +225,7 @@ export default function useSelectMention({
newPreset.iconURL = newPreset.iconURL ?? null;
newPreset.modelLabel = newPreset.modelLabel ?? null;
const isModular = isCurrentModular && isNewModular && shouldSwitch;
const disableParams = newPreset.defaultPreset === true;
if (isExistingConversation && isModular) {
template.endpointType = newEndpointType as EModelEndpoint | undefined;
template.spec = null;
@ -244,12 +245,17 @@ export default function useSelectMention({
preset: newPreset,
keepLatestMessage: true,
keepAddedConvos: true,
disableParams,
});
return;
}
logger.info('conversation', 'Switching conversation to new preset', template);
newConversation({ preset: newPreset, keepAddedConvos: isModular });
newConversation({
preset: newPreset,
keepAddedConvos: isModular,
disableParams,
});
},
[
modularChat,

View file

@ -71,6 +71,7 @@ const useNewConvo = (index = 0) => {
keepLatestMessage?: boolean,
keepAddedConvos?: boolean,
disableFocus?: boolean,
_disableParams?: boolean,
) => {
const modelsConfig = modelsData ?? modelsQuery.data;
const { endpoint = null } = conversation;
@ -87,6 +88,12 @@ const useNewConvo = (index = 0) => {
? defaultPreset
: preset;
const disableParams =
_disableParams ??
(activePreset?.presetId != null &&
activePreset.presetId &&
activePreset.presetId === defaultPreset?.presetId);
if (buildDefaultConversation) {
let defaultEndpoint = getDefaultEndpoint({
convoSetup: activePreset ?? conversation,
@ -148,6 +155,10 @@ const useNewConvo = (index = 0) => {
});
}
if (disableParams === true) {
conversation.disableParams = true;
}
if (!(keepAddedConvos ?? false)) {
clearAllConversations(true);
}
@ -160,7 +171,7 @@ const useNewConvo = (index = 0) => {
);
setConversation({
...conversation,
conversationId: 'new',
conversationId: Constants.NEW_CONVO as string,
});
} else {
logger.log('conversation', 'Setting conversation from `useNewConvo`', conversation);
@ -205,6 +216,7 @@ const useNewConvo = (index = 0) => {
buildDefault = true,
keepLatestMessage = false,
keepAddedConvos = false,
disableParams,
}: {
template?: Partial<TConversation>;
preset?: Partial<TPreset>;
@ -213,6 +225,7 @@ const useNewConvo = (index = 0) => {
disableFocus?: boolean;
keepLatestMessage?: boolean;
keepAddedConvos?: boolean;
disableParams?: boolean;
} = {}) {
pauseGlobalAudio();
if (!saveBadgesState) {
@ -282,17 +295,19 @@ const useNewConvo = (index = 0) => {
keepLatestMessage,
keepAddedConvos,
disableFocus,
disableParams,
);
},
[
pauseGlobalAudio,
startupConfig,
saveDrafts,
switchToConversation,
files,
setFiles,
saveDrafts,
mutateAsync,
resetBadges,
startupConfig,
saveBadgesState,
pauseGlobalAudio,
switchToConversation,
],
);