From 3c953cb0efd4465f092ad43ec95eae99becea022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AF=8C=E5=AE=9D?= <74889154+tinet-dongfb@users.noreply.github.com> Date: Wed, 14 May 2025 10:17:15 +0800 Subject: [PATCH] fix:#18447:When variables in the workflow are deleted or modified, it is impossible to visually identify subsequent node errors (#18554) Co-authored-by: crazywoola <427733928@qq.com> --- web/app/components/workflow/constants.ts | 16 ++++ .../components/workflow/header/checklist.tsx | 10 +++ .../workflow/hooks/use-checklist.ts | 58 +++++++++++++- .../nodes/_base/components/variable/utils.ts | 2 +- .../workflow/nodes/answer/default.ts | 16 +++- .../workflow/nodes/assigner/default.ts | 20 ++++- .../components/workflow/nodes/code/default.ts | 16 +++- .../nodes/document-extractor/default.ts | 16 +++- .../components/workflow/nodes/http/default.ts | 54 ++++++++++++- .../workflow/nodes/if-else/default.ts | 37 +++++++++ .../workflow/nodes/iteration/default.ts | 16 +++- .../nodes/knowledge-retrieval/default.ts | 16 +++- .../workflow/nodes/list-operator/default.ts | 15 +++- .../components/workflow/nodes/llm/default.ts | 35 ++++++++- .../nodes/parameter-extractor/default.ts | 27 ++++++- .../nodes/question-classifier/default.ts | 27 ++++++- .../nodes/template-transform/default.ts | 16 +++- .../components/workflow/nodes/tool/default.ts | 32 +++++++- .../nodes/variable-assigner/default.ts | 14 ++++ web/app/components/workflow/types.ts | 1 + .../workflow/utils/workflow-init.spec.ts | 12 +++ web/app/components/workflow/utils/workflow.ts | 76 ++++++++++++++++++- web/config/index.ts | 2 + web/i18n/en-US/workflow.ts | 3 + web/i18n/ja-JP/workflow.ts | 3 + web/i18n/zh-Hans/workflow.ts | 3 + 26 files changed, 522 insertions(+), 21 deletions(-) diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index cdfd963cfa..1b54add0ce 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -31,6 +31,7 @@ type NodesExtraData = { getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[] getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[] checkValid: any + checkVarValid?: any } export const NODES_EXTRA_DATA: Record = { [BlockEnum.Start]: { @@ -59,6 +60,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: AnswerDefault.getAvailablePrevNodes, getAvailableNextNodes: AnswerDefault.getAvailableNextNodes, checkValid: AnswerDefault.checkValid, + checkVarValid: AnswerDefault.checkVarValid, }, [BlockEnum.LLM]: { author: 'Dify', @@ -68,6 +70,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: LLMDefault.getAvailablePrevNodes, getAvailableNextNodes: LLMDefault.getAvailableNextNodes, checkValid: LLMDefault.checkValid, + checkVarValid: LLMDefault.checkVarValid, }, [BlockEnum.KnowledgeRetrieval]: { author: 'Dify', @@ -77,6 +80,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: KnowledgeRetrievalDefault.getAvailablePrevNodes, getAvailableNextNodes: KnowledgeRetrievalDefault.getAvailableNextNodes, checkValid: KnowledgeRetrievalDefault.checkValid, + checkVarValid: KnowledgeRetrievalDefault.checkVarValid, }, [BlockEnum.IfElse]: { author: 'Dify', @@ -86,6 +90,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: IfElseDefault.getAvailablePrevNodes, getAvailableNextNodes: IfElseDefault.getAvailableNextNodes, checkValid: IfElseDefault.checkValid, + checkVarValid: IfElseDefault.checkVarValid, }, [BlockEnum.Iteration]: { author: 'Dify', @@ -95,6 +100,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: IterationDefault.getAvailablePrevNodes, getAvailableNextNodes: IterationDefault.getAvailableNextNodes, checkValid: IterationDefault.checkValid, + checkVarValid: IterationDefault.checkVarValid, }, [BlockEnum.IterationStart]: { author: 'Dify', @@ -140,6 +146,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: CodeDefault.getAvailablePrevNodes, getAvailableNextNodes: CodeDefault.getAvailableNextNodes, checkValid: CodeDefault.checkValid, + checkVarValid: CodeDefault.checkVarValid, }, [BlockEnum.TemplateTransform]: { author: 'Dify', @@ -149,6 +156,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: TemplateTransformDefault.getAvailablePrevNodes, getAvailableNextNodes: TemplateTransformDefault.getAvailableNextNodes, checkValid: TemplateTransformDefault.checkValid, + checkVarValid: TemplateTransformDefault.checkVarValid, }, [BlockEnum.QuestionClassifier]: { author: 'Dify', @@ -158,6 +166,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: QuestionClassifierDefault.getAvailablePrevNodes, getAvailableNextNodes: QuestionClassifierDefault.getAvailableNextNodes, checkValid: QuestionClassifierDefault.checkValid, + checkVarValid: QuestionClassifierDefault.checkVarValid, }, [BlockEnum.HttpRequest]: { author: 'Dify', @@ -167,6 +176,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: HttpRequestDefault.getAvailablePrevNodes, getAvailableNextNodes: HttpRequestDefault.getAvailableNextNodes, checkValid: HttpRequestDefault.checkValid, + checkVarValid: HttpRequestDefault.checkVarValid, }, [BlockEnum.VariableAssigner]: { author: 'Dify', @@ -185,6 +195,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: AssignerDefault.getAvailablePrevNodes, getAvailableNextNodes: AssignerDefault.getAvailableNextNodes, checkValid: AssignerDefault.checkValid, + checkVarValid: AssignerDefault.checkVarValid, }, [BlockEnum.VariableAggregator]: { author: 'Dify', @@ -194,6 +205,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: VariableAssignerDefault.getAvailablePrevNodes, getAvailableNextNodes: VariableAssignerDefault.getAvailableNextNodes, checkValid: VariableAssignerDefault.checkValid, + checkVarValid: VariableAssignerDefault.checkVarValid, }, [BlockEnum.ParameterExtractor]: { author: 'Dify', @@ -203,6 +215,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: ParameterExtractorDefault.getAvailablePrevNodes, getAvailableNextNodes: ParameterExtractorDefault.getAvailableNextNodes, checkValid: ParameterExtractorDefault.checkValid, + checkVarValid: ParameterExtractorDefault.checkVarValid, }, [BlockEnum.Tool]: { author: 'Dify', @@ -212,6 +225,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: ToolDefault.getAvailablePrevNodes, getAvailableNextNodes: ToolDefault.getAvailableNextNodes, checkValid: ToolDefault.checkValid, + checkVarValid: ToolDefault.checkVarValid, }, [BlockEnum.DocExtractor]: { author: 'Dify', @@ -221,6 +235,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: DocExtractorDefault.getAvailablePrevNodes, getAvailableNextNodes: DocExtractorDefault.getAvailableNextNodes, checkValid: DocExtractorDefault.checkValid, + checkVarValid: DocExtractorDefault.checkVarValid, }, [BlockEnum.ListFilter]: { author: 'Dify', @@ -230,6 +245,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: ListFilterDefault.getAvailablePrevNodes, getAvailableNextNodes: ListFilterDefault.getAvailableNextNodes, checkValid: ListFilterDefault.checkValid, + checkVarValid: ListFilterDefault.checkVarValid, }, [BlockEnum.Agent]: { author: 'Dify', diff --git a/web/app/components/workflow/header/checklist.tsx b/web/app/components/workflow/header/checklist.tsx index 9da16c59c6..374726cbeb 100644 --- a/web/app/components/workflow/header/checklist.tsx +++ b/web/app/components/workflow/header/checklist.tsx @@ -140,6 +140,16 @@ const WorkflowChecklist = ({ ) } + { + node.varErrorMessage?.map((errorMessage: string) => ( +
+
+ + {errorMessage} +
+
+ )) + } )) diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index c1b0189b8b..4b0fcca192 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -15,6 +15,7 @@ import { useStore } from '../store' import { getToolCheckParams, getValidTreeNodes, + transformStartNodeVariables, } from '../utils' import { CUSTOM_NODE, @@ -45,6 +46,9 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { const { data: strategyProviders } = useStrategyProviders() const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail) + const chatVarList = useStore(s => s.conversationVariables) + const environmentVariables = useStore(s => s.environmentVariables) + const getCheckData = useCallback((data: CommonNodeType<{}>) => { let checkData = data if (data.type === BlockEnum.KnowledgeRetrieval) { @@ -64,7 +68,10 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { const needWarningNodes = useMemo(() => { const list = [] - const { validNodes } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges) + const { validNodes } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges, true) + + const allVariablesMap = transformStartNodeVariables(chatVarList, environmentVariables) + const errMessageMap = new Map() for (let i = 0; i < nodes.length; i++) { const node = nodes[i] @@ -110,8 +117,32 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { toolIcon, unConnected: !validNodes.find(n => n.id === node.id), errorMessage, + varErrorMessage: [], }) } + errMessageMap.set(node.id, list[list.length - 1]) + if (nodesExtraData[node.data.type as BlockEnum].checkVarValid) { + const { errorMessage: varErrorMessages } = nodesExtraData[node.data.type as BlockEnum].checkVarValid(node.data, { ...allVariablesMap, ...node._parentOutputVarMap }, t) + + if (varErrorMessages?.length) { + const errMessage = errMessageMap.get(node.id) + if (errMessage) { + errMessage.varErrorMessage = varErrorMessages + } + else { + list.push({ + id: node.id, + type: node.data.type, + title: node.data.title, + toolIcon, + unConnected: !validNodes.find(n => n.id === node.id), + errorMessage: '', + varErrorMessage: varErrorMessages, + }) + errMessageMap.set(node.id, list[list.length - 1]) + } + } + } } } @@ -133,8 +164,13 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { }) } + for (let i = 0; i < validNodes.length; i++) { + const node = validNodes[i] + delete node._parentOutputVarMap + } + return list - }, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData]) + }, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData, chatVarList, environmentVariables]) return needWarningNodes } @@ -153,6 +189,9 @@ export const useChecklistBeforePublish = () => { const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail) const updateTime = useRef(0) + const chatVarList = useStore(s => s.conversationVariables) + const environmentVariables = useStore(s => s.environmentVariables) + const getCheckData = useCallback((data: CommonNodeType<{}>, datasets: DataSet[]) => { let checkData = data if (data.type === BlockEnum.KnowledgeRetrieval) { @@ -183,12 +222,15 @@ export const useChecklistBeforePublish = () => { const { validNodes, maxDepth, - } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges) + } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges, true) if (maxDepth > MAX_TREE_DEPTH) { notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEPTH }) }) return false } + + const allVariablesMap = transformStartNodeVariables(chatVarList, environmentVariables) + // Before publish, we need to fetch datasets detail, in case of the settings of datasets have been changed const knowledgeRetrievalNodes = nodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval) const allDatasetIds = knowledgeRetrievalNodes.reduce((acc, node) => { @@ -239,6 +281,14 @@ export const useChecklistBeforePublish = () => { notify({ type: 'error', message: `[${node.data.title}] ${t('workflow.common.needConnectTip')}` }) return false } + + if (nodesExtraData[node.data.type as BlockEnum].checkVarValid) { + const { errorMessage: varErrorMessage } = nodesExtraData[node.data.type as BlockEnum].checkVarValid(node.data, { ...allVariablesMap, ...node._parentOutputVarMap }, t) + if (varErrorMessage?.length) { + notify({ type: 'error', message: `[${node.data.title}] ${varErrorMessage[0]}` }) + return false + } + } } if (isChatMode && !nodes.find(node => node.data.type === BlockEnum.Answer)) { @@ -252,7 +302,7 @@ export const useChecklistBeforePublish = () => { } return true - }, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData]) + }, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, chatVarList, environmentVariables]) return { handleCheckBeforePublish, diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index f804cf6902..751d438d68 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -164,7 +164,7 @@ const findExceptVarInObject = (obj: any, filterVar: (payload: Var, selector: Val return res } -const formatItem = ( +export const formatItem = ( item: any, isChatMode: boolean, filterVar: (payload: Var, selector: ValueSelector) => boolean, diff --git a/web/app/components/workflow/nodes/answer/default.ts b/web/app/components/workflow/nodes/answer/default.ts index 4ff6e49d7e..5dff505007 100644 --- a/web/app/components/workflow/nodes/answer/default.ts +++ b/web/app/components/workflow/nodes/answer/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByText } from '../../utils/workflow' import type { AnswerNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -31,6 +32,19 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: AnswerNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const answer_warnings = getNotExistVariablesByText(payload.answer || '', varMap) + if (answer_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.answer.answer')} ${t('workflow.common.referenceVar')}${answer_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...answer_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/assigner/default.ts b/web/app/components/workflow/nodes/assigner/default.ts index 6341305576..32dac73b36 100644 --- a/web/app/components/workflow/nodes/assigner/default.ts +++ b/web/app/components/workflow/nodes/assigner/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import { type AssignerNodeType, WriteMode } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' const i18nPrefix = 'workflow.errorMsg' @@ -47,6 +48,23 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + + checkVarValid(payload: AssignerNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + const variables_warnings = getNotExistVariablesByArray(payload.items.map(item => item.variable_selector ?? []) ?? [], varMap) + if (variables_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.assigner.assignedVariable')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`) + + const value_warnings = getNotExistVariablesByArray(payload.items.map(item => item.value ?? []) ?? [], varMap) + if (value_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.assigner.setVariable')} ${t('workflow.common.referenceVar')}${value_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...variables_warnings, ...value_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/code/default.ts b/web/app/components/workflow/nodes/code/default.ts index 5f90c18716..320cac59b4 100644 --- a/web/app/components/workflow/nodes/code/default.ts +++ b/web/app/components/workflow/nodes/code/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import { CodeLanguage, type CodeNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -37,7 +38,20 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: CodeNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + const variables_selector = payload.variables.map(v => v.value_selector) + const variables_selector_warnings = getNotExistVariablesByArray(variables_selector, varMap) + if (variables_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.code.inputVars')} ${t('workflow.common.referenceVar')}${variables_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: variables_selector_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/document-extractor/default.ts b/web/app/components/workflow/nodes/document-extractor/default.ts index e141844d19..57eb58b168 100644 --- a/web/app/components/workflow/nodes/document-extractor/default.ts +++ b/web/app/components/workflow/nodes/document-extractor/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import type { DocExtractorNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' const i18nPrefix = 'workflow.errorMsg' @@ -31,6 +32,19 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: DocExtractorNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + + const variables_warnings = getNotExistVariablesByArray([payload.variable_selector], varMap) + if (variables_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.docExtractor.inputVar')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: variables_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/http/default.ts b/web/app/components/workflow/nodes/http/default.ts index 1bd584eeb9..1c027a3fec 100644 --- a/web/app/components/workflow/nodes/http/default.ts +++ b/web/app/components/workflow/nodes/http/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' import { AuthorizationType, BodyType, Method } from './types' import type { BodyPayload, HttpNodeType } from './types' import { @@ -50,8 +51,8 @@ const nodeDefault: NodeDefault = { errorMessages = t('workflow.errorMsg.fieldRequired', { field: t('workflow.nodes.http.api') }) if (!errorMessages - && payload.body.type === BodyType.binary - && ((!(payload.body.data as BodyPayload)[0]?.file) || (payload.body.data as BodyPayload)[0]?.file?.length === 0) + && payload.body.type === BodyType.binary + && ((!(payload.body.data as BodyPayload)[0]?.file) || (payload.body.data as BodyPayload)[0]?.file?.length === 0) ) errorMessages = t('workflow.errorMsg.fieldRequired', { field: t('workflow.nodes.http.binaryFileVariable') }) @@ -60,6 +61,53 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: HttpNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + const url_warnings = getNotExistVariablesByText(payload.url, varMap) + if (url_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.http.api')} ${t('workflow.common.referenceVar')}${url_warnings.join('、')}${t('workflow.common.noExist')}`) + + const headers_warnings = getNotExistVariablesByText(payload.headers, varMap) + if (headers_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.http.headers')} ${t('workflow.common.referenceVar')}${headers_warnings.join('、')}${t('workflow.common.noExist')}`) + + const params_warnings = getNotExistVariablesByText(payload.params, varMap) + if (params_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.http.params')} ${t('workflow.common.referenceVar')}${params_warnings.join('、')}${t('workflow.common.noExist')}`) + + const body_warnings: string[] = [] + + if ([BodyType.binary].includes(payload.body.type)) { + const body_data = payload.body.data as BodyPayload + body_data.forEach((item) => { + const key_warnings = getNotExistVariablesByText(item.key || '', varMap) + if (key_warnings.length) + body_warnings.push(...key_warnings) + const warnings = getNotExistVariablesByArray([item.file || []], varMap) + if (warnings.length) + body_warnings.push(...warnings) + }) + } + else { + const body_data = payload.body.data as BodyPayload + body_data.forEach((item) => { + const key_warnings = getNotExistVariablesByText(item.key || '', varMap) + if (key_warnings.length) + body_warnings.push(...key_warnings) + const value_warnings = getNotExistVariablesByText(item.value || '', varMap) + if (value_warnings.length) + body_warnings.push(...value_warnings) + }) + } + if (body_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.http.body')} ${t('workflow.common.referenceVar')}${body_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...url_warnings, ...headers_warnings, ...params_warnings, ...body_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/if-else/default.ts b/web/app/components/workflow/nodes/if-else/default.ts index 1be80592e5..53c139b4c0 100644 --- a/web/app/components/workflow/nodes/if-else/default.ts +++ b/web/app/components/workflow/nodes/if-else/default.ts @@ -1,4 +1,6 @@ +import type { Var } from '../../types' import { BlockEnum, type NodeDefault } from '../../types' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' import { type IfElseNodeType, LogicalOperator } from './types' import { isEmptyRelatedOperator } from './utils' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -75,6 +77,41 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: IfElseNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const condition_variable_selector_warnings: string[] = [] + const condition_value_warnings: string[] = [] + payload.cases.forEach((caseItem) => { + caseItem.conditions.forEach((condition) => { + if (!condition.variable_selector) + return + const selector_warnings = getNotExistVariablesByArray([condition.variable_selector], varMap) + if (selector_warnings.length) + condition_variable_selector_warnings.push(...selector_warnings) + const value_warnings = Array.isArray(condition.value) ? getNotExistVariablesByArray([condition.value], varMap) : getNotExistVariablesByText(condition.value, varMap) + if (value_warnings.length) + condition_value_warnings.push(...value_warnings) + condition.sub_variable_condition?.conditions.forEach((subCondition) => { + const sub_variable_value_warnings = Array.isArray(subCondition.value) ? getNotExistVariablesByArray([subCondition.value], varMap) : getNotExistVariablesByText(subCondition.value, varMap) + if (sub_variable_value_warnings.length) + condition_value_warnings.push(...sub_variable_value_warnings) + }) + }) + }) + + if (condition_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.ifElse.condition')} ${t('workflow.common.referenceVar')}${condition_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + if (condition_value_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.ifElse.enterValue')} ${t('workflow.common.referenceVar')}${condition_value_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: condition_variable_selector_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/iteration/default.ts b/web/app/components/workflow/nodes/iteration/default.ts index 0ef8382abe..392c64137e 100644 --- a/web/app/components/workflow/nodes/iteration/default.ts +++ b/web/app/components/workflow/nodes/iteration/default.ts @@ -1,5 +1,6 @@ import { BlockEnum, ErrorHandleMode } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import type { IterationNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, @@ -58,6 +59,19 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: IterationNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + + const iterator_selector_warnings = getNotExistVariablesByArray([payload.iterator_selector], varMap) + if (iterator_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.iteration.input')} ${t('workflow.common.referenceVar')}${iterator_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: iterator_selector_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/default.ts b/web/app/components/workflow/nodes/knowledge-retrieval/default.ts index 09da8dd789..955e30d90f 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/default.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import type { KnowledgeRetrievalNodeType } from './types' import { checkoutRerankModelConfigedInRetrievalSettings } from './utils' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -52,6 +53,19 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: KnowledgeRetrievalNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const query_variable_selector_warnings = getNotExistVariablesByArray([payload.query_variable_selector], varMap) + if (query_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.knowledgeRetrieval.queryVariable')} ${t('workflow.common.referenceVar')}${query_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...query_variable_selector_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/list-operator/default.ts b/web/app/components/workflow/nodes/list-operator/default.ts index 0256cb8673..c2d15edc58 100644 --- a/web/app/components/workflow/nodes/list-operator/default.ts +++ b/web/app/components/workflow/nodes/list-operator/default.ts @@ -1,5 +1,6 @@ import { BlockEnum, VarType } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import { comparisonOperatorNotRequireValue } from '../if-else/utils' import { type ListFilterNodeType, OrderBy } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -60,6 +61,18 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: ListFilterNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const variable_warnings = getNotExistVariablesByArray([payload.variable], varMap) + if (variable_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.listFilter.inputVar')} ${t('workflow.common.referenceVar')}${variable_warnings.join('、')}${t('workflow.common.noExist')}`) + return { + isValid: true, + warning_vars: variable_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/llm/default.ts b/web/app/components/workflow/nodes/llm/default.ts index 92377f74b8..5625c85480 100644 --- a/web/app/components/workflow/nodes/llm/default.ts +++ b/web/app/components/workflow/nodes/llm/default.ts @@ -1,5 +1,7 @@ -import { BlockEnum, EditionType } from '../../types' +import type { Var } from '../../types' +import { BlockEnum, EditionType, VarType } from '../../types' import { type NodeDefault, type PromptItem, PromptRole } from '../../types' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' import type { LLMNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -86,6 +88,37 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: LLMNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + const prompt_templates_warnings: string[] = [] + if (payload.context?.enabled && payload.context?.variable_selector?.length) { + const context_variable_selector_warnings = getNotExistVariablesByArray([payload.context.variable_selector], varMap) + if (context_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.llm.context')} ${t('workflow.common.referenceVar')}${context_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + } + const prompt_templates = Array.isArray(payload.prompt_template) ? payload.prompt_template : [payload.prompt_template] as PromptItem[] + prompt_templates.forEach((v) => { + prompt_templates_warnings.push(...getNotExistVariablesByText(v.text, { context: { variable: 'context', type: VarType.string }, ...varMap })) + }) + if (prompt_templates_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.llm.prompt')} ${t('workflow.common.referenceVar')}${prompt_templates_warnings.join('、')}${t('workflow.common.noExist')}`) + + const memory_query_prompt_template_warnings = getNotExistVariablesByText(payload.memory?.query_prompt_template || '', varMap) + if (memory_query_prompt_template_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.common.memories.title')}USER ${t('workflow.common.referenceVar')}${memory_query_prompt_template_warnings.join('、')}${t('workflow.common.noExist')}`) + + if (payload.vision?.enabled && payload.vision?.configs?.variable_selector?.length) { + const vision_variable_selector_warnings = getNotExistVariablesByArray([payload.vision.configs.variable_selector], varMap) + if (vision_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.llm.vision')} ${t('workflow.common.referenceVar')}${vision_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + } + + return { + isValid: true, + warning_vars: [...prompt_templates_warnings, ...memory_query_prompt_template_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/parameter-extractor/default.ts b/web/app/components/workflow/nodes/parameter-extractor/default.ts index 0e3b707d30..5ab5384502 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/default.ts +++ b/web/app/components/workflow/nodes/parameter-extractor/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' import { type ParameterExtractorNodeType, ReasoningModeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' const i18nPrefix = 'workflow' @@ -64,6 +65,30 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: ParameterExtractorNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + + const variables_warnings = getNotExistVariablesByArray([payload.query], varMap) + if (variables_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.parameterExtractor.inputVar')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`) + + let vision_variable_warnings: string[] = [] + if (payload.vision?.configs?.variable_selector?.length) { + vision_variable_warnings = getNotExistVariablesByArray([payload.vision.configs.variable_selector], varMap) + if (vision_variable_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.llm.vision')} ${t('workflow.common.referenceVar')}${vision_variable_warnings.join('、')}${t('workflow.common.noExist')}`) + } + + const instruction_warnings = getNotExistVariablesByText(payload.instruction, varMap) + if (instruction_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.parameterExtractor.instruction')} ${t('workflow.common.referenceVar')}${instruction_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...variables_warnings, ...vision_variable_warnings, ...instruction_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/question-classifier/default.ts b/web/app/components/workflow/nodes/question-classifier/default.ts index 2729c53f29..70410a5f18 100644 --- a/web/app/components/workflow/nodes/question-classifier/default.ts +++ b/web/app/components/workflow/nodes/question-classifier/default.ts @@ -1,5 +1,6 @@ -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' import { BlockEnum } from '../../types' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' import type { QuestionClassifierNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -71,6 +72,30 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: QuestionClassifierNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const query_variable_selector_warnings = getNotExistVariablesByArray([payload.query_variable_selector], varMap) + if (query_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.questionClassifiers.inputVars')} ${t('workflow.common.referenceVar')}${query_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + let vision_variable_selector_warnings: string[] = [] + if (payload.vision?.configs?.variable_selector?.length) { + vision_variable_selector_warnings = getNotExistVariablesByArray([payload.vision?.configs?.variable_selector], varMap) + if (vision_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.llm.vision')} ${t('workflow.common.referenceVar')}${vision_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + } + + const instruction_warnings: string[] = getNotExistVariablesByText(payload.instruction, varMap) + if (instruction_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.questionClassifiers.advancedSetting')}-${t('workflow.nodes.questionClassifiers.instruction')} ${t('workflow.common.referenceVar')}${instruction_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...query_variable_selector_warnings, ...vision_variable_selector_warnings, ...instruction_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/template-transform/default.ts b/web/app/components/workflow/nodes/template-transform/default.ts index c698680342..25e5d7f961 100644 --- a/web/app/components/workflow/nodes/template-transform/default.ts +++ b/web/app/components/workflow/nodes/template-transform/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import type { TemplateTransformNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' const i18nPrefix = 'workflow.errorMsg' @@ -33,6 +34,19 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: TemplateTransformNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const variables_selector = payload.variables.map(v => v.value_selector) + const variables_selector_warnings = getNotExistVariablesByArray(variables_selector, varMap) + if (variables_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.templateTransform.inputVars')} ${t('workflow.common.referenceVar')}${variables_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/tool/default.ts b/web/app/components/workflow/nodes/tool/default.ts index f245929684..782c575c8e 100644 --- a/web/app/components/workflow/nodes/tool/default.ts +++ b/web/app/components/workflow/nodes/tool/default.ts @@ -1,8 +1,9 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' import type { ToolNodeType } from './types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' const i18nPrefix = 'workflow.errorMsg' @@ -63,6 +64,35 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: ToolNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + const tool_parametersMap = payload.tool_parameters + const tool_parameters_array = Object.values(tool_parametersMap) + const tool_parameters_warnings: string[] = [] + tool_parameters_array?.forEach((item) => { + if (!item.value) + return + if (Array.isArray(item.value)) { + const warnings = getNotExistVariablesByArray([item.value], varMap) + if (warnings.length) + tool_parameters_warnings.push(...warnings) + return + } + if (typeof item.value === 'string') { + const warnings = getNotExistVariablesByText(item.value, varMap) + if (warnings.length) + tool_parameters_warnings.push(...warnings) + } + }) + if (tool_parameters_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.tool.inputVars')} ${t('workflow.common.referenceVar')}${tool_parameters_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: tool_parameters_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/variable-assigner/default.ts b/web/app/components/workflow/nodes/variable-assigner/default.ts index 60c7c27969..8482406602 100644 --- a/web/app/components/workflow/nodes/variable-assigner/default.ts +++ b/web/app/components/workflow/nodes/variable-assigner/default.ts @@ -1,5 +1,7 @@ +import type { Var } from '../../types' import { type NodeDefault, VarType } from '../../types' import { BlockEnum } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import type { VariableAssignerNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -54,6 +56,18 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: VariableAssignerNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + const variables_warnings = getNotExistVariablesByArray(payload.variables ?? [], varMap) + if (variables_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.variableAssigner.title')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: variables_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 884bdfbd10..1e6186924e 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -297,6 +297,7 @@ export type NodeDefault = { getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[] getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[] checkValid: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean; errorMessage?: string } + checkVarValid?: (payload: T, varMap: Record, t: any,) => { isValid: boolean; errorMessage?: string[] } } export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void diff --git a/web/app/components/workflow/utils/workflow-init.spec.ts b/web/app/components/workflow/utils/workflow-init.spec.ts index 8b7bdfaa92..d96e54a76a 100644 --- a/web/app/components/workflow/utils/workflow-init.spec.ts +++ b/web/app/components/workflow/utils/workflow-init.spec.ts @@ -5,6 +5,18 @@ import type { } from '@/app/components/workflow/types' import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' +jest.mock('ky', () => ({ + __esModule: true, + default: { + create: jest.fn(), + }, +})) + +jest.mock('lodash-es/groupBy', () => ({ + __esModule: true, + default: jest.fn(), +})) + describe('preprocessNodesAndEdges', () => { it('process nodes without iteration node or loop node should return origin nodes and edges.', () => { const nodes = [ diff --git a/web/app/components/workflow/utils/workflow.ts b/web/app/components/workflow/utils/workflow.ts index 88c31f09b5..739908646a 100644 --- a/web/app/components/workflow/utils/workflow.ts +++ b/web/app/components/workflow/utils/workflow.ts @@ -10,14 +10,20 @@ import { uniqBy, } from 'lodash-es' import type { + ConversationVariable, Edge, + EnvironmentVariable, Node, + Var, } from '../types' import { BlockEnum, } from '../types' import type { IterationNodeType } from '../nodes/iteration/types' import type { LoopNodeType } from '../nodes/loop/types' +import { VAR_REGEX_TEXT } from '@/config' +import { formatItem } from '../nodes/_base/components/variable/utils' +import type { StructuredOutput } from '../nodes/llm/types' export const canRunBySingle = (nodeType: BlockEnum) => { return nodeType === BlockEnum.LLM @@ -86,7 +92,17 @@ export const getNodesConnectedSourceOrTargetHandleIdsMap = (changes: ConnectedSo return nodesConnectedSourceOrTargetHandleIdsMap } -export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => { +function getParentOutputVarMap(item: Var, path: string, varMap: Record) { + if (!item.children || (Array.isArray(item.children) && !item.children.length) || ((item.children as StructuredOutput).schema)) + return + (item.children as Var[]).forEach((child) => { + const newPath = `${path}.${child.variable}` + varMap[newPath] = child + getParentOutputVarMap(child, newPath, varMap) + }) +} + +export const getValidTreeNodes = (nodes: Node[], edges: Edge[], isCollectVar?: boolean) => { const startNode = nodes.find(node => node.data.type === BlockEnum.Start) if (!startNode) { @@ -109,6 +125,19 @@ export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => { outgoers.forEach((outgoer) => { list.push(outgoer) + if (isCollectVar) { + const nodeObj = formatItem(root, false, () => true) + const varMap = {} as Record + nodeObj.vars.forEach((item) => { + if (item.variable.startsWith('sys.')) + return + const newPath = `${nodeObj.nodeId}.${item.variable}` + varMap[newPath] = item + getParentOutputVarMap(item, newPath, varMap) + }) + outgoer._parentOutputVarMap = { ...(root._parentOutputVarMap ?? {}), ...varMap } + } + if (outgoer.data.type === BlockEnum.Iteration) list.push(...nodes.filter(node => node.parentId === outgoer.id)) if (outgoer.data.type === BlockEnum.Loop) @@ -327,3 +356,48 @@ export const getParallelInfo = (nodes: Node[], edges: Edge[], parentNodeId?: str export const hasErrorHandleNode = (nodeType?: BlockEnum) => { return nodeType === BlockEnum.LLM || nodeType === BlockEnum.Tool || nodeType === BlockEnum.HttpRequest || nodeType === BlockEnum.Code } + +export const transformStartNodeVariables = (chatVarList: ConversationVariable[], environmentVariables: EnvironmentVariable[]) => { + const variablesMap: Record = {} + chatVarList.forEach((variable) => { + variablesMap[`conversation.${variable.name}`] = variable + }) + environmentVariables.forEach((variable) => { + variablesMap[`env.${variable.name}`] = variable + }) + return variablesMap +} + +export const getNotExistVariablesByText = (text: string, varMap: Record) => { + const var_warnings: string[] = [] + text?.replace(VAR_REGEX_TEXT, (str, id_name) => { + if (id_name.startsWith('sys.')) + return str + if (varMap[id_name]) + return str + const arr = id_name.split('.') + arr.shift() + var_warnings.push(arr.join('.')) + return str + }) + return var_warnings +} + +export const getNotExistVariablesByArray = (array: string[][], varMap: Record) => { + if (!array.length) + return [] + const var_warnings: string[] = [] + array.forEach((item) => { + if (!item.length) + return + if (['sys'].includes(item[0])) + return + const var_warning = varMap[item.join('.')] + if (var_warning) + return + const arr = [...item] + arr.shift() + var_warnings.push(arr.join('.')) + }) + return var_warnings +} diff --git a/web/config/index.ts b/web/config/index.ts index 4d308807c5..4ddf30c2e4 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -282,6 +282,8 @@ Thought: {{agent_scratchpad}} export const VAR_REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_]\w{0,29}){1,10}#)\}\}/gi +export const VAR_REGEX_TEXT = /\{\{#([a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*)#\}\}/gi + export const resetReg = () => VAR_REGEX.lastIndex = 0 export let textGenerationTimeoutMs = 60000 diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index f7121a7a8a..009cfd4326 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -113,6 +113,8 @@ const translation = { addFailureBranch: 'Add Fail Branch', loadMore: 'Load More', noHistory: 'No History', + referenceVar: 'Reference Variable', + noExist: 'No such variable', }, env: { envPanelTitle: 'Environment Variables', @@ -596,6 +598,7 @@ const translation = { selectVariable: 'Select variable...', addSubVariable: 'Sub Variable', select: 'Select', + condition: 'Condition', }, variableAssigner: { title: 'Assign variables', diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index 5fc9a81875..46a20efa26 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -113,6 +113,8 @@ const translation = { addFailureBranch: '失敗ブランチを追加', loadMore: 'さらに読み込む', noHistory: '履歴がありません', + referenceVar: '参照変数', + noExist: '存在しません', }, env: { envPanelTitle: '環境変数', @@ -596,6 +598,7 @@ const translation = { }, select: '選ぶ', addSubVariable: 'サブ変数', + condition: '条件', }, variableAssigner: { title: '変数を代入する', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index ab77e0ef8d..74b948837e 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -113,6 +113,8 @@ const translation = { openInExplore: '在“探索”中打开', loadMore: '加载更多', noHistory: '没有历史版本', + referenceVar: '引用变量', + noExist: '不存在', }, env: { envPanelTitle: '环境变量', @@ -597,6 +599,7 @@ const translation = { selectVariable: '选择变量', addSubVariable: '添加子变量', select: '选择', + condition: '条件', }, variableAssigner: { title: '变量赋值',