sriiram18 commited on
Commit
d9a79cf
Β·
verified Β·
1 Parent(s): 2ba4bca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +633 -55
app.py CHANGED
@@ -1,69 +1,647 @@
1
- import gradio as gr
2
- from huggingface_hub import InferenceClient
 
 
 
 
 
 
 
 
3
 
 
 
 
 
 
 
4
 
5
- def respond(
6
- message,
7
- history: list[dict[str, str]],
8
- system_message,
9
- max_tokens,
10
- temperature,
11
- top_p,
12
- hf_token: gr.OAuthToken,
13
- ):
14
- """
15
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
16
- """
17
- client = InferenceClient(token=hf_token.token, model="openai/gpt-oss-20b")
18
 
19
- messages = [{"role": "system", "content": system_message}]
 
 
 
20
 
21
- messages.extend(history)
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- messages.append({"role": "user", "content": message})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- response = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- for message in client.chat_completion(
28
- messages,
29
- max_tokens=max_tokens,
30
- stream=True,
31
- temperature=temperature,
32
- top_p=top_p,
33
- ):
34
- choices = message.choices
35
- token = ""
36
- if len(choices) and choices[0].delta.content:
37
- token = choices[0].delta.content
 
 
 
 
 
 
38
 
39
- response += token
40
- yield response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  """
44
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
45
- """
46
- chatbot = gr.ChatInterface(
47
- respond,
48
- additional_inputs=[
49
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
50
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
51
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
52
- gr.Slider(
53
- minimum=0.1,
54
- maximum=1.0,
55
- value=0.95,
56
- step=0.05,
57
- label="Top-p (nucleus sampling)",
58
- ),
59
- ],
60
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
- with gr.Blocks() as demo:
63
- with gr.Sidebar():
64
- gr.LoginButton()
65
- chatbot.render()
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
- if __name__ == "__main__":
69
- demo.launch()
 
1
+ import streamlit as st
2
+ from PyPDF2 import PdfReader
3
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
4
+ from langchain_huggingface import HuggingFaceEmbeddings, HuggingFacePipeline
5
+ from langchain_community.vectorstores import FAISS
6
+ from langchain.prompts import PromptTemplate
7
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
8
+ import torch
9
+ import time
10
+ import base64
11
 
12
+ st.set_page_config(
13
+ page_title="QueryDocs AI",
14
+ page_icon="πŸ“š",
15
+ layout="wide",
16
+ initial_sidebar_state="expanded"
17
+ )
18
 
19
+ # ─── HELPERS ──────────────────────────────────────────────────────────────────
20
+ def img_to_base64(path):
21
+ try:
22
+ with open(path, "rb") as f:
23
+ return base64.b64encode(f.read()).decode()
24
+ except:
25
+ return None
 
 
 
 
 
 
26
 
27
+ # ─── GLOBAL CSS ───────────────────────────────────────────────────────────────
28
+ st.markdown("""
29
+ <style>
30
+ @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;600;700&family=Orbitron:wght@700;900&display=swap');
31
 
32
+ /* ── Base ── */
33
+ html, body, [data-testid="stAppViewContainer"] {
34
+ background: #0d1117 !important;
35
+ color: #c9d1d9 !important;
36
+ font-family: 'Rajdhani', sans-serif !important;
37
+ }
38
+ [data-testid="stSidebar"] {
39
+ background: #161b22 !important;
40
+ border-right: 1px solid #30363d !important;
41
+ }
42
+ [data-testid="stSidebar"] * { color: #c9d1d9 !important; }
43
+ footer, #MainMenu { visibility: hidden; }
44
 
45
+ /* ── Header ── */
46
+ .app-header {
47
+ display: flex; align-items: center; gap: 14px;
48
+ padding: 16px 0 14px;
49
+ border-bottom: 1px solid #21262d;
50
+ margin-bottom: 20px;
51
+ animation: fadeInDown 0.5s ease both;
52
+ }
53
+ .app-title {
54
+ font-family: 'Orbitron', monospace;
55
+ font-size: 1.6rem; font-weight: 900;
56
+ color: #fff; letter-spacing: 2px;
57
+ }
58
+ .app-title span { color: #58a6ff; }
59
+ .app-sub {
60
+ font-family: 'Share Tech Mono', monospace;
61
+ font-size: 0.68rem; color: #8b949e;
62
+ letter-spacing: 3px; margin-top: 3px;
63
+ }
64
 
65
+ /* ── Profile card in sidebar ── */
66
+ .profile-card {
67
+ text-align: center;
68
+ padding: 20px 0 16px;
69
+ border-bottom: 1px solid #21262d;
70
+ margin-bottom: 16px;
71
+ animation: fadeInDown 0.5s ease both;
72
+ }
73
+ .profile-avatar {
74
+ width: 72px; height: 72px;
75
+ border-radius: 50%;
76
+ overflow: hidden;
77
+ border: 2px solid #58a6ff;
78
+ box-shadow: 0 0 0 3px rgba(88,166,255,0.15),
79
+ 0 0 20px rgba(88,166,255,0.2);
80
+ margin: 0 auto 10px;
81
+ animation: pulse-av 2.5s ease-in-out infinite;
82
+ }
83
+ .profile-avatar img {
84
+ width: 100%; height: 100%;
85
+ object-fit: cover; border-radius: 50%;
86
+ }
87
+ @keyframes pulse-av {
88
+ 0%,100%{box-shadow:0 0 0 3px rgba(88,166,255,0.15),0 0 20px rgba(88,166,255,0.2);}
89
+ 50% {box-shadow:0 0 0 4px rgba(88,166,255,0.3),0 0 30px rgba(88,166,255,0.35);}
90
+ }
91
+ .profile-name {
92
+ font-family: 'Orbitron', monospace;
93
+ font-size: 0.82rem; font-weight: 700;
94
+ color: #fff; letter-spacing: 1px;
95
+ }
96
+ .profile-role {
97
+ font-family: 'Share Tech Mono', monospace;
98
+ font-size: 0.64rem; color: #58a6ff;
99
+ letter-spacing: 2px; margin-top: 3px;
100
+ }
101
+ .profile-links {
102
+ display: flex; justify-content: center;
103
+ gap: 8px; margin-top: 10px; flex-wrap: wrap;
104
+ }
105
+ .p-link {
106
+ font-family: 'Share Tech Mono', monospace;
107
+ font-size: 0.62rem; color: #8b949e !important;
108
+ text-decoration: none !important;
109
+ background: #0d1117;
110
+ border: 1px solid #30363d;
111
+ padding: 3px 10px; border-radius: 20px;
112
+ transition: all 0.25s;
113
+ }
114
+ .p-link:hover { color: #58a6ff !important; border-color: #58a6ff; }
115
 
116
+ /* ── Upload zone ── */
117
+ .upload-card {
118
+ background: #161b22;
119
+ border: 2px dashed #30363d;
120
+ border-radius: 12px;
121
+ padding: 32px;
122
+ text-align: center;
123
+ transition: all 0.3s ease;
124
+ animation: fadeInUp 0.5s ease both;
125
+ }
126
+ .upload-card:hover { border-color: #58a6ff; }
127
+ .upload-icon { font-size: 2.5rem; margin-bottom: 10px; }
128
+ .upload-title {
129
+ font-family: 'Orbitron', monospace;
130
+ font-size: 0.95rem; color: #fff; margin-bottom: 6px;
131
+ }
132
+ .upload-sub { font-size: 0.85rem; color: #8b949e; }
133
 
134
+ /* ── PDF info banner ── */
135
+ .pdf-banner {
136
+ background: #161b22;
137
+ border: 1px solid #21262d;
138
+ border-left: 3px solid #58a6ff;
139
+ border-radius: 8px;
140
+ padding: 12px 18px;
141
+ display: flex; align-items: center; gap: 12px;
142
+ margin-bottom: 16px;
143
+ animation: fadeInUp 0.4s ease both;
144
+ }
145
+ .pdf-name {
146
+ font-weight: 700; font-size: 0.95rem; color: #fff;
147
+ }
148
+ .pdf-meta {
149
+ font-family: 'Share Tech Mono', monospace;
150
+ font-size: 0.68rem; color: #8b949e; margin-top: 2px;
151
+ }
152
 
153
+ /* ── Chat bubbles ── */
154
+ .chat-wrap { display: flex; flex-direction: column; gap: 16px; padding: 8px 0; }
155
+ .msg-user { display: flex; justify-content: flex-end; animation: slideInR 0.3s ease both; }
156
+ .msg-ai { display: flex; justify-content: flex-start; animation: slideInL 0.3s ease both; }
157
+ .bubble-user {
158
+ background: linear-gradient(135deg, #1f6feb, #388bfd);
159
+ color: #fff; padding: 12px 18px;
160
+ border-radius: 18px 18px 4px 18px;
161
+ max-width: 75%; font-size: 0.97rem; line-height: 1.6;
162
+ box-shadow: 0 4px 15px rgba(31,111,235,0.25);
163
+ }
164
+ .bubble-ai {
165
+ background: #161b22; color: #c9d1d9;
166
+ padding: 14px 18px;
167
+ border-radius: 18px 18px 18px 4px;
168
+ max-width: 80%; font-size: 0.95rem; line-height: 1.75;
169
+ border: 1px solid #30363d;
170
+ box-shadow: 0 4px 15px rgba(0,0,0,0.3);
171
+ }
172
+ .bubble-ai .answer-label {
173
+ font-family: 'Share Tech Mono', monospace;
174
+ font-size: 0.65rem; color: #58a6ff;
175
+ letter-spacing: 2px; margin-bottom: 8px;
176
+ text-transform: uppercase;
177
+ }
178
+ .msg-meta {
179
+ font-family: 'Share Tech Mono', monospace;
180
+ font-size: 0.62rem; color: #484f58;
181
+ margin-top: 4px; padding: 0 6px;
182
+ }
183
 
184
+ /* ── Source chunks ── */
185
+ .sources-wrap {
186
+ margin-top: 10px;
187
+ border-top: 1px solid #21262d;
188
+ padding-top: 10px;
189
+ }
190
+ .source-label {
191
+ font-family: 'Share Tech Mono', monospace;
192
+ font-size: 0.62rem; color: #8b949e;
193
+ letter-spacing: 2px; margin-bottom: 6px;
194
+ }
195
+ .source-chip {
196
+ display: inline-block;
197
+ background: rgba(88,166,255,0.08);
198
+ border: 1px solid rgba(88,166,255,0.2);
199
+ color: #58a6ff; padding: 3px 10px;
200
+ border-radius: 4px; font-size: 0.72rem;
201
+ font-family: 'Share Tech Mono', monospace;
202
+ margin: 3px 3px; cursor: pointer;
203
+ }
204
+
205
+ /* ── Typing indicator ── */
206
+ .typing-wrap { display: flex; justify-content: flex-start; }
207
+ .typing-box {
208
+ display: flex; align-items: center; gap: 5px;
209
+ padding: 14px 18px; background: #161b22;
210
+ border: 1px solid #30363d;
211
+ border-radius: 18px 18px 18px 4px;
212
+ }
213
+ .t-dot {
214
+ width: 7px; height: 7px; background: #58a6ff;
215
+ border-radius: 50%;
216
+ animation: tdot 1.2s ease-in-out infinite;
217
+ }
218
+ .t-dot:nth-child(2){animation-delay:0.2s;}
219
+ .t-dot:nth-child(3){animation-delay:0.4s;}
220
+ @keyframes tdot{0%,60%,100%{transform:translateY(0);opacity:0.4;}30%{transform:translateY(-8px);opacity:1;}}
221
+
222
+ /* ── Input ── */
223
+ .stTextInput > div > div > input {
224
+ background: #161b22 !important;
225
+ border: 1px solid #30363d !important;
226
+ border-radius: 8px !important;
227
+ color: #c9d1d9 !important;
228
+ font-family: 'Rajdhani', sans-serif !important;
229
+ font-size: 0.97rem !important;
230
+ }
231
+ .stTextInput > div > div > input:focus {
232
+ border-color: #58a6ff !important;
233
+ box-shadow: 0 0 12px rgba(88,166,255,0.15) !important;
234
+ }
235
+
236
+ /* ── Buttons ── */
237
+ .stButton > button {
238
+ background: #21262d !important; color: #c9d1d9 !important;
239
+ border: 1px solid #30363d !important; border-radius: 8px !important;
240
+ font-family: 'Share Tech Mono', monospace !important;
241
+ font-size: 0.73rem !important; letter-spacing: 1px !important;
242
+ transition: all 0.25s !important;
243
+ }
244
+ .stButton > button:hover {
245
+ background: #30363d !important;
246
+ border-color: #58a6ff !important; color: #58a6ff !important;
247
+ }
248
+ .send-btn > button {
249
+ background: linear-gradient(135deg,#1f6feb,#388bfd) !important;
250
+ color: #fff !important; border: none !important;
251
+ box-shadow: 0 0 16px rgba(31,111,235,0.3) !important;
252
+ }
253
+ .send-btn > button:hover {
254
+ box-shadow: 0 0 28px rgba(31,111,235,0.55) !important;
255
+ transform: translateY(-2px) !important; color: #fff !important;
256
+ }
257
+
258
+ /* ── Stats ── */
259
+ .stat-row {
260
+ display: flex; gap: 10px; flex-wrap: wrap;
261
+ margin-bottom: 14px;
262
+ }
263
+ .stat-box {
264
+ flex: 1; min-width: 80px;
265
+ background: #161b22; border: 1px solid #21262d;
266
+ border-top: 2px solid #58a6ff; border-radius: 6px;
267
+ padding: 10px 12px; text-align: center;
268
+ }
269
+ .stat-num {
270
+ font-family: 'Orbitron', monospace;
271
+ font-size: 1.2rem; font-weight: 900; color: #58a6ff;
272
+ }
273
+ .stat-lbl {
274
+ font-family: 'Share Tech Mono', monospace;
275
+ font-size: 0.6rem; color: #8b949e;
276
+ letter-spacing: 1px; margin-top: 3px;
277
+ }
278
+
279
+ /* ── Welcome ── */
280
+ .welcome-card {
281
+ background: #161b22; border: 1px solid #21262d;
282
+ border-radius: 12px; padding: 30px;
283
+ text-align: center; margin: 10px 0;
284
+ animation: fadeInUp 0.6s ease both;
285
+ }
286
+ .wc-icon { font-size: 2.8rem; margin-bottom: 12px; }
287
+ .wc-title {
288
+ font-family: 'Orbitron', monospace;
289
+ font-size: 1rem; color: #fff; margin-bottom: 8px;
290
+ }
291
+ .wc-sub { font-size: 0.9rem; color: #8b949e; line-height: 1.65; }
292
+ .tip-chip {
293
+ display: inline-block;
294
+ background: rgba(88,166,255,0.08);
295
+ border: 1px solid rgba(88,166,255,0.25);
296
+ color: #58a6ff; padding: 4px 12px;
297
+ border-radius: 20px; margin: 4px 3px;
298
+ font-family: 'Share Tech Mono', monospace;
299
+ font-size: 0.67rem;
300
+ }
301
+
302
+ /* ── File uploader ── */
303
+ [data-testid="stFileUploader"] {
304
+ background: #161b22 !important;
305
+ border: 2px dashed #30363d !important;
306
+ border-radius: 10px !important;
307
+ }
308
+ [data-testid="stFileUploader"]:hover {
309
+ border-color: #58a6ff !important;
310
+ }
311
+
312
+ /* ── Glow divider ── */
313
+ .glow-div {
314
+ border: none; height: 1px;
315
+ background: linear-gradient(90deg,transparent,#58a6ff,transparent);
316
+ margin: 16px 0; box-shadow: 0 0 8px rgba(88,166,255,0.3);
317
+ }
318
+
319
+ /* ── Animations ── */
320
+ @keyframes fadeInDown { from{opacity:0;transform:translateY(-16px)} to{opacity:1;transform:translateY(0)} }
321
+ @keyframes fadeInUp { from{opacity:0;transform:translateY(16px)} to{opacity:1;transform:translateY(0)} }
322
+ @keyframes slideInR { from{opacity:0;transform:translateX(20px)} to{opacity:1;transform:translateX(0)} }
323
+ @keyframes slideInL { from{opacity:0;transform:translateX(-20px)} to{opacity:1;transform:translateX(0)} }
324
+
325
+ ::-webkit-scrollbar { width: 5px; }
326
+ ::-webkit-scrollbar-track { background: #0d1117; }
327
+ ::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; }
328
+ ::-webkit-scrollbar-thumb:hover { background: #58a6ff; }
329
+ </style>
330
+ """, unsafe_allow_html=True)
331
+
332
+ # ─── SESSION STATE ────────────────────────────────────────────────────────────
333
+ if "messages" not in st.session_state: st.session_state.messages = []
334
+ if "vectorstore" not in st.session_state: st.session_state.vectorstore = None
335
+ if "pdf_name" not in st.session_state: st.session_state.pdf_name = None
336
+ if "pdf_pages" not in st.session_state: st.session_state.pdf_pages = 0
337
+ if "pdf_chunks" not in st.session_state: st.session_state.pdf_chunks = 0
338
+ if "q_count" not in st.session_state: st.session_state.q_count = 0
339
+
340
+ # ─── MODEL LOADERS ────────────────────────────────────────────────────────────
341
+ @st.cache_resource(show_spinner=False)
342
+ def load_embeddings():
343
+ return HuggingFaceEmbeddings(
344
+ model_name="sentence-transformers/all-MiniLM-L6-v2"
345
+ )
346
+
347
+ @st.cache_resource(show_spinner=False)
348
+ def load_llm():
349
+ model_id = "TinyLlama/TinyLlama-1.1B-chat-v1.0"
350
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
351
+ model = AutoModelForCausalLM.from_pretrained(
352
+ model_id, device_map="auto",
353
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32
354
+ )
355
+ pipe = pipeline(
356
+ "text-generation", model=model, tokenizer=tokenizer,
357
+ max_new_tokens=512, temperature=0.3, do_sample=True,
358
+ pad_token_id=tokenizer.eos_token_id, repetition_penalty=1.1
359
+ )
360
+ return HuggingFacePipeline(pipeline=pipe)
361
+
362
+ # ─── PDF PROCESSOR ────────────────────────────────────────────────────────────
363
+ def process_pdf(uploaded_file):
364
+ reader = PdfReader(uploaded_file)
365
+ raw_text = ""
366
+ for page in reader.pages:
367
+ text = page.extract_text()
368
+ if text:
369
+ raw_text += text
370
+
371
+ if not raw_text.strip():
372
+ raise ValueError("No readable text found. PDF may be scanned/image-based.")
373
+
374
+ splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
375
+ chunks = splitter.split_text(raw_text)
376
+ embeddings = load_embeddings()
377
+ vectorstore = FAISS.from_texts(chunks, embeddings)
378
+ return vectorstore, len(reader.pages), len(chunks)
379
+
380
+ # ─── ANSWER FUNCTION ──────────────────────────────────────────────────────────
381
+ def get_answer(question, vectorstore):
382
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
383
+ relevant_docs = retriever.invoke(question)
384
+ context = "\n\n".join([f"---\n{doc.page_content}" for doc in relevant_docs])
385
+ sources = [doc.page_content[:120] + "..." for doc in relevant_docs]
386
+
387
+ prompt_template = PromptTemplate(
388
+ input_variables=["context", "question"],
389
+ template="""<|system|>
390
+ 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.
391
+ <|user|>
392
+ CONTEXT:
393
+ {context}
394
+
395
+ QUESTION:
396
+ {question}
397
+ <|assistant|>
398
  """
399
+ )
400
+ llm = load_llm()
401
+ chain = prompt_template | llm
402
+ result = chain.invoke({"context": context, "question": question})
403
+
404
+ # extract only the assistant reply
405
+ if "<|assistant|>" in result:
406
+ answer = result.split("<|assistant|>")[-1].strip()
407
+ else:
408
+ answer = result.strip()
409
+
410
+ return answer, sources
411
+
412
+ # ─── SIDEBAR ──────────────────────────────────────────────────────────────────
413
+ with st.sidebar:
414
+ # Profile card
415
+ img_b64 = img_to_base64("assets/NANII.png")
416
+ avatar = (f'<img src="data:image/png;base64,{img_b64}" alt="Sriram">'
417
+ if img_b64 else
418
+ '<div style="width:72px;height:72px;background:#1f6feb;border-radius:50%;margin:0 auto;"></div>')
419
+
420
+ st.markdown(f"""
421
+ <div class="profile-card">
422
+ <div class="profile-avatar">{avatar}</div>
423
+ <div class="profile-name">SRIRAM SAI</div>
424
+ <div class="profile-role">// AI &amp; ML ENGINEER</div>
425
+ <div class="profile-links">
426
+ <a class="p-link" href="https://github.com/sriramsai18" target="_blank">πŸ’» GitHub</a>
427
+ <a class="p-link" href="https://www.linkedin.com/in/sriram-sai-laggisetti/" target="_blank">πŸ’Ό LinkedIn</a>
428
+ </div>
429
+ </div>
430
+ """, unsafe_allow_html=True)
431
+
432
+ # PDF upload
433
+ 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)
434
+
435
+ uploaded_file = st.file_uploader(
436
+ "", type=["pdf"],
437
+ label_visibility="collapsed"
438
+ )
439
+
440
+ if uploaded_file:
441
+ if st.session_state.pdf_name != uploaded_file.name:
442
+ with st.spinner("πŸ” Processing PDF..."):
443
+ try:
444
+ vs, pages, chunks = process_pdf(uploaded_file)
445
+ st.session_state.vectorstore = vs
446
+ st.session_state.pdf_name = uploaded_file.name
447
+ st.session_state.pdf_pages = pages
448
+ st.session_state.pdf_chunks = chunks
449
+ st.session_state.messages = []
450
+ st.session_state.q_count = 0
451
+ st.success("βœ… PDF ready!")
452
+ except Exception as e:
453
+ st.error(f"❌ {str(e)}")
454
+
455
+ st.markdown("---")
456
+
457
+ # Stats
458
+ if st.session_state.pdf_name:
459
+ st.markdown(f"""
460
+ <div style="font-family:'Share Tech Mono',monospace;font-size:0.7rem;color:#8b949e;margin-bottom:10px;letter-spacing:2px;">πŸ“Š DOCUMENT STATS</div>
461
+ <div style="display:flex;flex-direction:column;gap:6px;">
462
+ <div style="background:#0d1117;border:1px solid #21262d;border-radius:6px;padding:8px 12px;font-family:'Share Tech Mono',monospace;font-size:0.7rem;">
463
+ πŸ“„ <span style="color:#c9d1d9;">{st.session_state.pdf_name[:22]}{'...' if len(st.session_state.pdf_name)>22 else ''}</span>
464
+ </div>
465
+ <div style="display:flex;gap:6px;">
466
+ <div style="flex:1;background:#0d1117;border:1px solid #21262d;border-top:2px solid #58a6ff;border-radius:6px;padding:8px;text-align:center;">
467
+ <div style="font-family:'Orbitron',monospace;font-size:1rem;color:#58a6ff;">{st.session_state.pdf_pages}</div>
468
+ <div style="font-family:'Share Tech Mono',monospace;font-size:0.58rem;color:#8b949e;">PAGES</div>
469
+ </div>
470
+ <div style="flex:1;background:#0d1117;border:1px solid #21262d;border-top:2px solid #58a6ff;border-radius:6px;padding:8px;text-align:center;">
471
+ <div style="font-family:'Orbitron',monospace;font-size:1rem;color:#58a6ff;">{st.session_state.pdf_chunks}</div>
472
+ <div style="font-family:'Share Tech Mono',monospace;font-size:0.58rem;color:#8b949e;">CHUNKS</div>
473
+ </div>
474
+ <div style="flex:1;background:#0d1117;border:1px solid #21262d;border-top:2px solid #58a6ff;border-radius:6px;padding:8px;text-align:center;">
475
+ <div style="font-family:'Orbitron',monospace;font-size:1rem;color:#58a6ff;">{st.session_state.q_count}</div>
476
+ <div style="font-family:'Share Tech Mono',monospace;font-size:0.58rem;color:#8b949e;">ASKED</div>
477
+ </div>
478
+ </div>
479
+ </div>
480
+ """, unsafe_allow_html=True)
481
+ st.markdown("---")
482
+
483
+ # Clear button
484
+ if st.button("πŸ—‘οΈ CLEAR CHAT", use_container_width=True):
485
+ st.session_state.messages = []
486
+ st.session_state.q_count = 0
487
+ st.rerun()
488
+
489
+ # New PDF button
490
+ if st.button("πŸ“„ LOAD NEW PDF", use_container_width=True):
491
+ st.session_state.vectorstore = None
492
+ st.session_state.pdf_name = None
493
+ st.session_state.pdf_pages = 0
494
+ st.session_state.pdf_chunks = 0
495
+ st.session_state.messages = []
496
+ st.session_state.q_count = 0
497
+ st.rerun()
498
+
499
+ # ─── MAIN AREA ────────────────────────────────────────────────────────────────
500
+ # Header
501
+ st.markdown("""
502
+ <div class="app-header">
503
+ <div>
504
+ <div class="app-title">QUERY<span>DOCS</span> AI πŸ“š</div>
505
+ <div class="app-sub">// INTELLIGENT DOCUMENT Q&amp;A Β· RAG PIPELINE Β· TINYLLAMA 1.1B</div>
506
+ </div>
507
+ </div>
508
+ """, unsafe_allow_html=True)
509
+
510
+ # PDF banner (when loaded)
511
+ if st.session_state.pdf_name:
512
+ st.markdown(f"""
513
+ <div class="pdf-banner">
514
+ <span style="font-size:1.4rem;">πŸ“„</span>
515
+ <div>
516
+ <div class="pdf-name">{st.session_state.pdf_name}</div>
517
+ <div class="pdf-meta">{st.session_state.pdf_pages} pages Β· {st.session_state.pdf_chunks} chunks Β· ready to query</div>
518
+ </div>
519
+ <span style="margin-left:auto;background:rgba(88,166,255,0.1);border:1px solid rgba(88,166,255,0.3);
520
+ color:#58a6ff;padding:4px 12px;border-radius:20px;
521
+ font-family:'Share Tech Mono',monospace;font-size:0.65rem;">● ACTIVE</span>
522
+ </div>
523
+ """, unsafe_allow_html=True)
524
+
525
+ # Chat area
526
+ if not st.session_state.vectorstore:
527
+ st.markdown("""
528
+ <div class="welcome-card">
529
+ <div class="wc-icon">πŸ“š</div>
530
+ <div class="wc-title">WELCOME TO QUERYDOCS AI</div>
531
+ <div class="wc-sub">
532
+ Upload any PDF document from the sidebar and start asking questions.<br>
533
+ Powered by RAG pipeline + TinyLlama 1.1B for accurate, context-aware answers.
534
+ </div>
535
+ <br>
536
+ <span class="tip-chip">πŸ“‹ Legal documents</span>
537
+ <span class="tip-chip">πŸ“Š Research papers</span>
538
+ <span class="tip-chip">πŸ“– Study material</span>
539
+ <span class="tip-chip">πŸ“ Reports</span>
540
+ </div>
541
+ """, unsafe_allow_html=True)
542
+
543
+ else:
544
+ # Chat history
545
+ if st.session_state.messages:
546
+ chat_html = '<div class="chat-wrap">'
547
+ for msg in st.session_state.messages:
548
+ ts = msg.get("time", "")
549
+ if msg["role"] == "user":
550
+ chat_html += f"""
551
+ <div class="msg-user">
552
+ <div>
553
+ <div class="bubble-user">{msg["content"]}</div>
554
+ <div class="msg-meta" style="text-align:right;">YOU Β· {ts}</div>
555
+ </div>
556
+ </div>"""
557
+ else:
558
+ sources_html = ""
559
+ if msg.get("sources"):
560
+ chips = "".join(f'<span class="source-chip">πŸ“Ž Chunk {i+1}</span>'
561
+ for i, _ in enumerate(msg["sources"]))
562
+ sources_html = f"""
563
+ <div class="sources-wrap">
564
+ <div class="source-label">// SOURCE CHUNKS USED</div>
565
+ {chips}
566
+ </div>"""
567
+ chat_html += f"""
568
+ <div class="msg-ai">
569
+ <div>
570
+ <div class="bubble-ai">
571
+ <div class="answer-label">// QUERYDOCS RESPONSE</div>
572
+ {msg["content"]}
573
+ {sources_html}
574
+ </div>
575
+ <div class="msg-meta">πŸ“š QUERYDOCS AI Β· {ts} Β· {msg.get("elapsed","?")}s</div>
576
+ </div>
577
+ </div>"""
578
+ chat_html += '</div>'
579
+ st.markdown(chat_html, unsafe_allow_html=True)
580
+ else:
581
+ st.markdown("""
582
+ <div class="welcome-card" style="padding:20px;">
583
+ <div style="font-size:1.6rem;margin-bottom:8px;">πŸ’¬</div>
584
+ <div class="wc-title" style="font-size:0.85rem;">DOCUMENT LOADED β€” START ASKING</div>
585
+ <div class="wc-sub" style="font-size:0.82rem;">Ask anything about the uploaded document.</div>
586
+ <br>
587
+ <span class="tip-chip">πŸ’‘ Summarize this document</span>
588
+ <span class="tip-chip">πŸ’‘ What are the key findings?</span>
589
+ <span class="tip-chip">πŸ’‘ List all important dates</span>
590
+ </div>
591
+ """, unsafe_allow_html=True)
592
+
593
+ # Input row
594
+ st.markdown('<hr class="glow-div">', unsafe_allow_html=True)
595
+ col_q, col_btn = st.columns((5, 1))
596
+
597
+ with col_q:
598
+ question = st.text_input(
599
+ "", placeholder="// ask a question about your document...",
600
+ label_visibility="collapsed", key="question_input"
601
+ )
602
+ with col_btn:
603
+ st.markdown('<div class="send-btn">', unsafe_allow_html=True)
604
+ ask_btn = st.button("β–Ά ASK", use_container_width=True)
605
+ st.markdown('</div>', unsafe_allow_html=True)
606
+
607
+ # Generate answer
608
+ if (ask_btn or question) and question.strip():
609
+ ts = time.strftime("%H:%M")
610
+ st.session_state.messages.append({
611
+ "role": "user", "content": question.strip(), "time": ts
612
+ })
613
+ st.session_state.q_count += 1
614
+
615
+ # typing indicator
616
+ typing_slot = st.empty()
617
+ typing_slot.markdown("""
618
+ <div class="typing-wrap">
619
+ <div class="typing-box">
620
+ <div class="t-dot"></div>
621
+ <div class="t-dot"></div>
622
+ <div class="t-dot"></div>
623
+ </div>
624
+ </div>
625
+ """, unsafe_allow_html=True)
626
 
627
+ try:
628
+ start = time.time()
629
+ answer, sources = get_answer(question.strip(), st.session_state.vectorstore)
630
+ elapsed = round(time.time() - start, 1)
631
 
632
+ st.session_state.messages.append({
633
+ "role": "assistant",
634
+ "content": answer,
635
+ "sources": sources,
636
+ "time": time.strftime("%H:%M"),
637
+ "elapsed": elapsed
638
+ })
639
+ except Exception as e:
640
+ st.session_state.messages.append({
641
+ "role": "assistant",
642
+ "content": f"⚠️ Error generating answer: {str(e)}",
643
+ "sources": [], "time": time.strftime("%H:%M"), "elapsed": 0
644
+ })
645
 
646
+ typing_slot.empty()
647
+ st.rerun()