Spaces:
Sleeping
Sleeping
File size: 2,942 Bytes
8a3cb2b a498dfd f9a51ca 5d10601 f9a51ca bea515c a550b37 f9a51ca bea515c a550b37 f9a51ca bea515c f9a51ca a550b37 f9a51ca a550b37 bea515c f9a51ca bea515c a550b37 bea515c f9a51ca bea515c a550b37 bea515c a550b37 bea515c a550b37 bea515c a550b37 bea515c a550b37 bea515c f9a51ca |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# Author: Liam Grinstead
# SVG tree with tier and overlay labels
from lineage_tracker import get_ancestors, get_descendants
def _row_positions(n: int, width: int, y: int):
if n <= 0:
return []
step = width // (n + 1)
return [(step * (i + 1), y) for i in range(n)]
def render_lineage_tree(agent_id: str, overlay_color_hue: int = 260, max_depth: int = 6) -> str:
width, height = 980, 480 # reduced height to avoid cutoff
svg = [f"<svg width='{width}' height='{height}' xmlns='http://www.w3.org/2000/svg'>"]
svg.append("<style>text{font-family:monospace}</style>")
svg.append(f"<text x='12' y='24' font-size='16'>RFT Lineage: {agent_id}</text>")
root_x, root_y = width // 2, 170
# Ancestors
ancestors = get_ancestors(agent_id)
if ancestors:
pos = _row_positions(len(ancestors), width, 80)
for (x, y), aid in zip(pos, ancestors):
svg.append(f"<circle cx='{x}' cy='{y}' r='14' fill='hsl({overlay_color_hue},60%,50%)'/>")
svg.append(f"<text x='{x-28}' y='{y+32}' font-size='11'>{aid}</text>")
x_last, y_last = pos[-1]
svg.append(f"<line x1='{x_last}' y1='{y_last+14}' x2='{root_x}' y2='{root_y-20}' stroke='#555'/>")
else:
svg.append("<text x='12' y='80' font-size='12' fill='#888'>No ancestors</text>")
# Root agent
svg.append(
f"<circle cx='{root_x}' cy='{root_y}' r='18' fill='#00b894'>"
"<animate attributeName='r' values='18;22;18' dur='2s' repeatCount='indefinite'/>"
"</circle>"
)
svg.append(f"<text x='{root_x-40}' y='{root_y+40}' font-size='12'>{agent_id}</text>")
# Descendants
levels = get_descendants(agent_id, depth=max_depth)
y = 260
gap = 90
for d in range(1, max_depth + 1):
nodes = levels.get(d, [])
if not nodes:
break
pos = _row_positions(len(nodes), width, y)
svg.append(f"<text x='12' y='{y-26}' font-size='12' fill='#888'>Gen {d}</text>")
for (x, yy), item in zip(pos, nodes):
hue = 40 + (15 * d)
child_id = item.get("child_id", "?")
tier = item.get("tier", "?")
overlay = item.get("overlay", "?")
svg.append(
f"<circle cx='{x}' cy='{yy}' r='14' fill='hsl({hue},70%,55%)'>"
"<animate attributeName='fill' "
f"values='hsl({hue},70%,55%);hsl({hue},70%,65%);hsl({hue},70%,55%)' "
"dur='2s' repeatCount='indefinite'/>"
"</circle>"
)
label = f"{child_id} · {tier} · {overlay}"
svg.append(f"<text x='{x-60}' y='{yy+32}' font-size='11'>{label}</text>")
svg.append(
f"<path d='M {root_x} {root_y+20} Q {(root_x+x)//2} {(root_y+yy)//2} {x} {yy-14}' "
"stroke='rgba(0,0,0,.35)' fill='none'/>"
)
y += gap
svg.append("</svg>")
return "".join(svg)
|