absiitr commited on
Commit
8574ab5
Β·
verified Β·
1 Parent(s): 6e8ccc0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -100
app.py CHANGED
@@ -35,7 +35,7 @@ st.set_page_config(page_title="PDF Assistant", page_icon="πŸ“˜", layout="wide")
35
  # ---------------- CSS ----------------
36
  st.markdown("""
37
  <style>
38
- /* 1. SCROLL FIX FOR SIDEBAR & MAIN AREA */
39
  [data-testid="stSidebar"] {
40
  position: fixed;
41
  overflow-y: hidden !important;
@@ -43,13 +43,29 @@ st.markdown("""
43
  z-index: 99;
44
  }
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  .main .block-container {
47
- overflow-y: auto;
48
- height: 100vh;
49
  padding-bottom: 5rem;
 
50
  }
51
 
52
- /* 2. SIDEBAR BUTTON STYLING (Vertical & Uniform) */
53
  [data-testid="stSidebar"] .stButton button {
54
  width: 100%;
55
  border-radius: 8px;
@@ -57,25 +73,12 @@ st.markdown("""
57
  margin-bottom: 6px;
58
  }
59
 
60
- /* 3. HIDE UPLOADED FILE LIST BELOW UPLOADER */
61
- /* This specifically hides the list of file names that appears below the browse button */
62
  section[data-testid="stFileUploader"] ul {
63
  display: none;
64
  }
65
- /* Also hide the small 'Limit 200MB' text if desired, optional:
66
- section[data-testid="stFileUploader"] small {
67
- display: none;
68
- }
69
- */
70
-
71
- /* 4. CHAT & UI STYLING */
72
- :root {
73
- --primary-color: #1e3a8a;
74
- --background-color: #0e1117;
75
- --secondary-background-color: #1a1d29;
76
- --text-color: #f0f2f6;
77
- }
78
 
 
79
  .chat-user {
80
  background: #2d3748;
81
  padding: 12px;
@@ -83,10 +86,10 @@ section[data-testid="stFileUploader"] small {
83
  margin: 6px 0 6px auto;
84
  max-width: 85%;
85
  text-align: right;
86
- color: var(--text-color);
87
  }
88
  .chat-bot {
89
- background: var(--primary-color);
90
  padding: 12px;
91
  border-radius: 10px 10px 10px 2px;
92
  margin: 6px auto 6px 0;
@@ -94,7 +97,6 @@ section[data-testid="stFileUploader"] small {
94
  text-align: left;
95
  color: #ffffff;
96
  }
97
-
98
  .sources {
99
  font-size: 0.8em;
100
  opacity: 0.7;
@@ -103,20 +105,15 @@ section[data-testid="stFileUploader"] small {
103
  padding-top: 5px;
104
  }
105
 
106
- /* 5. CENTER TITLE STYLING */
107
- .title-container {
108
- text-align: center;
109
- margin-bottom: 20px;
110
- }
111
  .title-text {
112
- font-size: 3rem;
113
  font-weight: 800;
114
  margin: 0;
115
  }
116
  .creator-text {
117
- font-size: 1.1rem;
118
  font-weight: 500;
119
- margin-top: -5px;
120
  color: #cccccc;
121
  }
122
  .creator-text a {
@@ -124,7 +121,7 @@ section[data-testid="stFileUploader"] small {
124
  text-decoration: none;
125
  }
126
 
127
- /* 6. INPUT FORM STYLING */
128
  [data-testid="stForm"] {
129
  border: none;
130
  padding: 0;
@@ -132,6 +129,17 @@ section[data-testid="stFileUploader"] small {
132
  </style>
133
  """, unsafe_allow_html=True)
134
 
 
 
 
 
 
 
 
 
 
 
 
135
  # ---------------- SESSION STATE ----------------
136
  if "chat" not in st.session_state:
137
  st.session_state.chat = []
@@ -162,7 +170,6 @@ def clear_memory():
162
  torch.cuda.empty_cache()
163
 
164
  def process_pdf(uploaded_file):
165
- """Process uploaded PDF and create vectorstore."""
166
  try:
167
  with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
168
  tmp.write(uploaded_file.getvalue())
@@ -171,10 +178,7 @@ def process_pdf(uploaded_file):
171
  loader = PyPDFLoader(path)
172
  docs = loader.load()
173
 
174
- splitter = RecursiveCharacterTextSplitter(
175
- chunk_size=800,
176
- chunk_overlap=50
177
- )
178
  chunks = splitter.split_documents(docs)
179
 
180
  embeddings = HuggingFaceEmbeddings(
@@ -193,18 +197,15 @@ def process_pdf(uploaded_file):
193
  os.unlink(path)
194
 
195
  return len(chunks)
196
-
197
  except Exception as e:
198
  st.error(f"Error processing PDF: {str(e)}")
199
  return None
200
 
201
  def ask_question(question):
202
- """Retrieve and generate answer for the question."""
203
  if not client:
204
- return None, 0, "Groq client is not initialized. Check API key setup."
205
-
206
  if not st.session_state.retriever:
207
- return None, 0, "Upload PDF first to initialize the knowledge base."
208
 
209
  try:
210
  docs = st.session_state.retriever.invoke(question)
@@ -212,87 +213,52 @@ def ask_question(question):
212
 
213
  prompt = f"""
214
  You are a strict RAG Q&A assistant.
215
- Use ONLY the context provided. If the answer is not found, reply:
216
- "I cannot find this in the PDF."
217
-
218
  ---------------- CONTEXT ----------------
219
  {context}
220
  -----------------------------------------
221
-
222
  QUESTION: {question}
223
-
224
- FINAL ANSWER:
225
- """
226
  response = client.chat.completions.create(
227
  model=GROQ_MODEL,
228
  messages=[
229
- {"role": "system",
230
- "content": "Use only the PDF content. If answer not found, say: 'I cannot find this in the PDF.'"},
231
  {"role": "user", "content": prompt}
232
  ],
233
  temperature=0.0
234
  )
235
-
236
- answer = response.choices[0].message.content.strip()
237
- return answer, len(docs), None
238
-
239
- except APIError as e:
240
- return None, 0, f"Groq API Error: {str(e)}"
241
  except Exception as e:
242
- return None, 0, f"General error: {str(e)}"
243
-
244
- # ---------------- CENTERED TITLE ----------------
245
- st.markdown("""
246
- <div class="title-container">
247
- <div class="title-text">πŸ“˜ PDF Assistant</div>
248
- <div class="creator-text">
249
- Created by <a href="https://www.linkedin.com/in/abhishek-iitr/" target="_blank">Abhishek Saxena</a>
250
- </div>
251
- </div>
252
- """, unsafe_allow_html=True)
253
 
254
  # ---------------- SIDEBAR ----------------
255
  with st.sidebar:
256
  st.write("")
257
-
258
- # Action Buttons
259
  if st.button("πŸ—‘οΈ Clear Chat History", use_container_width=True):
260
  clear_chat_history()
261
-
262
  if st.button("πŸ”₯ Clear PDF Memory", on_click=clear_memory, use_container_width=True):
263
  st.success("Memory Cleared!")
264
 
265
  st.markdown("---")
266
 
267
- # Dynamic Label based on state
268
- # If a file is loaded, show a success icon/text. If not, show "Upload PDF".
269
  upload_label = "βœ… PDF Uploaded!" if st.session_state.uploaded_file_name else "Upload PDF"
270
-
271
  uploaded = st.file_uploader(
272
- upload_label,
273
- type=["pdf"],
274
- key=st.session_state.uploader_key,
275
- label_visibility="collapsed" # Still hiding the main label text
276
  )
277
 
278
- # Status Message logic
279
  if uploaded:
280
  if uploaded.name != st.session_state.uploaded_file_name:
281
- # Processing
282
  st.session_state.uploaded_file_name = None
283
  st.session_state.chat = []
284
-
285
  with st.spinner(f"Processing '{uploaded.name}'..."):
286
- chunks_count = process_pdf(uploaded)
287
-
288
- if chunks_count is not None:
289
  st.session_state.uploaded_file_name = uploaded.name
290
- st.success(f"βœ… Processed! {chunks_count} chunks.")
291
  else:
292
  st.error("❌ Failed.")
293
  else:
294
- # Active State
295
- # This appears BELOW the uploader box
296
  st.success(f"πŸ‘ **Ready:** `{uploaded.name}`")
297
  else:
298
  st.warning("⬆️ Upload a PDF to start chatting!")
@@ -300,9 +266,9 @@ with st.sidebar:
300
  # ---------------- INPUT AREA ----------------
301
  disabled_input = st.session_state.uploaded_file_name is None or client is None
302
 
 
303
  with st.form(key='chat_form', clear_on_submit=True):
304
  col_input, col_btn = st.columns([0.85, 0.15], gap="small")
305
-
306
  with col_input:
307
  user_question = st.text_input(
308
  "Ask a question",
@@ -310,27 +276,26 @@ with st.form(key='chat_form', clear_on_submit=True):
310
  label_visibility="collapsed",
311
  disabled=disabled_input
312
  )
313
-
314
  with col_btn:
315
  submit_btn = st.form_submit_button("➀", disabled=disabled_input, use_container_width=True)
316
 
317
- # Processing the Input
318
  if submit_btn and user_question:
319
  st.session_state.chat.append(("user", user_question))
320
-
321
  with st.spinner("Thinking..."):
322
  answer, sources, error = ask_question(user_question)
323
-
324
  if answer:
325
- bot_message = f"{answer}<div class='sources'>Context Chunks Used: {sources}</div>"
326
- st.session_state.chat.append(("bot", bot_message))
327
  else:
328
  st.session_state.chat.append(("bot", f"πŸ”΄ **Error:** {error}"))
 
329
 
330
- # ---------------- CHAT HISTORY DISPLAY ----------------
331
- st.markdown("---")
332
- for role, msg in st.session_state.chat:
333
- if role == "user":
334
- st.markdown(f"<div class='chat-user'>{msg}</div>", unsafe_allow_html=True)
335
- else:
336
- st.markdown(f"<div class='chat-bot'>{msg}</div>", unsafe_allow_html=True)
 
 
 
35
  # ---------------- CSS ----------------
36
  st.markdown("""
37
  <style>
38
+ /* 1. SCROLL FIX FOR SIDEBAR */
39
  [data-testid="stSidebar"] {
40
  position: fixed;
41
  overflow-y: hidden !important;
 
43
  z-index: 99;
44
  }
45
 
46
+ /* 2. FIXED HEADER STYLING */
47
+ /* This pins the title to the top and adds a background so chat flows under it */
48
+ .fixed-header {
49
+ position: fixed;
50
+ top: 0;
51
+ left: 0;
52
+ width: 100%;
53
+ background-color: #0e1117; /* Match app background */
54
+ z-index: 98; /* Below sidebar, above content */
55
+ padding-top: 1rem;
56
+ padding-bottom: 1rem;
57
+ text-align: center;
58
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
59
+ }
60
+
61
+ /* 3. ADJUST MAIN CONTENT TO ACCOUNT FOR FIXED HEADER */
62
  .main .block-container {
63
+ margin-top: 6rem; /* Push content down so it doesn't hide behind header */
 
64
  padding-bottom: 5rem;
65
+ overflow-y: auto; /* Allow scrolling */
66
  }
67
 
68
+ /* 4. SIDEBAR BUTTON STYLING */
69
  [data-testid="stSidebar"] .stButton button {
70
  width: 100%;
71
  border-radius: 8px;
 
73
  margin-bottom: 6px;
74
  }
75
 
76
+ /* 5. HIDE UPLOADED FILE LIST */
 
77
  section[data-testid="stFileUploader"] ul {
78
  display: none;
79
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
+ /* 6. CHAT BUBBLES */
82
  .chat-user {
83
  background: #2d3748;
84
  padding: 12px;
 
86
  margin: 6px 0 6px auto;
87
  max-width: 85%;
88
  text-align: right;
89
+ color: #f0f2f6;
90
  }
91
  .chat-bot {
92
+ background: #1e3a8a;
93
  padding: 12px;
94
  border-radius: 10px 10px 10px 2px;
95
  margin: 6px auto 6px 0;
 
97
  text-align: left;
98
  color: #ffffff;
99
  }
 
100
  .sources {
101
  font-size: 0.8em;
102
  opacity: 0.7;
 
105
  padding-top: 5px;
106
  }
107
 
108
+ /* 7. TITLE TEXT */
 
 
 
 
109
  .title-text {
110
+ font-size: 2.5rem;
111
  font-weight: 800;
112
  margin: 0;
113
  }
114
  .creator-text {
115
+ font-size: 1rem;
116
  font-weight: 500;
 
117
  color: #cccccc;
118
  }
119
  .creator-text a {
 
121
  text-decoration: none;
122
  }
123
 
124
+ /* 8. INPUT FORM STYLING */
125
  [data-testid="stForm"] {
126
  border: none;
127
  padding: 0;
 
129
  </style>
130
  """, unsafe_allow_html=True)
131
 
132
+ # ---------------- FIXED HEADER ----------------
133
+ st.markdown("""
134
+ <div class="fixed-header">
135
+ <div class="title-text">πŸ“˜ PDF Assistant</div>
136
+ <div class="creator-text">
137
+ <a href="https://www.linkedin.com/in/abhishek-iitr/" target="_blank">Abhishek Saxena</a>
138
+ </div>
139
+ </div>
140
+ """, unsafe_allow_html=True)
141
+
142
+
143
  # ---------------- SESSION STATE ----------------
144
  if "chat" not in st.session_state:
145
  st.session_state.chat = []
 
170
  torch.cuda.empty_cache()
171
 
172
  def process_pdf(uploaded_file):
 
173
  try:
174
  with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
175
  tmp.write(uploaded_file.getvalue())
 
178
  loader = PyPDFLoader(path)
179
  docs = loader.load()
180
 
181
+ splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=50)
 
 
 
182
  chunks = splitter.split_documents(docs)
183
 
184
  embeddings = HuggingFaceEmbeddings(
 
197
  os.unlink(path)
198
 
199
  return len(chunks)
 
200
  except Exception as e:
201
  st.error(f"Error processing PDF: {str(e)}")
202
  return None
203
 
204
  def ask_question(question):
 
205
  if not client:
206
+ return None, 0, "Groq client is not initialized."
 
207
  if not st.session_state.retriever:
208
+ return None, 0, "Upload PDF first."
209
 
210
  try:
211
  docs = st.session_state.retriever.invoke(question)
 
213
 
214
  prompt = f"""
215
  You are a strict RAG Q&A assistant.
216
+ Use ONLY the context provided. If the answer is not found, reply: "I cannot find this in the PDF."
 
 
217
  ---------------- CONTEXT ----------------
218
  {context}
219
  -----------------------------------------
 
220
  QUESTION: {question}
221
+ FINAL ANSWER:"""
222
+
 
223
  response = client.chat.completions.create(
224
  model=GROQ_MODEL,
225
  messages=[
226
+ {"role": "system", "content": "Use only the PDF content."},
 
227
  {"role": "user", "content": prompt}
228
  ],
229
  temperature=0.0
230
  )
231
+ return response.choices[0].message.content.strip(), len(docs), None
 
 
 
 
 
232
  except Exception as e:
233
+ return None, 0, f"Error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
234
 
235
  # ---------------- SIDEBAR ----------------
236
  with st.sidebar:
237
  st.write("")
 
 
238
  if st.button("πŸ—‘οΈ Clear Chat History", use_container_width=True):
239
  clear_chat_history()
 
240
  if st.button("πŸ”₯ Clear PDF Memory", on_click=clear_memory, use_container_width=True):
241
  st.success("Memory Cleared!")
242
 
243
  st.markdown("---")
244
 
 
 
245
  upload_label = "βœ… PDF Uploaded!" if st.session_state.uploaded_file_name else "Upload PDF"
 
246
  uploaded = st.file_uploader(
247
+ upload_label, type=["pdf"], key=st.session_state.uploader_key, label_visibility="collapsed"
 
 
 
248
  )
249
 
 
250
  if uploaded:
251
  if uploaded.name != st.session_state.uploaded_file_name:
 
252
  st.session_state.uploaded_file_name = None
253
  st.session_state.chat = []
 
254
  with st.spinner(f"Processing '{uploaded.name}'..."):
255
+ chunks = process_pdf(uploaded)
256
+ if chunks:
 
257
  st.session_state.uploaded_file_name = uploaded.name
258
+ st.success(f"βœ… Processed! {chunks} chunks.")
259
  else:
260
  st.error("❌ Failed.")
261
  else:
 
 
262
  st.success(f"πŸ‘ **Ready:** `{uploaded.name}`")
263
  else:
264
  st.warning("⬆️ Upload a PDF to start chatting!")
 
266
  # ---------------- INPUT AREA ----------------
267
  disabled_input = st.session_state.uploaded_file_name is None or client is None
268
 
269
+ # Input Form
270
  with st.form(key='chat_form', clear_on_submit=True):
271
  col_input, col_btn = st.columns([0.85, 0.15], gap="small")
 
272
  with col_input:
273
  user_question = st.text_input(
274
  "Ask a question",
 
276
  label_visibility="collapsed",
277
  disabled=disabled_input
278
  )
 
279
  with col_btn:
280
  submit_btn = st.form_submit_button("➀", disabled=disabled_input, use_container_width=True)
281
 
 
282
  if submit_btn and user_question:
283
  st.session_state.chat.append(("user", user_question))
 
284
  with st.spinner("Thinking..."):
285
  answer, sources, error = ask_question(user_question)
 
286
  if answer:
287
+ bot_msg = f"{answer}<div class='sources'>Context Chunks Used: {sources}</div>"
288
+ st.session_state.chat.append(("bot", bot_msg))
289
  else:
290
  st.session_state.chat.append(("bot", f"πŸ”΄ **Error:** {error}"))
291
+ st.rerun() # Rerun to update the display at the top immediately
292
 
293
+ # ---------------- CHAT HISTORY (REVERSED) ----------------
294
+ # We iterate through the reversed list so the newest message (last in list) comes first.
295
+ if st.session_state.chat:
296
+ st.markdown("---")
297
+ for role, msg in reversed(st.session_state.chat):
298
+ if role == "user":
299
+ st.markdown(f"<div class='chat-user'>{msg}</div>", unsafe_allow_html=True)
300
+ else:
301
+ st.markdown(f"<div class='chat-bot'>{msg}</div>", unsafe_allow_html=True)