'use client' import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { Cog8ToothIcon, TrashIcon } from '@heroicons/react/24/outline' import { useBoolean } from 'ahooks' import type { Timeout } from 'ahooks/lib/useRequest/src/types' import Panel from '../base/feature-panel' import OperationBtn from '../base/operation-btn' import VarIcon from '../base/icons/var-icon' import EditModel from './config-model' import IconTypeIcon from './input-type-icon' import type { IInputTypeIconProps } from './input-type-icon' import s from './style.module.css' import Tooltip from '@/app/components/base/tooltip' import type { PromptVariable } from '@/models/debug' import { DEFAULT_VALUE_MAX_LEN, getMaxVarNameLength } from '@/config' import { checkKeys, getNewVar } from '@/utils/var' import Switch from '@/app/components/base/switch' import Toast from '@/app/components/base/toast' export type IConfigVarProps = { promptVariables: PromptVariable[] readonly?: boolean onPromptVariablesChange?: (promptVariables: PromptVariable[]) => void } let conflictTimer: Timeout const ConfigVar: FC = ({ promptVariables, readonly, onPromptVariablesChange }) => { const { t } = useTranslation() const hasVar = promptVariables.length > 0 const promptVariableObj = (() => { const obj: Record = {} promptVariables.forEach((item) => { obj[item.key] = true }) return obj })() const updatePromptVariable = (key: string, updateKey: string, newValue: string | boolean) => { const newPromptVariables = promptVariables.map((item) => { if (item.key === key) { return { ...item, [updateKey]: newValue, } } return item }) onPromptVariablesChange?.(newPromptVariables) } const batchUpdatePromptVariable = (key: string, updateKeys: string[], newValues: any[]) => { const newPromptVariables = promptVariables.map((item) => { if (item.key === key) { const newItem: any = { ...item } updateKeys.forEach((updateKey, i) => { newItem[updateKey] = newValues[i] }) return newItem } return item }) onPromptVariablesChange?.(newPromptVariables) } const updatePromptKey = (index: number, newKey: string) => { clearTimeout(conflictTimer) const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) if (!isValid) { Toast.notify({ type: 'error', message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), }) return } const newPromptVariables = promptVariables.map((item, i) => { if (i === index) { return { ...item, key: newKey, } } return item }) conflictTimer = setTimeout(() => { const isKeyExists = promptVariables.some(item => item.key.trim() === newKey.trim()) if (isKeyExists) { Toast.notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }), }) } }, 1000) onPromptVariablesChange?.(newPromptVariables) } const updatePromptNameIfNameEmpty = (index: number, newKey: string) => { if (!newKey) return const newPromptVariables = promptVariables.map((item, i) => { if (i === index && !item.name) { return { ...item, name: newKey, } } return item }) onPromptVariablesChange?.(newPromptVariables) } const handleAddVar = () => { const newVar = getNewVar('') onPromptVariablesChange?.([...promptVariables, newVar]) } const handleRemoveVar = (index: number) => { onPromptVariablesChange?.(promptVariables.filter((_, i) => i !== index)) } const [currKey, setCurrKey] = useState(null) const currItem = currKey ? promptVariables.find(item => item.key === currKey) : null const [isShowEditModal, { setTrue: showEditModal, setFalse: hideEditModal }] = useBoolean(false) const handleConfig = (key: string) => { setCurrKey(key) showEditModal() } return ( } title={
{t('appDebug.variableTitle')}
{!readonly && ( {t('appDebug.variableTip')}
} selector='config-var-tooltip'> )} } headerRight={!readonly ? : null} > {!hasVar && (
{t('appDebug.notSetVar')}
)} {hasVar && (
{!readonly && ( <> )} {promptVariables.map(({ key, name, type, required }, index) => ( {!readonly && ( <> )} ))}
{t('appDebug.variableTable.key')} {t('appDebug.variableTable.name')}{t('appDebug.variableTable.optional')} {t('appDebug.variableTable.action')}
{!readonly ? ( updatePromptKey(index, e.target.value)} onBlur={e => updatePromptNameIfNameEmpty(index, e.target.value)} maxLength={getMaxVarNameLength(name)} className="h-6 leading-6 block w-full rounded-md border-0 py-1.5 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200" /> ) : (
{key}
)}
{!readonly ? ( updatePromptVariable(key, 'name', e.target.value)} maxLength={getMaxVarNameLength(name)} className="h-6 leading-6 block w-full rounded-md border-0 py-1.5 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200" />) : (
{name}
)}
updatePromptVariable(key, 'required', !value)} />
handleConfig(key)}>
handleRemoveVar(index)} >
)} {isShowEditModal && ( { if (type === 'string') batchUpdatePromptVariable(currKey as string, ['type', 'max_length'], [type, value || DEFAULT_VALUE_MAX_LEN]) else batchUpdatePromptVariable(currKey as string, ['type', 'options'], [type, value || []]) hideEditModal() }} /> )}
) } export default React.memo(ConfigVar)