Files
dify/web/app/components/workflow/hooks/use-node-plugin-installation.ts
yyh ff478b6fef perf(workflow): optimize plugin installation hooks to reduce unnecessary queries and memo invalidations
Only enable the tool query matching the node's provider_type instead of
all four, and use primitive useMemo dependencies instead of the whole
data object to avoid redundant recomputations on every render.
2026-01-30 01:56:07 +08:00

206 lines
6.8 KiB
TypeScript

import type { DataSourceNodeType } from '../nodes/data-source/types'
import type { ToolNodeType } from '../nodes/tool/types'
import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types'
import type { CommonNodeType } from '../types'
import { useCallback, useMemo } from 'react'
import { CollectionType } from '@/app/components/tools/types'
import { useInvalidDataSourceList } from '@/service/use-pipeline'
import {
useAllBuiltInTools,
useAllCustomTools,
useAllMCPTools,
useAllWorkflowTools,
useInvalidToolsByType,
} from '@/service/use-tools'
import {
useAllTriggerPlugins,
useInvalidateAllTriggerPlugins,
} from '@/service/use-triggers'
import { useStore } from '../store'
import { BlockEnum } from '../types'
import { matchDataSource, matchToolInCollection, matchTriggerProvider } from '../utils/plugin-install-check'
export type InstallationState = {
isChecking: boolean
isMissing: boolean
uniqueIdentifier?: string
canInstall: boolean
onInstallSuccess: () => void
shouldDim: boolean
}
const NOOP_INSTALLATION: InstallationState = {
isChecking: false,
isMissing: false,
uniqueIdentifier: undefined,
canInstall: false,
onInstallSuccess: () => undefined,
shouldDim: false,
}
const useToolInstallation = (data: ToolNodeType, enabled: boolean): InstallationState => {
const isBuiltIn = enabled && data.provider_type === CollectionType.builtIn
const isCustom = enabled && data.provider_type === CollectionType.custom
const isWorkflow = enabled && data.provider_type === CollectionType.workflow
const isMcp = enabled && data.provider_type === CollectionType.mcp
const builtInQuery = useAllBuiltInTools(isBuiltIn)
const customQuery = useAllCustomTools(isCustom)
const workflowQuery = useAllWorkflowTools(isWorkflow)
const mcpQuery = useAllMCPTools(isMcp)
const invalidateTools = useInvalidToolsByType(enabled ? data.provider_type : undefined)
const collectionInfo = useMemo(() => {
if (!enabled)
return undefined
switch (data.provider_type) {
case CollectionType.builtIn:
return {
list: builtInQuery.data,
isLoading: builtInQuery.isLoading,
}
case CollectionType.custom:
return {
list: customQuery.data,
isLoading: customQuery.isLoading,
}
case CollectionType.workflow:
return {
list: workflowQuery.data,
isLoading: workflowQuery.isLoading,
}
case CollectionType.mcp:
return {
list: mcpQuery.data,
isLoading: mcpQuery.isLoading,
}
default:
return undefined
}
}, [
enabled,
builtInQuery.data,
builtInQuery.isLoading,
customQuery.data,
customQuery.isLoading,
data.provider_type,
mcpQuery.data,
mcpQuery.isLoading,
workflowQuery.data,
workflowQuery.isLoading,
])
const collection = collectionInfo?.list
const isLoading = collectionInfo?.isLoading ?? false
const isResolved = !!collectionInfo && !isLoading
const { plugin_id, provider_id, provider_name } = data
const matchedCollection = useMemo(() => {
if (!collection || !collection.length)
return undefined
return matchToolInCollection(collection, { plugin_id, provider_id, provider_name })
}, [collection, plugin_id, provider_id, provider_name])
const uniqueIdentifier = data.plugin_unique_identifier || data.plugin_id || data.provider_id
const canInstall = Boolean(data.plugin_unique_identifier)
const onInstallSuccess = useCallback(() => {
if (invalidateTools)
invalidateTools()
}, [invalidateTools])
const shouldDim = (!!collectionInfo && !isResolved) || (isResolved && !matchedCollection)
return {
isChecking: !!collectionInfo && !isResolved,
isMissing: isResolved && !matchedCollection,
uniqueIdentifier,
canInstall,
onInstallSuccess,
shouldDim,
}
}
const useTriggerInstallation = (data: PluginTriggerNodeType, enabled: boolean): InstallationState => {
const triggerPluginsQuery = useAllTriggerPlugins(enabled)
const invalidateTriggers = useInvalidateAllTriggerPlugins()
const triggerProviders = triggerPluginsQuery.data
const isLoading = triggerPluginsQuery.isLoading
const { plugin_id, provider_id, provider_name } = data
const matchedProvider = useMemo(() => {
if (!triggerProviders || !triggerProviders.length)
return undefined
return matchTriggerProvider(triggerProviders, { plugin_id, provider_id, provider_name })
}, [plugin_id, provider_id, provider_name, triggerProviders])
const uniqueIdentifier = data.plugin_unique_identifier || data.plugin_id || data.provider_id
const canInstall = Boolean(data.plugin_unique_identifier)
const onInstallSuccess = useCallback(() => {
invalidateTriggers()
}, [invalidateTriggers])
const shouldDim = isLoading || (!isLoading && !!triggerProviders && !matchedProvider)
return {
isChecking: isLoading,
isMissing: !isLoading && !!triggerProviders && !matchedProvider,
uniqueIdentifier,
canInstall,
onInstallSuccess,
shouldDim,
}
}
const useDataSourceInstallation = (data: DataSourceNodeType, _enabled: boolean): InstallationState => {
const dataSourceList = useStore(s => s.dataSourceList)
const invalidateDataSourceList = useInvalidDataSourceList()
const { plugin_unique_identifier, plugin_id, provider_name } = data
const matchedPlugin = useMemo(() => {
if (!dataSourceList || !dataSourceList.length)
return undefined
return matchDataSource(dataSourceList, { plugin_unique_identifier, plugin_id, provider_name })
}, [dataSourceList, plugin_id, plugin_unique_identifier, provider_name])
const uniqueIdentifier = data.plugin_unique_identifier || data.plugin_id
const canInstall = Boolean(data.plugin_unique_identifier)
const onInstallSuccess = useCallback(() => {
invalidateDataSourceList()
}, [invalidateDataSourceList])
const hasLoadedList = dataSourceList !== undefined
const shouldDim = !hasLoadedList || (hasLoadedList && !matchedPlugin)
return {
isChecking: !hasLoadedList,
isMissing: hasLoadedList && !matchedPlugin,
uniqueIdentifier,
canInstall,
onInstallSuccess,
shouldDim,
}
}
export const useNodePluginInstallation = (data: CommonNodeType): InstallationState => {
const isTool = data.type === BlockEnum.Tool
const isTrigger = data.type === BlockEnum.TriggerPlugin
const isDataSource = data.type === BlockEnum.DataSource
const toolInstallation = useToolInstallation(data as ToolNodeType, isTool)
const triggerInstallation = useTriggerInstallation(data as PluginTriggerNodeType, isTrigger)
const dataSourceInstallation = useDataSourceInstallation(data as DataSourceNodeType, isDataSource)
if (isTool)
return toolInstallation
if (isTrigger)
return triggerInstallation
if (isDataSource)
return dataSourceInstallation
return NOOP_INSTALLATION
}