"""
graph_view.py - Generiert interaktive Graph-Visualisierung (Cytoscape.js).
"""
import json
from pathlib import Path
from typing import Optional
from .store import EngramStore
_HTML_TEMPLATE = """
Second Brain Graph
🧠 Second Brain Graph
Knoten: Engramme (Farbe = Confidence)
Grün=hoch, Gelb=mittel, Rot=niedrig
Links: Verknüpfungen
Klicke für Details
"""
def _confidence_color(conf: float) -> str:
if conf >= 0.8:
return "#27ae60" # Green
elif conf >= 0.5:
return "#f39c12" # Yellow
else:
return "#e74c3c" # Red
def _node_size(access_count: int) -> float:
return max(20, min(60, 20 + access_count * 5))
def generate_graph_html(store: EngramStore, output_path: str) -> str:
"""Generiert interaktive HTML-Graph-Visualisierung."""
# store.get_all() defaults to 1000; paginate so the graph can include all nodes.
engrams = []
offset = 0
while True:
batch = store.get_all(limit=2000, offset=offset)
if not batch:
break
engrams.extend(batch)
offset += len(batch)
nodes = []
edges = []
node_ids = set(str(e.id) for e in engrams)
for eg in engrams:
eid = str(eg.id)
conf = eg.compute_confidence()
color = _confidence_color(conf)
size = _node_size(eg.metadata.get("access_count", 0))
tags = ", ".join(eg.metadata.get("tags", []))
nodes.append({
"data": {
"id": eid,
"label": eg.content[:40] + ("..." if len(eg.content) > 40 else ""),
"title": eg.content,
"color": color,
"size": size,
"confidence": conf,
"confirmed": eg.correctness.confirmed,
"verdict": getattr(eg.correctness, "verdict", "unknown"),
"source": eg.metadata.get("source", "?"),
"tags": tags,
}
})
# Add edges after all nodes are known (otherwise early nodes miss links).
for eg in engrams:
eid = str(eg.id)
for lid in eg.links:
lid_s = str(lid)
if lid_s in node_ids:
edges.append({"data": {"id": f"{eid}_{lid_s}", "source": eid, "target": lid_s}})
elements = {"nodes": nodes, "edges": edges}
html = _HTML_TEMPLATE.format(elements_json=json.dumps(elements, ensure_ascii=False))
out = Path(output_path)
out.parent.mkdir(parents=True, exist_ok=True)
with open(out, "w", encoding="utf-8") as f:
f.write(html)
return str(out)