| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  | 'use client' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { useEffect, useMemo, useState } from 'react' | 
					
						
							|  |  |  | import { useTranslation } from 'react-i18next' | 
					
						
							| 
									
										
										
										
											2025-03-05 14:59:04 +08:00
										 |  |  | import { useContext } from 'use-context-selector' | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  | import Link from 'next/link' | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   RiBookOpenLine, | 
					
						
							|  |  |  |   RiDragDropLine, | 
					
						
							|  |  |  |   RiEqualizer2Line, | 
					
						
							|  |  |  | } from '@remixicon/react' | 
					
						
							|  |  |  | import { useBoolean } from 'ahooks' | 
					
						
							|  |  |  | import InstallFromLocalPackage from '../install-plugin/install-from-local-package' | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   PluginPageContextProvider, | 
					
						
							|  |  |  |   usePluginPageContext, | 
					
						
							|  |  |  | } from './context' | 
					
						
							|  |  |  | import InstallPluginDropdown from './install-plugin-dropdown' | 
					
						
							|  |  |  | import { useUploader } from './use-uploader' | 
					
						
							|  |  |  | import usePermission from './use-permission' | 
					
						
							|  |  |  | import DebugInfo from './debug-info' | 
					
						
							|  |  |  | import PluginTasks from './plugin-tasks' | 
					
						
							|  |  |  | import Button from '@/app/components/base/button' | 
					
						
							|  |  |  | import TabSlider from '@/app/components/base/tab-slider' | 
					
						
							|  |  |  | import Tooltip from '@/app/components/base/tooltip' | 
					
						
							|  |  |  | import cn from '@/utils/classnames' | 
					
						
							|  |  |  | import PermissionSetModal from '@/app/components/plugins/permission-setting-modal/modal' | 
					
						
							|  |  |  | import { useSelector as useAppContextSelector } from '@/context/app-context' | 
					
						
							|  |  |  | import InstallFromMarketplace from '../install-plugin/install-from-marketplace' | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   useRouter, | 
					
						
							|  |  |  |   useSearchParams, | 
					
						
							|  |  |  | } from 'next/navigation' | 
					
						
							|  |  |  | import type { Dependency } from '../types' | 
					
						
							|  |  |  | import type { PluginDeclaration, PluginManifestInMarket } from '../types' | 
					
						
							|  |  |  | import { sleep } from '@/utils' | 
					
						
							|  |  |  | import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins' | 
					
						
							|  |  |  | import { marketplaceApiPrefix } from '@/config' | 
					
						
							|  |  |  | import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' | 
					
						
							| 
									
										
										
										
											2025-03-05 14:59:04 +08:00
										 |  |  | import { LanguagesSupported } from '@/i18n/language' | 
					
						
							|  |  |  | import I18n from '@/context/i18n' | 
					
						
							| 
									
										
										
										
											2025-04-06 17:56:08 +08:00
										 |  |  | import { noop } from 'lodash-es' | 
					
						
							| 
									
										
										
										
											2025-04-10 11:18:43 +08:00
										 |  |  | import { PLUGIN_TYPE_SEARCH_MAP } from '../marketplace/plugin-type-switch' | 
					
						
							|  |  |  | import { PLUGIN_PAGE_TABS_MAP } from '../hooks' | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | const PACKAGE_IDS_KEY = 'package-ids' | 
					
						
							|  |  |  | const BUNDLE_INFO_KEY = 'bundle-info' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export type PluginPageProps = { | 
					
						
							|  |  |  |   plugins: React.ReactNode | 
					
						
							|  |  |  |   marketplace: React.ReactNode | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | const PluginPage = ({ | 
					
						
							|  |  |  |   plugins, | 
					
						
							|  |  |  |   marketplace, | 
					
						
							|  |  |  | }: PluginPageProps) => { | 
					
						
							|  |  |  |   const { t } = useTranslation() | 
					
						
							| 
									
										
										
										
											2025-03-05 14:59:04 +08:00
										 |  |  |   const { locale } = useContext(I18n) | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |   const searchParams = useSearchParams() | 
					
						
							|  |  |  |   const { replace } = useRouter() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-02 22:35:51 +08:00
										 |  |  |   document.title = `${t('plugin.metadata.title')} - Dify` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |   // just support install one package now
 | 
					
						
							|  |  |  |   const packageId = useMemo(() => { | 
					
						
							|  |  |  |     const idStrings = searchParams.get(PACKAGE_IDS_KEY) | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       return idStrings ? JSON.parse(idStrings)[0] : '' | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-04-14 11:27:14 +08:00
										 |  |  |     catch { | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |       return '' | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }, [searchParams]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const [dependencies, setDependencies] = useState<Dependency[]>([]) | 
					
						
							|  |  |  |   const bundleInfo = useMemo(() => { | 
					
						
							|  |  |  |     const info = searchParams.get(BUNDLE_INFO_KEY) | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       return info ? JSON.parse(info) : undefined | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-04-14 11:27:14 +08:00
										 |  |  |     catch { | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |       return undefined | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }, [searchParams]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const [isShowInstallFromMarketplace, { | 
					
						
							|  |  |  |     setTrue: showInstallFromMarketplace, | 
					
						
							|  |  |  |     setFalse: doHideInstallFromMarketplace, | 
					
						
							|  |  |  |   }] = useBoolean(false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const hideInstallFromMarketplace = () => { | 
					
						
							|  |  |  |     doHideInstallFromMarketplace() | 
					
						
							|  |  |  |     const url = new URL(window.location.href) | 
					
						
							|  |  |  |     url.searchParams.delete(PACKAGE_IDS_KEY) | 
					
						
							|  |  |  |     url.searchParams.delete(BUNDLE_INFO_KEY) | 
					
						
							|  |  |  |     replace(url.toString()) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const [manifest, setManifest] = useState<PluginDeclaration | PluginManifestInMarket | null>(null) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     (async () => { | 
					
						
							|  |  |  |       await sleep(100) | 
					
						
							|  |  |  |       if (packageId) { | 
					
						
							|  |  |  |         const { data } = await fetchManifestFromMarketPlace(encodeURIComponent(packageId)) | 
					
						
							|  |  |  |         const { plugin, version } = data | 
					
						
							|  |  |  |         setManifest({ | 
					
						
							|  |  |  |           ...plugin, | 
					
						
							|  |  |  |           version: version.version, | 
					
						
							|  |  |  |           icon: `${marketplaceApiPrefix}/plugins/${plugin.org}/${plugin.name}/icon`, | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         showInstallFromMarketplace() | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (bundleInfo) { | 
					
						
							|  |  |  |         const { data } = await fetchBundleInfoFromMarketPlace(bundleInfo) | 
					
						
							|  |  |  |         setDependencies(data.version.dependencies) | 
					
						
							|  |  |  |         showInstallFromMarketplace() | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     })() | 
					
						
							|  |  |  |     // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
					
						
							|  |  |  |   }, [packageId, bundleInfo]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const { | 
					
						
							|  |  |  |     canManagement, | 
					
						
							|  |  |  |     canDebugger, | 
					
						
							|  |  |  |     canSetPermissions, | 
					
						
							|  |  |  |     permissions, | 
					
						
							|  |  |  |     setPermissions, | 
					
						
							|  |  |  |   } = usePermission() | 
					
						
							|  |  |  |   const [showPluginSettingModal, { | 
					
						
							|  |  |  |     setTrue: setShowPluginSettingModal, | 
					
						
							|  |  |  |     setFalse: setHidePluginSettingModal, | 
					
						
							|  |  |  |   }] = useBoolean() | 
					
						
							|  |  |  |   const [currentFile, setCurrentFile] = useState<File | null>(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 { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-10 11:18:43 +08:00
										 |  |  |   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]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |   const uploaderProps = useUploader({ | 
					
						
							|  |  |  |     onFileChange: setCurrentFile, | 
					
						
							|  |  |  |     containerRef, | 
					
						
							| 
									
										
										
										
											2025-04-10 11:18:43 +08:00
										 |  |  |     enabled: isPluginsTab, | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |   }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const { dragging, fileUploader, fileChangeHandle, removeFile } = uploaderProps | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <div | 
					
						
							|  |  |  |       id='marketplace-container' | 
					
						
							|  |  |  |       ref={containerRef} | 
					
						
							| 
									
										
										
										
											2025-03-18 10:12:49 +09:00
										 |  |  |       style={{ scrollbarGutter: 'stable' }} | 
					
						
							| 
									
										
										
										
											2025-04-10 11:18:43 +08:00
										 |  |  |       className={cn('relative flex grow flex-col overflow-y-auto border-t border-divider-subtle', isPluginsTab | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |         ? 'rounded-t-xl bg-components-panel-bg' | 
					
						
							|  |  |  |         : 'bg-background-body', | 
					
						
							|  |  |  |       )} | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       <div | 
					
						
							|  |  |  |         className={cn( | 
					
						
							| 
									
										
										
										
											2025-04-10 11:18:43 +08:00
										 |  |  |           'sticky top-0 z-10 flex min-h-[60px] items-center gap-1 self-stretch bg-components-panel-bg px-12 pb-2 pt-4', isExploringMarketplace && 'bg-background-body', | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |         )} | 
					
						
							|  |  |  |       > | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |         <div className='flex w-full items-center justify-between'> | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |           <div className='flex-1'> | 
					
						
							|  |  |  |             <TabSlider | 
					
						
							| 
									
										
										
										
											2025-04-10 11:18:43 +08:00
										 |  |  |               value={isPluginsTab ? PLUGIN_PAGE_TABS_MAP.plugins : PLUGIN_PAGE_TABS_MAP.marketplace} | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |               onChange={setActiveTab} | 
					
						
							|  |  |  |               options={options} | 
					
						
							|  |  |  |             /> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |           <div className='flex shrink-0 items-center gap-1'> | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2025-04-10 11:18:43 +08:00
										 |  |  |               isExploringMarketplace && ( | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |                 <> | 
					
						
							|  |  |  |                   <Link | 
					
						
							| 
									
										
										
										
											2025-03-05 14:59:04 +08:00
										 |  |  |                     href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/' : ''}plugins/publish-plugins/publish-to-dify-marketplace`} | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |                     target='_blank' | 
					
						
							|  |  |  |                   > | 
					
						
							|  |  |  |                     <Button | 
					
						
							|  |  |  |                       className='px-3' | 
					
						
							|  |  |  |                       variant='secondary-accent' | 
					
						
							|  |  |  |                     > | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |                       <RiBookOpenLine className='mr-1 h-4 w-4' /> | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |                       {t('plugin.submitPlugin')} | 
					
						
							|  |  |  |                     </Button> | 
					
						
							|  |  |  |                   </Link> | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |                   <div className='mx-2 h-3.5 w-[1px] bg-divider-regular'></div> | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |                 </> | 
					
						
							|  |  |  |               ) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             <PluginTasks /> | 
					
						
							|  |  |  |             {canManagement && ( | 
					
						
							|  |  |  |               <InstallPluginDropdown | 
					
						
							|  |  |  |                 onSwitchToMarketplaceTab={() => setActiveTab('discover')} | 
					
						
							|  |  |  |               /> | 
					
						
							|  |  |  |             )} | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               canDebugger && ( | 
					
						
							|  |  |  |                 <DebugInfo /> | 
					
						
							|  |  |  |               ) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               canSetPermissions && ( | 
					
						
							|  |  |  |                 <Tooltip | 
					
						
							|  |  |  |                   popupContent={t('plugin.privilege.title')} | 
					
						
							|  |  |  |                 > | 
					
						
							|  |  |  |                   <Button | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |                     className='group h-full w-full p-2 text-components-button-secondary-text' | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |                     onClick={setShowPluginSettingModal} | 
					
						
							|  |  |  |                   > | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |                     <RiEqualizer2Line className='h-4 w-4' /> | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |                   </Button> | 
					
						
							|  |  |  |                 </Tooltip> | 
					
						
							|  |  |  |               ) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       </div> | 
					
						
							| 
									
										
										
										
											2025-04-10 11:18:43 +08:00
										 |  |  |       {isPluginsTab && ( | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |         <> | 
					
						
							|  |  |  |           {plugins} | 
					
						
							|  |  |  |           {dragging && ( | 
					
						
							|  |  |  |             <div | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |               className="absolute inset-0 m-0.5 rounded-2xl border-2 border-dashed border-components-dropzone-border-accent | 
					
						
							|  |  |  |                   bg-[rgba(21,90,239,0.14)] p-2"> | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |             </div> | 
					
						
							|  |  |  |           )} | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |           <div className={`flex items-center justify-center gap-2 py-4 ${dragging ? 'text-text-accent' : 'text-text-quaternary'}`}> | 
					
						
							|  |  |  |             <RiDragDropLine className="h-4 w-4" /> | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |             <span className="system-xs-regular">{t('plugin.installModal.dropPluginToInstall')}</span> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |           {currentFile && ( | 
					
						
							|  |  |  |             <InstallFromLocalPackage | 
					
						
							|  |  |  |               file={currentFile} | 
					
						
							| 
									
										
										
										
											2025-04-06 17:56:08 +08:00
										 |  |  |               onClose={removeFile ?? noop} | 
					
						
							|  |  |  |               onSuccess={noop} | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |             /> | 
					
						
							|  |  |  |           )} | 
					
						
							|  |  |  |           <input | 
					
						
							|  |  |  |             ref={fileUploader} | 
					
						
							|  |  |  |             className="hidden" | 
					
						
							|  |  |  |             type="file" | 
					
						
							|  |  |  |             id="fileUploader" | 
					
						
							|  |  |  |             accept={SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS} | 
					
						
							| 
									
										
										
										
											2025-04-06 17:56:08 +08:00
										 |  |  |             onChange={fileChangeHandle ?? noop} | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |           /> | 
					
						
							|  |  |  |         </> | 
					
						
							|  |  |  |       )} | 
					
						
							|  |  |  |       { | 
					
						
							| 
									
										
										
										
											2025-04-10 11:18:43 +08:00
										 |  |  |         isExploringMarketplace && enable_marketplace && marketplace | 
					
						
							| 
									
										
										
										
											2025-02-17 17:05:13 +08:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       {showPluginSettingModal && ( | 
					
						
							|  |  |  |         <PermissionSetModal | 
					
						
							|  |  |  |           payload={permissions!} | 
					
						
							|  |  |  |           onHide={setHidePluginSettingModal} | 
					
						
							|  |  |  |           onSave={setPermissions} | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |       )} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         isShowInstallFromMarketplace && ( | 
					
						
							|  |  |  |           <InstallFromMarketplace | 
					
						
							|  |  |  |             manifest={manifest! as PluginManifestInMarket} | 
					
						
							|  |  |  |             uniqueIdentifier={packageId} | 
					
						
							|  |  |  |             isBundle={!!bundleInfo} | 
					
						
							|  |  |  |             dependencies={dependencies} | 
					
						
							|  |  |  |             onClose={hideInstallFromMarketplace} | 
					
						
							|  |  |  |             onSuccess={hideInstallFromMarketplace} | 
					
						
							|  |  |  |           /> | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const PluginPageWithContext = (props: PluginPageProps) => { | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <PluginPageContextProvider> | 
					
						
							|  |  |  |       <PluginPage {...props} /> | 
					
						
							|  |  |  |     </PluginPageContextProvider> | 
					
						
							|  |  |  |   ) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default PluginPageWithContext |