'use client' import type { BatchUploadNodeInput } from '@/types/app-asset' import { memo, useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Modal from '@/app/components/base/modal' import Toast from '@/app/components/base/toast' import { useWorkflowStore } from '@/app/components/workflow/store' import { useBatchUpload } from '@/service/use-app-asset' import { useExistingSkillNames } from '../hooks/file-tree/data/use-skill-asset-tree' import { useSkillTreeUpdateEmitter } from '../hooks/file-tree/data/use-skill-tree-collaboration' import { prepareSkillUploadFile } from '../utils/skill-upload-utils' const SKILL_MD_TEMPLATE = (name: string) => `--- name: ${name} description: --- # ${name} ` type CreateBlankSkillModalProps = { isOpen: boolean onClose: () => void } const CreateBlankSkillModal = ({ isOpen, onClose }: CreateBlankSkillModalProps) => { const { t } = useTranslation() const [skillName, setSkillName] = useState('') const [isCreating, setIsCreating] = useState(false) const appDetail = useAppStore(s => s.appDetail) const appId = appDetail?.id || '' const storeApi = useWorkflowStore() const batchUpload = useBatchUpload() const batchUploadRef = useRef(batchUpload) batchUploadRef.current = batchUpload const emitTreeUpdate = useSkillTreeUpdateEmitter() const emitTreeUpdateRef = useRef(emitTreeUpdate) emitTreeUpdateRef.current = emitTreeUpdate const { data: existingNames } = useExistingSkillNames() const inputRef = useRef(null) const trimmedName = skillName.trim() const isDuplicate = !!trimmedName && (existingNames?.has(trimmedName) ?? false) const canCreate = !!trimmedName && !isDuplicate && !isCreating const handleClose = useCallback(() => { if (isCreating) return setSkillName('') onClose() }, [isCreating, onClose]) const handleCreate = useCallback(async () => { if (!canCreate || !appId) return setIsCreating(true) storeApi.getState().setUploadStatus('uploading') storeApi.getState().setUploadProgress({ uploaded: 0, total: 1, failed: 0 }) try { const content = SKILL_MD_TEMPLATE(trimmedName) const rawFile = new File([content], 'SKILL.md', { type: 'text/markdown' }) const preparedFile = await prepareSkillUploadFile(rawFile) const tree: BatchUploadNodeInput[] = [{ name: trimmedName, node_type: 'folder', children: [{ name: 'SKILL.md', node_type: 'file', size: preparedFile.size }], }] const files = new Map() files.set(`${trimmedName}/SKILL.md`, preparedFile) const createdNodes = await batchUploadRef.current.mutateAsync({ appId, tree, files, parentId: null, onProgress: (uploaded, total) => { storeApi.getState().setUploadProgress({ uploaded, total, failed: 0 }) }, }) storeApi.getState().setUploadStatus('success') emitTreeUpdateRef.current() const skillMdId = createdNodes?.[0]?.children?.[0]?.id if (skillMdId) storeApi.getState().openTab(skillMdId, { pinned: true }) Toast.notify({ type: 'success', message: t('skill.startTab.createSuccess', { ns: 'workflow', name: trimmedName }) }) onClose() } catch { storeApi.getState().setUploadStatus('partial_error') Toast.notify({ type: 'error', message: t('skill.startTab.createError', { ns: 'workflow' }) }) } finally { setIsCreating(false) setSkillName('') } }, [canCreate, appId, trimmedName, storeApi, onClose, t]) return (
setSkillName(e.target.value)} placeholder={t('skill.startTab.createModal.namePlaceholder', { ns: 'workflow' }) || ''} destructive={isDuplicate} disabled={isCreating} onKeyDown={(e) => { if (e.key === 'Enter' && canCreate) handleCreate() }} /> {isDuplicate && (

{t('skill.startTab.createModal.nameDuplicate', { ns: 'workflow' })}

)}
) } export default memo(CreateBlankSkillModal)