From d325fb7cade7df9498cf1aac79d746693ae14023 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 3 Sep 2024 14:44:57 -0400 Subject: [PATCH] fix: use of new dynamic tags causing application crash --- .../Endpoints/Settings/DynamicTags.tsx | 205 ++++++++++++++++++ .../components/Endpoints/Settings/OpenAI.tsx | 2 +- 2 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 client/src/components/Endpoints/Settings/DynamicTags.tsx diff --git a/client/src/components/Endpoints/Settings/DynamicTags.tsx b/client/src/components/Endpoints/Settings/DynamicTags.tsx new file mode 100644 index 0000000000..d07701fd20 --- /dev/null +++ b/client/src/components/Endpoints/Settings/DynamicTags.tsx @@ -0,0 +1,205 @@ +// client/src/components/SidePanel/Parameters/DynamicTags.tsx +import { useState, useMemo, useCallback, useRef } from 'react'; +import { OptionTypes } from 'librechat-data-provider'; +import type { + DynamicSettingProps, + TConversation, + TSetOption, + TPreset, +} from 'librechat-data-provider'; +import { Label, Input, HoverCard, HoverCardTrigger, Tag } from '~/components/ui'; +import OptionHover from '~/components/SidePanel/Parameters/OptionHover'; +import { useChatContext, useToastContext } from '~/Providers'; +import { useLocalize, useParameterEffects } from '~/hooks'; +import { cn, defaultTextProps } from '~/utils'; +import { ESide } from '~/common'; + +function DynamicTags({ + label, + settingKey, + defaultValue = [], + description, + columnSpan, + setOption, + optionType, + placeholder, + readonly = false, + showDefault = true, + labelCode, + descriptionCode, + placeholderCode, + descriptionSide = ESide.Left, + conversation, + minTags, + maxTags, +}: DynamicSettingProps & { + setOption: TSetOption; + conversation: TConversation | TPreset | null; +}) { + const localize = useLocalize(); + const { preset } = useChatContext(); + const { showToast } = useToastContext(); + 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) { + inputRef.current.focus(); + } + }, [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 != null && 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 ( +
+ + +
+ +
+
+
+ {currentTags?.map((tag: string, index: number) => ( + { + onTagRemove(index); + if (inputRef.current) { + inputRef.current.focus(); + } + }} + /> + ))} + { + 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 === true + ? localize(placeholder ?? '') ?? placeholder + : placeholder + } + className={cn(defaultTextProps, 'flex h-10 max-h-10 px-3 py-2')} + /> +
+
+
+ {description != null && ( + + )} +
+
+ ); +} + +export default DynamicTags; diff --git a/client/src/components/Endpoints/Settings/OpenAI.tsx b/client/src/components/Endpoints/Settings/OpenAI.tsx index a95e7823c6..c65e6f5232 100644 --- a/client/src/components/Endpoints/Settings/OpenAI.tsx +++ b/client/src/components/Endpoints/Settings/OpenAI.tsx @@ -19,8 +19,8 @@ import { } from '~/components/ui'; import { cn, defaultTextProps, optionText, removeFocusOutlines, removeFocusRings } from '~/utils'; import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover'; -import { DynamicTags } from '~/components/SidePanel/Parameters'; import { useLocalize, useDebouncedInput } from '~/hooks'; +import DynamicTags from './DynamicTags'; import OptionHover from './OptionHover'; import { ESide } from '~/common';