| import { useMemo, useCallback } from 'react'; |
| import { OptionTypes } from 'librechat-data-provider'; |
| import type { DynamicSettingProps } from 'librechat-data-provider'; |
| import { Label, Slider, HoverCard, Input, InputNumber, HoverCardTrigger } from '@librechat/client'; |
| import { useLocalize, useDebouncedInput, useParameterEffects, TranslationKeys } from '~/hooks'; |
| import { cn, defaultTextProps, optionText } from '~/utils'; |
| import { ESide, defaultDebouncedDelay } from '~/common'; |
| import { useChatContext } from '~/Providers'; |
| import OptionHover from './OptionHover'; |
|
|
| function DynamicSlider({ |
| label = '', |
| settingKey, |
| defaultValue, |
| range, |
| description = '', |
| columnSpan, |
| setOption, |
| optionType, |
| options, |
| enumMappings, |
| readonly = false, |
| showDefault = false, |
| includeInput = true, |
| labelCode = false, |
| descriptionCode = false, |
| conversation, |
| }: DynamicSettingProps) { |
| const localize = useLocalize(); |
| const { preset } = useChatContext(); |
| const isEnum = useMemo( |
| () => (!range && options && options.length > 0) ?? false, |
| [options, range], |
| ); |
|
|
| const [setInputValue, inputValue, setLocalValue] = useDebouncedInput<string | number>({ |
| optionKey: settingKey, |
| initialValue: optionType !== OptionTypes.Custom ? conversation?.[settingKey] : defaultValue, |
| setter: () => ({}), |
| setOption, |
| delay: isEnum ? 0 : defaultDebouncedDelay, |
| }); |
|
|
| useParameterEffects({ |
| preset, |
| settingKey, |
| defaultValue, |
| conversation, |
| inputValue, |
| setInputValue: setLocalValue, |
| }); |
|
|
| const selectedValue = useMemo(() => { |
| if (isEnum) { |
| return conversation?.[settingKey] ?? defaultValue; |
| } |
| |
|
|
| return inputValue; |
| }, [conversation, defaultValue, settingKey, inputValue, isEnum]); |
|
|
| const enumToNumeric = useMemo(() => { |
| if (isEnum && options) { |
| return options.reduce( |
| (acc, mapping, index) => { |
| acc[mapping] = index; |
| return acc; |
| }, |
| {} as Record<string, number>, |
| ); |
| } |
| return {}; |
| }, [isEnum, options]); |
|
|
| const valueToEnumOption = useMemo(() => { |
| if (isEnum && options) { |
| return options.reduce( |
| (acc, option, index) => { |
| acc[index] = option; |
| return acc; |
| }, |
| {} as Record<number, string>, |
| ); |
| } |
| return {}; |
| }, [isEnum, options]); |
|
|
| const getDisplayValue = useCallback( |
| (value: string | number | undefined | null): string => { |
| if (isEnum && enumMappings && value != null) { |
| const stringValue = String(value); |
| |
| if (stringValue in enumMappings) { |
| const mappedValue = String(enumMappings[stringValue]); |
| |
| if (mappedValue.startsWith('com_')) { |
| return localize(mappedValue as TranslationKeys) ?? mappedValue; |
| } |
| return mappedValue; |
| } |
| } |
| |
| if (value != null) { |
| return String(value); |
| } |
| return String(defaultValue ?? ''); |
| }, |
| [isEnum, enumMappings, defaultValue, localize], |
| ); |
|
|
| const getDefaultDisplayValue = useCallback((): string => { |
| if (defaultValue != null && enumMappings) { |
| const stringDefault = String(defaultValue); |
| if (stringDefault in enumMappings) { |
| const mappedValue = String(enumMappings[stringDefault]); |
| |
| if (mappedValue.startsWith('com_')) { |
| return localize(mappedValue as TranslationKeys) ?? mappedValue; |
| } |
| return mappedValue; |
| } |
| } |
| return String(defaultValue ?? ''); |
| }, [defaultValue, enumMappings, localize]); |
|
|
| 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; |
| } |
|
|
| return ( |
| <div |
| className={cn( |
| 'flex flex-col items-center justify-start gap-2', |
| columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full', |
| )} |
| > |
| <HoverCard openDelay={300}> |
| <HoverCardTrigger className="grid w-full items-center gap-2"> |
| <div className="flex w-full items-center justify-between"> |
| <Label |
| htmlFor={`${settingKey}-dynamic-setting`} |
| className="break-words text-left text-sm font-medium" |
| > |
| {labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '} |
| {showDefault && ( |
| <small className="opacity-40"> |
| ({localize('com_endpoint_default')}: {getDefaultDisplayValue()}) |
| </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} |
| aria-label={localize(label as TranslationKeys)} |
| className={cn( |
| defaultTextProps, |
| cn( |
| optionText, |
| 'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 py-1 text-xs group-hover/temp:border-gray-200', |
| ), |
| )} |
| /> |
| ) : ( |
| <Input |
| id={`${settingKey}-dynamic-setting-input`} |
| disabled={readonly} |
| value={getDisplayValue(selectedValue)} |
| aria-label={localize(label as TranslationKeys)} |
| onChange={() => ({})} |
| className={cn( |
| defaultTextProps, |
| cn( |
| optionText, |
| 'reset-rc-number-input h-auto w-14 border-0 py-1 pl-1 text-center text-xs group-hover/temp:border-gray-200', |
| ), |
| )} |
| /> |
| )} |
| </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])} |
| onDoubleClick={() => setInputValue(defaultValue as string | number)} |
| max={max} |
| aria-label={localize(label as TranslationKeys)} |
| min={range ? range.min : 0} |
| step={range ? (range.step ?? 1) : 1} |
| className="flex h-4 w-full" |
| /> |
| </HoverCardTrigger> |
| {description && ( |
| <OptionHover |
| description={ |
| descriptionCode |
| ? (localize(description as TranslationKeys) ?? description) |
| : description |
| } |
| side={ESide.Left} |
| /> |
| )} |
| </HoverCard> |
| </div> |
| ); |
| } |
|
|
| export default DynamicSlider; |
|
|