mirror of
https://github.com/langgenius/dify.git
synced 2026-04-30 07:28:05 +08:00
feat: Improve error messages for missing workflow outputs
This commit is contained in:
@ -73,33 +73,32 @@ export const useSubGraphVariablesCheck = ({
|
||||
}
|
||||
}, [currentNodeId, nodesWithInspectVars])
|
||||
|
||||
const hasNullDependentOutputs = useCallback((vars?: ValueSelector[] | ValueSelector[][]) => {
|
||||
const getNullDependentOutput = useCallback((vars?: ValueSelector[] | ValueSelector[][]) => {
|
||||
if (!vars || vars.length === 0)
|
||||
return false
|
||||
return undefined
|
||||
|
||||
const isGroupedVars = Array.isArray(vars[0]) && Array.isArray((vars as ValueSelector[][])[0][0])
|
||||
const selectors = isGroupedVars ? (vars as ValueSelector[][]).flat() : (vars as ValueSelector[])
|
||||
const subGraphNodeIdSet = new Set(subGraphNodeIds)
|
||||
const details = selectors.map((selector) => {
|
||||
for (const selector of selectors) {
|
||||
const { found, value } = getInspectVarValueBySelector(selector)
|
||||
const valueType = value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value
|
||||
const isSubgraphOutput = subGraphNodeIdSet.has(selector[0])
|
||||
return {
|
||||
selector,
|
||||
found,
|
||||
valueType,
|
||||
isSubgraphOutput,
|
||||
}
|
||||
})
|
||||
const hasNull = details.some((item) => {
|
||||
if (!item.found)
|
||||
return item.isSubgraphOutput
|
||||
return item.valueType === 'null' || item.valueType === 'undefined'
|
||||
})
|
||||
return hasNull
|
||||
const isNull = !found
|
||||
? isSubgraphOutput
|
||||
: valueType === 'null' || valueType === 'undefined'
|
||||
if (isNull)
|
||||
return selector
|
||||
}
|
||||
return undefined
|
||||
}, [getInspectVarValueBySelector, subGraphNodeIds])
|
||||
|
||||
const hasNullDependentOutputs = useCallback((vars?: ValueSelector[] | ValueSelector[][]) => {
|
||||
return !!getNullDependentOutput(vars)
|
||||
}, [getNullDependentOutput])
|
||||
|
||||
return {
|
||||
hasNullDependentOutputs,
|
||||
getNullDependentOutput,
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import {
|
||||
useNodesSyncDraft,
|
||||
@ -141,7 +142,7 @@ const useLastRun = <T>({
|
||||
hasSetInspectVar,
|
||||
nodesWithInspectVars,
|
||||
} = useInspectVarsCrud()
|
||||
const { hasNullDependentOutputs } = useSubGraphVariablesCheck({
|
||||
const { getNullDependentOutput } = useSubGraphVariablesCheck({
|
||||
currentNodeId,
|
||||
nodesWithInspectVars,
|
||||
})
|
||||
@ -153,6 +154,7 @@ const useLastRun = <T>({
|
||||
const isAggregatorNode = blockType === BlockEnum.VariableAggregator
|
||||
const isCustomRunNode = isSupportCustomRunForm(blockType)
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const reactFlowStore = useStoreApi()
|
||||
const {
|
||||
getData: getDataForCheckMore,
|
||||
} = useGetDataForCheckMoreHooks<T>(blockType)(currentNodeId, oneStepRunParams.data)
|
||||
@ -238,6 +240,7 @@ const useLastRun = <T>({
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { setInitShowLastRunTab, setShowVariableInspectPanel } = workflowStore.getState()
|
||||
const initShowLastRunTab = useStore(s => s.initShowLastRunTab)
|
||||
const parentAvailableNodes = useStore(s => s.parentAvailableNodes) || []
|
||||
const [tabType, setTabType] = useState<TabType>(initShowLastRunTab ? TabType.lastRun : TabType.settings)
|
||||
useEffect(() => {
|
||||
if (initShowLastRunTab)
|
||||
@ -247,6 +250,31 @@ const useLastRun = <T>({
|
||||
}, [initShowLastRunTab])
|
||||
const invalidLastRun = useInvalidLastRun(flowType, flowId, currentNodeId)
|
||||
|
||||
const getContextNodeLabel = useCallback((nodeId: string) => {
|
||||
const nodeInFlow = reactFlowStore.getState().getNodes().find(node => node.id === nodeId)
|
||||
const flowNodeTitle = nodeInFlow?.data?.title
|
||||
if (flowNodeTitle && flowNodeTitle !== nodeId)
|
||||
return flowNodeTitle
|
||||
const parentNode = parentAvailableNodes.find(node => node.id === nodeId)
|
||||
const parentNodeTitle = parentNode?.data?.title
|
||||
if (parentNodeTitle && parentNodeTitle !== nodeId)
|
||||
return parentNodeTitle
|
||||
return ''
|
||||
}, [parentAvailableNodes, reactFlowStore])
|
||||
|
||||
const formatSubgraphOutputLabel = useCallback((selector: ValueSelector) => {
|
||||
const [nodeId, varName, ...restPath] = selector || []
|
||||
const nodeLabel = nodeId ? getContextNodeLabel(nodeId) : ''
|
||||
const outputPath = [varName, ...restPath].filter(Boolean).join('.')
|
||||
if (nodeLabel && outputPath)
|
||||
return `${nodeLabel}.${outputPath}`
|
||||
if (nodeLabel)
|
||||
return nodeLabel
|
||||
if (outputPath)
|
||||
return outputPath
|
||||
return t('nodes.llm.contextUnknownNode', { ns: 'workflow' })
|
||||
}, [getContextNodeLabel, t])
|
||||
|
||||
const ensureLLMContextReady = useCallback(() => {
|
||||
if (blockType !== BlockEnum.LLM)
|
||||
return true
|
||||
@ -265,12 +293,20 @@ const useLastRun = <T>({
|
||||
const [nodeId, varName] = selectorKey.split('::')
|
||||
const inspectVarValue = hasSetInspectVar(nodeId, varName, systemVars, conversationVars)
|
||||
if (!inspectVarValue) {
|
||||
Toast.notify({ type: 'error', message: t('nodes.llm.contextMissing', { ns: 'workflow' }) })
|
||||
const nodeLabel = getContextNodeLabel(nodeId)
|
||||
|| t('nodes.llm.contextUnknownNode', { ns: 'workflow' })
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('nodes.llm.contextMissing', {
|
||||
ns: 'workflow',
|
||||
nodeName: nodeLabel,
|
||||
}),
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}, [blockType, data, t])
|
||||
}, [blockType, data, t, hasSetInspectVar, systemVars, conversationVars, getContextNodeLabel])
|
||||
|
||||
const handleRunWithParams = async (data: Record<string, any>) => {
|
||||
if (blockIfChecklistFailed())
|
||||
@ -281,8 +317,15 @@ const useLastRun = <T>({
|
||||
if (!ensureLLMContextReady())
|
||||
return
|
||||
const dependentVars = singleRunParams?.getDependentVars?.()
|
||||
if (hasNullDependentOutputs(dependentVars)) {
|
||||
Toast.notify({ type: 'error', message: t('singleRun.subgraph.nullOutputError', { ns: 'workflow' }) })
|
||||
const nullOutput = getNullDependentOutput(dependentVars)
|
||||
if (nullOutput) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('singleRun.subgraph.nullOutputError', {
|
||||
ns: 'workflow',
|
||||
output: formatSubgraphOutputLabel(nullOutput),
|
||||
}),
|
||||
})
|
||||
return
|
||||
}
|
||||
setNodeRunning()
|
||||
@ -388,8 +431,15 @@ const useLastRun = <T>({
|
||||
if (!ensureLLMContextReady())
|
||||
return
|
||||
const dependentVars = singleRunParams?.getDependentVars?.()
|
||||
if (hasNullDependentOutputs(dependentVars)) {
|
||||
Toast.notify({ type: 'error', message: t('singleRun.subgraph.nullOutputError', { ns: 'workflow' }) })
|
||||
const nullOutput = getNullDependentOutput(dependentVars)
|
||||
if (nullOutput) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('singleRun.subgraph.nullOutputError', {
|
||||
ns: 'workflow',
|
||||
output: formatSubgraphOutputLabel(nullOutput),
|
||||
}),
|
||||
})
|
||||
return
|
||||
}
|
||||
if (blockType === BlockEnum.TriggerWebhook || blockType === BlockEnum.TriggerPlugin || blockType === BlockEnum.TriggerSchedule)
|
||||
|
||||
@ -672,8 +672,9 @@
|
||||
"nodes.llm.computerUse.tooltip": "Manage the runtime filesystem and tool access for your agent.",
|
||||
"nodes.llm.context": "context",
|
||||
"nodes.llm.contextBlock": "Context Block",
|
||||
"nodes.llm.contextMissing": "Missing context from previous nodes. Please select a context variable.",
|
||||
"nodes.llm.contextMissing": "Missing context from node {{nodeName}}. Please select a context variable.",
|
||||
"nodes.llm.contextTooltip": "You can import Knowledge as context",
|
||||
"nodes.llm.contextUnknownNode": "Unknown node",
|
||||
"nodes.llm.files": "Files",
|
||||
"nodes.llm.jsonSchema.addChildField": "Add Child Field",
|
||||
"nodes.llm.jsonSchema.addField": "Add Field",
|
||||
@ -1058,7 +1059,7 @@
|
||||
"singleRun.reRun": "Re-run",
|
||||
"singleRun.running": "Running",
|
||||
"singleRun.startRun": "Start Run",
|
||||
"singleRun.subgraph.nullOutputError": "Subgraph outputs contain null values. Run dependent nodes first.",
|
||||
"singleRun.subgraph.nullOutputError": "Referenced output {{output}} is empty. Run upstream nodes first.",
|
||||
"singleRun.testRun": "Test Run",
|
||||
"singleRun.testRunIteration": "Test Run Iteration",
|
||||
"singleRun.testRunLoop": "Test Run Loop",
|
||||
|
||||
@ -648,8 +648,9 @@
|
||||
"nodes.llm.addMessage": "メッセージ追加",
|
||||
"nodes.llm.context": "コンテキスト",
|
||||
"nodes.llm.contextBlock": "コンテキストブロック",
|
||||
"nodes.llm.contextMissing": "前のノードのコンテキストがありません。コンテキスト変数を選択してください。",
|
||||
"nodes.llm.contextMissing": "ノード「{{nodeName}}」のコンテキストがありません。コンテキスト変数を選択してください。",
|
||||
"nodes.llm.contextTooltip": "ナレッジベースをコンテキストとして利用",
|
||||
"nodes.llm.contextUnknownNode": "不明なノード",
|
||||
"nodes.llm.files": "ファイル",
|
||||
"nodes.llm.jsonSchema.addChildField": "サブフィールドを追加",
|
||||
"nodes.llm.jsonSchema.addField": "フィールドを追加",
|
||||
@ -1030,7 +1031,7 @@
|
||||
"singleRun.reRun": "再実行",
|
||||
"singleRun.running": "実行中",
|
||||
"singleRun.startRun": "実行開始",
|
||||
"singleRun.subgraph.nullOutputError": "サブグラフの出力にnullが含まれているため、単体デバッグできません。依存ノードを先に実行してください。",
|
||||
"singleRun.subgraph.nullOutputError": "サブグラフで参照している出力「{{output}}」が空です。先に上流ノードを実行してください。",
|
||||
"singleRun.testRun": "テスト実行",
|
||||
"singleRun.testRunIteration": "テスト実行(イテレーション)",
|
||||
"singleRun.testRunLoop": "テスト実行ループ",
|
||||
|
||||
@ -665,8 +665,9 @@
|
||||
"nodes.llm.computerUse.tooltip": "管理代理的运行时文件系统与工具访问权限。",
|
||||
"nodes.llm.context": "上下文",
|
||||
"nodes.llm.contextBlock": "上下文块",
|
||||
"nodes.llm.contextMissing": "缺少前序节点的上下文,请先选择上下文变量。",
|
||||
"nodes.llm.contextMissing": "缺少前序节点「{{nodeName}}」的上下文,请先选择上下文变量。",
|
||||
"nodes.llm.contextTooltip": "您可以导入知识库作为上下文",
|
||||
"nodes.llm.contextUnknownNode": "未知节点",
|
||||
"nodes.llm.files": "文件",
|
||||
"nodes.llm.jsonSchema.addChildField": "添加子字段",
|
||||
"nodes.llm.jsonSchema.addField": "添加字段",
|
||||
@ -1050,7 +1051,7 @@
|
||||
"singleRun.reRun": "重新运行",
|
||||
"singleRun.running": "运行中",
|
||||
"singleRun.startRun": "开始运行",
|
||||
"singleRun.subgraph.nullOutputError": "子图输出包含空值,无法单步调试。请先运行依赖节点。",
|
||||
"singleRun.subgraph.nullOutputError": "子图引用的输出「{{output}}」为空,请先运行上游节点。",
|
||||
"singleRun.testRun": "测试运行",
|
||||
"singleRun.testRunIteration": "测试运行迭代",
|
||||
"singleRun.testRunLoop": "测试运行循环",
|
||||
|
||||
@ -648,8 +648,9 @@
|
||||
"nodes.llm.addMessage": "新增消息",
|
||||
"nodes.llm.context": "上下文",
|
||||
"nodes.llm.contextBlock": "上下文區塊",
|
||||
"nodes.llm.contextMissing": "缺少前序節點的上下文,請先選擇上下文變數。",
|
||||
"nodes.llm.contextMissing": "缺少前序節點「{{nodeName}}」的上下文,請先選擇上下文變數。",
|
||||
"nodes.llm.contextTooltip": "您可以導入知識庫作為上下文",
|
||||
"nodes.llm.contextUnknownNode": "未知節點",
|
||||
"nodes.llm.files": "文件",
|
||||
"nodes.llm.jsonSchema.addChildField": "新增子欄位",
|
||||
"nodes.llm.jsonSchema.addField": "新增字段",
|
||||
@ -1031,7 +1032,7 @@
|
||||
"singleRun.reRun": "重新運行",
|
||||
"singleRun.running": "運行中",
|
||||
"singleRun.startRun": "開始運行",
|
||||
"singleRun.subgraph.nullOutputError": "子圖輸出包含空值,無法單步調試。請先執行依賴節點。",
|
||||
"singleRun.subgraph.nullOutputError": "子圖引用的輸出「{{output}}」為空,請先執行上游節點。",
|
||||
"singleRun.testRun": "測試運行",
|
||||
"singleRun.testRunIteration": "測試運行迭代",
|
||||
"singleRun.testRunLoop": "測試運行循環",
|
||||
|
||||
Reference in New Issue
Block a user