import React, { useCallback, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { v4 as uuid4 } from 'uuid' import { RiCloseLine, RiDraftLine, RiInputField } from '@remixicon/react' import VariableTypeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/variable-type-select' import ObjectValueList from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-list' import { DEFAULT_OBJECT_VALUE } from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-item' import ArrayValueList from '@/app/components/workflow/panel/chat-variable-panel/components/array-value-list' import Button from '@/app/components/base/button' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { ToastContext } from '@/app/components/base/toast' import { useStore } from '@/app/components/workflow/store' import type { ConversationVariable } from '@/app/components/workflow/types' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' import cn from '@/utils/classnames' import { checkKeys } from '@/utils/var' export type ModalPropsType = { chatVar?: ConversationVariable onClose: () => void onSave: (chatVar: ConversationVariable) => void } type ObjectValueItem = { key: string type: ChatVarType value: string | number | undefined } const typeList = [ ChatVarType.String, ChatVarType.Number, ChatVarType.Object, ChatVarType.ArrayString, ChatVarType.ArrayNumber, ChatVarType.ArrayObject, ] const objectPlaceholder = `# example # { # "name": "ray", # "age": 20 # }` const arrayStringPlaceholder = `# example # [ # "value1", # "value2" # ]` const arrayNumberPlaceholder = `# example # [ # 100, # 200 # ]` const arrayObjectPlaceholder = `# example # [ # { # "name": "ray", # "age": 20 # }, # { # "name": "lily", # "age": 18 # } # ]` const ChatVariableModal = ({ chatVar, onClose, onSave, }: ModalPropsType) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const varList = useStore(s => s.conversationVariables) const [name, setName] = React.useState('') const [type, setType] = React.useState(ChatVarType.String) const [value, setValue] = React.useState() const [objectValue, setObjectValue] = React.useState([DEFAULT_OBJECT_VALUE]) const [editorContent, setEditorContent] = React.useState() const [editInJSON, setEditInJSON] = React.useState(false) const [des, setDes] = React.useState('') const editorMinHeight = useMemo(() => { if (type === ChatVarType.ArrayObject) return '240px' return '120px' }, [type]) const placeholder = useMemo(() => { if (type === ChatVarType.ArrayString) return arrayStringPlaceholder if (type === ChatVarType.ArrayNumber) return arrayNumberPlaceholder if (type === ChatVarType.ArrayObject) return arrayObjectPlaceholder return objectPlaceholder }, [type]) const getObjectValue = useCallback(() => { if (!chatVar) return [DEFAULT_OBJECT_VALUE] return Object.keys(chatVar.value).map((key) => { return { key, type: typeof chatVar.value[key] === 'string' ? ChatVarType.String : ChatVarType.Number, value: chatVar.value[key], } }) }, [chatVar]) const formatValueFromObject = useCallback((list: ObjectValueItem[]) => { return list.reduce((acc: any, curr) => { if (curr.key) acc[curr.key] = curr.value || null return acc }, {}) }, []) const formatValue = (value: any) => { switch (type) { case ChatVarType.String: return value || '' case ChatVarType.Number: return value || 0 case ChatVarType.Object: return formatValueFromObject(objectValue) case ChatVarType.ArrayString: case ChatVarType.ArrayNumber: case ChatVarType.ArrayObject: return value?.filter(Boolean) || [] } } const checkVariableName = (value: string) => { const { isValid, errorMessageKey } = checkKeys([value], false) if (!isValid) { notify({ type: 'error', message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: t('workflow.env.modal.name') }), }) return false } return true } const handleTypeChange = (v: ChatVarType) => { setValue(undefined) setEditorContent(undefined) if (v === ChatVarType.ArrayObject) setEditInJSON(true) if (v === ChatVarType.String || v === ChatVarType.Number || v === ChatVarType.Object) setEditInJSON(false) setType(v) } const handleEditorChange = (editInJSON: boolean) => { if (type === ChatVarType.Object) { if (editInJSON) { const newValue = !objectValue[0].key ? undefined : formatValueFromObject(objectValue) setValue(newValue) setEditorContent(JSON.stringify(newValue)) } else { if (!editorContent) { setValue(undefined) setObjectValue([DEFAULT_OBJECT_VALUE]) } else { try { const newValue = JSON.parse(editorContent) setValue(newValue) const newObjectValue = Object.keys(newValue).map((key) => { return { key, type: typeof newValue[key] === 'string' ? ChatVarType.String : ChatVarType.Number, value: newValue[key], } }) setObjectValue(newObjectValue) } catch (e) { // ignore JSON.parse errors } } } } if (type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) { if (editInJSON) { const newValue = (value?.length && value.filter(Boolean).length) ? value.filter(Boolean) : undefined setValue(newValue) if (!editorContent) setEditorContent(JSON.stringify(newValue)) } else { setValue(value?.length ? value : [undefined]) } } setEditInJSON(editInJSON) } const handleEditorValueChange = (content: string) => { if (!content) { setEditorContent(content) return setValue(undefined) } else { setEditorContent(content) try { const newValue = JSON.parse(content) setValue(newValue) } catch (e) { // ignore JSON.parse errors } } } const handleSave = () => { if (!checkVariableName(name)) return if (!chatVar && varList.some(chatVar => chatVar.name === name)) return notify({ type: 'error', message: 'name is existed' }) // if (type !== ChatVarType.Object && !value) // return notify({ type: 'error', message: 'value can not be empty' }) if (type === ChatVarType.Object && objectValue.some(item => !item.key && !!item.value)) return notify({ type: 'error', message: 'object key can not be empty' }) onSave({ id: chatVar ? chatVar.id : uuid4(), name, value_type: type, value: formatValue(value), description: des, }) onClose() } useEffect(() => { if (chatVar) { setName(chatVar.name) setType(chatVar.value_type) setValue(chatVar.value) setDes(chatVar.description) setObjectValue(getObjectValue()) if (chatVar.value_type === ChatVarType.ArrayObject) { setEditorContent(JSON.stringify(chatVar.value)) setEditInJSON(true) } else { setEditInJSON(false) } } }, [chatVar, getObjectValue]) return (
{!chatVar ? t('workflow.chatVariable.modal.title') : t('workflow.chatVariable.modal.editTitle')}
{/* name */}
{t('workflow.chatVariable.modal.name')}
setName(e.target.value || '')} onBlur={e => checkVariableName(e.target.value)} type='text' />
{/* type */}
{t('workflow.chatVariable.modal.type')}
{/* default value */}
{t('workflow.chatVariable.modal.value')}
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) && ( )} {type === ChatVarType.Object && ( )}
{type === ChatVarType.String && ( setValue(e.target.value)} /> )} {type === ChatVarType.Number && ( setValue(Number(e.target.value))} type='number' /> )} {type === ChatVarType.Object && !editInJSON && ( )} {type === ChatVarType.ArrayString && !editInJSON && ( )} {type === ChatVarType.ArrayNumber && !editInJSON && ( )} {editInJSON && (
{placeholder}
} onChange={handleEditorValueChange} />
)}
{/* description */}
{t('workflow.chatVariable.modal.description')}