diff --git a/web/app/components/app/configuration/config-var/index.tsx b/web/app/components/app/configuration/config-var/index.tsx index bf528f8ca6..68e835602b 100644 --- a/web/app/components/app/configuration/config-var/index.tsx +++ b/web/app/components/app/configuration/config-var/index.tsx @@ -6,7 +6,7 @@ import type { PromptVariable } from '@/models/debug' import { useBoolean } from 'ahooks' import { produce } from 'immer' import * as React from 'react' -import { useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { ReactSortable } from 'react-sortablejs' import { useContext } from 'use-context-selector' @@ -33,11 +33,55 @@ type ExternalDataToolParams = { type: string index: number name: string - config?: Record + config?: Record icon?: string icon_background?: string } +const BASIC_INPUT_TYPES = new Set(['string', 'paragraph', 'select', 'number', 'checkbox']) + +const toInputVar = (item: PromptVariable): InputVar => ({ + ...item, + label: item.name, + variable: item.key, + type: (item.type === 'string' ? InputVarType.textInput : item.type) as InputVarType, + required: item.required ?? false, +}) + +const buildPromptVariableFromInput = (payload: InputVar): PromptVariable => { + const { variable, label, type, ...rest } = payload + const nextType = type === InputVarType.textInput ? 'string' : type + const nextItem: PromptVariable = { + ...rest, + type: nextType, + key: variable, + name: label as string, + } + if (payload.type === InputVarType.textInput) + nextItem.max_length = nextItem.max_length || DEFAULT_VALUE_MAX_LEN + + if (payload.type !== InputVarType.select) + delete nextItem.options + + return nextItem +} + +const getDuplicateError = (list: PromptVariable[]) => { + if (hasDuplicateStr(list.map(item => item.key))) { + return { + errorMsgKey: 'appDebug.varKeyError.keyAlreadyExists', + typeName: 'appDebug.variableConfig.varName', + } + } + if (hasDuplicateStr(list.map(item => item.name as string))) { + return { + errorMsgKey: 'appDebug.varKeyError.keyAlreadyExists', + typeName: 'appDebug.variableConfig.labelName', + } + } + return null +} + export type IConfigVarProps = { promptVariables: PromptVariable[] readonly?: boolean @@ -55,61 +99,32 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar const hasVar = promptVariables.length > 0 const [currIndex, setCurrIndex] = useState(-1) const currItem = currIndex !== -1 ? promptVariables[currIndex] : null - const currItemToEdit: InputVar | null = (() => { + const currItemToEdit = useMemo(() => { if (!currItem) return null - - return { - ...currItem, - label: currItem.name, - variable: currItem.key, - type: currItem.type === 'string' ? InputVarType.textInput : currItem.type, - } as InputVar - })() - const updatePromptVariableItem = (payload: InputVar) => { + return toInputVar(currItem) + }, [currItem]) + const updatePromptVariableItem = useCallback((payload: InputVar) => { const newPromptVariables = produce(promptVariables, (draft) => { - const { variable, label, type, ...rest } = payload - draft[currIndex] = { - ...rest, - type: type === InputVarType.textInput ? 'string' : type, - key: variable, - name: label as string, - } - - if (payload.type === InputVarType.textInput) - draft[currIndex].max_length = draft[currIndex].max_length || DEFAULT_VALUE_MAX_LEN - - if (payload.type !== InputVarType.select) - delete draft[currIndex].options + draft[currIndex] = buildPromptVariableFromInput(payload) }) - - const newList = newPromptVariables - let errorMsgKey = '' - let typeName = '' - if (hasDuplicateStr(newList.map(item => item.key))) { - errorMsgKey = 'appDebug.varKeyError.keyAlreadyExists' - typeName = 'appDebug.variableConfig.varName' - } - else if (hasDuplicateStr(newList.map(item => item.name as string))) { - errorMsgKey = 'appDebug.varKeyError.keyAlreadyExists' - typeName = 'appDebug.variableConfig.labelName' - } - - if (errorMsgKey) { + const duplicateError = getDuplicateError(newPromptVariables) + if (duplicateError) { Toast.notify({ type: 'error', - message: t(errorMsgKey as any, { key: t(typeName as any) as string }) as string, + // eslint-disable-next-line ts/no-explicit-any + message: t(duplicateError.errorMsgKey as any, { key: t(duplicateError.typeName as any) as string }) as string, }) return false } onPromptVariablesChange?.(newPromptVariables) return true - } + }, [currIndex, onPromptVariablesChange, promptVariables, t]) const { setShowExternalDataToolModal } = useModalContext() - const handleOpenExternalDataToolModal = ( + const handleOpenExternalDataToolModal = useCallback(( { key, type, index, name, config, icon, icon_background }: ExternalDataToolParams, oldPromptVariables: PromptVariable[], ) => { @@ -157,9 +172,9 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar return true }, }) - } + }, [onPromptVariablesChange, promptVariables, setShowExternalDataToolModal, t]) - const handleAddVar = (type: string) => { + const handleAddVar = useCallback((type: string) => { const newVar = getNewVar('', type) const newPromptVariables = [...promptVariables, newVar] onPromptVariablesChange?.(newPromptVariables) @@ -172,8 +187,9 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar index: promptVariables.length, }, newPromptVariables) } - } + }, [handleOpenExternalDataToolModal, onPromptVariablesChange, promptVariables]) + // eslint-disable-next-line ts/no-explicit-any eventEmitter?.useSubscription((v: any) => { if (v.type === ADD_EXTERNAL_DATA_TOOL) { const payload = v.payload @@ -195,11 +211,11 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar const [isShowDeleteContextVarModal, { setTrue: showDeleteContextVarModal, setFalse: hideDeleteContextVarModal }] = useBoolean(false) const [removeIndex, setRemoveIndex] = useState(null) - const didRemoveVar = (index: number) => { + const didRemoveVar = useCallback((index: number) => { onPromptVariablesChange?.(promptVariables.filter((_, i) => i !== index)) - } + }, [onPromptVariablesChange, promptVariables]) - const handleRemoveVar = (index: number) => { + const handleRemoveVar = useCallback((index: number) => { const removeVar = promptVariables[index] if (mode === AppModeEnum.COMPLETION && dataSets.length > 0 && removeVar.is_context_var) { @@ -208,21 +224,20 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar return } didRemoveVar(index) - } + }, [dataSets.length, didRemoveVar, mode, promptVariables, showDeleteContextVarModal]) - // const [currKey, setCurrKey] = useState(null) const [isShowEditModal, { setTrue: showEditModal, setFalse: hideEditModal }] = useBoolean(false) - const handleConfig = ({ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams) => { + const handleConfig = useCallback(({ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams) => { // setCurrKey(key) setCurrIndex(index) - if (type !== 'string' && type !== 'paragraph' && type !== 'select' && type !== 'number' && type !== 'checkbox') { + if (!BASIC_INPUT_TYPES.has(type)) { handleOpenExternalDataToolModal({ key, type, index, name, config, icon, icon_background }, promptVariables) return } showEditModal() - } + }, [handleOpenExternalDataToolModal, promptVariables, showEditModal]) const promptVariablesWithIds = useMemo(() => promptVariables.map((item) => { return {