Nikhil Pravin Pise commited on
Commit
193fabd
Β·
1 Parent(s): 150e705

Overhaul Gradio UI with modern medical design

Browse files

- Complete redesign with professional medical theme
- Gradient backgrounds and styled cards
- Beautiful biomarker status indicators
- Enhanced HTML output for analysis results
- Agent cards with hover effects
- Responsive grid layouts
- Better visual hierarchy
- Custom CSS for all components
- Improved error and status messages

Files changed (1) hide show
  1. huggingface/app.py +603 -152
huggingface/app.py CHANGED
@@ -176,35 +176,49 @@ def analyze_biomarkers(input_text: str, progress=gr.Progress()) -> tuple[str, st
176
  Returns: (summary, details_json, status)
177
  """
178
  if not input_text.strip():
179
- return "", "", "⚠️ Please enter biomarkers to analyze."
 
 
 
 
 
180
 
181
  # Check API key dynamically (HF injects secrets after startup)
182
  groq_key, google_key = get_api_keys()
183
 
184
  if not groq_key and not google_key:
185
- return "", "", (
186
- "❌ **Error**: No LLM API key configured.\n\n"
187
- "Please add your API key in Hugging Face Space Settings β†’ Secrets:\n"
188
- "- `GROQ_API_KEY` (get free at https://console.groq.com/keys)\n"
189
- "- or `GOOGLE_API_KEY` (get free at https://aistudio.google.com/app/apikey)"
190
- )
 
 
 
 
191
 
192
  # Setup provider based on available key
193
  provider = setup_llm_provider()
194
  logger.info(f"Using LLM provider: {provider}")
195
 
196
  try:
197
- progress(0.1, desc="Parsing biomarkers...")
198
  biomarkers = parse_biomarkers(input_text)
199
 
200
  if not biomarkers:
201
- return "", "", (
202
- "⚠️ Could not parse biomarkers. Try formats like:\n"
203
- "β€’ `Glucose: 140, HbA1c: 7.5`\n"
204
- "β€’ `{\"Glucose\": 140, \"HbA1c\": 7.5}`"
205
- )
 
 
 
 
 
206
 
207
- progress(0.2, desc="Initializing analysis...")
208
 
209
  # Initialize guild
210
  guild = get_guild()
@@ -221,14 +235,14 @@ def analyze_biomarkers(input_text: str, progress=gr.Progress()) -> tuple[str, st
221
  patient_context={"patient_id": "HF_User", "source": "huggingface_spaces"}
222
  )
223
 
224
- progress(0.4, desc="Running Clinical Insight Guild...")
225
 
226
  # Run analysis
227
  start = time.time()
228
  result = guild.run(patient_input)
229
  elapsed = time.time() - start
230
 
231
- progress(0.9, desc="Formatting results...")
232
 
233
  # Extract response
234
  final_response = result.get("final_response", {})
@@ -239,13 +253,31 @@ def analyze_biomarkers(input_text: str, progress=gr.Progress()) -> tuple[str, st
239
  # Format details
240
  details = json.dumps(final_response, indent=2, default=str)
241
 
242
- status = f"βœ… Analysis completed in {elapsed:.1f}s"
 
 
 
 
 
 
 
 
243
 
244
  return summary, details, status
245
 
246
  except Exception as exc:
247
  logger.error(f"Analysis error: {exc}", exc_info=True)
248
- return "", "", f"❌ **Error**: {exc}\n\n```\n{traceback.format_exc()}\n```"
 
 
 
 
 
 
 
 
 
 
249
 
250
 
251
  def auto_predict(biomarkers: dict[str, float]) -> dict[str, Any]:
@@ -314,103 +346,190 @@ def auto_predict(biomarkers: dict[str, float]) -> dict[str, Any]:
314
 
315
 
316
  def format_summary(response: dict, elapsed: float) -> str:
317
- """Format the analysis response as readable markdown."""
318
  if not response:
319
- return "No analysis results available."
 
 
 
 
 
320
 
321
  parts = []
322
 
323
- # Header
324
- primary = response.get("primary_finding", "Analysis")
325
  confidence = response.get("confidence", {})
326
  conf_score = confidence.get("overall_score", 0) if isinstance(confidence, dict) else 0
327
 
328
- parts.append(f"## πŸ₯ {primary}")
 
 
 
 
 
 
 
 
 
 
 
329
  if conf_score:
330
- parts.append(f"**Confidence**: {conf_score:.0%}")
331
- parts.append("")
 
 
 
 
 
 
 
 
 
 
 
332
 
333
  # Critical Alerts
334
  alerts = response.get("safety_alerts", [])
335
  if alerts:
336
- parts.append("### ⚠️ Critical Alerts")
337
  for alert in alerts[:5]:
338
  if isinstance(alert, dict):
339
- parts.append(f"- **{alert.get('alert_type', 'Alert')}**: {alert.get('message', '')}")
340
  else:
341
- parts.append(f"- {alert}")
342
- parts.append("")
 
 
 
 
 
 
 
 
343
 
344
  # Key Findings
345
  findings = response.get("key_findings", [])
346
  if findings:
347
- parts.append("### πŸ” Key Findings")
348
- for finding in findings[:5]:
349
- parts.append(f"- {finding}")
350
- parts.append("")
 
 
 
351
 
352
- # Biomarker Flags
353
  flags = response.get("biomarker_flags", [])
354
  if flags:
355
- parts.append("### πŸ“Š Biomarker Analysis")
356
  for flag in flags[:8]:
357
  if isinstance(flag, dict):
358
  name = flag.get("biomarker", "Unknown")
359
  status = flag.get("status", "normal")
360
  value = flag.get("value", "N/A")
361
- emoji = "πŸ”΄" if status == "critical" else "🟑" if status == "abnormal" else "🟒"
362
- parts.append(f"- {emoji} **{name}**: {value} ({status})")
363
- else:
364
- parts.append(f"- {flag}")
365
- parts.append("")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
367
- # Recommendations
368
  recs = response.get("recommendations", {})
369
  if recs:
370
- parts.append("### πŸ’‘ Recommendations")
371
 
372
  immediate = recs.get("immediate_actions", [])
373
  if immediate:
374
- parts.append("**Immediate Actions:**")
375
- for action in immediate[:3]:
376
- parts.append(f"- {action}")
 
 
 
 
377
 
378
  lifestyle = recs.get("lifestyle_modifications", [])
379
  if lifestyle:
380
- parts.append("\n**Lifestyle Modifications:**")
381
- for mod in lifestyle[:3]:
382
- parts.append(f"- {mod}")
 
 
 
 
383
 
384
  followup = recs.get("follow_up", [])
385
  if followup:
386
- parts.append("\n**Follow-up:**")
387
- for item in followup[:3]:
388
- parts.append(f"- {item}")
389
- parts.append("")
 
 
 
 
 
 
 
 
 
 
 
390
 
391
  # Disease Explanation
392
  explanation = response.get("disease_explanation", {})
393
  if explanation and isinstance(explanation, dict):
394
- parts.append("### πŸ“– Understanding Your Results")
395
-
396
  pathophys = explanation.get("pathophysiology", "")
397
  if pathophys:
398
- parts.append(f"{pathophys[:500]}...")
399
- parts.append("")
 
 
 
 
400
 
401
  # Conversational Summary
402
  conv_summary = response.get("conversational_summary", "")
403
  if conv_summary:
404
- parts.append("### πŸ“ Summary")
405
- parts.append(conv_summary[:1000])
406
- parts.append("")
 
 
 
407
 
408
  # Footer
409
- parts.append("---")
410
- parts.append(f"*Analysis completed in {elapsed:.1f}s using MediGuard AI*")
411
- parts.append("")
412
- parts.append("**⚠️ Disclaimer**: This is for informational purposes only. "
413
- "Consult a healthcare professional for medical advice.")
 
 
 
 
 
414
 
415
  return "\n".join(parts)
416
 
@@ -419,65 +538,330 @@ def format_summary(response: dict, elapsed: float) -> str:
419
  # Gradio Interface
420
  # ---------------------------------------------------------------------------
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  def create_demo() -> gr.Blocks:
423
- """Create the Gradio Blocks interface."""
424
 
425
  with gr.Blocks(
426
- title="MediGuard AI - Medical Biomarker Analysis",
427
- theme=gr.themes.Soft(primary_hue="blue", secondary_hue="cyan"),
428
- css="""
429
- .gradio-container { max-width: 1200px !important; }
430
- .status-box { font-size: 14px; }
431
- footer { display: none !important; }
432
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  ) as demo:
434
 
435
- # Header
436
- gr.Markdown("""
437
- # πŸ₯ MediGuard AI β€” Medical Biomarker Analysis
438
-
439
- **Multi-Agent RAG System** powered by 6 specialized AI agents with medical knowledge retrieval.
440
-
441
- Enter your biomarkers below and get evidence-based insights in seconds.
 
 
 
 
 
442
  """)
443
 
444
- # API Key warning - always show since keys are checked dynamically
445
- # The actual check happens in analyze_biomarkers()
446
- gr.Markdown("""
447
- <div style="background: #d4edda; padding: 10px; border-radius: 5px; margin: 10px 0;">
448
- ℹ️ <b>Note</b>: Make sure you've added <code>GROQ_API_KEY</code> or <code>GOOGLE_API_KEY</code>
449
- in Space Settings β†’ Secrets for analysis to work.
 
 
 
450
  </div>
451
  """)
452
 
453
- with gr.Row():
454
- # Input column
455
- with gr.Column(scale=1):
456
- gr.Markdown("### πŸ“ Enter Biomarkers")
457
-
458
- input_text = gr.Textbox(
459
- label="Biomarkers",
460
- placeholder=(
461
- "Enter biomarkers in any format:\n"
462
- "β€’ Glucose: 140, HbA1c: 7.5, Cholesterol: 210\n"
463
- "β€’ My glucose is 140 and HbA1c is 7.5\n"
464
- 'β€’ {"Glucose": 140, "HbA1c": 7.5}'
465
- ),
466
- lines=5,
467
- max_lines=10,
468
- )
469
 
470
- with gr.Row():
471
- analyze_btn = gr.Button("πŸ”¬ Analyze", variant="primary", size="lg")
472
- clear_btn = gr.Button("πŸ—‘οΈ Clear", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
 
 
474
  status_output = gr.Markdown(
475
- label="Status",
476
  elem_classes="status-box"
477
  )
478
 
479
- # Example inputs
480
- gr.Markdown("### πŸ“‹ Example Inputs")
 
481
 
482
  examples = gr.Examples(
483
  examples=[
@@ -485,31 +869,123 @@ def create_demo() -> gr.Blocks:
485
  ["Glucose: 95, HbA1c: 5.4, Cholesterol: 180, HDL: 55, LDL: 100"],
486
  ["Hemoglobin: 9.5, Iron: 40, Ferritin: 15"],
487
  ["TSH: 8.5, T4: 4.0, T3: 80"],
488
- ['{"Glucose": 140, "HbA1c": 7.0, "Triglycerides": 250}'],
489
  ],
490
  inputs=input_text,
491
- label="Click an example to load it",
492
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
 
494
- # Output column
495
- with gr.Column(scale=2):
496
- gr.Markdown("### πŸ“Š Analysis Results")
497
 
498
- with gr.Tabs():
499
- with gr.Tab("Summary"):
500
  summary_output = gr.Markdown(
501
- label="Analysis Summary",
502
- value="*Enter biomarkers and click Analyze to see results*"
 
 
 
 
 
 
503
  )
504
 
505
- with gr.Tab("Detailed JSON"):
506
  details_output = gr.Code(
507
- label="Full Response",
508
  language="json",
509
- lines=25,
 
510
  )
511
 
512
- # Event handlers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  analyze_btn.click(
514
  fn=analyze_biomarkers,
515
  inputs=[input_text],
@@ -518,40 +994,15 @@ def create_demo() -> gr.Blocks:
518
  )
519
 
520
  clear_btn.click(
521
- fn=lambda: ("", "", "", ""),
 
 
 
 
 
 
522
  outputs=[input_text, summary_output, details_output, status_output],
523
  )
524
-
525
- # Footer
526
- gr.Markdown("""
527
- ---
528
-
529
- ### ℹ️ About MediGuard AI
530
-
531
- MediGuard AI uses a **Clinical Insight Guild** of 6 specialized AI agents:
532
-
533
- | Agent | Role |
534
- |-------|------|
535
- | πŸ”¬ Biomarker Analyzer | Validates and flags abnormal values |
536
- | πŸ“š Disease Explainer | RAG-powered pathophysiology explanations |
537
- | πŸ”— Biomarker Linker | Connects biomarkers to disease predictions |
538
- | πŸ“‹ Clinical Guidelines | Evidence-based recommendations from medical literature |
539
- | βœ… Confidence Assessor | Evaluates reliability of findings |
540
- | πŸ“ Response Synthesizer | Compiles comprehensive patient-friendly output |
541
-
542
- **Data Sources**: 750+ pages of clinical guidelines (FAISS vector store)
543
-
544
- ---
545
-
546
- ⚠️ **Medical Disclaimer**: This tool is for **informational purposes only** and does not
547
- replace professional medical advice, diagnosis, or treatment. Always consult a qualified
548
- healthcare provider with questions regarding a medical condition.
549
-
550
- ---
551
-
552
- Built with ❀️ using [LangGraph](https://langchain-ai.github.io/langgraph/),
553
- [FAISS](https://faiss.ai/), and [Gradio](https://gradio.app/)
554
- """)
555
 
556
  return demo
557
 
 
176
  Returns: (summary, details_json, status)
177
  """
178
  if not input_text.strip():
179
+ return "", "", """
180
+ <div style="background: linear-gradient(135deg, #f0f4f8 0%, #e2e8f0 100%); border: 1px solid #cbd5e1; border-radius: 10px; padding: 16px; text-align: center;">
181
+ <span style="font-size: 2em;">✍️</span>
182
+ <p style="margin: 8px 0 0 0; color: #64748b;">Please enter biomarkers to analyze.</p>
183
+ </div>
184
+ """
185
 
186
  # Check API key dynamically (HF injects secrets after startup)
187
  groq_key, google_key = get_api_keys()
188
 
189
  if not groq_key and not google_key:
190
+ return "", "", """
191
+ <div style="background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); border: 1px solid #ef4444; border-radius: 10px; padding: 16px;">
192
+ <strong style="color: #dc2626;">❌ No API Key Configured</strong>
193
+ <p style="margin: 12px 0 8px 0; color: #991b1b;">Please add your API key in Space Settings β†’ Secrets:</p>
194
+ <ul style="margin: 0; color: #7f1d1d;">
195
+ <li><code>GROQ_API_KEY</code> - <a href="https://console.groq.com/keys" target="_blank" style="color: #2563eb;">Get free key β†’</a></li>
196
+ <li><code>GOOGLE_API_KEY</code> - <a href="https://aistudio.google.com/app/apikey" target="_blank" style="color: #2563eb;">Get free key β†’</a></li>
197
+ </ul>
198
+ </div>
199
+ """
200
 
201
  # Setup provider based on available key
202
  provider = setup_llm_provider()
203
  logger.info(f"Using LLM provider: {provider}")
204
 
205
  try:
206
+ progress(0.1, desc="πŸ“ Parsing biomarkers...")
207
  biomarkers = parse_biomarkers(input_text)
208
 
209
  if not biomarkers:
210
+ return "", "", """
211
+ <div style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border: 1px solid #fbbf24; border-radius: 10px; padding: 16px;">
212
+ <strong>⚠️ Could not parse biomarkers</strong>
213
+ <p style="margin: 8px 0 0 0; color: #92400e;">Try formats like:</p>
214
+ <ul style="margin: 8px 0 0 0; color: #92400e;">
215
+ <li><code>Glucose: 140, HbA1c: 7.5</code></li>
216
+ <li><code>{"Glucose": 140, "HbA1c": 7.5}</code></li>
217
+ </ul>
218
+ </div>
219
+ """
220
 
221
+ progress(0.2, desc="πŸ”§ Initializing AI agents...")
222
 
223
  # Initialize guild
224
  guild = get_guild()
 
235
  patient_context={"patient_id": "HF_User", "source": "huggingface_spaces"}
236
  )
237
 
238
+ progress(0.4, desc="πŸ€– Running Clinical Insight Guild...")
239
 
240
  # Run analysis
241
  start = time.time()
242
  result = guild.run(patient_input)
243
  elapsed = time.time() - start
244
 
245
+ progress(0.9, desc="✨ Formatting results...")
246
 
247
  # Extract response
248
  final_response = result.get("final_response", {})
 
253
  # Format details
254
  details = json.dumps(final_response, indent=2, default=str)
255
 
256
+ status = f"""
257
+ <div style="background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%); border: 1px solid #10b981; border-radius: 10px; padding: 12px; display: flex; align-items: center; gap: 10px;">
258
+ <span style="font-size: 1.5em;">βœ…</span>
259
+ <div>
260
+ <strong style="color: #047857;">Analysis Complete</strong>
261
+ <span style="color: #065f46; margin-left: 8px;">({elapsed:.1f}s)</span>
262
+ </div>
263
+ </div>
264
+ """
265
 
266
  return summary, details, status
267
 
268
  except Exception as exc:
269
  logger.error(f"Analysis error: {exc}", exc_info=True)
270
+ error_msg = f"""
271
+ <div style="background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); border: 1px solid #ef4444; border-radius: 10px; padding: 16px;">
272
+ <strong style="color: #dc2626;">❌ Analysis Error</strong>
273
+ <p style="margin: 8px 0 0 0; color: #991b1b;">{exc}</p>
274
+ <details style="margin-top: 12px;">
275
+ <summary style="cursor: pointer; color: #7f1d1d;">Show details</summary>
276
+ <pre style="margin-top: 8px; padding: 12px; background: #fef2f2; border-radius: 6px; overflow-x: auto; font-size: 0.8em;">{traceback.format_exc()}</pre>
277
+ </details>
278
+ </div>
279
+ """
280
+ return "", "", error_msg
281
 
282
 
283
  def auto_predict(biomarkers: dict[str, float]) -> dict[str, Any]:
 
346
 
347
 
348
  def format_summary(response: dict, elapsed: float) -> str:
349
+ """Format the analysis response as beautiful HTML/markdown."""
350
  if not response:
351
+ return """
352
+ <div style="text-align: center; padding: 40px; color: #94a3b8;">
353
+ <div style="font-size: 3em;">❌</div>
354
+ <p>No analysis results available.</p>
355
+ </div>
356
+ """
357
 
358
  parts = []
359
 
360
+ # Header with primary finding and confidence
361
+ primary = response.get("primary_finding", "Analysis Complete")
362
  confidence = response.get("confidence", {})
363
  conf_score = confidence.get("overall_score", 0) if isinstance(confidence, dict) else 0
364
 
365
+ # Determine severity color
366
+ severity = response.get("severity", "low")
367
+ severity_colors = {
368
+ "critical": ("#dc2626", "#fef2f2", "πŸ”΄"),
369
+ "high": ("#ea580c", "#fff7ed", "🟠"),
370
+ "moderate": ("#ca8a04", "#fefce8", "🟑"),
371
+ "low": ("#16a34a", "#f0fdf4", "🟒")
372
+ }
373
+ color, bg_color, emoji = severity_colors.get(severity, severity_colors["low"])
374
+
375
+ # Confidence badge
376
+ conf_badge = ""
377
  if conf_score:
378
+ conf_pct = int(conf_score * 100)
379
+ conf_color = "#16a34a" if conf_pct >= 80 else "#ca8a04" if conf_pct >= 60 else "#dc2626"
380
+ conf_badge = f'<span style="background: {conf_color}; color: white; padding: 4px 12px; border-radius: 20px; font-size: 0.85em; margin-left: 12px;">{conf_pct}% confidence</span>'
381
+
382
+ parts.append(f"""
383
+ <div style="background: linear-gradient(135deg, {bg_color} 0%, white 100%); border-left: 4px solid {color}; border-radius: 12px; padding: 20px; margin-bottom: 20px;">
384
+ <div style="display: flex; align-items: center; flex-wrap: wrap;">
385
+ <span style="font-size: 1.5em; margin-right: 12px;">{emoji}</span>
386
+ <h2 style="margin: 0; color: {color}; font-size: 1.4em;">{primary}</h2>
387
+ {conf_badge}
388
+ </div>
389
+ </div>
390
+ """)
391
 
392
  # Critical Alerts
393
  alerts = response.get("safety_alerts", [])
394
  if alerts:
395
+ alert_items = ""
396
  for alert in alerts[:5]:
397
  if isinstance(alert, dict):
398
+ alert_items += f'<li><strong>{alert.get("alert_type", "Alert")}:</strong> {alert.get("message", "")}</li>'
399
  else:
400
+ alert_items += f'<li>{alert}</li>'
401
+
402
+ parts.append(f"""
403
+ <div style="background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%); border: 1px solid #fecaca; border-radius: 12px; padding: 16px; margin-bottom: 16px;">
404
+ <h4 style="margin: 0 0 12px 0; color: #dc2626; display: flex; align-items: center; gap: 8px;">
405
+ ⚠️ Critical Alerts
406
+ </h4>
407
+ <ul style="margin: 0; padding-left: 20px; color: #991b1b;">{alert_items}</ul>
408
+ </div>
409
+ """)
410
 
411
  # Key Findings
412
  findings = response.get("key_findings", [])
413
  if findings:
414
+ finding_items = "".join([f'<li style="margin-bottom: 8px;">{f}</li>' for f in findings[:5]])
415
+ parts.append(f"""
416
+ <div style="background: #f8fafc; border-radius: 12px; padding: 16px; margin-bottom: 16px;">
417
+ <h4 style="margin: 0 0 12px 0; color: #1e3a5f;">πŸ” Key Findings</h4>
418
+ <ul style="margin: 0; padding-left: 20px; color: #475569;">{finding_items}</ul>
419
+ </div>
420
+ """)
421
 
422
+ # Biomarker Flags - as a visual grid
423
  flags = response.get("biomarker_flags", [])
424
  if flags:
425
+ flag_cards = ""
426
  for flag in flags[:8]:
427
  if isinstance(flag, dict):
428
  name = flag.get("biomarker", "Unknown")
429
  status = flag.get("status", "normal")
430
  value = flag.get("value", "N/A")
431
+
432
+ status_styles = {
433
+ "critical": ("πŸ”΄", "#dc2626", "#fef2f2"),
434
+ "abnormal": ("🟑", "#ca8a04", "#fefce8"),
435
+ "normal": ("🟒", "#16a34a", "#f0fdf4")
436
+ }
437
+ s_emoji, s_color, s_bg = status_styles.get(status, status_styles["normal"])
438
+
439
+ flag_cards += f"""
440
+ <div style="background: {s_bg}; border: 1px solid {s_color}33; border-radius: 8px; padding: 12px; text-align: center;">
441
+ <div style="font-size: 1.2em;">{s_emoji}</div>
442
+ <div style="font-weight: 600; color: #1e3a5f; margin: 4px 0;">{name}</div>
443
+ <div style="font-size: 1.1em; color: {s_color};">{value}</div>
444
+ <div style="font-size: 0.8em; color: #64748b; text-transform: uppercase;">{status}</div>
445
+ </div>
446
+ """
447
+
448
+ parts.append(f"""
449
+ <div style="margin-bottom: 16px;">
450
+ <h4 style="margin: 0 0 12px 0; color: #1e3a5f;">πŸ“Š Biomarker Analysis</h4>
451
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 12px;">
452
+ {flag_cards}
453
+ </div>
454
+ </div>
455
+ """)
456
 
457
+ # Recommendations - organized sections
458
  recs = response.get("recommendations", {})
459
  if recs:
460
+ rec_sections = ""
461
 
462
  immediate = recs.get("immediate_actions", [])
463
  if immediate:
464
+ items = "".join([f'<li style="margin-bottom: 6px;">{a}</li>' for a in immediate[:3]])
465
+ rec_sections += f"""
466
+ <div style="margin-bottom: 12px;">
467
+ <h5 style="margin: 0 0 8px 0; color: #dc2626;">🚨 Immediate Actions</h5>
468
+ <ul style="margin: 0; padding-left: 20px; color: #475569;">{items}</ul>
469
+ </div>
470
+ """
471
 
472
  lifestyle = recs.get("lifestyle_modifications", [])
473
  if lifestyle:
474
+ items = "".join([f'<li style="margin-bottom: 6px;">{m}</li>' for m in lifestyle[:3]])
475
+ rec_sections += f"""
476
+ <div style="margin-bottom: 12px;">
477
+ <h5 style="margin: 0 0 8px 0; color: #16a34a;">🌿 Lifestyle Modifications</h5>
478
+ <ul style="margin: 0; padding-left: 20px; color: #475569;">{items}</ul>
479
+ </div>
480
+ """
481
 
482
  followup = recs.get("follow_up", [])
483
  if followup:
484
+ items = "".join([f'<li style="margin-bottom: 6px;">{f}</li>' for f in followup[:3]])
485
+ rec_sections += f"""
486
+ <div>
487
+ <h5 style="margin: 0 0 8px 0; color: #2563eb;">πŸ“… Follow-up</h5>
488
+ <ul style="margin: 0; padding-left: 20px; color: #475569;">{items}</ul>
489
+ </div>
490
+ """
491
+
492
+ if rec_sections:
493
+ parts.append(f"""
494
+ <div style="background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); border-radius: 12px; padding: 16px; margin-bottom: 16px;">
495
+ <h4 style="margin: 0 0 16px 0; color: #1e3a5f;">πŸ’‘ Recommendations</h4>
496
+ {rec_sections}
497
+ </div>
498
+ """)
499
 
500
  # Disease Explanation
501
  explanation = response.get("disease_explanation", {})
502
  if explanation and isinstance(explanation, dict):
 
 
503
  pathophys = explanation.get("pathophysiology", "")
504
  if pathophys:
505
+ parts.append(f"""
506
+ <div style="background: #f8fafc; border-radius: 12px; padding: 16px; margin-bottom: 16px;">
507
+ <h4 style="margin: 0 0 12px 0; color: #1e3a5f;">πŸ“– Understanding Your Results</h4>
508
+ <p style="margin: 0; color: #475569; line-height: 1.6;">{pathophys[:600]}{'...' if len(pathophys) > 600 else ''}</p>
509
+ </div>
510
+ """)
511
 
512
  # Conversational Summary
513
  conv_summary = response.get("conversational_summary", "")
514
  if conv_summary:
515
+ parts.append(f"""
516
+ <div style="background: linear-gradient(135deg, #faf5ff 0%, #f3e8ff 100%); border-radius: 12px; padding: 16px; margin-bottom: 16px;">
517
+ <h4 style="margin: 0 0 12px 0; color: #7c3aed;">πŸ“ Summary</h4>
518
+ <p style="margin: 0; color: #475569; line-height: 1.6;">{conv_summary[:1000]}</p>
519
+ </div>
520
+ """)
521
 
522
  # Footer
523
+ parts.append(f"""
524
+ <div style="border-top: 1px solid #e2e8f0; padding-top: 16px; margin-top: 8px; text-align: center;">
525
+ <p style="margin: 0 0 8px 0; color: #94a3b8; font-size: 0.9em;">
526
+ ✨ Analysis completed in <strong>{elapsed:.1f}s</strong> using Agentic RagBot
527
+ </p>
528
+ <p style="margin: 0; color: #f59e0b; font-size: 0.85em;">
529
+ ⚠️ <em>This is for informational purposes only. Consult a healthcare professional for medical advice.</em>
530
+ </p>
531
+ </div>
532
+ """)
533
 
534
  return "\n".join(parts)
535
 
 
538
  # Gradio Interface
539
  # ---------------------------------------------------------------------------
540
 
541
+ # Custom CSS for modern medical UI
542
+ CUSTOM_CSS = """
543
+ /* Global Styles */
544
+ .gradio-container {
545
+ max-width: 1400px !important;
546
+ margin: auto !important;
547
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
548
+ }
549
+
550
+ /* Hide footer */
551
+ footer { display: none !important; }
552
+
553
+ /* Header styling */
554
+ .header-container {
555
+ background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 50%, #3d7ab5 100%);
556
+ border-radius: 16px;
557
+ padding: 32px;
558
+ margin-bottom: 24px;
559
+ color: white;
560
+ text-align: center;
561
+ box-shadow: 0 8px 32px rgba(30, 58, 95, 0.3);
562
+ }
563
+
564
+ .header-container h1 {
565
+ margin: 0 0 12px 0;
566
+ font-size: 2.5em;
567
+ font-weight: 700;
568
+ text-shadow: 0 2px 4px rgba(0,0,0,0.2);
569
+ }
570
+
571
+ .header-container p {
572
+ margin: 0;
573
+ opacity: 0.95;
574
+ font-size: 1.1em;
575
+ }
576
+
577
+ /* Input panel */
578
+ .input-panel {
579
+ background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
580
+ border-radius: 16px;
581
+ padding: 24px;
582
+ border: 1px solid #e2e8f0;
583
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
584
+ }
585
+
586
+ /* Output panel */
587
+ .output-panel {
588
+ background: white;
589
+ border-radius: 16px;
590
+ padding: 24px;
591
+ border: 1px solid #e2e8f0;
592
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
593
+ min-height: 500px;
594
+ }
595
+
596
+ /* Status badges */
597
+ .status-success {
598
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
599
+ color: white;
600
+ padding: 12px 20px;
601
+ border-radius: 10px;
602
+ font-weight: 600;
603
+ display: inline-block;
604
+ }
605
+
606
+ .status-error {
607
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
608
+ color: white;
609
+ padding: 12px 20px;
610
+ border-radius: 10px;
611
+ font-weight: 600;
612
+ }
613
+
614
+ .status-warning {
615
+ background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
616
+ color: white;
617
+ padding: 12px 20px;
618
+ border-radius: 10px;
619
+ font-weight: 600;
620
+ }
621
+
622
+ /* Info banner */
623
+ .info-banner {
624
+ background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
625
+ border: 1px solid #93c5fd;
626
+ border-radius: 12px;
627
+ padding: 16px 20px;
628
+ margin: 16px 0;
629
+ display: flex;
630
+ align-items: center;
631
+ gap: 12px;
632
+ }
633
+
634
+ .info-banner-icon {
635
+ font-size: 1.5em;
636
+ }
637
+
638
+ /* Agent cards */
639
+ .agent-grid {
640
+ display: grid;
641
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
642
+ gap: 16px;
643
+ margin: 20px 0;
644
+ }
645
+
646
+ .agent-card {
647
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
648
+ border: 1px solid #e2e8f0;
649
+ border-radius: 12px;
650
+ padding: 20px;
651
+ transition: all 0.3s ease;
652
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
653
+ }
654
+
655
+ .agent-card:hover {
656
+ transform: translateY(-2px);
657
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
658
+ border-color: #3b82f6;
659
+ }
660
+
661
+ .agent-card h4 {
662
+ margin: 0 0 8px 0;
663
+ color: #1e3a5f;
664
+ font-size: 1em;
665
+ }
666
+
667
+ .agent-card p {
668
+ margin: 0;
669
+ color: #64748b;
670
+ font-size: 0.9em;
671
+ }
672
+
673
+ /* Example buttons */
674
+ .example-btn {
675
+ background: #f1f5f9;
676
+ border: 1px solid #cbd5e1;
677
+ border-radius: 8px;
678
+ padding: 10px 14px;
679
+ cursor: pointer;
680
+ transition: all 0.2s ease;
681
+ text-align: left;
682
+ font-size: 0.85em;
683
+ }
684
+
685
+ .example-btn:hover {
686
+ background: #e2e8f0;
687
+ border-color: #94a3b8;
688
+ }
689
+
690
+ /* Buttons */
691
+ .primary-btn {
692
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
693
+ border: none !important;
694
+ border-radius: 12px !important;
695
+ padding: 14px 28px !important;
696
+ font-weight: 600 !important;
697
+ font-size: 1.1em !important;
698
+ box-shadow: 0 4px 14px rgba(59, 130, 246, 0.4) !important;
699
+ transition: all 0.3s ease !important;
700
+ }
701
+
702
+ .primary-btn:hover {
703
+ transform: translateY(-2px) !important;
704
+ box-shadow: 0 6px 20px rgba(59, 130, 246, 0.5) !important;
705
+ }
706
+
707
+ .secondary-btn {
708
+ background: #f1f5f9 !important;
709
+ border: 1px solid #cbd5e1 !important;
710
+ border-radius: 12px !important;
711
+ padding: 14px 28px !important;
712
+ font-weight: 500 !important;
713
+ transition: all 0.2s ease !important;
714
+ }
715
+
716
+ .secondary-btn:hover {
717
+ background: #e2e8f0 !important;
718
+ }
719
+
720
+ /* Results tabs */
721
+ .results-tabs {
722
+ border-radius: 12px;
723
+ overflow: hidden;
724
+ }
725
+
726
+ /* Disclaimer */
727
+ .disclaimer {
728
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
729
+ border: 1px solid #fbbf24;
730
+ border-radius: 12px;
731
+ padding: 16px 20px;
732
+ margin-top: 24px;
733
+ font-size: 0.9em;
734
+ }
735
+
736
+ /* Feature badges */
737
+ .feature-badge {
738
+ display: inline-block;
739
+ background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%);
740
+ color: #4338ca;
741
+ padding: 6px 12px;
742
+ border-radius: 20px;
743
+ font-size: 0.8em;
744
+ font-weight: 600;
745
+ margin: 4px;
746
+ }
747
+
748
+ /* Section titles */
749
+ .section-title {
750
+ font-size: 1.25em;
751
+ font-weight: 600;
752
+ color: #1e3a5f;
753
+ margin-bottom: 16px;
754
+ display: flex;
755
+ align-items: center;
756
+ gap: 8px;
757
+ }
758
+
759
+ /* Animations */
760
+ @keyframes pulse {
761
+ 0%, 100% { opacity: 1; }
762
+ 50% { opacity: 0.7; }
763
+ }
764
+
765
+ .analyzing {
766
+ animation: pulse 1.5s ease-in-out infinite;
767
+ }
768
+ """
769
+
770
+
771
  def create_demo() -> gr.Blocks:
772
+ """Create the Gradio Blocks interface with modern medical UI."""
773
 
774
  with gr.Blocks(
775
+ title="Agentic RagBot - Medical Biomarker Analysis",
776
+ theme=gr.themes.Soft(
777
+ primary_hue=gr.themes.colors.blue,
778
+ secondary_hue=gr.themes.colors.slate,
779
+ neutral_hue=gr.themes.colors.slate,
780
+ font=gr.themes.GoogleFont("Inter"),
781
+ font_mono=gr.themes.GoogleFont("JetBrains Mono"),
782
+ ).set(
783
+ body_background_fill="linear-gradient(135deg, #f0f4f8 0%, #e2e8f0 100%)",
784
+ block_background_fill="white",
785
+ block_border_width="0px",
786
+ block_shadow="0 4px 16px rgba(0, 0, 0, 0.08)",
787
+ block_radius="16px",
788
+ button_primary_background_fill="linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)",
789
+ button_primary_background_fill_hover="linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)",
790
+ button_primary_text_color="white",
791
+ button_primary_shadow="0 4px 14px rgba(59, 130, 246, 0.4)",
792
+ input_background_fill="#f8fafc",
793
+ input_border_width="1px",
794
+ input_border_color="#e2e8f0",
795
+ input_radius="12px",
796
+ ),
797
+ css=CUSTOM_CSS,
798
  ) as demo:
799
 
800
+ # ===== HEADER =====
801
+ gr.HTML("""
802
+ <div class="header-container">
803
+ <h1>πŸ₯ Agentic RagBot</h1>
804
+ <p>Multi-Agent RAG System for Medical Biomarker Analysis</p>
805
+ <div style="margin-top: 16px;">
806
+ <span class="feature-badge">πŸ€– 6 AI Agents</span>
807
+ <span class="feature-badge">πŸ“š RAG-Powered</span>
808
+ <span class="feature-badge">⚑ Real-time Analysis</span>
809
+ <span class="feature-badge">πŸ”¬ Evidence-Based</span>
810
+ </div>
811
+ </div>
812
  """)
813
 
814
+ # ===== API KEY INFO =====
815
+ gr.HTML("""
816
+ <div class="info-banner">
817
+ <span class="info-banner-icon">πŸ”‘</span>
818
+ <div>
819
+ <strong>Setup Required:</strong> Add your <code>GROQ_API_KEY</code> or
820
+ <code>GOOGLE_API_KEY</code> in Space Settings β†’ Secrets to enable analysis.
821
+ <a href="https://console.groq.com/keys" target="_blank" style="color: #2563eb;">Get free Groq key β†’</a>
822
+ </div>
823
  </div>
824
  """)
825
 
826
+ # ===== MAIN CONTENT =====
827
+ with gr.Row(equal_height=False):
828
+
829
+ # ----- LEFT PANEL: INPUT -----
830
+ with gr.Column(scale=2, min_width=400):
831
+ gr.HTML('<div class="section-title">πŸ“ Enter Your Biomarkers</div>')
 
 
 
 
 
 
 
 
 
 
832
 
833
+ with gr.Group():
834
+ input_text = gr.Textbox(
835
+ label="",
836
+ placeholder="Enter biomarkers in any format:\n\nβ€’ Glucose: 140, HbA1c: 7.5, Cholesterol: 210\nβ€’ My glucose is 140 and HbA1c is 7.5\nβ€’ {\"Glucose\": 140, \"HbA1c\": 7.5}",
837
+ lines=6,
838
+ max_lines=12,
839
+ show_label=False,
840
+ )
841
+
842
+ with gr.Row():
843
+ analyze_btn = gr.Button(
844
+ "πŸ”¬ Analyze Biomarkers",
845
+ variant="primary",
846
+ size="lg",
847
+ scale=3,
848
+ )
849
+ clear_btn = gr.Button(
850
+ "πŸ—‘οΈ Clear",
851
+ variant="secondary",
852
+ size="lg",
853
+ scale=1,
854
+ )
855
 
856
+ # Status display
857
  status_output = gr.Markdown(
858
+ value="",
859
  elem_classes="status-box"
860
  )
861
 
862
+ # Quick Examples
863
+ gr.HTML('<div class="section-title" style="margin-top: 24px;">⚑ Quick Examples</div>')
864
+ gr.HTML('<p style="color: #64748b; font-size: 0.9em; margin-bottom: 12px;">Click any example to load it instantly</p>')
865
 
866
  examples = gr.Examples(
867
  examples=[
 
869
  ["Glucose: 95, HbA1c: 5.4, Cholesterol: 180, HDL: 55, LDL: 100"],
870
  ["Hemoglobin: 9.5, Iron: 40, Ferritin: 15"],
871
  ["TSH: 8.5, T4: 4.0, T3: 80"],
872
+ ["Creatinine: 2.5, BUN: 45, eGFR: 35"],
873
  ],
874
  inputs=input_text,
875
+ label="",
876
  )
877
+
878
+ # Supported Biomarkers
879
+ with gr.Accordion("πŸ“Š Supported Biomarkers", open=False):
880
+ gr.HTML("""
881
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; padding: 12px;">
882
+ <div>
883
+ <h4 style="color: #1e3a5f; margin: 0 0 8px 0;">🩸 Diabetes</h4>
884
+ <p style="color: #64748b; font-size: 0.85em; margin: 0;">Glucose, HbA1c, Fasting Glucose, Insulin</p>
885
+ </div>
886
+ <div>
887
+ <h4 style="color: #1e3a5f; margin: 0 0 8px 0;">❀️ Cardiovascular</h4>
888
+ <p style="color: #64748b; font-size: 0.85em; margin: 0;">Cholesterol, LDL, HDL, Triglycerides</p>
889
+ </div>
890
+ <div>
891
+ <h4 style="color: #1e3a5f; margin: 0 0 8px 0;">🫘 Kidney</h4>
892
+ <p style="color: #64748b; font-size: 0.85em; margin: 0;">Creatinine, BUN, eGFR, Uric Acid</p>
893
+ </div>
894
+ <div>
895
+ <h4 style="color: #1e3a5f; margin: 0 0 8px 0;">🦴 Liver</h4>
896
+ <p style="color: #64748b; font-size: 0.85em; margin: 0;">ALT, AST, Bilirubin, Albumin</p>
897
+ </div>
898
+ <div>
899
+ <h4 style="color: #1e3a5f; margin: 0 0 8px 0;">πŸ¦‹ Thyroid</h4>
900
+ <p style="color: #64748b; font-size: 0.85em; margin: 0;">TSH, T3, T4, Free T4</p>
901
+ </div>
902
+ <div>
903
+ <h4 style="color: #1e3a5f; margin: 0 0 8px 0;">πŸ’‰ Blood</h4>
904
+ <p style="color: #64748b; font-size: 0.85em; margin: 0;">Hemoglobin, WBC, RBC, Platelets</p>
905
+ </div>
906
+ </div>
907
+ """)
908
 
909
+ # ----- RIGHT PANEL: RESULTS -----
910
+ with gr.Column(scale=3, min_width=500):
911
+ gr.HTML('<div class="section-title">πŸ“Š Analysis Results</div>')
912
 
913
+ with gr.Tabs() as result_tabs:
914
+ with gr.Tab("πŸ“‹ Summary", id="summary"):
915
  summary_output = gr.Markdown(
916
+ value="""
917
+ <div style="text-align: center; padding: 60px 20px; color: #94a3b8;">
918
+ <div style="font-size: 4em; margin-bottom: 16px;">πŸ”¬</div>
919
+ <h3 style="color: #64748b; font-weight: 500;">Ready to Analyze</h3>
920
+ <p>Enter your biomarkers on the left and click <strong>Analyze</strong> to get your personalized health insights.</p>
921
+ </div>
922
+ """,
923
+ elem_classes="summary-output"
924
  )
925
 
926
+ with gr.Tab("πŸ” Detailed JSON", id="json"):
927
  details_output = gr.Code(
928
+ label="",
929
  language="json",
930
+ lines=30,
931
+ show_label=False,
932
  )
933
 
934
+ # ===== HOW IT WORKS =====
935
+ gr.HTML('<div class="section-title" style="margin-top: 32px;">πŸ€– How It Works</div>')
936
+
937
+ gr.HTML("""
938
+ <div class="agent-grid">
939
+ <div class="agent-card">
940
+ <h4>πŸ”¬ Biomarker Analyzer</h4>
941
+ <p>Validates your biomarker values against clinical reference ranges and flags any abnormalities.</p>
942
+ </div>
943
+ <div class="agent-card">
944
+ <h4>πŸ“š Disease Explainer</h4>
945
+ <p>Uses RAG to retrieve relevant medical literature and explain potential conditions.</p>
946
+ </div>
947
+ <div class="agent-card">
948
+ <h4>πŸ”— Biomarker Linker</h4>
949
+ <p>Connects your specific biomarker patterns to disease predictions with clinical evidence.</p>
950
+ </div>
951
+ <div class="agent-card">
952
+ <h4>πŸ“‹ Clinical Guidelines</h4>
953
+ <p>Retrieves evidence-based recommendations from 750+ pages of medical guidelines.</p>
954
+ </div>
955
+ <div class="agent-card">
956
+ <h4>βœ… Confidence Assessor</h4>
957
+ <p>Evaluates the reliability of findings based on data quality and evidence strength.</p>
958
+ </div>
959
+ <div class="agent-card">
960
+ <h4>πŸ“ Response Synthesizer</h4>
961
+ <p>Compiles all insights into a comprehensive, easy-to-understand patient report.</p>
962
+ </div>
963
+ </div>
964
+ """)
965
+
966
+ # ===== DISCLAIMER =====
967
+ gr.HTML("""
968
+ <div class="disclaimer">
969
+ <strong>⚠️ Medical Disclaimer:</strong> This tool is for <strong>informational purposes only</strong>
970
+ and does not replace professional medical advice, diagnosis, or treatment. Always consult a qualified
971
+ healthcare provider with questions regarding a medical condition. The AI analysis is based on general
972
+ clinical guidelines and may not account for your specific medical history.
973
+ </div>
974
+ """)
975
+
976
+ # ===== FOOTER =====
977
+ gr.HTML("""
978
+ <div style="text-align: center; padding: 24px; color: #94a3b8; font-size: 0.85em; margin-top: 24px;">
979
+ <p>Built with ❀️ using
980
+ <a href="https://langchain-ai.github.io/langgraph/" target="_blank" style="color: #3b82f6;">LangGraph</a>,
981
+ <a href="https://faiss.ai/" target="_blank" style="color: #3b82f6;">FAISS</a>, and
982
+ <a href="https://gradio.app/" target="_blank" style="color: #3b82f6;">Gradio</a>
983
+ </p>
984
+ <p style="margin-top: 8px;">Powered by <strong>Groq</strong> (LLaMA 3.3-70B) β€’ Open Source on GitHub</p>
985
+ </div>
986
+ """)
987
+
988
+ # ===== EVENT HANDLERS =====
989
  analyze_btn.click(
990
  fn=analyze_biomarkers,
991
  inputs=[input_text],
 
994
  )
995
 
996
  clear_btn.click(
997
+ fn=lambda: ("", """
998
+ <div style="text-align: center; padding: 60px 20px; color: #94a3b8;">
999
+ <div style="font-size: 4em; margin-bottom: 16px;">πŸ”¬</div>
1000
+ <h3 style="color: #64748b; font-weight: 500;">Ready to Analyze</h3>
1001
+ <p>Enter your biomarkers on the left and click <strong>Analyze</strong> to get your personalized health insights.</p>
1002
+ </div>
1003
+ """, "", ""),
1004
  outputs=[input_text, summary_output, details_output, status_output],
1005
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1006
 
1007
  return demo
1008