From f50773b1e9e11bf9a02e517ba3d29b920b18a709 Mon Sep 17 00:00:00 2001 From: Mminamiyama Date: Fri, 4 Jul 2025 11:26:43 +0800 Subject: [PATCH] feat(workflow): add variable validation for code nodes - Introduce useNodesAvailableVarList hook to track available variables - Enhance code node validation to check variable references - Add debug logging for validation process --- .../workflow/hooks/use-checklist.ts | 14 +++- .../hooks/use-nodes-available-var-list.ts | 74 +++++++++++++++++++ .../components/workflow/nodes/code/default.ts | 24 +++++- 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 web/app/components/workflow/hooks/use-nodes-available-var-list.ts diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index 200bee1d26..075aebb9ba 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -33,6 +33,7 @@ import type { KnowledgeRetrievalNodeType } from '../nodes/knowledge-retrieval/ty import type { DataSet } from '@/models/datasets' import { fetchDatasets } from '@/service/datasets' import { MAX_TREE_DEPTH } from '@/config' +import useNodesAvailableVarList from './use-nodes-available-var-list' export const useChecklist = (nodes: Node[], edges: Edge[]) => { const { t } = useTranslation() @@ -45,6 +46,10 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { const { data: strategyProviders } = useStrategyProviders() const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail) + console.log('==========================nodes: ', nodes) + const map = useNodesAvailableVarList(nodes) + console.log('==========================map: ', map) + const getCheckData = useCallback((data: CommonNodeType<{}>) => { let checkData = data if (data.type === BlockEnum.KnowledgeRetrieval) { @@ -84,8 +89,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { if (provider_type === CollectionType.workflow) toolIcon = workflowTools.find(tool => tool.id === node.data.provider_id)?.icon } - - if (node.data.type === BlockEnum.Agent) { + else if (node.data.type === BlockEnum.Agent) { const data = node.data as AgentNodeType const isReadyForCheckValid = !!strategyProviders const provider = strategyProviders?.find(provider => provider.declaration.identity.name === data.agent_strategy_provider_name) @@ -97,6 +101,12 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { isReadyForCheckValid, } } + else { + moreDataForCheckValid = { + node, + ...map[node.id], + } + } if (node.type === CUSTOM_NODE) { const checkData = getCheckData(node.data) diff --git a/web/app/components/workflow/hooks/use-nodes-available-var-list.ts b/web/app/components/workflow/hooks/use-nodes-available-var-list.ts new file mode 100644 index 0000000000..761bdce6af --- /dev/null +++ b/web/app/components/workflow/hooks/use-nodes-available-var-list.ts @@ -0,0 +1,74 @@ +import { + useIsChatMode, + useWorkflow, + useWorkflowVariables, +} from '@/app/components/workflow/hooks' +import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' +type Params = { + onlyLeafNodeVar?: boolean + hideEnv?: boolean + hideChatVar?: boolean + filterVar: (payload: Var, selector: ValueSelector) => boolean + passedInAvailableNodes?: Node[] +} + +const getNodeInfo = (nodeId: string, nodes: Node[]) => { + const allNodes = nodes + const node = allNodes.find(n => n.id === nodeId) + const isInIteration = !!node?.data.isInIteration + const isInLoop = !!node?.data.isInLoop + const parentNodeId = node?.parentId + const parentNode = allNodes.find(n => n.id === parentNodeId) + return { + node, + isInIteration, + isInLoop, + parentNode, + } +} + +// TODO: loop type? +const useNodesAvailableVarList = (nodes: Node[], { + onlyLeafNodeVar, + filterVar, + hideEnv, + hideChatVar, + passedInAvailableNodes, +}: Params = { + onlyLeafNodeVar: false, + filterVar: () => true, + }) => { + const { getTreeLeafNodes, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() + const { getNodeAvailableVars } = useWorkflowVariables() + const isChatMode = useIsChatMode() + + const map: { [key: string ]: { availableVars: NodeOutPutVar[], availableNodes: Node[] } } = {} + + nodes.forEach((node) => { + const nodeId = node.id + const availableNodes = passedInAvailableNodes || (onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranchIncludeParent(nodeId)) + + const { + parentNode: iterationNode, + } = getNodeInfo(nodeId, nodes) + + const availableVars = getNodeAvailableVars({ + parentNode: iterationNode, + beforeNodes: availableNodes, + isChatMode, + filterVar, + hideEnv, + hideChatVar, + }) + + const result = { + node, + availableVars, + availableNodes, + } + map[nodeId] = result + }) + return map +} + +export default useNodesAvailableVarList diff --git a/web/app/components/workflow/nodes/code/default.ts b/web/app/components/workflow/nodes/code/default.ts index 5f90c18716..423ccc61fd 100644 --- a/web/app/components/workflow/nodes/code/default.ts +++ b/web/app/components/workflow/nodes/code/default.ts @@ -1,7 +1,8 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { Node, NodeDefault, NodeOutPutVar } from '../../types' import { CodeLanguage, type CodeNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' const i18nPrefix = 'workflow.errorMsg' @@ -22,7 +23,7 @@ const nodeDefault: NodeDefault = { const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS return nodes }, - checkValid(payload: CodeNodeType, t: any) { + checkValid(payload: CodeNodeType, t: any, moreDataForCheckValid: { node: Node, availableVars: NodeOutPutVar[], availableNodes: Node[] }) { let errorMessages = '' const { code, variables } = payload if (!errorMessages && variables.filter(v => !v.variable).length > 0) @@ -31,7 +32,24 @@ const nodeDefault: NodeDefault = { errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) }) if (!errorMessages && !code) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.code`) }) - + if (!errorMessages && moreDataForCheckValid) { + const availableVars = moreDataForCheckValid.availableVars + console.log('=======================节点名称:', moreDataForCheckValid.node.data.title) + console.log('=======================代码参数检查:', variables) + console.log('=======================可用变量:', moreDataForCheckValid.availableVars) + console.log('=======================可用节点:', moreDataForCheckValid.availableNodes) + console.log('=========================================================================================================') + variables.forEach((variable) => { + const isEnv = isENV(variable.value_selector) + const isConvVar = isConversationVar(variable.value_selector) + const isSysVar = isSystemVar(variable.value_selector) + if (!isEnv && !isConvVar && !isSysVar) { + const node = availableVars.find(v => v.nodeId === variable?.value_selector[0]) + if (!node) + errorMessages = t(`${i18nPrefix}.invalidVariable`) + } + }) + } return { isValid: !errorMessages, errorMessage: errorMessages,