| 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 = ({ | ||||
| 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', | ||||
| 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, | ||||
| </ToastContext.Provider>, | </ToastContext.Provider>, | ||||
| ) | ) | ||||
| document.body.appendChild(holder) | document.body.appendChild(holder) | ||||
| setTimeout(() => { | |||||
| if (holder) { | |||||
| root.unmount() | |||||
| holder.remove() | |||||
| } | |||||
| onClose?.() | |||||
| }, duration || defaultDuring) | |||||
| setTimeout(toastHandler.clear, duration || defaultDuring) | |||||
| } | } | ||||
| return toastHandler | |||||
| } | } | ||||
| export default Toast | export default Toast |
| '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' | ||||
| 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 | ||||
| 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 { | ||||
| 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 | ||||
| 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())) { | |||||
| 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) => { | const newOutputs = produce(outputs, (draft) => { | ||||
| draft[newKey] = draft[oldKey] | draft[newKey] = draft[oldKey] | ||||
| }) | }) | ||||
| onChange(newOutputs, index, newKey) | 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) => { | const handleVarTypeChange = useCallback((index: number) => { | ||||
| return (value: string) => { | return (value: string) => { | ||||
| }) | }) | ||||
| 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) => { |
| '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' | ||||
| 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 | ||||
| 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() | ||||
| } | } | ||||
| }), [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())) { | |||||
| 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) | onVarNameChange?.(list[index].variable, newKey) | ||||
| const newList = produce(list, (draft) => { | const newList = produce(list, (draft) => { | ||||
| }) | }) | ||||
| 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) => { |