mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
🛡️ 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:
parent
7a91f6ca62
commit
2f4a03b581
13 changed files with 126 additions and 112 deletions
|
|
@ -535,6 +535,7 @@ export type NewConversationParams = {
|
|||
buildDefault?: boolean;
|
||||
keepLatestMessage?: boolean;
|
||||
keepAddedConvos?: boolean;
|
||||
disableParams?: boolean;
|
||||
};
|
||||
|
||||
export type ConvoGenerator = (params: NewConversationParams) => void | t.TConversation;
|
||||
|
|
|
|||
|
|
@ -79,19 +79,19 @@ export default function HeaderOptions({
|
|||
{!noSettings[endpoint] &&
|
||||
interfaceConfig?.parameters === true &&
|
||||
paramEndpoint === false && (
|
||||
<TooltipAnchor
|
||||
id="parameters-button"
|
||||
aria-label={localize('com_ui_model_parameters')}
|
||||
description={localize('com_ui_model_parameters')}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={triggerAdvancedMode}
|
||||
data-testid="parameters-button"
|
||||
className="inline-flex size-10 items-center justify-center rounded-lg border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
|
||||
>
|
||||
<Settings2 size={16} aria-label="Settings/Parameters Icon" />
|
||||
</TooltipAnchor>
|
||||
)}
|
||||
<TooltipAnchor
|
||||
id="parameters-button"
|
||||
aria-label={localize('com_ui_model_parameters')}
|
||||
description={localize('com_ui_model_parameters')}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={triggerAdvancedMode}
|
||||
data-testid="parameters-button"
|
||||
className="inline-flex size-10 items-center justify-center rounded-lg border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
|
||||
>
|
||||
<Settings2 size={16} aria-label="Settings/Parameters Icon" />
|
||||
</TooltipAnchor>
|
||||
)}
|
||||
</div>
|
||||
{interfaceConfig?.parameters === true && paramEndpoint === false && (
|
||||
<OptionsPopover
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -106,10 +106,13 @@ const conversationByIndex = atomFamily<TConversation | null, string | number>({
|
|||
JSON.stringify(newValue),
|
||||
);
|
||||
|
||||
const disableParams = newValue.disableParams === true;
|
||||
const shouldUpdateParams =
|
||||
index === 0 &&
|
||||
!disableParams &&
|
||||
newValue.createdAt === '' &&
|
||||
JSON.stringify(newValue) !== JSON.stringify(oldValue) &&
|
||||
(oldValue as TConversation)?.conversationId === 'new';
|
||||
(oldValue as TConversation)?.conversationId === Constants.NEW_CONVO;
|
||||
|
||||
if (shouldUpdateParams) {
|
||||
const newParams = createChatSearchParams(newValue);
|
||||
|
|
@ -299,10 +302,10 @@ const conversationByKeySelector = selectorFamily({
|
|||
key: 'conversationByKeySelector',
|
||||
get:
|
||||
(index: string | number) =>
|
||||
({ get }) => {
|
||||
const conversation = get(conversationByIndex(index));
|
||||
return conversation;
|
||||
},
|
||||
({ get }) => {
|
||||
const conversation = get(conversationByIndex(index));
|
||||
return conversation;
|
||||
},
|
||||
});
|
||||
|
||||
function useClearSubmissionState() {
|
||||
|
|
@ -361,24 +364,24 @@ const updateConversationSelector = selectorFamily({
|
|||
get: () => () => null as Partial<TConversation> | null,
|
||||
set:
|
||||
(conversationId: string) =>
|
||||
({ set, get }, newPartialConversation) => {
|
||||
if (newPartialConversation instanceof DefaultValue) {
|
||||
return;
|
||||
}
|
||||
({ set, get }, newPartialConversation) => {
|
||||
if (newPartialConversation instanceof DefaultValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = get(conversationKeysAtom);
|
||||
keys.forEach((key) => {
|
||||
set(conversationByIndex(key), (prevConversation) => {
|
||||
if (prevConversation && prevConversation.conversationId === conversationId) {
|
||||
return {
|
||||
...prevConversation,
|
||||
...newPartialConversation,
|
||||
};
|
||||
}
|
||||
return prevConversation;
|
||||
});
|
||||
const keys = get(conversationKeysAtom);
|
||||
keys.forEach((key) => {
|
||||
set(conversationByIndex(key), (prevConversation) => {
|
||||
if (prevConversation && prevConversation.conversationId === conversationId) {
|
||||
return {
|
||||
...prevConversation,
|
||||
...newPartialConversation,
|
||||
};
|
||||
}
|
||||
return prevConversation;
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import { isAgentsEndpoint, isAssistantsEndpoint, Constants } from 'librechat-data-provider';
|
||||
import {
|
||||
Constants,
|
||||
isAgentsEndpoint,
|
||||
tQueryParamsSchema,
|
||||
isAssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type { TConversation, TPreset } from 'librechat-data-provider';
|
||||
|
||||
const allowedParams = Object.keys(tQueryParamsSchema.shape);
|
||||
export default function createChatSearchParams(
|
||||
input: TConversation | TPreset | Record<string, string> | null,
|
||||
): URLSearchParams {
|
||||
|
|
@ -10,25 +16,6 @@ export default function createChatSearchParams(
|
|||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
const allowedParams = [
|
||||
'endpoint',
|
||||
'model',
|
||||
'temperature',
|
||||
'presence_penalty',
|
||||
'frequency_penalty',
|
||||
'stop',
|
||||
'top_p',
|
||||
'max_tokens',
|
||||
'topP',
|
||||
'topK',
|
||||
'maxOutputTokens',
|
||||
'promptCache',
|
||||
'region',
|
||||
'maxTokens',
|
||||
'agent_id',
|
||||
'assistant_id',
|
||||
];
|
||||
|
||||
if (input && typeof input === 'object' && !('endpoint' in input) && !('model' in input)) {
|
||||
Object.entries(input as Record<string, string>).forEach(([key, value]) => {
|
||||
if (value != null && allowedParams.includes(key)) {
|
||||
|
|
@ -64,20 +51,15 @@ export default function createChatSearchParams(
|
|||
params.set('model', conversation.model);
|
||||
}
|
||||
|
||||
const paramMap = {
|
||||
temperature: conversation.temperature,
|
||||
presence_penalty: conversation.presence_penalty,
|
||||
frequency_penalty: conversation.frequency_penalty,
|
||||
stop: conversation.stop,
|
||||
top_p: conversation.top_p,
|
||||
max_tokens: conversation.max_tokens,
|
||||
topP: conversation.topP,
|
||||
topK: conversation.topK,
|
||||
maxOutputTokens: conversation.maxOutputTokens,
|
||||
promptCache: conversation.promptCache,
|
||||
region: conversation.region,
|
||||
maxTokens: conversation.maxTokens,
|
||||
};
|
||||
const paramMap: Record<string, any> = {};
|
||||
allowedParams.forEach((key) => {
|
||||
if (key === 'agent_id' && conversation.agent_id === Constants.EPHEMERAL_AGENT_ID) {
|
||||
return;
|
||||
}
|
||||
if (key !== 'endpoint' && key !== 'model') {
|
||||
paramMap[key] = (conversation as any)[key];
|
||||
}
|
||||
});
|
||||
|
||||
return Object.entries(paramMap).reduce((params, [key, value]) => {
|
||||
if (value != null) {
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -45306,7 +45306,7 @@
|
|||
},
|
||||
"packages/data-provider": {
|
||||
"name": "librechat-data-provider",
|
||||
"version": "0.7.82",
|
||||
"version": "0.7.83",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.8.2",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "librechat-data-provider",
|
||||
"version": "0.7.82",
|
||||
"version": "0.7.83",
|
||||
"description": "data services for librechat apps",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.es.js",
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export const excludedKeys = new Set([
|
|||
'model',
|
||||
'files',
|
||||
'spec',
|
||||
'disableParams',
|
||||
]);
|
||||
|
||||
export enum SettingsViews {
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ export function validateSettingDefinitions(settings: SettingsConfiguration): voi
|
|||
// continue;
|
||||
}
|
||||
setting.includeInput =
|
||||
setting.type === SettingTypes.Number ? setting.includeInput ?? true : false; // Default to true if type is number
|
||||
setting.type === SettingTypes.Number ? (setting.includeInput ?? true) : false; // Default to true if type is number
|
||||
}
|
||||
|
||||
if (setting.component === ComponentTypes.Slider && setting.type === SettingTypes.Number) {
|
||||
|
|
@ -445,7 +445,8 @@ export function validateSettingDefinitions(settings: SettingsConfiguration): voi
|
|||
|
||||
// Validate optionType and conversation schema
|
||||
if (setting.optionType !== OptionTypes.Custom) {
|
||||
const conversationSchema = tConversationSchema.shape[setting.key as keyof TConversation];
|
||||
const conversationSchema =
|
||||
tConversationSchema.shape[setting.key as keyof Omit<TConversation, 'disableParams'>];
|
||||
if (!conversationSchema) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
|
|
|
|||
|
|
@ -745,6 +745,7 @@ export type TSetOption = (
|
|||
|
||||
export type TConversation = z.infer<typeof tConversationSchema> & {
|
||||
presetOverride?: Partial<TPreset>;
|
||||
disableParams?: boolean;
|
||||
};
|
||||
|
||||
export const tSharedLinkSchema = z.object({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue