py/facett_demo/panes/graph_pane.py
"""GRAPH pane — the REAL facett graph navigator, embedded as wasm.
Like the map pane, this renders **no graph of its own**. It fetches the scene from
korp-server and hands it to the `graphnav` component in the `facet-wrapped` registry —
`facett_graphnav::GraphNav`, the same Graph-DB IDE the native demo ships (query bar,
canvas, inspector, layouts, path-finding), mounted as a live egui canvas.
It previously re-implemented the graph with a networkx spring layout drawn in plotly.
That is a second renderer with its own layout, styling and interaction model, and it
drifts from the real component by construction.
Data source: korp-server ``KorpControl.Graph`` — a Cypher query against the bound
FalkorDB graph (the fat node answers with a seed scene when no live FalkorDB is
attached).
"""
from __future__ import annotations
import streamlit as st
from ..facet_host import facett_component, is_data_driven
from ..client import KorpClient, KorpUnavailable
#: The facett component this pane embeds (registry name in `facet-wrapped`).
COMPONENT = "graphnav"
def _graph_payload(scene, graph_name: str) -> dict:
"""korp `GraphReply` → the `graphnav` payload (a pure re-shaping)."""
return {
"title": f"korp graph{f' · {graph_name}' if graph_name else ''}",
"nodes": [{"id": n.id, "label": n.label, "key": n.key} for n in scene.nodes],
"edges": [{"src": e.src, "dst": e.dst, "rel": e.rel} for e in scene.edges],
}
def render(client: KorpClient) -> None:
st.subheader("🕸 Graph — the real facett graph navigator")
st.caption(
"Renderer: `facet-wrapped` → `graphnav` (facett_graphnav::GraphNav, wasm) · "
"Data: korp-server `Graph` RPC"
)
# Blank = the server's own scene read. korp-server decodes ONE result shape (its
# `RETURN id(a) AS src, id(b) AS dst, type(r) AS rel, … a.lon AS slon …` tail); a
# query with another RETURN shape decodes to zero rows and comes back as
# `korp-graph-9 … holds 0 nodes` even against a full graph (loki, 2026-09-19).
# A hand-written query must therefore end in that tail — the help text says so.
default_q = ""
query = st.text_input(
"Cypher query (blank = the server's scene read)", value=default_q, key="graph_query",
help="Ad-hoc queries must RETURN the server's scene columns: id(a) AS src, "
"id(b) AS dst, type(r) AS rel, labels(a)[0] AS sl, labels(b)[0] AS dl, "
"a.lon AS slon, a.lat AS slat, b.lon AS dlon, b.lat AS dlat. Any other "
"RETURN shape reads as an empty graph.",
)
graph_name = st.text_input("Graph (blank = server default)", value="", key="graph_name")
try:
scene = client.graph(query=query, graph=graph_name)
except KorpUnavailable as e:
st.error(f"Graph unavailable — server could not answer.\n\n`{e}`")
return
if not scene.nodes:
st.warning("Server returned an empty graph (honest empty state).")
st.caption(scene.status)
return
c1, c2, c3 = st.columns(3)
c1.metric("Nodes", len(scene.nodes))
c2.metric("Edges", len(scene.edges))
c3.metric("Status", scene.status.split("(")[0].strip() or "ok")
if not is_data_driven(COMPONENT):
st.error(
f"`{COMPONENT}` is not available as a data-driven facett component — the "
"`facet-wrapped` wasm bundle is missing or stale. Rebuild it with "
"`cargo xtask wrap-wasm`. Refusing to show a demo fixture in place of "
"your data."
)
return
facett_component(
COMPONENT, data=_graph_payload(scene, graph_name), height=620, key="graphnav_graph"
)
st.caption(scene.status)