mirror of
https://github.com/langgenius/dify.git
synced 2025-07-18 23:02:25 +00:00
278 lines
9.4 KiB
TypeScript
278 lines
9.4 KiB
TypeScript
'use client'
|
|
import type { ForwardRefRenderFunction } from 'react'
|
|
import { useImperativeHandle } from 'react'
|
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../types'
|
|
import MarketplaceItem from '../item/marketplace-item'
|
|
import GithubItem from '../item/github-item'
|
|
import { useFetchPluginsInMarketPlaceByInfo } from '@/service/use-plugins'
|
|
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
|
|
import produce from 'immer'
|
|
import PackageItem from '../item/package-item'
|
|
import LoadingError from '../../base/loading-error'
|
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
|
import { pluginInstallLimit } from '../../hooks/use-install-plugin-limit'
|
|
|
|
type Props = {
|
|
allPlugins: Dependency[]
|
|
selectedPlugins: Plugin[]
|
|
onSelect: (plugin: Plugin, selectedIndex: number, allCanInstallPluginsLength: number) => void
|
|
onSelectAll: (plugins: Plugin[], selectedIndexes: number[]) => void
|
|
onDeSelectAll: () => void
|
|
onLoadedAllPlugin: (installedInfo: Record<string, VersionInfo>) => void
|
|
isFromMarketPlace?: boolean
|
|
}
|
|
|
|
export type ExposeRefs = {
|
|
selectAllPlugins: () => void
|
|
deSelectAllPlugins: () => void
|
|
}
|
|
|
|
const InstallByDSLList: ForwardRefRenderFunction<ExposeRefs, Props> = ({
|
|
allPlugins,
|
|
selectedPlugins,
|
|
onSelect,
|
|
onSelectAll,
|
|
onDeSelectAll,
|
|
onLoadedAllPlugin,
|
|
isFromMarketPlace,
|
|
}, ref) => {
|
|
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
|
// DSL has id, to get plugin info to show more info
|
|
const { isLoading: isFetchingMarketplaceDataById, data: infoGetById, error: infoByIdError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map((d) => {
|
|
const dependecy = (d as GitHubItemAndMarketPlaceDependency).value
|
|
// split org, name, version by / and :
|
|
// and remove @ and its suffix
|
|
const [orgPart, nameAndVersionPart] = dependecy.marketplace_plugin_unique_identifier!.split('@')[0].split('/')
|
|
const [name, version] = nameAndVersionPart.split(':')
|
|
return {
|
|
organization: orgPart,
|
|
plugin: name,
|
|
version,
|
|
}
|
|
}))
|
|
// has meta(org,name,version), to get id
|
|
const { isLoading: isFetchingDataByMeta, data: infoByMeta, error: infoByMetaError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value!))
|
|
|
|
const [plugins, doSetPlugins] = useState<(Plugin | undefined)[]>((() => {
|
|
const hasLocalPackage = allPlugins.some(d => d.type === 'package')
|
|
if (!hasLocalPackage)
|
|
return []
|
|
|
|
const _plugins = allPlugins.map((d) => {
|
|
if (d.type === 'package') {
|
|
return {
|
|
...(d as any).value.manifest,
|
|
plugin_id: (d as any).value.unique_identifier,
|
|
}
|
|
}
|
|
|
|
return undefined
|
|
})
|
|
return _plugins
|
|
})())
|
|
|
|
const pluginsRef = React.useRef<(Plugin | undefined)[]>(plugins)
|
|
|
|
const setPlugins = useCallback((p: (Plugin | undefined)[]) => {
|
|
doSetPlugins(p)
|
|
pluginsRef.current = p
|
|
}, [])
|
|
|
|
const [errorIndexes, setErrorIndexes] = useState<number[]>([])
|
|
|
|
const handleGitHubPluginFetched = useCallback((index: number) => {
|
|
return (p: Plugin) => {
|
|
const nextPlugins = produce(pluginsRef.current, (draft) => {
|
|
draft[index] = p
|
|
})
|
|
setPlugins(nextPlugins)
|
|
}
|
|
}, [setPlugins])
|
|
|
|
const handleGitHubPluginFetchError = useCallback((index: number) => {
|
|
return () => {
|
|
setErrorIndexes([...errorIndexes, index])
|
|
}
|
|
}, [errorIndexes])
|
|
|
|
const marketPlaceInDSLIndex = useMemo(() => {
|
|
const res: number[] = []
|
|
allPlugins.forEach((d, index) => {
|
|
if (d.type === 'marketplace')
|
|
res.push(index)
|
|
})
|
|
return res
|
|
}, [allPlugins])
|
|
|
|
useEffect(() => {
|
|
if (!isFetchingMarketplaceDataById && infoGetById?.data.list) {
|
|
const sortedList = allPlugins.filter(d => d.type === 'marketplace').map((d) => {
|
|
const p = d as GitHubItemAndMarketPlaceDependency
|
|
const id = p.value.marketplace_plugin_unique_identifier?.split(':')[0]
|
|
const retPluginInfo = infoGetById.data.list.find(item => item.plugin.plugin_id === id)?.plugin
|
|
return { ...retPluginInfo, from: d.type } as Plugin
|
|
})
|
|
const payloads = sortedList
|
|
const failedIndex: number[] = []
|
|
const nextPlugins = produce(pluginsRef.current, (draft) => {
|
|
marketPlaceInDSLIndex.forEach((index, i) => {
|
|
if (payloads[i]) {
|
|
draft[index] = {
|
|
...payloads[i],
|
|
version: payloads[i]!.version || payloads[i]!.latest_version,
|
|
}
|
|
}
|
|
else { failedIndex.push(index) }
|
|
})
|
|
})
|
|
setPlugins(nextPlugins)
|
|
|
|
if (failedIndex.length > 0)
|
|
setErrorIndexes([...errorIndexes, ...failedIndex])
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isFetchingMarketplaceDataById])
|
|
|
|
useEffect(() => {
|
|
if (!isFetchingDataByMeta && infoByMeta?.data.list) {
|
|
const payloads = infoByMeta?.data.list
|
|
const failedIndex: number[] = []
|
|
const nextPlugins = produce(pluginsRef.current, (draft) => {
|
|
marketPlaceInDSLIndex.forEach((index, i) => {
|
|
if (payloads[i]) {
|
|
const item = payloads[i]
|
|
draft[index] = {
|
|
...item.plugin,
|
|
plugin_id: item.version.unique_identifier,
|
|
}
|
|
}
|
|
else {
|
|
failedIndex.push(index)
|
|
}
|
|
})
|
|
})
|
|
setPlugins(nextPlugins)
|
|
if (failedIndex.length > 0)
|
|
setErrorIndexes([...errorIndexes, ...failedIndex])
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isFetchingDataByMeta])
|
|
|
|
useEffect(() => {
|
|
// get info all failed
|
|
if (infoByMetaError || infoByIdError)
|
|
setErrorIndexes([...errorIndexes, ...marketPlaceInDSLIndex])
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [infoByMetaError, infoByIdError])
|
|
|
|
const isLoadedAllData = (plugins.filter(p => !!p).length + errorIndexes.length) === allPlugins.length
|
|
|
|
const { installedInfo } = useCheckInstalled({
|
|
pluginIds: plugins?.filter(p => !!p).map((d) => {
|
|
return `${d?.org || d?.author}/${d?.name}`
|
|
}) || [],
|
|
enabled: isLoadedAllData,
|
|
})
|
|
|
|
const getVersionInfo = useCallback((pluginId: string) => {
|
|
const pluginDetail = installedInfo?.[pluginId]
|
|
const hasInstalled = !!pluginDetail
|
|
return {
|
|
hasInstalled,
|
|
installedVersion: pluginDetail?.installedVersion,
|
|
toInstallVersion: '',
|
|
}
|
|
}, [installedInfo])
|
|
|
|
useEffect(() => {
|
|
if (isLoadedAllData && installedInfo)
|
|
onLoadedAllPlugin(installedInfo!)
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isLoadedAllData, installedInfo])
|
|
|
|
const handleSelect = useCallback((index: number) => {
|
|
return () => {
|
|
const canSelectPlugins = plugins.filter((p) => {
|
|
const { canInstall } = pluginInstallLimit(p!, systemFeatures)
|
|
return canInstall
|
|
})
|
|
onSelect(plugins[index]!, index, canSelectPlugins.length)
|
|
}
|
|
}, [onSelect, plugins, systemFeatures])
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
selectAllPlugins: () => {
|
|
const selectedIndexes: number[] = []
|
|
const selectedPlugins: Plugin[] = []
|
|
allPlugins.forEach((d, index) => {
|
|
const p = plugins[index]
|
|
if (!p)
|
|
return
|
|
const { canInstall } = pluginInstallLimit(p, systemFeatures)
|
|
if (canInstall) {
|
|
selectedIndexes.push(index)
|
|
selectedPlugins.push(p)
|
|
}
|
|
})
|
|
onSelectAll(selectedPlugins, selectedIndexes)
|
|
},
|
|
deSelectAllPlugins: () => {
|
|
onDeSelectAll()
|
|
},
|
|
}))
|
|
|
|
return (
|
|
<>
|
|
{allPlugins.map((d, index) => {
|
|
if (errorIndexes.includes(index)) {
|
|
return (
|
|
<LoadingError key={index} />
|
|
)
|
|
}
|
|
const plugin = plugins[index]
|
|
if (d.type === 'github') {
|
|
return (<GithubItem
|
|
key={index}
|
|
checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
|
|
onCheckedChange={handleSelect(index)}
|
|
dependency={d as GitHubItemAndMarketPlaceDependency}
|
|
onFetchedPayload={handleGitHubPluginFetched(index)}
|
|
onFetchError={handleGitHubPluginFetchError(index)}
|
|
versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)}
|
|
/>)
|
|
}
|
|
|
|
if (d.type === 'marketplace') {
|
|
return (
|
|
<MarketplaceItem
|
|
key={index}
|
|
checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
|
|
onCheckedChange={handleSelect(index)}
|
|
payload={{ ...plugin, from: d.type } as Plugin}
|
|
version={(d as GitHubItemAndMarketPlaceDependency).value.version! || plugin?.version || ''}
|
|
versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
// Local package
|
|
return (
|
|
<PackageItem
|
|
key={index}
|
|
checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
|
|
onCheckedChange={handleSelect(index)}
|
|
payload={d as PackageDependency}
|
|
isFromMarketPlace={isFromMarketPlace}
|
|
versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)}
|
|
/>
|
|
)
|
|
})
|
|
}
|
|
</>
|
|
)
|
|
}
|
|
export default React.forwardRef(InstallByDSLList)
|