import type { DSLImportMode, DSLImportResponse } from '@/models/app' import type { AppIconType } from '@/types/app' import { toast } from '@langgenius/dify-ui/toast' import { useSuspenseQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useSetNeedRefreshAppList } from '@/app/components/apps/storage' import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' import { userProfileIdAtom } from '@/context/account-state' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { DSLImportStatus } from '@/models/app' import { useRouter } from '@/next/navigation' import { importDSL, importDSLConfirm } from '@/service/apps' import { useInvalidateAppList } from '@/service/use-apps' import { getRedirection } from '@/utils/app-redirection' import { resolveImportedAppRedirectionTarget } from '@/utils/imported-app-redirection' 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?: (payload: DSLImportResponse) => void onPending?: (payload: DSLImportResponse) => void onFailed?: () => void skipRedirectOnSuccess?: boolean } export const useImportDSL = () => { const { t } = useTranslation() const [isFetching, setIsFetching] = useState(false) const { handleCheckPluginDependencies } = usePluginDependencies() const { push } = useRouter() const invalidateAppList = useInvalidateAppList() const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions()) const currentUserId = useAtomValue(userProfileIdAtom) const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom) const isRbacEnabled = systemFeatures.rbac_enabled const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>() const importIdRef = useRef('') const setNeedRefresh = useSetNeedRefreshAppList() const handleImportDSL = useCallback( async ( payload: DSLPayload, { onSuccess, onPending, onFailed, skipRedirectOnSuccess }: ResponseCallback, ) => { if (isFetching) return setIsFetching(true) try { const response = await importDSL(payload) if (!response) return const { id, status, app_id, app_mode, imported_dsl_version, current_dsl_version, permission_keys, } = response if ( status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS ) { if (!app_id) return const message = t( ($) => $[status === DSLImportStatus.COMPLETED ? 'newApp.appCreated' : 'newApp.caution'], { ns: 'app' }, ) const description = status === DSLImportStatus.COMPLETED_WITH_WARNINGS ? t(($) => $['newApp.appCreateDSLWarning'], { ns: 'app' }) : undefined if (status === DSLImportStatus.COMPLETED) toast.success(message) else toast.warning(message, { description }) onSuccess?.(response) setNeedRefresh('1') invalidateAppList() await handleCheckPluginDependencies(app_id) if (!skipRedirectOnSuccess) { const redirectionTarget = await resolveImportedAppRedirectionTarget({ id: app_id, mode: app_mode, permission_keys, }) getRedirection(redirectionTarget, push, { currentUserId, resourceMaintainer: currentUserId, workspacePermissionKeys, isRbacEnabled, }) } } else if (status === DSLImportStatus.PENDING) { setVersions({ importedVersion: imported_dsl_version ?? '', systemVersion: current_dsl_version ?? '', }) importIdRef.current = id onPending?.(response) } else { toast.error(t(($) => $['newApp.appCreateFailed'], { ns: 'app' })) onFailed?.() } } catch { toast.error(t(($) => $['newApp.appCreateFailed'], { ns: 'app' })) onFailed?.() } finally { setIsFetching(false) } }, [ isFetching, t, handleCheckPluginDependencies, isRbacEnabled, push, setNeedRefresh, invalidateAppList, currentUserId, workspacePermissionKeys, ], ) const handleImportDSLConfirm = useCallback( async ({ onSuccess, onFailed, skipRedirectOnSuccess, }: Pick) => { if (isFetching) return setIsFetching(true) if (!importIdRef.current) return try { const response = await importDSLConfirm({ import_id: importIdRef.current, }) const { status, app_id, app_mode, permission_keys } = response if (!app_id) return if ( status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS ) { onSuccess?.(response) const message = t( ($) => $[status === DSLImportStatus.COMPLETED ? 'newApp.appCreated' : 'newApp.caution'], { ns: 'app' }, ) const description = status === DSLImportStatus.COMPLETED_WITH_WARNINGS ? t(($) => $['newApp.appCreateDSLWarning'], { ns: 'app' }) : undefined if (status === DSLImportStatus.COMPLETED) toast.success(message) else toast.warning(message, { description }) await handleCheckPluginDependencies(app_id) setNeedRefresh('1') invalidateAppList() if (!skipRedirectOnSuccess) { const redirectionTarget = await resolveImportedAppRedirectionTarget({ id: app_id, mode: app_mode, permission_keys, }) getRedirection(redirectionTarget, push, { currentUserId, resourceMaintainer: currentUserId, workspacePermissionKeys, isRbacEnabled, }) } } else if (status === DSLImportStatus.FAILED) { toast.error(t(($) => $['newApp.appCreateFailed'], { ns: 'app' })) onFailed?.() } } catch { toast.error(t(($) => $['newApp.appCreateFailed'], { ns: 'app' })) onFailed?.() } finally { setIsFetching(false) } }, [ isFetching, t, handleCheckPluginDependencies, isRbacEnabled, setNeedRefresh, push, invalidateAppList, currentUserId, workspacePermissionKeys, ], ) return { handleImportDSL, handleImportDSLConfirm, versions, isFetching, } }