Samarth-Health-AI commited on
Commit
1bc8e79
Β·
verified Β·
1 Parent(s): 6267180

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +45 -77
src/streamlit_app.py CHANGED
@@ -14,12 +14,13 @@ AudioSegment.ffprobe = shutil.which("ffprobe") or "ffprobe"
14
 
15
  # --- 0. PAGE CONFIG ---
16
  st.set_page_config(page_title="Clinical Portal", layout="wide", initial_sidebar_state="expanded")
17
- # Line 17: Define api_key (minimal change: default to "local" so app works without Gemini)
 
18
  api_key = os.environ.get("GEMINI_API_KEY") or st.secrets.get("GEMINI_API_KEY") or "local"
19
- if api_key:
20
  st.sidebar.success(f"βœ… API Key loaded: {api_key[:12]}...")
21
  else:
22
- st.sidebar.error("❌ NO API KEY FOUND! Set GEMINI_API_KEY in HF Spaces Secrets")
23
 
24
 
25
  # --- 1. JAVASCRIPT ERROR SUPPRESSOR (kept; harmless now) ---
@@ -209,35 +210,6 @@ st.markdown("""
209
  border-left: 3px solid #e0e0e0;
210
  }
211
 
212
- /* Sidebar Sections */
213
- .sidebar-section {
214
- background: rgba(255,255,255,0.08);
215
- border-radius: 10px;
216
- padding: 20px 15px;
217
- margin-bottom: 25px;
218
- border: 1px solid rgba(255,255,255,0.1);
219
- }
220
-
221
- .section-title {
222
- font-size: 1.1rem;
223
- font-weight: 700;
224
- margin-bottom: 15px;
225
- display: flex;
226
- align-items: center;
227
- color: #ffffff !important;
228
- }
229
-
230
- .step-badge {
231
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
232
- color: white;
233
- padding: 6px 12px;
234
- border-radius: 50%;
235
- font-weight: 700;
236
- margin-right: 12px;
237
- font-size: 1.2rem;
238
- box-shadow: 0 2px 8px rgba(102,126,234,0.3);
239
- }
240
-
241
  /* Empty State */
242
  .empty-state {
243
  text-align: center;
@@ -289,20 +261,17 @@ with st.sidebar:
289
  st.markdown(
290
  '<div style="margin-bottom: 18px;">'
291
  '<h2 style="color:#ffffff; font-size:1.6rem; text-align:center;">Workflow</h2>'
292
- "</div>",
293
  unsafe_allow_html=True
294
  )
295
 
296
  # Access Control (card)
297
  st.markdown('<div class="sb-card">', unsafe_allow_html=True)
298
  st.markdown('<div class="sb-title">πŸ” Access Control</div>', unsafe_allow_html=True)
299
- if api_key:
300
- st.success("βœ“ Authorized βœ…")
301
- else:
302
- api_key = st.text_input("Enter API Key", type="password", help="OpenRouter or similar API key")
303
- st.markdown("</div>", unsafe_allow_html=True)
304
 
305
- # Voice Consultation (card) - keeps preview + checkbox in SAME card
306
  st.markdown('<div class="sb-card">', unsafe_allow_html=True)
307
  st.markdown('<div class="sb-title">🎀 Voice Consultation</div>', unsafe_allow_html=True)
308
 
@@ -322,25 +291,24 @@ with st.sidebar:
322
  audio_data = audio_bytes
323
  st.audio(audio_data, format="audio/wav")
324
 
325
- if api_key:
326
- status_placeholder = st.empty()
327
- if st.button("πŸ“ Transcribe Voice", use_container_width=True, key="btn_transcribe_voice"):
328
- if audio_data is None:
329
- status_placeholder.warning("⚠️ Record audio first.")
330
- else:
331
- with st.spinner("πŸ”„ Transcribing..."):
332
- try:
333
- st.session_state.voice_transcript = transcribe_and_tag(audio_data, api_key)
334
- status_placeholder.success("βœ“ Done!")
335
- except Exception as e:
336
- status_placeholder.error(f"❌ {str(e)}")
337
 
338
  if "voice_transcript" in st.session_state:
339
  st.text_area("πŸ“‹ Preview", st.session_state.voice_transcript, height=100, disabled=True)
340
  st.download_button("πŸ’Ύ Download", st.session_state.voice_transcript, "transcript.txt", use_container_width=True)
341
  st.checkbox("βœ“ Use Voice for Analysis", value=True, key="use_voice")
342
 
343
- st.markdown("</div>", unsafe_allow_html=True)
344
 
345
  # Clinical Documents (card)
346
  st.markdown('<div class="sb-card">', unsafe_allow_html=True)
@@ -354,9 +322,9 @@ with st.sidebar:
354
  st.download_button("πŸ“₯ Sample Template", f, "Sample_Template.docx", use_container_width=True)
355
  st.checkbox("πŸ“„ Use Sample", value=False, key="use_sample")
356
 
357
- st.markdown("</div>", unsafe_allow_html=True)
358
 
359
- # Execute Analysis (final green-glow card)
360
  st.markdown('<div class="sb-card sb-execute">', unsafe_allow_html=True)
361
  st.markdown('<div class="sb-title">πŸš€ Execute Analysis <span class="sb-arrow">β†’</span></div>', unsafe_allow_html=True)
362
  st.markdown('<div class="sb-subtitle">Ready? Hit Execute Below!</div>', unsafe_allow_html=True)
@@ -367,8 +335,7 @@ with st.sidebar:
367
  type="primary",
368
  help="Combines voice + docs for full analysis",
369
  key="btn_generate_soap",
370
- ) and api_key:
371
- # Your existing generate logic (unchanged)
372
  status_label = st.empty()
373
  progress_bar = st.progress(0)
374
 
@@ -408,7 +375,7 @@ with st.sidebar:
408
  else:
409
  st.warning("Upload docs or record voice first.")
410
 
411
- st.markdown("</div>", unsafe_allow_html=True)
412
 
413
 
414
  # --- 5. MAIN WORKSPACE ---
@@ -423,19 +390,16 @@ if "soap" in st.session_state:
423
  st.markdown(f'<div class="soap-header">{sec}</div>', unsafe_allow_html=True)
424
  st.markdown(f'<div class="soap-content">{data["content"]}</div>', unsafe_allow_html=True)
425
 
426
- # FIXED: Indented to stay within the 'for' loop and 'col_l' container
427
  with st.expander(f"πŸ” View Supporting Evidence for {sec}"):
428
  if data.get("evidence"):
429
  for i, snippet in enumerate(data["evidence"]):
430
- # Redact PHI from evidence chunks locally
431
  redacted_snippet = redact_phi(snippet)
432
-
433
- # Truncate long chunks (show first 300 chars)
434
  max_display_length = 300
435
  display_snippet = redacted_snippet[:max_display_length]
436
  if len(redacted_snippet) > max_display_length:
437
  display_snippet += "... [truncated]"
438
-
439
  st.markdown(f"""
440
  <div style="background: #f8f9fa; padding: 12px; border-radius: 6px; border-left: 3px solid #667eea; margin-bottom: 10px;">
441
  <p style="color: #1e3c72; font-weight: 600; font-size: 0.9rem; margin: 0 0 8px 0;">
@@ -451,14 +415,18 @@ if "soap" in st.session_state:
451
 
452
  st.divider()
453
 
454
- # Export Section Header
455
- st.markdown('<div style="text-align: center; margin: 20px 0 15px 0;"><h4 style="color: #1e3c72; font-weight: 700;">πŸ“₯ Export Clinical Note</h4></div>', unsafe_allow_html=True)
456
-
 
 
 
 
457
  col1, col2 = st.columns(2)
458
  with col1:
459
  st.markdown("""
460
- <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
461
- padding: 15px; border-radius: 10px; text-align: center;
462
  box-shadow: 0 4px 12px rgba(102,126,234,0.3); margin-bottom: 10px;">
463
  <p style="color: white; font-weight: 600; font-size: 1.05rem; margin: 0;">
464
  πŸ“„ FHIR Standard Format
@@ -469,17 +437,17 @@ if "soap" in st.session_state:
469
  </div>
470
  """, unsafe_allow_html=True)
471
  st.download_button(
472
- "⬇️ Download FHIR",
473
- export_as_fhir(st.session_state.soap),
474
- "clinical_note.json",
475
  use_container_width=True,
476
  type="primary"
477
  )
478
-
479
  with col2:
480
  st.markdown("""
481
- <div style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
482
- padding: 15px; border-radius: 10px; text-align: center;
483
  box-shadow: 0 4px 12px rgba(17,153,142,0.3); margin-bottom: 10px;">
484
  <p style="color: white; font-weight: 600; font-size: 1.05rem; margin: 0;">
485
  πŸ“‹ Plain Text Format
@@ -490,9 +458,9 @@ if "soap" in st.session_state:
490
  </div>
491
  """, unsafe_allow_html=True)
492
  st.download_button(
493
- "⬇️ Download Text",
494
- str(st.session_state.soap),
495
- "soap_note.txt",
496
  use_container_width=True,
497
  type="secondary"
498
  )
@@ -503,7 +471,7 @@ if "soap" in st.session_state:
503
 
504
  for metric, details in st.session_state.eval.items():
505
  if isinstance(details, dict):
506
- score = details.get('score', 0)
507
  color = "metric-good" if score >= 4 else "metric-bad"
508
 
509
  st.markdown(f'<div class="metric-card {color}">{metric}: {score}/5 ⭐</div>', unsafe_allow_html=True)
 
14
 
15
  # --- 0. PAGE CONFIG ---
16
  st.set_page_config(page_title="Clinical Portal", layout="wide", initial_sidebar_state="expanded")
17
+
18
+ # Minimal change: default api_key to "local" so app works on HF Spaces without Gemini
19
  api_key = os.environ.get("GEMINI_API_KEY") or st.secrets.get("GEMINI_API_KEY") or "local"
20
+ if api_key and api_key != "local":
21
  st.sidebar.success(f"βœ… API Key loaded: {api_key[:12]}...")
22
  else:
23
+ st.sidebar.success("βœ… Local model mode enabled")
24
 
25
 
26
  # --- 1. JAVASCRIPT ERROR SUPPRESSOR (kept; harmless now) ---
 
210
  border-left: 3px solid #e0e0e0;
211
  }
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  /* Empty State */
214
  .empty-state {
215
  text-align: center;
 
261
  st.markdown(
262
  '<div style="margin-bottom: 18px;">'
263
  '<h2 style="color:#ffffff; font-size:1.6rem; text-align:center;">Workflow</h2>'
264
+ '</div>',
265
  unsafe_allow_html=True
266
  )
267
 
268
  # Access Control (card)
269
  st.markdown('<div class="sb-card">', unsafe_allow_html=True)
270
  st.markdown('<div class="sb-title">πŸ” Access Control</div>', unsafe_allow_html=True)
271
+ st.success("βœ“ Authorized βœ…")
272
+ st.markdown('</div>', unsafe_allow_html=True)
 
 
 
273
 
274
+ # Voice Consultation (card)
275
  st.markdown('<div class="sb-card">', unsafe_allow_html=True)
276
  st.markdown('<div class="sb-title">🎀 Voice Consultation</div>', unsafe_allow_html=True)
277
 
 
291
  audio_data = audio_bytes
292
  st.audio(audio_data, format="audio/wav")
293
 
294
+ status_placeholder = st.empty()
295
+ if st.button("πŸ“ Transcribe Voice", use_container_width=True, key="btn_transcribe_voice"):
296
+ if audio_data is None:
297
+ status_placeholder.warning("⚠️ Record audio first.")
298
+ else:
299
+ with st.spinner("πŸ”„ Transcribing..."):
300
+ try:
301
+ st.session_state.voice_transcript = transcribe_and_tag(audio_data, api_key)
302
+ status_placeholder.success("βœ“ Done!")
303
+ except Exception as e:
304
+ status_placeholder.error(f"❌ {str(e)}")
 
305
 
306
  if "voice_transcript" in st.session_state:
307
  st.text_area("πŸ“‹ Preview", st.session_state.voice_transcript, height=100, disabled=True)
308
  st.download_button("πŸ’Ύ Download", st.session_state.voice_transcript, "transcript.txt", use_container_width=True)
309
  st.checkbox("βœ“ Use Voice for Analysis", value=True, key="use_voice")
310
 
311
+ st.markdown('</div>', unsafe_allow_html=True)
312
 
313
  # Clinical Documents (card)
314
  st.markdown('<div class="sb-card">', unsafe_allow_html=True)
 
322
  st.download_button("πŸ“₯ Sample Template", f, "Sample_Template.docx", use_container_width=True)
323
  st.checkbox("πŸ“„ Use Sample", value=False, key="use_sample")
324
 
325
+ st.markdown('</div>', unsafe_allow_html=True)
326
 
327
+ # Execute Analysis (final card)
328
  st.markdown('<div class="sb-card sb-execute">', unsafe_allow_html=True)
329
  st.markdown('<div class="sb-title">πŸš€ Execute Analysis <span class="sb-arrow">β†’</span></div>', unsafe_allow_html=True)
330
  st.markdown('<div class="sb-subtitle">Ready? Hit Execute Below!</div>', unsafe_allow_html=True)
 
335
  type="primary",
336
  help="Combines voice + docs for full analysis",
337
  key="btn_generate_soap",
338
+ ):
 
339
  status_label = st.empty()
340
  progress_bar = st.progress(0)
341
 
 
375
  else:
376
  st.warning("Upload docs or record voice first.")
377
 
378
+ st.markdown('</div>', unsafe_allow_html=True)
379
 
380
 
381
  # --- 5. MAIN WORKSPACE ---
 
390
  st.markdown(f'<div class="soap-header">{sec}</div>', unsafe_allow_html=True)
391
  st.markdown(f'<div class="soap-content">{data["content"]}</div>', unsafe_allow_html=True)
392
 
 
393
  with st.expander(f"πŸ” View Supporting Evidence for {sec}"):
394
  if data.get("evidence"):
395
  for i, snippet in enumerate(data["evidence"]):
 
396
  redacted_snippet = redact_phi(snippet)
397
+
 
398
  max_display_length = 300
399
  display_snippet = redacted_snippet[:max_display_length]
400
  if len(redacted_snippet) > max_display_length:
401
  display_snippet += "... [truncated]"
402
+
403
  st.markdown(f"""
404
  <div style="background: #f8f9fa; padding: 12px; border-radius: 6px; border-left: 3px solid #667eea; margin-bottom: 10px;">
405
  <p style="color: #1e3c72; font-weight: 600; font-size: 0.9rem; margin: 0 0 8px 0;">
 
415
 
416
  st.divider()
417
 
418
+ st.markdown(
419
+ '<div style="text-align: center; margin: 20px 0 15px 0;">'
420
+ '<h4 style="color: #1e3c72; font-weight: 700;">πŸ“₯ Export Clinical Note</h4>'
421
+ '</div>',
422
+ unsafe_allow_html=True
423
+ )
424
+
425
  col1, col2 = st.columns(2)
426
  with col1:
427
  st.markdown("""
428
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
429
+ padding: 15px; border-radius: 10px; text-align: center;
430
  box-shadow: 0 4px 12px rgba(102,126,234,0.3); margin-bottom: 10px;">
431
  <p style="color: white; font-weight: 600; font-size: 1.05rem; margin: 0;">
432
  πŸ“„ FHIR Standard Format
 
437
  </div>
438
  """, unsafe_allow_html=True)
439
  st.download_button(
440
+ "⬇️ Download FHIR",
441
+ export_as_fhir(st.session_state.soap),
442
+ "clinical_note.json",
443
  use_container_width=True,
444
  type="primary"
445
  )
446
+
447
  with col2:
448
  st.markdown("""
449
+ <div style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
450
+ padding: 15px; border-radius: 10px; text-align: center;
451
  box-shadow: 0 4px 12px rgba(17,153,142,0.3); margin-bottom: 10px;">
452
  <p style="color: white; font-weight: 600; font-size: 1.05rem; margin: 0;">
453
  πŸ“‹ Plain Text Format
 
458
  </div>
459
  """, unsafe_allow_html=True)
460
  st.download_button(
461
+ "⬇️ Download Text",
462
+ str(st.session_state.soap),
463
+ "soap_note.txt",
464
  use_container_width=True,
465
  type="secondary"
466
  )
 
471
 
472
  for metric, details in st.session_state.eval.items():
473
  if isinstance(details, dict):
474
+ score = details.get("score", 0)
475
  color = "metric-good" if score >= 4 else "metric-bad"
476
 
477
  st.markdown(f'<div class="metric-card {color}">{metric}: {score}/5 ⭐</div>', unsafe_allow_html=True)