mirror of
https://github.com/langgenius/dify.git
synced 2026-02-24 18:05:11 +00:00
feat: Human Input Node (#32060)
The frontend and backend implementation for the human input node. Co-authored-by: twwu <twwu@dify.ai> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> Co-authored-by: zhsama <torvalds@linux.do>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { ElkNode, LayoutOptions } from 'elkjs/lib/elk-api'
|
||||
import type { HumanInputNodeType } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import type { CaseItem, IfElseNodeType } from '@/app/components/workflow/nodes/if-else/types'
|
||||
import type {
|
||||
Edge,
|
||||
@@ -345,6 +346,76 @@ const buildIfElseWithPorts = (
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Human Input node with ELK native Ports for multiple branches
|
||||
* Handles user actions as branches with __timeout as the last fixed branch
|
||||
*/
|
||||
const buildHumanInputWithPorts = (
|
||||
humanInputNode: Node,
|
||||
edges: Edge[],
|
||||
): { node: ElkNodeShape, portMap: Map<string, string> } | null => {
|
||||
const childEdges = edges.filter(edge => edge.source === humanInputNode.id)
|
||||
|
||||
if (childEdges.length <= 1)
|
||||
return null
|
||||
|
||||
// Sort child edges: user actions first (by order), then __timeout last
|
||||
const sortedChildEdges = [...childEdges].sort((edgeA, edgeB) => {
|
||||
const handleA = edgeA.sourceHandle
|
||||
const handleB = edgeB.sourceHandle
|
||||
|
||||
if (handleA && handleB) {
|
||||
const userActions = (humanInputNode.data as HumanInputNodeType).user_actions || []
|
||||
const isATimeout = handleA === '__timeout'
|
||||
const isBTimeout = handleB === '__timeout'
|
||||
|
||||
// __timeout should always be last
|
||||
if (isATimeout)
|
||||
return 1
|
||||
if (isBTimeout)
|
||||
return -1
|
||||
|
||||
// Sort by user_actions order
|
||||
const indexA = userActions.findIndex(action => action.id === handleA)
|
||||
const indexB = userActions.findIndex(action => action.id === handleB)
|
||||
|
||||
if (indexA !== -1 && indexB !== -1)
|
||||
return indexA - indexB
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
|
||||
// Create ELK ports for each branch
|
||||
const ports: ElkPortShape[] = sortedChildEdges.map((edge, index) => ({
|
||||
id: `${humanInputNode.id}-port-${edge.sourceHandle || index}`,
|
||||
layoutOptions: {
|
||||
'port.side': 'EAST',
|
||||
'port.index': String(index),
|
||||
},
|
||||
}))
|
||||
|
||||
// Build port mapping: edge.id -> portId
|
||||
const portMap = new Map<string, string>()
|
||||
sortedChildEdges.forEach((edge, index) => {
|
||||
const portId = `${humanInputNode.id}-port-${edge.sourceHandle || index}`
|
||||
portMap.set(edge.id, portId)
|
||||
})
|
||||
|
||||
return {
|
||||
node: {
|
||||
id: humanInputNode.id,
|
||||
width: humanInputNode.width ?? DEFAULT_NODE_WIDTH,
|
||||
height: humanInputNode.height ?? DEFAULT_NODE_HEIGHT,
|
||||
ports,
|
||||
layoutOptions: {
|
||||
'elk.portConstraints': 'FIXED_ORDER',
|
||||
},
|
||||
},
|
||||
portMap,
|
||||
}
|
||||
}
|
||||
|
||||
const normaliseBounds = (layout: LayoutResult): LayoutResult => {
|
||||
const {
|
||||
nodes,
|
||||
@@ -388,7 +459,7 @@ export const getLayoutByDagre = async (originNodes: Node[], originEdges: Edge[])
|
||||
// Track which edges have been processed for If/Else nodes with ports
|
||||
const edgeToPortMap = new Map<string, string>()
|
||||
|
||||
// Build nodes with ports for If/Else nodes
|
||||
// Build nodes with ports for If/Else and Human Input nodes
|
||||
nodes.forEach((node) => {
|
||||
if (node.data.type === BlockEnum.IfElse) {
|
||||
const portsResult = buildIfElseWithPorts(node, edges)
|
||||
@@ -405,6 +476,21 @@ export const getLayoutByDagre = async (originNodes: Node[], originEdges: Edge[])
|
||||
elkNodes.push(toElkNode(node))
|
||||
}
|
||||
}
|
||||
else if (node.data.type === BlockEnum.HumanInput) {
|
||||
const portsResult = buildHumanInputWithPorts(node, edges)
|
||||
if (portsResult) {
|
||||
// Use node with ports
|
||||
elkNodes.push(portsResult.node)
|
||||
// Store port mappings for edges
|
||||
portsResult.portMap.forEach((portId, edgeId) => {
|
||||
edgeToPortMap.set(edgeId, portId)
|
||||
})
|
||||
}
|
||||
else {
|
||||
// No multiple branches, use normal node
|
||||
elkNodes.push(toElkNode(node))
|
||||
}
|
||||
}
|
||||
else {
|
||||
elkNodes.push(toElkNode(node))
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ export const canRunBySingle = (nodeType: BlockEnum, isChildNode: boolean) => {
|
||||
|| nodeType === BlockEnum.IfElse
|
||||
|| nodeType === BlockEnum.VariableAggregator
|
||||
|| nodeType === BlockEnum.Assigner
|
||||
|| nodeType === BlockEnum.HumanInput
|
||||
|| nodeType === BlockEnum.DataSource
|
||||
|| nodeType === BlockEnum.TriggerSchedule
|
||||
|| nodeType === BlockEnum.TriggerWebhook
|
||||
|
||||
Reference in New Issue
Block a user