From e19520db74ef36fb55b14a5bbea6990453cd162d Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 29 Mar 2026 18:42:53 +0200 Subject: [PATCH] v0.15.4: Populate graph + nodes panel on page load - /api/graph/active now includes node_details (model, max_tokens per node) - graph.js calls initNodesFromGraph() after fetching active graph - Nodes panel shows all nodes with models immediately on load (before first message) Co-Authored-By: Claude Opus 4.6 (1M context) --- agent/api.py | 15 +++++++++++++++ static/js/awareness.js | 17 +++++++++++++++++ static/js/graph.js | 3 +++ 3 files changed, 35 insertions(+) diff --git a/agent/api.py b/agent/api.py index 2e46368..474eb06 100644 --- a/agent/api.py +++ b/agent/api.py @@ -380,11 +380,26 @@ def register_routes(app): from .engine import load_graph, get_graph_for_cytoscape from .runtime import _active_graph_name graph = load_graph(_active_graph_name) + # Include model info from instantiated nodes if runtime exists + node_details = {} + if _active_runtime: + for role, impl_name in graph["nodes"].items(): + # Find the node instance by role + node_inst = getattr(_active_runtime, 'frame_engine', None) + if node_inst and hasattr(node_inst, 'nodes'): + inst = node_inst.nodes.get(role) + if inst: + node_details[role] = { + "impl": impl_name, + "model": getattr(inst, 'model', None) or '', + "max_tokens": getattr(inst, 'max_context_tokens', 0), + } return { "name": graph["name"], "description": graph["description"], "nodes": graph["nodes"], "edges": graph["edges"], + "node_details": node_details, "cytoscape": get_graph_for_cytoscape(graph), } diff --git a/static/js/awareness.js b/static/js/awareness.js index 4e08f63..05e7b82 100644 --- a/static/js/awareness.js +++ b/static/js/awareness.js @@ -111,6 +111,23 @@ function renderNodes() { el.innerHTML = html; } +export function initNodesFromGraph(graphData) { + // Populate node cards from graph definition (before any messages) + const nodes = graphData.nodes || {}; + const details = graphData.node_details || {}; + for (const [role, impl] of Object.entries(nodes)) { + const n = _getNode(role); + const d = details[role]; + if (d) { + n.model = (d.model || '').replace('google/', '').replace('anthropic/', ''); + n.maxTokens = d.max_tokens || 0; + } + n.lastEvent = 'idle'; + n.status = 'idle'; + } + renderNodes(); +} + export function clearNodes() { for (const key of Object.keys(_nodeState)) delete _nodeState[key]; const el = document.getElementById('node-metrics'); diff --git a/static/js/graph.js b/static/js/graph.js index c717ac7..4c25920 100644 --- a/static/js/graph.js +++ b/static/js/graph.js @@ -1,5 +1,7 @@ /** Pipeline graph: Cytoscape visualization + animation. */ +import { initNodesFromGraph } from './awareness.js'; + let cy = null; let _dragEnabled = true; let _physicsRunning = false; @@ -90,6 +92,7 @@ export async function initGraph() { if (resp.ok) { const graph = await resp.json(); graphElements = buildGraphElements(graph, mx, cw, mid, row1, row2); + initNodesFromGraph(graph); } } catch (e) {}