mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-04-23 14:19:42 +08:00
feat: workflow continue on error (#11474)
This commit is contained in:
parent
86dfdcb8ec
commit
bec5451f12
@ -26,6 +26,7 @@ import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
|||||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||||
|
|
||||||
type WorkflowVariableBlockComponentProps = {
|
type WorkflowVariableBlockComponentProps = {
|
||||||
nodeKey: string
|
nodeKey: string
|
||||||
@ -53,6 +54,7 @@ const WorkflowVariableBlockComponent = ({
|
|||||||
const node = localWorkflowNodesMap![variables[0]]
|
const node = localWorkflowNodesMap![variables[0]]
|
||||||
const isEnv = isENV(variables)
|
const isEnv = isENV(variables)
|
||||||
const isChatVar = isConversationVar(variables)
|
const isChatVar = isConversationVar(variables)
|
||||||
|
const isException = isExceptionVariable(varName, node?.type)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor.hasNodes([WorkflowVariableBlockNode]))
|
if (!editor.hasNodes([WorkflowVariableBlockNode]))
|
||||||
@ -98,10 +100,10 @@ const WorkflowVariableBlockComponent = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className='flex items-center text-primary-600'>
|
<div className='flex items-center text-primary-600'>
|
||||||
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5' />}
|
{!isEnv && !isChatVar && <Variable02 className={cn('shrink-0 w-3.5 h-3.5', isException && 'text-text-warning')} />}
|
||||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||||
<div className={cn('shrink-0 ml-0.5 text-xs font-medium truncate', (isEnv || isChatVar) && 'text-gray-900')} title={varName}>{varName}</div>
|
<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>
|
||||||
{
|
{
|
||||||
!node && !isEnv && !isChatVar && (
|
!node && !isEnv && !isChatVar && (
|
||||||
<RiErrorWarningFill className='ml-0.5 w-3 h-3 text-[#D92D20]' />
|
<RiErrorWarningFill className='ml-0.5 w-3 h-3 text-[#D92D20]' />
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
type CustomEdgeLinearGradientRenderProps = {
|
||||||
|
id: string
|
||||||
|
startColor: string
|
||||||
|
stopColor: string
|
||||||
|
position: {
|
||||||
|
x1: number
|
||||||
|
x2: number
|
||||||
|
y1: number
|
||||||
|
y2: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const CustomEdgeLinearGradientRender = ({
|
||||||
|
id,
|
||||||
|
startColor,
|
||||||
|
stopColor,
|
||||||
|
position,
|
||||||
|
}: CustomEdgeLinearGradientRenderProps) => {
|
||||||
|
const {
|
||||||
|
x1,
|
||||||
|
x2,
|
||||||
|
y1,
|
||||||
|
y2,
|
||||||
|
} = position
|
||||||
|
return (
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
id={id}
|
||||||
|
gradientUnits='userSpaceOnUse'
|
||||||
|
x1={x1}
|
||||||
|
y1={y1}
|
||||||
|
x2={x2}
|
||||||
|
y2={y2}
|
||||||
|
>
|
||||||
|
<stop
|
||||||
|
offset='0%'
|
||||||
|
style={{
|
||||||
|
stopColor: startColor,
|
||||||
|
stopOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset='100%'
|
||||||
|
style={{
|
||||||
|
stopColor,
|
||||||
|
stopOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomEdgeLinearGradientRender
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { intersection } from 'lodash-es'
|
import { intersection } from 'lodash-es'
|
||||||
@ -20,8 +21,12 @@ import type {
|
|||||||
Edge,
|
Edge,
|
||||||
OnSelectBlock,
|
OnSelectBlock,
|
||||||
} from './types'
|
} from './types'
|
||||||
|
import { NodeRunningStatus } from './types'
|
||||||
|
import { getEdgeColor } from './utils'
|
||||||
import { ITERATION_CHILDREN_Z_INDEX } from './constants'
|
import { ITERATION_CHILDREN_Z_INDEX } from './constants'
|
||||||
|
import CustomEdgeLinearGradientRender from './custom-edge-linear-gradient-render'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
|
|
||||||
const CustomEdge = ({
|
const CustomEdge = ({
|
||||||
id,
|
id,
|
||||||
@ -53,6 +58,26 @@ const CustomEdge = ({
|
|||||||
const { handleNodeAdd } = useNodesInteractions()
|
const { handleNodeAdd } = useNodesInteractions()
|
||||||
const { availablePrevBlocks } = useAvailableBlocks((data as Edge['data'])!.targetType, (data as Edge['data'])?.isInIteration)
|
const { availablePrevBlocks } = useAvailableBlocks((data as Edge['data'])!.targetType, (data as Edge['data'])?.isInIteration)
|
||||||
const { availableNextBlocks } = useAvailableBlocks((data as Edge['data'])!.sourceType, (data as Edge['data'])?.isInIteration)
|
const { availableNextBlocks } = useAvailableBlocks((data as Edge['data'])!.sourceType, (data as Edge['data'])?.isInIteration)
|
||||||
|
const {
|
||||||
|
_sourceRunningStatus,
|
||||||
|
_targetRunningStatus,
|
||||||
|
} = data
|
||||||
|
|
||||||
|
const linearGradientId = useMemo(() => {
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
_sourceRunningStatus === NodeRunningStatus.Succeeded
|
||||||
|
|| _sourceRunningStatus === NodeRunningStatus.Failed
|
||||||
|
|| _sourceRunningStatus === NodeRunningStatus.Exception
|
||||||
|
) && (
|
||||||
|
_targetRunningStatus === NodeRunningStatus.Succeeded
|
||||||
|
|| _targetRunningStatus === NodeRunningStatus.Failed
|
||||||
|
|| _targetRunningStatus === NodeRunningStatus.Exception
|
||||||
|
|| _targetRunningStatus === NodeRunningStatus.Running
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return id
|
||||||
|
}, [_sourceRunningStatus, _targetRunningStatus, id])
|
||||||
|
|
||||||
const handleOpenChange = useCallback((v: boolean) => {
|
const handleOpenChange = useCallback((v: boolean) => {
|
||||||
setOpen(v)
|
setOpen(v)
|
||||||
@ -73,14 +98,43 @@ const CustomEdge = ({
|
|||||||
)
|
)
|
||||||
}, [handleNodeAdd, source, sourceHandleId, target, targetHandleId])
|
}, [handleNodeAdd, source, sourceHandleId, target, targetHandleId])
|
||||||
|
|
||||||
|
const stroke = useMemo(() => {
|
||||||
|
if (selected)
|
||||||
|
return getEdgeColor(NodeRunningStatus.Running)
|
||||||
|
|
||||||
|
if (linearGradientId)
|
||||||
|
return `url(#${linearGradientId})`
|
||||||
|
|
||||||
|
if (data?._connectedNodeIsHovering)
|
||||||
|
return getEdgeColor(NodeRunningStatus.Running, sourceHandleId === ErrorHandleTypeEnum.failBranch)
|
||||||
|
|
||||||
|
return getEdgeColor()
|
||||||
|
}, [data._connectedNodeIsHovering, linearGradientId, selected, sourceHandleId])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{
|
||||||
|
linearGradientId && (
|
||||||
|
<CustomEdgeLinearGradientRender
|
||||||
|
id={linearGradientId}
|
||||||
|
startColor={getEdgeColor(_sourceRunningStatus)}
|
||||||
|
stopColor={getEdgeColor(_targetRunningStatus)}
|
||||||
|
position={{
|
||||||
|
x1: sourceX,
|
||||||
|
y1: sourceY,
|
||||||
|
x2: targetX,
|
||||||
|
y2: targetY,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
<BaseEdge
|
<BaseEdge
|
||||||
id={id}
|
id={id}
|
||||||
path={edgePath}
|
path={edgePath}
|
||||||
style={{
|
style={{
|
||||||
stroke: (selected || data?._connectedNodeIsHovering || data?._run) ? '#2970FF' : '#D0D5DD',
|
stroke,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
|
opacity: data._waitingRun ? 0.7 : 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabelRenderer>
|
||||||
@ -95,6 +149,7 @@ const CustomEdge = ({
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
|
opacity: data._waitingRun ? 0.7 : 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BlockSelector
|
<BlockSelector
|
||||||
|
@ -63,25 +63,29 @@ export const useEdgesInteractions = () => {
|
|||||||
edges,
|
edges,
|
||||||
setEdges,
|
setEdges,
|
||||||
} = store.getState()
|
} = store.getState()
|
||||||
const currentEdgeIndex = edges.findIndex(edge => edge.source === nodeId && edge.sourceHandle === branchId)
|
const edgeWillBeDeleted = edges.filter(edge => edge.source === nodeId && edge.sourceHandle === branchId)
|
||||||
|
|
||||||
if (currentEdgeIndex < 0)
|
if (!edgeWillBeDeleted.length)
|
||||||
return
|
return
|
||||||
|
|
||||||
const currentEdge = edges[currentEdgeIndex]
|
const nodes = getNodes()
|
||||||
const newNodes = produce(getNodes(), (draft: Node[]) => {
|
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||||
const sourceNode = draft.find(node => node.id === currentEdge.source)
|
edgeWillBeDeleted.map(edge => ({ type: 'remove', edge })),
|
||||||
const targetNode = draft.find(node => node.id === currentEdge.target)
|
nodes,
|
||||||
|
)
|
||||||
if (sourceNode)
|
const newNodes = produce(nodes, (draft: Node[]) => {
|
||||||
sourceNode.data._connectedSourceHandleIds = sourceNode.data._connectedSourceHandleIds?.filter(handleId => handleId !== currentEdge.sourceHandle)
|
draft.forEach((node) => {
|
||||||
|
if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
|
||||||
if (targetNode)
|
node.data = {
|
||||||
targetNode.data._connectedTargetHandleIds = targetNode.data._connectedTargetHandleIds?.filter(handleId => handleId !== currentEdge.targetHandle)
|
...node.data,
|
||||||
|
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
const newEdges = produce(edges, (draft) => {
|
const newEdges = produce(edges, (draft) => {
|
||||||
draft.splice(currentEdgeIndex, 1)
|
return draft.filter(edge => !edgeWillBeDeleted.find(e => e.id === edge.id))
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
handleSyncWorkflowDraft()
|
handleSyncWorkflowDraft()
|
||||||
@ -155,7 +159,9 @@ export const useEdgesInteractions = () => {
|
|||||||
|
|
||||||
const newEdges = produce(edges, (draft) => {
|
const newEdges = produce(edges, (draft) => {
|
||||||
draft.forEach((edge) => {
|
draft.forEach((edge) => {
|
||||||
edge.data._run = false
|
edge.data._sourceRunningStatus = undefined
|
||||||
|
edge.data._targetRunningStatus = undefined
|
||||||
|
edge.data._waitingRun = false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
|
@ -1033,6 +1033,7 @@ export const useNodesInteractions = () => {
|
|||||||
const newNodes = produce(nodes, (draft) => {
|
const newNodes = produce(nodes, (draft) => {
|
||||||
draft.forEach((node) => {
|
draft.forEach((node) => {
|
||||||
node.data._runningStatus = undefined
|
node.data._runningStatus = undefined
|
||||||
|
node.data._waitingRun = false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import {
|
import {
|
||||||
getIncomers,
|
|
||||||
useReactFlow,
|
useReactFlow,
|
||||||
useStoreApi,
|
useStoreApi,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
@ -9,8 +8,8 @@ import { v4 as uuidV4 } from 'uuid'
|
|||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
import { useWorkflowStore } from '../store'
|
import { useWorkflowStore } from '../store'
|
||||||
import { useNodesSyncDraft } from '../hooks'
|
import { useNodesSyncDraft } from '../hooks'
|
||||||
import type { Node } from '../types'
|
|
||||||
import {
|
import {
|
||||||
|
BlockEnum,
|
||||||
NodeRunningStatus,
|
NodeRunningStatus,
|
||||||
WorkflowRunningStatus,
|
WorkflowRunningStatus,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
@ -28,6 +27,7 @@ import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player
|
|||||||
import {
|
import {
|
||||||
getFilesInLogs,
|
getFilesInLogs,
|
||||||
} from '@/app/components/base/file-uploader/utils'
|
} from '@/app/components/base/file-uploader/utils'
|
||||||
|
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
|
|
||||||
export const useWorkflowRun = () => {
|
export const useWorkflowRun = () => {
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
@ -174,6 +174,8 @@ export const useWorkflowRun = () => {
|
|||||||
setIterParallelLogMap,
|
setIterParallelLogMap,
|
||||||
} = workflowStore.getState()
|
} = workflowStore.getState()
|
||||||
const {
|
const {
|
||||||
|
getNodes,
|
||||||
|
setNodes,
|
||||||
edges,
|
edges,
|
||||||
setEdges,
|
setEdges,
|
||||||
} = store.getState()
|
} = store.getState()
|
||||||
@ -186,12 +188,20 @@ export const useWorkflowRun = () => {
|
|||||||
status: WorkflowRunningStatus.Running,
|
status: WorkflowRunningStatus.Running,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
const nodes = getNodes()
|
||||||
|
const newNodes = produce(nodes, (draft) => {
|
||||||
|
draft.forEach((node) => {
|
||||||
|
node.data._waitingRun = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setNodes(newNodes)
|
||||||
const newEdges = produce(edges, (draft) => {
|
const newEdges = produce(edges, (draft) => {
|
||||||
draft.forEach((edge) => {
|
draft.forEach((edge) => {
|
||||||
edge.data = {
|
edge.data = {
|
||||||
...edge.data,
|
...edge.data,
|
||||||
_run: false,
|
_sourceRunningStatus: undefined,
|
||||||
|
_targetRunningStatus: undefined,
|
||||||
|
_waitingRun: true,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -311,13 +321,27 @@ export const useWorkflowRun = () => {
|
|||||||
}
|
}
|
||||||
const newNodes = produce(nodes, (draft) => {
|
const newNodes = produce(nodes, (draft) => {
|
||||||
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
|
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
|
||||||
|
draft[currentNodeIndex].data._waitingRun = false
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
const incomeNodesId = getIncomers({ id: data.node_id } as Node, newNodes, edges).filter(node => node.data._runningStatus === NodeRunningStatus.Succeeded).map(node => node.id)
|
|
||||||
const newEdges = produce(edges, (draft) => {
|
const newEdges = produce(edges, (draft) => {
|
||||||
draft.forEach((edge) => {
|
const incomeEdges = draft.filter((edge) => {
|
||||||
if (edge.target === data.node_id && incomeNodesId.includes(edge.source))
|
return edge.target === data.node_id
|
||||||
edge.data = { ...edge.data, _run: true } as any
|
})
|
||||||
|
|
||||||
|
incomeEdges.forEach((edge) => {
|
||||||
|
const incomeNode = nodes.find(node => node.id === edge.source)!
|
||||||
|
if (
|
||||||
|
(!incomeNode.data._runningBranchId && edge.sourceHandle === 'source')
|
||||||
|
|| (incomeNode.data._runningBranchId && edge.sourceHandle === incomeNode.data._runningBranchId)
|
||||||
|
) {
|
||||||
|
edge.data = {
|
||||||
|
...edge.data,
|
||||||
|
_sourceRunningStatus: incomeNode.data._runningStatus,
|
||||||
|
_targetRunningStatus: NodeRunningStatus.Running,
|
||||||
|
_waitingRun: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
@ -336,6 +360,8 @@ export const useWorkflowRun = () => {
|
|||||||
const {
|
const {
|
||||||
getNodes,
|
getNodes,
|
||||||
setNodes,
|
setNodes,
|
||||||
|
edges,
|
||||||
|
setEdges,
|
||||||
} = store.getState()
|
} = store.getState()
|
||||||
const nodes = getNodes()
|
const nodes = getNodes()
|
||||||
const nodeParentId = nodes.find(node => node.id === data.node_id)!.parentId
|
const nodeParentId = nodes.find(node => node.id === data.node_id)!.parentId
|
||||||
@ -423,8 +449,31 @@ export const useWorkflowRun = () => {
|
|||||||
const newNodes = produce(nodes, (draft) => {
|
const newNodes = produce(nodes, (draft) => {
|
||||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||||
currentNode.data._runningStatus = data.status as any
|
currentNode.data._runningStatus = data.status as any
|
||||||
|
if (data.status === NodeRunningStatus.Exception) {
|
||||||
|
if (data.execution_metadata.error_strategy === ErrorHandleTypeEnum.failBranch)
|
||||||
|
currentNode.data._runningBranchId = ErrorHandleTypeEnum.failBranch
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (data.node_type === BlockEnum.IfElse)
|
||||||
|
currentNode.data._runningBranchId = data?.outputs?.selected_case_id
|
||||||
|
|
||||||
|
if (data.node_type === BlockEnum.QuestionClassifier)
|
||||||
|
currentNode.data._runningBranchId = data?.outputs?.class_id
|
||||||
|
}
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
|
const newEdges = produce(edges, (draft) => {
|
||||||
|
const incomeEdges = draft.filter((edge) => {
|
||||||
|
return edge.target === data.node_id
|
||||||
|
})
|
||||||
|
incomeEdges.forEach((edge) => {
|
||||||
|
edge.data = {
|
||||||
|
...edge.data,
|
||||||
|
_targetRunningStatus: data.status as any,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setEdges(newEdges)
|
||||||
prevNodeId = data.node_id
|
prevNodeId = data.node_id
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,13 +523,20 @@ export const useWorkflowRun = () => {
|
|||||||
const newNodes = produce(nodes, (draft) => {
|
const newNodes = produce(nodes, (draft) => {
|
||||||
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
|
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
|
||||||
draft[currentNodeIndex].data._iterationLength = data.metadata.iterator_length
|
draft[currentNodeIndex].data._iterationLength = data.metadata.iterator_length
|
||||||
|
draft[currentNodeIndex].data._waitingRun = false
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
const newEdges = produce(edges, (draft) => {
|
const newEdges = produce(edges, (draft) => {
|
||||||
const edge = draft.find(edge => edge.target === data.node_id && edge.source === prevNodeId)
|
const incomeEdges = draft.filter(edge => edge.target === data.node_id)
|
||||||
|
|
||||||
if (edge)
|
incomeEdges.forEach((edge) => {
|
||||||
edge.data = { ...edge.data, _run: true } as any
|
edge.data = {
|
||||||
|
...edge.data,
|
||||||
|
_sourceRunningStatus: nodes.find(node => node.id === edge.source)!.data._runningStatus,
|
||||||
|
_targetRunningStatus: NodeRunningStatus.Running,
|
||||||
|
_waitingRun: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const isFinished = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
|
const isFinished = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed || runningStatus === NodeRunningStatus.Exception
|
||||||
const isRunning = runningStatus === NodeRunningStatus.Running
|
const isRunning = runningStatus === NodeRunningStatus.Running
|
||||||
const isFileLoaded = (() => {
|
const isFileLoaded = (() => {
|
||||||
// system files
|
// system files
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import Collapse from '.'
|
||||||
|
|
||||||
|
type FieldCollapseProps = {
|
||||||
|
title: string
|
||||||
|
children: JSX.Element
|
||||||
|
}
|
||||||
|
const FieldCollapse = ({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}: FieldCollapseProps) => {
|
||||||
|
return (
|
||||||
|
<div className='py-4'>
|
||||||
|
<Collapse
|
||||||
|
trigger={
|
||||||
|
<div className='flex items-center h-6 system-sm-semibold-uppercase text-text-secondary cursor-pointer'>{title}</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className='px-4'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FieldCollapse
|
@ -0,0 +1,56 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { RiArrowDropRightLine } from '@remixicon/react'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
export { default as FieldCollapse } from './field-collapse'
|
||||||
|
|
||||||
|
type CollapseProps = {
|
||||||
|
disabled?: boolean
|
||||||
|
trigger: JSX.Element
|
||||||
|
children: JSX.Element
|
||||||
|
collapsed?: boolean
|
||||||
|
onCollapse?: (collapsed: boolean) => void
|
||||||
|
}
|
||||||
|
const Collapse = ({
|
||||||
|
disabled,
|
||||||
|
trigger,
|
||||||
|
children,
|
||||||
|
collapsed,
|
||||||
|
onCollapse,
|
||||||
|
}: CollapseProps) => {
|
||||||
|
const [collapsedLocal, setCollapsedLocal] = useState(true)
|
||||||
|
const collapsedMerged = collapsed !== undefined ? collapsed : collapsedLocal
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className='flex items-center'
|
||||||
|
onClick={() => {
|
||||||
|
if (!disabled) {
|
||||||
|
setCollapsedLocal(!collapsedMerged)
|
||||||
|
onCollapse?.(!collapsedMerged)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='shrink-0 w-4 h-4'>
|
||||||
|
{
|
||||||
|
!disabled && (
|
||||||
|
<RiArrowDropRightLine
|
||||||
|
className={cn(
|
||||||
|
'w-4 h-4 text-text-tertiary',
|
||||||
|
!collapsedMerged && 'transform rotate-90',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{trigger}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
!collapsedMerged && children
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Collapse
|
@ -33,6 +33,7 @@ type Props = {
|
|||||||
}[]
|
}[]
|
||||||
showFileList?: boolean
|
showFileList?: boolean
|
||||||
showCodeGenerator?: boolean
|
showCodeGenerator?: boolean
|
||||||
|
tip?: JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
const Base: FC<Props> = ({
|
const Base: FC<Props> = ({
|
||||||
@ -49,6 +50,7 @@ const Base: FC<Props> = ({
|
|||||||
fileList = [],
|
fileList = [],
|
||||||
showFileList,
|
showFileList,
|
||||||
showCodeGenerator = false,
|
showCodeGenerator = false,
|
||||||
|
tip,
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
const {
|
const {
|
||||||
@ -100,6 +102,7 @@ const Base: FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{tip && <div className='px-1 py-0.5'>{tip}</div>}
|
||||||
<PromptEditorHeightResizeWrap
|
<PromptEditorHeightResizeWrap
|
||||||
height={isExpand ? editorExpandHeight : editorContentHeight}
|
height={isExpand ? editorExpandHeight : editorContentHeight}
|
||||||
minHeight={editorContentMinHeight}
|
minHeight={editorContentMinHeight}
|
||||||
|
@ -34,6 +34,7 @@ export type Props = {
|
|||||||
onGenerated?: (value: string) => void
|
onGenerated?: (value: string) => void
|
||||||
showCodeGenerator?: boolean
|
showCodeGenerator?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
|
tip?: JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
export const languageMap = {
|
export const languageMap = {
|
||||||
@ -69,6 +70,7 @@ const CodeEditor: FC<Props> = ({
|
|||||||
onGenerated,
|
onGenerated,
|
||||||
showCodeGenerator = false,
|
showCodeGenerator = false,
|
||||||
className,
|
className,
|
||||||
|
tip,
|
||||||
}) => {
|
}) => {
|
||||||
const [isFocus, setIsFocus] = React.useState(false)
|
const [isFocus, setIsFocus] = React.useState(false)
|
||||||
const [isMounted, setIsMounted] = React.useState(false)
|
const [isMounted, setIsMounted] = React.useState(false)
|
||||||
@ -211,6 +213,7 @@ const CodeEditor: FC<Props> = ({
|
|||||||
fileList={fileList as any}
|
fileList={fileList as any}
|
||||||
showFileList={showFileList}
|
showFileList={showFileList}
|
||||||
showCodeGenerator={showCodeGenerator}
|
showCodeGenerator={showCodeGenerator}
|
||||||
|
tip={tip}
|
||||||
>
|
>
|
||||||
{main}
|
{main}
|
||||||
</Base>
|
</Base>
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type { DefaultValueForm } from './types'
|
||||||
|
import Input from '@/app/components/base/input'
|
||||||
|
import { VarType } from '@/app/components/workflow/types'
|
||||||
|
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||||
|
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||||
|
|
||||||
|
type DefaultValueProps = {
|
||||||
|
forms: DefaultValueForm[]
|
||||||
|
onFormChange: (form: DefaultValueForm) => void
|
||||||
|
}
|
||||||
|
const DefaultValue = ({
|
||||||
|
forms,
|
||||||
|
onFormChange,
|
||||||
|
}: DefaultValueProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const getFormChangeHandler = useCallback(({ key, type }: DefaultValueForm) => {
|
||||||
|
return (payload: any) => {
|
||||||
|
let value
|
||||||
|
if (type === VarType.string || type === VarType.number)
|
||||||
|
value = payload.target.value
|
||||||
|
|
||||||
|
if (type === VarType.array || type === VarType.arrayNumber || type === VarType.arrayString || type === VarType.arrayObject || type === VarType.arrayFile || type === VarType.object)
|
||||||
|
value = payload
|
||||||
|
|
||||||
|
onFormChange({ key, type, value })
|
||||||
|
}
|
||||||
|
}, [onFormChange])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='px-4 pt-2'>
|
||||||
|
<div className='mb-2 body-xs-regular text-text-tertiary'>
|
||||||
|
{t('workflow.nodes.common.errorHandle.defaultValue.desc')}
|
||||||
|
|
||||||
|
<a
|
||||||
|
href='https://docs.dify.ai/guides/workflow/error-handling'
|
||||||
|
target='_blank'
|
||||||
|
className='text-text-accent'
|
||||||
|
>
|
||||||
|
{t('workflow.common.learnMore')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-1'>
|
||||||
|
{
|
||||||
|
forms.map((form, index) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className='py-1'
|
||||||
|
>
|
||||||
|
<div className='flex items-center mb-1'>
|
||||||
|
<div className='mr-1 system-sm-medium text-text-primary'>{form.key}</div>
|
||||||
|
<div className='system-xs-regular text-text-tertiary'>{form.type}</div>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
(form.type === VarType.string || form.type === VarType.number) && (
|
||||||
|
<Input
|
||||||
|
type={form.type}
|
||||||
|
value={form.value || (form.type === VarType.string ? '' : 0)}
|
||||||
|
onChange={getFormChangeHandler({ key: form.key, type: form.type })}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
(
|
||||||
|
form.type === VarType.array
|
||||||
|
|| form.type === VarType.arrayNumber
|
||||||
|
|| form.type === VarType.arrayString
|
||||||
|
|| form.type === VarType.arrayObject
|
||||||
|
|| form.type === VarType.object
|
||||||
|
) && (
|
||||||
|
<CodeEditor
|
||||||
|
language={CodeLanguage.json}
|
||||||
|
value={form.value}
|
||||||
|
onChange={getFormChangeHandler({ key: form.key, type: form.type })}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DefaultValue
|
@ -0,0 +1,67 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useUpdateNodeInternals } from 'reactflow'
|
||||||
|
import { NodeSourceHandle } from '../node-handle'
|
||||||
|
import { ErrorHandleTypeEnum } from './types'
|
||||||
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
|
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type ErrorHandleOnNodeProps = Pick<Node, 'id' | 'data'>
|
||||||
|
const ErrorHandleOnNode = ({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
}: ErrorHandleOnNodeProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { error_strategy } = data
|
||||||
|
const updateNodeInternals = useUpdateNodeInternals()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error_strategy === ErrorHandleTypeEnum.failBranch)
|
||||||
|
updateNodeInternals(id)
|
||||||
|
}, [error_strategy, id, updateNodeInternals])
|
||||||
|
|
||||||
|
if (!error_strategy)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative pt-1 pb-2 px-3'>
|
||||||
|
<div className={cn(
|
||||||
|
'relative flex items-center justify-between px-[5px] h-6 bg-workflow-block-parma-bg rounded-md',
|
||||||
|
data._runningStatus === NodeRunningStatus.Exception && 'border-[0.5px] border-components-badge-status-light-warning-halo bg-state-warning-hover',
|
||||||
|
)}>
|
||||||
|
<div className='system-xs-medium-uppercase text-text-tertiary'>
|
||||||
|
{t('workflow.common.onFailure')}
|
||||||
|
</div>
|
||||||
|
<div className={cn(
|
||||||
|
'system-xs-medium text-text-secondary',
|
||||||
|
data._runningStatus === NodeRunningStatus.Exception && 'text-text-warning',
|
||||||
|
)}>
|
||||||
|
{
|
||||||
|
error_strategy === ErrorHandleTypeEnum.defaultValue && (
|
||||||
|
t('workflow.nodes.common.errorHandle.defaultValue.output')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
error_strategy === ErrorHandleTypeEnum.failBranch && (
|
||||||
|
t('workflow.nodes.common.errorHandle.failBranch.title')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
error_strategy === ErrorHandleTypeEnum.failBranch && (
|
||||||
|
<NodeSourceHandle
|
||||||
|
id={id}
|
||||||
|
data={data}
|
||||||
|
handleId={ErrorHandleTypeEnum.failBranch}
|
||||||
|
handleClassName='!top-1/2 !-right-[21px] !-translate-y-1/2 after:!bg-workflow-link-line-failure-button-bg'
|
||||||
|
nodeSelectorClassName='!bg-workflow-link-line-failure-button-bg'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorHandleOnNode
|
@ -0,0 +1,90 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Collapse from '../collapse'
|
||||||
|
import { ErrorHandleTypeEnum } from './types'
|
||||||
|
import ErrorHandleTypeSelector from './error-handle-type-selector'
|
||||||
|
import FailBranchCard from './fail-branch-card'
|
||||||
|
import DefaultValue from './default-value'
|
||||||
|
import {
|
||||||
|
useDefaultValue,
|
||||||
|
useErrorHandle,
|
||||||
|
} from './hooks'
|
||||||
|
import type { DefaultValueForm } from './types'
|
||||||
|
import type {
|
||||||
|
CommonNodeType,
|
||||||
|
Node,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
|
||||||
|
type ErrorHandleProps = Pick<Node, 'id' | 'data'>
|
||||||
|
const ErrorHandle = ({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
}: ErrorHandleProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { error_strategy, default_value } = data
|
||||||
|
const {
|
||||||
|
collapsed,
|
||||||
|
setCollapsed,
|
||||||
|
handleErrorHandleTypeChange,
|
||||||
|
} = useErrorHandle(id, data)
|
||||||
|
const { handleFormChange } = useDefaultValue(id)
|
||||||
|
|
||||||
|
const getHandleErrorHandleTypeChange = useCallback((data: CommonNodeType) => {
|
||||||
|
return (value: ErrorHandleTypeEnum) => {
|
||||||
|
handleErrorHandleTypeChange(value, data)
|
||||||
|
}
|
||||||
|
}, [handleErrorHandleTypeChange])
|
||||||
|
|
||||||
|
const getHandleFormChange = useCallback((data: CommonNodeType) => {
|
||||||
|
return (v: DefaultValueForm) => {
|
||||||
|
handleFormChange(v, data)
|
||||||
|
}
|
||||||
|
}, [handleFormChange])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Split />
|
||||||
|
<div className='py-4'>
|
||||||
|
<Collapse
|
||||||
|
disabled={!error_strategy}
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={setCollapsed}
|
||||||
|
trigger={
|
||||||
|
<div className='grow flex items-center justify-between pr-4'>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<div className='mr-0.5 system-sm-semibold-uppercase text-text-secondary'>
|
||||||
|
{t('workflow.nodes.common.errorHandle.title')}
|
||||||
|
</div>
|
||||||
|
<Tooltip popupContent={t('workflow.nodes.common.errorHandle.tip')} />
|
||||||
|
</div>
|
||||||
|
<ErrorHandleTypeSelector
|
||||||
|
value={error_strategy || ErrorHandleTypeEnum.none}
|
||||||
|
onSelected={getHandleErrorHandleTypeChange(data)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
error_strategy === ErrorHandleTypeEnum.failBranch && !collapsed && (
|
||||||
|
<FailBranchCard />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
error_strategy === ErrorHandleTypeEnum.defaultValue && !collapsed && !!default_value?.length && (
|
||||||
|
<DefaultValue
|
||||||
|
forms={default_value}
|
||||||
|
onFormChange={getHandleFormChange(data)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorHandle
|
@ -0,0 +1,43 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { RiAlertFill } from '@remixicon/react'
|
||||||
|
import { ErrorHandleTypeEnum } from './types'
|
||||||
|
|
||||||
|
type ErrorHandleTipProps = {
|
||||||
|
type?: ErrorHandleTypeEnum
|
||||||
|
}
|
||||||
|
const ErrorHandleTip = ({
|
||||||
|
type,
|
||||||
|
}: ErrorHandleTipProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const text = useMemo(() => {
|
||||||
|
if (type === ErrorHandleTypeEnum.failBranch)
|
||||||
|
return t('workflow.nodes.common.errorHandle.failBranch.inLog')
|
||||||
|
|
||||||
|
if (type === ErrorHandleTypeEnum.defaultValue)
|
||||||
|
return t('workflow.nodes.common.errorHandle.defaultValue.inLog')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!type)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='relative flex p-2 pr-[52px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xs'
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='absolute inset-0 opacity-40 rounded-lg'
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%)',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<RiAlertFill className='shrink-0 mr-1 w-4 h-4 text-text-warning-secondary' />
|
||||||
|
<div className='grow system-xs-medium text-text-primary'>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorHandleTip
|
@ -0,0 +1,95 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
RiArrowDownSLine,
|
||||||
|
RiCheckLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import { ErrorHandleTypeEnum } from './types'
|
||||||
|
import {
|
||||||
|
PortalToFollowElem,
|
||||||
|
PortalToFollowElemContent,
|
||||||
|
PortalToFollowElemTrigger,
|
||||||
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
|
||||||
|
type ErrorHandleTypeSelectorProps = {
|
||||||
|
value: ErrorHandleTypeEnum
|
||||||
|
onSelected: (value: ErrorHandleTypeEnum) => void
|
||||||
|
}
|
||||||
|
const ErrorHandleTypeSelector = ({
|
||||||
|
value,
|
||||||
|
onSelected,
|
||||||
|
}: ErrorHandleTypeSelectorProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: ErrorHandleTypeEnum.none,
|
||||||
|
label: t('workflow.nodes.common.errorHandle.none.title'),
|
||||||
|
description: t('workflow.nodes.common.errorHandle.none.desc'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ErrorHandleTypeEnum.defaultValue,
|
||||||
|
label: t('workflow.nodes.common.errorHandle.defaultValue.title'),
|
||||||
|
description: t('workflow.nodes.common.errorHandle.defaultValue.desc'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ErrorHandleTypeEnum.failBranch,
|
||||||
|
label: t('workflow.nodes.common.errorHandle.failBranch.title'),
|
||||||
|
description: t('workflow.nodes.common.errorHandle.failBranch.desc'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const selectedOption = options.find(option => option.value === value)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PortalToFollowElem
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
placement='bottom-end'
|
||||||
|
offset={4}
|
||||||
|
>
|
||||||
|
<PortalToFollowElemTrigger onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setOpen(v => !v)
|
||||||
|
}}>
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
>
|
||||||
|
{selectedOption?.label}
|
||||||
|
<RiArrowDownSLine className='w-3.5 h-3.5' />
|
||||||
|
</Button>
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
<PortalToFollowElemContent className='z-[11]'>
|
||||||
|
<div className='p-1 w-[280px] border-[0.5px] border-components-panel-border rounded-xl bg-components-panel-bg-blur shadow-lg'>
|
||||||
|
{
|
||||||
|
options.map(option => (
|
||||||
|
<div
|
||||||
|
key={option.value}
|
||||||
|
className='flex p-2 pr-3 rounded-lg hover:bg-state-base-hover cursor-pointer'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onSelected(option.value)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='mr-1 w-4 shrink-0'>
|
||||||
|
{
|
||||||
|
value === option.value && (
|
||||||
|
<RiCheckLine className='w-4 h-4 text-text-accent' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className='grow'>
|
||||||
|
<div className='mb-0.5 system-sm-semibold text-text-secondary'>{option.label}</div>
|
||||||
|
<div className='system-xs-regular text-text-tertiary'>{option.description}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorHandleTypeSelector
|
@ -0,0 +1,32 @@
|
|||||||
|
import { RiMindMap } from '@remixicon/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
const FailBranchCard = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='pt-2 px-4'>
|
||||||
|
<div className='p-4 rounded-[10px] bg-workflow-process-bg'>
|
||||||
|
<div className='flex items-center justify-center mb-2 w-8 h-8 rounded-[10px] border-[0.5px] bg-components-card-bg shadow-lg'>
|
||||||
|
<RiMindMap className='w-5 h-5 text-text-tertiary' />
|
||||||
|
</div>
|
||||||
|
<div className='mb-1 system-sm-medium text-text-secondary'>
|
||||||
|
{t('workflow.nodes.common.errorHandle.failBranch.customize')}
|
||||||
|
</div>
|
||||||
|
<div className='system-xs-regular text-text-tertiary'>
|
||||||
|
{t('workflow.nodes.common.errorHandle.failBranch.customizeTip')}
|
||||||
|
|
||||||
|
<a
|
||||||
|
href='https://docs.dify.ai/guides/workflow/error-handling'
|
||||||
|
target='_blank'
|
||||||
|
className='text-text-accent'
|
||||||
|
>
|
||||||
|
{t('workflow.common.learnMore')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FailBranchCard
|
@ -0,0 +1,123 @@
|
|||||||
|
import {
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import { ErrorHandleTypeEnum } from './types'
|
||||||
|
import type { DefaultValueForm } from './types'
|
||||||
|
import { getDefaultValue } from './utils'
|
||||||
|
import type {
|
||||||
|
CommonNodeType,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
import {
|
||||||
|
useEdgesInteractions,
|
||||||
|
useNodeDataUpdate,
|
||||||
|
} from '@/app/components/workflow/hooks'
|
||||||
|
|
||||||
|
export const useDefaultValue = (
|
||||||
|
id: string,
|
||||||
|
) => {
|
||||||
|
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||||
|
const handleFormChange = useCallback((
|
||||||
|
{
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
type,
|
||||||
|
}: DefaultValueForm,
|
||||||
|
data: CommonNodeType,
|
||||||
|
) => {
|
||||||
|
const default_value = data.default_value || []
|
||||||
|
const index = default_value.findIndex(form => form.key === key)
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
const newDefaultValue = [...default_value]
|
||||||
|
newDefaultValue[index].value = value
|
||||||
|
handleNodeDataUpdateWithSyncDraft({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
default_value: newDefaultValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNodeDataUpdateWithSyncDraft({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
default_value: [
|
||||||
|
...default_value,
|
||||||
|
{
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
type,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [handleNodeDataUpdateWithSyncDraft, id])
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleFormChange,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useErrorHandle = (
|
||||||
|
id: string,
|
||||||
|
data: CommonNodeType,
|
||||||
|
) => {
|
||||||
|
const initCollapsed = useMemo(() => {
|
||||||
|
if (data.error_strategy === ErrorHandleTypeEnum.none)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}, [data.error_strategy])
|
||||||
|
const [collapsed, setCollapsed] = useState(initCollapsed)
|
||||||
|
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||||
|
const { handleEdgeDeleteByDeleteBranch } = useEdgesInteractions()
|
||||||
|
|
||||||
|
const handleErrorHandleTypeChange = useCallback((value: ErrorHandleTypeEnum, data: CommonNodeType) => {
|
||||||
|
if (data.error_strategy === value)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (value === ErrorHandleTypeEnum.none) {
|
||||||
|
handleNodeDataUpdateWithSyncDraft({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
error_strategy: undefined,
|
||||||
|
default_value: undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setCollapsed(true)
|
||||||
|
handleEdgeDeleteByDeleteBranch(id, ErrorHandleTypeEnum.failBranch)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === ErrorHandleTypeEnum.failBranch) {
|
||||||
|
handleNodeDataUpdateWithSyncDraft({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
error_strategy: value,
|
||||||
|
default_value: undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setCollapsed(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === ErrorHandleTypeEnum.defaultValue) {
|
||||||
|
handleNodeDataUpdateWithSyncDraft({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
error_strategy: value,
|
||||||
|
default_value: getDefaultValue(data),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setCollapsed(false)
|
||||||
|
handleEdgeDeleteByDeleteBranch(id, ErrorHandleTypeEnum.failBranch)
|
||||||
|
}
|
||||||
|
}, [id, handleNodeDataUpdateWithSyncDraft, handleEdgeDeleteByDeleteBranch])
|
||||||
|
|
||||||
|
return {
|
||||||
|
collapsed,
|
||||||
|
setCollapsed,
|
||||||
|
handleErrorHandleTypeChange,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import type { VarType } from '@/app/components/workflow/types'
|
||||||
|
|
||||||
|
export enum ErrorHandleTypeEnum {
|
||||||
|
none = 'none',
|
||||||
|
failBranch = 'fail-branch',
|
||||||
|
defaultValue = 'default-value',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DefaultValueForm = {
|
||||||
|
key: string
|
||||||
|
type: VarType
|
||||||
|
value?: any
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||||
|
import {
|
||||||
|
BlockEnum,
|
||||||
|
VarType,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types'
|
||||||
|
|
||||||
|
const getDefaultValueByType = (type: VarType) => {
|
||||||
|
if (type === VarType.string)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if (type === VarType.number)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if (type === VarType.object)
|
||||||
|
return '{}'
|
||||||
|
|
||||||
|
if (type === VarType.arrayObject || type === VarType.arrayString || type === VarType.arrayNumber || type === VarType.arrayFile)
|
||||||
|
return '[]'
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getDefaultValue = (data: CommonNodeType) => {
|
||||||
|
const { type } = data
|
||||||
|
|
||||||
|
if (type === BlockEnum.LLM) {
|
||||||
|
return [{
|
||||||
|
key: 'text',
|
||||||
|
type: VarType.string,
|
||||||
|
value: getDefaultValueByType(VarType.string),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === BlockEnum.HttpRequest) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'body',
|
||||||
|
type: VarType.string,
|
||||||
|
value: getDefaultValueByType(VarType.string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'status_code',
|
||||||
|
type: VarType.number,
|
||||||
|
value: getDefaultValueByType(VarType.number),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'headers',
|
||||||
|
type: VarType.object,
|
||||||
|
value: getDefaultValueByType(VarType.object),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === BlockEnum.Tool) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'text',
|
||||||
|
type: VarType.string,
|
||||||
|
value: getDefaultValueByType(VarType.string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'json',
|
||||||
|
type: VarType.arrayObject,
|
||||||
|
value: getDefaultValueByType(VarType.arrayObject),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === BlockEnum.Code) {
|
||||||
|
const { outputs } = data as CodeNodeType
|
||||||
|
|
||||||
|
return Object.keys(outputs).map((key) => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
type: outputs[key].type,
|
||||||
|
value: getDefaultValueByType(outputs[key].type),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -24,12 +25,14 @@ type AddProps = {
|
|||||||
nodeData: CommonNodeType
|
nodeData: CommonNodeType
|
||||||
sourceHandle: string
|
sourceHandle: string
|
||||||
isParallel?: boolean
|
isParallel?: boolean
|
||||||
|
isFailBranch?: boolean
|
||||||
}
|
}
|
||||||
const Add = ({
|
const Add = ({
|
||||||
nodeId,
|
nodeId,
|
||||||
nodeData,
|
nodeData,
|
||||||
sourceHandle,
|
sourceHandle,
|
||||||
isParallel,
|
isParallel,
|
||||||
|
isFailBranch,
|
||||||
}: AddProps) => {
|
}: AddProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
@ -58,6 +61,15 @@ const Add = ({
|
|||||||
setOpen(newOpen)
|
setOpen(newOpen)
|
||||||
}, [checkParallelLimit, nodeId, sourceHandle])
|
}, [checkParallelLimit, nodeId, sourceHandle])
|
||||||
|
|
||||||
|
const tip = useMemo(() => {
|
||||||
|
if (isFailBranch)
|
||||||
|
return t('workflow.common.addFailureBranch')
|
||||||
|
|
||||||
|
if (isParallel)
|
||||||
|
return t('workflow.common.addParallelNode')
|
||||||
|
|
||||||
|
return t('workflow.panel.selectNextStep')
|
||||||
|
}, [isFailBranch, isParallel, t])
|
||||||
const renderTrigger = useCallback((open: boolean) => {
|
const renderTrigger = useCallback((open: boolean) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -72,15 +84,11 @@ const Add = ({
|
|||||||
<RiAddLine className='w-3 h-3' />
|
<RiAddLine className='w-3 h-3' />
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center uppercase'>
|
<div className='flex items-center uppercase'>
|
||||||
{
|
{tip}
|
||||||
isParallel
|
|
||||||
? t('workflow.common.addParallelNode')
|
|
||||||
: t('workflow.panel.selectNextStep')
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}, [t, nodesReadOnly, isParallel])
|
}, [nodesReadOnly, tip])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlockSelector
|
<BlockSelector
|
||||||
|
@ -4,6 +4,7 @@ import type {
|
|||||||
CommonNodeType,
|
CommonNodeType,
|
||||||
Node,
|
Node,
|
||||||
} from '@/app/components/workflow/types'
|
} from '@/app/components/workflow/types'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type ContainerProps = {
|
type ContainerProps = {
|
||||||
nodeId: string
|
nodeId: string
|
||||||
@ -11,6 +12,7 @@ type ContainerProps = {
|
|||||||
sourceHandle: string
|
sourceHandle: string
|
||||||
nextNodes: Node[]
|
nextNodes: Node[]
|
||||||
branchName?: string
|
branchName?: string
|
||||||
|
isFailBranch?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = ({
|
const Container = ({
|
||||||
@ -19,13 +21,20 @@ const Container = ({
|
|||||||
sourceHandle,
|
sourceHandle,
|
||||||
nextNodes,
|
nextNodes,
|
||||||
branchName,
|
branchName,
|
||||||
|
isFailBranch,
|
||||||
}: ContainerProps) => {
|
}: ContainerProps) => {
|
||||||
return (
|
return (
|
||||||
<div className='p-0.5 space-y-0.5 rounded-[10px] bg-background-section-burn'>
|
<div className={cn(
|
||||||
|
'p-0.5 space-y-0.5 rounded-[10px] bg-background-section-burn',
|
||||||
|
isFailBranch && 'border-[0.5px] border-state-warning-hover-alt bg-state-warning-hover',
|
||||||
|
)}>
|
||||||
{
|
{
|
||||||
branchName && (
|
branchName && (
|
||||||
<div
|
<div
|
||||||
className='flex items-center px-2 system-2xs-semibold-uppercase text-text-tertiary truncate'
|
className={cn(
|
||||||
|
'flex items-center px-2 system-2xs-semibold-uppercase text-text-tertiary truncate',
|
||||||
|
isFailBranch && 'text-text-warning',
|
||||||
|
)}
|
||||||
title={branchName}
|
title={branchName}
|
||||||
>
|
>
|
||||||
{branchName}
|
{branchName}
|
||||||
@ -44,6 +53,7 @@ const Container = ({
|
|||||||
}
|
}
|
||||||
<Add
|
<Add
|
||||||
isParallel={!!nextNodes.length}
|
isParallel={!!nextNodes.length}
|
||||||
|
isFailBranch={isFailBranch}
|
||||||
nodeId={nodeId}
|
nodeId={nodeId}
|
||||||
nodeData={nodeData}
|
nodeData={nodeData}
|
||||||
sourceHandle={sourceHandle}
|
sourceHandle={sourceHandle}
|
||||||
|
@ -14,6 +14,8 @@ import type {
|
|||||||
import { BlockEnum } from '../../../../types'
|
import { BlockEnum } from '../../../../types'
|
||||||
import Line from './line'
|
import Line from './line'
|
||||||
import Container from './container'
|
import Container from './container'
|
||||||
|
import { hasErrorHandleNode } from '@/app/components/workflow/utils'
|
||||||
|
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
|
|
||||||
type NextStepProps = {
|
type NextStepProps = {
|
||||||
selectedNode: Node
|
selectedNode: Node
|
||||||
@ -28,25 +30,54 @@ const NextStep = ({
|
|||||||
const branches = useMemo(() => {
|
const branches = useMemo(() => {
|
||||||
return data._targetBranches || []
|
return data._targetBranches || []
|
||||||
}, [data])
|
}, [data])
|
||||||
const nodeWithBranches = data.type === BlockEnum.IfElse || data.type === BlockEnum.QuestionClassifier
|
|
||||||
const edges = useEdges()
|
const edges = useEdges()
|
||||||
const outgoers = getOutgoers(selectedNode as Node, store.getState().getNodes(), edges)
|
const outgoers = getOutgoers(selectedNode as Node, store.getState().getNodes(), edges)
|
||||||
const connectedEdges = getConnectedEdges([selectedNode] as Node[], edges).filter(edge => edge.source === selectedNode!.id)
|
const connectedEdges = getConnectedEdges([selectedNode] as Node[], edges).filter(edge => edge.source === selectedNode!.id)
|
||||||
|
|
||||||
const branchesOutgoers = useMemo(() => {
|
const list = useMemo(() => {
|
||||||
if (!branches?.length)
|
let items = []
|
||||||
return []
|
if (branches?.length) {
|
||||||
|
items = branches.map((branch, index) => {
|
||||||
return branches.map((branch) => {
|
|
||||||
const connected = connectedEdges.filter(edge => edge.sourceHandle === branch.id)
|
const connected = connectedEdges.filter(edge => edge.sourceHandle === branch.id)
|
||||||
const nextNodes = connected.map(edge => outgoers.find(outgoer => outgoer.id === edge.target)!)
|
const nextNodes = connected.map(edge => outgoers.find(outgoer => outgoer.id === edge.target)!)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
branch,
|
branch: {
|
||||||
|
...branch,
|
||||||
|
name: data.type === BlockEnum.QuestionClassifier ? `${t('workflow.nodes.questionClassifiers.class')} ${index + 1}` : branch.name,
|
||||||
|
},
|
||||||
nextNodes,
|
nextNodes,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [branches, connectedEdges, outgoers])
|
}
|
||||||
|
else {
|
||||||
|
const connected = connectedEdges.filter(edge => edge.sourceHandle === 'source')
|
||||||
|
const nextNodes = connected.map(edge => outgoers.find(outgoer => outgoer.id === edge.target)!)
|
||||||
|
|
||||||
|
items = [{
|
||||||
|
branch: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
nextNodes,
|
||||||
|
}]
|
||||||
|
|
||||||
|
if (data.error_strategy === ErrorHandleTypeEnum.failBranch && hasErrorHandleNode(data.type)) {
|
||||||
|
const connected = connectedEdges.filter(edge => edge.sourceHandle === ErrorHandleTypeEnum.failBranch)
|
||||||
|
const nextNodes = connected.map(edge => outgoers.find(outgoer => outgoer.id === edge.target)!)
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
branch: {
|
||||||
|
id: ErrorHandleTypeEnum.failBranch,
|
||||||
|
name: t('workflow.common.onFailure'),
|
||||||
|
},
|
||||||
|
nextNodes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}, [branches, connectedEdges, data.error_strategy, data.type, outgoers, t])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex py-1'>
|
<div className='flex py-1'>
|
||||||
@ -57,34 +88,23 @@ const NextStep = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Line
|
<Line
|
||||||
list={nodeWithBranches ? branchesOutgoers.map(item => item.nextNodes.length + 1) : [1]}
|
list={list.length ? list.map(item => item.nextNodes.length + 1) : [1]}
|
||||||
/>
|
/>
|
||||||
<div className='grow space-y-2'>
|
<div className='grow space-y-2'>
|
||||||
{
|
{
|
||||||
!nodeWithBranches && (
|
list.map((item, index) => {
|
||||||
<Container
|
|
||||||
nodeId={selectedNode!.id}
|
|
||||||
nodeData={selectedNode!.data}
|
|
||||||
sourceHandle='source'
|
|
||||||
nextNodes={outgoers}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
nodeWithBranches && (
|
|
||||||
branchesOutgoers.map((item, index) => {
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
key={item.branch.id}
|
key={index}
|
||||||
nodeId={selectedNode!.id}
|
nodeId={selectedNode!.id}
|
||||||
nodeData={selectedNode!.data}
|
nodeData={selectedNode!.data}
|
||||||
sourceHandle={item.branch.id}
|
sourceHandle={item.branch.id}
|
||||||
nextNodes={item.nextNodes}
|
nextNodes={item.nextNodes}
|
||||||
branchName={item.branch.name || `${t('workflow.nodes.questionClassifiers.class')} ${index + 1}`}
|
branchName={item.branch.name}
|
||||||
|
isFailBranch={item.branch.id === ErrorHandleTypeEnum.failBranch}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,10 @@ import {
|
|||||||
Position,
|
Position,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { BlockEnum } from '../../../types'
|
import {
|
||||||
|
BlockEnum,
|
||||||
|
NodeRunningStatus,
|
||||||
|
} from '../../../types'
|
||||||
import type { Node } from '../../../types'
|
import type { Node } from '../../../types'
|
||||||
import BlockSelector from '../../../block-selector'
|
import BlockSelector from '../../../block-selector'
|
||||||
import type { ToolDefaultValue } from '../../../block-selector/types'
|
import type { ToolDefaultValue } from '../../../block-selector/types'
|
||||||
@ -24,11 +27,13 @@ import {
|
|||||||
import {
|
import {
|
||||||
useStore,
|
useStore,
|
||||||
} from '../../../store'
|
} from '../../../store'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type NodeHandleProps = {
|
type NodeHandleProps = {
|
||||||
handleId: string
|
handleId: string
|
||||||
handleClassName?: string
|
handleClassName?: string
|
||||||
nodeSelectorClassName?: string
|
nodeSelectorClassName?: string
|
||||||
|
showExceptionStatus?: boolean
|
||||||
} & Pick<Node, 'id' | 'data'>
|
} & Pick<Node, 'id' | 'data'>
|
||||||
|
|
||||||
export const NodeTargetHandle = memo(({
|
export const NodeTargetHandle = memo(({
|
||||||
@ -72,14 +77,17 @@ export const NodeTargetHandle = memo(({
|
|||||||
id={handleId}
|
id={handleId}
|
||||||
type='target'
|
type='target'
|
||||||
position={Position.Left}
|
position={Position.Left}
|
||||||
className={`
|
className={cn(
|
||||||
!w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]
|
'!w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]',
|
||||||
after:absolute after:w-0.5 after:h-2 after:left-1.5 after:top-1 after:bg-primary-500
|
'after:absolute after:w-0.5 after:h-2 after:left-1.5 after:top-1 after:bg-workflow-link-line-handle',
|
||||||
hover:scale-125 transition-all
|
'hover:scale-125 transition-all',
|
||||||
${!connected && 'after:opacity-0'}
|
data._runningStatus === NodeRunningStatus.Succeeded && 'after:bg-workflow-link-line-success-handle',
|
||||||
${data.type === BlockEnum.Start && 'opacity-0'}
|
data._runningStatus === NodeRunningStatus.Failed && 'after:bg-workflow-link-line-error-handle',
|
||||||
${handleClassName}
|
data._runningStatus === NodeRunningStatus.Exception && 'after:bg-workflow-link-line-failure-handle',
|
||||||
`}
|
!connected && 'after:opacity-0',
|
||||||
|
data.type === BlockEnum.Start && 'opacity-0',
|
||||||
|
handleClassName,
|
||||||
|
)}
|
||||||
isConnectable={isConnectable}
|
isConnectable={isConnectable}
|
||||||
onClick={handleHandleClick}
|
onClick={handleHandleClick}
|
||||||
>
|
>
|
||||||
@ -114,6 +122,7 @@ export const NodeSourceHandle = memo(({
|
|||||||
handleId,
|
handleId,
|
||||||
handleClassName,
|
handleClassName,
|
||||||
nodeSelectorClassName,
|
nodeSelectorClassName,
|
||||||
|
showExceptionStatus,
|
||||||
}: NodeHandleProps) => {
|
}: NodeHandleProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const notInitialWorkflow = useStore(s => s.notInitialWorkflow)
|
const notInitialWorkflow = useStore(s => s.notInitialWorkflow)
|
||||||
@ -157,13 +166,16 @@ export const NodeSourceHandle = memo(({
|
|||||||
id={handleId}
|
id={handleId}
|
||||||
type='source'
|
type='source'
|
||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
className={`
|
className={cn(
|
||||||
group/handle !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]
|
'group/handle !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]',
|
||||||
after:absolute after:w-0.5 after:h-2 after:right-1.5 after:top-1 after:bg-primary-500
|
'after:absolute after:w-0.5 after:h-2 after:right-1.5 after:top-1 after:bg-workflow-link-line-handle',
|
||||||
hover:scale-125 transition-all
|
'hover:scale-125 transition-all',
|
||||||
${!connected && 'after:opacity-0'}
|
data._runningStatus === NodeRunningStatus.Succeeded && 'after:bg-workflow-link-line-success-handle',
|
||||||
${handleClassName}
|
data._runningStatus === NodeRunningStatus.Failed && 'after:bg-workflow-link-line-error-handle',
|
||||||
`}
|
showExceptionStatus && data._runningStatus === NodeRunningStatus.Exception && 'after:bg-workflow-link-line-failure-handle',
|
||||||
|
!connected && 'after:opacity-0',
|
||||||
|
handleClassName,
|
||||||
|
)}
|
||||||
isConnectable={isConnectable}
|
isConnectable={isConnectable}
|
||||||
onClick={handleHandleClick}
|
onClick={handleHandleClick}
|
||||||
>
|
>
|
||||||
|
@ -2,11 +2,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useBoolean } from 'ahooks'
|
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||||
import {
|
|
||||||
RiArrowDownSLine,
|
|
||||||
} from '@remixicon/react'
|
|
||||||
import cn from '@/utils/classnames'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -15,28 +11,14 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const OutputVars: FC<Props> = ({
|
const OutputVars: FC<Props> = ({
|
||||||
className,
|
|
||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [isFold, {
|
|
||||||
toggle: toggleFold,
|
|
||||||
}] = useBoolean(true)
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<FieldCollapse title={title || t('workflow.nodes.common.outputVars')}>
|
||||||
<div
|
|
||||||
onClick={toggleFold}
|
|
||||||
className={cn(className, 'flex justify-between system-sm-semibold-uppercase text-text-secondary cursor-pointer')}>
|
|
||||||
<div>{title || t('workflow.nodes.common.outputVars')}</div>
|
|
||||||
<RiArrowDownSLine className='w-4 h-4 text-text-tertiary transform transition-transform' style={{ transform: isFold ? 'rotate(-90deg)' : 'rotate(0deg)' }} />
|
|
||||||
</div>
|
|
||||||
{!isFold && (
|
|
||||||
<div className='mt-2 space-y-1'>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</FieldCollapse>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
type VarItemProps = {
|
type VarItemProps = {
|
||||||
|
@ -17,6 +17,7 @@ import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others
|
|||||||
import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||||
|
|
||||||
type VariableTagProps = {
|
type VariableTagProps = {
|
||||||
valueSelector: ValueSelector
|
valueSelector: ValueSelector
|
||||||
@ -45,6 +46,7 @@ const VariableTag = ({
|
|||||||
const isValid = Boolean(node) || isEnv || isChatVar
|
const isValid = Boolean(node) || isEnv || isChatVar
|
||||||
|
|
||||||
const variableName = isSystemVar(valueSelector) ? valueSelector.slice(0).join('.') : valueSelector.slice(1).join('.')
|
const variableName = isSystemVar(valueSelector) ? valueSelector.slice(0).join('.') : valueSelector.slice(1).join('.')
|
||||||
|
const isException = isExceptionVariable(variableName, node?.data.type)
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
@ -67,12 +69,12 @@ const VariableTag = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Line3 className='shrink-0 mx-0.5' />
|
<Line3 className='shrink-0 mx-0.5' />
|
||||||
<Variable02 className='shrink-0 mr-0.5 w-3.5 h-3.5 text-text-accent' />
|
<Variable02 className={cn('shrink-0 mr-0.5 w-3.5 h-3.5 text-text-accent', isException && 'text-text-warning')} />
|
||||||
</>)}
|
</>)}
|
||||||
{isEnv && <Env className='shrink-0 mr-0.5 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
{isEnv && <Env className='shrink-0 mr-0.5 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||||
<div
|
<div
|
||||||
className={cn('truncate ml-0.5 text-text-accent font-medium', (isEnv || isChatVar) && 'text-text-secondary')}
|
className={cn('truncate ml-0.5 text-text-accent font-medium', (isEnv || isChatVar) && 'text-text-secondary', isException && 'text-text-warning')}
|
||||||
title={variableName}
|
title={variableName}
|
||||||
>
|
>
|
||||||
{variableName}
|
{variableName}
|
||||||
|
@ -315,6 +315,24 @@ const formatItem = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { error_strategy } = data
|
||||||
|
|
||||||
|
if (error_strategy) {
|
||||||
|
res.vars = [
|
||||||
|
...res.vars,
|
||||||
|
{
|
||||||
|
variable: 'error_message',
|
||||||
|
type: VarType.string,
|
||||||
|
isException: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variable: 'error_type',
|
||||||
|
type: VarType.string,
|
||||||
|
isException: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
const selector = [id]
|
const selector = [id]
|
||||||
res.vars = res.vars.filter((v) => {
|
res.vars = res.vars.filter((v) => {
|
||||||
const isCurrentMatched = filterVar(v, (() => {
|
const isCurrentMatched = filterVar(v, (() => {
|
||||||
|
@ -36,6 +36,7 @@ import TypeSelector from '@/app/components/workflow/nodes/_base/components/selec
|
|||||||
import AddButton from '@/app/components/base/button/add-button'
|
import AddButton from '@/app/components/base/button/add-button'
|
||||||
import Badge from '@/app/components/base/badge'
|
import Badge from '@/app/components/base/badge'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||||
|
|
||||||
const TRIGGER_DEFAULT_WIDTH = 227
|
const TRIGGER_DEFAULT_WIDTH = 227
|
||||||
|
|
||||||
@ -224,16 +225,18 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
isConstant: !!isConstant,
|
isConstant: !!isConstant,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { isEnv, isChatVar, isValidVar } = useMemo(() => {
|
const { isEnv, isChatVar, isValidVar, isException } = useMemo(() => {
|
||||||
const isEnv = isENV(value as ValueSelector)
|
const isEnv = isENV(value as ValueSelector)
|
||||||
const isChatVar = isConversationVar(value as ValueSelector)
|
const isChatVar = isConversationVar(value as ValueSelector)
|
||||||
const isValidVar = Boolean(outputVarNode) || isEnv || isChatVar
|
const isValidVar = Boolean(outputVarNode) || isEnv || isChatVar
|
||||||
|
const isException = isExceptionVariable(varName, outputVarNode?.type)
|
||||||
return {
|
return {
|
||||||
isEnv,
|
isEnv,
|
||||||
isChatVar,
|
isChatVar,
|
||||||
isValidVar,
|
isValidVar,
|
||||||
|
isException,
|
||||||
}
|
}
|
||||||
}, [value, outputVarNode])
|
}, [value, outputVarNode, varName])
|
||||||
|
|
||||||
// 8(left/right-padding) + 14(icon) + 4 + 14 + 2 = 42 + 17 buff
|
// 8(left/right-padding) + 14(icon) + 4 + 14 + 2 = 42 + 17 buff
|
||||||
const availableWidth = triggerWidth - 56
|
const availableWidth = triggerWidth - 56
|
||||||
@ -335,7 +338,7 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
{!hasValue && <Variable02 className='w-3.5 h-3.5' />}
|
{!hasValue && <Variable02 className='w-3.5 h-3.5' />}
|
||||||
{isEnv && <Env className='w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
{isEnv && <Env className='w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||||
<div className={cn('ml-0.5 text-xs font-medium truncate', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700')} title={varName} style={{
|
<div className={cn('ml-0.5 text-xs font-medium truncate', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700', isException && 'text-text-warning')} title={varName} style={{
|
||||||
maxWidth: maxVarNameWidth,
|
maxWidth: maxVarNameWidth,
|
||||||
}}>{varName}</div>
|
}}>{varName}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,6 +37,7 @@ type ItemProps = {
|
|||||||
onHovering?: (value: boolean) => void
|
onHovering?: (value: boolean) => void
|
||||||
itemWidth?: number
|
itemWidth?: number
|
||||||
isSupportFileVar?: boolean
|
isSupportFileVar?: boolean
|
||||||
|
isException?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Item: FC<ItemProps> = ({
|
const Item: FC<ItemProps> = ({
|
||||||
@ -48,6 +49,7 @@ const Item: FC<ItemProps> = ({
|
|||||||
onHovering,
|
onHovering,
|
||||||
itemWidth,
|
itemWidth,
|
||||||
isSupportFileVar,
|
isSupportFileVar,
|
||||||
|
isException,
|
||||||
}) => {
|
}) => {
|
||||||
const isFile = itemData.type === VarType.file
|
const isFile = itemData.type === VarType.file
|
||||||
const isObj = ([VarType.object, VarType.file].includes(itemData.type) && itemData.children && itemData.children.length > 0)
|
const isObj = ([VarType.object, VarType.file].includes(itemData.type) && itemData.children && itemData.children.length > 0)
|
||||||
@ -109,7 +111,7 @@ const Item: FC<ItemProps> = ({
|
|||||||
onClick={handleChosen}
|
onClick={handleChosen}
|
||||||
>
|
>
|
||||||
<div className='flex items-center w-0 grow'>
|
<div className='flex items-center w-0 grow'>
|
||||||
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5 text-text-accent' />}
|
{!isEnv && !isChatVar && <Variable02 className={cn('shrink-0 w-3.5 h-3.5 text-text-accent', isException && 'text-text-warning')} />}
|
||||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||||
{!isEnv && !isChatVar && (
|
{!isEnv && !isChatVar && (
|
||||||
@ -216,6 +218,7 @@ const ObjectChildren: FC<ObjectChildrenProps> = ({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onHovering={setIsChildrenHovering}
|
onHovering={setIsChildrenHovering}
|
||||||
isSupportFileVar={isSupportFileVar}
|
isSupportFileVar={isSupportFileVar}
|
||||||
|
isException={v.isException}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -312,6 +315,7 @@ const VarReferenceVars: FC<Props> = ({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
itemWidth={itemWidth}
|
itemWidth={itemWidth}
|
||||||
isSupportFileVar={isSupportFileVar}
|
isSupportFileVar={isSupportFileVar}
|
||||||
|
isException={v.isException}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>))
|
</div>))
|
||||||
|
@ -1,12 +1,22 @@
|
|||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import { type OutputVar } from '../../code/types'
|
import type {
|
||||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
CodeNodeType,
|
||||||
import { VarType } from '@/app/components/workflow/types'
|
OutputVar,
|
||||||
|
} from '../../code/types'
|
||||||
|
import type {
|
||||||
|
ValueSelector,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
import {
|
||||||
|
BlockEnum,
|
||||||
|
VarType,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
import {
|
import {
|
||||||
useWorkflow,
|
useWorkflow,
|
||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
|
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
|
import { getDefaultValue } from '@/app/components/workflow/nodes/_base/components/error-handle/utils'
|
||||||
|
|
||||||
type Params<T> = {
|
type Params<T> = {
|
||||||
id: string
|
id: string
|
||||||
@ -29,6 +39,9 @@ function useOutputVarList<T>({
|
|||||||
const handleVarsChange = useCallback((newVars: OutputVar, changedIndex?: number, newKey?: string) => {
|
const handleVarsChange = useCallback((newVars: OutputVar, changedIndex?: number, newKey?: string) => {
|
||||||
const newInputs = produce(inputs, (draft: any) => {
|
const newInputs = produce(inputs, (draft: any) => {
|
||||||
draft[varKey] = newVars
|
draft[varKey] = newVars
|
||||||
|
|
||||||
|
if ((inputs as CodeNodeType).type === BlockEnum.Code && (inputs as CodeNodeType).error_strategy === ErrorHandleTypeEnum.defaultValue && varKey === 'outputs')
|
||||||
|
draft.default_value = getDefaultValue(draft as any)
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
|
|
||||||
@ -59,6 +72,9 @@ function useOutputVarList<T>({
|
|||||||
children: null,
|
children: null,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((inputs as CodeNodeType).type === BlockEnum.Code && (inputs as CodeNodeType).error_strategy === ErrorHandleTypeEnum.defaultValue && varKey === 'outputs')
|
||||||
|
draft.default_value = getDefaultValue(draft as any)
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
onOutputKeyOrdersChange([...outputKeyOrders, newKey])
|
onOutputKeyOrdersChange([...outputKeyOrders, newKey])
|
||||||
@ -84,6 +100,9 @@ function useOutputVarList<T>({
|
|||||||
|
|
||||||
const newInputs = produce(inputs, (draft: any) => {
|
const newInputs = produce(inputs, (draft: any) => {
|
||||||
delete draft[varKey][key]
|
delete draft[varKey][key]
|
||||||
|
|
||||||
|
if ((inputs as CodeNodeType).type === BlockEnum.Code && (inputs as CodeNodeType).error_strategy === ErrorHandleTypeEnum.defaultValue && varKey === 'outputs')
|
||||||
|
draft.default_value = getDefaultValue(draft as any)
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
onOutputKeyOrdersChange(outputKeyOrders.filter((_, i) => i !== index))
|
onOutputKeyOrdersChange(outputKeyOrders.filter((_, i) => i !== index))
|
||||||
|
@ -10,8 +10,9 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
RiCheckboxCircleLine,
|
RiAlertFill,
|
||||||
RiErrorWarningLine,
|
RiCheckboxCircleFill,
|
||||||
|
RiErrorWarningFill,
|
||||||
RiLoader2Line,
|
RiLoader2Line,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -24,6 +25,7 @@ import {
|
|||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
useToolIcon,
|
useToolIcon,
|
||||||
} from '../../hooks'
|
} from '../../hooks'
|
||||||
|
import { hasErrorHandleNode } from '../../utils'
|
||||||
import { useNodeIterationInteractions } from '../iteration/use-interactions'
|
import { useNodeIterationInteractions } from '../iteration/use-interactions'
|
||||||
import type { IterationNodeType } from '../iteration/types'
|
import type { IterationNodeType } from '../iteration/types'
|
||||||
import {
|
import {
|
||||||
@ -32,6 +34,7 @@ import {
|
|||||||
} from './components/node-handle'
|
} from './components/node-handle'
|
||||||
import NodeResizer from './components/node-resizer'
|
import NodeResizer from './components/node-resizer'
|
||||||
import NodeControl from './components/node-control'
|
import NodeControl from './components/node-control'
|
||||||
|
import ErrorHandleOnNode from './components/error-handle/error-handle-on-node'
|
||||||
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
@ -71,11 +74,13 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||||||
showRunningBorder,
|
showRunningBorder,
|
||||||
showSuccessBorder,
|
showSuccessBorder,
|
||||||
showFailedBorder,
|
showFailedBorder,
|
||||||
|
showExceptionBorder,
|
||||||
} = useMemo(() => {
|
} = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
showRunningBorder: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
|
showRunningBorder: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
|
||||||
showSuccessBorder: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder,
|
showSuccessBorder: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder,
|
||||||
showFailedBorder: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
|
showFailedBorder: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
|
||||||
|
showExceptionBorder: data._runningStatus === NodeRunningStatus.Exception && !showSelectedBorder,
|
||||||
}
|
}
|
||||||
}, [data._runningStatus, showSelectedBorder])
|
}, [data._runningStatus, showSelectedBorder])
|
||||||
|
|
||||||
@ -85,6 +90,7 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||||||
'flex border-[2px] rounded-2xl',
|
'flex border-[2px] rounded-2xl',
|
||||||
showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
|
showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
|
||||||
!showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
|
!showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
|
||||||
|
data._waitingRun && 'opacity-70',
|
||||||
)}
|
)}
|
||||||
ref={nodeRef}
|
ref={nodeRef}
|
||||||
style={{
|
style={{
|
||||||
@ -99,9 +105,10 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||||||
data.type !== BlockEnum.Iteration && 'w-[240px] bg-workflow-block-bg',
|
data.type !== BlockEnum.Iteration && 'w-[240px] bg-workflow-block-bg',
|
||||||
data.type === BlockEnum.Iteration && 'flex flex-col w-full h-full bg-[#fcfdff]/80',
|
data.type === BlockEnum.Iteration && 'flex flex-col w-full h-full bg-[#fcfdff]/80',
|
||||||
!data._runningStatus && 'hover:shadow-lg',
|
!data._runningStatus && 'hover:shadow-lg',
|
||||||
showRunningBorder && '!border-primary-500',
|
showRunningBorder && '!border-state-accent-solid',
|
||||||
showSuccessBorder && '!border-[#12B76A]',
|
showSuccessBorder && '!border-state-success-solid',
|
||||||
showFailedBorder && '!border-[#F04438]',
|
showFailedBorder && '!border-state-destructive-solid',
|
||||||
|
showExceptionBorder && '!border-state-warning-solid',
|
||||||
data._isBundled && '!shadow-lg',
|
data._isBundled && '!shadow-lg',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -192,24 +199,29 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && (
|
data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && (
|
||||||
<div className='mr-1.5 text-xs font-medium text-primary-600'>
|
<div className='mr-1.5 text-xs font-medium text-text-accent'>
|
||||||
{data._iterationIndex > data._iterationLength ? data._iterationLength : data._iterationIndex}/{data._iterationLength}
|
{data._iterationIndex > data._iterationLength ? data._iterationLength : data._iterationIndex}/{data._iterationLength}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running) && (
|
(data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running) && (
|
||||||
<RiLoader2Line className='w-3.5 h-3.5 text-primary-600 animate-spin' />
|
<RiLoader2Line className='w-3.5 h-3.5 text-text-accent animate-spin' />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
data._runningStatus === NodeRunningStatus.Succeeded && (
|
data._runningStatus === NodeRunningStatus.Succeeded && (
|
||||||
<RiCheckboxCircleLine className='w-3.5 h-3.5 text-[#12B76A]' />
|
<RiCheckboxCircleFill className='w-3.5 h-3.5 text-text-success' />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
data._runningStatus === NodeRunningStatus.Failed && (
|
data._runningStatus === NodeRunningStatus.Failed && (
|
||||||
<RiErrorWarningLine className='w-3.5 h-3.5 text-[#F04438]' />
|
<RiErrorWarningFill className='w-3.5 h-3.5 text-text-destructive' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
data._runningStatus === NodeRunningStatus.Exception && (
|
||||||
|
<RiAlertFill className='w-3.5 h-3.5 text-text-warning-secondary' />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -225,6 +237,14 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
hasErrorHandleNode(data.type) && (
|
||||||
|
<ErrorHandleOnNode
|
||||||
|
id={id}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
data.desc && data.type !== BlockEnum.Iteration && (
|
data.desc && data.type !== BlockEnum.Iteration && (
|
||||||
<div className='px-3 pt-1 pb-2 system-xs-regular text-text-tertiary whitespace-pre-line break-words'>
|
<div className='px-3 pt-1 pb-2 system-xs-regular text-text-tertiary whitespace-pre-line break-words'>
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
DescriptionInput,
|
DescriptionInput,
|
||||||
TitleInput,
|
TitleInput,
|
||||||
} from './components/title-description-input'
|
} from './components/title-description-input'
|
||||||
|
import ErrorHandleOnPanel from './components/error-handle/error-handle-on-panel'
|
||||||
import { useResizePanel } from './hooks/use-resize-panel'
|
import { useResizePanel } from './hooks/use-resize-panel'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
@ -34,7 +35,10 @@ import {
|
|||||||
useWorkflow,
|
useWorkflow,
|
||||||
useWorkflowHistory,
|
useWorkflowHistory,
|
||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
import { canRunBySingle } from '@/app/components/workflow/utils'
|
import {
|
||||||
|
canRunBySingle,
|
||||||
|
hasErrorHandleNode,
|
||||||
|
} from '@/app/components/workflow/utils'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import type { Node } from '@/app/components/workflow/types'
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
@ -161,9 +165,17 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='py-2'>
|
<div>
|
||||||
{cloneElement(children, { id, data })}
|
{cloneElement(children, { id, data })}
|
||||||
</div>
|
</div>
|
||||||
|
{
|
||||||
|
hasErrorHandleNode(data.type) && (
|
||||||
|
<ErrorHandleOnPanel
|
||||||
|
id={id}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
!!availableNextBlocks.length && (
|
!!availableNextBlocks.length && (
|
||||||
<div className='p-4 border-t-[0.5px] border-t-black/5'>
|
<div className='p-4 border-t-[0.5px] border-t-black/5'>
|
||||||
|
@ -72,7 +72,7 @@ const Panel: FC<NodePanelProps<DocExtractorNodeType>> = ({
|
|||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
<Split />
|
<Split />
|
||||||
<div className='px-4 pt-4 pb-2'>
|
<div>
|
||||||
<OutputVars>
|
<OutputVars>
|
||||||
<VarItem
|
<VarItem
|
||||||
name='text'
|
name='text'
|
||||||
|
@ -2,11 +2,9 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useBoolean } from 'ahooks'
|
|
||||||
import type { Timeout as TimeoutPayloadType } from '../../types'
|
import type { Timeout as TimeoutPayloadType } from '../../types'
|
||||||
import cn from '@/utils/classnames'
|
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
readonly: boolean
|
readonly: boolean
|
||||||
@ -53,20 +51,8 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { connect, read, write, max_connect_timeout, max_read_timeout, max_write_timeout } = payload ?? {}
|
const { connect, read, write, max_connect_timeout, max_read_timeout, max_write_timeout } = payload ?? {}
|
||||||
|
|
||||||
const [isFold, {
|
|
||||||
toggle: toggleFold,
|
|
||||||
}] = useBoolean(true)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<FieldCollapse title={t(`${i18nPrefix}.timeout.title`)}>
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
onClick={toggleFold}
|
|
||||||
className={cn('flex justify-between leading-[18px] text-[13px] font-semibold text-gray-700 uppercase cursor-pointer')}>
|
|
||||||
<div>{t(`${i18nPrefix}.timeout.title`)}</div>
|
|
||||||
<ChevronRight className='w-4 h-4 text-gray-500 transform transition-transform' style={{ transform: isFold ? 'rotate(0deg)' : 'rotate(90deg)' }} />
|
|
||||||
</div>
|
|
||||||
{!isFold && (
|
|
||||||
<div className='mt-2 space-y-1'>
|
<div className='mt-2 space-y-1'>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<InputField
|
<InputField
|
||||||
@ -101,10 +87,7 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</FieldCollapse>
|
||||||
</div>
|
|
||||||
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(Timeout)
|
export default React.memo(Timeout)
|
||||||
|
@ -65,7 +65,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-2'>
|
<div className='pt-2'>
|
||||||
<div className='px-4 pb-4 space-y-4'>
|
<div className='px-4 pb-4 space-y-4'>
|
||||||
<Field
|
<Field
|
||||||
title={t(`${i18nPrefix}.api`)}
|
title={t(`${i18nPrefix}.api`)}
|
||||||
@ -136,14 +136,12 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
|||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
<Split />
|
<Split />
|
||||||
<div className='px-4 pt-4 pb-4'>
|
|
||||||
<Timeout
|
<Timeout
|
||||||
nodeId={id}
|
nodeId={id}
|
||||||
readonly={readOnly}
|
readonly={readOnly}
|
||||||
payload={inputs.timeout}
|
payload={inputs.timeout}
|
||||||
onChange={setTimeout}
|
onChange={setTimeout}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
{(isShowAuthorization && !readOnly) && (
|
{(isShowAuthorization && !readOnly) && (
|
||||||
<AuthorizationModal
|
<AuthorizationModal
|
||||||
nodeId={id}
|
nodeId={id}
|
||||||
@ -154,7 +152,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Split />
|
<Split />
|
||||||
<div className='px-4 pt-4 pb-2'>
|
<div className=''>
|
||||||
<OutputVars>
|
<OutputVars>
|
||||||
<>
|
<>
|
||||||
<VarItem
|
<VarItem
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
useMemo,
|
useMemo,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useNodes } from 'reactflow'
|
||||||
import { ComparisonOperator } from '../types'
|
import { ComparisonOperator } from '../types'
|
||||||
import {
|
import {
|
||||||
comparisonOperatorNotRequireValue,
|
comparisonOperatorNotRequireValue,
|
||||||
@ -13,6 +14,11 @@ import { Variable02 } from '@/app/components/base/icons/src/vender/solid/develop
|
|||||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||||
|
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||||
|
import type {
|
||||||
|
CommonNodeType,
|
||||||
|
Node,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
|
||||||
type ConditionValueProps = {
|
type ConditionValueProps = {
|
||||||
variableSelector: string[]
|
variableSelector: string[]
|
||||||
@ -27,11 +33,14 @@ const ConditionValue = ({
|
|||||||
value,
|
value,
|
||||||
}: ConditionValueProps) => {
|
}: ConditionValueProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const nodes = useNodes()
|
||||||
const variableName = labelName || (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.'))
|
const variableName = labelName || (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.'))
|
||||||
const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator
|
const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator
|
||||||
const notHasValue = comparisonOperatorNotRequireValue(operator)
|
const notHasValue = comparisonOperatorNotRequireValue(operator)
|
||||||
const isEnvVar = isENV(variableSelector)
|
const isEnvVar = isENV(variableSelector)
|
||||||
const isChatVar = isConversationVar(variableSelector)
|
const isChatVar = isConversationVar(variableSelector)
|
||||||
|
const node: Node<CommonNodeType> | undefined = nodes.find(n => n.id === variableSelector[0]) as Node<CommonNodeType>
|
||||||
|
const isException = isExceptionVariable(variableName, node?.data.type)
|
||||||
const formatValue = useMemo(() => {
|
const formatValue = useMemo(() => {
|
||||||
if (notHasValue)
|
if (notHasValue)
|
||||||
return ''
|
return ''
|
||||||
@ -67,7 +76,7 @@ const ConditionValue = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center px-1 h-6 rounded-md bg-workflow-block-parma-bg'>
|
<div className='flex items-center px-1 h-6 rounded-md bg-workflow-block-parma-bg'>
|
||||||
{!isEnvVar && !isChatVar && <Variable02 className='shrink-0 mr-1 w-3.5 h-3.5 text-text-accent' />}
|
{!isEnvVar && !isChatVar && <Variable02 className={cn('shrink-0 mr-1 w-3.5 h-3.5 text-text-accent', isException && 'text-text-warning')} />}
|
||||||
{isEnvVar && <Env className='shrink-0 mr-1 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
{isEnvVar && <Env className='shrink-0 mr-1 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||||
|
|
||||||
@ -75,6 +84,7 @@ const ConditionValue = ({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'shrink-0 ml-0.5 truncate text-xs font-medium text-text-accent',
|
'shrink-0 ml-0.5 truncate text-xs font-medium text-text-accent',
|
||||||
!notHasValue && 'max-w-[70px]',
|
!notHasValue && 'max-w-[70px]',
|
||||||
|
isException && 'text-text-warning',
|
||||||
)}
|
)}
|
||||||
title={variableName}
|
title={variableName}
|
||||||
>
|
>
|
||||||
|
@ -18,7 +18,6 @@ import Switch from '@/app/components/base/switch'
|
|||||||
import Select from '@/app/components/base/select'
|
import Select from '@/app/components/base/select'
|
||||||
import Slider from '@/app/components/base/slider'
|
import Slider from '@/app/components/base/slider'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Divider from '@/app/components/base/divider'
|
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.iteration'
|
const i18nPrefix = 'workflow.nodes.iteration'
|
||||||
|
|
||||||
@ -72,7 +71,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
|||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-2'>
|
<div className='pt-2 pb-2'>
|
||||||
<div className='px-4 pb-4 space-y-4'>
|
<div className='px-4 pb-4 space-y-4'>
|
||||||
<Field
|
<Field
|
||||||
title={t(`${i18nPrefix}.input`)}
|
title={t(`${i18nPrefix}.input`)}
|
||||||
@ -131,9 +130,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
|||||||
</Field>
|
</Field>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
<div className='px-4 py-2'>
|
<Split />
|
||||||
<Divider className='h-[1px]'/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='px-4 py-2'>
|
<div className='px-4 py-2'>
|
||||||
<Field title={t(`${i18nPrefix}.errorResponseMethod`)} >
|
<Field title={t(`${i18nPrefix}.errorResponseMethod`)} >
|
||||||
|
@ -53,7 +53,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({
|
|||||||
}, [setRerankModelOpen])
|
}, [setRerankModelOpen])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-2'>
|
<div className='pt-2'>
|
||||||
<div className='px-4 pb-4 space-y-4'>
|
<div className='px-4 pb-4 space-y-4'>
|
||||||
{/* {JSON.stringify(inputs, null, 2)} */}
|
{/* {JSON.stringify(inputs, null, 2)} */}
|
||||||
<Field
|
<Field
|
||||||
@ -108,7 +108,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Split />
|
<Split />
|
||||||
<div className='px-4 pt-4 pb-2'>
|
<div>
|
||||||
<OutputVars>
|
<OutputVars>
|
||||||
<>
|
<>
|
||||||
<VarItem
|
<VarItem
|
||||||
|
@ -42,8 +42,8 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
|
|||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-2'>
|
<div className='pt-2'>
|
||||||
<div className='px-4 pb-4 space-y-4'>
|
<div className='px-4 space-y-4'>
|
||||||
<Field
|
<Field
|
||||||
title={t(`${i18nPrefix}.inputVar`)}
|
title={t(`${i18nPrefix}.inputVar`)}
|
||||||
>
|
>
|
||||||
@ -157,7 +157,7 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
|
|||||||
</Field>
|
</Field>
|
||||||
<Split />
|
<Split />
|
||||||
</div>
|
</div>
|
||||||
<div className='px-4 pt-4 pb-2'>
|
<div>
|
||||||
<OutputVars>
|
<OutputVars>
|
||||||
<>
|
<>
|
||||||
<VarItem
|
<VarItem
|
||||||
|
@ -270,7 +270,6 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Split />
|
<Split />
|
||||||
<div className='px-4 pt-4 pb-2'>
|
|
||||||
<OutputVars>
|
<OutputVars>
|
||||||
<>
|
<>
|
||||||
<VarItem
|
<VarItem
|
||||||
@ -280,7 +279,6 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
</div>
|
|
||||||
{isShowSingleRun && (
|
{isShowSingleRun && (
|
||||||
<BeforeRunForm
|
<BeforeRunForm
|
||||||
nodeName={inputs.title}
|
nodeName={inputs.title}
|
||||||
|
@ -20,6 +20,7 @@ import { InputVarType, type NodePanelProps } from '@/app/components/workflow/typ
|
|||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||||
import { VarType } from '@/app/components/workflow/types'
|
import { VarType } from '@/app/components/workflow/types'
|
||||||
|
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.parameterExtractor'
|
const i18nPrefix = 'workflow.nodes.parameterExtractor'
|
||||||
const i18nCommonPrefix = 'workflow.common'
|
const i18nCommonPrefix = 'workflow.common'
|
||||||
@ -67,8 +68,8 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({
|
|||||||
const model = inputs.model
|
const model = inputs.model
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-2'>
|
<div className='pt-2'>
|
||||||
<div className='px-4 pb-4 space-y-4'>
|
<div className='px-4 space-y-4'>
|
||||||
<Field
|
<Field
|
||||||
title={t(`${i18nCommonPrefix}.model`)}
|
title={t(`${i18nCommonPrefix}.model`)}
|
||||||
>
|
>
|
||||||
@ -157,12 +158,9 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({
|
|||||||
nodesOutputVars={availableVars}
|
nodesOutputVars={availableVars}
|
||||||
availableNodes={availableNodesWithParent}
|
availableNodes={availableNodesWithParent}
|
||||||
/>
|
/>
|
||||||
<Field
|
</div>
|
||||||
title={t(`${i18nPrefix}.advancedSetting`)}
|
<FieldCollapse title={t(`${i18nPrefix}.advancedSetting`)}>
|
||||||
supportFold
|
|
||||||
>
|
|
||||||
<>
|
<>
|
||||||
|
|
||||||
{/* Memory */}
|
{/* Memory */}
|
||||||
{isChatMode && (
|
{isChatMode && (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
@ -183,12 +181,10 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</Field>
|
</FieldCollapse>
|
||||||
|
|
||||||
</div>
|
|
||||||
{inputs.parameters?.length > 0 && (<>
|
{inputs.parameters?.length > 0 && (<>
|
||||||
<Split />
|
<Split />
|
||||||
<div className='px-4 pt-4 pb-2'>
|
<div>
|
||||||
<OutputVars>
|
<OutputVars>
|
||||||
<>
|
<>
|
||||||
{inputs.parameters.map((param, index) => (
|
{inputs.parameters.map((param, index) => (
|
||||||
|
@ -26,6 +26,16 @@ const nodeDefault: NodeDefault<QuestionClassifierNodeType> = {
|
|||||||
name: '',
|
name: '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
_targetBranches: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
vision: {
|
vision: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
@ -14,6 +14,7 @@ import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/befo
|
|||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
|
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
||||||
|
|
||||||
@ -55,8 +56,8 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
|||||||
const model = inputs.model
|
const model = inputs.model
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-2'>
|
<div className='pt-2'>
|
||||||
<div className='px-4 pb-4 space-y-4'>
|
<div className='px-4 space-y-4'>
|
||||||
<Field
|
<Field
|
||||||
title={t(`${i18nPrefix}.model`)}
|
title={t(`${i18nPrefix}.model`)}
|
||||||
>
|
>
|
||||||
@ -107,9 +108,10 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
|||||||
readonly={readOnly}
|
readonly={readOnly}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field
|
<Split />
|
||||||
|
</div>
|
||||||
|
<FieldCollapse
|
||||||
title={t(`${i18nPrefix}.advancedSetting`)}
|
title={t(`${i18nPrefix}.advancedSetting`)}
|
||||||
supportFold
|
|
||||||
>
|
>
|
||||||
<AdvancedSetting
|
<AdvancedSetting
|
||||||
hideMemorySetting={!isChatMode}
|
hideMemorySetting={!isChatMode}
|
||||||
@ -124,10 +126,9 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
|||||||
nodesOutputVars={availableVars}
|
nodesOutputVars={availableVars}
|
||||||
availableNodes={availableNodesWithParent}
|
availableNodes={availableNodesWithParent}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</FieldCollapse>
|
||||||
</div>
|
|
||||||
<Split />
|
<Split />
|
||||||
<div className='px-4 pt-4 pb-2'>
|
<div>
|
||||||
<OutputVars>
|
<OutputVars>
|
||||||
<>
|
<>
|
||||||
<VarItem
|
<VarItem
|
||||||
|
@ -95,7 +95,7 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Split />
|
<Split />
|
||||||
<div className='px-4 pt-4 pb-2'>
|
<div>
|
||||||
<OutputVars>
|
<OutputVars>
|
||||||
<>
|
<>
|
||||||
<VarItem
|
<VarItem
|
||||||
|
@ -56,10 +56,10 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-2'>
|
<div className='pt-2'>
|
||||||
{!readOnly && isShowAuthBtn && (
|
{!readOnly && isShowAuthBtn && (
|
||||||
<>
|
<>
|
||||||
<div className='px-4 pb-3'>
|
<div className='px-4'>
|
||||||
<Button
|
<Button
|
||||||
variant='primary'
|
variant='primary'
|
||||||
className='w-full'
|
className='w-full'
|
||||||
@ -71,7 +71,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!isShowAuthBtn && <>
|
{!isShowAuthBtn && <>
|
||||||
<div className='px-4 pb-4 space-y-4'>
|
<div className='px-4 space-y-4'>
|
||||||
{toolInputVarSchema.length > 0 && (
|
{toolInputVarSchema.length > 0 && (
|
||||||
<Field
|
<Field
|
||||||
title={t(`${i18nPrefix}.inputVars`)}
|
title={t(`${i18nPrefix}.inputVars`)}
|
||||||
@ -118,7 +118,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='px-4 pt-4 pb-2'>
|
<div>
|
||||||
<OutputVars>
|
<OutputVars>
|
||||||
<>
|
<>
|
||||||
<VarItem
|
<VarItem
|
||||||
|
@ -21,6 +21,7 @@ import AddVariable from './add-variable'
|
|||||||
import NodeVariableItem from './node-variable-item'
|
import NodeVariableItem from './node-variable-item'
|
||||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.variableAssigner'
|
const i18nPrefix = 'workflow.nodes.variableAssigner'
|
||||||
type GroupItem = {
|
type GroupItem = {
|
||||||
@ -128,12 +129,14 @@ const NodeGroupItem = ({
|
|||||||
|
|
||||||
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
|
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
|
||||||
const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.')
|
const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.')
|
||||||
|
const isException = isExceptionVariable(varName, node?.data.type)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeVariableItem
|
<NodeVariableItem
|
||||||
key={index}
|
key={index}
|
||||||
isEnv={isEnv}
|
isEnv={isEnv}
|
||||||
isChatVar={isChatVar}
|
isChatVar={isChatVar}
|
||||||
|
isException={isException}
|
||||||
node={node as Node}
|
node={node as Node}
|
||||||
varName={varName}
|
varName={varName}
|
||||||
showBorder={showSelectedBorder || showSelectionBorder}
|
showBorder={showSelectedBorder || showSelectionBorder}
|
||||||
|
@ -17,6 +17,7 @@ type NodeVariableItemProps = {
|
|||||||
writeMode?: string
|
writeMode?: string
|
||||||
showBorder?: boolean
|
showBorder?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
|
isException?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.assigner'
|
const i18nPrefix = 'workflow.nodes.assigner'
|
||||||
@ -29,6 +30,7 @@ const NodeVariableItem = ({
|
|||||||
writeMode,
|
writeMode,
|
||||||
showBorder,
|
showBorder,
|
||||||
className,
|
className,
|
||||||
|
isException,
|
||||||
}: NodeVariableItemProps) => {
|
}: NodeVariableItemProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
@ -50,14 +52,14 @@ const NodeVariableItem = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className='flex items-center text-primary-600 w-full'>
|
<div className='flex items-center text-primary-600 w-full'>
|
||||||
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
{!isEnv && !isChatVar && <Variable02 className={cn('shrink-0 w-3.5 h-3.5 text-primary-500', isException && 'text-text-warning')} />}
|
||||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||||
{!isChatVar && <div className={cn('max-w-[75px] truncate ml-0.5 system-xs-medium overflow-hidden text-ellipsis', isEnv && 'text-gray-900')} title={varName}>{varName}</div>}
|
{!isChatVar && <div className={cn('max-w-[75px] truncate ml-0.5 system-xs-medium overflow-hidden text-ellipsis', isEnv && 'text-gray-900', isException && 'text-text-warning')} title={varName}>{varName}</div>}
|
||||||
{isChatVar
|
{isChatVar
|
||||||
&& <div className='flex items-center w-full gap-1'>
|
&& <div className='flex items-center w-full gap-1'>
|
||||||
<div className='flex h-[18px] min-w-[18px] items-center gap-0.5 flex-1'>
|
<div className='flex h-[18px] min-w-[18px] items-center gap-0.5 flex-1'>
|
||||||
<BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />
|
<BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />
|
||||||
<div className='max-w-[75px] truncate ml-0.5 system-xs-medium overflow-hidden text-ellipsis text-util-colors-teal-teal-700'>{varName}</div>
|
<div className={cn('max-w-[75px] truncate ml-0.5 system-xs-medium overflow-hidden text-ellipsis text-util-colors-teal-teal-700')}>{varName}</div>
|
||||||
</div>
|
</div>
|
||||||
{writeMode && <Badge className='shrink-0' text={t(`${i18nPrefix}.operations.${writeMode}`)} />}
|
{writeMode && <Badge className='shrink-0' text={t(`${i18nPrefix}.operations.${writeMode}`)} />}
|
||||||
</div>
|
</div>
|
||||||
|
@ -193,6 +193,7 @@ const WorkflowPreview = () => {
|
|||||||
created_at={workflowRunningData?.result?.created_at}
|
created_at={workflowRunningData?.result?.created_at}
|
||||||
created_by={(workflowRunningData?.result?.created_by as any)?.name}
|
created_by={(workflowRunningData?.result?.created_by as any)?.name}
|
||||||
steps={workflowRunningData?.result?.total_steps}
|
steps={workflowRunningData?.result?.total_steps}
|
||||||
|
exceptionCounts={workflowRunningData?.result?.exceptions_count}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentTab === 'DETAIL' && !workflowRunningData?.result && (
|
{currentTab === 'DETAIL' && !workflowRunningData?.result && (
|
||||||
|
@ -258,6 +258,7 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
|||||||
created_at={runDetail.created_at}
|
created_at={runDetail.created_at}
|
||||||
created_by={executor}
|
created_by={executor}
|
||||||
steps={runDetail.total_steps}
|
steps={runDetail.total_steps}
|
||||||
|
exceptionCounts={runDetail.exceptions_count}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!loading && currentTab === 'TRACING' && (
|
{!loading && currentTab === 'TRACING' && (
|
||||||
|
@ -38,6 +38,12 @@ const MetaData: FC<Props> = ({
|
|||||||
{status === 'succeeded' && (
|
{status === 'succeeded' && (
|
||||||
<span>SUCCESS</span>
|
<span>SUCCESS</span>
|
||||||
)}
|
)}
|
||||||
|
{status === 'partial-succeeded' && (
|
||||||
|
<span>PARTIAL SUCCESS</span>
|
||||||
|
)}
|
||||||
|
{status === 'exception' && (
|
||||||
|
<span>EXCEPTION</span>
|
||||||
|
)}
|
||||||
{status === 'failed' && (
|
{status === 'failed' && (
|
||||||
<span>FAIL</span>
|
<span>FAIL</span>
|
||||||
)}
|
)}
|
||||||
|
@ -19,6 +19,7 @@ import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/
|
|||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||||
import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
|
import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
|
||||||
|
import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -128,6 +129,9 @@ const NodePanel: FC<Props> = ({
|
|||||||
{nodeInfo.status === 'stopped' && (
|
{nodeInfo.status === 'stopped' && (
|
||||||
<RiAlertFill className={cn('shrink-0 ml-2 w-4 h-4 text-text-warning-secondary', inMessage && 'w-3.5 h-3.5')} />
|
<RiAlertFill className={cn('shrink-0 ml-2 w-4 h-4 text-text-warning-secondary', inMessage && 'w-3.5 h-3.5')} />
|
||||||
)}
|
)}
|
||||||
|
{nodeInfo.status === 'exception' && (
|
||||||
|
<RiAlertFill className={cn('shrink-0 ml-2 w-4 h-4 text-text-warning-secondary', inMessage && 'w-3.5 h-3.5')} />
|
||||||
|
)}
|
||||||
{nodeInfo.status === 'running' && (
|
{nodeInfo.status === 'running' && (
|
||||||
<div className='shrink-0 flex items-center text-text-accent text-[13px] leading-[16px] font-medium'>
|
<div className='shrink-0 flex items-center text-text-accent text-[13px] leading-[16px] font-medium'>
|
||||||
<span className='mr-2 text-xs font-normal'>Running</span>
|
<span className='mr-2 text-xs font-normal'>Running</span>
|
||||||
@ -165,12 +169,24 @@ const NodePanel: FC<Props> = ({
|
|||||||
<Split className='mt-2' />
|
<Split className='mt-2' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={cn('px-[10px]', hideInfo && '!px-2 !py-0.5')}>
|
<div className={cn('mb-1', hideInfo && '!px-2 !py-0.5')}>
|
||||||
{nodeInfo.status === 'stopped' && (
|
{(nodeInfo.status === 'stopped') && (
|
||||||
<StatusContainer status='stopped'>
|
<StatusContainer status='stopped'>
|
||||||
{t('workflow.tracing.stopBy', { user: nodeInfo.created_by ? nodeInfo.created_by.name : 'N/A' })}
|
{t('workflow.tracing.stopBy', { user: nodeInfo.created_by ? nodeInfo.created_by.name : 'N/A' })}
|
||||||
</StatusContainer>
|
</StatusContainer>
|
||||||
)}
|
)}
|
||||||
|
{(nodeInfo.status === 'exception') && (
|
||||||
|
<StatusContainer status='stopped'>
|
||||||
|
{nodeInfo.error}
|
||||||
|
<a
|
||||||
|
href='https://docs.dify.ai/guides/workflow/error-handling/predefined-nodes-failure-logic'
|
||||||
|
target='_blank'
|
||||||
|
className='text-text-accent'
|
||||||
|
>
|
||||||
|
{t('workflow.common.learnMore')}
|
||||||
|
</a>
|
||||||
|
</StatusContainer>
|
||||||
|
)}
|
||||||
{nodeInfo.status === 'failed' && (
|
{nodeInfo.status === 'failed' && (
|
||||||
<StatusContainer status='failed'>
|
<StatusContainer status='failed'>
|
||||||
{nodeInfo.error}
|
{nodeInfo.error}
|
||||||
@ -207,6 +223,7 @@ const NodePanel: FC<Props> = ({
|
|||||||
language={CodeLanguage.json}
|
language={CodeLanguage.json}
|
||||||
value={nodeInfo.outputs}
|
value={nodeInfo.outputs}
|
||||||
isJSONStringifyBeauty
|
isJSONStringifyBeauty
|
||||||
|
tip={<ErrorHandleTip type={nodeInfo.execution_metadata?.error_strategy} />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -5,6 +5,7 @@ import StatusPanel from './status'
|
|||||||
import MetaData from './meta'
|
import MetaData from './meta'
|
||||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||||
|
import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
|
||||||
|
|
||||||
type ResultPanelProps = {
|
type ResultPanelProps = {
|
||||||
inputs?: string
|
inputs?: string
|
||||||
@ -19,6 +20,8 @@ type ResultPanelProps = {
|
|||||||
finished_at?: number
|
finished_at?: number
|
||||||
steps?: number
|
steps?: number
|
||||||
showSteps?: boolean
|
showSteps?: boolean
|
||||||
|
exceptionCounts?: number
|
||||||
|
execution_metadata?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResultPanel: FC<ResultPanelProps> = ({
|
const ResultPanel: FC<ResultPanelProps> = ({
|
||||||
@ -33,6 +36,8 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
|||||||
created_by,
|
created_by,
|
||||||
steps,
|
steps,
|
||||||
showSteps,
|
showSteps,
|
||||||
|
exceptionCounts,
|
||||||
|
execution_metadata,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
@ -43,6 +48,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
|||||||
time={elapsed_time}
|
time={elapsed_time}
|
||||||
tokens={total_tokens}
|
tokens={total_tokens}
|
||||||
error={error}
|
error={error}
|
||||||
|
exceptionCounts={exceptionCounts}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='px-4 py-2 flex flex-col gap-2'>
|
<div className='px-4 py-2 flex flex-col gap-2'>
|
||||||
@ -69,6 +75,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
|||||||
language={CodeLanguage.json}
|
language={CodeLanguage.json}
|
||||||
value={outputs}
|
value={outputs}
|
||||||
isJSONStringifyBeauty
|
isJSONStringifyBeauty
|
||||||
|
tip={<ErrorHandleTip type={execution_metadata?.error_strategy} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,10 +14,12 @@ const StatusContainer: FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative px-3 py-2.5 rounded-lg border system-xs-regular',
|
'relative px-3 py-2.5 rounded-lg border system-xs-regular break-all',
|
||||||
status === 'succeeded' && 'border-[rgba(23,178,106,0.8)] bg-workflow-display-success-bg bg-[url(~@/app/components/workflow/run/assets/bg-line-success.svg)] shadow-[inset_2px_2px_0_0_rgba(255,255,255,0.5),inset_0_1px_3px_0_rgba(0,0,0,0.12),inset_0_2px_24px_0_rgba(23,178,106,0.2),0_1px_2px_0_rgba(9,9,11,0.05),0_0_0_1px_rgba(0,0,0,0.05)] text-text-success',
|
status === 'succeeded' && 'border-[rgba(23,178,106,0.8)] bg-workflow-display-success-bg bg-[url(~@/app/components/workflow/run/assets/bg-line-success.svg)] shadow-[inset_2px_2px_0_0_rgba(255,255,255,0.5),inset_0_1px_3px_0_rgba(0,0,0,0.12),inset_0_2px_24px_0_rgba(23,178,106,0.2),0_1px_2px_0_rgba(9,9,11,0.05),0_0_0_1px_rgba(0,0,0,0.05)] text-text-success',
|
||||||
|
status === 'partial-succeeded' && 'border-[rgba(23,178,106,0.8)] bg-workflow-display-success-bg bg-[url(~@/app/components/workflow/run/assets/bg-line-success.svg)] shadow-[inset_2px_2px_0_0_rgba(255,255,255,0.5),inset_0_1px_3px_0_rgba(0,0,0,0.12),inset_0_2px_24px_0_rgba(23,178,106,0.2),0_1px_2px_0_rgba(9,9,11,0.05),0_0_0_1px_rgba(0,0,0,0.05)] text-text-success',
|
||||||
status === 'failed' && 'border-[rgba(240,68,56,0.8)] bg-workflow-display-error-bg bg-[url(~@/app/components/workflow/run/assets/bg-line-error.svg)] shadow-[inset_2px_2px_0_0_rgba(255,255,255,0.5),inset_0_1px_3px_0_rgba(0,0,0,0.12),inset_0_2px_24px_0_rgba(240,68,56,0.2),0_1px_2px_0_rgba(9,9,11,0.05),0_0_0_1px_rgba(0,0,0,0.05)] text-text-warning',
|
status === 'failed' && 'border-[rgba(240,68,56,0.8)] bg-workflow-display-error-bg bg-[url(~@/app/components/workflow/run/assets/bg-line-error.svg)] shadow-[inset_2px_2px_0_0_rgba(255,255,255,0.5),inset_0_1px_3px_0_rgba(0,0,0,0.12),inset_0_2px_24px_0_rgba(240,68,56,0.2),0_1px_2px_0_rgba(9,9,11,0.05),0_0_0_1px_rgba(0,0,0,0.05)] text-text-warning',
|
||||||
status === 'stopped' && 'border-[rgba(247,144,9,0.8)] bg-workflow-display-warning-bg bg-[url(~@/app/components/workflow/run/assets/bg-line-warning.svg)] shadow-[inset_2px_2px_0_0_rgba(255,255,255,0.5),inset_0_1px_3px_0_rgba(0,0,0,0.12),inset_0_2px_24px_0_rgba(247,144,9,0.2),0_1px_2px_0_rgba(9,9,11,0.05),0_0_0_1px_rgba(0,0,0,0.05)] text-text-destructive',
|
status === 'stopped' && 'border-[rgba(247,144,9,0.8)] bg-workflow-display-warning-bg bg-[url(~@/app/components/workflow/run/assets/bg-line-warning.svg)] shadow-[inset_2px_2px_0_0_rgba(255,255,255,0.5),inset_0_1px_3px_0_rgba(0,0,0,0.12),inset_0_2px_24px_0_rgba(247,144,9,0.2),0_1px_2px_0_rgba(9,9,11,0.05),0_0_0_1px_rgba(0,0,0,0.05)] text-text-destructive',
|
||||||
|
status === 'exception' && 'border-[rgba(247,144,9,0.8)] bg-workflow-display-warning-bg bg-[url(~@/app/components/workflow/run/assets/bg-line-warning.svg)] shadow-[inset_2px_2px_0_0_rgba(255,255,255,0.5),inset_0_1px_3px_0_rgba(0,0,0,0.12),inset_0_2px_24px_0_rgba(247,144,9,0.2),0_1px_2px_0_rgba(9,9,11,0.05),0_0_0_1px_rgba(0,0,0,0.05)] text-text-destructive',
|
||||||
status === 'running' && 'border-[rgba(11,165,236,0.8)] bg-workflow-display-normal-bg bg-[url(~@/app/components/workflow/run/assets/bg-line-running.svg)] shadow-[inset_2px_2px_0_0_rgba(255,255,255,0.5),inset_0_1px_3px_0_rgba(0,0,0,0.12),inset_0_2px_24px_0_rgba(11,165,236,0.2),0_1px_2px_0_rgba(9,9,11,0.05),0_0_0_1px_rgba(0,0,0,0.05)] text-util-colors-blue-light-blue-light-600',
|
status === 'running' && 'border-[rgba(11,165,236,0.8)] bg-workflow-display-normal-bg bg-[url(~@/app/components/workflow/run/assets/bg-line-running.svg)] shadow-[inset_2px_2px_0_0_rgba(255,255,255,0.5),inset_0_1px_3px_0_rgba(0,0,0,0.12),inset_0_2px_24px_0_rgba(11,165,236,0.2),0_1px_2px_0_rgba(9,9,11,0.05),0_0_0_1px_rgba(0,0,0,0.05)] text-util-colors-blue-light-blue-light-600',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -10,6 +10,7 @@ type ResultProps = {
|
|||||||
time?: number
|
time?: number
|
||||||
tokens?: number
|
tokens?: number
|
||||||
error?: string
|
error?: string
|
||||||
|
exceptionCounts?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusPanel: FC<ResultProps> = ({
|
const StatusPanel: FC<ResultProps> = ({
|
||||||
@ -17,18 +18,23 @@ const StatusPanel: FC<ResultProps> = ({
|
|||||||
time,
|
time,
|
||||||
tokens,
|
tokens,
|
||||||
error,
|
error,
|
||||||
|
exceptionCounts,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusContainer status={status}>
|
<StatusContainer status={status}>
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<div className='flex-[33%] max-w-[120px]'>
|
<div className={cn(
|
||||||
|
'flex-[33%] max-w-[120px]',
|
||||||
|
status === 'partial-succeeded' && 'min-w-[140px]',
|
||||||
|
)}>
|
||||||
<div className='mb-1 text-text-tertiary system-2xs-medium-uppercase'>{t('runLog.resultPanel.status')}</div>
|
<div className='mb-1 text-text-tertiary system-2xs-medium-uppercase'>{t('runLog.resultPanel.status')}</div>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 system-xs-semibold-uppercase',
|
'flex items-center gap-1 system-xs-semibold-uppercase',
|
||||||
status === 'succeeded' && 'text-util-colors-green-green-600',
|
status === 'succeeded' && 'text-util-colors-green-green-600',
|
||||||
|
status === 'partial-succeeded' && 'text-util-colors-green-green-600',
|
||||||
status === 'failed' && 'text-util-colors-red-red-600',
|
status === 'failed' && 'text-util-colors-red-red-600',
|
||||||
status === 'stopped' && 'text-util-colors-warning-warning-600',
|
status === 'stopped' && 'text-util-colors-warning-warning-600',
|
||||||
status === 'running' && 'text-util-colors-blue-light-blue-light-600',
|
status === 'running' && 'text-util-colors-blue-light-blue-light-600',
|
||||||
@ -46,6 +52,18 @@ const StatusPanel: FC<ResultProps> = ({
|
|||||||
<span>SUCCESS</span>
|
<span>SUCCESS</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{status === 'partial-succeeded' && (
|
||||||
|
<>
|
||||||
|
<Indicator color={'green'} />
|
||||||
|
<span>PARTIAL SUCCESS</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{status === 'exception' && (
|
||||||
|
<>
|
||||||
|
<Indicator color={'yellow'} />
|
||||||
|
<span>EXCEPTION</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{status === 'failed' && (
|
{status === 'failed' && (
|
||||||
<>
|
<>
|
||||||
<Indicator color={'red'} />
|
<Indicator color={'red'} />
|
||||||
@ -87,8 +105,45 @@ const StatusPanel: FC<ResultProps> = ({
|
|||||||
<>
|
<>
|
||||||
<div className='my-2 h-[0.5px] bg-divider-subtle'/>
|
<div className='my-2 h-[0.5px] bg-divider-subtle'/>
|
||||||
<div className='system-xs-regular text-text-destructive'>{error}</div>
|
<div className='system-xs-regular text-text-destructive'>{error}</div>
|
||||||
|
{
|
||||||
|
!!exceptionCounts && (
|
||||||
|
<>
|
||||||
|
<div className='my-2 h-[0.5px] bg-divider-subtle'/>
|
||||||
|
<div className='system-xs-regular text-text-destructive'>
|
||||||
|
{t('workflow.nodes.common.errorHandle.partialSucceeded.tip', { num: exceptionCounts })}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{
|
||||||
|
status === 'partial-succeeded' && !!exceptionCounts && (
|
||||||
|
<>
|
||||||
|
<div className='my-2 h-[0.5px] bg-divider-deep'/>
|
||||||
|
<div className='system-xs-medium text-text-warning'>
|
||||||
|
{t('workflow.nodes.common.errorHandle.partialSucceeded.tip', { num: exceptionCounts })}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
status === 'exception' && (
|
||||||
|
<>
|
||||||
|
<div className='my-2 h-[0.5px] bg-divider-deep'/>
|
||||||
|
<div className='system-xs-medium text-text-warning'>
|
||||||
|
{error}
|
||||||
|
<a
|
||||||
|
href='https://docs.dify.ai/guides/workflow/error-handling/predefined-nodes-failure-logic'
|
||||||
|
target='_blank'
|
||||||
|
className='text-text-accent'
|
||||||
|
>
|
||||||
|
{t('workflow.common.learnMore')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
</StatusContainer>
|
</StatusContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,10 @@ import type { VarType as VarKindType } from '@/app/components/workflow/nodes/too
|
|||||||
import type { FileResponse, NodeTracing } from '@/types/workflow'
|
import type { FileResponse, NodeTracing } from '@/types/workflow'
|
||||||
import type { Collection, Tool } from '@/app/components/tools/types'
|
import type { Collection, Tool } from '@/app/components/tools/types'
|
||||||
import type { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
import type { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||||
|
import type {
|
||||||
|
DefaultValueForm,
|
||||||
|
ErrorHandleTypeEnum,
|
||||||
|
} from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
|
|
||||||
export enum BlockEnum {
|
export enum BlockEnum {
|
||||||
Start = 'start',
|
Start = 'start',
|
||||||
@ -52,6 +56,7 @@ export type CommonNodeType<T = {}> = {
|
|||||||
_targetBranches?: Branch[]
|
_targetBranches?: Branch[]
|
||||||
_isSingleRun?: boolean
|
_isSingleRun?: boolean
|
||||||
_runningStatus?: NodeRunningStatus
|
_runningStatus?: NodeRunningStatus
|
||||||
|
_runningBranchId?: string
|
||||||
_singleRunningStatus?: NodeRunningStatus
|
_singleRunningStatus?: NodeRunningStatus
|
||||||
_isCandidate?: boolean
|
_isCandidate?: boolean
|
||||||
_isBundled?: boolean
|
_isBundled?: boolean
|
||||||
@ -62,6 +67,7 @@ export type CommonNodeType<T = {}> = {
|
|||||||
_iterationLength?: number
|
_iterationLength?: number
|
||||||
_iterationIndex?: number
|
_iterationIndex?: number
|
||||||
_inParallelHovering?: boolean
|
_inParallelHovering?: boolean
|
||||||
|
_waitingRun?: boolean
|
||||||
isInIteration?: boolean
|
isInIteration?: boolean
|
||||||
iteration_id?: string
|
iteration_id?: string
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
@ -70,14 +76,18 @@ export type CommonNodeType<T = {}> = {
|
|||||||
type: BlockEnum
|
type: BlockEnum
|
||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
|
error_strategy?: ErrorHandleTypeEnum
|
||||||
|
default_value?: DefaultValueForm[]
|
||||||
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
|
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
|
||||||
|
|
||||||
export type CommonEdgeType = {
|
export type CommonEdgeType = {
|
||||||
_hovering?: boolean
|
_hovering?: boolean
|
||||||
_connectedNodeIsHovering?: boolean
|
_connectedNodeIsHovering?: boolean
|
||||||
_connectedNodeIsSelected?: boolean
|
_connectedNodeIsSelected?: boolean
|
||||||
_run?: boolean
|
|
||||||
_isBundled?: boolean
|
_isBundled?: boolean
|
||||||
|
_sourceRunningStatus?: NodeRunningStatus
|
||||||
|
_targetRunningStatus?: NodeRunningStatus
|
||||||
|
_waitingRun?: boolean
|
||||||
isInIteration?: boolean
|
isInIteration?: boolean
|
||||||
iteration_id?: string
|
iteration_id?: string
|
||||||
sourceType: BlockEnum
|
sourceType: BlockEnum
|
||||||
@ -242,6 +252,7 @@ export type Var = {
|
|||||||
options?: string[]
|
options?: string[]
|
||||||
required?: boolean
|
required?: boolean
|
||||||
des?: string
|
des?: string
|
||||||
|
isException?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodeOutPutVar = {
|
export type NodeOutPutVar = {
|
||||||
@ -281,6 +292,7 @@ export enum NodeRunningStatus {
|
|||||||
Running = 'running',
|
Running = 'running',
|
||||||
Succeeded = 'succeeded',
|
Succeeded = 'succeeded',
|
||||||
Failed = 'failed',
|
Failed = 'failed',
|
||||||
|
Exception = 'exception',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnNodeAdd = (
|
export type OnNodeAdd = (
|
||||||
@ -331,6 +343,7 @@ export type WorkflowRunningData = {
|
|||||||
showSteps?: boolean
|
showSteps?: boolean
|
||||||
total_steps?: number
|
total_steps?: number
|
||||||
files?: FileResponse[]
|
files?: FileResponse[]
|
||||||
|
exceptions_count?: number
|
||||||
}
|
}
|
||||||
tracing?: NodeTracing[]
|
tracing?: NodeTracing[]
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,11 @@ import type {
|
|||||||
ToolWithProvider,
|
ToolWithProvider,
|
||||||
ValueSelector,
|
ValueSelector,
|
||||||
} from './types'
|
} from './types'
|
||||||
import { BlockEnum, ErrorHandleMode } from './types'
|
import {
|
||||||
|
BlockEnum,
|
||||||
|
ErrorHandleMode,
|
||||||
|
NodeRunningStatus,
|
||||||
|
} from './types'
|
||||||
import {
|
import {
|
||||||
CUSTOM_NODE,
|
CUSTOM_NODE,
|
||||||
ITERATION_CHILDREN_Z_INDEX,
|
ITERATION_CHILDREN_Z_INDEX,
|
||||||
@ -761,3 +765,34 @@ export const getParallelInfo = (nodes: Node[], edges: Edge[], parentNodeId?: str
|
|||||||
hasAbnormalEdges,
|
hasAbnormalEdges,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const hasErrorHandleNode = (nodeType?: BlockEnum) => {
|
||||||
|
return nodeType === BlockEnum.LLM || nodeType === BlockEnum.Tool || nodeType === BlockEnum.HttpRequest || nodeType === BlockEnum.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getEdgeColor = (nodeRunningStatus?: NodeRunningStatus, isFailBranch?: boolean) => {
|
||||||
|
if (nodeRunningStatus === NodeRunningStatus.Succeeded)
|
||||||
|
return 'var(--color-workflow-link-line-success-handle)'
|
||||||
|
|
||||||
|
if (nodeRunningStatus === NodeRunningStatus.Failed)
|
||||||
|
return 'var(--color-workflow-link-line-error-handle)'
|
||||||
|
|
||||||
|
if (nodeRunningStatus === NodeRunningStatus.Exception)
|
||||||
|
return 'var(--color-workflow-link-line-failure-handle)'
|
||||||
|
|
||||||
|
if (nodeRunningStatus === NodeRunningStatus.Running) {
|
||||||
|
if (isFailBranch)
|
||||||
|
return 'var(--color-workflow-link-line-failure-handle)'
|
||||||
|
|
||||||
|
return 'var(--color-workflow-link-line-handle)'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'var(--color-workflow-link-line-normal)'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isExceptionVariable = (variable: string, nodeType?: BlockEnum) => {
|
||||||
|
if ((variable === 'error_message' || variable === 'error_type') && hasErrorHandleNode(nodeType))
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -102,6 +102,8 @@ const translation = {
|
|||||||
addParallelNode: 'Add Parallel Node',
|
addParallelNode: 'Add Parallel Node',
|
||||||
parallel: 'PARALLEL',
|
parallel: 'PARALLEL',
|
||||||
branch: 'BRANCH',
|
branch: 'BRANCH',
|
||||||
|
onFailure: 'On Failure',
|
||||||
|
addFailureBranch: 'Add Fail Branch',
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
envPanelTitle: 'Environment Variables',
|
envPanelTitle: 'Environment Variables',
|
||||||
@ -302,6 +304,31 @@ const translation = {
|
|||||||
tip: 'Chat memory',
|
tip: 'Chat memory',
|
||||||
builtIn: 'Built-in',
|
builtIn: 'Built-in',
|
||||||
},
|
},
|
||||||
|
errorHandle: {
|
||||||
|
title: 'Error Handling',
|
||||||
|
tip: 'Exception handling strategy, triggered when a node encounters an exception.',
|
||||||
|
none: {
|
||||||
|
title: 'None',
|
||||||
|
desc: 'The node will stop running if an exception occurs and is not handled',
|
||||||
|
},
|
||||||
|
defaultValue: {
|
||||||
|
title: 'Default Value',
|
||||||
|
desc: 'When an error occurs, specify a static output content.',
|
||||||
|
tip: 'On error, will return below value.',
|
||||||
|
inLog: 'Node exception, outputting according to default values.',
|
||||||
|
output: 'Output Default Value',
|
||||||
|
},
|
||||||
|
failBranch: {
|
||||||
|
title: 'Fail Branch',
|
||||||
|
desc: 'When an error occurs, it will execute the exception branch',
|
||||||
|
customize: 'Go to the canvas to customize the fail branch logic.',
|
||||||
|
customizeTip: 'When the fail branch is activated, exceptions thrown by nodes will not terminate the process. Instead, it will automatically execute the predefined fail branch, allowing you to flexibly provide error messages, reports, fixes, or skip actions.',
|
||||||
|
inLog: 'Node exception, will automatically execute the fail branch. The node output will return an error type and error message and pass them to downstream.',
|
||||||
|
},
|
||||||
|
partialSucceeded: {
|
||||||
|
tip: 'There are {{num}} nodes in the process running abnormally, please go to tracing to check the logs.',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
start: {
|
start: {
|
||||||
required: 'required',
|
required: 'required',
|
||||||
|
@ -101,6 +101,8 @@ const translation = {
|
|||||||
addParallelNode: '添加并行节点',
|
addParallelNode: '添加并行节点',
|
||||||
parallel: '并行',
|
parallel: '并行',
|
||||||
branch: '分支',
|
branch: '分支',
|
||||||
|
onFailure: '异常时',
|
||||||
|
addFailureBranch: '添加异常分支',
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
envPanelTitle: '环境变量',
|
envPanelTitle: '环境变量',
|
||||||
@ -301,6 +303,31 @@ const translation = {
|
|||||||
tip: '聊天记忆',
|
tip: '聊天记忆',
|
||||||
builtIn: '内置',
|
builtIn: '内置',
|
||||||
},
|
},
|
||||||
|
errorHandle: {
|
||||||
|
title: '异常处理',
|
||||||
|
tip: '配置异常处理策略,当节点发生异常时触发。',
|
||||||
|
none: {
|
||||||
|
title: '无',
|
||||||
|
desc: '当发生异常且未处理时,节点将停止运行',
|
||||||
|
},
|
||||||
|
defaultValue: {
|
||||||
|
title: '默认值',
|
||||||
|
desc: '当发生异常时,指定默认输出内容。',
|
||||||
|
tip: '当发生异常时,将返回以下值。',
|
||||||
|
inLog: '节点异常,根据默认值输出。',
|
||||||
|
output: '输出默认值',
|
||||||
|
},
|
||||||
|
failBranch: {
|
||||||
|
title: '异常分支',
|
||||||
|
desc: '当发生异常时,将执行异常分支',
|
||||||
|
customize: '在画布自定义失败分支逻辑。',
|
||||||
|
customizeTip: '当节点发生异常时,将自动执行失败分支。失败分支允许您灵活地提供错误消息、报告、修复或跳过操作。',
|
||||||
|
inLog: '节点异常,将自动执行失败分支。节点输出将返回错误类型和错误信息,并传递给下游。',
|
||||||
|
},
|
||||||
|
partialSucceeded: {
|
||||||
|
tip: '流程中有 {{num}} 个节点运行异常,请前往追踪查看日志。',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
start: {
|
start: {
|
||||||
required: '必填',
|
required: '必填',
|
||||||
|
@ -297,6 +297,7 @@ export type WorkflowRunDetailResponse = {
|
|||||||
created_by_end_user?: EndUserInfo
|
created_by_end_user?: EndUserInfo
|
||||||
created_at: number
|
created_at: number
|
||||||
finished_at: number
|
finished_at: number
|
||||||
|
exceptions_count?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AgentLogMeta = {
|
export type AgentLogMeta = {
|
||||||
|
@ -7,6 +7,7 @@ import type {
|
|||||||
Node,
|
Node,
|
||||||
} from '@/app/components/workflow/types'
|
} from '@/app/components/workflow/types'
|
||||||
import type { TransferMethod } from '@/types/app'
|
import type { TransferMethod } from '@/types/app'
|
||||||
|
import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
|
|
||||||
export type NodeTracing = {
|
export type NodeTracing = {
|
||||||
id: string
|
id: string
|
||||||
@ -22,7 +23,7 @@ export type NodeTracing = {
|
|||||||
parallel_run_id?: string
|
parallel_run_id?: string
|
||||||
error?: string
|
error?: string
|
||||||
elapsed_time: number
|
elapsed_time: number
|
||||||
execution_metadata: {
|
execution_metadata?: {
|
||||||
total_tokens: number
|
total_tokens: number
|
||||||
total_price: number
|
total_price: number
|
||||||
currency: string
|
currency: string
|
||||||
@ -34,6 +35,7 @@ export type NodeTracing = {
|
|||||||
parent_parallel_start_node_id?: string
|
parent_parallel_start_node_id?: string
|
||||||
parallel_mode_run_id?: string
|
parallel_mode_run_id?: string
|
||||||
iteration_duration_map?: IterationDurationMap
|
iteration_duration_map?: IterationDurationMap
|
||||||
|
error_strategy?: ErrorHandleTypeEnum
|
||||||
}
|
}
|
||||||
metadata: {
|
metadata: {
|
||||||
iterator_length: number
|
iterator_length: number
|
||||||
@ -172,6 +174,7 @@ export type NodeFinishedResponse = {
|
|||||||
iteration_index?: number
|
iteration_index?: number
|
||||||
iteration_id?: string
|
iteration_id?: string
|
||||||
parallel_mode_run_id: string
|
parallel_mode_run_id: string
|
||||||
|
error_strategy?: ErrorHandleTypeEnum
|
||||||
}
|
}
|
||||||
created_at: number
|
created_at: number
|
||||||
files?: FileResponse[]
|
files?: FileResponse[]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user