From 40b31bafd559ed62f72f508f26f63d8895e1e5a6 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Sun, 27 Apr 2025 14:21:45 +0800 Subject: [PATCH] fix: check dsl version when create app from explore template (#18872) (#18878) --- .../dsl-confirm-modal.tsx | 46 +++++ web/app/components/explore/app-list/index.tsx | 79 +++++---- .../explore/create-app-modal/index.tsx | 4 +- web/hooks/use-import-dsl.ts | 158 ++++++++++++++++++ 4 files changed, 254 insertions(+), 33 deletions(-) create mode 100644 web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx create mode 100644 web/hooks/use-import-dsl.ts 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 new file mode 100644 index 0000000000..e6aadaa326 --- /dev/null +++ b/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx @@ -0,0 +1,46 @@ +import { useTranslation } from 'react-i18next' +import Modal from '@/app/components/base/modal' +import Button from '@/app/components/base/button' + +type DSLConfirmModalProps = { + versions?: { + importedVersion: string + systemVersion: string + } + onCancel: () => void + onConfirm: () => void + confirmDisabled?: boolean +} +const DSLConfirmModal = ({ + versions = { importedVersion: '', systemVersion: '' }, + onCancel, + onConfirm, + confirmDisabled = false, +}: DSLConfirmModalProps) => { + const { t } = useTranslation() + + return ( + onCancel()} + 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 DSLConfirmModal diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index b8e7939328..fa8b03c7fe 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -1,12 +1,10 @@ 'use client' -import React, { useMemo, useState } from 'react' -import { useRouter } from 'next/navigation' +import React, { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import useSWR from 'swr' import { useDebounceFn } from 'ahooks' -import Toast from '../../base/toast' import s from './style.module.css' import cn from '@/utils/classnames' import ExploreContext from '@/context/explore-context' @@ -14,17 +12,17 @@ import type { App } from '@/models/explore' import Category from '@/app/components/explore/category' import AppCard from '@/app/components/explore/app-card' import { fetchAppDetail, fetchAppList } from '@/service/explore' -import { importDSL } from '@/service/apps' import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import CreateAppModal from '@/app/components/explore/create-app-modal' import AppTypeSelector from '@/app/components/app/type-selector' import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import Loading from '@/app/components/base/loading' -import { NEED_REFRESH_APP_LIST_KEY } from '@/config' -import { useAppContext } from '@/context/app-context' -import { getRedirection } from '@/utils/app-redirection' import Input from '@/app/components/base/input' -import { DSLImportMode } from '@/models/app' +import { + DSLImportMode, +} from '@/models/app' +import { useImportDSL } from '@/hooks/use-import-dsl' +import DSLConfirmModal from '@/app/components/app/create-from-dsl-modal/dsl-confirm-modal' type AppsProps = { pageType?: PageType @@ -41,8 +39,6 @@ const Apps = ({ onSuccess, }: AppsProps) => { const { t } = useTranslation() - const { isCurrentWorkspaceEditor } = useAppContext() - const { push } = useRouter() const { hasEditPermission } = useContext(ExploreContext) const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' }) @@ -117,6 +113,14 @@ const Apps = ({ const [currApp, setCurrApp] = React.useState(null) const [isShowCreateModal, setIsShowCreateModal] = React.useState(false) + + const { + handleImportDSL, + handleImportDSLConfirm, + versions, + isFetching, + } = useImportDSL() + const [showDSLConfirmModal, setShowDSLConfirmModal] = useState(false) const onCreate: CreateAppModalProps['onConfirm'] = async ({ name, icon_type, @@ -127,31 +131,31 @@ const Apps = ({ const { export_data } = await fetchAppDetail( currApp?.app.id as string, ) - try { - const app = await importDSL({ - mode: DSLImportMode.YAML_CONTENT, - yaml_content: export_data, - name, - icon_type, - icon, - icon_background, - description, - }) - setIsShowCreateModal(false) - Toast.notify({ - type: 'success', - message: t('app.newApp.appCreated'), - }) - if (onSuccess) - onSuccess() - localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') - getRedirection(isCurrentWorkspaceEditor, { id: app.app_id }, push) - } - catch (e) { - Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + const payload = { + mode: DSLImportMode.YAML_CONTENT, + yaml_content: export_data, + name, + icon_type, + icon, + icon_background, + description, } + await handleImportDSL(payload, { + onSuccess: () => { + setIsShowCreateModal(false) + }, + onPending: () => { + setShowDSLConfirmModal(true) + }, + }) } + const onConfirmDSL = useCallback(async () => { + await handleImportDSLConfirm({ + onSuccess, + }) + }, [handleImportDSLConfirm, onSuccess]) + if (!categories || categories.length === 0) { return (
@@ -234,9 +238,20 @@ const Apps = ({ appDescription={currApp?.app.description || ''} show={isShowCreateModal} onConfirm={onCreate} + confirmDisabled={isFetching} onHide={() => setIsShowCreateModal(false)} /> )} + { + showDSLConfirmModal && ( + setShowDSLConfirmModal(false)} + onConfirm={onConfirmDSL} + confirmDisabled={isFetching} + /> + ) + }
) } diff --git a/web/app/components/explore/create-app-modal/index.tsx b/web/app/components/explore/create-app-modal/index.tsx index 45baf773f8..be11488902 100644 --- a/web/app/components/explore/create-app-modal/index.tsx +++ b/web/app/components/explore/create-app-modal/index.tsx @@ -33,6 +33,7 @@ export type CreateAppModalProps = { description: string use_icon_as_answer_icon?: boolean }) => Promise + confirmDisabled?: boolean onHide: () => void } @@ -48,6 +49,7 @@ const CreateAppModal = ({ appMode, appUseIconAsAnswerIcon, onConfirm, + confirmDisabled, onHide, }: CreateAppModalProps) => { const { t } = useTranslation() @@ -145,7 +147,7 @@ const CreateAppModal = ({ {!isEditModal && isAppsFull && }
- +
diff --git a/web/hooks/use-import-dsl.ts b/web/hooks/use-import-dsl.ts new file mode 100644 index 0000000000..486f7ffa84 --- /dev/null +++ b/web/hooks/use-import-dsl.ts @@ -0,0 +1,158 @@ +import { + useCallback, + useRef, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useRouter } from 'next/navigation' +import type { + DSLImportMode, + DSLImportResponse, +} from '@/models/app' +import { DSLImportStatus } from '@/models/app' +import { + importDSL, + importDSLConfirm, +} from '@/service/apps' +import type { AppIconType } from '@/types/app' +import { useToastContext } from '@/app/components/base/toast' +import { getRedirection } from '@/utils/app-redirection' +import { useSelector } from '@/context/app-context' +import { NEED_REFRESH_APP_LIST_KEY } from '@/config' + +type DSLPayload = { + mode: DSLImportMode + yaml_content?: string + yaml_url?: string + name?: string + icon_type?: AppIconType + icon?: string + icon_background?: string + description?: string +} +type ResponseCallback = { + onSuccess?: () => void + onPending?: (payload: DSLImportResponse) => void + onFailed?: () => void +} +export const useImportDSL = () => { + const { t } = useTranslation() + const { notify } = useToastContext() + const [isFetching, setIsFetching] = useState(false) + const isCurrentWorkspaceEditor = useSelector(s => s.isCurrentWorkspaceEditor) + const { push } = useRouter() + const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>() + const importIdRef = useRef('') + + const handleImportDSL = useCallback(async ( + payload: DSLPayload, + { + onSuccess, + onPending, + onFailed, + }: ResponseCallback, + ) => { + if (isFetching) + return + setIsFetching(true) + + try { + const response = await importDSL(payload) + + if (!response) + return + + const { + id, + status, + app_id, + imported_dsl_version, + current_dsl_version, + } = response + + if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) { + if (!app_id) + return + + 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'), + }) + onSuccess?.() + localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + getRedirection(isCurrentWorkspaceEditor, { id: app_id }, push) + } + else if (status === DSLImportStatus.PENDING) { + setVersions({ + importedVersion: imported_dsl_version ?? '', + systemVersion: current_dsl_version ?? '', + }) + importIdRef.current = id + onPending?.(response) + } + else { + notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + onFailed?.() + } + } + catch { + notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + onFailed?.() + } + finally { + setIsFetching(false) + } + }, [t, notify, isCurrentWorkspaceEditor, push, isFetching]) + + const handleImportDSLConfirm = useCallback(async ( + { + onSuccess, + onFailed, + }: Pick, + ) => { + if (isFetching) + return + setIsFetching(true) + if (!importIdRef.current) + return + + try { + const response = await importDSLConfirm({ + import_id: importIdRef.current, + }) + + const { status, app_id } = response + if (!app_id) + return + + if (status === DSLImportStatus.COMPLETED) { + onSuccess?.() + notify({ + type: 'success', + message: t('app.newApp.appCreated'), + }) + localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + getRedirection(isCurrentWorkspaceEditor, { id: app_id! }, push) + } + else if (status === DSLImportStatus.FAILED) { + notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + onFailed?.() + } + } + catch { + notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + onFailed?.() + } + finally { + setIsFetching(false) + } + }, [t, notify, isCurrentWorkspaceEditor, push, isFetching]) + + return { + handleImportDSL, + handleImportDSLConfirm, + versions, + isFetching, + } +}