| import streamlit as st |
| from PyPDF2 import PdfReader |
| from langchain_text_splitters import RecursiveCharacterTextSplitter |
| from langchain_huggingface import HuggingFaceEmbeddings, HuggingFacePipeline |
| from langchain_community.vectorstores import FAISS |
| from langchain_core.prompts import PromptTemplate |
| from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline |
| import torch |
| import time |
| import base64 |
| import html as html_module |
| from datetime import datetime, timezone, timedelta |
|
|
| |
| IST = timezone(timedelta(hours=5, minutes=30)) |
| def get_ist_time(): |
| return datetime.now(IST).strftime("%H:%M") |
|
|
| st.set_page_config( |
| page_title="QueryDocs AI", |
| page_icon="📚", |
| layout="wide", |
| initial_sidebar_state="expanded" |
| ) |
|
|
| |
| def img_to_base64(path): |
| try: |
| with open(path, "rb") as f: |
| return base64.b64encode(f.read()).decode() |
| except: |
| return None |
|
|
| |
| st.markdown(""" |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;600;700&family=Orbitron:wght@700;900&display=swap'); |
| |
| /* ── Base ── */ |
| html, body, [data-testid="stAppViewContainer"] { |
| background: #0d1117 !important; |
| color: #c9d1d9 !important; |
| font-family: 'Rajdhani', sans-serif !important; |
| } |
| [data-testid="stSidebar"] { |
| background: #161b22 !important; |
| border-right: 1px solid #30363d !important; |
| } |
| [data-testid="stSidebar"] * { color: #c9d1d9 !important; } |
| footer, #MainMenu { visibility: hidden; } |
| |
| /* ── Header ── */ |
| .app-header { |
| display: flex; align-items: center; gap: 14px; |
| padding: 16px 0 14px; |
| border-bottom: 1px solid #21262d; |
| margin-bottom: 20px; |
| animation: fadeInDown 0.5s ease both; |
| } |
| .app-title { |
| font-family: 'Orbitron', monospace; |
| font-size: 1.6rem; font-weight: 900; |
| color: #fff; letter-spacing: 2px; |
| } |
| .app-title span { color: #58a6ff; } |
| .app-sub { |
| font-family: 'Share Tech Mono', monospace; |
| font-size: 0.68rem; color: #8b949e; |
| letter-spacing: 3px; margin-top: 3px; |
| } |
| |
| /* ── Profile card in sidebar ── */ |
| .profile-card { |
| text-align: center; |
| padding: 20px 0 16px; |
| border-bottom: 1px solid #21262d; |
| margin-bottom: 16px; |
| animation: fadeInDown 0.5s ease both; |
| } |
| .profile-avatar { |
| width: 72px; height: 72px; |
| border-radius: 50%; overflow: hidden; |
| border: 2px solid #58a6ff; |
| box-shadow: 0 0 0 3px rgba(88,166,255,0.15), 0 0 20px rgba(88,166,255,0.2); |
| margin: 0 auto 10px; |
| animation: pulse-av 2.5s ease-in-out infinite; |
| } |
| .profile-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; } |
| @keyframes pulse-av { |
| 0%,100%{box-shadow:0 0 0 3px rgba(88,166,255,0.15),0 0 20px rgba(88,166,255,0.2);} |
| 50% {box-shadow:0 0 0 4px rgba(88,166,255,0.3),0 0 30px rgba(88,166,255,0.35);} |
| } |
| .profile-name { |
| font-family: 'Orbitron', monospace; |
| font-size: 0.82rem; font-weight: 700; color: #fff; letter-spacing: 1px; |
| } |
| .profile-role { |
| font-family: 'Share Tech Mono', monospace; |
| font-size: 0.64rem; color: #58a6ff; letter-spacing: 2px; margin-top: 3px; |
| } |
| .profile-links { display: flex; justify-content: center; gap: 8px; margin-top: 10px; flex-wrap: wrap; } |
| .p-link { |
| font-family: 'Share Tech Mono', monospace; |
| font-size: 0.62rem; color: #8b949e !important; |
| text-decoration: none !important; |
| background: #0d1117; border: 1px solid #30363d; |
| padding: 3px 10px; border-radius: 20px; transition: all 0.25s; |
| } |
| .p-link:hover { color: #58a6ff !important; border-color: #58a6ff; } |
| |
| /* ── PDF info banner ── */ |
| .pdf-banner { |
| background: #161b22; border: 1px solid #21262d; |
| border-left: 3px solid #58a6ff; border-radius: 8px; |
| padding: 12px 18px; display: flex; align-items: center; gap: 12px; |
| margin-bottom: 16px; animation: fadeInUp 0.4s ease both; |
| } |
| .pdf-name { font-weight: 700; font-size: 0.95rem; color: #fff; } |
| .pdf-meta { font-family: 'Share Tech Mono', monospace; font-size: 0.68rem; color: #8b949e; margin-top: 2px; } |
| |
| /* ── Chat bubbles ── */ |
| .msg-user { display: flex; justify-content: flex-end; animation: slideInR 0.3s ease both; } |
| .msg-ai { display: flex; justify-content: flex-start; animation: slideInL 0.3s ease both; } |
| .bubble-user { |
| background: linear-gradient(135deg, #1f6feb, #388bfd); |
| color: #fff; padding: 12px 18px; |
| border-radius: 18px 18px 4px 18px; |
| max-width: 75%; font-size: 0.97rem; line-height: 1.6; |
| box-shadow: 0 4px 15px rgba(31,111,235,0.25); |
| } |
| .msg-meta { |
| font-family: 'Share Tech Mono', monospace; |
| font-size: 0.62rem; color: #484f58; margin-top: 4px; padding: 0 6px; |
| } |
| |
| /* ── Source chunks ── */ |
| .sources-wrap { margin-top: 10px; border-top: 1px solid #21262d; padding-top: 10px; } |
| .source-label { |
| font-family: 'Share Tech Mono', monospace; |
| font-size: 0.62rem; color: #8b949e; letter-spacing: 2px; margin-bottom: 6px; |
| } |
| .source-chip { |
| display: inline-block; |
| background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.2); |
| color: #58a6ff; padding: 3px 10px; border-radius: 4px; |
| font-size: 0.72rem; font-family: 'Share Tech Mono', monospace; margin: 3px 3px; |
| } |
| |
| /* ── Typing indicator ── */ |
| .typing-wrap { display: flex; justify-content: flex-start; } |
| .typing-box { |
| display: flex; align-items: center; gap: 5px; |
| padding: 14px 18px; background: #161b22; |
| border: 1px solid #30363d; border-radius: 18px 18px 18px 4px; |
| } |
| .t-dot { |
| width: 7px; height: 7px; background: #58a6ff; |
| border-radius: 50%; animation: tdot 1.2s ease-in-out infinite; |
| } |
| .t-dot:nth-child(2){animation-delay:0.2s;} |
| .t-dot:nth-child(3){animation-delay:0.4s;} |
| @keyframes tdot{0%,60%,100%{transform:translateY(0);opacity:0.4;}30%{transform:translateY(-8px);opacity:1;}} |
| |
| /* ── Input — merged input+button bar (scoped to main area) ── */ |
| [data-testid="stAppViewContainer"] div[data-testid="column"]:first-child .stTextInput > div > div > input { |
| background: #161b22 !important; |
| border: 1px solid #30363d !important; |
| border-right: none !important; |
| border-radius: 12px 0 0 12px !important; |
| color: #c9d1d9 !important; |
| font-family: 'Rajdhani', sans-serif !important; |
| font-size: 0.97rem !important; |
| height: 48px !important; |
| padding: 12px 18px !important; |
| } |
| [data-testid="stAppViewContainer"] div[data-testid="column"]:first-child .stTextInput > div > div > input:focus { |
| border-color: #58a6ff !important; |
| box-shadow: none !important; |
| outline: none !important; |
| } |
| [data-testid="stAppViewContainer"] div[data-testid="column"]:last-child .stButton > button { |
| background: linear-gradient(135deg, #1f6feb, #388bfd) !important; |
| color: #fff !important; |
| border: 1px solid #1f6feb !important; |
| border-left: none !important; |
| border-radius: 0 12px 12px 0 !important; |
| height: 48px !important; width: 100% !important; |
| font-size: 1rem !important; letter-spacing: 1px !important; |
| box-shadow: 0 0 14px rgba(31,111,235,0.3) !important; |
| transition: all 0.2s ease !important; |
| margin-top: 0 !important; padding: 0 !important; |
| } |
| [data-testid="stAppViewContainer"] div[data-testid="column"]:last-child .stButton > button:hover { |
| box-shadow: 0 0 24px rgba(31,111,235,0.55) !important; |
| color: #fff !important; |
| } |
| [data-testid="stAppViewContainer"] div[data-testid="column"]:first-child { padding-right: 0 !important; } |
| [data-testid="stAppViewContainer"] div[data-testid="column"]:last-child { padding-left: 0 !important; } |
| |
| /* ── Buttons (sidebar/general) ── */ |
| .stButton > button { |
| background: #21262d !important; color: #c9d1d9 !important; |
| border: 1px solid #30363d !important; border-radius: 8px !important; |
| font-family: 'Share Tech Mono', monospace !important; |
| font-size: 0.73rem !important; letter-spacing: 1px !important; |
| transition: all 0.25s !important; |
| } |
| .stButton > button:hover { |
| background: #30363d !important; border-color: #58a6ff !important; color: #58a6ff !important; |
| } |
| |
| /* ── Welcome card ── */ |
| .welcome-card { |
| background: #161b22; border: 1px solid #21262d; |
| border-radius: 12px; padding: 30px; text-align: center; margin: 10px 0; |
| animation: fadeInUp 0.6s ease both; |
| } |
| .wc-icon { font-size: 2.8rem; margin-bottom: 12px; } |
| .wc-title { font-family: 'Orbitron', monospace; font-size: 1rem; color: #fff; margin-bottom: 8px; } |
| .wc-sub { font-size: 0.9rem; color: #8b949e; line-height: 1.65; } |
| .tip-chip { |
| display: inline-block; |
| background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.25); |
| color: #58a6ff; padding: 4px 12px; border-radius: 20px; margin: 4px 3px; |
| font-family: 'Share Tech Mono', monospace; font-size: 0.67rem; |
| } |
| |
| /* ── File uploader ── */ |
| [data-testid="stFileUploader"] { |
| background: #161b22 !important; border: 2px dashed #30363d !important; border-radius: 10px !important; |
| } |
| [data-testid="stFileUploader"]:hover { border-color: #58a6ff !important; } |
| |
| /* ── Glow divider ── */ |
| .glow-div { |
| border: none; height: 1px; |
| background: linear-gradient(90deg,transparent,#58a6ff,transparent); |
| margin: 16px 0; box-shadow: 0 0 8px rgba(88,166,255,0.3); |
| } |
| |
| /* ── AI bubble native markdown styling ── */ |
| [data-testid="stAppViewContainer"] [data-testid="column"] p { color: #c9d1d9; font-size: 0.95rem; line-height: 1.75; margin: 4px 0; } |
| [data-testid="stAppViewContainer"] [data-testid="column"] h1, |
| [data-testid="stAppViewContainer"] [data-testid="column"] h2, |
| [data-testid="stAppViewContainer"] [data-testid="column"] h3 { color: #fff; margin: 10px 0 4px; } |
| [data-testid="stAppViewContainer"] [data-testid="column"] ul, |
| [data-testid="stAppViewContainer"] [data-testid="column"] ol { color: #c9d1d9; padding-left: 20px; } |
| [data-testid="stAppViewContainer"] [data-testid="column"] code { background: #0d1117; color: #58a6ff; padding: 2px 6px; border-radius: 4px; font-size: 0.85rem; } |
| [data-testid="stAppViewContainer"] [data-testid="column"] pre { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 12px; } |
| [data-testid="stAppViewContainer"] [data-testid="column"] strong { color: #fff; } |
| |
| /* ── Stats ── */ |
| .stat-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; } |
| .stat-box { |
| flex: 1; min-width: 80px; background: #161b22; border: 1px solid #21262d; |
| border-top: 2px solid #58a6ff; border-radius: 6px; padding: 10px 12px; text-align: center; |
| } |
| .stat-num { font-family: 'Orbitron', monospace; font-size: 1.2rem; font-weight: 900; color: #58a6ff; } |
| .stat-lbl { font-family: 'Share Tech Mono', monospace; font-size: 0.6rem; color: #8b949e; letter-spacing: 1px; margin-top: 3px; } |
| |
| /* ── Animations ── */ |
| @keyframes fadeInDown { from{opacity:0;transform:translateY(-16px)} to{opacity:1;transform:translateY(0)} } |
| @keyframes fadeInUp { from{opacity:0;transform:translateY(16px)} to{opacity:1;transform:translateY(0)} } |
| @keyframes slideInR { from{opacity:0;transform:translateX(20px)} to{opacity:1;transform:translateX(0)} } |
| @keyframes slideInL { from{opacity:0;transform:translateX(-20px)} to{opacity:1;transform:translateX(0)} } |
| |
| ::-webkit-scrollbar { width: 5px; } |
| ::-webkit-scrollbar-track { background: #0d1117; } |
| ::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; } |
| ::-webkit-scrollbar-thumb:hover { background: #58a6ff; } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| |
| if "messages" not in st.session_state: st.session_state.messages = [] |
| if "vectorstore" not in st.session_state: st.session_state.vectorstore = None |
| if "pdf_name" not in st.session_state: st.session_state.pdf_name = None |
| if "pdf_pages" not in st.session_state: st.session_state.pdf_pages = 0 |
| if "pdf_chunks" not in st.session_state: st.session_state.pdf_chunks = 0 |
| if "q_count" not in st.session_state: st.session_state.q_count = 0 |
| if "last_q" not in st.session_state: st.session_state.last_q = "" |
| if "input_key" not in st.session_state: st.session_state.input_key = 0 |
| if "pending_input" not in st.session_state: st.session_state.pending_input = "" |
|
|
| |
| @st.cache_resource(show_spinner=False) |
| def load_embeddings(): |
| return HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") |
|
|
| @st.cache_resource(show_spinner=False) |
| def load_llm(): |
| model_id = "TinyLlama/TinyLlama-1.1B-chat-v1.0" |
| tokenizer = AutoTokenizer.from_pretrained(model_id) |
| model = AutoModelForCausalLM.from_pretrained( |
| model_id, |
| torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, |
| low_cpu_mem_usage=True, |
| device_map="cuda" if torch.cuda.is_available() else None |
| ) |
| if not torch.cuda.is_available(): |
| model = model.to("cpu") |
| pipe = pipeline( |
| "text-generation", model=model, tokenizer=tokenizer, |
| max_new_tokens=512, temperature=0.3, do_sample=True, |
| pad_token_id=tokenizer.eos_token_id, repetition_penalty=1.1 |
| ) |
| return HuggingFacePipeline(pipeline=pipe) |
|
|
| |
| def process_pdf(uploaded_file): |
| reader = PdfReader(uploaded_file) |
| raw_text = "" |
| for page in reader.pages: |
| text = page.extract_text() |
| if text: |
| raw_text += text |
|
|
| if not raw_text.strip(): |
| raise ValueError("No readable text found. PDF may be scanned/image-based.") |
|
|
| splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) |
| chunks = splitter.split_text(raw_text) |
| embeddings = load_embeddings() |
| vectorstore = FAISS.from_texts(chunks, embeddings) |
| return vectorstore, len(reader.pages), len(chunks) |
|
|
| |
| def get_answer(question, vectorstore): |
| retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) |
| relevant_docs = retriever.invoke(question) |
| context = "\n\n".join([f"---\n{doc.page_content}" for doc in relevant_docs]) |
| sources = [doc.page_content[:120] + "..." for doc in relevant_docs] |
|
|
| prompt_template = PromptTemplate( |
| input_variables=["context", "question"], |
| template="""<|system|> |
| You are QueryDocs AI, an intelligent document assistant. Use ONLY the context provided to answer the question clearly and accurately. If the answer is not in the context, say so honestly. |
| <|user|> |
| CONTEXT: |
| {context} |
| |
| QUESTION: |
| {question} |
| <|assistant|> |
| """ |
| ) |
| llm = load_llm() |
| chain = prompt_template | llm |
| result = chain.invoke({"context": context, "question": question}) |
|
|
| |
| if "<|assistant|>" in result: |
| answer = result.split("<|assistant|>")[-1].strip() |
| else: |
| answer = result.strip() |
|
|
| return answer, sources |
|
|
| |
| with st.sidebar: |
| |
| img_b64 = img_to_base64("assets/NANII.png") |
| avatar = (f'<img src="data:image/png;base64,{img_b64}" alt="Sriram">' |
| if img_b64 else |
| '<div style="width:72px;height:72px;background:#1f6feb;border-radius:50%;margin:0 auto;"></div>') |
|
|
| st.markdown(f""" |
| <div class="profile-card"> |
| <div class="profile-avatar">{avatar}</div> |
| <div class="profile-name">SRIRAM SAI</div> |
| <div class="profile-role">AI & ML ENGINEER</div> |
| <div class="profile-links"> |
| <a class="p-link" href="https://github.com/sriramsai18" target="_blank">💻 GitHub</a> |
| <a class="p-link" href="https://www.linkedin.com/in/sriram-sai-laggisetti/" target="_blank">💼 LinkedIn</a> |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| st.markdown('<div style="font-family:\'Share Tech Mono\',monospace;font-size:0.7rem;color:#8b949e;letter-spacing:2px;margin-bottom:8px;">📄 UPLOAD DOCUMENT</div>', unsafe_allow_html=True) |
|
|
| uploaded_file = st.file_uploader("Upload PDF", type=["pdf"], label_visibility="collapsed") |
|
|
| if uploaded_file: |
| if st.session_state.pdf_name != uploaded_file.name: |
| with st.spinner("🔍 Processing PDF..."): |
| try: |
| vs, pages, chunks = process_pdf(uploaded_file) |
| st.session_state.vectorstore = vs |
| st.session_state.pdf_name = uploaded_file.name |
| st.session_state.pdf_pages = pages |
| st.session_state.pdf_chunks = chunks |
| st.session_state.messages = [] |
| st.session_state.q_count = 0 |
| st.success("✅ PDF ready!") |
| except Exception as e: |
| st.error(f"❌ {str(e)}") |
|
|
| st.markdown("---") |
|
|
| if st.session_state.pdf_name: |
| st.markdown(f""" |
| <div style="font-family:'Share Tech Mono',monospace;font-size:0.7rem;color:#8b949e;margin-bottom:10px;letter-spacing:2px;">📊 DOCUMENT STATS</div> |
| <div style="display:flex;flex-direction:column;gap:6px;"> |
| <div style="background:#0d1117;border:1px solid #21262d;border-radius:6px;padding:8px 12px;font-family:'Share Tech Mono',monospace;font-size:0.7rem;"> |
| 📄 <span style="color:#c9d1d9;">{st.session_state.pdf_name[:22]}{'...' if len(st.session_state.pdf_name)>22 else ''}</span> |
| </div> |
| <div style="display:flex;gap:6px;"> |
| <div style="flex:1;background:#0d1117;border:1px solid #21262d;border-top:2px solid #58a6ff;border-radius:6px;padding:8px;text-align:center;"> |
| <div style="font-family:'Orbitron',monospace;font-size:1rem;color:#58a6ff;">{st.session_state.pdf_pages}</div> |
| <div style="font-family:'Share Tech Mono',monospace;font-size:0.58rem;color:#8b949e;">PAGES</div> |
| </div> |
| <div style="flex:1;background:#0d1117;border:1px solid #21262d;border-top:2px solid #58a6ff;border-radius:6px;padding:8px;text-align:center;"> |
| <div style="font-family:'Orbitron',monospace;font-size:1rem;color:#58a6ff;">{st.session_state.pdf_chunks}</div> |
| <div style="font-family:'Share Tech Mono',monospace;font-size:0.58rem;color:#8b949e;">CHUNKS</div> |
| </div> |
| <div style="flex:1;background:#0d1117;border:1px solid #21262d;border-top:2px solid #58a6ff;border-radius:6px;padding:8px;text-align:center;"> |
| <div style="font-family:'Orbitron',monospace;font-size:1rem;color:#58a6ff;">{st.session_state.q_count}</div> |
| <div style="font-family:'Share Tech Mono',monospace;font-size:0.58rem;color:#8b949e;">ASKED</div> |
| </div> |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
| st.markdown("---") |
|
|
| if st.button("🗑️ CLEAR CHAT", use_container_width=True): |
| st.session_state.messages = [] |
| st.session_state.q_count = 0 |
| st.rerun() |
|
|
| if st.button("📄 LOAD NEW PDF", use_container_width=True): |
| st.session_state.vectorstore = None |
| st.session_state.pdf_name = None |
| st.session_state.pdf_pages = 0 |
| st.session_state.pdf_chunks = 0 |
| st.session_state.messages = [] |
| st.session_state.q_count = 0 |
| st.rerun() |
|
|
| |
| st.markdown(""" |
| <div class="app-header"> |
| <div> |
| <div class="app-title">QUERY<span>DOCS</span> AI 📚</div> |
| <div class="app-sub">INTELLIGENT DOCUMENT Q&A · RAG PIPELINE</div> |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| if st.session_state.pdf_name: |
| safe_pdf_name = html_module.escape(st.session_state.pdf_name) |
| st.markdown(f""" |
| <div class="pdf-banner"> |
| <span style="font-size:1.4rem;">📄</span> |
| <div> |
| <div class="pdf-name">{safe_pdf_name}</div> |
| <div class="pdf-meta">{st.session_state.pdf_pages} pages · {st.session_state.pdf_chunks} chunks · ready to query</div> |
| </div> |
| <span style="margin-left:auto;background:rgba(88,166,255,0.1);border:1px solid rgba(88,166,255,0.3); |
| color:#58a6ff;padding:4px 12px;border-radius:20px; |
| font-family:'Share Tech Mono',monospace;font-size:0.65rem;">● ACTIVE</span> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| if not st.session_state.vectorstore: |
| st.markdown(""" |
| <div class="welcome-card"> |
| <div class="wc-icon">📚</div> |
| <div class="wc-title">WELCOME TO QUERYDOCS AI</div> |
| <div class="wc-sub"> |
| Upload any PDF document from the sidebar and start asking questions.<br> |
| Powered by RAG pipeline — context-aware answers. |
| </div> |
| <br> |
| <span class="tip-chip">📋 Legal documents</span> |
| <span class="tip-chip">📊 Research papers</span> |
| <span class="tip-chip">📖 Study material</span> |
| <span class="tip-chip">📝 Reports</span> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| else: |
| |
| if st.session_state.messages: |
| for msg in st.session_state.messages: |
| ts = msg.get("time", "") |
| if msg["role"] == "user": |
| safe_user = html_module.escape(msg["content"]) |
| st.markdown(f""" |
| <div class="msg-user"> |
| <div> |
| <div class="bubble-user">{safe_user}</div> |
| <div class="msg-meta" style="text-align:right;">YOU · {ts}</div> |
| </div> |
| </div>""", unsafe_allow_html=True) |
| else: |
| |
| col_ai, col_space = st.columns([4, 1]) |
| with col_ai: |
| |
| st.markdown(f""" |
| <div style="background:#161b22;color:#c9d1d9;padding:14px 18px 6px 18px; |
| border-radius:18px 18px 0 4px;font-size:0.95rem;line-height:1.75; |
| border:1px solid #30363d;border-bottom:none; |
| box-shadow:0 4px 15px rgba(0,0,0,0.3);"> |
| <div style="font-family:'Share Tech Mono',monospace;font-size:0.65rem; |
| color:#58a6ff;letter-spacing:2px;margin-bottom:6px;">// QUERYDOCS RESPONSE</div> |
| </div>""", unsafe_allow_html=True) |
| |
| st.markdown(f'<div style="background:#161b22;padding:0 18px 6px 18px;border-left:1px solid #30363d;border-right:1px solid #30363d;">', unsafe_allow_html=True) |
| st.markdown(msg["content"]) |
| st.markdown('</div>', unsafe_allow_html=True) |
| |
| if msg.get("sources"): |
| chips = "".join(f'<span class="source-chip">📎 Chunk {i+1}</span>' |
| for i, _ in enumerate(msg["sources"])) |
| st.markdown(f""" |
| <div style="background:#161b22;padding:6px 18px 8px 18px; |
| border-left:1px solid #30363d;border-right:1px solid #30363d;"> |
| <div class="sources-wrap"> |
| <div class="source-label">// SOURCE CHUNKS USED</div> |
| {chips} |
| </div> |
| </div>""", unsafe_allow_html=True) |
| |
| st.markdown(f""" |
| <div style="background:#161b22;padding:6px 18px 12px 18px; |
| border-radius:0 0 18px 4px;border:1px solid #30363d;border-top:none; |
| box-shadow:0 4px 15px rgba(0,0,0,0.3);margin-bottom:4px;"> |
| <div style="font-family:'Share Tech Mono',monospace;font-size:0.62rem;color:#484f58;"> |
| 📚 QUERYDOCS AI · {ts} · {msg.get("elapsed","?")}s |
| </div> |
| </div>""", unsafe_allow_html=True) |
| else: |
| st.markdown(""" |
| <div class="welcome-card" style="padding:20px;"> |
| <div style="font-size:1.6rem;margin-bottom:8px;">💬</div> |
| <div class="wc-title" style="font-size:0.85rem;">DOCUMENT LOADED — START ASKING</div> |
| <div class="wc-sub" style="font-size:0.82rem;">Ask anything about the uploaded document.</div> |
| <br> |
| <span class="tip-chip">💡 Summarize this document</span> |
| <span class="tip-chip">💡 What are the key findings?</span> |
| <span class="tip-chip">💡 List all important dates</span> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| typing_slot = st.empty() |
|
|
| |
| st.markdown('<hr class="glow-div">', unsafe_allow_html=True) |
| _current_key = f"question_input_{st.session_state.input_key}" |
|
|
| def _sync_pending(): |
| st.session_state.pending_input = st.session_state.get(_current_key, "") |
|
|
| col_q, col_btn = st.columns([6, 0.7]) |
| with col_q: |
| question = st.text_input( |
| "", placeholder="ask a question about your document...", |
| label_visibility="collapsed", |
| key=_current_key, |
| on_change=_sync_pending |
| ) |
| if question: |
| st.session_state.pending_input = question |
| with col_btn: |
| ask_btn = st.button("▶ ASK", use_container_width=True) |
|
|
| trigger_q = (st.session_state.get("pending_input", "") or question).strip() |
|
|
| |
| if trigger_q and trigger_q != st.session_state.last_q: |
|
|
| |
| st.session_state.last_q = trigger_q |
| st.session_state.input_key += 1 |
| st.session_state.pending_input = "" |
|
|
| ts = get_ist_time() |
| st.session_state.messages.append({ |
| "role": "user", "content": trigger_q, "time": ts |
| }) |
| st.session_state.q_count += 1 |
|
|
| typing_slot.markdown(""" |
| <div class="typing-wrap"> |
| <div class="typing-box"> |
| <div class="t-dot"></div> |
| <div class="t-dot"></div> |
| <div class="t-dot"></div> |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| try: |
| start = time.time() |
| answer, sources = get_answer(trigger_q, st.session_state.vectorstore) |
| elapsed = round(time.time() - start, 1) |
|
|
| st.session_state.messages.append({ |
| "role": "assistant", |
| "content": answer, |
| "sources": sources, |
| "time": get_ist_time(), |
| "elapsed": elapsed |
| }) |
| except Exception as e: |
| st.session_state.messages.append({ |
| "role": "assistant", |
| "content": f"⚠️ Error generating answer: {str(e)}", |
| "sources": [], |
| "time": get_ist_time(), |
| "elapsed": 0 |
| }) |
|
|
| typing_slot.empty() |
| st.rerun() |