2025-12-14 12:50:54 +01:00
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 " )
2025-12-14 13:23:16 +01:00
# Session State initialisieren
2025-12-14 12:50:54 +01:00
if " graph_center_id " not in st . session_state : st . session_state . graph_center_id = None
2025-12-14 13:23:16 +01:00
# Defaults speichern für Persistenz
2025-12-14 12:50:54 +01:00
st . session_state . setdefault ( " graph_depth " , 2 )
st . session_state . setdefault ( " graph_show_labels " , True )
2025-12-14 13:23:16 +01:00
# Defaults angepasst für BarnesHut (andere Skala!)
2025-12-14 12:50:54 +01:00
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 " )
2025-12-14 13:23:16 +01:00
# Suche
2025-12-14 12:50:54 +01:00
search_term = st . text_input ( " Suche Notiz " , placeholder = " Titel eingeben... " )
2025-12-14 13:23:16 +01:00
options = { }
2025-12-14 12:50:54 +01:00
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 }
2025-12-14 13:23:16 +01:00
2025-12-14 12:50:54 +01:00
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 ( )
2025-12-14 13:23:16 +01:00
# View Settings
2025-12-14 12:50:54 +01:00
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)** " )
2025-12-14 13:23:16 +01:00
# 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? " )
2025-12-14 12:50:54 +01:00
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 :
2025-12-14 13:23:16 +01:00
# Container für Action Bar OBERHALB des Graphen (Layout Fix)
2025-12-14 12:50:54 +01:00
action_container = st . container ( )
2025-12-14 13:23:16 +01:00
# Graph Laden
2025-12-14 12:50:54 +01:00
with st . spinner ( f " Lade Graph... " ) :
2025-12-14 13:23:16 +01:00
# Daten laden (Cache wird genutzt)
2025-12-14 12:50:54 +01:00
nodes , edges = graph_service . get_ego_graph (
center_id ,
depth = st . session_state . graph_depth ,
show_labels = st . session_state . graph_show_labels
)
2025-12-14 13:23:16 +01:00
# Fetch Note Data für Button & Debug
# Wir holen die Metadaten (inkl. path), was für den Editor-Callback reicht.
2025-12-14 12:50:54 +01:00
note_data = graph_service . _fetch_note_cached ( center_id )
2025-12-14 13:23:16 +01:00
# --- ACTION BAR RENDEREN ---
2025-12-14 12:50:54 +01:00
with action_container :
2025-12-14 13:23:16 +01:00
c_act1 , c_act2 = st . columns ( [ 3 , 1 ] )
with c_act1 :
st . caption ( f " Aktives Zentrum: ** { center_id } ** " )
with c_act2 :
2025-12-14 12:50:54 +01:00
if note_data :
2025-12-14 13:23:16 +01:00
st . button ( " 📝 Bearbeiten " ,
use_container_width = True ,
on_click = switch_to_editor_callback ,
args = ( note_data , ) )
2025-12-14 12:50:54 +01:00
else :
2025-12-14 13:23:16 +01:00
st . error ( " Daten nicht verfügbar " )
2025-12-14 12:50:54 +01:00
2025-12-14 13:23:16 +01:00
# DATA INSPECTOR (Payload Debug)
with st . expander ( " 🕵️ Data Inspector (Payload Debug) " , expanded = False ) :
2025-12-14 12:50:54 +01:00
if note_data :
st . json ( note_data )
2025-12-14 13:23:16 +01:00
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. " )
2025-12-14 12:50:54 +01:00
if not nodes :
st . warning ( " Keine Daten gefunden. " )
else :
2025-12-14 13:23:16 +01:00
# --- CONFIGURATION (BarnesHut) ---
# Height-Trick für Re-Render (da key-Parameter nicht funktioniert)
# Ändere Height minimal basierend auf Gravity
2025-12-14 12:50:54 +01:00
dyn_height = 800 + ( abs ( st . session_state . graph_gravity ) % 5 )
config = Config (
width = 1000 ,
2025-12-14 13:23:16 +01:00
height = dyn_height ,
2025-12-14 12:50:54 +01:00
directed = True ,
physics = {
" enabled " : True ,
2025-12-14 13:23:16 +01:00
# BarnesHut ist der Standard und stabilste Solver für Agraph
2025-12-14 12:50:54 +01:00
" 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 )
2025-12-14 13:23:16 +01:00
# Interaktions-Logik
2025-12-14 12:50:54 +01:00
if return_value :
if return_value != center_id :
2025-12-14 13:23:16 +01:00
# Navigation: Neues Zentrum setzen
2025-12-14 12:50:54 +01:00
st . session_state . graph_center_id = return_value
st . rerun ( )
else :
2025-12-14 13:23:16 +01:00
# Klick auf das Zentrum selbst
2025-12-14 12:50:54 +01:00
st . toast ( f " Zentrum: { return_value } " )
2025-12-14 13:23:16 +01:00
2025-12-14 12:50:54 +01:00
else :
st . info ( " 👈 Bitte wähle links eine Notiz aus. " )