dify/web/app/components/integrations/plugin-category-page.tsx
Jingyi 9b74df21d0
feat(web): refine onboarding UI (#37433)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: hjlarry <hjlarry@163.com>
Co-authored-by: fatelei <fatelei@gmail.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com>
Co-authored-by: gigglewang <gigglewang@dify.ai>
Co-authored-by: Yunlu Wen <yunlu.wen@dify.ai>
Co-authored-by: chariri <w@chariri.moe>
Co-authored-by: Evan <2869018789@qq.com>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
2026-06-15 08:47:15 +00:00

116 lines
3.9 KiB
TypeScript

'use client'
import type { ReactNode } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { noop } from 'es-toolkit/function'
import { useMemo, useState } from 'react'
import InstallFromLocalPackage from '@/app/components/plugins/install-plugin/install-from-local-package'
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
import { PluginPageContextProvider } from '@/app/components/plugins/plugin-page/context-provider'
import PluginsPanel from '@/app/components/plugins/plugin-page/plugins-panel'
import { useUploader } from '@/app/components/plugins/plugin-page/use-uploader'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
type PluginCategoryPageProps = {
canInstall?: boolean
category: PluginCategoryEnum
layout?: (parts: { body: ReactNode, toolbar: ReactNode }) => ReactNode
onSwitchToMarketplace?: () => void
toolbarAction?: ReactNode
}
const supportedLocalPackageExtensions = SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS.split(',')
const PluginCategoryPageContent = ({
canInstall = true,
category,
layout,
onSwitchToMarketplace,
toolbarAction,
}: PluginCategoryPageProps) => {
const [currentFile, setCurrentFile] = useState<File | null>(null)
const containerRef = usePluginPageContext(v => v.containerRef)
const { data: pluginInstallationPermission } = useSuspenseQuery({
...systemFeaturesQueryOptions(),
select: s => s.plugin_installation_permission,
})
const supportsDropInstall = category === PluginCategoryEnum.tool || category === PluginCategoryEnum.trigger || category === PluginCategoryEnum.agent || category === PluginCategoryEnum.extension
const canDropLocalPackage = canInstall && supportsDropInstall && !pluginInstallationPermission.restrict_to_marketplace_only
const handleFileChange = (file: File | null) => {
if (!canInstall) {
setCurrentFile(null)
return
}
if (!file || !supportedLocalPackageExtensions.some(extension => file.name.endsWith(extension))) {
setCurrentFile(null)
return
}
setCurrentFile(file)
}
const {
dragging,
fileUploader,
fileChangeHandle,
removeFile,
} = useUploader({
onFileChange: handleFileChange,
containerRef,
enabled: canDropLocalPackage,
})
return (
<div ref={containerRef} className="relative flex h-0 grow flex-col overflow-hidden bg-components-panel-bg">
<PluginsPanel canInstall={canInstall} contentInset="compact" fixedCategory={category} layout={layout} onSwitchToMarketplace={onSwitchToMarketplace} toolbarAction={toolbarAction} />
{dragging && (
<div
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"
/>
)}
{currentFile && (
<InstallFromLocalPackage
file={currentFile}
installContextCategory={category}
onClose={removeFile ?? noop}
onSuccess={noop}
/>
)}
<input
ref={fileUploader}
className="hidden"
type="file"
id="fileUploader"
accept={SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS}
onChange={fileChangeHandle ?? noop}
/>
</div>
)
}
const PluginCategoryPage = ({
canInstall = true,
category,
layout,
onSwitchToMarketplace,
toolbarAction,
}: PluginCategoryPageProps) => {
const initialFilters = useMemo(() => ({
categories: [category],
tags: [],
searchQuery: '',
}), [category])
return (
<PluginPageContextProvider key={category} initialFilters={initialFilters}>
<PluginCategoryPageContent canInstall={canInstall} category={category} layout={layout} onSwitchToMarketplace={onSwitchToMarketplace} toolbarAction={toolbarAction} />
</PluginPageContextProvider>
)
}
export default PluginCategoryPage