From 788deffa2b9dd117dcc47e00c7b54ffc4ed91676 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 23 Jan 2026 15:22:56 +0800 Subject: [PATCH] feat: support import zip --- .../dsl-confirm-modal.tsx | 71 ++++++++++++++++++- .../app/create-from-dsl-modal/index.tsx | 67 +++++++++-------- 2 files changed, 103 insertions(+), 35 deletions(-) diff --git a/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx b/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx index db675228c4..d1277b60d2 100644 --- a/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx +++ b/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx @@ -1,23 +1,84 @@ +import { useRouter } from 'next/navigation' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' +import { useToastContext } from '@/app/components/base/toast' +import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' +import { NEED_REFRESH_APP_LIST_KEY } from '@/config' +import { useAppContext } from '@/context/app-context' +import { DSLImportStatus } from '@/models/app' +import { importAppBundle } from '@/service/apps' +import { getRedirection } from '@/utils/app-redirection' type DSLConfirmModalProps = { + file?: File versions?: { importedVersion: string systemVersion: string } onCancel: () => void onConfirm: () => void + onSuccess?: () => void confirmDisabled?: boolean } const DSLConfirmModal = ({ + file, versions = { importedVersion: '', systemVersion: '' }, onCancel, onConfirm, + onSuccess, confirmDisabled = false, }: DSLConfirmModalProps) => { const { t } = useTranslation() + const { notify } = useToastContext() + const { push } = useRouter() + const { isCurrentWorkspaceEditor } = useAppContext() + const { handleCheckPluginDependencies } = usePluginDependencies() + const [isImporting, setIsImporting] = useState(false) + const isZipFile = !!file && file.name.toLowerCase().endsWith('.zip') + + const handleConfirm = async () => { + if (confirmDisabled || isImporting) + return + if (!isZipFile) { + onConfirm() + return + } + if (!file) + return + + setIsImporting(true) + try { + const response = await importAppBundle({ file }) + const { status, app_id, app_mode } = response + + if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) { + notify({ + type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning', + message: t(status === DSLImportStatus.COMPLETED ? 'newApp.appCreated' : 'newApp.caution', { ns: 'app' }), + children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('newApp.appCreateDSLWarning', { ns: 'app' }), + }) + localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + if (app_id) + await handleCheckPluginDependencies(app_id) + if (app_id) + getRedirection(isCurrentWorkspaceEditor, { id: app_id, mode: app_mode }, push) + onSuccess?.() + onCancel() + } + else { + notify({ type: 'error', message: t('importBundleFailed', { ns: 'app' }) }) + } + } + catch (e) { + const error = e as Error + notify({ type: 'error', message: error.message || t('importBundleFailed', { ns: 'app' }) }) + } + finally { + setIsImporting(false) + } + } return (
- +
) diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx index 056bb185ad..129a54e2a3 100644 --- a/web/app/components/app/create-from-dsl-modal/index.tsx +++ b/web/app/components/app/create-from-dsl-modal/index.tsx @@ -1,6 +1,5 @@ 'use client' -import type { MouseEventHandler } from 'react' import { RiCloseLine, RiExternalLinkLine } from '@remixicon/react' import { useDebounceFn, useKeyPress } from 'ahooks' import { noop } from 'es-toolkit/function' @@ -24,11 +23,13 @@ import { DSLImportStatus, } from '@/models/app' import { + importAppBundle, importDSL, importDSLConfirm, } from '@/service/apps' import { getRedirection } from '@/utils/app-redirection' import { cn } from '@/utils/classnames' +import DSLConfirmModal from './dsl-confirm-modal' import Uploader from './uploader' type CreateFromDSLModalProps = { @@ -59,6 +60,8 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS const [importId, setImportId] = useState() const { handleCheckPluginDependencies } = usePluginDependencies() + const isZipFile = (file?: File) => !!file && file.name.toLowerCase().endsWith('.zip') + const readFile = (file: File) => { const reader = new FileReader() reader.onload = function (event) { @@ -70,9 +73,9 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS const handleFile = (file?: File) => { setDSLFile(file) - if (file) + if (file && !isZipFile(file)) readFile(file) - if (!file) + if (!file || isZipFile(file)) setFileContent('') } @@ -99,10 +102,15 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS let response if (currentTab === CreateFromDSLModalTab.FROM_FILE) { - response = await importDSL({ - mode: DSLImportMode.YAML_CONTENT, - yaml_content: fileContent || '', - }) + if (isZipFile(currentFile)) { + response = await importAppBundle({ file: currentFile! }) + } + else { + response = await importDSL({ + mode: DSLImportMode.YAML_CONTENT, + yaml_content: fileContent || '', + }) + } } if (currentTab === CreateFromDSLModalTab.FROM_URL) { response = await importDSL({ @@ -170,7 +178,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS onClose() }) - const onDSLConfirm: MouseEventHandler = async () => { + const onDSLConfirm = async () => { try { if (!importId) return @@ -205,6 +213,12 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS } } + const handleConfirmSuccess = () => { + if (onSuccess) + onSuccess() + onClose() + } + const tabs = [ { key: CreateFromDSLModalTab.FROM_FILE, @@ -273,6 +287,8 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS className="mt-0" file={currentFile} updateFile={handleFile} + accept=".yaml,.yml,.zip" + displayName={isZipFile(currentFile) ? 'ZIP' : 'YAML'} /> )} {currentTab === CreateFromDSLModalTab.FROM_URL && ( @@ -317,32 +333,15 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS - setShowErrorModal(false)} - className="w-[480px]" - > -
-
{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}
-
-
{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}
-
{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}
-
-
- {t('newApp.appCreateDSLErrorPart3', { ns: 'app' })} - {versions?.importedVersion} -
-
- {t('newApp.appCreateDSLErrorPart4', { ns: 'app' })} - {versions?.systemVersion} -
-
-
-
- - -
-
+ {showErrorModal && ( + setShowErrorModal(false)} + onConfirm={onDSLConfirm} + onSuccess={handleConfirmSuccess} + /> + )} ) }