fix: check dsl version when create app from explore template (#18872) (#18878)

This commit is contained in:
zxhlyh 2025-04-27 14:21:45 +08:00 committed by GitHub
parent d38a2c95fb
commit 40b31bafd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 254 additions and 33 deletions

View File

@ -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 (
<Modal
isShow
onClose={() => onCancel()}
className='w-[480px]'
>
<div className='flex flex-col items-start gap-2 self-stretch pb-4'>
<div className='title-2xl-semi-bold text-text-primary'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
<div className='system-md-regular flex grow flex-col text-text-secondary'>
<div>{t('app.newApp.appCreateDSLErrorPart1')}</div>
<div>{t('app.newApp.appCreateDSLErrorPart2')}</div>
<br />
<div>{t('app.newApp.appCreateDSLErrorPart3')}<span className='system-md-medium'>{versions.importedVersion}</span></div>
<div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions.systemVersion}</span></div>
</div>
</div>
<div className='flex items-start justify-end gap-2 self-stretch pt-6'>
<Button variant='secondary' onClick={() => onCancel()}>{t('app.newApp.Cancel')}</Button>
<Button variant='primary' destructive onClick={onConfirm} disabled={confirmDisabled}>{t('app.newApp.Confirm')}</Button>
</div>
</Modal>
)
}
export default DSLConfirmModal

View File

@ -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<App | null>(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 (
<div className="flex h-full items-center">
@ -234,9 +238,20 @@ const Apps = ({
appDescription={currApp?.app.description || ''}
show={isShowCreateModal}
onConfirm={onCreate}
confirmDisabled={isFetching}
onHide={() => setIsShowCreateModal(false)}
/>
)}
{
showDSLConfirmModal && (
<DSLConfirmModal
versions={versions}
onCancel={() => setShowDSLConfirmModal(false)}
onConfirm={onConfirmDSL}
confirmDisabled={isFetching}
/>
)
}
</div>
)
}

View File

@ -33,6 +33,7 @@ export type CreateAppModalProps = {
description: string
use_icon_as_answer_icon?: boolean
}) => Promise<void>
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 && <AppsFull loc='app-explore-create' />}
</div>
<div className='flex flex-row-reverse'>
<Button disabled={!isEditModal && isAppsFull} className='w-24 ml-2' variant='primary' onClick={submit}>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</Button>
<Button disabled={(!isEditModal && isAppsFull) || !name.trim() || confirmDisabled} className='w-24 ml-2' variant='primary' onClick={submit}>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</Button>
<Button className='w-24' onClick={onHide}>{t('common.operation.cancel')}</Button>
</div>
</Modal>

158
web/hooks/use-import-dsl.ts Normal file
View File

@ -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<string>('')
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<ResponseCallback, 'onSuccess' | 'onFailed'>,
) => {
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,
}
}