feat: enhance workflow error handling and internationalization (#24648)

This commit is contained in:
lyzno1 2025-08-28 09:41:22 +08:00 committed by GitHub
parent 5cbe6bf8f8
commit c90dad566f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 73 additions and 13 deletions

View File

@ -9,6 +9,7 @@ import { BlockEnum } from '@/app/components/workflow/types'
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
import Modal from '@/app/components/base/modal'
import StartNodeSelectionPanel from './start-node-selection-panel'
import { useDocLink } from '@/context/i18n'
type WorkflowOnboardingModalProps = {
isShow: boolean
@ -22,6 +23,7 @@ const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({
onSelectStartNode,
}) => {
const { t } = useTranslation()
const docLink = useDocLink()
const handleSelectUserInput = useCallback(() => {
onSelectStartNode(BlockEnum.Start)
@ -60,15 +62,15 @@ const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({
</h3>
<div className="body-xs-regular leading-4 text-text-tertiary">
{t('workflow.onboarding.description')}{' '}
<button
type="button"
<a
href={docLink('guides/workflow/node/start')}
target="_blank"
rel="noopener noreferrer"
className="hover:text-text-accent-hover cursor-pointer text-text-accent underline"
onClick={() => {
console.log('Navigate to start node documentation')
}}
>
Learn more
</button> about start node.
{t('workflow.onboarding.learnMore')}
</a>{' '}
{t('workflow.onboarding.aboutStartNode')}
</div>
</div>

View File

@ -1,6 +1,7 @@
import type { MouseEvent } from 'react'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useToastContext } from '@/app/components/base/toast'
import produce from 'immer'
import type {
NodeDragHandler,
@ -67,6 +68,7 @@ import { getNodeUsedVars } from '../nodes/_base/components/variable/utils'
export const useNodesInteractions = () => {
const { t } = useTranslation()
const { notify } = useToastContext()
const store = useStoreApi()
const workflowStore = useWorkflowStore()
const reactflow = useReactFlow()
@ -91,6 +93,32 @@ export const useNodesInteractions = () => {
const { saveStateToHistory, undo, redo } = useWorkflowHistory()
// Unified error handler for start node missing scenarios
const handleStartNodeMissingError = useCallback((error: Error, operationKey: string): boolean => {
if (error.message === 'Start node not found') {
const operation = t(`workflow.error.operations.${operationKey}`) || operationKey
notify({
type: 'error',
message: t('workflow.error.startNodeRequired', { operation }) || `Please add a start node first before ${operation}`,
})
return true // Error handled
}
return false // Error not handled, should re-throw
}, [notify, t])
// Safe wrapper for checkNestedParallelLimit with error handling
const safeCheckParallelLimit = useCallback((nodes: Node[], edges: Edge[], parentNodeId?: string, operationKey = 'updatingWorkflow') => {
try {
return checkNestedParallelLimit(nodes, edges, parentNodeId)
}
catch (error: any) {
if (handleStartNodeMissingError(error, operationKey))
return false // Operation blocked but gracefully handled
throw error // Re-throw other errors
}
}, [checkNestedParallelLimit, handleStartNodeMissingError])
const handleNodeDragStart = useCallback<NodeDragHandler>((_, node) => {
workflowStore.setState({ nodeAnimation: false })
@ -419,7 +447,7 @@ export const useNodesInteractions = () => {
draft.push(newEdge)
})
if (checkNestedParallelLimit(newNodes, newEdges, targetNode?.parentId)) {
if (safeCheckParallelLimit(newNodes, newEdges, targetNode?.parentId, 'connectingNodes')) {
setNodes(newNodes)
setEdges(newEdges)
@ -434,7 +462,7 @@ export const useNodesInteractions = () => {
setConnectingNodePayload(undefined)
setEnteringNodePayload(undefined)
}
}, [getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, saveStateToHistory, checkNestedParallelLimit])
}, [getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, saveStateToHistory, safeCheckParallelLimit])
const handleNodeConnectStart = useCallback<OnConnectStart>((_, { nodeId, handleType, handleId }) => {
if (getNodesReadOnly())
@ -824,7 +852,7 @@ export const useNodesInteractions = () => {
draft.push(newEdge)
})
if (checkNestedParallelLimit(newNodes, newEdges, prevNode.parentId)) {
if (safeCheckParallelLimit(newNodes, newEdges, prevNode.parentId, 'addingNodes')) {
setNodes(newNodes)
setEdges(newEdges)
}
@ -944,7 +972,7 @@ export const useNodesInteractions = () => {
draft.push(newEdge)
})
if (checkNestedParallelLimit(newNodes, newEdges, nextNode.parentId)) {
if (safeCheckParallelLimit(newNodes, newEdges, nextNode.parentId, 'modifyingWorkflow')) {
setNodes(newNodes)
setEdges(newEdges)
}
@ -953,7 +981,7 @@ export const useNodesInteractions = () => {
}
}
else {
if (checkNestedParallelLimit(newNodes, edges))
if (safeCheckParallelLimit(newNodes, edges, undefined, 'updatingWorkflow'))
setNodes(newNodes)
else
@ -1102,7 +1130,7 @@ export const useNodesInteractions = () => {
}
handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.NodeAdd)
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, checkNestedParallelLimit])
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, safeCheckParallelLimit])
const handleNodeChange = useCallback((
currentNodeId: string,

View File

@ -210,6 +210,16 @@ const translation = {
invalidVariable: 'Invalid variable',
noValidTool: '{{field}} no valid tool selected',
toolParameterRequired: '{{field}}: parameter [{{param}}] is required',
startNodeRequired: 'Please add a start node first before {{operation}}',
},
error: {
startNodeRequired: 'Please add a start node first before {{operation}}',
operations: {
connectingNodes: 'connecting nodes',
addingNodes: 'adding nodes',
modifyingWorkflow: 'modifying workflow',
updatingWorkflow: 'updating workflow',
},
},
singleRun: {
testRun: 'Test Run ',

View File

@ -210,6 +210,16 @@ const translation = {
invalidVariable: '無効な変数です',
noValidTool: '{{field}} に利用可能なツールがありません',
toolParameterRequired: '{{field}}: パラメータ [{{param}}] は必須です',
startNodeRequired: '{{operation}}前に開始ノードを追加してください',
},
error: {
startNodeRequired: '{{operation}}前に開始ノードを追加してください',
operations: {
connectingNodes: 'ノード接続',
addingNodes: 'ノード追加',
modifyingWorkflow: 'ワークフロー変更',
updatingWorkflow: 'ワークフロー更新',
},
},
singleRun: {
testRun: 'テスト実行',

View File

@ -210,6 +210,16 @@ const translation = {
invalidVariable: '无效的变量',
noValidTool: '{{field}} 无可用工具',
toolParameterRequired: '{{field}}: 参数 [{{param}}] 不能为空',
startNodeRequired: '请先添加开始节点,然后再{{operation}}',
},
error: {
startNodeRequired: '请先添加开始节点,然后再{{operation}}',
operations: {
connectingNodes: '连接节点',
addingNodes: '添加节点',
modifyingWorkflow: '修改工作流',
updatingWorkflow: '更新工作流',
},
},
singleRun: {
testRun: '测试运行 ',