mirror of
https://github.com/langgenius/dify.git
synced 2026-02-12 13:00:45 -05:00
213 lines
8.7 KiB
TypeScript
213 lines
8.7 KiB
TypeScript
import type { FC } from 'react'
|
|
import type { ToolNodeType } from './types'
|
|
import type { StrategyDetail, StrategyPluginDetail } from '@/app/components/plugins/types'
|
|
import type { AgentNodeType } from '@/app/components/workflow/nodes/agent/types'
|
|
import type { CommonNodeType, NodeProps, Node as WorkflowNode } from '@/app/components/workflow/types'
|
|
import * as React from 'react'
|
|
import { useMemo } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { useNodes } from 'reactflow'
|
|
import AlertTriangle from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertTriangle'
|
|
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
|
import { useNodesMetaData } from '@/app/components/workflow/hooks'
|
|
import { useNodePluginInstallation } from '@/app/components/workflow/hooks/use-node-plugin-installation'
|
|
import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button'
|
|
import { BlockEnum } from '@/app/components/workflow/types'
|
|
import { AGENT_CONTEXT_VAR_PATTERN, getAgentNodeIdFromContextVar } from '@/app/components/workflow/utils/agent-context'
|
|
import { useGetLanguage } from '@/context/i18n'
|
|
import { useStrategyProviders } from '@/service/use-strategy'
|
|
import { cn } from '@/utils/classnames'
|
|
import { VarType } from './types'
|
|
|
|
type AgentCheckValidContext = {
|
|
provider?: StrategyPluginDetail
|
|
strategy?: StrategyDetail
|
|
language: string
|
|
isReadyForCheckValid: boolean
|
|
}
|
|
|
|
const Node: FC<NodeProps<ToolNodeType>> = ({
|
|
id,
|
|
data,
|
|
}) => {
|
|
const { t } = useTranslation()
|
|
const language = useGetLanguage()
|
|
const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
|
|
const { data: strategyProviders } = useStrategyProviders()
|
|
const nodes = useNodes<CommonNodeType>()
|
|
const { tool_configurations, paramSchemas } = data
|
|
const toolConfigs = Object.keys(tool_configurations || {})
|
|
const {
|
|
isChecking,
|
|
isMissing,
|
|
uniqueIdentifier,
|
|
canInstall,
|
|
onInstallSuccess,
|
|
shouldDim,
|
|
} = useNodePluginInstallation(data)
|
|
const showInstallButton = !isChecking && isMissing && canInstall && uniqueIdentifier
|
|
const nodesById = useMemo(() => {
|
|
return nodes.reduce((acc, node) => {
|
|
acc[node.id] = node
|
|
return acc
|
|
}, {} as Record<string, WorkflowNode>)
|
|
}, [nodes])
|
|
|
|
const nestedNodeEntries = useMemo(() => {
|
|
const entries: Array<{ agentNodeId: string, extractorNodeId?: string, paramKey: string }> = []
|
|
const seen = new Set<string>()
|
|
const toolParams = data.tool_parameters || {}
|
|
Object.entries(toolParams).forEach(([paramKey, param]) => {
|
|
const value = param?.value
|
|
if (typeof value !== 'string')
|
|
return
|
|
const matches = value.matchAll(AGENT_CONTEXT_VAR_PATTERN)
|
|
for (const match of matches) {
|
|
const agentNodeId = getAgentNodeIdFromContextVar(match[0])
|
|
if (!agentNodeId)
|
|
continue
|
|
const entryKey = `${paramKey}:${agentNodeId}`
|
|
if (seen.has(entryKey))
|
|
continue
|
|
seen.add(entryKey)
|
|
entries.push({
|
|
agentNodeId,
|
|
paramKey,
|
|
extractorNodeId: param?.nested_node_config?.extractor_node_id
|
|
|| (param?.type === VarType.nested_node ? `${id}_ext_${paramKey}` : undefined),
|
|
})
|
|
}
|
|
})
|
|
return entries
|
|
}, [data.tool_parameters, id])
|
|
|
|
const referenceItems = useMemo(() => {
|
|
if (!nestedNodeEntries.length)
|
|
return []
|
|
|
|
const getNodeWarning = (node?: WorkflowNode) => {
|
|
if (!node)
|
|
return true
|
|
const validator = nodesMetaDataMap?.[node.data.type as BlockEnum]?.checkValid
|
|
if (!validator)
|
|
return false
|
|
let moreDataForCheckValid: AgentCheckValidContext | undefined
|
|
if (node.data.type === BlockEnum.Agent) {
|
|
const agentData = node.data as AgentNodeType
|
|
const isReadyForCheckValid = !!strategyProviders
|
|
const provider = strategyProviders?.find(provider => provider.declaration.identity.name === agentData.agent_strategy_provider_name)
|
|
const strategy = provider?.declaration.strategies?.find(s => s.identity.name === agentData.agent_strategy_name)
|
|
moreDataForCheckValid = {
|
|
provider,
|
|
strategy,
|
|
language,
|
|
isReadyForCheckValid,
|
|
}
|
|
}
|
|
const { errorMessage } = validator(node.data, t, moreDataForCheckValid)
|
|
return Boolean(errorMessage)
|
|
}
|
|
|
|
return nestedNodeEntries.map(({ agentNodeId, extractorNodeId, paramKey }) => {
|
|
const agentNode = nodesById[agentNodeId]
|
|
const agentLabel = `@${agentNode?.data.title || agentNodeId}`
|
|
const agentWarning = getNodeWarning(agentNode)
|
|
|
|
const extractorWarning = extractorNodeId
|
|
? getNodeWarning(nodesById[extractorNodeId])
|
|
: false
|
|
const hasWarning = agentWarning || extractorWarning
|
|
return {
|
|
key: `${paramKey}-${agentNodeId}-${extractorNodeId || 'no-extractor'}`,
|
|
label: agentLabel,
|
|
type: BlockEnum.Agent,
|
|
hasWarning,
|
|
}
|
|
})
|
|
}, [nestedNodeEntries, nodesById, nodesMetaDataMap, strategyProviders, language, t])
|
|
|
|
const hasConfigs = toolConfigs.length > 0
|
|
const hasReferences = referenceItems.length > 0
|
|
|
|
if (!showInstallButton && !hasConfigs && !hasReferences)
|
|
return null
|
|
|
|
return (
|
|
<div className="relative mb-1 px-3 py-1">
|
|
{showInstallButton && (
|
|
<div className="pointer-events-auto absolute right-3 top-[-32px] z-40">
|
|
<InstallPluginButton
|
|
size="small"
|
|
className="!font-medium !text-text-accent"
|
|
extraIdentifiers={[
|
|
data.plugin_id,
|
|
data.provider_id,
|
|
data.provider_name,
|
|
].filter(Boolean) as string[]}
|
|
uniqueIdentifier={uniqueIdentifier!}
|
|
onSuccess={onInstallSuccess}
|
|
/>
|
|
</div>
|
|
)}
|
|
{hasConfigs && (
|
|
<div className="space-y-0.5" aria-disabled={shouldDim}>
|
|
{toolConfigs.map((key, index) => (
|
|
<div key={index} className="flex h-6 items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary">
|
|
<div title={key} className="max-w-[100px] shrink-0 truncate text-xs font-medium uppercase text-text-tertiary">
|
|
{key}
|
|
</div>
|
|
{typeof tool_configurations[key].value === 'string' && (
|
|
<div title={tool_configurations[key].value} className="w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary">
|
|
{paramSchemas?.find(i => i.name === key)?.type === FormTypeEnum.secretInput ? '********' : tool_configurations[key].value}
|
|
</div>
|
|
)}
|
|
{typeof tool_configurations[key].value === 'number' && (
|
|
<div title={Number.isNaN(tool_configurations[key].value) ? '' : tool_configurations[key].value} className="w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary">
|
|
{Number.isNaN(tool_configurations[key].value) ? '' : tool_configurations[key].value}
|
|
</div>
|
|
)}
|
|
{typeof tool_configurations[key] !== 'string' && tool_configurations[key]?.type === FormTypeEnum.modelSelector && (
|
|
<div title={tool_configurations[key].model} className="w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary">
|
|
{tool_configurations[key].model}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
{hasReferences && (
|
|
<div className={cn('space-y-0.5', hasConfigs && 'mt-1')} aria-disabled={shouldDim}>
|
|
{referenceItems.map(item => (
|
|
<div
|
|
key={item.key}
|
|
className={cn(
|
|
'flex h-6 items-center justify-between space-x-1 rounded-md border px-1 text-xs font-normal text-text-secondary',
|
|
item.hasWarning
|
|
? 'border-text-warning-secondary bg-components-badge-status-light-warning-halo'
|
|
: 'border-transparent bg-workflow-block-parma-bg',
|
|
)}
|
|
>
|
|
<div className="flex min-w-0 items-center gap-1">
|
|
<BlockIcon
|
|
className="shrink-0"
|
|
type={item.type}
|
|
size="xs"
|
|
/>
|
|
<span title={item.label} className="truncate text-text-secondary system-xs-medium">
|
|
{item.label}
|
|
</span>
|
|
</div>
|
|
{item.hasWarning && (
|
|
<AlertTriangle className="h-3.5 w-3.5 text-text-warning-secondary" />
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default React.memo(Node)
|