mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-07-26 09:44:25 +08:00
This commit is contained in:
parent
d38a2c95fb
commit
40b31bafd5
@ -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
|
@ -1,12 +1,10 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useMemo, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { useDebounceFn } from 'ahooks'
|
import { useDebounceFn } from 'ahooks'
|
||||||
import Toast from '../../base/toast'
|
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import ExploreContext from '@/context/explore-context'
|
import ExploreContext from '@/context/explore-context'
|
||||||
@ -14,17 +12,17 @@ import type { App } from '@/models/explore'
|
|||||||
import Category from '@/app/components/explore/category'
|
import Category from '@/app/components/explore/category'
|
||||||
import AppCard from '@/app/components/explore/app-card'
|
import AppCard from '@/app/components/explore/app-card'
|
||||||
import { fetchAppDetail, fetchAppList } from '@/service/explore'
|
import { fetchAppDetail, fetchAppList } from '@/service/explore'
|
||||||
import { importDSL } from '@/service/apps'
|
|
||||||
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
||||||
import CreateAppModal from '@/app/components/explore/create-app-modal'
|
import CreateAppModal from '@/app/components/explore/create-app-modal'
|
||||||
import AppTypeSelector from '@/app/components/app/type-selector'
|
import AppTypeSelector from '@/app/components/app/type-selector'
|
||||||
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
|
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
|
||||||
import Loading from '@/app/components/base/loading'
|
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 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 = {
|
type AppsProps = {
|
||||||
pageType?: PageType
|
pageType?: PageType
|
||||||
@ -41,8 +39,6 @@ const Apps = ({
|
|||||||
onSuccess,
|
onSuccess,
|
||||||
}: AppsProps) => {
|
}: AppsProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
|
||||||
const { push } = useRouter()
|
|
||||||
const { hasEditPermission } = useContext(ExploreContext)
|
const { hasEditPermission } = useContext(ExploreContext)
|
||||||
const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' })
|
const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' })
|
||||||
|
|
||||||
@ -117,6 +113,14 @@ const Apps = ({
|
|||||||
|
|
||||||
const [currApp, setCurrApp] = React.useState<App | null>(null)
|
const [currApp, setCurrApp] = React.useState<App | null>(null)
|
||||||
const [isShowCreateModal, setIsShowCreateModal] = React.useState(false)
|
const [isShowCreateModal, setIsShowCreateModal] = React.useState(false)
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleImportDSL,
|
||||||
|
handleImportDSLConfirm,
|
||||||
|
versions,
|
||||||
|
isFetching,
|
||||||
|
} = useImportDSL()
|
||||||
|
const [showDSLConfirmModal, setShowDSLConfirmModal] = useState(false)
|
||||||
const onCreate: CreateAppModalProps['onConfirm'] = async ({
|
const onCreate: CreateAppModalProps['onConfirm'] = async ({
|
||||||
name,
|
name,
|
||||||
icon_type,
|
icon_type,
|
||||||
@ -127,31 +131,31 @@ const Apps = ({
|
|||||||
const { export_data } = await fetchAppDetail(
|
const { export_data } = await fetchAppDetail(
|
||||||
currApp?.app.id as string,
|
currApp?.app.id as string,
|
||||||
)
|
)
|
||||||
try {
|
const payload = {
|
||||||
const app = await importDSL({
|
mode: DSLImportMode.YAML_CONTENT,
|
||||||
mode: DSLImportMode.YAML_CONTENT,
|
yaml_content: export_data,
|
||||||
yaml_content: export_data,
|
name,
|
||||||
name,
|
icon_type,
|
||||||
icon_type,
|
icon,
|
||||||
icon,
|
icon_background,
|
||||||
icon_background,
|
description,
|
||||||
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') })
|
|
||||||
}
|
}
|
||||||
|
await handleImportDSL(payload, {
|
||||||
|
onSuccess: () => {
|
||||||
|
setIsShowCreateModal(false)
|
||||||
|
},
|
||||||
|
onPending: () => {
|
||||||
|
setShowDSLConfirmModal(true)
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onConfirmDSL = useCallback(async () => {
|
||||||
|
await handleImportDSLConfirm({
|
||||||
|
onSuccess,
|
||||||
|
})
|
||||||
|
}, [handleImportDSLConfirm, onSuccess])
|
||||||
|
|
||||||
if (!categories || categories.length === 0) {
|
if (!categories || categories.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center">
|
<div className="flex h-full items-center">
|
||||||
@ -234,9 +238,20 @@ const Apps = ({
|
|||||||
appDescription={currApp?.app.description || ''}
|
appDescription={currApp?.app.description || ''}
|
||||||
show={isShowCreateModal}
|
show={isShowCreateModal}
|
||||||
onConfirm={onCreate}
|
onConfirm={onCreate}
|
||||||
|
confirmDisabled={isFetching}
|
||||||
onHide={() => setIsShowCreateModal(false)}
|
onHide={() => setIsShowCreateModal(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{
|
||||||
|
showDSLConfirmModal && (
|
||||||
|
<DSLConfirmModal
|
||||||
|
versions={versions}
|
||||||
|
onCancel={() => setShowDSLConfirmModal(false)}
|
||||||
|
onConfirm={onConfirmDSL}
|
||||||
|
confirmDisabled={isFetching}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ export type CreateAppModalProps = {
|
|||||||
description: string
|
description: string
|
||||||
use_icon_as_answer_icon?: boolean
|
use_icon_as_answer_icon?: boolean
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
|
confirmDisabled?: boolean
|
||||||
onHide: () => void
|
onHide: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +49,7 @@ const CreateAppModal = ({
|
|||||||
appMode,
|
appMode,
|
||||||
appUseIconAsAnswerIcon,
|
appUseIconAsAnswerIcon,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
|
confirmDisabled,
|
||||||
onHide,
|
onHide,
|
||||||
}: CreateAppModalProps) => {
|
}: CreateAppModalProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -145,7 +147,7 @@ const CreateAppModal = ({
|
|||||||
{!isEditModal && isAppsFull && <AppsFull loc='app-explore-create' />}
|
{!isEditModal && isAppsFull && <AppsFull loc='app-explore-create' />}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-row-reverse'>
|
<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>
|
<Button className='w-24' onClick={onHide}>{t('common.operation.cancel')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
158
web/hooks/use-import-dsl.ts
Normal file
158
web/hooks/use-import-dsl.ts
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user