Update app.py
Browse files
app.py
CHANGED
|
@@ -1,23 +1,29 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import torch
|
| 4 |
import time
|
| 5 |
-
|
| 6 |
-
import html as html_lib
|
| 7 |
-
|
| 8 |
-
# βββ TIMEZONE (IST = UTC+5:30) ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 9 |
-
IST = timezone(timedelta(hours=5, minutes=30))
|
| 10 |
-
|
| 11 |
-
def get_ist_time():
|
| 12 |
-
return datetime.now(IST).strftime("%H:%M")
|
| 13 |
|
| 14 |
st.set_page_config(
|
| 15 |
-
page_title="
|
| 16 |
-
page_icon="
|
| 17 |
-
layout="
|
| 18 |
initial_sidebar_state="expanded"
|
| 19 |
)
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
# βββ GLOBAL CSS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 22 |
st.markdown("""
|
| 23 |
<style>
|
|
@@ -37,241 +43,292 @@ html, body, [data-testid="stAppViewContainer"] {
|
|
| 37 |
footer, #MainMenu { visibility: hidden; }
|
| 38 |
|
| 39 |
/* ββ Header ββ */
|
| 40 |
-
.
|
| 41 |
-
|
| 42 |
-
padding:
|
| 43 |
border-bottom: 1px solid #21262d;
|
| 44 |
margin-bottom: 20px;
|
|
|
|
| 45 |
}
|
| 46 |
-
.
|
| 47 |
font-family: 'Orbitron', monospace;
|
| 48 |
-
font-size: 1.
|
| 49 |
-
|
| 50 |
-
color: #fff;
|
| 51 |
-
letter-spacing: 2px;
|
| 52 |
}
|
| 53 |
-
.
|
| 54 |
-
.
|
| 55 |
font-family: 'Share Tech Mono', monospace;
|
| 56 |
-
font-size: 0.
|
| 57 |
-
|
| 58 |
-
letter-spacing: 3px;
|
| 59 |
-
margin-top: 4px;
|
| 60 |
}
|
| 61 |
|
| 62 |
-
/* ββ
|
| 63 |
-
.
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
.bubble-user {
|
| 80 |
background: linear-gradient(135deg, #1f6feb, #388bfd);
|
| 81 |
-
color: #fff;
|
| 82 |
-
padding: 12px 18px;
|
| 83 |
border-radius: 18px 18px 4px 18px;
|
| 84 |
-
max-width:
|
| 85 |
-
font-size: 0.97rem;
|
| 86 |
-
line-height: 1.6;
|
| 87 |
box-shadow: 0 4px 15px rgba(31,111,235,0.25);
|
| 88 |
}
|
| 89 |
.bubble-ai {
|
| 90 |
-
background: #161b22;
|
| 91 |
-
|
| 92 |
-
padding: 12px 18px;
|
| 93 |
border-radius: 18px 18px 18px 4px;
|
| 94 |
-
max-width:
|
| 95 |
-
font-size: 0.97rem;
|
| 96 |
-
line-height: 1.7;
|
| 97 |
border: 1px solid #30363d;
|
| 98 |
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
| 99 |
-
position: relative;
|
| 100 |
}
|
| 101 |
-
.bubble-ai
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
background: #0d1117;
|
| 107 |
-
padding: 0 4px;
|
| 108 |
-
border-radius: 50%;
|
| 109 |
}
|
| 110 |
.msg-meta {
|
| 111 |
font-family: 'Share Tech Mono', monospace;
|
| 112 |
-
font-size: 0.62rem;
|
| 113 |
-
|
| 114 |
-
margin-top: 4px;
|
| 115 |
-
padding: 0 6px;
|
| 116 |
}
|
| 117 |
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
padding: 14px 18px;
|
| 124 |
-
background: #161b22;
|
| 125 |
-
border: 1px solid #30363d;
|
| 126 |
-
border-radius: 18px 18px 18px 4px;
|
| 127 |
-
width: fit-content;
|
| 128 |
-
animation: slideInLeft 0.3s ease both;
|
| 129 |
}
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
|
|
|
| 135 |
}
|
| 136 |
-
.
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
30% { transform: translateY(-8px); opacity:1; }
|
| 141 |
}
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
padding: 8px 12px;
|
| 149 |
-
background: #161b22;
|
| 150 |
-
border: 1px solid #21262d;
|
| 151 |
-
border-radius: 8px;
|
| 152 |
-
margin-bottom: 12px;
|
| 153 |
font-family: 'Share Tech Mono', monospace;
|
| 154 |
-
|
| 155 |
-
color: #8b949e;
|
| 156 |
}
|
| 157 |
-
.stat-item { display: flex; align-items: center; gap: 5px; }
|
| 158 |
-
.stat-val { color: #58a6ff; font-weight: 600; }
|
| 159 |
|
| 160 |
-
/* ββ
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
background: #161b22 !important;
|
| 163 |
border: 1px solid #30363d !important;
|
| 164 |
-
border-
|
| 165 |
-
border-radius: 12px 0 0 12px !important;
|
| 166 |
color: #c9d1d9 !important;
|
| 167 |
font-family: 'Rajdhani', sans-serif !important;
|
| 168 |
font-size: 0.97rem !important;
|
| 169 |
-
padding: 12px 18px !important;
|
| 170 |
-
height: 48px !important;
|
| 171 |
}
|
| 172 |
-
|
| 173 |
border-color: #58a6ff !important;
|
| 174 |
-
box-shadow:
|
| 175 |
-
|
| 176 |
-
}
|
| 177 |
-
/* Send button column */
|
| 178 |
-
div[data-testid="column"]:last-child .stButton > button {
|
| 179 |
-
background: linear-gradient(135deg, #1f6feb, #388bfd) !important;
|
| 180 |
-
color: #fff !important;
|
| 181 |
-
border: 1px solid #1f6feb !important;
|
| 182 |
-
border-left: none !important;
|
| 183 |
-
border-radius: 0 12px 12px 0 !important;
|
| 184 |
-
height: 48px !important;
|
| 185 |
-
width: 100% !important;
|
| 186 |
-
font-size: 1.1rem !important;
|
| 187 |
-
letter-spacing: 0 !important;
|
| 188 |
-
box-shadow: 0 0 14px rgba(31,111,235,0.3) !important;
|
| 189 |
-
transition: all 0.2s ease !important;
|
| 190 |
-
margin-top: 0 !important;
|
| 191 |
-
padding: 0 !important;
|
| 192 |
-
}
|
| 193 |
-
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 |
-
/* Remove gap between columns so they touch */
|
| 198 |
-
div[data-testid="column"]:first-child { padding-right: 0 !important; }
|
| 199 |
-
div[data-testid="column"]:last-child { padding-left: 0 !important; }
|
| 200 |
|
| 201 |
/* ββ Buttons ββ */
|
| 202 |
.stButton > button {
|
| 203 |
-
background: #21262d !important;
|
| 204 |
-
|
| 205 |
-
border: 1px solid #30363d !important;
|
| 206 |
-
border-radius: 8px !important;
|
| 207 |
font-family: 'Share Tech Mono', monospace !important;
|
| 208 |
-
font-size: 0.
|
| 209 |
-
|
| 210 |
-
transition: all 0.25s ease !important;
|
| 211 |
}
|
| 212 |
.stButton > button:hover {
|
| 213 |
background: #30363d !important;
|
| 214 |
-
border-color: #58a6ff !important;
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
}
|
| 217 |
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
[data-testid="stSidebar"] input {
|
| 223 |
-
background: #0d1117 !important;
|
| 224 |
-
border: 1px solid #30363d !important;
|
| 225 |
-
color: #c9d1d9 !important;
|
| 226 |
-
border-radius: 6px !important;
|
| 227 |
}
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
border: 1px solid #
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
}
|
| 233 |
|
| 234 |
-
/* ββ Welcome
|
| 235 |
.welcome-card {
|
| 236 |
-
background: #161b22;
|
| 237 |
-
border:
|
| 238 |
-
|
| 239 |
-
padding: 28px;
|
| 240 |
-
text-align: center;
|
| 241 |
-
margin: 20px 0;
|
| 242 |
animation: fadeInUp 0.6s ease both;
|
| 243 |
}
|
| 244 |
-
.
|
| 245 |
-
.
|
| 246 |
font-family: 'Orbitron', monospace;
|
| 247 |
font-size: 1rem; color: #fff; margin-bottom: 8px;
|
| 248 |
}
|
| 249 |
-
.
|
| 250 |
.tip-chip {
|
| 251 |
display: inline-block;
|
| 252 |
-
background: rgba(88,166,255,0.
|
| 253 |
-
border: 1px solid rgba(88,166,255,0.
|
| 254 |
-
color: #58a6ff;
|
| 255 |
-
|
| 256 |
font-family: 'Share Tech Mono', monospace;
|
| 257 |
-
font-size: 0.
|
| 258 |
}
|
| 259 |
|
| 260 |
-
/* ββ
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
|
|
|
| 264 |
}
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
to { opacity:1; transform: translateX(0); }
|
| 268 |
}
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
|
|
|
|
|
|
|
|
|
| 272 |
}
|
| 273 |
|
| 274 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
::-webkit-scrollbar { width: 5px; }
|
| 276 |
::-webkit-scrollbar-track { background: #0d1117; }
|
| 277 |
::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; }
|
|
@@ -279,225 +336,331 @@ div[data-testid="column"]:last-child { padding-left: 0 !important; }
|
|
| 279 |
</style>
|
| 280 |
""", unsafe_allow_html=True)
|
| 281 |
|
| 282 |
-
# βββ
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
)
|
| 291 |
-
model.eval()
|
| 292 |
-
if torch.cuda.is_available():
|
| 293 |
-
model = model.to("cuda")
|
| 294 |
-
return tokenizer, model
|
| 295 |
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
|
| 304 |
# βββ SIDEBAR ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 305 |
with st.sidebar:
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
</div>
|
| 311 |
""", unsafe_allow_html=True)
|
| 312 |
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
"",
|
| 316 |
-
value="""You, in the role of TurboChat, are an assistant designed to deliver fast, reliable, and high-quality responses while maintaining clarity and efficiency.
|
| 317 |
-
You specialize in breaking down complex information into easily understandable insights without losing accuracy or depth.
|
| 318 |
-
Your main goal and objective are to provide responses that are precise, logically structured, and immediately useful to the user.
|
| 319 |
-
Your task is to interpret user queries correctly, detect ambiguity, apply reasoning, and present the most relevant answer in the most efficient format possible.
|
| 320 |
-
To make this work as it should, you must prioritize clarity, accuracy, and usefulness over verbosity, eliminate redundant explanations, and ensure every sentence contributes meaningful value.
|
| 321 |
-
When appropriate, you organize information into structured sections, lists, or steps to improve readability and comprehension. If a question is unclear, you ask a concise clarification question before answering.
|
| 322 |
-
If multiple solutions exist, you present the most practical and high-impact options first.""",
|
| 323 |
-
height=100,
|
| 324 |
-
label_visibility="collapsed",
|
| 325 |
-
key="sys_prompt"
|
| 326 |
-
)
|
| 327 |
|
| 328 |
-
st.
|
| 329 |
-
|
|
|
|
|
|
|
| 330 |
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
|
| 337 |
st.markdown("---")
|
| 338 |
-
st.markdown("**π SESSION STATS**")
|
| 339 |
-
st.markdown(f"""
|
| 340 |
-
<div style="font-family:'Share Tech Mono',monospace;font-size:0.72rem;color:#8b949e;line-height:2;">
|
| 341 |
-
π¬ Messages <span style="color:#58a6ff;">{st.session_state.msg_count}</span><br>
|
| 342 |
-
π€ Tokens <span style="color:#58a6ff;">{st.session_state.total_tokens}</span><br>
|
| 343 |
-
β±οΈ Total time <span style="color:#58a6ff;">{st.session_state.total_time:.1f}s</span>
|
| 344 |
-
</div>
|
| 345 |
-
""", unsafe_allow_html=True)
|
| 346 |
|
| 347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
if st.button("ποΈ CLEAR CHAT", use_container_width=True):
|
| 349 |
-
st.session_state.messages
|
| 350 |
-
st.session_state.
|
| 351 |
-
st.session_state.total_time = 0.0
|
| 352 |
-
st.session_state.msg_count = 0
|
| 353 |
st.rerun()
|
| 354 |
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
|
| 361 |
-
# βββ
|
|
|
|
| 362 |
st.markdown("""
|
| 363 |
-
<div class="
|
| 364 |
-
<div
|
| 365 |
-
|
|
|
|
|
|
|
| 366 |
</div>
|
| 367 |
""", unsafe_allow_html=True)
|
| 368 |
|
| 369 |
-
#
|
| 370 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
st.markdown("""
|
| 372 |
<div class="welcome-card">
|
| 373 |
-
<div class="
|
| 374 |
-
<div class="
|
| 375 |
-
<div class="
|
| 376 |
-
|
| 377 |
-
Powered by
|
| 378 |
</div>
|
| 379 |
<br>
|
| 380 |
-
<span class="tip-chip">
|
| 381 |
-
<span class="tip-chip">
|
| 382 |
-
<span class="tip-chip">
|
|
|
|
| 383 |
</div>
|
| 384 |
""", unsafe_allow_html=True)
|
|
|
|
| 385 |
else:
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
<
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
<div>
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
#
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
"
|
| 430 |
-
"
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
<div class="typing-indicator">
|
| 439 |
-
<div class="typing-dot"></div>
|
| 440 |
-
<div class="typing-dot"></div>
|
| 441 |
-
<div class="typing-dot"></div>
|
| 442 |
</div>
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
start_time = time.time()
|
| 463 |
-
|
| 464 |
-
with torch.no_grad():
|
| 465 |
-
output = model.generate(
|
| 466 |
-
input_ids,
|
| 467 |
-
max_new_tokens=max_tokens,
|
| 468 |
-
temperature=temperature,
|
| 469 |
-
top_p=top_p,
|
| 470 |
-
do_sample=True,
|
| 471 |
-
pad_token_id=tokenizer.eos_token_id,
|
| 472 |
-
repetition_penalty=1.1,
|
| 473 |
-
)
|
| 474 |
-
|
| 475 |
-
elapsed = round(time.time() - start_time, 1)
|
| 476 |
-
new_tokens = output[0][input_ids.shape[-1]:]
|
| 477 |
-
response = tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
|
| 478 |
-
token_count = len(new_tokens)
|
| 479 |
-
|
| 480 |
-
st.session_state.total_tokens += token_count
|
| 481 |
-
st.session_state.total_time += elapsed
|
| 482 |
-
|
| 483 |
-
st.session_state.messages.append({
|
| 484 |
-
"role": "assistant",
|
| 485 |
-
"content": response,
|
| 486 |
-
"time": get_ist_time(),
|
| 487 |
-
"tokens": token_count,
|
| 488 |
-
"elapsed": elapsed
|
| 489 |
-
})
|
| 490 |
-
|
| 491 |
-
except Exception as e:
|
| 492 |
st.session_state.messages.append({
|
| 493 |
-
"role":
|
| 494 |
-
"content": f"β οΈ Error: {str(e)}",
|
| 495 |
-
"time": get_ist_time(),
|
| 496 |
-
"tokens": 0,
|
| 497 |
-
"elapsed": 0
|
| 498 |
})
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
+
from PyPDF2 import PdfReader
|
| 3 |
+
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 4 |
+
from langchain_huggingface import HuggingFaceEmbeddings, HuggingFacePipeline
|
| 5 |
+
from langchain_community.vectorstores import FAISS
|
| 6 |
+
from langchain_core.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>
|
|
|
|
| 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 |
+
.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 ββ */
|
| 213 |
+
.typing-wrap { display: flex; justify-content: flex-start; }
|
| 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;
|
|
|
|
|
|
|
| 247 |
font-family: 'Share Tech Mono', monospace !important;
|
| 248 |
+
font-size: 0.73rem !important; letter-spacing: 1px !important;
|
| 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 {
|
| 321 |
+
border: none; height: 1px;
|
| 322 |
+
background: linear-gradient(90deg,transparent,#58a6ff,transparent);
|
| 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)} }
|
| 329 |
+
@keyframes slideInR { from{opacity:0;transform:translateX(20px)} to{opacity:1;transform:translateX(0)} }
|
| 330 |
+
@keyframes slideInL { from{opacity:0;transform:translateX(-20px)} to{opacity:1;transform:translateX(0)} }
|
| 331 |
+
|
| 332 |
::-webkit-scrollbar { width: 5px; }
|
| 333 |
::-webkit-scrollbar-track { background: #0d1117; }
|
| 334 |
::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; }
|
|
|
|
| 336 |
</style>
|
| 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():
|
| 357 |
+
model_id = "TinyLlama/TinyLlama-1.1B-chat-v1.0"
|
| 358 |
+
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 359 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 360 |
+
model_id,
|
| 361 |
+
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
|
| 362 |
+
low_cpu_mem_usage=True,
|
| 363 |
+
device_map="cuda" if torch.cuda.is_available() else None
|
| 364 |
+
)
|
| 365 |
+
if not torch.cuda.is_available():
|
| 366 |
+
model = model.to("cpu")
|
| 367 |
+
pipe = pipeline(
|
| 368 |
+
"text-generation", model=model, tokenizer=tokenizer,
|
| 369 |
+
max_new_tokens=512, temperature=0.3, do_sample=True,
|
| 370 |
+
pad_token_id=tokenizer.eos_token_id, repetition_penalty=1.1
|
| 371 |
+
)
|
| 372 |
+
return HuggingFacePipeline(pipeline=pipe)
|
| 373 |
+
|
| 374 |
+
# βββ PDF PROCESSOR ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 375 |
+
def process_pdf(uploaded_file):
|
| 376 |
+
reader = PdfReader(uploaded_file)
|
| 377 |
+
raw_text = ""
|
| 378 |
+
for page in reader.pages:
|
| 379 |
+
text = page.extract_text()
|
| 380 |
+
if text:
|
| 381 |
+
raw_text += text
|
| 382 |
+
|
| 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)
|
| 391 |
+
|
| 392 |
+
# βββ ANSWER FUNCTION ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 393 |
+
def get_answer(question, vectorstore):
|
| 394 |
+
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
|
| 395 |
+
relevant_docs = retriever.invoke(question)
|
| 396 |
+
context = "\n\n".join([f"---\n{doc.page_content}" for doc in relevant_docs])
|
| 397 |
+
sources = [doc.page_content[:120] + "..." for doc in relevant_docs]
|
| 398 |
+
|
| 399 |
+
prompt_template = PromptTemplate(
|
| 400 |
+
input_variables=["context", "question"],
|
| 401 |
+
template="""<|system|>
|
| 402 |
+
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.
|
| 403 |
+
<|user|>
|
| 404 |
+
CONTEXT:
|
| 405 |
+
{context}
|
| 406 |
+
|
| 407 |
+
QUESTION:
|
| 408 |
+
{question}
|
| 409 |
+
<|assistant|>
|
| 410 |
+
"""
|
| 411 |
+
)
|
| 412 |
+
llm = load_llm()
|
| 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:
|
| 420 |
+
answer = result.strip()
|
| 421 |
+
|
| 422 |
+
return answer, sources
|
| 423 |
|
| 424 |
# βββ SIDEBAR ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 425 |
with st.sidebar:
|
| 426 |
+
# Profile card
|
| 427 |
+
img_b64 = img_to_base64("assets/NANII.png")
|
| 428 |
+
avatar = (f'<img src="data:image/png;base64,{img_b64}" alt="Sriram">'
|
| 429 |
+
if img_b64 else
|
| 430 |
+
'<div style="width:72px;height:72px;background:#1f6feb;border-radius:50%;margin:0 auto;"></div>')
|
| 431 |
+
|
| 432 |
+
st.markdown(f"""
|
| 433 |
+
<div class="profile-card">
|
| 434 |
+
<div class="profile-avatar">{avatar}</div>
|
| 435 |
+
<div class="profile-name">SRIRAM SAI</div>
|
| 436 |
+
<div class="profile-role">AI & ML ENGINEER</div>
|
| 437 |
+
<div class="profile-links">
|
| 438 |
+
<a class="p-link" href="https://github.com/sriramsai18" target="_blank">π» GitHub</a>
|
| 439 |
+
<a class="p-link" href="https://www.linkedin.com/in/sriram-sai-laggisetti/" target="_blank">πΌ LinkedIn</a>
|
| 440 |
+
</div>
|
| 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:
|
| 454 |
+
with st.spinner("π Processing PDF..."):
|
| 455 |
+
try:
|
| 456 |
+
vs, pages, chunks = process_pdf(uploaded_file)
|
| 457 |
+
st.session_state.vectorstore = vs
|
| 458 |
+
st.session_state.pdf_name = uploaded_file.name
|
| 459 |
+
st.session_state.pdf_pages = pages
|
| 460 |
+
st.session_state.pdf_chunks = chunks
|
| 461 |
+
st.session_state.messages = []
|
| 462 |
+
st.session_state.q_count = 0
|
| 463 |
+
st.success("β
PDF ready!")
|
| 464 |
+
except Exception as e:
|
| 465 |
+
st.error(f"β {str(e)}")
|
| 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>
|
| 473 |
+
<div style="display:flex;flex-direction:column;gap:6px;">
|
| 474 |
+
<div style="background:#0d1117;border:1px solid #21262d;border-radius:6px;padding:8px 12px;font-family:'Share Tech Mono',monospace;font-size:0.7rem;">
|
| 475 |
+
π <span style="color:#c9d1d9;">{st.session_state.pdf_name[:22]}{'...' if len(st.session_state.pdf_name)>22 else ''}</span>
|
| 476 |
+
</div>
|
| 477 |
+
<div style="display:flex;gap:6px;">
|
| 478 |
+
<div style="flex:1;background:#0d1117;border:1px solid #21262d;border-top:2px solid #58a6ff;border-radius:6px;padding:8px;text-align:center;">
|
| 479 |
+
<div style="font-family:'Orbitron',monospace;font-size:1rem;color:#58a6ff;">{st.session_state.pdf_pages}</div>
|
| 480 |
+
<div style="font-family:'Share Tech Mono',monospace;font-size:0.58rem;color:#8b949e;">PAGES</div>
|
| 481 |
+
</div>
|
| 482 |
+
<div style="flex:1;background:#0d1117;border:1px solid #21262d;border-top:2px solid #58a6ff;border-radius:6px;padding:8px;text-align:center;">
|
| 483 |
+
<div style="font-family:'Orbitron',monospace;font-size:1rem;color:#58a6ff;">{st.session_state.pdf_chunks}</div>
|
| 484 |
+
<div style="font-family:'Share Tech Mono',monospace;font-size:0.58rem;color:#8b949e;">CHUNKS</div>
|
| 485 |
+
</div>
|
| 486 |
+
<div style="flex:1;background:#0d1117;border:1px solid #21262d;border-top:2px solid #58a6ff;border-radius:6px;padding:8px;text-align:center;">
|
| 487 |
+
<div style="font-family:'Orbitron',monospace;font-size:1rem;color:#58a6ff;">{st.session_state.q_count}</div>
|
| 488 |
+
<div style="font-family:'Share Tech Mono',monospace;font-size:0.58rem;color:#8b949e;">ASKED</div>
|
| 489 |
+
</div>
|
| 490 |
+
</div>
|
| 491 |
+
</div>
|
| 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
|
| 505 |
+
st.session_state.pdf_pages = 0
|
| 506 |
+
st.session_state.pdf_chunks = 0
|
| 507 |
+
st.session_state.messages = []
|
| 508 |
+
st.session_state.q_count = 0
|
| 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&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);
|
| 532 |
+
color:#58a6ff;padding:4px 12px;border-radius:20px;
|
| 533 |
+
font-family:'Share Tech Mono',monospace;font-size:0.65rem;">β ACTIVE</span>
|
| 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">
|
| 541 |
+
<div class="wc-icon">π</div>
|
| 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>
|
| 549 |
+
<span class="tip-chip">π Research papers</span>
|
| 550 |
+
<span class="tip-chip">π Study material</span>
|
| 551 |
+
<span class="tip-chip">π Reports</span>
|
| 552 |
</div>
|
| 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;">
|
| 601 |
+
<div style="font-size:1.6rem;margin-bottom:8px;">π¬</div>
|
| 602 |
+
<div class="wc-title" style="font-size:0.85rem;">DOCUMENT LOADED β START ASKING</div>
|
| 603 |
+
<div class="wc-sub" style="font-size:0.82rem;">Ask anything about the uploaded document.</div>
|
| 604 |
+
<br>
|
| 605 |
+
<span class="tip-chip">π‘ Summarize this document</span>
|
| 606 |
+
<span class="tip-chip">π‘ What are the key findings?</span>
|
| 607 |
+
<span class="tip-chip">π‘ List all important dates</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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">
|
| 638 |
+
<div class="t-dot"></div>
|
| 639 |
+
<div class="t-dot"></div>
|
| 640 |
+
<div class="t-dot"></div>
|
| 641 |
+
</div>
|
| 642 |
+
</div>
|
| 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()
|