Sumayyea's picture
Upload 7 files
269c0ee verified
Raw History Blame Contribute Delete
5.87 kB
"""
app.py — Streamlit interface for the Digital KP 2030 assistant.
Run locally: streamlit run app.py
On Hugging Face Spaces this file is detected and launched automatically.
"""
from __future__ import annotations
import streamlit as st
from rag import Assistant, EXAMPLE_QUESTIONS, LLM_MODEL
st.set_page_config(
page_title="Digital KP 2030 Assistant",
page_icon="📘",
layout="centered",
initial_sidebar_state="expanded",
)
# --------------------------------------------------------------------------
# Styling — a restrained navy palette matching the source document
# --------------------------------------------------------------------------
st.markdown("""
<style>
.block-container { padding-top: 2.2rem; max-width: 52rem; }
.kp-header {
background: #0B2545; border-radius: 10px;
padding: 1.4rem 1.6rem; margin-bottom: 1.2rem;
}
.kp-header h1 { color: #fff; font-size: 1.55rem; margin: 0 0 .3rem 0; }
.kp-header p { color: #8AB6E8; font-size: .92rem; margin: 0; }
.kp-badge {
display: inline-block; background: #EAF1FA; color: #1B5299;
border: 1px solid #D3E1F2; border-radius: 999px;
padding: .12rem .6rem; font-size: .74rem; margin-right: .3rem;
}
.kp-src {
background: #F5F9FD; border: 1px solid #D3E1F2; border-radius: 8px;
padding: .7rem .9rem; margin-bottom: .55rem; font-size: .86rem;
}
.kp-src b { color: #1B5299; }
</style>
""", unsafe_allow_html=True)
st.markdown("""
<div class="kp-header">
<h1>Digital KP 2030 Assistant</h1>
<p>Answers drawn only from the Khyber Pakhtunkhwa Digital Transformation
Policy &amp; Roadmap 2030, with page citations.</p>
</div>
""", unsafe_allow_html=True)
# --------------------------------------------------------------------------
# Load the index once per session
# --------------------------------------------------------------------------
@st.cache_resource(show_spinner="Loading the document index…")
def load_assistant() -> Assistant:
return Assistant()
try:
assistant = load_assistant()
except FileNotFoundError as exc:
st.error(str(exc))
st.stop()
except Exception as exc: # noqa: BLE001
st.error(f"Could not start the assistant: {type(exc).__name__}: {exc}")
st.stop()
# --------------------------------------------------------------------------
# Sidebar
# --------------------------------------------------------------------------
with st.sidebar:
st.subheader("About")
st.write(
"This assistant retrieves passages from the published Digital KP 2030 "
"document and answers strictly from them. If something is not in the "
"document, it says so rather than guessing."
)
meta = assistant.meta
st.markdown(
f'<span class="kp-badge">{meta["pages"]} pages</span>'
f'<span class="kp-badge">{meta["chunks"]} chunks</span>'
f'<span class="kp-badge">{LLM_MODEL.split("/")[-1]}</span>',
unsafe_allow_html=True,
)
st.divider()
st.subheader("Try a question")
for q in EXAMPLE_QUESTIONS[:6]:
if st.button(q, key=f"ex-{q}", use_container_width=True):
st.session_state.pending = q
st.divider()
if st.button("Clear conversation", use_container_width=True):
st.session_state.messages = []
st.rerun()
st.caption(
"Source: Khyber Pakhtunkhwa Digital Transformation Board. "
"Verify anything consequential against the published PDF."
)
# --------------------------------------------------------------------------
# Chat state
# --------------------------------------------------------------------------
if "messages" not in st.session_state:
st.session_state.messages = []
for msg in st.session_state.messages:
with st.chat_message(msg["role"]):
st.markdown(msg["content"])
if msg.get("sources"):
with st.expander(f"Sources — {len(msg['sources'])} passage(s)"):
for s in msg["sources"]:
preview = s["text"][:420] + ("…" if len(s["text"]) > 420 else "")
st.markdown(
f'<div class="kp-src"><b>Page {s["page"]}</b> '
f'· relevance {s["score"]:.2f}<br>{preview}</div>',
unsafe_allow_html=True,
)
typed = st.chat_input("Ask about goals, pillars, KPIs, governance or timelines…")
question = typed or st.session_state.pop("pending", None)
if question:
st.session_state.messages.append({"role": "user", "content": question})
with st.chat_message("user"):
st.markdown(question)
with st.chat_message("assistant"):
with st.spinner("Searching the document…"):
history = [
{"role": m["role"], "content": m["content"]}
for m in st.session_state.messages[:-1]
]
result = assistant.answer(question, history=history)
st.markdown(result["answer"])
if result["error"] and result["error"] != "missing_key":
st.warning(f"Model error: {result['error']}")
sources = [
{"page": s.page, "text": s.text, "score": s.score}
for s in result["sources"]
]
if sources:
with st.expander(f"Sources — {len(sources)} passage(s)"):
for s in sources:
preview = s["text"][:420] + ("…" if len(s["text"]) > 420 else "")
st.markdown(
f'<div class="kp-src"><b>Page {s["page"]}</b> '
f'· relevance {s["score"]:.2f}<br>{preview}</div>',
unsafe_allow_html=True,
)
st.session_state.messages.append({
"role": "assistant",
"content": result["answer"],
"sources": sources,
})