""" frontend/app.py --------------- Streamlit UI for the Nepali Document RAG system. Talks to the FastAPI backend at BACKEND_URL. """ import os import json import requests import streamlit as st # ── Config ──────────────────────────────────────────────────────────────────── BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:8080") st.set_page_config( page_title="Nepali Document Search", page_icon="🔍", layout="centered", initial_sidebar_state="collapsed", ) # ── CSS ───────────────────────────────────────────────────── st.markdown( """ """, unsafe_allow_html=True, ) # ── Session State ────────────────────────────────────────────────────────────── if "messages" not in st.session_state: st.session_state.messages = [] # ── Sidebar ─────────────────────────────────────────────────────────────────── with st.sidebar: st.markdown("
", unsafe_allow_html=True) st.subheader("Configuration") st.markdown("
", unsafe_allow_html=True) top_k_retrieval = st.number_input( "Documents to retrieve", min_value=5, max_value=50, value=20, step=5, help="Number of candidate documents fetched from the vector store.", key="config_top_k_retrieval", ) top_k_context = st.number_input( "Documents to use in context", min_value=1, max_value=10, value=5, help="Top-ranked documents passed to the language model.", key="config_top_k_context", ) st.markdown("
", unsafe_allow_html=True) use_streaming = st.checkbox("Stream response", value=True, key="config_streaming") st.markdown("---") st.markdown( "

Nepali Document RAG by Anup Aryal

", unsafe_allow_html=True, ) # ── Page Header ─────────────────────────────────────────────────────────────── st.markdown("
", unsafe_allow_html=True) st.markdown( """
Retrieval-Augmented Generation

Nepali Document Search

Ask questions about your documents in Nepali or English

""", unsafe_allow_html=True, ) # ── Conversation History ────────────────────────────────────────────────────── for msg in st.session_state.messages: with st.chat_message(msg["role"]): if msg["role"] == "user": st.markdown(msg["content"]) else: st.markdown( f'
{msg["content"]}
', unsafe_allow_html=True, ) if msg.get("metrics"): m = msg["metrics"] st.caption( f"⏱ Retrieval {m['retrieval']} ms · Generation {m['generation']} ms · Total {m['total']} ms" ) if msg.get("sources"): with st.expander( f"📄 {len(msg['sources'])} source(s) referenced", expanded=False ): for i, src in enumerate(msg["sources"]): st.markdown( f"""
Document {i + 1}  ·  {src['id']} score {src['relevance']:.4f}
{src['text']}
""", unsafe_allow_html=True, ) # ── Chat Input ──────────────────────────────────────────────────────────────── if prompt := st.chat_input("Ask a question about your documents…"): with st.chat_message("user"): st.markdown(prompt) st.session_state.messages.append({"role": "user", "content": prompt}) payload = { "query": prompt.strip(), "top_k_retrieval": st.session_state.config_top_k_retrieval, "top_k_context": st.session_state.config_top_k_context, } with st.chat_message("assistant"): answer_placeholder = st.empty() metrics_placeholder = st.empty() sources_placeholder = st.empty() full_answer = "" sources = [] metrics_data = None if st.session_state.config_streaming: # ── Streaming mode ── try: with requests.post( f"{BACKEND_URL}/query/stream", json=payload, stream=True, timeout=120, ) as resp: if resp.status_code != 200: st.error(f"Backend error {resp.status_code}: {resp.text}") else: for line in resp.iter_lines(): if not line: continue text = line.decode("utf-8") if not text.startswith("data: "): continue content = text[6:] if content == "[DONE]": break elif content.startswith("[ERROR]"): st.error(f"Error: {content[7:]}") break elif content.startswith("[SOURCES]"): try: sources = json.loads(content[9:]) except ValueError: pass else: decoded_content = content.replace("\\n", "\n") full_answer += decoded_content answer_placeholder.markdown( f'
{full_answer}▌
', unsafe_allow_html=True, ) answer_placeholder.markdown( f'
{full_answer}
', unsafe_allow_html=True, ) except requests.exceptions.ConnectionError: st.error( "Unable to reach the backend service. Please check that it is running." ) else: # ── Non-streaming mode ── with st.spinner("Searching and generating response…"): try: resp = requests.post( f"{BACKEND_URL}/query", json=payload, timeout=120 ) if resp.status_code != 200: st.error( f"Backend error {resp.status_code}: {resp.json().get('detail', resp.text)}" ) else: data = resp.json() full_answer = data["answer"] sources = data.get("sources", []) metrics_data = { "retrieval": data["retrieval_time_ms"], "generation": data["generation_time_ms"], "total": data["total_time_ms"], } answer_placeholder.markdown( f'
{full_answer}
', unsafe_allow_html=True, ) except requests.exceptions.ConnectionError: st.error( "Unable to reach the backend service. Please check that it is running." ) if metrics_data: metrics_placeholder.caption( f"⏱ Retrieval {metrics_data['retrieval']} ms · Generation {metrics_data['generation']} ms · Total {metrics_data['total']} ms" ) if sources: with sources_placeholder.expander( f"📄 {len(sources)} source(s) referenced", expanded=False ): for i, src in enumerate(sources): st.markdown( f"""
Document {i + 1}  ·  {src['id']} score {src['relevance']:.4f}
{src['text']}
""", unsafe_allow_html=True, ) st.session_state.messages.append( { "role": "assistant", "content": full_answer, "sources": sources, "metrics": metrics_data, } )