diff --git a/web/app/components/base/toast/index.tsx b/web/app/components/base/toast/index.tsx index 725c7af8c..a23a60dbf 100644 --- a/web/app/components/base/toast/index.tsx +++ b/web/app/components/base/toast/index.tsx @@ -29,6 +29,10 @@ type IToastContext = { close: () => void } +export type ToastHandle = { + clear?: VoidFunction +} + export const ToastContext = createContext({} as IToastContext) export const useToastContext = () => useContext(ToastContext) const Toast = ({ @@ -46,7 +50,7 @@ const Toast = ({ return
) => { +}: Pick): ToastHandle => { const defaultDuring = (type === 'success' || type === 'info') ? 3000 : 6000 + const toastHandler: ToastHandle = {} + if (typeof window === 'object') { const holder = document.createElement('div') const root = createRoot(holder) + toastHandler.clear = () => { + if (holder) { + root.unmount() + holder.remove() + } + onClose?.() + } + root.render( , ) document.body.appendChild(holder) - setTimeout(() => { - if (holder) { - root.unmount() - holder.remove() - } - onClose?.() - }, duration || defaultDuring) + setTimeout(toastHandler.clear, duration || defaultDuring) } + + return toastHandler } export default Toast diff --git a/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx index a7c9a9d17..9fef1fe7b 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import React, { useCallback, useState } from 'react' import produce from 'immer' import { useTranslation } from 'react-i18next' import type { OutputVar } from '../../../code/types' @@ -9,7 +9,9 @@ import VarTypePicker from './var-type-picker' import Input from '@/app/components/base/input' import type { VarType } from '@/app/components/workflow/types' import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var' +import type { ToastHandle } from '@/app/components/base/toast' import Toast from '@/app/components/base/toast' +import { useDebounceFn } from 'ahooks' type Props = { readonly: boolean @@ -27,6 +29,7 @@ const OutputVarList: FC = ({ onRemove, }) => { const { t } = useTranslation() + const [toastHandler, setToastHandler] = useState() const list = outputKeyOrders.map((key) => { return { @@ -34,6 +37,27 @@ const OutputVarList: FC = ({ variable_type: outputs[key]?.type, } }) + + const { run: validateVarInput } = useDebounceFn((existingVariables: typeof list, newKey: string) => { + const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) + if (!isValid) { + setToastHandler(Toast.notify({ + type: 'error', + message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), + })) + return + } + if (existingVariables.some(key => key.variable?.trim() === newKey.trim())) { + setToastHandler(Toast.notify({ + type: 'error', + message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }), + })) + } + else { + toastHandler?.clear?.() + } + }, { wait: 500 }) + const handleVarNameChange = useCallback((index: number) => { return (e: React.ChangeEvent) => { const oldKey = list[index].variable @@ -41,22 +65,8 @@ const OutputVarList: FC = ({ replaceSpaceWithUnderscreInVarNameInput(e.target) const newKey = e.target.value - const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) - if (!isValid) { - Toast.notify({ - type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), - }) - return - } - - if (list.map(item => item.variable?.trim()).includes(newKey.trim())) { - Toast.notify({ - type: 'error', - message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }), - }) - return - } + toastHandler?.clear?.() + validateVarInput(list.toSpliced(index, 1), newKey) const newOutputs = produce(outputs, (draft) => { draft[newKey] = draft[oldKey] @@ -64,8 +74,7 @@ const OutputVarList: FC = ({ }) onChange(newOutputs, index, newKey) } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [list, onChange, outputs, outputKeyOrders]) + }, [list, onChange, outputs, outputKeyOrders, validateVarInput]) const handleVarTypeChange = useCallback((index: number) => { return (value: string) => { @@ -75,7 +84,6 @@ const OutputVarList: FC = ({ }) onChange(newOutputs) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [list, onChange, outputs, outputKeyOrders]) const handleVarRemove = useCallback((index: number) => { diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx index b1a8d52a0..2972b3351 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useMemo } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import produce from 'immer' import RemoveButton from '../remove-button' @@ -9,11 +9,13 @@ import Input from '@/app/components/base/input' import type { ValueSelector, Var, Variable } from '@/app/components/workflow/types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var' +import type { ToastHandle } from '@/app/components/base/toast' import Toast from '@/app/components/base/toast' import { ReactSortable } from 'react-sortablejs' import { v4 as uuid4 } from 'uuid' import { RiDraggable } from '@remixicon/react' import cn from '@/utils/classnames' +import { useDebounceFn } from 'ahooks' type Props = { nodeId: string @@ -39,6 +41,7 @@ const VarList: FC = ({ isSupportFileVar = true, }) => { const { t } = useTranslation() + const [toastHandle, setToastHandle] = useState() const listWithIds = useMemo(() => list.map((item) => { const id = uuid4() @@ -48,27 +51,35 @@ const VarList: FC = ({ } }), [list]) + const { run: validateVarInput } = useDebounceFn((list: Variable[], newKey: string) => { + const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) + if (!isValid) { + setToastHandle(Toast.notify({ + type: 'error', + message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), + })) + return + } + if (list.some(item => item.variable?.trim() === newKey.trim())) { + console.log('new key', newKey.trim()) + setToastHandle(Toast.notify({ + type: 'error', + message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }), + })) + } + else { + toastHandle?.clear?.() + } + }, { wait: 500 }) + const handleVarNameChange = useCallback((index: number) => { return (e: React.ChangeEvent) => { replaceSpaceWithUnderscreInVarNameInput(e.target) const newKey = e.target.value - const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) - if (!isValid) { - Toast.notify({ - type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), - }) - return - } - if (list.map(item => item.variable?.trim()).includes(newKey.trim())) { - Toast.notify({ - type: 'error', - message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }), - }) - return - } + toastHandle?.clear?.() + validateVarInput(list.toSpliced(index, 1), newKey) onVarNameChange?.(list[index].variable, newKey) const newList = produce(list, (draft) => { @@ -76,7 +87,7 @@ const VarList: FC = ({ }) onChange(newList) } - }, [list, onVarNameChange, onChange]) + }, [list, onVarNameChange, onChange, validateVarInput]) const handleVarReferenceChange = useCallback((index: number) => { return (value: ValueSelector | string, varKindType: VarKindType, varInfo?: Var) => {