mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-10 08:05:22 +02:00
feat: refactor node spec and add mcp tools (#244)
* refactor: carve out extraction panel * refactor: create spec versions for node types * refactor: create a GenericNode and remove custom nodes * feat: add python and typescript sdk * add dograh sdk * fix: fetch draft workflow definition over published one * fix: fix routes of SDKs to use code gen * chore: remove doclink dependency to reduce image size * chore: format files * chore: bump pipecat * feat: let mcp fetch archived workflows on demand * chore: fix tests * feat: add sdk documentation * chore: change banner and add badge
This commit is contained in:
parent
0a61ef295f
commit
00a1a22b74
162 changed files with 14355 additions and 3554 deletions
124
api/tests/test_layout.py
Normal file
124
api/tests/test_layout.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""Tests for position reconciliation after the LLM save round-trip."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from api.services.workflow.layout import reconcile_positions
|
||||
|
||||
|
||||
def _node(
|
||||
id: str,
|
||||
type: str,
|
||||
*,
|
||||
name: str | None = None,
|
||||
x: float = 0.0,
|
||||
y: float = 0.0,
|
||||
) -> dict:
|
||||
data: dict = {}
|
||||
if name is not None:
|
||||
data["name"] = name
|
||||
return {"id": id, "type": type, "position": {"x": x, "y": y}, "data": data}
|
||||
|
||||
|
||||
def _edge(src: str, tgt: str) -> dict:
|
||||
return {
|
||||
"id": f"{src}-{tgt}",
|
||||
"source": src,
|
||||
"target": tgt,
|
||||
"data": {"label": "x", "condition": "y"},
|
||||
}
|
||||
|
||||
|
||||
def test_named_match_preserves_position():
|
||||
previous = {
|
||||
"nodes": [_node("99", "startCall", name="greeting", x=100, y=200)],
|
||||
"edges": [],
|
||||
}
|
||||
new = {
|
||||
"nodes": [_node("1", "startCall", name="greeting")],
|
||||
"edges": [],
|
||||
}
|
||||
out = reconcile_positions(new, previous)
|
||||
assert out["nodes"][0]["position"] == {"x": 100, "y": 200}
|
||||
|
||||
|
||||
def test_unnamed_match_by_type_ordering():
|
||||
previous = {
|
||||
"nodes": [
|
||||
_node("7", "agentNode", x=-648, y=-158),
|
||||
_node("8", "agentNode", x=500, y=-100),
|
||||
],
|
||||
"edges": [],
|
||||
}
|
||||
new = {
|
||||
"nodes": [
|
||||
_node("1", "agentNode"),
|
||||
_node("2", "agentNode"),
|
||||
],
|
||||
"edges": [],
|
||||
}
|
||||
out = reconcile_positions(new, previous)
|
||||
assert out["nodes"][0]["position"] == {"x": -648, "y": -158}
|
||||
assert out["nodes"][1]["position"] == {"x": 500, "y": -100}
|
||||
|
||||
|
||||
def test_new_node_placed_relative_to_incoming_neighbor():
|
||||
previous = {
|
||||
"nodes": [_node("99", "startCall", name="greeting", x=100, y=200)],
|
||||
"edges": [],
|
||||
}
|
||||
new = {
|
||||
"nodes": [
|
||||
_node("1", "startCall", name="greeting"),
|
||||
_node("2", "agentNode", name="new_node"),
|
||||
],
|
||||
"edges": [_edge("1", "2")],
|
||||
}
|
||||
out = reconcile_positions(new, previous)
|
||||
# Start call keeps its previous position.
|
||||
assert out["nodes"][0]["position"] == {"x": 100, "y": 200}
|
||||
# New node offset from its incoming neighbor.
|
||||
assert out["nodes"][1]["position"] == {"x": 500, "y": 400}
|
||||
|
||||
|
||||
def test_orphan_new_node_stays_at_origin():
|
||||
new = {
|
||||
"nodes": [_node("1", "agentNode", name="orphan")],
|
||||
"edges": [],
|
||||
}
|
||||
out = reconcile_positions(new, None)
|
||||
assert out["nodes"][0]["position"] == {"x": 0.0, "y": 0.0}
|
||||
|
||||
|
||||
def test_named_wins_over_unnamed_ordering():
|
||||
previous = {
|
||||
"nodes": [
|
||||
_node("7", "agentNode", x=-648, y=-158), # unnamed
|
||||
_node("8", "agentNode", name="qualify", x=900, y=900),
|
||||
],
|
||||
"edges": [],
|
||||
}
|
||||
new = {
|
||||
"nodes": [
|
||||
_node("1", "agentNode", name="qualify"), # matches named
|
||||
_node("2", "agentNode"), # falls to unnamed queue
|
||||
],
|
||||
"edges": [],
|
||||
}
|
||||
out = reconcile_positions(new, previous)
|
||||
assert out["nodes"][0]["position"] == {"x": 900, "y": 900}
|
||||
assert out["nodes"][1]["position"] == {"x": -648, "y": -158}
|
||||
|
||||
|
||||
def test_no_previous_keeps_origin_for_all_matched_positions():
|
||||
new = {
|
||||
"nodes": [
|
||||
_node("1", "startCall", name="greeting"),
|
||||
_node("2", "agentNode", name="reply"),
|
||||
],
|
||||
"edges": [_edge("1", "2")],
|
||||
}
|
||||
out = reconcile_positions(new, None)
|
||||
# No previous → first node stays at origin (no incoming), second
|
||||
# node placed relative to its incoming neighbor at origin.
|
||||
assert out["nodes"][0]["position"] == {"x": 0.0, "y": 0.0}
|
||||
assert out["nodes"][1]["position"] == {"x": 400.0, "y": 200.0}
|
||||
Loading…
Add table
Add a link
Reference in a new issue