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" {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.")