'use client' import type { PluginPageTab } from './context' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiBookOpenLine, RiBugLine, RiDragDropLine, RiEqualizer2Line } from '@remixicon/react' import { useSuspenseQuery } from '@tanstack/react-query' import { useBoolean } from 'ahooks' import { noop } from 'es-toolkit/function' import { cloneElement, isValidElement, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import TabSlider from '@/app/components/base/tab-slider' import ReferenceSettingModal from '@/app/components/plugins/reference-setting-modal' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' import { useDocLink } from '@/context/i18n' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useDocumentTitle from '@/hooks/use-document-title' import Link from '@/next/link' import { useRouter } from '@/next/navigation' import { PLUGIN_PAGE_TABS_MAP } from '../hooks' import { PluginInstallPermissionProvider } from '../install-plugin/components/plugin-install-permission-provider' import InstallFromLocalPackage from '../install-plugin/install-from-local-package' import InstallFromMarketplaceQuery from '../install-plugin/install-from-marketplace-query' import { PLUGIN_TYPE_SEARCH_MAP } from '../marketplace/constants' import { getInstallRedirectPathByPluginCategory } from '../plugin-routes' import { PluginCategoryEnum } from '../types' import { usePluginPageContext } from './context' import { PluginPageContextProvider } from './context-provider' import DebugInfo from './debug-info' import InstallPluginDropdown from './install-plugin-dropdown' import PluginTasks from './plugin-tasks' import useReferenceSetting from './use-reference-setting' import { useUploader } from './use-uploader' const pluginPageTabSet = new Set([ PLUGIN_PAGE_TABS_MAP.plugins, PLUGIN_PAGE_TABS_MAP.marketplace, ...Object.values(PLUGIN_TYPE_SEARCH_MAP), ]) const isPluginPageTab = (value: string): value is PluginPageTab => { return pluginPageTabSet.has(value) } const getCurrentInstallSearchParams = (packageId: string) => { const searchParams: Record = {} new URLSearchParams(window.location.search).forEach((value, key) => { const existing = searchParams[key] if (existing === undefined) { searchParams[key] = value return } if (Array.isArray(existing)) { existing.push(value) return } searchParams[key] = [existing, value] }) searchParams['package-ids'] ??= JSON.stringify([packageId]) return searchParams } export type PluginPageProps = { plugins: React.ReactNode marketplace: React.ReactNode } type PluginPanelPermissionProps = { canInstall?: boolean canDeletePlugin?: boolean canUpdatePlugin?: boolean } const PluginPage = ({ plugins, marketplace }: PluginPageProps) => { const { t } = useTranslation() const docLink = useDocLink() const { replace } = useRouter() const { referenceSetting, canInstallPlugin, canUpdatePlugin, canDeletePlugin, canDebugger, canSetPermissions, canSetPluginPreferences, currentDifyVersion, isPermissionLoading, isReferenceSettingLoading, setReferenceSettings, } = useReferenceSetting(PluginCategoryEnum.tool) const handlePackageCategoryResolved = useCallback( (category: string | undefined, packageId: string) => { const installRedirectPath = getInstallRedirectPathByPluginCategory( category, getCurrentInstallSearchParams(packageId), ) if (!installRedirectPath) return false replace(installRedirectPath) return true }, [replace], ) const [ showPluginSettingModal, { setTrue: setShowPluginSettingModal, setFalse: setHidePluginSettingModal }, ] = useBoolean(false) const [currentFile, setCurrentFile] = useState(null) const containerRef = usePluginPageContext((v) => v.containerRef) const options = usePluginPageContext((v) => v.options) const activeTab = usePluginPageContext((v) => v.activeTab) const setActiveTab = usePluginPageContext((v) => v.setActiveTab) const { data: enable_marketplace } = useSuspenseQuery({ ...systemFeaturesQueryOptions(), select: (s) => s.enable_marketplace, }) const isPluginsTab = useMemo(() => activeTab === PLUGIN_PAGE_TABS_MAP.plugins, [activeTab]) const isExploringMarketplace = useMemo(() => { const values = Object.values(PLUGIN_TYPE_SEARCH_MAP) return activeTab === PLUGIN_PAGE_TABS_MAP.marketplace || values.includes(activeTab) }, [activeTab]) useDocumentTitle( isExploringMarketplace ? t(($) => $['mainNav.marketplace'], { ns: 'common' }) : t(($) => $['metadata.title'], { ns: 'plugin' }), ) const handleFileChange = (file: File | null) => { if (!canInstallPlugin) { setCurrentFile(null) return } if (!file || !file.name.endsWith('.difypkg')) { setCurrentFile(null) return } setCurrentFile(file) } const uploaderProps = useUploader({ onFileChange: handleFileChange, containerRef, enabled: isPluginsTab && canInstallPlugin, }) const { dragging, fileUploader, fileChangeHandle, removeFile } = uploaderProps const pluginsWithPermission = useMemo(() => { if (!isValidElement(plugins) || typeof plugins.type === 'string') return plugins return cloneElement(plugins as React.ReactElement, { canInstall: canInstallPlugin, canDeletePlugin, canUpdatePlugin, }) }, [canInstallPlugin, canDeletePlugin, canUpdatePlugin, plugins]) return (
{ if (isPluginPageTab(nextTab)) setActiveTab(nextTab) }} options={options} />
{isExploringMarketplace && ( <>
)} {(canInstallPlugin || isPermissionLoading) && ( setActiveTab('discover')} /> )} {canDebugger && } {isPermissionLoading && ( )} {(canSetPermissions || canSetPluginPreferences) && ( $['privilege.title'], { ns: 'plugin' })} className="group size-full p-2 text-components-button-secondary-text" disabled={isReferenceSettingLoading || !referenceSetting} loading={isReferenceSettingLoading} onClick={setShowPluginSettingModal} > )}
{isPluginsTab && ( <> {pluginsWithPermission} {dragging && (
)} {canInstallPlugin && (
{t(($) => $['installModal.dropPluginToInstall'], { ns: 'plugin' })}
)} {currentFile && ( )} )} {isExploringMarketplace && enable_marketplace && ( {marketplace} )} {showPluginSettingModal && referenceSetting && ( )}
) } const PluginPageWithContext = (props: PluginPageProps) => { return ( ) } export default PluginPageWithContext