Fix/variable input validation issue (#23300)
This commit is contained in:
@@ -29,6 +29,10 @@ type IToastContext = {
|
|||||||
close: () => void
|
close: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ToastHandle = {
|
||||||
|
clear?: VoidFunction
|
||||||
|
}
|
||||||
|
|
||||||
export const ToastContext = createContext<IToastContext>({} as IToastContext)
|
export const ToastContext = createContext<IToastContext>({} as IToastContext)
|
||||||
export const useToastContext = () => useContext(ToastContext)
|
export const useToastContext = () => useContext(ToastContext)
|
||||||
const Toast = ({
|
const Toast = ({
|
||||||
@@ -46,7 +50,7 @@ const Toast = ({
|
|||||||
|
|
||||||
return <div className={classNames(
|
return <div className={classNames(
|
||||||
className,
|
className,
|
||||||
'fixed w-[360px] rounded-xl my-4 mx-8 flex-grow z-[9999] overflow-hidden',
|
'fixed z-[9999] mx-8 my-4 w-[360px] grow overflow-hidden rounded-xl',
|
||||||
size === 'md' ? 'p-3' : 'p-2',
|
size === 'md' ? 'p-3' : 'p-2',
|
||||||
'border border-components-panel-border-subtle bg-components-panel-bg-blur shadow-sm',
|
'border border-components-panel-border-subtle bg-components-panel-bg-blur shadow-sm',
|
||||||
'top-0',
|
'top-0',
|
||||||
@@ -127,12 +131,22 @@ Toast.notify = ({
|
|||||||
className,
|
className,
|
||||||
customComponent,
|
customComponent,
|
||||||
onClose,
|
onClose,
|
||||||
}: Pick<IToastProps, 'type' | 'size' | 'message' | 'duration' | 'className' | 'customComponent' | 'onClose'>) => {
|
}: Pick<IToastProps, 'type' | 'size' | 'message' | 'duration' | 'className' | 'customComponent' | 'onClose'>): ToastHandle => {
|
||||||
const defaultDuring = (type === 'success' || type === 'info') ? 3000 : 6000
|
const defaultDuring = (type === 'success' || type === 'info') ? 3000 : 6000
|
||||||
|
const toastHandler: ToastHandle = {}
|
||||||
|
|
||||||
if (typeof window === 'object') {
|
if (typeof window === 'object') {
|
||||||
const holder = document.createElement('div')
|
const holder = document.createElement('div')
|
||||||
const root = createRoot(holder)
|
const root = createRoot(holder)
|
||||||
|
|
||||||
|
toastHandler.clear = () => {
|
||||||
|
if (holder) {
|
||||||
|
root.unmount()
|
||||||
|
holder.remove()
|
||||||
|
}
|
||||||
|
onClose?.()
|
||||||
|
}
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<ToastContext.Provider value={{
|
<ToastContext.Provider value={{
|
||||||
notify: noop,
|
notify: noop,
|
||||||
@@ -148,14 +162,10 @@ Toast.notify = ({
|
|||||||
</ToastContext.Provider>,
|
</ToastContext.Provider>,
|
||||||
)
|
)
|
||||||
document.body.appendChild(holder)
|
document.body.appendChild(holder)
|
||||||
setTimeout(() => {
|
setTimeout(toastHandler.clear, duration || defaultDuring)
|
||||||
if (holder) {
|
|
||||||
root.unmount()
|
|
||||||
holder.remove()
|
|
||||||
}
|
|
||||||
onClose?.()
|
|
||||||
}, duration || defaultDuring)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return toastHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Toast
|
export default Toast
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import type { OutputVar } from '../../../code/types'
|
import type { OutputVar } from '../../../code/types'
|
||||||
@@ -9,7 +9,9 @@ import VarTypePicker from './var-type-picker'
|
|||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import type { VarType } from '@/app/components/workflow/types'
|
import type { VarType } from '@/app/components/workflow/types'
|
||||||
import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var'
|
import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var'
|
||||||
|
import type { ToastHandle } from '@/app/components/base/toast'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
|
import { useDebounceFn } from 'ahooks'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
readonly: boolean
|
readonly: boolean
|
||||||
@@ -27,6 +29,7 @@ const OutputVarList: FC<Props> = ({
|
|||||||
onRemove,
|
onRemove,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const [toastHandler, setToastHandler] = useState<ToastHandle>()
|
||||||
|
|
||||||
const list = outputKeyOrders.map((key) => {
|
const list = outputKeyOrders.map((key) => {
|
||||||
return {
|
return {
|
||||||
@@ -34,6 +37,27 @@ const OutputVarList: FC<Props> = ({
|
|||||||
variable_type: outputs[key]?.type,
|
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) => {
|
const handleVarNameChange = useCallback((index: number) => {
|
||||||
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const oldKey = list[index].variable
|
const oldKey = list[index].variable
|
||||||
@@ -41,22 +65,8 @@ const OutputVarList: FC<Props> = ({
|
|||||||
replaceSpaceWithUnderscreInVarNameInput(e.target)
|
replaceSpaceWithUnderscreInVarNameInput(e.target)
|
||||||
const newKey = e.target.value
|
const newKey = e.target.value
|
||||||
|
|
||||||
const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true)
|
toastHandler?.clear?.()
|
||||||
if (!isValid) {
|
validateVarInput(list.toSpliced(index, 1), newKey)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
const newOutputs = produce(outputs, (draft) => {
|
const newOutputs = produce(outputs, (draft) => {
|
||||||
draft[newKey] = draft[oldKey]
|
draft[newKey] = draft[oldKey]
|
||||||
@@ -64,8 +74,7 @@ const OutputVarList: FC<Props> = ({
|
|||||||
})
|
})
|
||||||
onChange(newOutputs, index, newKey)
|
onChange(newOutputs, index, newKey)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [list, onChange, outputs, outputKeyOrders, validateVarInput])
|
||||||
}, [list, onChange, outputs, outputKeyOrders])
|
|
||||||
|
|
||||||
const handleVarTypeChange = useCallback((index: number) => {
|
const handleVarTypeChange = useCallback((index: number) => {
|
||||||
return (value: string) => {
|
return (value: string) => {
|
||||||
@@ -75,7 +84,6 @@ const OutputVarList: FC<Props> = ({
|
|||||||
})
|
})
|
||||||
onChange(newOutputs)
|
onChange(newOutputs)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [list, onChange, outputs, outputKeyOrders])
|
}, [list, onChange, outputs, outputKeyOrders])
|
||||||
|
|
||||||
const handleVarRemove = useCallback((index: number) => {
|
const handleVarRemove = useCallback((index: number) => {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import RemoveButton from '../remove-button'
|
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 type { ValueSelector, Var, Variable } from '@/app/components/workflow/types'
|
||||||
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||||
import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var'
|
import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var'
|
||||||
|
import type { ToastHandle } from '@/app/components/base/toast'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import { ReactSortable } from 'react-sortablejs'
|
import { ReactSortable } from 'react-sortablejs'
|
||||||
import { v4 as uuid4 } from 'uuid'
|
import { v4 as uuid4 } from 'uuid'
|
||||||
import { RiDraggable } from '@remixicon/react'
|
import { RiDraggable } from '@remixicon/react'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { useDebounceFn } from 'ahooks'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
nodeId: string
|
nodeId: string
|
||||||
@@ -39,6 +41,7 @@ const VarList: FC<Props> = ({
|
|||||||
isSupportFileVar = true,
|
isSupportFileVar = true,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const [toastHandle, setToastHandle] = useState<ToastHandle>()
|
||||||
|
|
||||||
const listWithIds = useMemo(() => list.map((item) => {
|
const listWithIds = useMemo(() => list.map((item) => {
|
||||||
const id = uuid4()
|
const id = uuid4()
|
||||||
@@ -48,27 +51,35 @@ const VarList: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}), [list])
|
}), [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) => {
|
const handleVarNameChange = useCallback((index: number) => {
|
||||||
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
replaceSpaceWithUnderscreInVarNameInput(e.target)
|
replaceSpaceWithUnderscreInVarNameInput(e.target)
|
||||||
|
|
||||||
const newKey = e.target.value
|
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())) {
|
toastHandle?.clear?.()
|
||||||
Toast.notify({
|
validateVarInput(list.toSpliced(index, 1), newKey)
|
||||||
type: 'error',
|
|
||||||
message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
onVarNameChange?.(list[index].variable, newKey)
|
onVarNameChange?.(list[index].variable, newKey)
|
||||||
const newList = produce(list, (draft) => {
|
const newList = produce(list, (draft) => {
|
||||||
@@ -76,7 +87,7 @@ const VarList: FC<Props> = ({
|
|||||||
})
|
})
|
||||||
onChange(newList)
|
onChange(newList)
|
||||||
}
|
}
|
||||||
}, [list, onVarNameChange, onChange])
|
}, [list, onVarNameChange, onChange, validateVarInput])
|
||||||
|
|
||||||
const handleVarReferenceChange = useCallback((index: number) => {
|
const handleVarReferenceChange = useCallback((index: number) => {
|
||||||
return (value: ValueSelector | string, varKindType: VarKindType, varInfo?: Var) => {
|
return (value: ValueSelector | string, varKindType: VarKindType, varInfo?: Var) => {
|
||||||
|
Reference in New Issue
Block a user