mindnet/app/frontend/ui_graph.py
2025-12-14 13:23:16 +01:00

155 lines
6.9 KiB
Python

import streamlit as st
from streamlit_agraph import agraph, Config
from qdrant_client import models
from ui_config import COLLECTION_PREFIX, GRAPH_COLORS
from ui_callbacks import switch_to_editor_callback
def render_graph_explorer(graph_service):
st.header("🕸️ Graph Explorer")
# Session State initialisieren
if "graph_center_id" not in st.session_state: st.session_state.graph_center_id = None
# Defaults speichern für Persistenz
st.session_state.setdefault("graph_depth", 2)
st.session_state.setdefault("graph_show_labels", True)
# Defaults angepasst für BarnesHut (andere Skala!)
st.session_state.setdefault("graph_spacing", 150)
st.session_state.setdefault("graph_gravity", -3000)
col_ctrl, col_graph = st.columns([1, 4])
with col_ctrl:
st.subheader("Fokus")
# Suche
search_term = st.text_input("Suche Notiz", placeholder="Titel eingeben...")
options = {}
if search_term:
hits, _ = graph_service.client.scroll(
collection_name=f"{COLLECTION_PREFIX}_notes",
scroll_filter=models.Filter(must=[models.FieldCondition(key="title", match=models.MatchText(text=search_term))]),
limit=10
)
options = {h.payload['title']: h.payload['note_id'] for h in hits}
if options:
selected_title = st.selectbox("Ergebnisse:", list(options.keys()))
if st.button("Laden", use_container_width=True):
st.session_state.graph_center_id = options[selected_title]
st.rerun()
st.divider()
# View Settings
with st.expander("👁️ Ansicht & Layout", expanded=True):
st.session_state.graph_depth = st.slider("Tiefe (Tier)", 1, 3, st.session_state.graph_depth)
st.session_state.graph_show_labels = st.checkbox("Kanten-Beschriftung", st.session_state.graph_show_labels)
st.markdown("**Physik (BarnesHut)**")
# ACHTUNG: BarnesHut reagiert anders. Spring Length ist wichtig.
st.session_state.graph_spacing = st.slider("Federlänge (Abstand)", 50, 500, st.session_state.graph_spacing, help="Wie lang sollen die Verbindungen sein?")
st.session_state.graph_gravity = st.slider("Abstoßung (Gravity)", -20000, -1000, st.session_state.graph_gravity, help="Wie stark sollen sich Knoten abstoßen?")
if st.button("Reset Layout"):
st.session_state.graph_spacing = 150
st.session_state.graph_gravity = -3000
st.rerun()
st.divider()
st.caption("Legende (Top Typen)")
for k, v in list(GRAPH_COLORS.items())[:8]:
st.markdown(f"<span style='color:{v}'>●</span> {k}", unsafe_allow_html=True)
with col_graph:
center_id = st.session_state.graph_center_id
if center_id:
# Container für Action Bar OBERHALB des Graphen (Layout Fix)
action_container = st.container()
# Graph Laden
with st.spinner(f"Lade Graph..."):
# Daten laden (Cache wird genutzt)
nodes, edges = graph_service.get_ego_graph(
center_id,
depth=st.session_state.graph_depth,
show_labels=st.session_state.graph_show_labels
)
# Fetch Note Data für Button & Debug
# Wir holen die Metadaten (inkl. path), was für den Editor-Callback reicht.
note_data = graph_service._fetch_note_cached(center_id)
# --- ACTION BAR RENDEREN ---
with action_container:
c_act1, c_act2 = st.columns([3, 1])
with c_act1:
st.caption(f"Aktives Zentrum: **{center_id}**")
with c_act2:
if note_data:
st.button("📝 Bearbeiten",
use_container_width=True,
on_click=switch_to_editor_callback,
args=(note_data,))
else:
st.error("Daten nicht verfügbar")
# DATA INSPECTOR (Payload Debug)
with st.expander("🕵️ Data Inspector (Payload Debug)", expanded=False):
if note_data:
st.json(note_data)
if 'path' not in note_data:
st.error("ACHTUNG: Feld 'path' fehlt im Qdrant-Payload! Update-Modus wird nicht funktionieren.")
else:
st.success(f"Pfad gefunden: {note_data['path']}")
else:
st.info("Keine Daten geladen.")
if not nodes:
st.warning("Keine Daten gefunden.")
else:
# --- CONFIGURATION (BarnesHut) ---
# Height-Trick für Re-Render (da key-Parameter nicht funktioniert)
# Ändere Height minimal basierend auf Gravity
dyn_height = 800 + (abs(st.session_state.graph_gravity) % 5)
config = Config(
width=1000,
height=dyn_height,
directed=True,
physics={
"enabled": True,
# BarnesHut ist der Standard und stabilste Solver für Agraph
"solver": "barnesHut",
"barnesHut": {
"gravitationalConstant": st.session_state.graph_gravity,
"centralGravity": 0.3,
"springLength": st.session_state.graph_spacing,
"springConstant": 0.04,
"damping": 0.09,
"avoidOverlap": 0.1
},
"stabilization": {"enabled": True, "iterations": 600}
},
hierarchical=False,
nodeHighlightBehavior=True,
highlightColor="#F7A7A6",
collapsible=False
)
return_value = agraph(nodes=nodes, edges=edges, config=config)
# Interaktions-Logik
if return_value:
if return_value != center_id:
# Navigation: Neues Zentrum setzen
st.session_state.graph_center_id = return_value
st.rerun()
else:
# Klick auf das Zentrum selbst
st.toast(f"Zentrum: {return_value}")
else:
st.info("👈 Bitte wähle links eine Notiz aus.")