'use client' import { useMemo, useRef, useState } from 'react' import { useRouter } from 'next/navigation' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { useDebounceFn, useKeyPress } from 'ahooks' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Modal from '@/app/components/base/modal' import { ToastContext } from '@/app/components/base/toast' import { DSLImportMode, DSLImportStatus, } from '@/models/app' import { useProviderContextSelector } from '@/context/provider-context' import AppsFull from '@/app/components/billing/apps-full-in-dialog' import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' import { noop } from 'lodash-es' import Uploader from './uploader' import Header from './header' import Tab from './tab' import { useImportPipelineDSL, useImportPipelineDSLConfirm } from '@/service/use-pipeline' type CreateFromDSLModalProps = { show: boolean onSuccess?: () => void onClose: () => void activeTab?: CreateFromDSLModalTab dslUrl?: string } export enum CreateFromDSLModalTab { FROM_FILE = 'from-file', FROM_URL = 'from-url', } const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDSLModalTab.FROM_FILE, dslUrl = '', }: CreateFromDSLModalProps) => { const { push } = useRouter() const { t } = useTranslation() const { notify } = useContext(ToastContext) const [currentFile, setDSLFile] = useState() const [fileContent, setFileContent] = useState() const [currentTab, setCurrentTab] = useState(activeTab) const [dslUrlValue, setDslUrlValue] = useState(dslUrl) const [showErrorModal, setShowErrorModal] = useState(false) const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>() const [importId, setImportId] = useState() const { handleCheckPluginDependencies } = usePluginDependencies() const readFile = (file: File) => { const reader = new FileReader() reader.onload = function (event) { const content = event.target?.result setFileContent(content as string) } reader.readAsText(file) } const handleFile = (file?: File) => { setDSLFile(file) if (file) readFile(file) if (!file) setFileContent('') } const plan = useProviderContextSelector(state => state.plan) const enableBilling = useProviderContextSelector(state => state.enableBilling) const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) const isCreatingRef = useRef(false) const { mutateAsync: importDSL } = useImportPipelineDSL() const onCreate = async () => { if (currentTab === CreateFromDSLModalTab.FROM_FILE && !currentFile) return if (currentTab === CreateFromDSLModalTab.FROM_URL && !dslUrlValue) return if (isCreatingRef.current) return isCreatingRef.current = true try { let response if (currentTab === CreateFromDSLModalTab.FROM_FILE) { response = await importDSL({ mode: DSLImportMode.YAML_CONTENT, yaml_content: fileContent || '', }) } if (currentTab === CreateFromDSLModalTab.FROM_URL) { response = await importDSL({ mode: DSLImportMode.YAML_URL, yaml_url: dslUrlValue || '', }) } if (!response) return const { id, status, pipeline_id, dataset_id, imported_dsl_version, current_dsl_version } = response if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) { if (onSuccess) onSuccess() if (onClose) onClose() notify({ type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning', message: t(status === DSLImportStatus.COMPLETED ? 'app.newApp.appCreated' : 'app.newApp.caution'), children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('app.newApp.appCreateDSLWarning'), }) if (pipeline_id) await handleCheckPluginDependencies(pipeline_id, true) push(`datasets/${dataset_id}/pipeline`) } else if (status === DSLImportStatus.PENDING) { setVersions({ importedVersion: imported_dsl_version ?? '', systemVersion: current_dsl_version ?? '', }) if (onClose) onClose() setTimeout(() => { setShowErrorModal(true) }, 300) setImportId(id) } else { notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) } } catch { notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) } finally { isCreatingRef.current = false } } const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 }) useKeyPress('esc', () => { if (show && !showErrorModal) onClose() }) const { mutateAsync: importDSLConfirm } = useImportPipelineDSLConfirm() const onDSLConfirm = async () => { try { if (!importId) return const response = await importDSLConfirm(importId) const { status, pipeline_id, dataset_id } = response if (status === DSLImportStatus.COMPLETED) { if (onSuccess) onSuccess() if (onClose) onClose() notify({ type: 'success', message: t('app.newApp.appCreated'), }) if (pipeline_id) await handleCheckPluginDependencies(pipeline_id, true) push(`datasets/${dataset_id}/pipeline`) } else if (status === DSLImportStatus.FAILED) { notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) } } catch { notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) } } const buttonDisabled = useMemo(() => { if (isAppsFull) return true if (currentTab === CreateFromDSLModalTab.FROM_FILE) return !currentFile if (currentTab === CreateFromDSLModalTab.FROM_URL) return !dslUrlValue return false }, [isAppsFull, currentTab, currentFile, dslUrlValue]) return ( <>
{ currentTab === CreateFromDSLModalTab.FROM_FILE && ( ) } { currentTab === CreateFromDSLModalTab.FROM_URL && (
DSL URL
setDslUrlValue(e.target.value)} />
) }
{isAppsFull && (
)}
setShowErrorModal(false)} className='w-[480px]' >
{t('app.newApp.appCreateDSLErrorTitle')}
{t('app.newApp.appCreateDSLErrorPart1')}
{t('app.newApp.appCreateDSLErrorPart2')}

{t('app.newApp.appCreateDSLErrorPart3')}{versions?.importedVersion}
{t('app.newApp.appCreateDSLErrorPart4')}{versions?.systemVersion}
) } export default CreateFromDSLModal