'use client' import type { PluginPageContentInset } from '../content-inset' import type { PluginCategoryEnum } from '@/app/components/plugins/types' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' import { noop } from 'es-toolkit/function' import * as React from 'react' import { useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { Group } from '@/app/components/base/icons/src/vender/other' import { FileZip } from '@/app/components/base/icons/src/vender/solid/files' import { Github } from '@/app/components/base/icons/src/vender/solid/general' import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' import InstallFromGitHub from '@/app/components/plugins/install-plugin/install-from-github' import InstallFromLocalPackage from '@/app/components/plugins/install-plugin/install-from-local-package' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useInstalledPluginList } from '@/service/use-plugins' import Line from '../../marketplace/empty/line' import { pluginPageContentFrameClassNames, pluginPageContentInsetClassNames } from '../content-inset' import { usePluginPageContext } from '../context' import { DropHintInstallSourceIcon, GithubInstallSourceIcon, LocalPackageInstallSourceIcon, MarketplaceInstallSourceIcon, } from '../install-source-icons' type InstallMethod = { icon: React.ComponentType<{ className?: string }> integrationIcon: React.ComponentType text: string action: string } const TriggerEmptyIcon = () => ( ) const AgentStrategyEmptyIcon = () => ( ) const ExtensionEmptyIcon = () => ( ) type EmptyProps = { canInstall?: boolean contentInset?: PluginPageContentInset installContextCategory?: PluginCategoryEnum onSwitchToMarketplace?: () => void variant?: 'default' | 'integrationsAgentStrategy' | 'integrationsExtension' | 'integrationsTrigger' } const Empty = ({ canInstall = true, contentInset = 'default', installContextCategory, onSwitchToMarketplace, variant = 'default', }: EmptyProps) => { const { t } = useTranslation() const fileInputRef = useRef(null) const [selectedAction, setSelectedAction] = useState(null) const [selectedFile, setSelectedFile] = useState(null) const { data: enable_marketplace } = useSuspenseQuery({ ...systemFeaturesQueryOptions(), select: s => s.enable_marketplace, }) const { data: plugin_installation_permission } = useSuspenseQuery({ ...systemFeaturesQueryOptions(), select: s => s.plugin_installation_permission, }) const setActiveTab = usePluginPageContext(v => v.setActiveTab) const handleFileChange = (event: React.ChangeEvent) => { if (!canInstall) { setSelectedFile(null) setSelectedAction(null) return } const file = event.target.files?.[0] if (file) { setSelectedFile(file) setSelectedAction('local') } } const filters = usePluginPageContext(v => v.filters) const { data: pluginList } = useInstalledPluginList() const text = useMemo(() => { if (pluginList?.plugins.length === 0) return t('list.noInstalled', { ns: 'plugin' }) if (filters.categories.length > 0 || filters.tags.length > 0 || filters.searchQuery) return t('list.notFound', { ns: 'plugin' }) }, [pluginList?.plugins.length, t, filters.categories.length, filters.tags.length, filters.searchQuery]) const installMethods = useMemo(() => { if (!canInstall) return [] const methods: InstallMethod[] = [] if (enable_marketplace) methods.push({ icon: MagicBox, integrationIcon: MarketplaceInstallSourceIcon, text: t('source.marketplace', { ns: 'plugin' }), action: 'marketplace' }) if (plugin_installation_permission.restrict_to_marketplace_only) return methods methods.push({ icon: Github, integrationIcon: GithubInstallSourceIcon, text: t('source.github', { ns: 'plugin' }), action: 'github' }) methods.push({ icon: FileZip, integrationIcon: LocalPackageInstallSourceIcon, text: t('source.local', { ns: 'plugin' }), action: 'local' }) return methods }, [canInstall, plugin_installation_permission, enable_marketplace, t]) const contentPaddingClassName = pluginPageContentInsetClassNames[contentInset] const canInstallLocalPackage = canInstall && !plugin_installation_permission.restrict_to_marketplace_only const isIntegrationsTrigger = variant === 'integrationsTrigger' const isIntegrationsAgentStrategy = variant === 'integrationsAgentStrategy' const isIntegrationsExtension = variant === 'integrationsExtension' const isIntegrationsCategory = isIntegrationsTrigger || isIntegrationsAgentStrategy || isIntegrationsExtension const supportsDropInstall = isIntegrationsCategory const showDropInstallTip = supportsDropInstall && canInstallLocalPackage const contentFrameClassName = cn( pluginPageContentFrameClassNames[contentInset], contentPaddingClassName, ) const emptyText = isIntegrationsTrigger ? t('list.noTriggerFound', { ns: 'plugin' }) : isIntegrationsAgentStrategy ? t('list.noAgentStrategyFound', { ns: 'plugin' }) : isIntegrationsExtension ? t('list.noExtensionFound', { ns: 'plugin' }) : text return (
{/* skeleton */}
{Array.from({ length: isIntegrationsCategory ? 22 : 20 }).fill(0).map((_, i) => (
))}
{/* mask */}
{isIntegrationsCategory ? ( {isIntegrationsAgentStrategy ? : isIntegrationsExtension ? : } ) : } {!isIntegrationsCategory && ( <> )}
{emptyText}
{installMethods.map(({ icon: Icon, integrationIcon: IntegrationIcon, text, action }) => ( ))}
{showDropInstallTip && (
{t('installModal.dropIntegrationToInstall', { ns: 'plugin' })}
)} {selectedAction === 'github' && ( setSelectedAction(null)} /> )} {selectedAction === 'local' && selectedFile && ( setSelectedAction(null)} onSuccess={noop} /> )}
) } Empty.displayName = 'Empty' export default React.memo(Empty)