Skip to content

Commit bec5451

Browse files
authored
feat: workflow continue on error (#11474)
1 parent 86dfdcb commit bec5451

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1481
-282
lines changed

web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { VarBlockIcon } from '@/app/components/workflow/block-icon'
2626
import { Line3 } from '@/app/components/base/icons/src/public/common'
2727
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
2828
import Tooltip from '@/app/components/base/tooltip'
29+
import { isExceptionVariable } from '@/app/components/workflow/utils'
2930

3031
type WorkflowVariableBlockComponentProps = {
3132
nodeKey: string
@@ -53,6 +54,7 @@ const WorkflowVariableBlockComponent = ({
5354
const node = localWorkflowNodesMap![variables[0]]
5455
const isEnv = isENV(variables)
5556
const isChatVar = isConversationVar(variables)
57+
const isException = isExceptionVariable(varName, node?.type)
5658

5759
useEffect(() => {
5860
if (!editor.hasNodes([WorkflowVariableBlockNode]))
@@ -98,10 +100,10 @@ const WorkflowVariableBlockComponent = ({
98100
</div>
99101
)}
100102
<div className='flex items-center text-primary-600'>
101-
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5' />}
103+
{!isEnv && !isChatVar && <Variable02 className={cn('shrink-0 w-3.5 h-3.5', isException && 'text-text-warning')} />}
102104
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
103105
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
104-
<div className={cn('shrink-0 ml-0.5 text-xs font-medium truncate', (isEnv || isChatVar) && 'text-gray-900')} title={varName}>{varName}</div>
106+
<div className={cn('shrink-0 ml-0.5 text-xs font-medium truncate', (isEnv || isChatVar) && 'text-gray-900', isException && 'text-text-warning')} title={varName}>{varName}</div>
105107
{
106108
!node && !isEnv && !isChatVar && (
107109
<RiErrorWarningFill className='ml-0.5 w-3 h-3 text-[#D92D20]' />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
type CustomEdgeLinearGradientRenderProps = {
2+
id: string
3+
startColor: string
4+
stopColor: string
5+
position: {
6+
x1: number
7+
x2: number
8+
y1: number
9+
y2: number
10+
}
11+
}
12+
const CustomEdgeLinearGradientRender = ({
13+
id,
14+
startColor,
15+
stopColor,
16+
position,
17+
}: CustomEdgeLinearGradientRenderProps) => {
18+
const {
19+
x1,
20+
x2,
21+
y1,
22+
y2,
23+
} = position
24+
return (
25+
<defs>
26+
<linearGradient
27+
id={id}
28+
gradientUnits='userSpaceOnUse'
29+
x1={x1}
30+
y1={y1}
31+
x2={x2}
32+
y2={y2}
33+
>
34+
<stop
35+
offset='0%'
36+
style={{
37+
stopColor: startColor,
38+
stopOpacity: 1,
39+
}}
40+
/>
41+
<stop
42+
offset='100%'
43+
style={{
44+
stopColor,
45+
stopOpacity: 1,
46+
}}
47+
/>
48+
</linearGradient>
49+
</defs>
50+
)
51+
}
52+
53+
export default CustomEdgeLinearGradientRender

web/app/components/workflow/custom-edge.tsx

+56-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
memo,
33
useCallback,
4+
useMemo,
45
useState,
56
} from 'react'
67
import { intersection } from 'lodash-es'
@@ -20,8 +21,12 @@ import type {
2021
Edge,
2122
OnSelectBlock,
2223
} from './types'
24+
import { NodeRunningStatus } from './types'
25+
import { getEdgeColor } from './utils'
2326
import { ITERATION_CHILDREN_Z_INDEX } from './constants'
27+
import CustomEdgeLinearGradientRender from './custom-edge-linear-gradient-render'
2428
import cn from '@/utils/classnames'
29+
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
2530

2631
const CustomEdge = ({
2732
id,
@@ -53,6 +58,26 @@ const CustomEdge = ({
5358
const { handleNodeAdd } = useNodesInteractions()
5459
const { availablePrevBlocks } = useAvailableBlocks((data as Edge['data'])!.targetType, (data as Edge['data'])?.isInIteration)
5560
const { availableNextBlocks } = useAvailableBlocks((data as Edge['data'])!.sourceType, (data as Edge['data'])?.isInIteration)
61+
const {
62+
_sourceRunningStatus,
63+
_targetRunningStatus,
64+
} = data
65+
66+
const linearGradientId = useMemo(() => {
67+
if (
68+
(
69+
_sourceRunningStatus === NodeRunningStatus.Succeeded
70+
|| _sourceRunningStatus === NodeRunningStatus.Failed
71+
|| _sourceRunningStatus === NodeRunningStatus.Exception
72+
) && (
73+
_targetRunningStatus === NodeRunningStatus.Succeeded
74+
|| _targetRunningStatus === NodeRunningStatus.Failed
75+
|| _targetRunningStatus === NodeRunningStatus.Exception
76+
|| _targetRunningStatus === NodeRunningStatus.Running
77+
)
78+
)
79+
return id
80+
}, [_sourceRunningStatus, _targetRunningStatus, id])
5681

5782
const handleOpenChange = useCallback((v: boolean) => {
5883
setOpen(v)
@@ -73,14 +98,43 @@ const CustomEdge = ({
7398
)
7499
}, [handleNodeAdd, source, sourceHandleId, target, targetHandleId])
75100

101+
const stroke = useMemo(() => {
102+
if (selected)
103+
return getEdgeColor(NodeRunningStatus.Running)
104+
105+
if (linearGradientId)
106+
return `url(#${linearGradientId})`
107+
108+
if (data?._connectedNodeIsHovering)
109+
return getEdgeColor(NodeRunningStatus.Running, sourceHandleId === ErrorHandleTypeEnum.failBranch)
110+
111+
return getEdgeColor()
112+
}, [data._connectedNodeIsHovering, linearGradientId, selected, sourceHandleId])
113+
76114
return (
77115
<>
116+
{
117+
linearGradientId && (
118+
<CustomEdgeLinearGradientRender
119+
id={linearGradientId}
120+
startColor={getEdgeColor(_sourceRunningStatus)}
121+
stopColor={getEdgeColor(_targetRunningStatus)}
122+
position={{
123+
x1: sourceX,
124+
y1: sourceY,
125+
x2: targetX,
126+
y2: targetY,
127+
}}
128+
/>
129+
)
130+
}
78131
<BaseEdge
79132
id={id}
80133
path={edgePath}
81134
style={{
82-
stroke: (selected || data?._connectedNodeIsHovering || data?._run) ? '#2970FF' : '#D0D5DD',
135+
stroke,
83136
strokeWidth: 2,
137+
opacity: data._waitingRun ? 0.7 : 1,
84138
}}
85139
/>
86140
<EdgeLabelRenderer>
@@ -95,6 +149,7 @@ const CustomEdge = ({
95149
position: 'absolute',
96150
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
97151
pointerEvents: 'all',
152+
opacity: data._waitingRun ? 0.7 : 1,
98153
}}
99154
>
100155
<BlockSelector

web/app/components/workflow/hooks/use-edges-interactions.ts

+20-14
Original file line numberDiff line numberDiff line change
@@ -63,25 +63,29 @@ export const useEdgesInteractions = () => {
6363
edges,
6464
setEdges,
6565
} = store.getState()
66-
const currentEdgeIndex = edges.findIndex(edge => edge.source === nodeId && edge.sourceHandle === branchId)
66+
const edgeWillBeDeleted = edges.filter(edge => edge.source === nodeId && edge.sourceHandle === branchId)
6767

68-
if (currentEdgeIndex < 0)
68+
if (!edgeWillBeDeleted.length)
6969
return
7070

71-
const currentEdge = edges[currentEdgeIndex]
72-
const newNodes = produce(getNodes(), (draft: Node[]) => {
73-
const sourceNode = draft.find(node => node.id === currentEdge.source)
74-
const targetNode = draft.find(node => node.id === currentEdge.target)
75-
76-
if (sourceNode)
77-
sourceNode.data._connectedSourceHandleIds = sourceNode.data._connectedSourceHandleIds?.filter(handleId => handleId !== currentEdge.sourceHandle)
78-
79-
if (targetNode)
80-
targetNode.data._connectedTargetHandleIds = targetNode.data._connectedTargetHandleIds?.filter(handleId => handleId !== currentEdge.targetHandle)
71+
const nodes = getNodes()
72+
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
73+
edgeWillBeDeleted.map(edge => ({ type: 'remove', edge })),
74+
nodes,
75+
)
76+
const newNodes = produce(nodes, (draft: Node[]) => {
77+
draft.forEach((node) => {
78+
if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
79+
node.data = {
80+
...node.data,
81+
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
82+
}
83+
}
84+
})
8185
})
8286
setNodes(newNodes)
8387
const newEdges = produce(edges, (draft) => {
84-
draft.splice(currentEdgeIndex, 1)
88+
return draft.filter(edge => !edgeWillBeDeleted.find(e => e.id === edge.id))
8589
})
8690
setEdges(newEdges)
8791
handleSyncWorkflowDraft()
@@ -155,7 +159,9 @@ export const useEdgesInteractions = () => {
155159

156160
const newEdges = produce(edges, (draft) => {
157161
draft.forEach((edge) => {
158-
edge.data._run = false
162+
edge.data._sourceRunningStatus = undefined
163+
edge.data._targetRunningStatus = undefined
164+
edge.data._waitingRun = false
159165
})
160166
})
161167
setEdges(newEdges)

web/app/components/workflow/hooks/use-nodes-interactions.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,7 @@ export const useNodesInteractions = () => {
10331033
const newNodes = produce(nodes, (draft) => {
10341034
draft.forEach((node) => {
10351035
node.data._runningStatus = undefined
1036+
node.data._waitingRun = false
10361037
})
10371038
})
10381039
setNodes(newNodes)

web/app/components/workflow/hooks/use-workflow-run.ts

+67-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useCallback } from 'react'
22
import {
3-
getIncomers,
43
useReactFlow,
54
useStoreApi,
65
} from 'reactflow'
@@ -9,8 +8,8 @@ import { v4 as uuidV4 } from 'uuid'
98
import { usePathname } from 'next/navigation'
109
import { useWorkflowStore } from '../store'
1110
import { useNodesSyncDraft } from '../hooks'
12-
import type { Node } from '../types'
1311
import {
12+
BlockEnum,
1413
NodeRunningStatus,
1514
WorkflowRunningStatus,
1615
} from '../types'
@@ -28,6 +27,7 @@ import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player
2827
import {
2928
getFilesInLogs,
3029
} from '@/app/components/base/file-uploader/utils'
30+
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
3131

3232
export const useWorkflowRun = () => {
3333
const store = useStoreApi()
@@ -174,6 +174,8 @@ export const useWorkflowRun = () => {
174174
setIterParallelLogMap,
175175
} = workflowStore.getState()
176176
const {
177+
getNodes,
178+
setNodes,
177179
edges,
178180
setEdges,
179181
} = store.getState()
@@ -186,12 +188,20 @@ export const useWorkflowRun = () => {
186188
status: WorkflowRunningStatus.Running,
187189
}
188190
}))
189-
191+
const nodes = getNodes()
192+
const newNodes = produce(nodes, (draft) => {
193+
draft.forEach((node) => {
194+
node.data._waitingRun = true
195+
})
196+
})
197+
setNodes(newNodes)
190198
const newEdges = produce(edges, (draft) => {
191199
draft.forEach((edge) => {
192200
edge.data = {
193201
...edge.data,
194-
_run: false,
202+
_sourceRunningStatus: undefined,
203+
_targetRunningStatus: undefined,
204+
_waitingRun: true,
195205
}
196206
})
197207
})
@@ -311,13 +321,27 @@ export const useWorkflowRun = () => {
311321
}
312322
const newNodes = produce(nodes, (draft) => {
313323
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
324+
draft[currentNodeIndex].data._waitingRun = false
314325
})
315326
setNodes(newNodes)
316-
const incomeNodesId = getIncomers({ id: data.node_id } as Node, newNodes, edges).filter(node => node.data._runningStatus === NodeRunningStatus.Succeeded).map(node => node.id)
317327
const newEdges = produce(edges, (draft) => {
318-
draft.forEach((edge) => {
319-
if (edge.target === data.node_id && incomeNodesId.includes(edge.source))
320-
edge.data = { ...edge.data, _run: true } as any
328+
const incomeEdges = draft.filter((edge) => {
329+
return edge.target === data.node_id
330+
})
331+
332+
incomeEdges.forEach((edge) => {
333+
const incomeNode = nodes.find(node => node.id === edge.source)!
334+
if (
335+
(!incomeNode.data._runningBranchId && edge.sourceHandle === 'source')
336+
|| (incomeNode.data._runningBranchId && edge.sourceHandle === incomeNode.data._runningBranchId)
337+
) {
338+
edge.data = {
339+
...edge.data,
340+
_sourceRunningStatus: incomeNode.data._runningStatus,
341+
_targetRunningStatus: NodeRunningStatus.Running,
342+
_waitingRun: false,
343+
}
344+
}
321345
})
322346
})
323347
setEdges(newEdges)
@@ -336,6 +360,8 @@ export const useWorkflowRun = () => {
336360
const {
337361
getNodes,
338362
setNodes,
363+
edges,
364+
setEdges,
339365
} = store.getState()
340366
const nodes = getNodes()
341367
const nodeParentId = nodes.find(node => node.id === data.node_id)!.parentId
@@ -423,8 +449,31 @@ export const useWorkflowRun = () => {
423449
const newNodes = produce(nodes, (draft) => {
424450
const currentNode = draft.find(node => node.id === data.node_id)!
425451
currentNode.data._runningStatus = data.status as any
452+
if (data.status === NodeRunningStatus.Exception) {
453+
if (data.execution_metadata.error_strategy === ErrorHandleTypeEnum.failBranch)
454+
currentNode.data._runningBranchId = ErrorHandleTypeEnum.failBranch
455+
}
456+
else {
457+
if (data.node_type === BlockEnum.IfElse)
458+
currentNode.data._runningBranchId = data?.outputs?.selected_case_id
459+
460+
if (data.node_type === BlockEnum.QuestionClassifier)
461+
currentNode.data._runningBranchId = data?.outputs?.class_id
462+
}
426463
})
427464
setNodes(newNodes)
465+
const newEdges = produce(edges, (draft) => {
466+
const incomeEdges = draft.filter((edge) => {
467+
return edge.target === data.node_id
468+
})
469+
incomeEdges.forEach((edge) => {
470+
edge.data = {
471+
...edge.data,
472+
_targetRunningStatus: data.status as any,
473+
}
474+
})
475+
})
476+
setEdges(newEdges)
428477
prevNodeId = data.node_id
429478
}
430479

@@ -474,13 +523,20 @@ export const useWorkflowRun = () => {
474523
const newNodes = produce(nodes, (draft) => {
475524
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
476525
draft[currentNodeIndex].data._iterationLength = data.metadata.iterator_length
526+
draft[currentNodeIndex].data._waitingRun = false
477527
})
478528
setNodes(newNodes)
479529
const newEdges = produce(edges, (draft) => {
480-
const edge = draft.find(edge => edge.target === data.node_id && edge.source === prevNodeId)
530+
const incomeEdges = draft.filter(edge => edge.target === data.node_id)
481531

482-
if (edge)
483-
edge.data = { ...edge.data, _run: true } as any
532+
incomeEdges.forEach((edge) => {
533+
edge.data = {
534+
...edge.data,
535+
_sourceRunningStatus: nodes.find(node => node.id === edge.source)!.data._runningStatus,
536+
_targetRunningStatus: NodeRunningStatus.Running,
537+
_waitingRun: false,
538+
}
539+
})
484540
})
485541
setEdges(newEdges)
486542

0 commit comments

Comments
 (0)