🔧 fix: Preset Dialog Styling and Values (#2657)

* style: preset dialog styling

* refactor: coerce number input for convo schema

* refactor: replace dynamic input number with static component
This commit is contained in:
Danny Avila 2024-05-10 03:05:45 -04:00 committed by GitHub
parent 98c96cd020
commit 2b37a44b8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 258 additions and 133 deletions

View file

@ -1,4 +1,5 @@
import { FileSources } from 'librechat-data-provider'; import { FileSources } from 'librechat-data-provider';
import type * as InputNumberPrimitive from 'rc-input-number';
import type { ColumnDef } from '@tanstack/react-table'; import type { ColumnDef } from '@tanstack/react-table';
import type { SetterOrUpdater } from 'recoil'; import type { SetterOrUpdater } from 'recoil';
import type { import type {
@ -115,6 +116,8 @@ export type TSetExample = (
newValue: number | string | boolean | null, newValue: number | string | boolean | null,
) => void; ) => void;
export type OnInputNumberChange = InputNumberPrimitive.InputNumberProps['onChange'];
export const defaultDebouncedDelay = 450; export const defaultDebouncedDelay = 450;
export enum ESide { export enum ESide {

View file

@ -49,9 +49,9 @@ const EditPresetDialog = ({
title={`${localize('com_ui_edit') + ' ' + localize('com_endpoint_preset')} - ${ title={`${localize('com_ui_edit') + ' ' + localize('com_endpoint_preset')} - ${
preset?.title preset?.title
}`} }`}
className="h-full max-w-full overflow-y-auto pb-4 sm:w-[680px] sm:pb-0 md:h-[720px] md:w-[750px] md:overflow-y-hidden lg:w-[950px] xl:h-[720px]" className="h-full max-w-full overflow-y-auto pb-4 sm:w-[680px] sm:pb-0 md:h-[720px] md:w-[750px] md:overflow-y-hidden md:overflow-y-hidden lg:w-[950px] xl:h-[720px]"
main={ main={
<div className="flex w-full flex-col items-center gap-2 md:h-[530px]"> <div className="flex w-full flex-col items-center gap-2 md:h-[550px] md:overflow-y-auto">
<div className="grid w-full"> <div className="grid w-full">
<div className="col-span-4 flex flex-col items-start justify-start gap-6 pb-4 md:flex-row"> <div className="col-span-4 flex flex-col items-start justify-start gap-6 pb-4 md:flex-row">
<div className="flex w-full flex-col"> <div className="flex w-full flex-col">
@ -126,6 +126,7 @@ const EditPresetDialog = ({
</DialogClose> </DialogClose>
</div> </div>
} }
footerClassName="bg-white dark:bg-gray-700"
/> />
</Dialog> </Dialog>
); );

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import type { TModelSelectProps } from '~/common'; import type { TModelSelectProps, OnInputNumberChange } from '~/common';
import { import {
Input, Input,
Label, Label,
@ -12,18 +12,34 @@ import {
HoverCardTrigger, HoverCardTrigger,
} from '~/components/ui'; } from '~/components/ui';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils'; import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils';
import { DynamicInputNumber } from '~/components/SidePanel/Parameters'; import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover';
import { useLocalize, useDebouncedInput } from '~/hooks';
import OptionHover from './OptionHover'; import OptionHover from './OptionHover';
import { useLocalize } from '~/hooks';
import { ESide } from '~/common'; import { ESide } from '~/common';
export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) { export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) {
const localize = useLocalize(); const localize = useLocalize();
const {
model,
modelLabel,
promptPrefix,
temperature,
topP,
topK,
maxOutputTokens,
maxContextTokens,
resendFiles,
} = conversation ?? {};
const [setMaxContextTokens, maxContextTokensValue] = useDebouncedInput<number | null | undefined>(
{
setOption,
optionKey: 'maxContextTokens',
initialValue: maxContextTokens,
},
);
if (!conversation) { if (!conversation) {
return null; return null;
} }
const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens, resendFiles } =
conversation;
const setModel = setOption('model'); const setModel = setOption('model');
const setModelLabel = setOption('modelLabel'); const setModelLabel = setOption('modelLabel');
@ -84,28 +100,40 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div> </div>
</div> </div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2"> <div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<DynamicInputNumber <HoverCard openDelay={300}>
columnSpan={2} <HoverCardTrigger className="grid w-full items-center gap-2">
settingKey="maxContextTokens" <div className="mt-1 flex w-full justify-between">
setOption={setOption} <Label htmlFor="max-context-tokens" className="text-left text-sm font-medium">
label="com_endpoint_context_tokens" {localize('com_endpoint_context_tokens')}{' '}
labelCode={true} </Label>
description="com_endpoint_context_info" <InputNumber
descriptionCode={true} id="max-context-tokens"
placeholder="com_nav_theme_system" stringMode={false}
placeholderCode={true} disabled={readonly}
descriptionSide="right" value={maxContextTokensValue as number}
conversation={conversation} onChange={setMaxContextTokens as OnInputNumberChange}
readonly={readonly} placeholder={localize('com_nav_theme_system')}
range={{ min={10}
min: 10, max={2000000}
max: 2000000, step={1000}
step: 1000, controls={false}
}} className={cn(
className="mt-1 w-full justify-between" defaultTextProps,
inputClassName="w-1/3" cn(
showDefault={false} optionText,
/> 'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
'w-1/3',
),
)}
/>
</div>
</HoverCardTrigger>
<OptionHoverAlt
description="com_endpoint_context_info"
langCode={true}
side={ESide.Left}
/>
</HoverCard>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">

View file

@ -1,7 +1,7 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import { EModelEndpoint, endpointSettings } from 'librechat-data-provider'; import { EModelEndpoint, endpointSettings } from 'librechat-data-provider';
import type { TModelSelectProps } from '~/common'; import type { TModelSelectProps, OnInputNumberChange } from '~/common';
import { import {
Input, Input,
Label, Label,
@ -12,16 +12,24 @@ import {
HoverCardTrigger, HoverCardTrigger,
} from '~/components/ui'; } from '~/components/ui';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils'; import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils';
import { DynamicInputNumber } from '~/components/SidePanel/Parameters'; import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover';
import { useLocalize, useDebouncedInput } from '~/hooks';
import OptionHover from './OptionHover'; import OptionHover from './OptionHover';
import { useLocalize } from '~/hooks';
import { ESide } from '~/common'; import { ESide } from '~/common';
export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) { export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) {
const localize = useLocalize(); const localize = useLocalize();
const google = endpointSettings[EModelEndpoint.google]; const google = endpointSettings[EModelEndpoint.google];
const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens } = const {
conversation ?? {}; model,
modelLabel,
promptPrefix,
temperature,
topP,
topK,
maxContextTokens,
maxOutputTokens,
} = conversation ?? {};
const isGemini = model?.toLowerCase()?.includes('gemini'); const isGemini = model?.toLowerCase()?.includes('gemini');
@ -42,6 +50,14 @@ export default function Settings({ conversation, setOption, models, readonly }:
[model], [model],
); );
const [setMaxContextTokens, maxContextTokensValue] = useDebouncedInput<number | null | undefined>(
{
setOption,
optionKey: 'maxContextTokens',
initialValue: maxContextTokens,
},
);
if (!conversation) { if (!conversation) {
return null; return null;
} }
@ -104,28 +120,40 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div> </div>
</div> </div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2"> <div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<DynamicInputNumber <HoverCard openDelay={300}>
columnSpan={2} <HoverCardTrigger className="grid w-full items-center gap-2">
settingKey="maxContextTokens" <div className="mt-1 flex w-full justify-between">
setOption={setOption} <Label htmlFor="max-context-tokens" className="text-left text-sm font-medium">
label="com_endpoint_context_tokens" {localize('com_endpoint_context_tokens')}{' '}
labelCode={true} </Label>
description="com_endpoint_context_info" <InputNumber
descriptionCode={true} id="max-context-tokens"
placeholder="com_nav_theme_system" stringMode={false}
placeholderCode={true} disabled={readonly}
descriptionSide="right" value={maxContextTokensValue as number}
conversation={conversation} onChange={setMaxContextTokens as OnInputNumberChange}
readonly={readonly} placeholder={localize('com_nav_theme_system')}
range={{ min={10}
min: 10, max={2000000}
max: 2000000, step={1000}
step: 1000, controls={false}
}} className={cn(
className="mt-1 w-full justify-between" defaultTextProps,
inputClassName="w-1/3" cn(
showDefault={false} optionText,
/> 'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
'w-1/3',
),
)}
/>
</div>
</HoverCardTrigger>
<OptionHoverAlt
description="com_endpoint_context_info"
langCode={true}
side={ESide.Left}
/>
</HoverCard>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">

View file

@ -1,12 +1,12 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import * as InputNumberPrimitive from 'rc-input-number';
import { import {
EModelEndpoint, EModelEndpoint,
ImageDetail, ImageDetail,
imageDetailNumeric, imageDetailNumeric,
imageDetailValue, imageDetailValue,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import type { TModelSelectProps, OnInputNumberChange } from '~/common';
import { import {
Input, Input,
Label, Label,
@ -18,14 +18,12 @@ import {
HoverCardTrigger, HoverCardTrigger,
} from '~/components/ui'; } from '~/components/ui';
import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils'; import { cn, defaultTextProps, optionText, removeFocusOutlines } from '~/utils';
import { DynamicTags, DynamicInputNumber } from '~/components/SidePanel/Parameters'; import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover';
import { DynamicTags } from '~/components/SidePanel/Parameters';
import { useLocalize, useDebouncedInput } from '~/hooks'; import { useLocalize, useDebouncedInput } from '~/hooks';
import type { TModelSelectProps } from '~/common';
import OptionHover from './OptionHover'; import OptionHover from './OptionHover';
import { ESide } from '~/common'; import { ESide } from '~/common';
type OnInputNumberChange = InputNumberPrimitive.InputNumberProps['onChange'];
export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) { export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) {
const localize = useLocalize(); const localize = useLocalize();
const { const {
@ -41,6 +39,8 @@ export default function Settings({ conversation, setOption, models, readonly }:
presence_penalty: presP, presence_penalty: presP,
resendFiles, resendFiles,
imageDetail, imageDetail,
maxContextTokens,
max_tokens,
} = conversation ?? {}; } = conversation ?? {};
const [setChatGptLabel, chatGptLabelValue] = useDebouncedInput<string | null | undefined>({ const [setChatGptLabel, chatGptLabelValue] = useDebouncedInput<string | null | undefined>({
@ -73,6 +73,18 @@ export default function Settings({ conversation, setOption, models, readonly }:
optionKey: 'presence_penalty', optionKey: 'presence_penalty',
initialValue: presP, initialValue: presP,
}); });
const [setMaxContextTokens, maxContextTokensValue] = useDebouncedInput<number | null | undefined>(
{
setOption,
optionKey: 'maxContextTokens',
initialValue: maxContextTokens,
},
);
const [setMaxOutputTokens, maxOutputTokensValue] = useDebouncedInput<number | null | undefined>({
setOption,
optionKey: 'max_tokens',
initialValue: max_tokens,
});
const optionEndpoint = useMemo(() => endpointType ?? endpoint, [endpoint, endpointType]); const optionEndpoint = useMemo(() => endpointType ?? endpoint, [endpoint, endpointType]);
const isOpenAI = useMemo( const isOpenAI = useMemo(
@ -154,50 +166,74 @@ export default function Settings({ conversation, setOption, models, readonly }:
</div> </div>
</div> </div>
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2"> <div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
<DynamicInputNumber <HoverCard openDelay={300}>
columnSpan={2} <HoverCardTrigger className="grid w-full items-center gap-2">
settingKey="maxContextTokens" <div className="mt-1 flex w-full justify-between">
setOption={setOption} <Label htmlFor="max-context-tokens" className="text-left text-sm font-medium">
label="com_endpoint_context_tokens" {localize('com_endpoint_context_tokens')}{' '}
labelCode={true} </Label>
description="com_endpoint_context_info" <InputNumber
descriptionCode={true} id="max-context-tokens"
placeholder="com_nav_theme_system" stringMode={false}
placeholderCode={true} disabled={readonly}
descriptionSide="right" value={maxContextTokensValue as number}
conversation={conversation} onChange={setMaxContextTokens as OnInputNumberChange}
readonly={readonly} placeholder={localize('com_nav_theme_system')}
range={{ min={10}
min: 10, max={2000000}
max: 2000000, step={1000}
step: 1000, controls={false}
}} className={cn(
className="mt-1 w-full justify-between" defaultTextProps,
inputClassName="w-1/3" cn(
showDefault={false} optionText,
/> 'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
<DynamicInputNumber 'w-1/3',
columnSpan={2} ),
settingKey="max_tokens" )}
setOption={setOption} />
label="com_endpoint_max_output_tokens" </div>
labelCode={true} </HoverCardTrigger>
description="com_endpoint_openai_max_tokens" <OptionHoverAlt
descriptionCode={true} description="com_endpoint_context_info"
placeholder="com_nav_theme_system" langCode={true}
placeholderCode={true} side={ESide.Left}
descriptionSide="top" />
conversation={conversation} </HoverCard>
readonly={readonly} <HoverCard openDelay={300}>
range={{ <HoverCardTrigger className="grid w-full items-center gap-2">
min: 10, <div className="mt-1 flex w-full justify-between">
max: 2000000, <Label htmlFor="max-output-tokens" className="text-left text-sm font-medium">
step: 1000, {localize('com_endpoint_max_output_tokens')}{' '}
}} </Label>
className="mt-1 w-full justify-between" <InputNumber
inputClassName="w-1/3" id="max-output-tokens"
showDefault={false} stringMode={false}
/> disabled={readonly}
value={maxOutputTokensValue as number}
onChange={setMaxOutputTokens as OnInputNumberChange}
placeholder={localize('com_nav_theme_system')}
min={10}
max={2000000}
step={1000}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
'w-1/3',
),
)}
/>
</div>
</HoverCardTrigger>
<OptionHoverAlt
description="com_endpoint_openai_max_tokens"
langCode={true}
side={ESide.Left}
/>
</HoverCard>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">

View file

@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query'; import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query';
import type { TPlugin } from 'librechat-data-provider'; import type { TPlugin } from 'librechat-data-provider';
import type { TModelSelectProps } from '~/common'; import type { TModelSelectProps, OnInputNumberChange } from '~/common';
import { import {
Input, Input,
Label, Label,
@ -22,7 +22,7 @@ import {
processPlugins, processPlugins,
selectPlugins, selectPlugins,
} from '~/utils'; } from '~/utils';
import { DynamicInputNumber } from '~/components/SidePanel/Parameters'; import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover';
import { useLocalize, useDebouncedInput } from '~/hooks'; import { useLocalize, useDebouncedInput } from '~/hooks';
import OptionHover from './OptionHover'; import OptionHover from './OptionHover';
import { ESide } from '~/common'; import { ESide } from '~/common';
@ -69,6 +69,7 @@ export default function Settings({
top_p: topP, top_p: topP,
frequency_penalty: freqP, frequency_penalty: freqP,
presence_penalty: presP, presence_penalty: presP,
maxContextTokens,
} = conversation ?? {}; } = conversation ?? {};
const [setChatGptLabel, chatGptLabelValue] = useDebouncedInput<string | null | undefined>({ const [setChatGptLabel, chatGptLabelValue] = useDebouncedInput<string | null | undefined>({
@ -101,6 +102,13 @@ export default function Settings({
optionKey: 'presence_penalty', optionKey: 'presence_penalty',
initialValue: presP, initialValue: presP,
}); });
const [setMaxContextTokens, maxContextTokensValue] = useDebouncedInput<number | null | undefined>(
{
setOption,
optionKey: 'maxContextTokens',
initialValue: maxContextTokens,
},
);
const setModel = setOption('model'); const setModel = setOption('model');
@ -177,28 +185,40 @@ export default function Settings({
containerClassName="flex w-full resize-none border border-transparent" containerClassName="flex w-full resize-none border border-transparent"
labelClassName="dark:text-white" labelClassName="dark:text-white"
/> />
<DynamicInputNumber <HoverCard openDelay={300}>
columnSpan={2} <HoverCardTrigger className="grid w-full items-center gap-2">
settingKey="maxContextTokens" <div className="mt-1 flex w-full justify-between">
setOption={setOption} <Label htmlFor="max-context-tokens" className="text-left text-sm font-medium">
label="com_endpoint_context_tokens" {localize('com_endpoint_context_tokens')}{' '}
labelCode={true} </Label>
description="com_endpoint_context_info" <InputNumber
descriptionCode={true} id="max-context-tokens"
placeholder="com_nav_theme_system" stringMode={false}
placeholderCode={true} disabled={readonly}
descriptionSide="right" value={maxContextTokensValue as number}
conversation={conversation} onChange={setMaxContextTokens as OnInputNumberChange}
readonly={readonly} placeholder={localize('com_nav_theme_system')}
range={{ min={10}
min: 10, max={2000000}
max: 2000000, step={1000}
step: 1000, controls={false}
}} className={cn(
className="mt-1 w-full justify-between" defaultTextProps,
inputClassName="w-1/3" cn(
showDefault={false} optionText,
/> 'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
'w-1/3',
),
)}
/>
</div>
</HoverCardTrigger>
<OptionHoverAlt
description="com_endpoint_context_info"
langCode={true}
side={ESide.Left}
/>
</HoverCard>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">

View file

@ -25,6 +25,7 @@ type DialogTemplateProps = {
selection?: SelectionProps; selection?: SelectionProps;
className?: string; className?: string;
headerClassName?: string; headerClassName?: string;
footerClassName?: string;
showCloseButton?: boolean; showCloseButton?: boolean;
showCancelButton?: boolean; showCancelButton?: boolean;
}; };
@ -40,6 +41,7 @@ const DialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDivE
selection, selection,
className, className,
headerClassName, headerClassName,
footerClassName,
showCloseButton, showCloseButton,
showCancelButton = true, showCancelButton = true,
} = props; } = props;
@ -66,7 +68,7 @@ const DialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDivE
)} )}
</DialogHeader> </DialogHeader>
<div className="px-6">{main ? main : null}</div> <div className="px-6">{main ? main : null}</div>
<DialogFooter> <DialogFooter className={footerClassName}>
<div>{leftButtons ? leftButtons : null}</div> <div>{leftButtons ? leftButtons : null}</div>
<div className="flex h-auto gap-3"> <div className="flex h-auto gap-3">
{showCancelButton && ( {showCancelButton && (

View file

@ -1,6 +1,6 @@
{ {
"name": "librechat-data-provider", "name": "librechat-data-provider",
"version": "0.6.1", "version": "0.6.2",
"description": "data services for librechat apps", "description": "data services for librechat apps",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.es.js", "module": "dist/index.es.js",

View file

@ -280,6 +280,13 @@ export type TMessage = z.input<typeof tMessageSchema> & {
files?: Partial<TFile>[]; files?: Partial<TFile>[];
}; };
export const coerceNumber = z.union([z.number(), z.string()]).transform((val) => {
if (typeof val === 'string') {
return val.trim() === '' ? undefined : parseFloat(val);
}
return val;
});
export const tConversationSchema = z.object({ export const tConversationSchema = z.object({
conversationId: z.string().nullable(), conversationId: z.string().nullable(),
title: z.string().nullable().or(z.literal('New Chat')).default('New Chat'), title: z.string().nullable().or(z.literal('New Chat')).default('New Chat'),
@ -315,8 +322,8 @@ export const tConversationSchema = z.object({
maxOutputTokens: z.number().optional(), maxOutputTokens: z.number().optional(),
agentOptions: tAgentOptionsSchema.nullable().optional(), agentOptions: tAgentOptionsSchema.nullable().optional(),
file_ids: z.array(z.string()).optional(), file_ids: z.array(z.string()).optional(),
maxContextTokens: z.number().optional(), maxContextTokens: coerceNumber.optional(),
max_tokens: z.number().optional(), max_tokens: coerceNumber.optional(),
/** @deprecated */ /** @deprecated */
resendImages: z.boolean().optional(), resendImages: z.boolean().optional(),
/* vision */ /* vision */