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 für View & Physik setzen st.session_state.setdefault("graph_depth", 2) st.session_state.setdefault("graph_show_labels", True) # Höhere Default-Werte für Abstand st.session_state.setdefault("graph_spacing", 250) st.session_state.setdefault("graph_gravity", -4000) col_ctrl, col_graph = st.columns([1, 4]) # --- LINKE SPALTE: CONTROLS --- with col_ctrl: st.subheader("Fokus") # Sucheingabe search_term = st.text_input("Suche Notiz", placeholder="Titel eingeben...") # Suchlogik Qdrant 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() # Layout & Physik Einstellungen 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)**") st.session_state.graph_spacing = st.slider("Federlänge (Abstand)", 50, 800, st.session_state.graph_spacing) st.session_state.graph_gravity = st.slider("Abstoßung (Gravity)", -20000, -500, st.session_state.graph_gravity) if st.button("Reset Layout"): st.session_state.graph_spacing = 250 st.session_state.graph_gravity = -4000 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) # --- RECHTE SPALTE: GRAPH & ACTION BAR --- with col_graph: center_id = st.session_state.graph_center_id if center_id: # Action Container oben fixieren (Layout Fix) action_container = st.container() # Graph und Daten laden with st.spinner(f"Lade Graph..."): nodes, edges = graph_service.get_ego_graph( center_id, depth=st.session_state.graph_depth, show_labels=st.session_state.graph_show_labels ) # WICHTIG: Daten für Editor holen (inkl. Pfad) note_data = graph_service.get_note_with_full_content(center_id) # Action Bar rendern with action_container: c1, c2 = st.columns([3, 1]) with c1: st.caption(f"Aktives Zentrum: **{center_id}**") with c2: if note_data: st.button("📝 Bearbeiten", use_container_width=True, on_click=switch_to_editor_callback, args=(note_data,)) else: st.error("Datenfehler: Note nicht gefunden") # Debug Inspector with st.expander("🕵️ Data Inspector", expanded=False): if note_data: st.json(note_data) if 'path' in note_data: st.success(f"Pfad OK: {note_data['path']}") else: st.error("Pfad fehlt!") else: st.info("Leer.") if not nodes: st.warning("Keine Daten gefunden.") else: # --- CONFIGURATION (BarnesHut) --- # Height-Trick für Re-Render (da key-Parameter manchmal crasht) dyn_height = 800 + (abs(st.session_state.graph_gravity) % 5) config = Config( width=1000, height=dyn_height, directed=True, physics={ "enabled": True, "solver": "barnesHut", "barnesHut": { "gravitationalConstant": st.session_state.graph_gravity, "centralGravity": 0.005, # Extrem wichtig für die Ausbreitung! "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 (Klick auf Node) 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, um den Graphen zu starten.")