diff --git a/client/src/components/Prompts/Groups/VariableForm.tsx b/client/src/components/Prompts/Groups/VariableForm.tsx index 711a39bbcde..74103d7c46b 100644 --- a/client/src/components/Prompts/Groups/VariableForm.tsx +++ b/client/src/components/Prompts/Groups/VariableForm.tsx @@ -15,7 +15,7 @@ import { extractVariableInfo, } from '~/utils'; import { useAuthContext, useLocalize, useSubmitMessage } from '~/hooks'; -import { TextareaAutosize, InputWithDropdown } from '~/components/ui'; +import { TextareaAutosize, InputCombobox } from '~/components/ui'; import { code } from '~/components/Chat/Messages/Content/Markdown'; type FieldType = 'text' | 'select'; @@ -51,11 +51,15 @@ type FormValues = { */ const parseFieldConfig = (variable: string): FieldConfig => { - const content = variable; + const content = variable.trim(); if (content.includes(':')) { const [name, options] = content.split(':'); if (options && options.includes('|')) { - return { variable: name, type: 'select', options: options.split('|') }; + return { + variable: name.trim(), + type: 'select', + options: options.split('|').map((opt) => opt.trim()), + }; } } return { variable: content, type: 'text' }; @@ -121,10 +125,13 @@ export default function VariableForm({ const onSubmit = (data: FormValues) => { let text = mainText; data.fields.forEach(({ variable, value }) => { - if (value) { - const regex = new RegExp(variable, 'g'); - text = text.replace(regex, value); + if (!value) { + return; } + + const escapedVariable = variable.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); + const regex = new RegExp(escapedVariable, 'g'); + text = text.replace(regex, value); }); submitPrompt(text); @@ -153,22 +160,29 @@ export default function VariableForm({ { + render={({ field: { onChange, onBlur, value, ref } }) => { if (field.config.type === 'select') { return ( - ); } return ( void; + onBlur: () => void; +}; + +export const InputCombobox: React.FC = ({ + label, + labelClassName, + placeholder = 'Select an option', + options, + className, + value, + onChange, + onBlur, +}) => { + const isOptionObject = (option: unknown): option is OptionWithIcon => { + return option != null && typeof option === 'object' && 'value' in option; + }; + + const [isOpen, setIsOpen] = React.useState(false); + const [inputValue, setInputValue] = React.useState(value); + const [isKeyboardFocus, setIsKeyboardFocus] = React.useState(false); + + React.useEffect(() => { + setInputValue(value); + }, [value]); + + const handleChange = (newValue: string) => { + setInputValue(newValue); + onChange(newValue); + }; + + return ( + + {label != null && ( + + {label} + + )} +
+ handleChange(event.target.value)} + onBlur={() => { + setIsKeyboardFocus(false); + onBlur(); + }} + onFocusVisible={() => { + setIsKeyboardFocus(true); + setIsOpen(true); + }} + onMouseDown={() => { + setIsKeyboardFocus(false); + }} + /> +
+ setIsOpen(false)} + className={cn( + 'z-50 max-h-60 w-full overflow-auto rounded-md bg-surface-primary p-1 shadow-lg', + 'animate-in fade-in-0 zoom-in-95', + )} + > + {options.map((option: string | OptionWithIcon, index: number) => ( + + {isOptionObject(option) && option.icon != null && ( + {option.icon} + )} + {isOptionObject(option) ? option.label : option} + + ))} + +
+ ); +}; diff --git a/client/src/components/ui/index.ts b/client/src/components/ui/index.ts index 4f89d2934c5..0d18503be36 100644 --- a/client/src/components/ui/index.ts +++ b/client/src/components/ui/index.ts @@ -14,6 +14,7 @@ export * from './Prompt'; export * from './QuestionMark'; export * from './Slider'; export * from './Separator'; +export * from './InputCombobox'; export * from './Skeleton'; export * from './Switch'; export * from './Table'; @@ -32,4 +33,4 @@ export { default as SelectDropDown } from './SelectDropDown'; export { default as MultiSelectPop } from './MultiSelectPop'; export { default as InputWithDropdown } from './InputWithDropDown'; export { default as SelectDropDownPop } from './SelectDropDownPop'; -export { default as MultiSelectDropDown } from './MultiSelectDropDown'; \ No newline at end of file +export { default as MultiSelectDropDown } from './MultiSelectDropDown';