import uuid import os import sys import tempfile import streamlit as st from langchain_core.messages import HumanMessage, AIMessage, ToolMessage sys.path.insert(0, os.path.dirname(__file__)) st.set_page_config( page_title="Research Assistant", page_icon="๐Ÿ”ฌ", layout="wide", ) if "thread_id" not in st.session_state: st.session_state.thread_id = str(uuid.uuid4()) if "messages" not in st.session_state: st.session_state.messages = [] if "threads" not in st.session_state: st.session_state.threads = [st.session_state.thread_id] if "titles" not in st.session_state: st.session_state.titles = {} if "last_meta" not in st.session_state: st.session_state.last_meta = {} if "docs_ingested" not in st.session_state: st.session_state.docs_ingested = [] @st.cache_resource(show_spinner="Loading agent...") def load_agent(): from src.agents.graph import get_graph return get_graph() with st.sidebar: st.title("๐Ÿ”ฌ Research Assistant") st.caption("LangGraph ยท RAG ยท Multi-Agent") st.divider() st.subheader("๐Ÿ“š Upload Documents") uploaded = st.file_uploader( "Upload PDFs", type=["pdf"], accept_multiple_files=True, ) if st.button("๐Ÿ“ฅ Index Documents", type="primary"): if uploaded: from src.agents.graph import ingest_pdf bar = st.progress(0) for i, pdf in enumerate(uploaded): meta = ingest_pdf( pdf.getvalue(), thread_id=st.session_state.thread_id, filename=pdf.name, ) st.session_state.docs_ingested.append(pdf.name) bar.progress((i + 1) / len(uploaded)) st.success(f"โœ… Indexed {len(uploaded)} document(s)") else: st.warning("Please upload PDFs first.") if st.session_state.docs_ingested: st.caption(f"{len(st.session_state.docs_ingested)} doc(s) indexed") for name in st.session_state.docs_ingested[-5:]: st.caption(f" ๐Ÿ“„ {name}") st.divider() st.subheader("๐Ÿ’ฌ Chats") if st.button("โž• New Chat"): new_id = str(uuid.uuid4()) st.session_state.thread_id = new_id st.session_state.messages = [] st.session_state.threads.insert(0, new_id) st.rerun() for tid in st.session_state.threads[:10]: title = st.session_state.titles.get(tid, f"Chat {tid[:6]}") active = "โ–ถ " if tid == st.session_state.thread_id else " " if st.button(f"{active}{title}", key=f"t_{tid}", use_container_width=True): st.session_state.thread_id = tid try: graph = load_agent() state = graph.get_state({"configurable": {"thread_id": tid}}) st.session_state.messages = [ { "role": "user" if isinstance(m, HumanMessage) else "assistant", "content": m.content, } for m in state.values.get("messages", []) if isinstance(m, (HumanMessage, AIMessage)) and m.content ] except Exception: st.session_state.messages = [] st.rerun() st.title("๐Ÿ”ฌ Research Assistant") if st.session_state.last_meta: m = st.session_state.last_meta model = m.get("model_used", "") label = "โšก Fast (8B)" if "8b" in model else "๐Ÿง  Smart (70B)" qtype = m.get("query_type", "?") st.caption(f"{label} ยท {qtype} ยท {m.get('latency_ms', 0):.0f}ms") for msg in st.session_state.messages: with st.chat_message(msg["role"]): st.markdown(msg["content"]) user_input = st.chat_input("Ask anything about your documents...") if user_input: if st.session_state.thread_id not in st.session_state.titles: st.session_state.titles[st.session_state.thread_id] = user_input[:28] st.session_state.messages.append({"role": "user", "content": user_input}) with st.chat_message("user"): st.markdown(user_input) graph = load_agent() config = {"configurable": {"thread_id": st.session_state.thread_id}} with st.chat_message("assistant"): tool_box = [None] def stream(): for chunk, _ in graph.stream( {"messages": [HumanMessage(content=user_input)]}, config=config, stream_mode="messages", ): if isinstance(chunk, ToolMessage) and tool_box[0] is None: tool_box[0] = st.status("๐Ÿ”ง Using tools...", expanded=True) if isinstance(chunk, AIMessage) and chunk.content: content = chunk.content for prefix in ["simple", "complex", "calc"]: if content.lower().startswith(prefix): content = content[len(prefix):] yield content answer = st.write_stream(stream()) if tool_box[0]: tool_box[0].update(label="โœ… Done", state="complete", expanded=False) try: state = graph.get_state(config) st.session_state.last_meta = { "query_type": state.values.get("query_type"), "model_used": state.values.get("model_used"), "latency_ms": state.values.get("latency_ms"), } except Exception: pass st.session_state.messages.append({"role": "assistant", "content": answer}) st.rerun()