diff --git a/web/app/components/workflow/create-snippet-dialog.tsx b/web/app/components/workflow/create-snippet-dialog.tsx new file mode 100644 index 0000000000..bb42b33d1f --- /dev/null +++ b/web/app/components/workflow/create-snippet-dialog.tsx @@ -0,0 +1,178 @@ +'use client' + +import type { FC } from 'react' +import type { AppIconSelection } from '@/app/components/base/app-icon-picker' +import { useKeyPress } from 'ahooks' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import AppIcon from '@/app/components/base/app-icon' +import AppIconPicker from '@/app/components/base/app-icon-picker' +import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' +import Textarea from '@/app/components/base/textarea' +import Toast from '@/app/components/base/toast' +import { Dialog, DialogCloseButton, DialogContent, DialogPortal, DialogTitle } from '@/app/components/base/ui/dialog' +import ShortcutsName from './shortcuts-name' + +export type CreateSnippetDialogPayload = { + name: string + description: string + icon: AppIconSelection + selectedNodeIds: string[] +} + +type CreateSnippetDialogProps = { + isOpen: boolean + selectedNodeIds: string[] + onClose: () => void + onConfirm: (payload: CreateSnippetDialogPayload) => void +} + +const defaultIcon: AppIconSelection = { + type: 'emoji', + icon: '🤖', + background: '#FFEAD5', +} + +const CreateSnippetDialog: FC = ({ + isOpen, + selectedNodeIds, + onClose, + onConfirm, +}) => { + const { t } = useTranslation() + const [name, setName] = useState('') + const [description, setDescription] = useState('') + const [icon, setIcon] = useState(defaultIcon) + const [showAppIconPicker, setShowAppIconPicker] = useState(false) + + const resetForm = useCallback(() => { + setName('') + setDescription('') + setIcon(defaultIcon) + setShowAppIconPicker(false) + }, []) + + const handleClose = useCallback(() => { + resetForm() + onClose() + }, [onClose, resetForm]) + + const handleConfirm = useCallback(() => { + const trimmedName = name.trim() + const trimmedDescription = description.trim() + + if (!trimmedName) + return + + const payload = { + name: trimmedName, + description: trimmedDescription, + icon, + selectedNodeIds, + } + + onConfirm(payload) + Toast.notify({ + type: 'success', + message: t('snippet.createSuccess', { ns: 'workflow' }), + }) + handleClose() + }, [description, handleClose, icon, name, onConfirm, selectedNodeIds, t]) + + useKeyPress(['meta.enter', 'ctrl.enter'], () => { + if (!isOpen) + return + + handleConfirm() + }) + + return ( + <> + !open && handleClose()}> + + + +
+ + {t('snippet.createDialogTitle', { ns: 'workflow' })} + +
+ +
+
+
+
+ {t('snippet.nameLabel', { ns: 'workflow' })} +
+ setName(e.target.value)} + placeholder={t('snippet.namePlaceholder', { ns: 'workflow' }) || ''} + autoFocus + /> +
+ + setShowAppIconPicker(true)} + /> +
+ +
+
+ {t('snippet.descriptionLabel', { ns: 'workflow' })} +
+