"""
GESDA Knowledge Graph Explorer โ Streamlit entry point.
Run with:
streamlit run graphrag_UI/app.py
"""
from __future__ import annotations
import sys
from pathlib import Path
# Ensure graphrag_UI/ is on the path for relative imports inside the package
_HERE = Path(__file__).parent.resolve()
if str(_HERE) not in sys.path:
sys.path.insert(0, str(_HERE))
import streamlit as st
import config # noqa: F401 โ bootstraps project paths + loads .env
from config import APP_TITLE, APP_ICON
from db.neo4j_client import get_neo4j_resources, get_graph_summary
from db.vector_client import get_vector_resources
from queries.schema import NODE_COLORS
from ui.styles import inject_css
import ui.tab_vector_search as tab_vs
import ui.tab_hardcoded as tab_hq
import ui.tab_query_builder as tab_qb
# ---------------------------------------------------------------------------
# Page config (must be first Streamlit call)
# ---------------------------------------------------------------------------
st.set_page_config(
page_title=APP_TITLE,
page_icon=APP_ICON,
layout="wide",
initial_sidebar_state="expanded",
)
inject_css()
# ---------------------------------------------------------------------------
# Header
# ---------------------------------------------------------------------------
st.markdown(
"""
๐ฌ GESDA Knowledge Graph Explorer
""",
unsafe_allow_html=True,
)
# ---------------------------------------------------------------------------
# Sidebar โ connection status
# ---------------------------------------------------------------------------
with st.sidebar:
st.markdown("### Connection")
# Neo4j
summary = get_graph_summary()
if summary.get("connected"):
st.markdown(
' **Neo4j** connected',
unsafe_allow_html=True,
)
with st.expander("Graph statistics", expanded=False):
st.metric("Total nodes", f'{summary["total_nodes"]:,}')
st.metric("Total relationships", f'{summary["total_relationships"]:,}')
nt = summary.get("node_types", {})
if nt:
rows = sorted(nt.items(), key=lambda x: x[1], reverse=True)
for label, cnt in rows:
color = NODE_COLORS.get(label, "#888")
st.markdown(
f'โ {label}: {cnt:,}',
unsafe_allow_html=True,
)
else:
st.markdown(
f' **Neo4j** โ {summary.get("error", "unavailable")}',
unsafe_allow_html=True,
)
# Vector search
_, _, vec_err = get_vector_resources()
if not vec_err:
st.markdown(
' **Vector search** ready',
unsafe_allow_html=True,
)
else:
st.markdown(
' **Vector search** unavailable',
unsafe_allow_html=True,
)
with st.expander("Details", expanded=False):
st.caption(vec_err)
st.divider()
st.markdown(
"""
**Tabs**
- **Hardcoded Queries** - examples of graph capabilities and explore the relationships
- **Vector Search** โ find nodes using non-jargon language
- **Query Builder** โ interactive Cypher builder
""",
unsafe_allow_html=False,
)
st.divider()
st.caption("GESDA ยท EPFL ยท 2026")
# ---------------------------------------------------------------------------
# Main tabs
# ---------------------------------------------------------------------------
tab_hard, tab_vec, tab_build = st.tabs([
"๐ Hardcoded Queries",
"๐ญ Vector Search",
"๐ ๏ธ Query Builder",
])
with tab_hard:
tab_hq.render()
with tab_vec:
tab_vs.render()
with tab_build:
tab_qb.render()