sriiram18 commited on
Commit
96a57de
·
verified ·
1 Parent(s): 100026d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +181 -231
app.py CHANGED
@@ -8,6 +8,13 @@ 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",
@@ -72,88 +79,48 @@ footer, #MainMenu { visibility: hidden; }
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;
@@ -161,52 +128,22 @@ footer, #MainMenu { visibility: hidden; }
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
- .answer-text {
185
- white-space: pre-wrap;
186
- word-break: break-word;
187
- line-height: 1.75;
188
- font-size: 0.95rem;
189
  }
190
 
191
  /* ── Source chunks ── */
192
- .sources-wrap {
193
- margin-top: 10px;
194
- border-top: 1px solid #21262d;
195
- padding-top: 10px;
196
- }
197
  .source-label {
198
  font-family: 'Share Tech Mono', monospace;
199
- font-size: 0.62rem; color: #8b949e;
200
- letter-spacing: 2px; margin-bottom: 6px;
201
  }
202
  .source-chip {
203
  display: inline-block;
204
- background: rgba(88,166,255,0.08);
205
- border: 1px solid rgba(88,166,255,0.2);
206
- color: #58a6ff; padding: 3px 10px;
207
- border-radius: 4px; font-size: 0.72rem;
208
- font-family: 'Share Tech Mono', monospace;
209
- margin: 3px 3px; cursor: pointer;
210
  }
211
 
212
  /* ── Typing indicator ── */
@@ -214,33 +151,53 @@ footer, #MainMenu { visibility: hidden; }
214
  .typing-box {
215
  display: flex; align-items: center; gap: 5px;
216
  padding: 14px 18px; background: #161b22;
217
- border: 1px solid #30363d;
218
- border-radius: 18px 18px 18px 4px;
219
  }
220
  .t-dot {
221
  width: 7px; height: 7px; background: #58a6ff;
222
- border-radius: 50%;
223
- animation: tdot 1.2s ease-in-out infinite;
224
  }
225
  .t-dot:nth-child(2){animation-delay:0.2s;}
226
  .t-dot:nth-child(3){animation-delay:0.4s;}
227
  @keyframes tdot{0%,60%,100%{transform:translateY(0);opacity:0.4;}30%{transform:translateY(-8px);opacity:1;}}
228
 
229
- /* ── Input ── */
230
- .stTextInput > div > div > input {
231
  background: #161b22 !important;
232
  border: 1px solid #30363d !important;
233
- border-radius: 8px !important;
 
234
  color: #c9d1d9 !important;
235
  font-family: 'Rajdhani', sans-serif !important;
236
  font-size: 0.97rem !important;
 
 
237
  }
238
- .stTextInput > div > div > input:focus {
239
  border-color: #58a6ff !important;
240
- box-shadow: 0 0 12px rgba(88,166,255,0.15) !important;
241
- }
242
-
243
- /* ── Buttons ── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  .stButton > button {
245
  background: #21262d !important; color: #c9d1d9 !important;
246
  border: 1px solid #30363d !important; border-radius: 8px !important;
@@ -249,72 +206,30 @@ footer, #MainMenu { visibility: hidden; }
249
  transition: all 0.25s !important;
250
  }
251
  .stButton > button:hover {
252
- background: #30363d !important;
253
- border-color: #58a6ff !important; color: #58a6ff !important;
254
- }
255
- .send-btn > button {
256
- background: linear-gradient(135deg,#1f6feb,#388bfd) !important;
257
- color: #fff !important; border: none !important;
258
- box-shadow: 0 0 16px rgba(31,111,235,0.3) !important;
259
- }
260
- .send-btn > button:hover {
261
- box-shadow: 0 0 28px rgba(31,111,235,0.55) !important;
262
- transform: translateY(-2px) !important; color: #fff !important;
263
  }
264
 
265
- /* ── Stats ── */
266
- .stat-row {
267
- display: flex; gap: 10px; flex-wrap: wrap;
268
- margin-bottom: 14px;
269
- }
270
- .stat-box {
271
- flex: 1; min-width: 80px;
272
- background: #161b22; border: 1px solid #21262d;
273
- border-top: 2px solid #58a6ff; border-radius: 6px;
274
- padding: 10px 12px; text-align: center;
275
- }
276
- .stat-num {
277
- font-family: 'Orbitron', monospace;
278
- font-size: 1.2rem; font-weight: 900; color: #58a6ff;
279
- }
280
- .stat-lbl {
281
- font-family: 'Share Tech Mono', monospace;
282
- font-size: 0.6rem; color: #8b949e;
283
- letter-spacing: 1px; margin-top: 3px;
284
- }
285
-
286
- /* ── Welcome ── */
287
  .welcome-card {
288
  background: #161b22; border: 1px solid #21262d;
289
- border-radius: 12px; padding: 30px;
290
- text-align: center; margin: 10px 0;
291
  animation: fadeInUp 0.6s ease both;
292
  }
293
  .wc-icon { font-size: 2.8rem; margin-bottom: 12px; }
294
- .wc-title {
295
- font-family: 'Orbitron', monospace;
296
- font-size: 1rem; color: #fff; margin-bottom: 8px;
297
- }
298
  .wc-sub { font-size: 0.9rem; color: #8b949e; line-height: 1.65; }
299
  .tip-chip {
300
  display: inline-block;
301
- background: rgba(88,166,255,0.08);
302
- border: 1px solid rgba(88,166,255,0.25);
303
- color: #58a6ff; padding: 4px 12px;
304
- border-radius: 20px; margin: 4px 3px;
305
- font-family: 'Share Tech Mono', monospace;
306
- font-size: 0.67rem;
307
  }
308
 
309
  /* ── File uploader ── */
310
  [data-testid="stFileUploader"] {
311
- background: #161b22 !important;
312
- border: 2px dashed #30363d !important;
313
- border-radius: 10px !important;
314
- }
315
- [data-testid="stFileUploader"]:hover {
316
- border-color: #58a6ff !important;
317
  }
 
318
 
319
  /* ── Glow divider ── */
320
  .glow-div {
@@ -323,6 +238,26 @@ footer, #MainMenu { visibility: hidden; }
323
  margin: 16px 0; box-shadow: 0 0 8px rgba(88,166,255,0.3);
324
  }
325
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  /* ── Animations ── */
327
  @keyframes fadeInDown { from{opacity:0;transform:translateY(-16px)} to{opacity:1;transform:translateY(0)} }
328
  @keyframes fadeInUp { from{opacity:0;transform:translateY(16px)} to{opacity:1;transform:translateY(0)} }
@@ -337,20 +272,20 @@ footer, #MainMenu { visibility: hidden; }
337
  """, unsafe_allow_html=True)
338
 
339
  # ─── SESSION STATE ────────────────────────────────────────────────────────────
340
- if "messages" not in st.session_state: st.session_state.messages = []
341
- if "vectorstore" not in st.session_state: st.session_state.vectorstore = None
342
- if "pdf_name" not in st.session_state: st.session_state.pdf_name = None
343
- if "pdf_pages" not in st.session_state: st.session_state.pdf_pages = 0
344
- if "pdf_chunks" not in st.session_state: st.session_state.pdf_chunks = 0
345
- if "q_count" not in st.session_state: st.session_state.q_count = 0
346
- if "last_q" not in st.session_state: st.session_state.last_q = ""
 
 
347
 
348
  # ─── MODEL LOADERS ────────────────────────────────────────────────────────────
349
  @st.cache_resource(show_spinner=False)
350
  def load_embeddings():
351
- return HuggingFaceEmbeddings(
352
- model_name="sentence-transformers/all-MiniLM-L6-v2"
353
- )
354
 
355
  @st.cache_resource(show_spinner=False)
356
  def load_llm():
@@ -383,8 +318,8 @@ def process_pdf(uploaded_file):
383
  if not raw_text.strip():
384
  raise ValueError("No readable text found. PDF may be scanned/image-based.")
385
 
386
- splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
387
- chunks = splitter.split_text(raw_text)
388
  embeddings = load_embeddings()
389
  vectorstore = FAISS.from_texts(chunks, embeddings)
390
  return vectorstore, len(reader.pages), len(chunks)
@@ -413,7 +348,7 @@ QUESTION:
413
  chain = prompt_template | llm
414
  result = chain.invoke({"context": context, "question": question})
415
 
416
- # extract only the assistant reply
417
  if "<|assistant|>" in result:
418
  answer = result.split("<|assistant|>")[-1].strip()
419
  else:
@@ -441,13 +376,9 @@ with st.sidebar:
441
  </div>
442
  """, unsafe_allow_html=True)
443
 
444
- # PDF upload
445
  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)
446
 
447
- uploaded_file = st.file_uploader(
448
- "Upload PDF", type=["pdf"],
449
- label_visibility="collapsed"
450
- )
451
 
452
  if uploaded_file:
453
  if st.session_state.pdf_name != uploaded_file.name:
@@ -466,7 +397,6 @@ with st.sidebar:
466
 
467
  st.markdown("---")
468
 
469
- # Stats
470
  if st.session_state.pdf_name:
471
  st.markdown(f"""
472
  <div style="font-family:'Share Tech Mono',monospace;font-size:0.7rem;color:#8b949e;margin-bottom:10px;letter-spacing:2px;">📊 DOCUMENT STATS</div>
@@ -492,13 +422,11 @@ with st.sidebar:
492
  """, unsafe_allow_html=True)
493
  st.markdown("---")
494
 
495
- # Clear button
496
  if st.button("🗑️ CLEAR CHAT", use_container_width=True):
497
  st.session_state.messages = []
498
  st.session_state.q_count = 0
499
  st.rerun()
500
 
501
- # New PDF button
502
  if st.button("📄 LOAD NEW PDF", use_container_width=True):
503
  st.session_state.vectorstore = None
504
  st.session_state.pdf_name = None
@@ -509,23 +437,22 @@ with st.sidebar:
509
  st.rerun()
510
 
511
  # ─── MAIN AREA ────────────────────────────────────────────────────────────────
512
- # Header
513
  st.markdown("""
514
  <div class="app-header">
515
  <div>
516
  <div class="app-title">QUERY<span>DOCS</span> AI 📚</div>
517
- <div class="app-sub">INTELLIGENT DOCUMENT Q&amp;A · RAG PIPELINE </div>
518
  </div>
519
  </div>
520
  """, unsafe_allow_html=True)
521
 
522
- # PDF banner (when loaded)
523
  if st.session_state.pdf_name:
 
524
  st.markdown(f"""
525
  <div class="pdf-banner">
526
  <span style="font-size:1.4rem;">📄</span>
527
  <div>
528
- <div class="pdf-name">{st.session_state.pdf_name}</div>
529
  <div class="pdf-meta">{st.session_state.pdf_pages} pages · {st.session_state.pdf_chunks} chunks · ready to query</div>
530
  </div>
531
  <span style="margin-left:auto;background:rgba(88,166,255,0.1);border:1px solid rgba(88,166,255,0.3);
@@ -534,7 +461,6 @@ if st.session_state.pdf_name:
534
  </div>
535
  """, unsafe_allow_html=True)
536
 
537
- # Chat area
538
  if not st.session_state.vectorstore:
539
  st.markdown("""
540
  <div class="welcome-card">
@@ -542,7 +468,7 @@ if not st.session_state.vectorstore:
542
  <div class="wc-title">WELCOME TO QUERYDOCS AI</div>
543
  <div class="wc-sub">
544
  Upload any PDF document from the sidebar and start asking questions.<br>
545
- Powered by RAG pipeline, context-aware answers.
546
  </div>
547
  <br>
548
  <span class="tip-chip">📋 Legal documents</span>
@@ -553,48 +479,57 @@ if not st.session_state.vectorstore:
553
  """, unsafe_allow_html=True)
554
 
555
  else:
556
- # Chat history
557
  if st.session_state.messages:
558
- chat_html = '<div class="chat-wrap">'
559
  for msg in st.session_state.messages:
560
  ts = msg.get("time", "")
561
  if msg["role"] == "user":
562
- import html as html_module
563
- safe_user = html_module.escape(msg["content"])
564
- chat_html += f"""
565
  <div class="msg-user">
566
  <div>
567
  <div class="bubble-user">{safe_user}</div>
568
  <div class="msg-meta" style="text-align:right;">YOU · {ts}</div>
569
  </div>
570
- </div>"""
571
  else:
572
- sources_html = ""
573
- if msg.get("sources"):
574
- chips = "".join(f'<span class="source-chip">📎 Chunk {i+1}</span>'
575
- for i, _ in enumerate(msg["sources"]))
576
- sources_html = f"""<div class="sources-wrap">
577
- <div class="source-label">// SOURCE CHUNKS USED</div>
578
- {chips}
579
- </div>"""
580
-
581
- # Escape answer to prevent HTML injection from LLM output
582
- import html as html_module
583
- safe_answer = html_module.escape(msg["content"]).replace("\n", "<br>")
584
-
585
- chat_html += f"""
586
- <div class="msg-ai">
587
- <div>
588
- <div class="bubble-ai">
589
- <div class="answer-label">// QUERYDOCS RESPONSE</div>
590
- <div class="answer-text">{safe_answer}</div>
591
- {sources_html}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
  </div>
593
- <div class="msg-meta">📚 QUERYDOCS AI · {ts} · {msg.get("elapsed","?")}s</div>
594
- </div>
595
- </div>"""
596
- chat_html += '</div>'
597
- st.markdown(chat_html, unsafe_allow_html=True)
598
  else:
599
  st.markdown("""
600
  <div class="welcome-card" style="padding:20px;">
@@ -608,30 +543,44 @@ else:
608
  </div>
609
  """, unsafe_allow_html=True)
610
 
611
- # Input row
 
 
612
  st.markdown('<hr class="glow-div">', unsafe_allow_html=True)
613
- col_q, col_btn = st.columns((5, 1))
 
 
 
614
 
 
615
  with col_q:
616
  question = st.text_input(
617
  "", placeholder="ask a question about your document...",
618
- label_visibility="collapsed", key="question_input"
 
 
619
  )
 
 
620
  with col_btn:
621
- st.markdown('<div class="send-btn">', unsafe_allow_html=True)
622
  ask_btn = st.button("▶ ASK", use_container_width=True)
623
- st.markdown('</div>', unsafe_allow_html=True)
624
 
625
- # Generate answer guard against infinite loop
626
- if (ask_btn or question) and question.strip() and question.strip() != st.session_state.last_q:
627
- ts = time.strftime("%H:%M")
 
 
 
 
 
 
 
 
628
  st.session_state.messages.append({
629
- "role": "user", "content": question.strip(), "time": ts
630
  })
631
  st.session_state.q_count += 1
632
 
633
- # typing indicator
634
- typing_slot = st.empty()
635
  typing_slot.markdown("""
636
  <div class="typing-wrap">
637
  <div class="typing-box">
@@ -643,24 +592,25 @@ else:
643
  """, unsafe_allow_html=True)
644
 
645
  try:
646
- start = time.time()
647
- answer, sources = get_answer(question.strip(), st.session_state.vectorstore)
648
- elapsed = round(time.time() - start, 1)
649
 
650
  st.session_state.messages.append({
651
  "role": "assistant",
652
  "content": answer,
653
  "sources": sources,
654
- "time": time.strftime("%H:%M"),
655
  "elapsed": elapsed
656
  })
657
  except Exception as e:
658
  st.session_state.messages.append({
659
- "role": "assistant",
660
  "content": f"⚠️ Error generating answer: {str(e)}",
661
- "sources": [], "time": time.strftime("%H:%M"), "elapsed": 0
 
 
662
  })
663
 
664
  typing_slot.empty()
665
- st.session_state.last_q = question.strip()
666
  st.rerun()
 
8
  import torch
9
  import time
10
  import base64
11
+ import html as html_module
12
+ from datetime import datetime, timezone, timedelta
13
+
14
+ # ─── TIMEZONE (IST = UTC+5:30) ────────────────────────────────────────────────
15
+ IST = timezone(timedelta(hours=5, minutes=30))
16
+ def get_ist_time():
17
+ return datetime.now(IST).strftime("%H:%M")
18
 
19
  st.set_page_config(
20
  page_title="QueryDocs AI",
 
79
  }
80
  .profile-avatar {
81
  width: 72px; height: 72px;
82
+ border-radius: 50%; overflow: hidden;
 
83
  border: 2px solid #58a6ff;
84
+ box-shadow: 0 0 0 3px rgba(88,166,255,0.15), 0 0 20px rgba(88,166,255,0.2);
 
85
  margin: 0 auto 10px;
86
  animation: pulse-av 2.5s ease-in-out infinite;
87
  }
88
+ .profile-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
 
 
 
89
  @keyframes pulse-av {
90
  0%,100%{box-shadow:0 0 0 3px rgba(88,166,255,0.15),0 0 20px rgba(88,166,255,0.2);}
91
  50% {box-shadow:0 0 0 4px rgba(88,166,255,0.3),0 0 30px rgba(88,166,255,0.35);}
92
  }
93
  .profile-name {
94
  font-family: 'Orbitron', monospace;
95
+ font-size: 0.82rem; font-weight: 700; color: #fff; letter-spacing: 1px;
 
96
  }
97
  .profile-role {
98
  font-family: 'Share Tech Mono', monospace;
99
+ font-size: 0.64rem; color: #58a6ff; letter-spacing: 2px; margin-top: 3px;
 
 
 
 
 
100
  }
101
+ .profile-links { display: flex; justify-content: center; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
102
  .p-link {
103
  font-family: 'Share Tech Mono', monospace;
104
  font-size: 0.62rem; color: #8b949e !important;
105
  text-decoration: none !important;
106
+ background: #0d1117; border: 1px solid #30363d;
107
+ padding: 3px 10px; border-radius: 20px; transition: all 0.25s;
 
 
108
  }
109
  .p-link:hover { color: #58a6ff !important; border-color: #58a6ff; }
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  /* ── PDF info banner ── */
112
  .pdf-banner {
113
+ background: #161b22; border: 1px solid #21262d;
114
+ border-left: 3px solid #58a6ff; border-radius: 8px;
115
+ padding: 12px 18px; display: flex; align-items: center; gap: 12px;
116
+ margin-bottom: 16px; animation: fadeInUp 0.4s ease both;
 
 
 
 
 
 
 
 
 
 
 
117
  }
118
+ .pdf-name { font-weight: 700; font-size: 0.95rem; color: #fff; }
119
+ .pdf-meta { font-family: 'Share Tech Mono', monospace; font-size: 0.68rem; color: #8b949e; margin-top: 2px; }
120
 
121
  /* ── Chat bubbles ── */
122
+ .msg-user { display: flex; justify-content: flex-end; animation: slideInR 0.3s ease both; }
123
+ .msg-ai { display: flex; justify-content: flex-start; animation: slideInL 0.3s ease both; }
 
124
  .bubble-user {
125
  background: linear-gradient(135deg, #1f6feb, #388bfd);
126
  color: #fff; padding: 12px 18px;
 
128
  max-width: 75%; font-size: 0.97rem; line-height: 1.6;
129
  box-shadow: 0 4px 15px rgba(31,111,235,0.25);
130
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  .msg-meta {
132
  font-family: 'Share Tech Mono', monospace;
133
+ font-size: 0.62rem; color: #484f58; margin-top: 4px; padding: 0 6px;
 
 
 
 
 
 
 
 
134
  }
135
 
136
  /* ── Source chunks ── */
137
+ .sources-wrap { margin-top: 10px; border-top: 1px solid #21262d; padding-top: 10px; }
 
 
 
 
138
  .source-label {
139
  font-family: 'Share Tech Mono', monospace;
140
+ font-size: 0.62rem; color: #8b949e; letter-spacing: 2px; margin-bottom: 6px;
 
141
  }
142
  .source-chip {
143
  display: inline-block;
144
+ background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.2);
145
+ color: #58a6ff; padding: 3px 10px; border-radius: 4px;
146
+ font-size: 0.72rem; font-family: 'Share Tech Mono', monospace; margin: 3px 3px;
 
 
 
147
  }
148
 
149
  /* ── Typing indicator ── */
 
151
  .typing-box {
152
  display: flex; align-items: center; gap: 5px;
153
  padding: 14px 18px; background: #161b22;
154
+ border: 1px solid #30363d; border-radius: 18px 18px 18px 4px;
 
155
  }
156
  .t-dot {
157
  width: 7px; height: 7px; background: #58a6ff;
158
+ border-radius: 50%; animation: tdot 1.2s ease-in-out infinite;
 
159
  }
160
  .t-dot:nth-child(2){animation-delay:0.2s;}
161
  .t-dot:nth-child(3){animation-delay:0.4s;}
162
  @keyframes tdot{0%,60%,100%{transform:translateY(0);opacity:0.4;}30%{transform:translateY(-8px);opacity:1;}}
163
 
164
+ /* ── Input — merged input+button bar (scoped to main area) ── */
165
+ [data-testid="stAppViewContainer"] div[data-testid="column"]:first-child .stTextInput > div > div > input {
166
  background: #161b22 !important;
167
  border: 1px solid #30363d !important;
168
+ border-right: none !important;
169
+ border-radius: 12px 0 0 12px !important;
170
  color: #c9d1d9 !important;
171
  font-family: 'Rajdhani', sans-serif !important;
172
  font-size: 0.97rem !important;
173
+ height: 48px !important;
174
+ padding: 12px 18px !important;
175
  }
176
+ [data-testid="stAppViewContainer"] div[data-testid="column"]:first-child .stTextInput > div > div > input:focus {
177
  border-color: #58a6ff !important;
178
+ box-shadow: none !important;
179
+ outline: none !important;
180
+ }
181
+ [data-testid="stAppViewContainer"] div[data-testid="column"]:last-child .stButton > button {
182
+ background: linear-gradient(135deg, #1f6feb, #388bfd) !important;
183
+ color: #fff !important;
184
+ border: 1px solid #1f6feb !important;
185
+ border-left: none !important;
186
+ border-radius: 0 12px 12px 0 !important;
187
+ height: 48px !important; width: 100% !important;
188
+ font-size: 1rem !important; letter-spacing: 1px !important;
189
+ box-shadow: 0 0 14px rgba(31,111,235,0.3) !important;
190
+ transition: all 0.2s ease !important;
191
+ margin-top: 0 !important; padding: 0 !important;
192
+ }
193
+ [data-testid="stAppViewContainer"] div[data-testid="column"]:last-child .stButton > button:hover {
194
+ box-shadow: 0 0 24px rgba(31,111,235,0.55) !important;
195
+ color: #fff !important;
196
+ }
197
+ [data-testid="stAppViewContainer"] div[data-testid="column"]:first-child { padding-right: 0 !important; }
198
+ [data-testid="stAppViewContainer"] div[data-testid="column"]:last-child { padding-left: 0 !important; }
199
+
200
+ /* ── Buttons (sidebar/general) ── */
201
  .stButton > button {
202
  background: #21262d !important; color: #c9d1d9 !important;
203
  border: 1px solid #30363d !important; border-radius: 8px !important;
 
206
  transition: all 0.25s !important;
207
  }
208
  .stButton > button:hover {
209
+ background: #30363d !important; border-color: #58a6ff !important; color: #58a6ff !important;
 
 
 
 
 
 
 
 
 
 
210
  }
211
 
212
+ /* ── Welcome card ── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  .welcome-card {
214
  background: #161b22; border: 1px solid #21262d;
215
+ border-radius: 12px; padding: 30px; text-align: center; margin: 10px 0;
 
216
  animation: fadeInUp 0.6s ease both;
217
  }
218
  .wc-icon { font-size: 2.8rem; margin-bottom: 12px; }
219
+ .wc-title { font-family: 'Orbitron', monospace; font-size: 1rem; color: #fff; margin-bottom: 8px; }
 
 
 
220
  .wc-sub { font-size: 0.9rem; color: #8b949e; line-height: 1.65; }
221
  .tip-chip {
222
  display: inline-block;
223
+ background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.25);
224
+ color: #58a6ff; padding: 4px 12px; border-radius: 20px; margin: 4px 3px;
225
+ font-family: 'Share Tech Mono', monospace; font-size: 0.67rem;
 
 
 
226
  }
227
 
228
  /* ── File uploader ── */
229
  [data-testid="stFileUploader"] {
230
+ background: #161b22 !important; border: 2px dashed #30363d !important; border-radius: 10px !important;
 
 
 
 
 
231
  }
232
+ [data-testid="stFileUploader"]:hover { border-color: #58a6ff !important; }
233
 
234
  /* ── Glow divider ── */
235
  .glow-div {
 
238
  margin: 16px 0; box-shadow: 0 0 8px rgba(88,166,255,0.3);
239
  }
240
 
241
+ /* ── AI bubble native markdown styling ── */
242
+ [data-testid="stAppViewContainer"] [data-testid="column"] p { color: #c9d1d9; font-size: 0.95rem; line-height: 1.75; margin: 4px 0; }
243
+ [data-testid="stAppViewContainer"] [data-testid="column"] h1,
244
+ [data-testid="stAppViewContainer"] [data-testid="column"] h2,
245
+ [data-testid="stAppViewContainer"] [data-testid="column"] h3 { color: #fff; margin: 10px 0 4px; }
246
+ [data-testid="stAppViewContainer"] [data-testid="column"] ul,
247
+ [data-testid="stAppViewContainer"] [data-testid="column"] ol { color: #c9d1d9; padding-left: 20px; }
248
+ [data-testid="stAppViewContainer"] [data-testid="column"] code { background: #0d1117; color: #58a6ff; padding: 2px 6px; border-radius: 4px; font-size: 0.85rem; }
249
+ [data-testid="stAppViewContainer"] [data-testid="column"] pre { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 12px; }
250
+ [data-testid="stAppViewContainer"] [data-testid="column"] strong { color: #fff; }
251
+
252
+ /* ── Stats ── */
253
+ .stat-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
254
+ .stat-box {
255
+ flex: 1; min-width: 80px; background: #161b22; border: 1px solid #21262d;
256
+ border-top: 2px solid #58a6ff; border-radius: 6px; padding: 10px 12px; text-align: center;
257
+ }
258
+ .stat-num { font-family: 'Orbitron', monospace; font-size: 1.2rem; font-weight: 900; color: #58a6ff; }
259
+ .stat-lbl { font-family: 'Share Tech Mono', monospace; font-size: 0.6rem; color: #8b949e; letter-spacing: 1px; margin-top: 3px; }
260
+
261
  /* ── Animations ── */
262
  @keyframes fadeInDown { from{opacity:0;transform:translateY(-16px)} to{opacity:1;transform:translateY(0)} }
263
  @keyframes fadeInUp { from{opacity:0;transform:translateY(16px)} to{opacity:1;transform:translateY(0)} }
 
272
  """, unsafe_allow_html=True)
273
 
274
  # ─── SESSION STATE ────────────────────────────────────────────────────────────
275
+ if "messages" not in st.session_state: st.session_state.messages = []
276
+ if "vectorstore" not in st.session_state: st.session_state.vectorstore = None
277
+ if "pdf_name" not in st.session_state: st.session_state.pdf_name = None
278
+ if "pdf_pages" not in st.session_state: st.session_state.pdf_pages = 0
279
+ if "pdf_chunks" not in st.session_state: st.session_state.pdf_chunks = 0
280
+ if "q_count" not in st.session_state: st.session_state.q_count = 0
281
+ if "last_q" not in st.session_state: st.session_state.last_q = ""
282
+ if "input_key" not in st.session_state: st.session_state.input_key = 0
283
+ if "pending_input" not in st.session_state: st.session_state.pending_input = ""
284
 
285
  # ─── MODEL LOADERS ────────────────────────────────────────────────────────────
286
  @st.cache_resource(show_spinner=False)
287
  def load_embeddings():
288
+ return HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
 
 
289
 
290
  @st.cache_resource(show_spinner=False)
291
  def load_llm():
 
318
  if not raw_text.strip():
319
  raise ValueError("No readable text found. PDF may be scanned/image-based.")
320
 
321
+ splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
322
+ chunks = splitter.split_text(raw_text)
323
  embeddings = load_embeddings()
324
  vectorstore = FAISS.from_texts(chunks, embeddings)
325
  return vectorstore, len(reader.pages), len(chunks)
 
348
  chain = prompt_template | llm
349
  result = chain.invoke({"context": context, "question": question})
350
 
351
+
352
  if "<|assistant|>" in result:
353
  answer = result.split("<|assistant|>")[-1].strip()
354
  else:
 
376
  </div>
377
  """, unsafe_allow_html=True)
378
 
 
379
  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)
380
 
381
+ uploaded_file = st.file_uploader("Upload PDF", type=["pdf"], label_visibility="collapsed")
 
 
 
382
 
383
  if uploaded_file:
384
  if st.session_state.pdf_name != uploaded_file.name:
 
397
 
398
  st.markdown("---")
399
 
 
400
  if st.session_state.pdf_name:
401
  st.markdown(f"""
402
  <div style="font-family:'Share Tech Mono',monospace;font-size:0.7rem;color:#8b949e;margin-bottom:10px;letter-spacing:2px;">📊 DOCUMENT STATS</div>
 
422
  """, unsafe_allow_html=True)
423
  st.markdown("---")
424
 
 
425
  if st.button("🗑️ CLEAR CHAT", use_container_width=True):
426
  st.session_state.messages = []
427
  st.session_state.q_count = 0
428
  st.rerun()
429
 
 
430
  if st.button("📄 LOAD NEW PDF", use_container_width=True):
431
  st.session_state.vectorstore = None
432
  st.session_state.pdf_name = None
 
437
  st.rerun()
438
 
439
  # ─── MAIN AREA ────────────────────────────────────────────────────────────────
 
440
  st.markdown("""
441
  <div class="app-header">
442
  <div>
443
  <div class="app-title">QUERY<span>DOCS</span> AI 📚</div>
444
+ <div class="app-sub">INTELLIGENT DOCUMENT Q&amp;A · RAG PIPELINE</div>
445
  </div>
446
  </div>
447
  """, unsafe_allow_html=True)
448
 
 
449
  if st.session_state.pdf_name:
450
+ safe_pdf_name = html_module.escape(st.session_state.pdf_name) # FIX: XSS
451
  st.markdown(f"""
452
  <div class="pdf-banner">
453
  <span style="font-size:1.4rem;">📄</span>
454
  <div>
455
+ <div class="pdf-name">{safe_pdf_name}</div>
456
  <div class="pdf-meta">{st.session_state.pdf_pages} pages · {st.session_state.pdf_chunks} chunks · ready to query</div>
457
  </div>
458
  <span style="margin-left:auto;background:rgba(88,166,255,0.1);border:1px solid rgba(88,166,255,0.3);
 
461
  </div>
462
  """, unsafe_allow_html=True)
463
 
 
464
  if not st.session_state.vectorstore:
465
  st.markdown("""
466
  <div class="welcome-card">
 
468
  <div class="wc-title">WELCOME TO QUERYDOCS AI</div>
469
  <div class="wc-sub">
470
  Upload any PDF document from the sidebar and start asking questions.<br>
471
+ Powered by RAG pipeline context-aware answers.
472
  </div>
473
  <br>
474
  <span class="tip-chip">📋 Legal documents</span>
 
479
  """, unsafe_allow_html=True)
480
 
481
  else:
482
+ # ── Chat history ──
483
  if st.session_state.messages:
 
484
  for msg in st.session_state.messages:
485
  ts = msg.get("time", "")
486
  if msg["role"] == "user":
487
+ safe_user = html_module.escape(msg["content"]) # FIX: XSS
488
+ st.markdown(f"""
 
489
  <div class="msg-user">
490
  <div>
491
  <div class="bubble-user">{safe_user}</div>
492
  <div class="msg-meta" style="text-align:right;">YOU · {ts}</div>
493
  </div>
494
+ </div>""", unsafe_allow_html=True)
495
  else:
496
+
497
+ col_ai, col_space = st.columns([4, 1])
498
+ with col_ai:
499
+
500
+ st.markdown(f"""
501
+ <div style="background:#161b22;color:#c9d1d9;padding:14px 18px 6px 18px;
502
+ border-radius:18px 18px 0 4px;font-size:0.95rem;line-height:1.75;
503
+ border:1px solid #30363d;border-bottom:none;
504
+ box-shadow:0 4px 15px rgba(0,0,0,0.3);">
505
+ <div style="font-family:'Share Tech Mono',monospace;font-size:0.65rem;
506
+ color:#58a6ff;letter-spacing:2px;margin-bottom:6px;">// QUERYDOCS RESPONSE</div>
507
+ </div>""", unsafe_allow_html=True)
508
+
509
+ 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)
510
+ st.markdown(msg["content"])
511
+ st.markdown('</div>', unsafe_allow_html=True)
512
+ # Source chips
513
+ if msg.get("sources"):
514
+ chips = "".join(f'<span class="source-chip">📎 Chunk {i+1}</span>'
515
+ for i, _ in enumerate(msg["sources"]))
516
+ st.markdown(f"""
517
+ <div style="background:#161b22;padding:6px 18px 8px 18px;
518
+ border-left:1px solid #30363d;border-right:1px solid #30363d;">
519
+ <div class="sources-wrap">
520
+ <div class="source-label">// SOURCE CHUNKS USED</div>
521
+ {chips}
522
+ </div>
523
+ </div>""", unsafe_allow_html=True)
524
+ # Bubble footer
525
+ st.markdown(f"""
526
+ <div style="background:#161b22;padding:6px 18px 12px 18px;
527
+ border-radius:0 0 18px 4px;border:1px solid #30363d;border-top:none;
528
+ box-shadow:0 4px 15px rgba(0,0,0,0.3);margin-bottom:4px;">
529
+ <div style="font-family:'Share Tech Mono',monospace;font-size:0.62rem;color:#484f58;">
530
+ 📚 QUERYDOCS AI · {ts} · {msg.get("elapsed","?")}s
531
  </div>
532
+ </div>""", unsafe_allow_html=True)
 
 
 
 
533
  else:
534
  st.markdown("""
535
  <div class="welcome-card" style="padding:20px;">
 
543
  </div>
544
  """, unsafe_allow_html=True)
545
 
546
+ typing_slot = st.empty()
547
+
548
+ # ── Input row — merged input+button ──
549
  st.markdown('<hr class="glow-div">', unsafe_allow_html=True)
550
+ _current_key = f"question_input_{st.session_state.input_key}"
551
+
552
+ def _sync_pending():
553
+ st.session_state.pending_input = st.session_state.get(_current_key, "")
554
 
555
+ col_q, col_btn = st.columns([6, 0.7])
556
  with col_q:
557
  question = st.text_input(
558
  "", placeholder="ask a question about your document...",
559
+ label_visibility="collapsed",
560
+ key=_current_key,
561
+ on_change=_sync_pending
562
  )
563
+ if question:
564
+ st.session_state.pending_input = question
565
  with col_btn:
 
566
  ask_btn = st.button("▶ ASK", use_container_width=True)
 
567
 
568
+ trigger_q = (st.session_state.get("pending_input", "") or question).strip()
569
+
570
+ # ── Generate answer ──
571
+ if trigger_q and trigger_q != st.session_state.last_q:
572
+
573
+ # FIX: set last_q and clear input IMMEDIATELY to prevent double-trigger
574
+ st.session_state.last_q = trigger_q
575
+ st.session_state.input_key += 1
576
+ st.session_state.pending_input = ""
577
+
578
+ ts = get_ist_time() # FIX: IST time instead of UTC server time
579
  st.session_state.messages.append({
580
+ "role": "user", "content": trigger_q, "time": ts
581
  })
582
  st.session_state.q_count += 1
583
 
 
 
584
  typing_slot.markdown("""
585
  <div class="typing-wrap">
586
  <div class="typing-box">
 
592
  """, unsafe_allow_html=True)
593
 
594
  try:
595
+ start = time.time()
596
+ answer, sources = get_answer(trigger_q, st.session_state.vectorstore)
597
+ elapsed = round(time.time() - start, 1)
598
 
599
  st.session_state.messages.append({
600
  "role": "assistant",
601
  "content": answer,
602
  "sources": sources,
603
+ "time": get_ist_time(), # FIX: IST time
604
  "elapsed": elapsed
605
  })
606
  except Exception as e:
607
  st.session_state.messages.append({
608
+ "role": "assistant",
609
  "content": f"⚠️ Error generating answer: {str(e)}",
610
+ "sources": [],
611
+ "time": get_ist_time(),
612
+ "elapsed": 0
613
  })
614
 
615
  typing_slot.empty()
 
616
  st.rerun()