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") if "graph_center_id" not in st.session_state: st.session_state.graph_center_id = None # Defaults st.session_state.setdefault("graph_depth", 2) st.session_state.setdefault("graph_show_labels", True) 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") search_term = st.text_input("Suche Notiz", placeholder="Titel eingeben...") 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() 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", 50, 500, st.session_state.graph_spacing) st.session_state.graph_gravity = st.slider("Abstoßung", -20000, -500, st.session_state.graph_gravity) 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: # Action Container oben action_container = st.container() 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 ) note_data = graph_service._fetch_note_cached(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") 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: # Physik Config für BarnesHut + Height-Trick 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.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) if return_value: if return_value != center_id: st.session_state.graph_center_id = return_value st.rerun() else: st.toast(f"Zentrum: {return_value}") else: st.info("👈 Bitte wähle links eine Notiz aus.")