mirror of
https://github.com/langgenius/dify.git
synced 2026-10-04 13:47:55 +08:00
The previous ESQ1-288 fix dropped the iteration/loop start marker, stripped
start_node_id from the container config and dropped the generator's container
entry edge. All three are required by the ENGINE, not just the canvas:
IterationNodeData.start_node_id / LoopNodeData.start_node_id have no default,
node data is validated eagerly at graph construction, and the body frame is
rooted at start_node_id. Every container workflow the Builder produced was
therefore non-runnable -- worse than the canvas crash it replaced.
The real root cause was never the marker node. The generator already emits it
with the correct node-level ReactFlow type ("custom-iteration-start" /
"custom-loop-start", runner.py:1350-1362); _build_node hardcoded "type":
"custom" and threw it away, leaving a node the frontend has no component for
(React error #130).
Carry it through instead, the same way the previous commit carried
parentId/position: a defaulted `flow_type` on _build_node / apply_create_node
(absent -> "custom", so every existing caller is byte-identical), set from the
source node's type in to_intents. The marker, start_node_id and the entry edge
are all persisted again.
Tests: the three tests that asserted the ABSENCE of the marker, start_node_id
and the entry edge are replaced by ones asserting their presence, plus the
round-trip test that would have caught this -- to_intents -> filter_applicable
-> APPLY_FNS -> IterationNodeData/LoopNodeData.model_validate on the persisted
container.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
73 lines
3.0 KiB
Python
73 lines
3.0 KiB
Python
"""Translate a WorkflowGenerator graph into Dify Builder MutationIntents.
|
|
|
|
The generator emits ReactFlow nodes ({id, type, data:{type:<real>,...}}) and
|
|
edges. The node-level "type" is the ReactFlow component key -- "custom" for
|
|
ordinary nodes, but "custom-iteration-start" / "custom-loop-start" for the
|
|
container start markers -- and it is carried through as `flow_type`, because
|
|
a marker persisted as a generic "custom" node has no canvas component and
|
|
crashes the editor (React error #130, ESQ1-288). We emit one create_node per
|
|
node (config = node.data minus the redundant "type" key -- apply_create_node
|
|
re-sets it) followed by one connect per edge, so every connect endpoint
|
|
already exists at apply time. Node ids are preserved verbatim -- including
|
|
the generator's start node id -- so intra-node references (variable
|
|
selectors, prompt {{#id.var#}}) stay valid; reconciling the generator's start
|
|
node with the draft's existing start is the handler's job, not this
|
|
translation step."""
|
|
|
|
from typing import Any
|
|
|
|
from core.dify_builder.models import MutationIntent
|
|
|
|
# The node-level ReactFlow type every ordinary node carries. Only a
|
|
# non-generic value is worth putting on an intent -- `_build_node` already
|
|
# defaults to this one.
|
|
_GENERIC_FLOW_TYPE = "custom"
|
|
|
|
|
|
def to_intents(graph: dict[str, Any]) -> list[MutationIntent]:
|
|
nodes = graph.get("nodes") or []
|
|
edges = graph.get("edges") or []
|
|
|
|
creates: list[MutationIntent] = []
|
|
for node in nodes:
|
|
data = node.get("data") or {}
|
|
node_type = data.get("type")
|
|
node_id = node.get("id")
|
|
if not node_type or not node_id:
|
|
continue
|
|
config = {k: v for k, v in data.items() if k != "type"}
|
|
args: dict[str, Any] = {
|
|
"node_type": str(node_type),
|
|
"config": config,
|
|
"node_id": str(node_id),
|
|
}
|
|
# The ReactFlow wrapper carries nesting and layout OUTSIDE `data`, so
|
|
# reading only id+data dropped them on every build: children landed
|
|
# un-nested and the generator's layout was replaced by defaults.
|
|
parent_id = node.get("parentId")
|
|
if parent_id:
|
|
args["parent_id"] = str(parent_id)
|
|
position = node.get("position")
|
|
if isinstance(position, dict):
|
|
args["position"] = dict(position)
|
|
flow_type = node.get("type")
|
|
if flow_type and flow_type != _GENERIC_FLOW_TYPE:
|
|
args["flow_type"] = str(flow_type)
|
|
creates.append(MutationIntent(op="create_node", args=args))
|
|
|
|
connects: list[MutationIntent] = []
|
|
for edge in edges:
|
|
src, tgt = edge.get("source"), edge.get("target")
|
|
if not src or not tgt:
|
|
continue
|
|
args: dict[str, Any] = {"from_node": str(src), "to_node": str(tgt)}
|
|
source_handle = edge.get("sourceHandle")
|
|
if source_handle:
|
|
args["source_handle"] = source_handle
|
|
target_handle = edge.get("targetHandle")
|
|
if target_handle:
|
|
args["target_handle"] = target_handle
|
|
connects.append(MutationIntent(op="connect", args=args))
|
|
|
|
return creates + connects
|