import os import time import json import hashlib import requests import pandas as pd import plotly.express as px import plotly.graph_objects as go import streamlit as st import extra_streamlit_components as stx from datetime import datetime, timedelta RAG_API_URL = os.getenv("RAG_API_URL", "").rstrip("/") RAG_API_KEY = os.getenv("RAG_API_KEY", "") APP_USERNAME = os.getenv("USERNAME", "admin") APP_PASSWORD = os.getenv("PASSWORD", "") SESSION_DURATION_DAYS = 30 COOKIE_NAME = "nexus_auth_token" st.set_page_config( page_title="Nexus RAG", page_icon="⬡", layout="wide", initial_sidebar_state="expanded", ) st.markdown(""" """, unsafe_allow_html=True) LUCIDE_ICONS = { "lock": """""", } PLOTLY_LAYOUT = dict( paper_bgcolor="#0d0d18", plot_bgcolor="#0a0a0f", font=dict(color="#6b7280", family="Inter", size=12), margin=dict(t=30, b=30, l=10, r=10), xaxis=dict(gridcolor="#1e1e2e", zerolinecolor="#1e1e2e"), yaxis=dict(gridcolor="#1e1e2e", zerolinecolor="#1e1e2e"), legend=dict(bgcolor="rgba(0,0,0,0)", font=dict(color="#6b7280")), ) COLOR_PRIMARY = "#e94560" COLOR_SECONDARY = "#3b82f6" COLOR_SUCCESS = "#10b981" COLOR_WARNING = "#f59e0b" def get_cookie_manager(): if "cookie_manager" not in st.session_state: st.session_state.cookie_manager = stx.CookieManager(key="nexus_cookie_mgr") return st.session_state.cookie_manager def generate_token(username: str) -> str: secret = APP_PASSWORD + username + "nexus_salt_2024" return hashlib.sha256(secret.encode()).hexdigest() def verify_token(token: str) -> bool: return token == generate_token(APP_USERNAME) def check_auth() -> bool: try: cm = get_cookie_manager() token = cm.get(COOKIE_NAME) if token and verify_token(str(token)): return True except Exception: pass return False def do_login(username: str, password: str) -> bool: if username.strip() == APP_USERNAME and password == APP_PASSWORD: token = generate_token(username) expiry = datetime.now() + timedelta(days=SESSION_DURATION_DAYS) try: cm = get_cookie_manager() cm.set(COOKIE_NAME, token, expires_at=expiry, key="cookie_set_login") except Exception: pass return True return False def do_logout(): try: cm = get_cookie_manager() cm.delete(COOKIE_NAME, key="cookie_del_logout") except Exception: pass st.session_state.authenticated = False st.session_state.current_page = "Overview" st.rerun() def api_get(endpoint: str) -> tuple[bool, dict]: try: r = requests.get( f"{RAG_API_URL}{endpoint}", headers={"Authorization": f"Bearer {RAG_API_KEY}"}, timeout=15, ) return r.status_code == 200, r.json() except Exception as e: return False, {"error": str(e)} def api_post(endpoint: str, json_data: dict = None, files=None, data=None) -> tuple[bool, dict]: try: headers = {"Authorization": f"Bearer {RAG_API_KEY}"} if json_data is not None: headers["Content-Type"] = "application/json" r = requests.post(f"{RAG_API_URL}{endpoint}", headers=headers, json=json_data, timeout=120) else: r = requests.post(f"{RAG_API_URL}{endpoint}", headers=headers, files=files, data=data, timeout=120) return r.status_code == 200, r.json() except Exception as e: return False, {"error": str(e)} def api_delete(endpoint: str) -> tuple[bool, dict]: try: r = requests.delete( f"{RAG_API_URL}{endpoint}", headers={"Authorization": f"Bearer {RAG_API_KEY}"}, timeout=15, ) return r.status_code == 200, r.json() except Exception as e: return False, {"error": str(e)} def get_health() -> dict: try: r = requests.get(f"{RAG_API_URL}/health", timeout=10) return r.json() if r.status_code == 200 else {} except Exception: return {} def render_login(): col1, col2, col3 = st.columns([1, 1.2, 1]) with col2: st.markdown(f"""
{LUCIDE_ICONS['lock']}
NEXUS RAG
Restricted Access — Authenticate to Continue
""", unsafe_allow_html=True) if st.session_state.get("login_error"): st.markdown(f'
{st.session_state.login_error}
', unsafe_allow_html=True) with st.form("login_form", clear_on_submit=False): username = st.text_input("Username", placeholder="Enter username") password = st.text_input("Password", type="password", placeholder="Enter password") submitted = st.form_submit_button("Sign In", use_container_width=True) if submitted: if not username or not password: st.session_state.login_error = "Username and password are required." st.rerun() else: success = do_login(username, password) if success: st.session_state.login_error = "" st.session_state.authenticated = True st.rerun() else: st.session_state.login_error = "Invalid credentials. Access denied." st.rerun() st.markdown("""
NEXUS RAG ENGINE — SECURED
""", unsafe_allow_html=True) def render_sidebar(health: dict) -> str: with st.sidebar: status = health.get("status", "offline") status_color = "#10b981" if status == "healthy" else "#f59e0b" if status == "degraded" else "#e94560" status_label = status.upper() if status else "OFFLINE" st.markdown(f"""
NEXUS RAG
Engine Dashboard
{status_label}
""", unsafe_allow_html=True) if health: uptime = health.get("uptime_seconds", 0) hours = int(uptime // 3600) minutes = int((uptime % 3600) // 60) qdrant_ok = health.get("qdrant_connection", "error") == "ok" cache_n = health.get("cache_entries", 0) st.markdown(f""" """, unsafe_allow_html=True) pages = ["Overview", "Upload", "Test Search", "Documents", "Analytics"] for label in pages: is_active = st.session_state.get("current_page", "Overview") == label if st.button( label, key=f"nav_{label}", use_container_width=True, type="primary" if is_active else "secondary", ): st.session_state.current_page = label st.rerun() st.markdown("
", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) st.markdown( f"
" f"Signed in as {APP_USERNAME}
", unsafe_allow_html=True, ) st.markdown('
', unsafe_allow_html=True) if st.button("Sign Out", key="logout_btn", use_container_width=True): do_logout() st.markdown("
", unsafe_allow_html=True) return st.session_state.get("current_page", "Overview") def page_overview(stats: dict, health: dict): st.markdown('
Overview
', unsafe_allow_html=True) st.markdown('
System status and key metrics at a glance.
', unsafe_allow_html=True) storage = stats.get("storage", {}) documents = stats.get("documents", {}) queries = stats.get("queries", {}) c1, c2, c3, c4 = st.columns(4) for col, val, label, sub in [ (c1, documents.get("total_parents", 0), "Parent Chunks", "Indexed parent segments"), (c2, documents.get("total_children", 0), "Child Chunks", "Searchable vectors"), (c3, queries.get("today", 0), "Queries Today", "RAG calls this session"), (c4, queries.get("cache_size", 0), "Cache Entries", "In-memory cached results"), ]: with col: st.markdown(f"""
{val:,}
{label}
{sub}
""", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) col_l, col_r = st.columns(2) with col_l: used = storage.get("used_mb", 0) total = storage.get("total_mb", 1024) pct = storage.get("percentage", 0) bar_color = COLOR_SUCCESS if pct < 60 else COLOR_WARNING if pct < 85 else COLOR_PRIMARY fig_gauge = go.Figure(go.Indicator( mode="gauge+number", value=pct, number={"suffix": "%", "font": {"color": COLOR_PRIMARY, "size": 32}}, title={"text": f"Storage {used:.1f} MB / {total} MB", "font": {"color": "#6b7280", "size": 13}}, gauge={ "axis": {"range": [0, 100], "tickcolor": "#1e1e2e", "tickfont": {"color": "#4a4a6a"}}, "bar": {"color": bar_color}, "bgcolor": "#0a0a0f", "borderwidth": 0, "steps": [ {"range": [0, 60], "color": "#0d0d18"}, {"range": [60, 85], "color": "#111122"}, {"range": [85, 100], "color": "#150a0a"}, ], "threshold": {"line": {"color": COLOR_PRIMARY, "width": 2}, "thickness": 0.75, "value": 90}, }, )) fig_gauge.update_layout(**PLOTLY_LAYOUT, height=260) st.plotly_chart(fig_gauge, use_container_width=True) with col_r: top_queries = stats.get("top_queries", []) st.markdown('
Top Queries
', unsafe_allow_html=True) if top_queries: max_count = top_queries[0].get("count", 1) for q in top_queries[:6]: qtext = q.get("query", "")[:45] count = q.get("count", 0) pct_bar = int((count / max_count) * 100) st.markdown(f"""
{qtext} {count}x
""", unsafe_allow_html=True) else: st.markdown('
No query logs yet.
', unsafe_allow_html=True) rate_limits = stats.get("rate_limits", {}) if rate_limits: st.markdown('
Rate Limit — Current Hour
', unsafe_allow_html=True) rl_cols = st.columns(min(len(rate_limits), 4)) for i, (key, count) in enumerate(rate_limits.items()): pct_rl = (count / 3000) * 100 rl_color = COLOR_SUCCESS if pct_rl < 70 else COLOR_WARNING if pct_rl < 90 else COLOR_PRIMARY with rl_cols[i % 4]: st.markdown(f"""
{key}
{count}/3000
""", unsafe_allow_html=True) def page_upload(): st.markdown('
Upload Documents
', unsafe_allow_html=True) st.markdown('
Add .md or .txt files to the Nexus RAG knowledge base.
', unsafe_allow_html=True) ok, collections_data = api_get("/collections") existing_collections = collections_data.get("collections", []) if ok else [] with st.form("upload_form", clear_on_submit=True): uploaded_files = st.file_uploader("Select files", type=["md", "txt"], accept_multiple_files=True, help="Max 50MB per file") c1, c2 = st.columns(2) with c1: col_mode = st.selectbox("Collection mode", ["Use existing", "Create new"]) with c2: if col_mode == "Use existing" and existing_collections: collection_name = st.selectbox("Collection", existing_collections) else: collection_name = st.text_input("Collection name", placeholder="e.g. devops, tutorials") metadata_raw = st.text_area("Metadata JSON (optional)", placeholder='{"category": "devops"}', height=70) submitted = st.form_submit_button("Upload", use_container_width=True) if submitted: if not uploaded_files: st.markdown('
Select at least one file.
', unsafe_allow_html=True) return if not collection_name or not collection_name.strip(): st.markdown('
Collection name is required.
', unsafe_allow_html=True) return meta_dict = {} if metadata_raw.strip(): try: meta_dict = json.loads(metadata_raw.strip()) except json.JSONDecodeError: st.markdown('
Invalid JSON in metadata.
', unsafe_allow_html=True) return progress = st.progress(0, text="Preparing...") results_box = st.container() success_n, fail_n = 0, 0 for i, f in enumerate(uploaded_files): progress.progress(i / len(uploaded_files), text=f"Uploading {f.name}...") raw = f.read() size_mb = len(raw) / (1024 * 1024) if size_mb > 50: with results_box: st.markdown(f'
{f.name} exceeds 50MB ({size_mb:.1f}MB)
', unsafe_allow_html=True) fail_n += 1 continue ok, resp = api_post("/upload", files={"file": (f.name, raw, "text/plain")}, data={"collection": collection_name.strip(), "metadata": json.dumps(meta_dict)}) with results_box: if ok and resp.get("success"): st.markdown(f'
{f.name} — {resp.get("parents_created",0)} parents, {resp.get("children_created",0)} children — {size_mb:.2f}MB — {resp.get("processing_time_ms",0):.0f}ms
', unsafe_allow_html=True) success_n += 1 else: st.markdown(f'
{f.name} — {resp.get("error") or resp.get("detail","Unknown error")}
', unsafe_allow_html=True) fail_n += 1 progress.progress(1.0, text="Done") st.markdown("
", unsafe_allow_html=True) c1, c2, c3 = st.columns(3) c1.metric("Uploaded", success_n) c2.metric("Failed", fail_n) c3.metric("Collection", collection_name) def page_test_search(): st.markdown('
Test Search
', unsafe_allow_html=True) st.markdown('
Run live queries against the Nexus RAG engine.
', unsafe_allow_html=True) ok, collections_data = api_get("/collections") collections = ["all"] + (collections_data.get("collections", []) if ok else []) with st.form("search_form"): query_text = st.text_area("Query", placeholder="Type your query here...", height=90) c1, c2, c3 = st.columns(3) with c1: selected_col = st.selectbox("Collection", collections) with c2: top_k = st.slider("Top K", 1, 20, 5) with c3: use_rerank = st.checkbox("Reranking", value=True) submitted = st.form_submit_button("Search", use_container_width=True) if submitted: if not query_text.strip(): st.markdown('
Enter a query.
', unsafe_allow_html=True) return with st.spinner("Processing..."): t0 = time.time() ok, resp = api_post("/query", json_data={"query": query_text.strip(), "collection": selected_col, "top_k": top_k, "use_reranking": use_rerank}) elapsed = (time.time() - t0) * 1000 if not ok: st.markdown(f'
Query failed: {resp.get("error","Unknown")}
', unsafe_allow_html=True) return c1, c2, c3, c4 = st.columns(4) c1.metric("Latency", f"{resp.get('processing_time_ms', elapsed):.0f}ms") c2.metric("Sources", len(resp.get("sources", []))) c3.metric("Cached", "Yes" if resp.get("cached") else "No") c4.metric("Reranked", "Yes" if use_rerank else "No") st.markdown("
", unsafe_allow_html=True) tab_ctx, tab_src = st.tabs(["Context Output", "Sources Detail"]) with tab_ctx: context = resp.get("context", "") if context: st.markdown(f'
Context length: {len(context):,} chars
', unsafe_allow_html=True) st.text_area("", value=context, height=400, label_visibility="collapsed") else: st.markdown('
No context returned.
', unsafe_allow_html=True) with tab_src: sources = resp.get("sources", []) if sources: for i, src in enumerate(sources, 1): score = src.get("score", 0) score_cls = "score-high" if score > 0.7 else "score-med" if score > 0.5 else "score-low" preview = src.get("text", "")[:300] dots = "..." if len(src.get("text", "")) > 300 else "" st.markdown(f"""
Result #{i} {score:.4f}
{preview}{dots}
{src.get('filename','unknown')} · {src.get('collection','unknown')} · {src.get('doc_id','')[:12]}...
""", unsafe_allow_html=True) else: st.markdown('
No sources found.
', unsafe_allow_html=True) def page_documents(): st.markdown('
Documents
', unsafe_allow_html=True) st.markdown('
Manage all indexed documents in the knowledge base.
', unsafe_allow_html=True) ok, collections_data = api_get("/collections") collections = ["all"] + (collections_data.get("collections", []) if ok else []) c1, c2 = st.columns([3, 1]) with c1: filter_col = st.selectbox("Filter by collection", collections, key="doc_filter_col") with c2: st.markdown("
", unsafe_allow_html=True) if st.button("Refresh", use_container_width=True): st.rerun() col_param = "" if filter_col == "all" else f"&collection={filter_col}" ok, docs_data = api_get(f"/documents?limit=200{col_param}") if not ok: st.markdown('
Failed to fetch documents.
', unsafe_allow_html=True) return documents = docs_data.get("documents", []) total = docs_data.get("total", 0) st.markdown(f'
{total} document(s) found
', unsafe_allow_html=True) if not documents: st.markdown('
No documents indexed yet.
', unsafe_allow_html=True) return if "del_confirm" not in st.session_state: st.session_state.del_confirm = {} st.markdown('
', unsafe_allow_html=True) st.markdown("""
Filename Collection Doc ID Parents Action
""", unsafe_allow_html=True) for doc in documents: doc_id = doc.get("doc_id", "") filename = doc.get("filename", "unknown") collection = doc.get("collection", "general") parent_count = doc.get("parent_count", 0) ext = "MD" if filename.endswith(".md") else "TXT" badge_color = "badge-blue" if ext == "MD" else "badge-gray" c1, c2, c3, c4, c5 = st.columns([3, 2, 2, 1, 1]) with c1: st.markdown(f'{ext} {filename}', unsafe_allow_html=True) with c2: st.markdown(f'{collection}', unsafe_allow_html=True) with c3: st.markdown(f'{doc_id[:16]}...', unsafe_allow_html=True) with c4: st.markdown(f'
{parent_count}
', unsafe_allow_html=True) with c5: if st.session_state.del_confirm.get(doc_id): if st.button("Confirm", key=f"confirm_{doc_id}", use_container_width=True): ok, _ = api_delete(f"/delete/{doc_id}") if ok: st.session_state.del_confirm[doc_id] = False st.rerun() else: st.markdown('
Delete failed.
', unsafe_allow_html=True) st.session_state.del_confirm[doc_id] = False else: if st.button("Delete", key=f"del_{doc_id}", use_container_width=True): st.session_state.del_confirm[doc_id] = True st.rerun() st.markdown("
", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) def page_analytics(stats: dict): st.markdown('
Analytics
', unsafe_allow_html=True) st.markdown('
Performance insights, usage trends, and system health reports.
', unsafe_allow_html=True) queries_data = stats.get("queries", {}) top_queries = stats.get("top_queries", []) storage = stats.get("storage", {}) documents = stats.get("documents", {}) c1, c2, c3, c4, c5 = st.columns(5) for col, val, label in [ (c1, queries_data.get("total_logged", 0), "Total Logged"), (c2, queries_data.get("today", 0), "Today"), (c3, queries_data.get("cache_size", 0), "Cache Size"), (c4, f"{storage.get('used_mb', 0):.1f}", "Storage MB"), (c5, f"{storage.get('percentage', 0):.1f}%", "Usage"), ]: with col: st.markdown(f"""
{val}
{label}
""", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) row1_l, row1_r = st.columns(2) with row1_l: st.markdown('
Top Queries — Frequency
', unsafe_allow_html=True) if top_queries: df_q = pd.DataFrame(top_queries) df_q.columns = ["Query", "Count"] df_q["Query"] = df_q["Query"].str[:35] fig = px.bar(df_q.head(10), x="Count", y="Query", orientation="h", color="Count", color_continuous_scale=["#1e1e2e", COLOR_PRIMARY]) fig.update_layout(**PLOTLY_LAYOUT, height=320, coloraxis_showscale=False) fig.update_traces(marker_line_width=0) st.plotly_chart(fig, use_container_width=True) else: st.markdown('
No query data yet.
', unsafe_allow_html=True) with row1_r: st.markdown('
Storage Distribution
', unsafe_allow_html=True) child_count = documents.get("total_children", 0) parent_count = documents.get("total_parents", 0) used_mb = storage.get("used_mb", 0) free_mb = max(storage.get("total_mb", 1024) - used_mb, 0) if child_count + parent_count > 0: fig_pie = go.Figure(go.Pie( labels=["Child Vectors", "Parent Texts", "Free"], values=[child_count * 3, parent_count * 1, max(free_mb, 1)], hole=0.55, marker=dict(colors=[COLOR_PRIMARY, COLOR_SECONDARY, "#1e1e2e"]), textfont=dict(color="#6b7280", size=11), )) fig_pie.update_layout(**PLOTLY_LAYOUT, height=320) fig_pie.update_traces(textposition="outside") st.plotly_chart(fig_pie, use_container_width=True) else: st.markdown('
No storage data yet.
', unsafe_allow_html=True) row2_l, row2_r = st.columns(2) with row2_l: st.markdown('
Chunk Ratio Analysis
', unsafe_allow_html=True) if parent_count > 0: ratio = child_count / parent_count fig_bar = go.Figure() fig_bar.add_trace(go.Bar( x=["Parent Chunks", "Child Chunks"], y=[parent_count, child_count], marker_color=[COLOR_SECONDARY, COLOR_PRIMARY], marker_line_width=0, text=[f"{parent_count:,}", f"{child_count:,}"], textposition="outside", textfont=dict(color="#6b7280", size=11), )) fig_bar.update_layout(**PLOTLY_LAYOUT, height=280, showlegend=False) st.plotly_chart(fig_bar, use_container_width=True) st.markdown(f"""
Avg children per parent: {ratio:.1f}x
""", unsafe_allow_html=True) else: st.markdown('
No chunk data yet.
', unsafe_allow_html=True) with row2_r: st.markdown('
Cache vs Live Queries
', unsafe_allow_html=True) total_logged = queries_data.get("total_logged", 0) cache_size = queries_data.get("cache_size", 0) live_queries = max(total_logged - cache_size, 0) if total_logged > 0: fig_donut = go.Figure(go.Pie( labels=["Live (Computed)", "Cached (Fast)"], values=[live_queries, cache_size], hole=0.6, marker=dict(colors=[COLOR_PRIMARY, COLOR_SUCCESS]), textfont=dict(color="#6b7280", size=11), )) fig_donut.update_layout(**PLOTLY_LAYOUT, height=280) fig_donut.update_traces(textposition="outside") st.plotly_chart(fig_donut, use_container_width=True) hit_rate = (cache_size / total_logged * 100) if total_logged > 0 else 0 st.markdown(f"""
Cache hit rate: {hit_rate:.1f}%
""", unsafe_allow_html=True) else: st.markdown('
No query data yet.
', unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) st.markdown('
Storage Utilization
', unsafe_allow_html=True) pct = storage.get("percentage", 0) fig_bullet = go.Figure(go.Indicator( mode="number+gauge+delta", value=pct, delta={"reference": 70, "increasing": {"color": COLOR_PRIMARY}, "decreasing": {"color": COLOR_SUCCESS}}, number={"suffix": "%", "font": {"color": COLOR_PRIMARY, "size": 28}}, gauge={ "shape": "bullet", "axis": {"range": [0, 100], "tickfont": {"color": "#4a4a6a"}}, "threshold": {"line": {"color": COLOR_PRIMARY, "width": 2}, "thickness": 0.75, "value": 90}, "bgcolor": "#0a0a0f", "steps": [ {"range": [0, 60], "color": "#0d1117"}, {"range": [60, 80], "color": "#111827"}, {"range": [80, 100], "color": "#1a0a0a"}, ], "bar": {"color": COLOR_PRIMARY}, }, title={"text": f"Used {storage.get('used_mb',0):.1f} MB of {storage.get('total_mb',1024)} MB", "font": {"color": "#4a4a6a", "size": 12}}, )) fig_bullet.update_layout(**PLOTLY_LAYOUT, height=160) st.plotly_chart(fig_bullet, use_container_width=True) st.markdown("
", unsafe_allow_html=True) st.markdown('
Engine Configuration
', unsafe_allow_html=True) config_items = [ ("Embedding Model", "LazarusNLP/all-indobert-base-v2"), ("Reranking Model", "cross-encoder/ms-marco-MiniLM-L-6-v2"), ("Vector Dimension", "768"), ("Parent Chunk", "1500 chars"), ("Child Chunk", "500 chars"), ("Child Overlap", "50 chars"), ("Rerank Candidates", "20"), ("Cache TTL", "3600s"), ("Rate Limit", "3000 req/hr"), ("Max File Size", "50 MB"), ] cfg_c1, cfg_c2 = st.columns(2) for i, (key, val) in enumerate(config_items): col = cfg_c1 if i % 2 == 0 else cfg_c2 with col: st.markdown(f"""
{key} {val}
""", unsafe_allow_html=True) def main(): if not RAG_API_URL or not RAG_API_KEY: st.markdown('
RAG_API_URL and RAG_API_KEY secrets not configured.
', unsafe_allow_html=True) return if not APP_PASSWORD: st.markdown('
PASSWORD secret not configured.
', unsafe_allow_html=True) return get_cookie_manager() if "authenticated" not in st.session_state: st.session_state.authenticated = False if "login_error" not in st.session_state: st.session_state.login_error = "" if "current_page" not in st.session_state: st.session_state.current_page = "Overview" if "del_confirm" not in st.session_state: st.session_state.del_confirm = {} if not st.session_state.authenticated: if check_auth(): st.session_state.authenticated = True else: render_login() return health = get_health() ok_stats, stats = api_get("/stats") stats = stats if ok_stats else {} page = render_sidebar(health) if page == "Overview": page_overview(stats, health) elif page == "Upload": page_upload() elif page == "Test Search": page_test_search() elif page == "Documents": page_documents() elif page == "Analytics": page_analytics(stats) if __name__ == "__main__": main()