Update app.py
Browse files
app.py
CHANGED
|
@@ -1,29 +1,23 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
-
from
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
st.set_page_config(
|
| 13 |
-
page_title="
|
| 14 |
-
page_icon="
|
| 15 |
-
layout="
|
| 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,292 +37,241 @@ html, body, [data-testid="stAppViewContainer"] {
|
|
| 43 |
footer, #MainMenu { visibility: hidden; }
|
| 44 |
|
| 45 |
/* ββ Header ββ */
|
| 46 |
-
.
|
| 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
|
| 69 |
border-bottom: 1px solid #21262d;
|
| 70 |
-
margin-bottom:
|
| 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 |
-
.
|
| 92 |
font-family: 'Orbitron', monospace;
|
| 93 |
-
font-size:
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 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 |
-
.
|
|
|
|
| 106 |
font-family: 'Share Tech Mono', monospace;
|
| 107 |
-
font-size: 0.
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 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 {
|
| 155 |
-
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
.bubble-user {
|
| 158 |
background: linear-gradient(135deg, #1f6feb, #388bfd);
|
| 159 |
-
color: #fff;
|
|
|
|
| 160 |
border-radius: 18px 18px 4px 18px;
|
| 161 |
-
max-width:
|
|
|
|
|
|
|
| 162 |
box-shadow: 0 4px 15px rgba(31,111,235,0.25);
|
| 163 |
}
|
| 164 |
.bubble-ai {
|
| 165 |
-
background: #161b22;
|
| 166 |
-
|
|
|
|
| 167 |
border-radius: 18px 18px 18px 4px;
|
| 168 |
-
max-width:
|
|
|
|
|
|
|
| 169 |
border: 1px solid #30363d;
|
| 170 |
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
|
|
|
| 171 |
}
|
| 172 |
-
.bubble-ai
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
| 177 |
}
|
| 178 |
.msg-meta {
|
| 179 |
font-family: 'Share Tech Mono', monospace;
|
| 180 |
-
font-size: 0.62rem;
|
| 181 |
-
|
| 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 |
-
/* ββ
|
| 192 |
-
.
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 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 |
-
.
|
| 221 |
-
width: 7px; height: 7px;
|
|
|
|
| 222 |
border-radius: 50%;
|
| 223 |
-
animation:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
/* ββ
|
| 230 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
background: #161b22 !important;
|
| 232 |
border: 1px solid #30363d !important;
|
| 233 |
-
border-
|
|
|
|
| 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:
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
| 243 |
/* ββ Buttons ββ */
|
| 244 |
.stButton > button {
|
| 245 |
-
background: #21262d !important;
|
| 246 |
-
|
|
|
|
|
|
|
| 247 |
font-family: 'Share Tech Mono', monospace !important;
|
| 248 |
-
font-size: 0.
|
| 249 |
-
|
|
|
|
| 250 |
}
|
| 251 |
.stButton > button:hover {
|
| 252 |
background: #30363d !important;
|
| 253 |
-
border-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 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
border-
|
| 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 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
}
|
| 285 |
|
| 286 |
-
/* ββ Welcome ββ */
|
| 287 |
.welcome-card {
|
| 288 |
-
background: #161b22;
|
| 289 |
-
border
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
| 291 |
animation: fadeInUp 0.6s ease both;
|
| 292 |
}
|
| 293 |
-
.
|
| 294 |
-
.
|
| 295 |
font-family: 'Orbitron', monospace;
|
| 296 |
font-size: 1rem; color: #fff; margin-bottom: 8px;
|
| 297 |
}
|
| 298 |
-
.
|
| 299 |
.tip-chip {
|
| 300 |
display: inline-block;
|
| 301 |
-
background: rgba(88,166,255,0.
|
| 302 |
-
border: 1px solid rgba(88,166,255,0.
|
| 303 |
-
color: #58a6ff;
|
| 304 |
-
border-radius: 20px;
|
| 305 |
font-family: 'Share Tech Mono', monospace;
|
| 306 |
-
font-size: 0.
|
| 307 |
}
|
| 308 |
|
| 309 |
-
/* ββ
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
border-radius: 10px !important;
|
| 314 |
}
|
| 315 |
-
|
| 316 |
-
|
|
|
|
| 317 |
}
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 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 |
-
/*
|
| 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,331 +279,225 @@ footer, #MainMenu { visibility: hidden; }
|
|
| 336 |
</style>
|
| 337 |
""", unsafe_allow_html=True)
|
| 338 |
|
| 339 |
-
# βββ
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
if
|
| 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 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 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 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 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 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 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 |
-
|
| 496 |
if st.button("ποΈ CLEAR CHAT", use_container_width=True):
|
| 497 |
-
st.session_state.messages
|
| 498 |
-
st.session_state.
|
|
|
|
|
|
|
| 499 |
st.rerun()
|
| 500 |
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
st.session_state.pdf_chunks = 0
|
| 507 |
-
st.session_state.messages = []
|
| 508 |
-
st.session_state.q_count = 0
|
| 509 |
-
st.rerun()
|
| 510 |
|
| 511 |
-
# βββ
|
| 512 |
-
# Header
|
| 513 |
st.markdown("""
|
| 514 |
-
<div class="
|
| 515 |
-
<div>
|
| 516 |
-
|
| 517 |
-
<div class="app-sub">INTELLIGENT DOCUMENT Q&A Β· RAG PIPELINE </div>
|
| 518 |
-
</div>
|
| 519 |
</div>
|
| 520 |
""", unsafe_allow_html=True)
|
| 521 |
|
| 522 |
-
#
|
| 523 |
-
if st.session_state.
|
| 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="
|
| 542 |
-
<div class="
|
| 543 |
-
<div class="
|
| 544 |
-
|
| 545 |
-
Powered by
|
| 546 |
</div>
|
| 547 |
<br>
|
| 548 |
-
<span class="tip-chip">
|
| 549 |
-
<span class="tip-chip">
|
| 550 |
-
<span class="tip-chip">
|
| 551 |
-
<span class="tip-chip">π Reports</span>
|
| 552 |
</div>
|
| 553 |
""", unsafe_allow_html=True)
|
| 554 |
-
|
| 555 |
else:
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
<div
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
<
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
</div>
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 628 |
st.session_state.messages.append({
|
| 629 |
-
"role":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 630 |
})
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 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()
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import torch
|
| 4 |
import time
|
| 5 |
+
from datetime import datetime, timezone, timedelta
|
| 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="TurboChat AI",
|
| 16 |
+
page_icon="β‘",
|
| 17 |
+
layout="centered",
|
| 18 |
initial_sidebar_state="expanded"
|
| 19 |
)
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
# βββ GLOBAL CSS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 22 |
st.markdown("""
|
| 23 |
<style>
|
|
|
|
| 37 |
footer, #MainMenu { visibility: hidden; }
|
| 38 |
|
| 39 |
/* ββ Header ββ */
|
| 40 |
+
.chat-header {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
text-align: center;
|
| 42 |
+
padding: 20px 0 10px;
|
| 43 |
border-bottom: 1px solid #21262d;
|
| 44 |
+
margin-bottom: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
}
|
| 46 |
+
.chat-title {
|
| 47 |
font-family: 'Orbitron', monospace;
|
| 48 |
+
font-size: 1.8rem;
|
| 49 |
+
font-weight: 900;
|
| 50 |
+
color: #fff;
|
| 51 |
+
letter-spacing: 2px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
+
.chat-title span { color: #58a6ff; }
|
| 54 |
+
.chat-sub {
|
| 55 |
font-family: 'Share Tech Mono', monospace;
|
| 56 |
+
font-size: 0.7rem;
|
| 57 |
+
color: #8b949e;
|
| 58 |
+
letter-spacing: 3px;
|
| 59 |
+
margin-top: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
/* ββ Chat bubbles ββ */
|
| 63 |
+
.chat-wrap {
|
| 64 |
+
display: flex;
|
| 65 |
+
flex-direction: column;
|
| 66 |
+
gap: 14px;
|
| 67 |
+
padding: 10px 0;
|
| 68 |
+
}
|
| 69 |
+
.msg-user {
|
| 70 |
+
display: flex;
|
| 71 |
+
justify-content: flex-end;
|
| 72 |
+
animation: slideInRight 0.3s ease both;
|
| 73 |
+
}
|
| 74 |
+
.msg-ai {
|
| 75 |
+
display: flex;
|
| 76 |
+
justify-content: flex-start;
|
| 77 |
+
animation: slideInLeft 0.3s ease both;
|
| 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: 78%;
|
| 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 |
+
color: #c9d1d9;
|
| 92 |
+
padding: 12px 18px;
|
| 93 |
border-radius: 18px 18px 18px 4px;
|
| 94 |
+
max-width: 78%;
|
| 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::before {
|
| 102 |
+
content: 'β‘';
|
| 103 |
+
position: absolute;
|
| 104 |
+
top: -10px; left: 12px;
|
| 105 |
+
font-size: 0.7rem;
|
| 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 |
+
color: #8b949e;
|
| 114 |
+
margin-top: 4px;
|
| 115 |
+
padding: 0 6px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
}
|
| 117 |
|
| 118 |
+
/* ββ Typing animation ββ */
|
| 119 |
+
.typing-indicator {
|
| 120 |
+
display: flex;
|
| 121 |
+
align-items: center;
|
| 122 |
+
gap: 5px;
|
| 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 |
+
.typing-dot {
|
| 131 |
+
width: 7px; height: 7px;
|
| 132 |
+
background: #58a6ff;
|
| 133 |
border-radius: 50%;
|
| 134 |
+
animation: bounce 1.2s ease-in-out infinite;
|
| 135 |
+
}
|
| 136 |
+
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
|
| 137 |
+
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
|
| 138 |
+
@keyframes bounce {
|
| 139 |
+
0%,60%,100% { transform: translateY(0); opacity:0.4; }
|
| 140 |
+
30% { transform: translateY(-8px); opacity:1; }
|
| 141 |
}
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
+
/* ββ Stats bar ββ */
|
| 144 |
+
.stats-bar {
|
| 145 |
+
display: flex;
|
| 146 |
+
gap: 16px;
|
| 147 |
+
flex-wrap: wrap;
|
| 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 |
+
font-size: 0.68rem;
|
| 155 |
+
color: #8b949e;
|
| 156 |
+
}
|
| 157 |
+
.stat-item { display: flex; align-items: center; gap: 5px; }
|
| 158 |
+
.stat-val { color: #58a6ff; font-weight: 600; }
|
| 159 |
+
|
| 160 |
+
/* ββ Input area ββ */
|
| 161 |
+
div[data-testid="column"]:first-child .stTextInput > div > div > input {
|
| 162 |
background: #161b22 !important;
|
| 163 |
border: 1px solid #30363d !important;
|
| 164 |
+
border-right: none !important;
|
| 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 |
+
div[data-testid="column"]:first-child .stTextInput > div > div > input:focus {
|
| 173 |
border-color: #58a6ff !important;
|
| 174 |
+
box-shadow: none !important;
|
| 175 |
+
outline: none !important;
|
| 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 |
+
color: #c9d1d9 !important;
|
| 205 |
+
border: 1px solid #30363d !important;
|
| 206 |
+
border-radius: 8px !important;
|
| 207 |
font-family: 'Share Tech Mono', monospace !important;
|
| 208 |
+
font-size: 0.75rem !important;
|
| 209 |
+
letter-spacing: 1px !important;
|
| 210 |
+
transition: all 0.25s ease !important;
|
| 211 |
}
|
| 212 |
.stButton > button:hover {
|
| 213 |
background: #30363d !important;
|
| 214 |
+
border-color: #58a6ff !important;
|
| 215 |
+
color: #58a6ff !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
}
|
| 217 |
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
/* ββ Sidebar inputs ββ */
|
| 221 |
+
[data-testid="stSidebar"] textarea,
|
| 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 |
+
[data-testid="stSidebar"] .stSelectbox > div > div {
|
| 229 |
+
background: #0d1117 !important;
|
| 230 |
+
border: 1px solid #30363d !important;
|
| 231 |
+
color: #c9d1d9 !important;
|
| 232 |
}
|
| 233 |
|
| 234 |
+
/* ββ Welcome card ββ */
|
| 235 |
.welcome-card {
|
| 236 |
+
background: #161b22;
|
| 237 |
+
border: 1px solid #21262d;
|
| 238 |
+
border-radius: 12px;
|
| 239 |
+
padding: 28px;
|
| 240 |
+
text-align: center;
|
| 241 |
+
margin: 20px 0;
|
| 242 |
animation: fadeInUp 0.6s ease both;
|
| 243 |
}
|
| 244 |
+
.welcome-icon { font-size: 2.5rem; margin-bottom: 10px; }
|
| 245 |
+
.welcome-title {
|
| 246 |
font-family: 'Orbitron', monospace;
|
| 247 |
font-size: 1rem; color: #fff; margin-bottom: 8px;
|
| 248 |
}
|
| 249 |
+
.welcome-sub { font-size: 0.9rem; color: #8b949e; line-height: 1.6; }
|
| 250 |
.tip-chip {
|
| 251 |
display: inline-block;
|
| 252 |
+
background: rgba(88,166,255,0.1);
|
| 253 |
+
border: 1px solid rgba(88,166,255,0.3);
|
| 254 |
+
color: #58a6ff;
|
| 255 |
+
padding: 4px 12px; border-radius: 20px;
|
| 256 |
font-family: 'Share Tech Mono', monospace;
|
| 257 |
+
font-size: 0.68rem; margin: 4px 3px;
|
| 258 |
}
|
| 259 |
|
| 260 |
+
/* ββ Animations ββ */
|
| 261 |
+
@keyframes slideInRight {
|
| 262 |
+
from { opacity:0; transform: translateX(20px); }
|
| 263 |
+
to { opacity:1; transform: translateX(0); }
|
|
|
|
| 264 |
}
|
| 265 |
+
@keyframes slideInLeft {
|
| 266 |
+
from { opacity:0; transform: translateX(-20px); }
|
| 267 |
+
to { opacity:1; transform: translateX(0); }
|
| 268 |
}
|
| 269 |
+
@keyframes fadeInUp {
|
| 270 |
+
from { opacity:0; transform: translateY(16px); }
|
| 271 |
+
to { opacity:1; transform: translateY(0); }
|
|
|
|
|
|
|
|
|
|
| 272 |
}
|
| 273 |
|
| 274 |
+
/* scrollbar */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
::-webkit-scrollbar { width: 5px; }
|
| 276 |
::-webkit-scrollbar-track { background: #0d1117; }
|
| 277 |
::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; }
|
|
|
|
| 279 |
</style>
|
| 280 |
""", unsafe_allow_html=True)
|
| 281 |
|
| 282 |
+
# βββ MODEL LOAD βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 283 |
+
@st.cache_resource
|
| 284 |
+
def load_model():
|
| 285 |
+
model_name = "microsoft/Phi-3-mini-4k-instruct"
|
| 286 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 287 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 288 |
+
model_name,
|
| 289 |
+
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
)
|
| 291 |
+
model.eval()
|
| 292 |
+
if torch.cuda.is_available():
|
| 293 |
+
model = model.to("cuda")
|
| 294 |
+
return tokenizer, model
|
| 295 |
|
| 296 |
+
# βββ SESSION STATE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 297 |
+
if "messages" not in st.session_state: st.session_state.messages = []
|
| 298 |
+
if "total_tokens" not in st.session_state: st.session_state.total_tokens = 0
|
| 299 |
+
if "total_time" not in st.session_state: st.session_state.total_time = 0.0
|
| 300 |
+
if "msg_count" not in st.session_state: st.session_state.msg_count = 0
|
| 301 |
+
if "last_q" not in st.session_state: st.session_state.last_q = ""
|
| 302 |
+
if "input_key" not in st.session_state: st.session_state.input_key = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
|
| 304 |
# βββ SIDEBAR ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 305 |
with st.sidebar:
|
| 306 |
+
st.markdown("""
|
| 307 |
+
<div style="text-align:center;padding:16px 0 20px;">
|
| 308 |
+
<div style="font-family:'Orbitron',monospace;font-size:1rem;color:#fff;letter-spacing:2px;">β‘ TURBO<span style="color:#58a6ff;">CHAT</span></div>
|
| 309 |
+
<div style="font-family:'Share Tech Mono',monospace;font-size:0.65rem;color:#8b949e;letter-spacing:3px;margin-top:4px;">PHI-3 MINI</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
</div>
|
| 311 |
""", unsafe_allow_html=True)
|
| 312 |
|
| 313 |
+
st.markdown("**π SYSTEM PROMPT**")
|
| 314 |
+
system_prompt = st.text_area(
|
| 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.markdown("---")
|
| 329 |
+
st.markdown("**βοΈ GENERATION SETTINGS**")
|
| 330 |
|
| 331 |
+
max_tokens = st.slider("Max Tokens", 50, 300, 120, 10)
|
| 332 |
+
temperature = st.slider("Temperature", 0.1, 1.5, 0.6, 0.1,
|
| 333 |
+
help="Higher = more creative, Lower = more focused")
|
| 334 |
+
top_p = st.slider("Top-P", 0.1, 1.0, 0.9, 0.05,
|
| 335 |
+
help="Nucleus sampling threshold")
|
| 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 |
+
st.markdown("---")
|
| 348 |
if st.button("ποΈ CLEAR CHAT", use_container_width=True):
|
| 349 |
+
st.session_state.messages = []
|
| 350 |
+
st.session_state.total_tokens = 0
|
| 351 |
+
st.session_state.total_time = 0.0
|
| 352 |
+
st.session_state.msg_count = 0
|
| 353 |
st.rerun()
|
| 354 |
|
| 355 |
+
st.markdown("""
|
| 356 |
+
<div style="text-align:center;margin-top:16px;font-family:'Share Tech Mono',monospace;font-size:0.62rem;color:#484f58;">
|
| 357 |
+
Built by <a href="https://github.com/sriramsai18" style="color:#58a6ff;text-decoration:none;">Sriram Sai</a>
|
| 358 |
+
</div>
|
| 359 |
+
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
|
| 361 |
+
# βββ HEADER βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 362 |
st.markdown("""
|
| 363 |
+
<div class="chat-header">
|
| 364 |
+
<div class="chat-title">TURBO<span>CHAT</span> β‘</div>
|
| 365 |
+
<div class="chat-sub">POWERED BY LLM's</div>
|
|
|
|
|
|
|
| 366 |
</div>
|
| 367 |
""", unsafe_allow_html=True)
|
| 368 |
|
| 369 |
+
# βββ CHAT HISTORY βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 370 |
+
if not st.session_state.messages:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
st.markdown("""
|
| 372 |
<div class="welcome-card">
|
| 373 |
+
<div class="welcome-icon">β‘</div>
|
| 374 |
+
<div class="welcome-title">TURBOCHAT IS READY</div>
|
| 375 |
+
<div class="welcome-sub">
|
| 376 |
+
Ask me anything β code, concepts, advice, or just chat.<br>
|
| 377 |
+
Powered by LLM.
|
| 378 |
</div>
|
| 379 |
<br>
|
| 380 |
+
<span class="tip-chip">π‘ Try: Explain neural networks</span>
|
| 381 |
+
<span class="tip-chip">π‘ Try: Write a Python function</span>
|
| 382 |
+
<span class="tip-chip">π‘ Try: What is RAG?</span>
|
|
|
|
| 383 |
</div>
|
| 384 |
""", unsafe_allow_html=True)
|
|
|
|
| 385 |
else:
|
| 386 |
+
chat_html = '<div class="chat-wrap">'
|
| 387 |
+
for msg in st.session_state.messages:
|
| 388 |
+
safe_content = html_lib.escape(msg["content"])
|
| 389 |
+
if msg["role"] == "user":
|
| 390 |
+
chat_html += f"""
|
| 391 |
+
<div class="msg-user">
|
| 392 |
+
<div>
|
| 393 |
+
<div class="bubble-user">{safe_content}</div>
|
| 394 |
+
<div class="msg-meta" style="text-align:right;">YOU Β· {msg.get("time","")}</div>
|
| 395 |
+
</div>
|
| 396 |
+
</div>"""
|
| 397 |
+
else:
|
| 398 |
+
chat_html += f"""
|
| 399 |
+
<div class="msg-ai">
|
| 400 |
+
<div>
|
| 401 |
+
<div class="bubble-ai">{safe_content}</div>
|
| 402 |
+
<div class="msg-meta">β‘ TURBOCHAT Β· {msg.get("time","")} Β· {msg.get("tokens","")} tokens Β· {msg.get("elapsed","")}s</div>
|
| 403 |
+
</div>
|
| 404 |
+
</div>"""
|
| 405 |
+
chat_html += '</div>'
|
| 406 |
+
st.markdown(chat_html, unsafe_allow_html=True)
|
| 407 |
+
|
| 408 |
+
# Typing indicator slot sits HERE β above the input box
|
| 409 |
+
typing_slot = st.empty()
|
| 410 |
+
|
| 411 |
+
# βββ INPUT ROW ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 412 |
+
st.write("")
|
| 413 |
+
col_input, col_btn = st.columns([6, 0.7])
|
| 414 |
+
with col_input:
|
| 415 |
+
user_input = st.text_input(
|
| 416 |
+
"",
|
| 417 |
+
placeholder="Type your message and press Enter...",
|
| 418 |
+
label_visibility="collapsed",
|
| 419 |
+
key=f"user_input_{st.session_state.input_key}"
|
| 420 |
+
)
|
| 421 |
+
with col_btn:
|
| 422 |
+
send = st.button("β‘", use_container_width=True)
|
| 423 |
+
|
| 424 |
+
# βββ GENERATE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 425 |
+
if (send or user_input) and user_input.strip() and user_input.strip() != st.session_state.last_q:
|
| 426 |
+
|
| 427 |
+
ts = get_ist_time()
|
| 428 |
+
st.session_state.messages.append({
|
| 429 |
+
"role": "user",
|
| 430 |
+
"content": user_input.strip(),
|
| 431 |
+
"time": ts
|
| 432 |
+
})
|
| 433 |
+
st.session_state.msg_count += 1
|
| 434 |
+
|
| 435 |
+
# Show typing indicator above the input
|
| 436 |
+
typing_slot.markdown("""
|
| 437 |
+
<div class="msg-ai">
|
| 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 |
+
</div>
|
| 444 |
+
""", unsafe_allow_html=True)
|
| 445 |
+
|
| 446 |
+
# generate response
|
| 447 |
+
try:
|
| 448 |
+
tokenizer, model = load_model()
|
| 449 |
+
|
| 450 |
+
prompt = f"<|system|>\n{system_prompt.strip()}\n<|end|>\n"
|
| 451 |
+
for m in st.session_state.messages:
|
| 452 |
+
if m["role"] == "user":
|
| 453 |
+
prompt += f"<|user|>\n{m['content']}\n<|end|>\n"
|
| 454 |
+
elif m["role"] == "assistant":
|
| 455 |
+
prompt += f"<|assistant|>\n{m['content']}\n<|end|>\n"
|
| 456 |
+
prompt += "<|assistant|>\n"
|
| 457 |
+
|
| 458 |
+
input_ids = tokenizer(prompt, return_tensors="pt").input_ids
|
| 459 |
+
if torch.cuda.is_available():
|
| 460 |
+
input_ids = input_ids.to("cuda")
|
| 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": "assistant",
|
| 494 |
+
"content": f"β οΈ Error: {str(e)}",
|
| 495 |
+
"time": get_ist_time(),
|
| 496 |
+
"tokens": 0,
|
| 497 |
+
"elapsed": 0
|
| 498 |
+
})
|
| 499 |
+
|
| 500 |
+
typing_slot.empty()
|
| 501 |
+
st.session_state.last_q = user_input.strip()
|
| 502 |
+
st.session_state.input_key += 1
|
| 503 |
+
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|