mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
refactor: dynamic form elements using react-hook-form Controllers
This commit is contained in:
parent
fac2acd4cf
commit
2150c4815d
10 changed files with 376 additions and 619 deletions
|
|
@ -1,63 +1,30 @@
|
|||
// client/src/components/SidePanel/Parameters/DynamicCheckbox.tsx
|
||||
import { useMemo, useState } from 'react';
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Label, Checkbox, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
|
||||
function DynamicCheckbox({
|
||||
label,
|
||||
label = '',
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
description = '',
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
conversation,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
const [inputValue, setInputValue] = useState<boolean>(!!(defaultValue as boolean | undefined));
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
return conversation?.[settingKey] ?? defaultValue;
|
||||
}, [conversation, defaultValue, optionType, settingKey, inputValue]);
|
||||
|
||||
const handleCheckedChange = (checked: boolean) => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
setInputValue(checked);
|
||||
return;
|
||||
}
|
||||
setOption(settingKey)(checked);
|
||||
};
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
preventDelayedUpdate: true,
|
||||
});
|
||||
const { control } = useFormContext();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col items-center justify-start gap-6 ${
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
}`}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
|
|
@ -67,26 +34,35 @@ function DynamicCheckbox({
|
|||
htmlFor={`${settingKey}-dynamic-checkbox`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{labelCode === true ? localize(label) ?? label : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}:{' '}
|
||||
{defaultValue ? localize('com_ui_yes') : localize('com_ui_no')})
|
||||
{defaultValue != null ? localize('com_ui_yes') : localize('com_ui_no')})
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
<Checkbox
|
||||
id={`${settingKey}-dynamic-checkbox`}
|
||||
disabled={readonly}
|
||||
checked={selectedValue}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
className="mt-[2px] focus:ring-opacity-20 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:focus:ring-gray-600 dark:focus:ring-opacity-50 dark:focus:ring-offset-0"
|
||||
<Controller
|
||||
name={settingKey}
|
||||
control={control}
|
||||
defaultValue={defaultValue as boolean}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
id={`${settingKey}-dynamic-checkbox`}
|
||||
disabled={readonly}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
className="mt-[2px] focus:ring-opacity-20 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:focus:ring-gray-600 dark:focus:ring-opacity-50 dark:focus:ring-offset-0"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
description={
|
||||
descriptionCode === true ? localize(description) ?? description : description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,27 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
// client/src/components/SidePanel/Parameters/DynamicDropdown.tsx
|
||||
import React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Label, HoverCard, HoverCardTrigger, SelectDropDown } from '~/components/ui';
|
||||
import { useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
import { cn } from '~/utils';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
|
||||
function DynamicDropdown({
|
||||
label,
|
||||
label = '',
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
description = '',
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
options,
|
||||
// type: _type,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
conversation,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
const [inputValue, setInputValue] = useState<string | null>(null);
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
return conversation?.[settingKey] ?? defaultValue;
|
||||
}, [conversation, defaultValue, optionType, settingKey, inputValue]);
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
setInputValue(value);
|
||||
return;
|
||||
}
|
||||
setOption(settingKey)(value);
|
||||
};
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
preventDelayedUpdate: true,
|
||||
});
|
||||
const { control } = useFormContext();
|
||||
|
||||
if (!options || options.length === 0) {
|
||||
return null;
|
||||
|
|
@ -64,7 +31,7 @@ function DynamicDropdown({
|
|||
<div
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-start gap-6',
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full',
|
||||
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full',
|
||||
)}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
|
|
@ -74,7 +41,7 @@ function DynamicDropdown({
|
|||
htmlFor={`${settingKey}-dynamic-dropdown`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}
|
||||
{labelCode === true ? localize(label) ?? label : label || settingKey}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue})
|
||||
|
|
@ -82,20 +49,29 @@ function DynamicDropdown({
|
|||
)}
|
||||
</Label>
|
||||
</div>
|
||||
<SelectDropDown
|
||||
showLabel={false}
|
||||
emptyTitle={true}
|
||||
disabled={readonly}
|
||||
value={selectedValue}
|
||||
setValue={handleChange}
|
||||
availableValues={options}
|
||||
containerClassName="w-full"
|
||||
id={`${settingKey}-dynamic-dropdown`}
|
||||
<Controller
|
||||
name={settingKey}
|
||||
control={control}
|
||||
defaultValue={defaultValue as string}
|
||||
render={({ field }) => (
|
||||
<SelectDropDown
|
||||
showLabel={false}
|
||||
emptyTitle={true}
|
||||
disabled={readonly}
|
||||
value={field.value}
|
||||
setValue={field.onChange}
|
||||
availableValues={options}
|
||||
containerClassName="w-full"
|
||||
id={`${settingKey}-dynamic-dropdown`}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
description={
|
||||
descriptionCode === true ? localize(description) ?? description : description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,33 @@
|
|||
// client/src/components/SidePanel/Parameters/DynamicInput.tsx
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { useLocalize, useDebouncedInput, useParameterEffects } from '~/hooks';
|
||||
import React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { Label, Input, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { cn, defaultTextProps } from '~/utils';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
|
||||
function DynamicInput({
|
||||
label,
|
||||
label = '',
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
description = '',
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
placeholder,
|
||||
placeholder = '',
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
placeholderCode,
|
||||
conversation,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
|
||||
const [setInputValue, inputValue] = useDebouncedInput<string | null>({
|
||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||
initialValue:
|
||||
optionType !== OptionTypes.Custom
|
||||
? (conversation?.[settingKey] as string)
|
||||
: (defaultValue as string),
|
||||
setter: () => ({}),
|
||||
setOption,
|
||||
});
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
});
|
||||
const { control } = useFormContext();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col items-center justify-start gap-6 ${
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
}`}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
|
|
@ -59,30 +37,40 @@ function DynamicInput({
|
|||
htmlFor={`${settingKey}-dynamic-input`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{labelCode === true ? localize(label) ?? label : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
(
|
||||
{typeof defaultValue === 'undefined' || !(defaultValue as string)?.length
|
||||
{typeof defaultValue === 'undefined' ||
|
||||
!((defaultValue as string | undefined)?.length ?? 0)
|
||||
? localize('com_endpoint_default_blank')
|
||||
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
||||
)
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
<Input
|
||||
id={`${settingKey}-dynamic-input`}
|
||||
disabled={readonly}
|
||||
value={inputValue ?? ''}
|
||||
onChange={setInputValue}
|
||||
placeholder={placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder}
|
||||
className={cn(defaultTextProps, 'flex h-10 max-h-10 w-full resize-none px-3 py-2')}
|
||||
<Controller
|
||||
name={settingKey}
|
||||
control={control}
|
||||
defaultValue={defaultValue as string}
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
id={`${settingKey}-dynamic-input`}
|
||||
disabled={readonly}
|
||||
value={field.value ?? ''}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
placeholder={
|
||||
placeholderCode === true ? localize(placeholder) ?? placeholder : placeholder
|
||||
}
|
||||
className={cn(defaultTextProps, 'flex h-10 max-h-10 w-full resize-none px-3 py-2')}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
description={
|
||||
descriptionCode === true ? localize(description) ?? description : description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,37 @@
|
|||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import type { ValueType } from '@rc-component/mini-decimal';
|
||||
// client/src/components/SidePanel/Parameters/DynamicInputNumber.tsx
|
||||
import React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Label, HoverCard, InputNumber, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize, useDebouncedInput, useParameterEffects } from '~/hooks';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn, defaultTextProps, optionText } from '~/utils';
|
||||
import { ESide } from '~/common';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
|
||||
function DynamicInputNumber({
|
||||
label,
|
||||
label = '',
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
description = '',
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
placeholderCode,
|
||||
placeholder,
|
||||
conversation,
|
||||
placeholder = '',
|
||||
range,
|
||||
className = '',
|
||||
inputClassName = '',
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
|
||||
const [setInputValue, inputValue] = useDebouncedInput<ValueType | null>({
|
||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||
initialValue:
|
||||
optionType !== OptionTypes.Custom
|
||||
? (conversation?.[settingKey] as number)
|
||||
: (defaultValue as number),
|
||||
setter: () => ({}),
|
||||
setOption,
|
||||
});
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
});
|
||||
const { control } = useFormContext();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-start gap-6',
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full',
|
||||
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
|
@ -64,39 +42,46 @@ function DynamicInputNumber({
|
|||
htmlFor={`${settingKey}-dynamic-setting`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{labelCode === true ? localize(label) ?? label : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue})
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
<InputNumber
|
||||
id={`${settingKey}-dynamic-setting-input-number`}
|
||||
disabled={readonly}
|
||||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
min={range?.min}
|
||||
max={range?.max}
|
||||
step={range?.step}
|
||||
placeholder={
|
||||
placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder
|
||||
}
|
||||
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',
|
||||
),
|
||||
inputClassName,
|
||||
<Controller
|
||||
name={settingKey}
|
||||
control={control}
|
||||
defaultValue={defaultValue as number}
|
||||
render={({ field }) => (
|
||||
<InputNumber
|
||||
id={`${settingKey}-dynamic-setting-input-number`}
|
||||
disabled={readonly}
|
||||
value={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
min={range?.min}
|
||||
max={range?.max}
|
||||
step={range?.step}
|
||||
placeholder={
|
||||
placeholderCode === true ? localize(placeholder) ?? placeholder : placeholder
|
||||
}
|
||||
controls={false}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
optionText,
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
|
||||
inputClassName,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
description={
|
||||
descriptionCode === true ? localize(description) ?? description : description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,67 +1,41 @@
|
|||
import { useMemo, useCallback } from 'react';
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
// client/src/components/SidePanel/Parameters/DynamicSlider.tsx
|
||||
import React, { useMemo } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Label, Slider, HoverCard, Input, InputNumber, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize, useDebouncedInput, useParameterEffects } from '~/hooks';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn, defaultTextProps, optionText } from '~/utils';
|
||||
import { ESide, defaultDebouncedDelay } from '~/common';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { ESide } from '~/common';
|
||||
import OptionHover from './OptionHover';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
|
||||
function DynamicSlider({
|
||||
label,
|
||||
label = '',
|
||||
settingKey,
|
||||
defaultValue,
|
||||
range,
|
||||
description,
|
||||
description = '',
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
options,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
includeInput = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
conversation,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
const isEnum = useMemo(() => !range && options && options.length > 0, [options, range]);
|
||||
const { control } = useFormContext();
|
||||
|
||||
const [setInputValue, inputValue] = useDebouncedInput<string | number>({
|
||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||
initialValue: optionType !== OptionTypes.Custom ? conversation?.[settingKey] : defaultValue,
|
||||
setter: () => ({}),
|
||||
setOption,
|
||||
delay: isEnum ? 0 : defaultDebouncedDelay,
|
||||
});
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
preventDelayedUpdate: isEnum,
|
||||
});
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
if (isEnum) {
|
||||
return conversation?.[settingKey] ?? defaultValue;
|
||||
}
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
|
||||
return inputValue;
|
||||
}, [conversation, defaultValue, settingKey, inputValue, isEnum]);
|
||||
const isEnum = useMemo(
|
||||
() => (!range && options && options.length > 0) ?? false,
|
||||
[options, range],
|
||||
);
|
||||
|
||||
const enumToNumeric = useMemo(() => {
|
||||
if (isEnum && options) {
|
||||
return options.reduce((acc, mapping, index) => {
|
||||
acc[mapping] = index;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
}, {} as Record<string, number | undefined>);
|
||||
}
|
||||
return {};
|
||||
}, [isEnum, options]);
|
||||
|
|
@ -76,16 +50,15 @@ function DynamicSlider({
|
|||
return {};
|
||||
}, [isEnum, options]);
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(value: number) => {
|
||||
if (isEnum) {
|
||||
setInputValue(valueToEnumOption[value]);
|
||||
} else {
|
||||
setInputValue(value);
|
||||
}
|
||||
},
|
||||
[isEnum, setInputValue, valueToEnumOption],
|
||||
);
|
||||
const max = useMemo(() => {
|
||||
if (isEnum && options) {
|
||||
return options.length - 1;
|
||||
} else if (range) {
|
||||
return range.max;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}, [isEnum, options, range]);
|
||||
|
||||
if (!range && !isEnum) {
|
||||
return null;
|
||||
|
|
@ -95,7 +68,7 @@ function DynamicSlider({
|
|||
<div
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-start gap-6',
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full',
|
||||
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full',
|
||||
)}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
|
|
@ -105,66 +78,72 @@ function DynamicSlider({
|
|||
htmlFor={`${settingKey}-dynamic-setting`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{labelCode === true ? localize(label) ?? label : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue})
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
{includeInput && !isEnum ? (
|
||||
<InputNumber
|
||||
id={`${settingKey}-dynamic-setting-input-number`}
|
||||
disabled={readonly}
|
||||
value={inputValue ?? defaultValue}
|
||||
onChange={(value) => setInputValue(Number(value))}
|
||||
max={range ? range.max : (options?.length ?? 0) - 1}
|
||||
min={range ? range.min : 0}
|
||||
step={range ? range.step ?? 1 : 1}
|
||||
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',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
id={`${settingKey}-dynamic-setting-input`}
|
||||
disabled={readonly}
|
||||
value={selectedValue ?? defaultValue}
|
||||
onChange={() => ({})}
|
||||
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',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Controller
|
||||
name={settingKey}
|
||||
control={control}
|
||||
defaultValue={defaultValue as number | string}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
{includeInput && !isEnum ? (
|
||||
<InputNumber
|
||||
id={`${settingKey}-dynamic-setting-input-number`}
|
||||
disabled={readonly}
|
||||
value={field.value as number}
|
||||
onChange={(value) => field.onChange(Number(value))}
|
||||
max={range ? range.max : (options?.length ?? 0) - 1}
|
||||
min={range ? range.min : 0}
|
||||
step={range ? range.step ?? 1 : 1}
|
||||
controls={false}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
optionText,
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
id={`${settingKey}-dynamic-setting-input`}
|
||||
disabled={readonly}
|
||||
value={field.value as string}
|
||||
onChange={() => ({})}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
optionText,
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Slider
|
||||
id={`${settingKey}-dynamic-setting-slider`}
|
||||
disabled={readonly}
|
||||
value={[
|
||||
isEnum ? enumToNumeric[field.value as string] ?? 0 : (field.value as number),
|
||||
]}
|
||||
onValueChange={(value) =>
|
||||
field.onChange(isEnum ? valueToEnumOption[value[0]] : value[0])
|
||||
}
|
||||
max={max}
|
||||
min={range ? range.min : 0}
|
||||
step={range ? range.step ?? 1 : 1}
|
||||
className="flex h-4 w-full"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
id={`${settingKey}-dynamic-setting-slider`}
|
||||
disabled={readonly}
|
||||
value={[
|
||||
isEnum
|
||||
? enumToNumeric[(selectedValue as number) ?? '']
|
||||
: (inputValue as number) ?? (defaultValue as number),
|
||||
]}
|
||||
onValueChange={(value) => handleValueChange(value[0])}
|
||||
doubleClickHandler={() => setInputValue(defaultValue as string | number)}
|
||||
max={isEnum && options ? options.length - 1 : range ? range.max : 0}
|
||||
min={range ? range.min : 0}
|
||||
step={range ? range.step ?? 1 : 1}
|
||||
className="flex h-4 w-full"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
description={
|
||||
descriptionCode === true ? localize(description) ?? description : description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,30 @@
|
|||
import { useState, useMemo } from 'react';
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
// client/src/components/SidePanel/Parameters/DynamicSwitch.tsx
|
||||
import React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Label, Switch, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
|
||||
function DynamicSwitch({
|
||||
label,
|
||||
label = '',
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
description = '',
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
conversation,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
const [inputValue, setInputValue] = useState<boolean>(!!(defaultValue as boolean | undefined));
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
preventDelayedUpdate: true,
|
||||
});
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
return conversation?.[settingKey] ?? defaultValue;
|
||||
}, [conversation, defaultValue, optionType, settingKey, inputValue]);
|
||||
|
||||
const handleCheckedChange = (checked: boolean) => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
setInputValue(checked);
|
||||
return;
|
||||
}
|
||||
setOption(settingKey)(checked);
|
||||
};
|
||||
const { control } = useFormContext();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col items-center justify-start gap-6 ${
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
}`}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
|
|
@ -65,25 +34,35 @@ function DynamicSwitch({
|
|||
htmlFor={`${settingKey}-dynamic-switch`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{labelCode === true ? localize(label) ?? label : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue ? 'com_ui_on' : 'com_ui_off'})
|
||||
({localize('com_endpoint_default')}:{' '}
|
||||
{defaultValue != null ? localize('com_ui_on') : localize('com_ui_off')})
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
<Switch
|
||||
id={`${settingKey}-dynamic-switch`}
|
||||
checked={selectedValue}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
disabled={readonly}
|
||||
className="flex"
|
||||
<Controller
|
||||
name={settingKey}
|
||||
control={control}
|
||||
defaultValue={defaultValue as boolean}
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id={`${settingKey}-dynamic-switch`}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={readonly}
|
||||
className="flex"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
description={
|
||||
descriptionCode === true ? localize(description) ?? description : description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,35 @@
|
|||
// client/src/components/SidePanel/Parameters/DynamicTags.tsx
|
||||
import { useState, useMemo, useCallback, useRef } from 'react';
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Label, Input, HoverCard, HoverCardTrigger, Tag } from '~/components/ui';
|
||||
import { useChatContext, useToastContext } from '~/Providers';
|
||||
import { useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn, defaultTextProps } from '~/utils';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
|
||||
function DynamicTags({
|
||||
label,
|
||||
label = '',
|
||||
settingKey,
|
||||
defaultValue = [],
|
||||
description,
|
||||
description = '',
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
placeholder,
|
||||
placeholder = '',
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
placeholderCode,
|
||||
descriptionSide = ESide.Left,
|
||||
conversation,
|
||||
minTags,
|
||||
maxTags,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
const { showToast } = useToastContext();
|
||||
const { control } = useFormContext();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [tagText, setTagText] = useState<string>('');
|
||||
const [tags, setTags] = useState<string[] | undefined>(
|
||||
(defaultValue as string[] | undefined) ?? [],
|
||||
);
|
||||
|
||||
const updateState = useCallback(
|
||||
(update: string[]) => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
setTags(update);
|
||||
return;
|
||||
}
|
||||
setOption(settingKey)(update);
|
||||
},
|
||||
[optionType, setOption, settingKey],
|
||||
);
|
||||
|
||||
const onTagClick = useCallback(() => {
|
||||
if (inputRef.current) {
|
||||
|
|
@ -55,69 +37,10 @@ function DynamicTags({
|
|||
}
|
||||
}, [inputRef]);
|
||||
|
||||
const currentTags: string[] | undefined = useMemo(() => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
return tags;
|
||||
}
|
||||
|
||||
if (!conversation?.[settingKey]) {
|
||||
return defaultValue ?? [];
|
||||
}
|
||||
|
||||
return conversation?.[settingKey];
|
||||
}, [conversation, defaultValue, optionType, settingKey, tags]);
|
||||
|
||||
const onTagRemove = useCallback(
|
||||
(indexToRemove: number) => {
|
||||
if (!currentTags) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (minTags && currentTags.length <= minTags) {
|
||||
showToast({
|
||||
message: localize('com_ui_min_tags', minTags + ''),
|
||||
status: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const update = currentTags.filter((_, index) => index !== indexToRemove);
|
||||
updateState(update);
|
||||
},
|
||||
[localize, minTags, currentTags, showToast, updateState],
|
||||
);
|
||||
|
||||
const onTagAdd = useCallback(() => {
|
||||
if (!tagText) {
|
||||
return;
|
||||
}
|
||||
|
||||
let update = [...(currentTags ?? []), tagText];
|
||||
if (maxTags && update.length > maxTags) {
|
||||
showToast({
|
||||
message: localize('com_ui_max_tags', maxTags + ''),
|
||||
status: 'warning',
|
||||
});
|
||||
update = update.slice(-maxTags);
|
||||
}
|
||||
updateState(update);
|
||||
setTagText('');
|
||||
}, [tagText, currentTags, updateState, maxTags, showToast, localize]);
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue: typeof defaultValue === 'undefined' ? [] : defaultValue,
|
||||
inputValue: tags,
|
||||
setInputValue: setTags,
|
||||
preventDelayedUpdate: true,
|
||||
conversation,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col items-center justify-start gap-6 ${
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
}`}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
|
|
@ -127,61 +50,86 @@ function DynamicTags({
|
|||
htmlFor={`${settingKey}-dynamic-input`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{labelCode === true ? localize(label) ?? label : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
(
|
||||
{typeof defaultValue === 'undefined' || !(defaultValue as string)?.length
|
||||
{typeof defaultValue === 'undefined' ||
|
||||
!((defaultValue as string[] | undefined)?.length ?? 0)
|
||||
? localize('com_endpoint_default_blank')
|
||||
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
||||
)
|
||||
: `${localize('com_endpoint_default')}: ${(defaultValue as string[]).join(
|
||||
', ',
|
||||
)}`}
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-muted mb-2 flex flex-wrap gap-1 break-all rounded-lg">
|
||||
{currentTags?.map((tag: string, index: number) => (
|
||||
<Tag
|
||||
key={`${tag}-${index}`}
|
||||
label={tag}
|
||||
onClick={onTagClick}
|
||||
onRemove={() => {
|
||||
onTagRemove(index);
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
<Controller
|
||||
name={settingKey}
|
||||
control={control}
|
||||
defaultValue={defaultValue as string[]}
|
||||
render={({ field }) => (
|
||||
<div>
|
||||
<div className="bg-muted mb-2 flex flex-wrap gap-1 break-all rounded-lg">
|
||||
{field.value?.map((tag: string, index: number) => (
|
||||
<Tag
|
||||
key={`${tag}-${index}`}
|
||||
label={tag}
|
||||
onClick={onTagClick}
|
||||
onRemove={() => {
|
||||
if (minTags != null && field.value.length <= minTags) {
|
||||
showToast({
|
||||
message: localize('com_ui_min_tags', minTags + ''),
|
||||
status: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const newTags = field.value.filter((_, i) => i !== index);
|
||||
field.onChange(newTags);
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<Input
|
||||
ref={inputRef}
|
||||
id={`${settingKey}-dynamic-input`}
|
||||
disabled={readonly}
|
||||
value={tagText}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Backspace' && !tagText && field.value.length > 0) {
|
||||
const newTags = field.value.slice(0, -1);
|
||||
field.onChange(newTags);
|
||||
}
|
||||
if (e.key === 'Enter' && tagText) {
|
||||
const newTags = [...field.value, tagText];
|
||||
if (maxTags != null && newTags.length > maxTags) {
|
||||
showToast({
|
||||
message: localize('com_ui_max_tags', maxTags + ''),
|
||||
status: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
field.onChange(newTags);
|
||||
setTagText('');
|
||||
}
|
||||
}}
|
||||
onChange={(e) => setTagText(e.target.value)}
|
||||
placeholder={
|
||||
placeholderCode === true ? localize(placeholder) ?? placeholder : placeholder
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<Input
|
||||
ref={inputRef}
|
||||
id={`${settingKey}-dynamic-input`}
|
||||
disabled={readonly}
|
||||
value={tagText}
|
||||
onKeyDown={(e) => {
|
||||
if (!currentTags) {
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Backspace' && !tagText) {
|
||||
onTagRemove(currentTags.length - 1);
|
||||
}
|
||||
if (e.key === 'Enter') {
|
||||
onTagAdd();
|
||||
}
|
||||
}}
|
||||
onChange={(e) => setTagText(e.target.value)}
|
||||
placeholder={
|
||||
placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder
|
||||
}
|
||||
className={cn(defaultTextProps, 'flex h-10 max-h-10 px-3 py-2')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
className={cn(defaultTextProps, 'flex h-10 max-h-10 px-3 py-2')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
description={
|
||||
descriptionCode === true ? localize(description) ?? description : description
|
||||
}
|
||||
side={descriptionSide as ESide}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,33 @@
|
|||
// client/src/components/SidePanel/Parameters/DynamicTextarea.tsx
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Label, TextareaAutosize, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize, useDebouncedInput, useParameterEffects } from '~/hooks';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn, defaultTextProps } from '~/utils';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
|
||||
function DynamicTextarea({
|
||||
label,
|
||||
label = '',
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
description = '',
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
placeholder,
|
||||
placeholder = '',
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
placeholderCode,
|
||||
conversation,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
|
||||
const [setInputValue, inputValue] = useDebouncedInput<string | null>({
|
||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||
initialValue:
|
||||
optionType !== OptionTypes.Custom
|
||||
? (conversation?.[settingKey] as string)
|
||||
: (defaultValue as string),
|
||||
setter: () => ({}),
|
||||
setOption,
|
||||
});
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
});
|
||||
const { control } = useFormContext();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col items-center justify-start gap-6 ${
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
}`}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
|
|
@ -59,34 +37,43 @@ function DynamicTextarea({
|
|||
htmlFor={`${settingKey}-dynamic-textarea`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{labelCode === true ? localize(label) ?? label : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
(
|
||||
{typeof defaultValue === 'undefined' || !(defaultValue as string)?.length
|
||||
{typeof defaultValue === 'undefined' ||
|
||||
!((defaultValue as string | undefined)?.length ?? 0)
|
||||
? localize('com_endpoint_default_blank')
|
||||
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
||||
)
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
<TextareaAutosize
|
||||
id={`${settingKey}-dynamic-textarea`}
|
||||
disabled={readonly}
|
||||
value={inputValue ?? ''}
|
||||
onChange={setInputValue}
|
||||
placeholder={placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
// TODO: configurable max height
|
||||
'flex max-h-[138px] min-h-[100px] w-full resize-none px-3 py-2',
|
||||
<Controller
|
||||
name={settingKey}
|
||||
control={control}
|
||||
defaultValue={defaultValue as string}
|
||||
render={({ field }) => (
|
||||
<TextareaAutosize
|
||||
id={`${settingKey}-dynamic-textarea`}
|
||||
disabled={readonly}
|
||||
value={field.value ?? ''}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
placeholder={
|
||||
placeholderCode === true ? localize(placeholder) ?? placeholder : placeholder
|
||||
}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
'flex max-h-[138px] min-h-[100px] w-full resize-none px-3 py-2',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
description={
|
||||
descriptionCode === true ? localize(description) ?? description : description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import React from 'react';
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import { ComponentTypes } from 'librechat-data-provider';
|
||||
import type {
|
||||
DynamicSettingProps,
|
||||
SettingDefinition,
|
||||
SettingsConfiguration,
|
||||
} from 'librechat-data-provider';
|
||||
import { useSetIndexOptions } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import type { DynamicSettingProps, SettingsConfiguration } from 'librechat-data-provider';
|
||||
import {
|
||||
DynamicDropdown,
|
||||
DynamicCheckbox,
|
||||
|
|
@ -162,92 +158,37 @@ const componentMapping: Record<ComponentTypes, React.ComponentType<DynamicSettin
|
|||
};
|
||||
|
||||
export default function Parameters() {
|
||||
const { conversation } = useChatContext();
|
||||
const { setOption } = useSetIndexOptions();
|
||||
const methods = useForm({
|
||||
defaultValues: settingsConfiguration.reduce((acc, setting) => {
|
||||
acc[setting.key] = setting.default;
|
||||
return acc;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
}, {} as Record<string, any>),
|
||||
});
|
||||
|
||||
const temperature = settingsConfiguration.find(
|
||||
(setting) => setting.key === 'temperature',
|
||||
) as SettingDefinition;
|
||||
const TempComponent = componentMapping[temperature.component];
|
||||
const { key: temp, default: tempDefault, ...tempSettings } = temperature;
|
||||
|
||||
const imageDetail = settingsConfiguration.find(
|
||||
(setting) => setting.key === 'imageDetail',
|
||||
) as SettingDefinition;
|
||||
const DetailComponent = componentMapping[imageDetail.component];
|
||||
const { key: detail, default: detailDefault, ...detailSettings } = imageDetail;
|
||||
|
||||
const resendFiles = settingsConfiguration.find(
|
||||
(setting) => setting.key === 'resendFiles',
|
||||
) as SettingDefinition;
|
||||
const Switch = componentMapping[resendFiles.component];
|
||||
const { key: switchKey, default: switchDefault, ...switchSettings } = resendFiles;
|
||||
|
||||
const promptPrefix = settingsConfiguration.find(
|
||||
(setting) => setting.key === 'promptPrefix',
|
||||
) as SettingDefinition;
|
||||
const Textarea = componentMapping[promptPrefix.component];
|
||||
const { key: textareaKey, default: textareaDefault, ...textareaSettings } = promptPrefix;
|
||||
|
||||
const chatGptLabel = settingsConfiguration.find(
|
||||
(setting) => setting.key === 'chatGptLabel',
|
||||
) as SettingDefinition;
|
||||
const Input = componentMapping[chatGptLabel.component];
|
||||
const { key: inputKey, default: inputDefault, ...inputSettings } = chatGptLabel;
|
||||
|
||||
const stop = settingsConfiguration.find((setting) => setting.key === 'stop') as SettingDefinition;
|
||||
const Tags = componentMapping[stop.component];
|
||||
const { key: stopKey, default: stopDefault, ...stopSettings } = stop;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const onSubmit = (data: Record<string, any>) => {
|
||||
console.log('Form data:', data);
|
||||
// Here you can handle the form submission, e.g., send the data to an API
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-auto max-w-full overflow-x-hidden p-3">
|
||||
<div className="grid grid-cols-4 gap-6">
|
||||
{' '}
|
||||
{/* This is the parent element containing all settings */}
|
||||
{/* Below is an example of an applied dynamic setting, each be contained by a div with the column span specified */}
|
||||
<Input
|
||||
settingKey={inputKey}
|
||||
defaultValue={inputDefault}
|
||||
{...inputSettings}
|
||||
setOption={setOption}
|
||||
conversation={conversation}
|
||||
/>
|
||||
<Textarea
|
||||
settingKey={textareaKey}
|
||||
defaultValue={textareaDefault}
|
||||
{...textareaSettings}
|
||||
setOption={setOption}
|
||||
conversation={conversation}
|
||||
/>
|
||||
<TempComponent
|
||||
settingKey={temp}
|
||||
defaultValue={tempDefault}
|
||||
{...tempSettings}
|
||||
setOption={setOption}
|
||||
conversation={conversation}
|
||||
/>
|
||||
<Switch
|
||||
settingKey={switchKey}
|
||||
defaultValue={switchDefault}
|
||||
{...switchSettings}
|
||||
setOption={setOption}
|
||||
conversation={conversation}
|
||||
/>
|
||||
<DetailComponent
|
||||
settingKey={detail}
|
||||
defaultValue={detailDefault}
|
||||
{...detailSettings}
|
||||
setOption={setOption}
|
||||
conversation={conversation}
|
||||
/>
|
||||
<Tags
|
||||
settingKey={stopKey}
|
||||
defaultValue={stopDefault}
|
||||
{...stopSettings}
|
||||
setOption={setOption}
|
||||
conversation={conversation}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={methods.handleSubmit(onSubmit)}>
|
||||
<div className="h-auto max-w-full overflow-x-hidden p-3">
|
||||
<div className="grid grid-cols-4 gap-6">
|
||||
{settingsConfiguration.map((setting) => {
|
||||
const Component = componentMapping[setting.component];
|
||||
const { key, default: defaultValue, ...rest } = setting;
|
||||
|
||||
return <Component key={key} settingKey={key} defaultValue={defaultValue} {...rest} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" className="mt-4 rounded bg-blue-500 px-4 py-2 text-white">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,8 +69,6 @@ export interface SettingDefinition {
|
|||
export type DynamicSettingProps = Partial<SettingDefinition> & {
|
||||
readonly?: boolean;
|
||||
settingKey: string;
|
||||
setOption: TSetOption;
|
||||
conversation: TConversation | TPreset | null;
|
||||
defaultValue?: number | boolean | string | string[];
|
||||
className?: string;
|
||||
inputClassName?: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue