From 2150c4815d8c74a978a4b697aa8f54dc11e035d7 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 3 Sep 2024 11:38:55 -0400 Subject: [PATCH] refactor: dynamic form elements using react-hook-form Controllers --- .../SidePanel/Parameters/DynamicCheckbox.tsx | 76 +++---- .../SidePanel/Parameters/DynamicDropdown.tsx | 82 +++---- .../SidePanel/Parameters/DynamicInput.tsx | 74 +++--- .../Parameters/DynamicInputNumber.tsx | 91 ++++---- .../SidePanel/Parameters/DynamicSlider.tsx | 181 +++++++-------- .../SidePanel/Parameters/DynamicSwitch.tsx | 77 +++---- .../SidePanel/Parameters/DynamicTags.tsx | 210 +++++++----------- .../SidePanel/Parameters/DynamicTextarea.tsx | 79 +++---- .../components/SidePanel/Parameters/Panel.tsx | 123 +++------- packages/data-provider/src/generate.ts | 2 - 10 files changed, 376 insertions(+), 619 deletions(-) diff --git a/client/src/components/SidePanel/Parameters/DynamicCheckbox.tsx b/client/src/components/SidePanel/Parameters/DynamicCheckbox.tsx index 5722a9e5ec..b30a71938b 100644 --- a/client/src/components/SidePanel/Parameters/DynamicCheckbox.tsx +++ b/client/src/components/SidePanel/Parameters/DynamicCheckbox.tsx @@ -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(!!(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 (
@@ -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 && ( ({localize('com_endpoint_default')}:{' '} - {defaultValue ? localize('com_ui_yes') : localize('com_ui_no')}) + {defaultValue != null ? localize('com_ui_yes') : localize('com_ui_no')}) )} - ( + + )} />
{description && ( )} diff --git a/client/src/components/SidePanel/Parameters/DynamicDropdown.tsx b/client/src/components/SidePanel/Parameters/DynamicDropdown.tsx index 4e298fb872..15815e3676 100644 --- a/client/src/components/SidePanel/Parameters/DynamicDropdown.tsx +++ b/client/src/components/SidePanel/Parameters/DynamicDropdown.tsx @@ -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(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({
@@ -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 && ( ({localize('com_endpoint_default')}: {defaultValue}) @@ -82,20 +49,29 @@ function DynamicDropdown({ )}
- ( + + )} /> {description && ( )} diff --git a/client/src/components/SidePanel/Parameters/DynamicInput.tsx b/client/src/components/SidePanel/Parameters/DynamicInput.tsx index feccbae784..ab2d03cc2a 100644 --- a/client/src/components/SidePanel/Parameters/DynamicInput.tsx +++ b/client/src/components/SidePanel/Parameters/DynamicInput.tsx @@ -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({ - 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 (
@@ -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 && ( - ( - {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}`} - ) )}
- ( + 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')} + /> + )} /> {description && ( )} diff --git a/client/src/components/SidePanel/Parameters/DynamicInputNumber.tsx b/client/src/components/SidePanel/Parameters/DynamicInputNumber.tsx index 3807f2ed6f..34a9d908e5 100644 --- a/client/src/components/SidePanel/Parameters/DynamicInputNumber.tsx +++ b/client/src/components/SidePanel/Parameters/DynamicInputNumber.tsx @@ -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({ - 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 (
@@ -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 && ( ({localize('com_endpoint_default')}: {defaultValue}) )} - ( + 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, + )} + /> )} />
{description && ( )} diff --git a/client/src/components/SidePanel/Parameters/DynamicSlider.tsx b/client/src/components/SidePanel/Parameters/DynamicSlider.tsx index 365f07177a..91b89e2bb4 100644 --- a/client/src/components/SidePanel/Parameters/DynamicSlider.tsx +++ b/client/src/components/SidePanel/Parameters/DynamicSlider.tsx @@ -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({ - 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); + }, {} as Record); } 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({
@@ -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 && ( ({localize('com_endpoint_default')}: {defaultValue}) )} - {includeInput && !isEnum ? ( - 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', - ), - )} - /> - ) : ( - ({})} - 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', - ), - )} - /> - )} + ( + <> + {includeInput && !isEnum ? ( + 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', + )} + /> + ) : ( + ({})} + 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', + )} + /> + )} + + 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" + /> + + )} + />
- 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" - /> {description && ( )} diff --git a/client/src/components/SidePanel/Parameters/DynamicSwitch.tsx b/client/src/components/SidePanel/Parameters/DynamicSwitch.tsx index f069e899d2..e310ad4bba 100644 --- a/client/src/components/SidePanel/Parameters/DynamicSwitch.tsx +++ b/client/src/components/SidePanel/Parameters/DynamicSwitch.tsx @@ -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(!!(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 (
@@ -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 && ( - ({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')}) )}
- ( + + )} /> {description && ( )} diff --git a/client/src/components/SidePanel/Parameters/DynamicTags.tsx b/client/src/components/SidePanel/Parameters/DynamicTags.tsx index a028a8aa56..57606372d0 100644 --- a/client/src/components/SidePanel/Parameters/DynamicTags.tsx +++ b/client/src/components/SidePanel/Parameters/DynamicTags.tsx @@ -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(null); const [tagText, setTagText] = useState(''); - const [tags, setTags] = useState( - (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 (
@@ -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 && ( - ( - {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( + ', ', + )}`} )}
-
-
- {currentTags?.map((tag: string, index: number) => ( - { - onTagRemove(index); - if (inputRef.current) { - inputRef.current.focus(); + ( +
+
+ {field.value?.map((tag: string, index: number) => ( + { + 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(); + } + }} + /> + ))} + { + 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 } - }} - /> - ))} - { - 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')} - /> -
-
+ className={cn(defaultTextProps, 'flex h-10 max-h-10 px-3 py-2')} + /> +
+
+ )} + /> {description && ( )} diff --git a/client/src/components/SidePanel/Parameters/DynamicTextarea.tsx b/client/src/components/SidePanel/Parameters/DynamicTextarea.tsx index f6411b155b..af0411fb06 100644 --- a/client/src/components/SidePanel/Parameters/DynamicTextarea.tsx +++ b/client/src/components/SidePanel/Parameters/DynamicTextarea.tsx @@ -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({ - 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 (
@@ -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 && ( - ( - {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}`} - ) )}
- ( + 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', + )} + /> )} /> {description && ( )} diff --git a/client/src/components/SidePanel/Parameters/Panel.tsx b/client/src/components/SidePanel/Parameters/Panel.tsx index f277da1692..e5c4d55fec 100644 --- a/client/src/components/SidePanel/Parameters/Panel.tsx +++ b/client/src/components/SidePanel/Parameters/Panel.tsx @@ -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 { + acc[setting.key] = setting.default; + return acc; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + }, {} as Record), + }); - 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) => { + console.log('Form data:', data); + // Here you can handle the form submission, e.g., send the data to an API + }; return ( -
-
- {' '} - {/* 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 */} - -