Saicharan21 commited on
Commit
19a951c
·
verified ·
1 Parent(s): 3e80eff

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +178 -328
app.py CHANGED
@@ -1,409 +1,259 @@
1
  import gradio as gr
2
- import os
3
- import requests
4
  import matplotlib
5
- matplotlib.use("Agg")
6
  import matplotlib.pyplot as plt
7
  import matplotlib.patches as mpatches
8
  import numpy as np
9
  from groq import Groq
10
- import io
11
  from PIL import Image
12
 
13
- GROQ_KEY = os.environ.get("GROQ_API_KEY", "")
 
14
 
15
- KNOWHOW = ("SJSU CardioLab: "
16
- "MCL: Sylgard 184 PDMS 10:1 ratio 48hr cure green laser PIV 70bpm 5L/min. "
17
- "TGT: Arduino Uno Stepper Motor 150mL blood sampled at 0 20 40 60min measures TAT PF1.2 hemolysis platelets. "
18
- "uPAD: Jaffe reaction creatinine plus picric acid gives orange-red color normal 0.6-1.2 mg/dL CKD above 1.5. "
19
- "MHV: 27mm SJM Regent bileaflet also trileaflet monoleaflet pediatric. "
20
- "Equipment: Heska HT5 hematology analyzer time-resolved PIV Tygon tubing Arduino Uno.")
21
-
22
- CSS = """
23
  body, .gradio-container { background: #f0f4f8 !important; color: #1a202c !important; }
24
  .tab-nav { background: #ffffff !important; border-bottom: 2px solid #e2e8f0 !important; padding: 0 10px !important; }
25
- .tab-nav button { background: #f7fafc !important; color: #2d3748 !important; border: 1px solid #e2e8f0 !important; border-radius: 8px 8px 0 0 !important; padding: 12px 18px !important; font-size: 0.9em !important; font-weight: 600 !important; margin-top: 6px !important; transition: all 0.2s !important; }
26
- .tab-nav button:hover { background: #ebf4ff !important; color: #1a237e !important; border-color: #4361ee !important; }
27
- .tab-nav button.selected { background: linear-gradient(135deg, #e63946, #c1121f) !important; color: #ffffff !important; border-color: #e63946 !important; font-weight: 700 !important; }
28
- .tabitem { background: #ffffff !important; padding: 20px !important; border: 1px solid #e2e8f0 !important; border-top: none !important; border-radius: 0 0 12px 12px !important; }
29
- .main-header { background: linear-gradient(135deg, #1a237e 0%, #b71c1c 100%); border-bottom: 3px solid #e63946; padding: 25px 20px; text-align: center; border-radius: 12px 12px 0 0; }
30
- textarea, input[type=number], input[type=text] { background: #f7fafc !important; color: #1a202c !important; border: 1px solid #cbd5e0 !important; border-radius: 8px !important; }
31
- textarea::placeholder { color: #a0aec0 !important; }
32
- button.primary { background: linear-gradient(135deg, #e63946 0%, #c1121f 100%) !important; color: white !important; border: none !important; border-radius: 8px !important; font-weight: 700 !important; padding: 12px 24px !important; }
33
  button.secondary { background: #edf2f7 !important; color: #4a5568 !important; border: 1px solid #cbd5e0 !important; border-radius: 8px !important; }
34
- label span, .label-wrap span { color: #2b6cb0 !important; font-weight: 600 !important; font-size: 0.85em !important; text-transform: uppercase !important; }
35
- .message.user { background: linear-gradient(135deg, #e63946, #c1121f) !important; color: white !important; border-radius: 12px 12px 4px 12px !important; }
36
- .message.bot { background: #ebf4ff !important; color: #1a202c !important; border: 1px solid #bee3f8 !important; border-radius: 12px 12px 12px 4px !important; }
37
- """
 
38
 
39
  def get_pubmed(query, n=5):
40
  try:
41
- r = requests.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi",
42
- params={"db":"pubmed","term":query+" AND (mechanical heart valve OR microfluidic OR CKD OR thrombogenicity)",
43
- "retmax":n,"retmode":"json","sort":"date"},timeout=10)
44
- ids = r.json()["esearchresult"]["idlist"]
45
- if not ids: return ""
46
- return chr(10).join(["https://pubmed.ncbi.nlm.nih.gov/"+i for i in ids])
47
- except: return ""
48
 
49
  def get_scholar(query, n=5):
50
  try:
51
- r = requests.get("https://api.semanticscholar.org/graph/v1/paper/search",
52
- params={"query":query+" biomedical","limit":n,"fields":"title,year,url,citationCount"},timeout=10)
53
- papers = r.json().get("data",[])
54
  out = []
55
  for p in papers:
56
- title = p.get("title","")
57
- year = str(p.get("year",""))
58
- url = p.get("url","")
59
- citations = str(p.get("citationCount",0))
60
- if url:
61
- out.append(title[:80]+" ("+year+") - "+citations+" citations"+chr(10)+" "+url)
62
- return chr(10)+chr(10).join(out)
63
- except: return ""
64
 
65
  def quick_search(query):
66
- if not query.strip(): return "Please enter a research topic."
67
  pubmed = get_pubmed(query, n=8)
68
  scholar = get_scholar(query, n=5)
69
- result = "PAPERS FOR: " + query + chr(10)+chr(10)
70
- result += "PUBMED (verified links):"+chr(10)+pubmed+chr(10)+chr(10)
71
- result += "SEMANTIC SCHOLAR:"+chr(10)+scholar
72
- return result
73
 
74
  def research_chat(message, history):
75
  if not GROQ_KEY:
76
- history.append({"role":"user","content":message})
77
- history.append({"role":"assistant","content":"Error: Add GROQ_API_KEY to Space Settings Secrets tab."})
78
- return "", history
79
  try:
80
  client = Groq(api_key=GROQ_KEY)
81
  pubmed = get_pubmed(message, n=3)
82
- msgs = [{"role":"system","content":"You are CardioLab AI assistant. Expert in MHV MCL PIV TGT uPAD CKD FSI. Remember full conversation. Never invent URLs. "+KNOWHOW}]
83
  for item in history:
84
- if isinstance(item, dict):
85
- msgs.append({"role":item["role"],"content":item["content"]})
86
- msgs.append({"role":"user","content":message})
87
- resp = client.chat.completions.create(model="llama-3.3-70b-versatile",messages=msgs,max_tokens=700)
88
  answer = resp.choices[0].message.content
89
- if pubmed: answer += chr(10)+chr(10)+"PUBMED LINKS:"+chr(10)+pubmed
90
- history.append({"role":"user","content":message})
91
- history.append({"role":"assistant","content":answer})
92
- return "", history
93
  except Exception as e:
94
- history.append({"role":"user","content":message})
95
- history.append({"role":"assistant","content":"Error: "+str(e)})
96
- return "", history
97
 
98
  def voice_chat(audio, history):
99
  if audio is None:
100
- history.append({"role":"assistant","content":"Please record your question first."})
101
  return history
102
  try:
103
  client = Groq(api_key=GROQ_KEY)
104
- with open(audio, "rb") as f:
105
- transcription = client.audio.transcriptions.create(
106
- file=("audio.wav", f, "audio/wav"),
107
- model="whisper-large-v3"
108
- )
109
- text = transcription.text
110
- msgs = [{"role":"system","content":"You are CardioLab AI assistant. Expert in MHV MCL PIV TGT uPAD CKD FSI. "+KNOWHOW}]
111
  for item in history:
112
- if isinstance(item, dict):
113
- msgs.append({"role":item["role"],"content":item["content"]})
114
- msgs.append({"role":"user","content":text})
115
- resp = client.chat.completions.create(model="llama-3.3-70b-versatile",messages=msgs,max_tokens=500)
116
- answer = resp.choices[0].message.content
117
- history.append({"role":"user","content":"[Voice] "+text})
118
- history.append({"role":"assistant","content":answer})
119
  return history
120
  except Exception as e:
121
- history.append({"role":"assistant","content":"Voice error: "+str(e)})
122
  return history
123
 
124
  def generate_diagram(topic):
125
- topic_lower = topic.lower()
126
- fig, ax = plt.subplots(1, 1, figsize=(12, 8))
127
- ax.set_xlim(0, 12)
128
- ax.set_ylim(0, 8)
129
- ax.axis("off")
130
- fig.patch.set_facecolor("#0d1b3e")
131
- ax.set_facecolor("#0d1b3e")
132
-
133
- if "tgt" in topic_lower or "thrombogen" in topic_lower:
134
- ax.set_title("Thrombogenicity Tester (TGT) — SJSU CardioLab", color="white", fontsize=14, fontweight="bold", pad=15)
135
- components = [
136
- (1, 6, 2, 1, "#e63946", "Arduino Uno
137
- (Controller)"),
138
- (4, 6, 2, 1, "#4361ee", "Motor Driver"),
139
- (7, 6, 2, 1, "#2ecc71", "Stepper Motor"),
140
- (4, 3.5, 4, 2, "#1a2744", "Test Chamber
141
- 27mm SJM Regent MHV
142
- (150mL Blood)"),
143
- (1, 1, 2, 1, "#e67e22", "Heska HT5
144
- Analyzer"),
145
- (9, 1, 2, 1, "#9b59b6", "Data Logger"),
146
  ]
147
- for x, y, w, h, color, label in components:
148
- rect = mpatches.FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.1", facecolor=color, edgecolor="white", linewidth=2)
149
- ax.add_patch(rect)
150
- ax.text(x+w/2, y+h/2, label, ha="center", va="center", color="white", fontsize=8, fontweight="bold")
151
- arrows = [(2, 6.5, 4, 6.5), (5, 6.5, 7, 6.5), (8, 6, 8, 5.5), (6, 3.5, 6, 2), (4, 4.5, 2, 2), (8, 3.5, 10, 2)]
152
- for x1,y1,x2,y2 in arrows:
153
- ax.annotate("", xy=(x2,y2), xytext=(x1,y1), arrowprops=dict(arrowstyle="->", color="#7eb8f7", lw=2))
154
- labels = [(3, 7, "Signal"), (5.5, 7, "Power"), (8.5, 5.7, "Rotate"), (6.5, 2.8, "Sample"), (2.8, 3.5, "Analyze"), (9, 2.8, "Record")]
155
- for x, y, t in labels:
156
- ax.text(x, y, t, color="#a8b2d8", fontsize=7, ha="center")
157
- times = ["0 min", "20 min", "40 min", "60 min"]
158
- for i, t in enumerate(times):
159
- ax.text(1+i*2.5, 0.5, t, color="#e63946", fontsize=8, ha="center", fontweight="bold")
160
- ax.text(6, 0.5, "Blood Sampling Times", color="#7eb8f7", fontsize=8, ha="center")
161
-
162
- elif "mcl" in topic_lower or "circulatory" in topic_lower or "piv" in topic_lower:
163
- ax.set_title("Mock Circulatory Loop (MCL) + PIV System — SJSU CardioLab", color="white", fontsize=13, fontweight="bold", pad=15)
164
- loop_x = [2, 4, 6, 8, 10, 10, 8, 6, 4, 2, 2]
165
- loop_y = [4, 6, 6.5, 6, 4, 3, 2, 1.5, 2, 3, 4]
166
- ax.plot(loop_x, loop_y, color="#4361ee", linewidth=4, label="Flow Loop")
167
- components = [
168
- (5, 5.8, 2, 0.8, "#e63946", "MHV Test Section
169
- (Sylgard 184)"),
170
- (1, 3.5, 1.5, 0.8, "#2ecc71", "Pump"),
171
- (8.5, 3.5, 1.5, 0.8, "#e67e22", "Compliance
172
- Chamber"),
173
- (4.5, 1.2, 2, 0.8, "#9b59b6", "Reservoir"),
174
- ]
175
- for x, y, w, h, color, label in components:
176
- rect = mpatches.FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.1", facecolor=color, edgecolor="white", linewidth=2)
177
- ax.add_patch(rect)
178
- ax.text(x+w/2, y+h/2, label, ha="center", va="center", color="white", fontsize=7, fontweight="bold")
179
- ax.annotate("", xy=(6, 7.5), xytext=(6, 6.5), arrowprops=dict(arrowstyle="->", color="#00ff88", lw=2))
180
- ax.text(6, 7.7, "Green Laser (PIV)", color="#00ff88", fontsize=8, ha="center", fontweight="bold")
181
- rect_laser = mpatches.FancyBboxPatch((5, 7.5), 2, 0.4, boxstyle="round,pad=0.05", facecolor="#00ff44", edgecolor="white", linewidth=1)
182
- ax.add_patch(rect_laser)
183
- ax.text(6, 7.7, "LASER", ha="center", va="center", color="black", fontsize=7, fontweight="bold")
184
- rect_cam = mpatches.FancyBboxPatch((9.5, 5.5), 1.5, 0.8, boxstyle="round,pad=0.05", facecolor="#1a2744", edgecolor="#7eb8f7", linewidth=2)
185
- ax.add_patch(rect_cam)
186
- ax.text(10.25, 5.9, "Camera
187
- (PIV)", ha="center", va="center", color="white", fontsize=7, fontweight="bold")
188
-
189
- elif "upad" in topic_lower or "ckd" in topic_lower or "creatinine" in topic_lower:
190
- ax.set_title("uPAD Fabrication and CKD Detection Flow — SJSU CardioLab", color="white", fontsize=13, fontweight="bold", pad=15)
191
- steps = [
192
- (0.5, 6, "#e63946", "1. Whatman
193
- Paper"),
194
- (2.5, 6, "#e67e22", "2. Wax
195
- Printer"),
196
- (4.5, 6, "#f1c40f", "3. Heat
197
- 120°C"),
198
- (6.5, 6, "#2ecc71", "4. Add
199
- Reagents"),
200
- (8.5, 6, "#4361ee", "5. Apply
201
- Sample"),
202
- (10, 6, "#9b59b6", "6. Image
203
- Analysis"),
204
- ]
205
- for x, y, color, label in steps:
206
- circle = plt.Circle((x+0.75, y+0.5), 0.7, color=color, zorder=5)
207
- ax.add_patch(circle)
208
- ax.text(x+0.75, y+0.5, label, ha="center", va="center", color="white", fontsize=7, fontweight="bold", zorder=6)
209
- for i in range(len(steps)-1):
210
- x1 = steps[i][0]+1.45
211
- x2 = steps[i+1][0]+0.05
212
- y = 6.5
213
- ax.annotate("", xy=(x2,y), xytext=(x1,y), arrowprops=dict(arrowstyle="->", color="white", lw=2))
214
- colors_ckd = ["#2ecc71", "#f1c40f", "#e67e22", "#e74c3c", "#c0392b"]
215
- stages = ["Normal
216
- <1.2", "Borderline
217
- 1.2-1.5", "Stage 2
218
- 1.5-3.0", "Stage 3-4
219
- 3.0-6.0", "Stage 5
220
- >6.0"]
221
- for i, (color, stage) in enumerate(zip(colors_ckd, stages)):
222
- rect = mpatches.FancyBboxPatch((0.5+i*2.2, 2), 2, 2.5, boxstyle="round,pad=0.1", facecolor=color, edgecolor="white", linewidth=2, alpha=0.8)
223
- ax.add_patch(rect)
224
- ax.text(1.5+i*2.2, 3.2, stage, ha="center", va="center", color="white", fontsize=8, fontweight="bold")
225
- ax.text(6, 1.5, "Creatinine Level (mg/dL)", color="#7eb8f7", fontsize=9, ha="center", fontweight="bold")
226
- ax.text(6, 4.8, "Jaffe Reaction: Creatinine + Picric Acid → Orange-Red Color", color="#a8b2d8", fontsize=8, ha="center")
227
-
228
- elif "mhv" in topic_lower or "valve" in topic_lower or "bileaflet" in topic_lower:
229
- ax.set_title("Mechanical Heart Valve (MHV) — Bileaflet Design", color="white", fontsize=14, fontweight="bold", pad=15)
230
- circle_outer = plt.Circle((6, 4), 3, color="#2d3a5a", fill=True, zorder=1)
231
- circle_inner = plt.Circle((6, 4), 2.8, color="#1a2744", fill=True, zorder=2)
232
- ax.add_patch(circle_outer)
233
- ax.add_patch(circle_inner)
234
- border = plt.Circle((6, 4), 3, color="#7eb8f7", fill=False, linewidth=3, zorder=3)
235
- ax.add_patch(border)
236
- leaflet1 = mpatches.FancyBboxPatch((4.5, 3), 1.3, 2, boxstyle="round,pad=0.1", facecolor="#e63946", edgecolor="white", linewidth=2, zorder=4)
237
- leaflet2 = mpatches.FancyBboxPatch((6.2, 3), 1.3, 2, boxstyle="round,pad=0.1", facecolor="#e63946", edgecolor="white", linewidth=2, zorder=4)
238
- ax.add_patch(leaflet1)
239
- ax.add_patch(leaflet2)
240
- ax.text(5.15, 4, "Leaflet
241
- 1", ha="center", va="center", color="white", fontsize=8, fontweight="bold", zorder=5)
242
- ax.text(6.85, 4, "Leaflet
243
- 2", ha="center", va="center", color="white", fontsize=8, fontweight="bold", zorder=5)
244
- ax.annotate("", xy=(6, 7.5), xytext=(6, 7), arrowprops=dict(arrowstyle="->", color="#4361ee", lw=3))
245
- ax.text(6, 7.7, "Blood Flow", color="#4361ee", ha="center", fontsize=9, fontweight="bold")
246
- ax.annotate("", xy=(6, 0.5), xytext=(6, 1), arrowprops=dict(arrowstyle="->", color="#4361ee", lw=3))
247
- specs = ["Diameter: 27mm (SJM Regent)", "Material: Pyrolytic Carbon", "Leaflets: 2 (Bileaflet)", "Opening Angle: 85°"]
248
- for i, spec in enumerate(specs):
249
- ax.text(0.3, 7-i*0.7, "• "+spec, color="#a8b2d8", fontsize=8)
250
-
251
  else:
252
- ax.set_title("CardioLab AI Research Overview", color="white", fontsize=14, fontweight="bold", pad=15)
253
- pillars = [
254
- (2, 5, "#e63946", "Pillar 1
255
- MHV Design
256
- & Testing", ["MCL", "PIV", "FSI"]),
257
- (5, 5, "#4361ee", "Pillar 2
258
- Thrombosis
259
- Research", ["TGT", "Blood Clots", "Hemolysis"]),
260
- (8, 5, "#2ecc71", "Pillar 3
261
- CKD
262
- Diagnostics", ["uPAD", "Jaffe", "Creatinine"]),
263
- ]
264
- for x, y, color, title, items in pillars:
265
- rect = mpatches.FancyBboxPatch((x-1, y-0.5), 2.5, 2.5, boxstyle="round,pad=0.2", facecolor=color, edgecolor="white", linewidth=2, alpha=0.9)
266
- ax.add_patch(rect)
267
- ax.text(x+0.25, y+1.7, title, ha="center", va="center", color="white", fontsize=9, fontweight="bold")
268
- for j, item in enumerate(items):
269
- ax.text(x+0.25, y+1-j*0.5, "• "+item, ha="center", color="white", fontsize=8)
270
- equipment = ["27mm SJM Regent MHV", "Arduino Uno + Stepper Motor", "Time-resolved PIV (Green Laser)", "Sylgard 184 Transparent Sections", "Heska Element HT5 Analyzer"]
271
- rect_eq = mpatches.FancyBboxPatch((1, 1), 10, 2.5, boxstyle="round,pad=0.2", facecolor="#1a2744", edgecolor="#7eb8f7", linewidth=2)
272
- ax.add_patch(rect_eq)
273
- ax.text(6, 3.2, "KEY EQUIPMENT", ha="center", color="#7eb8f7", fontsize=10, fontweight="bold")
274
- for i, eq in enumerate(equipment):
275
- col = i % 2
276
- row = i // 2
277
- ax.text(2+col*5, 2.7-row*0.7, "• "+eq, color="#e2e8f0", fontsize=7.5)
278
-
279
  buf = io.BytesIO()
280
  plt.tight_layout()
281
- plt.savefig(buf, format="png", facecolor=fig.get_facecolor(), bbox_inches="tight", dpi=120)
282
  buf.seek(0)
283
  img = Image.open(buf)
284
  plt.close()
285
  return img
286
 
287
  def piv_tool(velocity, shear, hr):
288
- v = "HIGH - stenosis risk" if float(velocity)>2.0 else "NORMAL"
289
- s = "HIGH - thrombosis risk" if float(shear)>10 else "ELEVATED - monitor" if float(shear)>5 else "NORMAL"
290
- hr_s = "ABNORMAL" if float(hr)<60 or float(hr)>100 else "NORMAL"
291
- return ("PIV ANALYSIS RESULTS"+chr(10)+
292
- "━━━━━━━━━━━━━━━━━━━━"+chr(10)+
293
- "Velocity: "+str(velocity)+" m/s → "+v+chr(10)+
294
- "Shear: "+str(shear)+" Pa → "+s+chr(10)+
295
- "Heart Rate: "+str(hr)+" bpm → "+hr_s)
296
 
297
  def tgt_tool(tat,pf12,hemo,platelets,time):
298
  risk=sum([float(tat)>15,float(pf12)>2.0,float(hemo)>50,float(platelets)<150])
299
- r="HIGH THROMBOGENIC RISK" if risk>=3 else "MODERATE RISK" if risk>=2 else "LOW RISK"
300
- return ("TGT BLOOD ANALYSIS"+chr(10)+"━━━━━━━━━━━━━━━━━━━━"+chr(10)+
301
- "Time: "+str(time)+" min"+chr(10)+
302
- "TAT: "+str(tat)+" → "+("HIGH" if float(tat)>15 else "NORMAL")+chr(10)+
303
- "PF1.2: "+str(pf12)+" → "+("HIGH" if float(pf12)>2.0 else "NORMAL")+chr(10)+
304
- "Hemoglobin: "+str(hemo)+" → "+("HIGH" if float(hemo)>50 else "NORMAL")+chr(10)+
305
- "Platelets: "+str(platelets)+" → "+("LOW" if float(platelets)<150 else "NORMAL")+chr(10)+
306
- "━━━━━━━━━━━━━━━━━━━━"+chr(10)+"OVERALL: "+r)
307
 
308
  def upad_tool(r,g,b):
309
  c=max(0,round(0.02*(float(r)-float(b))-0.5,2))
310
- if c<1.2: s="Normal - No CKD"
311
- elif c<1.5: s="Borderline - Monitor"
312
- elif c<3.0: s="Stage 2 CKD"
313
- elif c<6.0: s="Stage 3-4 CKD"
314
- else: s="Stage 5 CKD - Kidney Failure"
315
- return ("uPAD CKD ANALYSIS"+chr(10)+"━━━━━━━━━━━━━━━━━━━━"+chr(10)+
316
- "RGB: R="+str(r)+" G="+str(g)+" B="+str(b)+chr(10)+
317
- "Creatinine: "+str(c)+" mg/dL"+chr(10)+
318
- "━━━━━━━━━━━━━━━━━━━━"+chr(10)+"CKD STAGE: "+s+chr(10)+
319
- "Confirm with: Heska Element HT5")
320
-
321
- with gr.Blocks(title="CardioLab AI", css=CSS) as demo:
322
- gr.HTML("""
323
- <div class="main-header">
324
- <div style="font-size:2.8em;font-weight:900;color:#ffffff;letter-spacing:3px;text-shadow:0 0 30px rgba(255,255,255,0.3);">
325
- ❤️ CardioLab AI
326
- </div>
327
- </div>
328
- """)
329
 
 
 
330
  with gr.Tabs():
331
- with gr.Tab("💬 Chat"):
332
- chatbot = gr.Chatbot(label="", height=450)
333
  with gr.Row():
334
- msg_box = gr.Textbox(placeholder="Ask anything about CardioLab research...", label="", lines=2, scale=4)
335
  with gr.Column(scale=1, min_width=100):
336
- send_btn = gr.Button("Send ➤", variant="primary")
337
- clear_btn = gr.Button("Clear", variant="secondary")
338
  send_btn.click(research_chat, inputs=[msg_box, chatbot], outputs=[msg_box, chatbot])
339
  msg_box.submit(research_chat, inputs=[msg_box, chatbot], outputs=[msg_box, chatbot])
340
- clear_btn.click(lambda: ([], ""), outputs=[chatbot, msg_box])
341
-
342
- with gr.Tab("🎙️ Voice"):
343
- gr.Markdown("### Speak your research question — powered by Groq Whisper AI")
344
- voice_chatbot = gr.Chatbot(label="", height=350)
345
- audio_input = gr.Audio(sources=["microphone"], type="filepath", label="Record Question")
346
  with gr.Row():
347
- voice_btn = gr.Button("Ask by Voice ➤", variant="primary")
348
- voice_clear = gr.Button("Clear", variant="secondary")
349
  voice_btn.click(voice_chat, inputs=[audio_input, voice_chatbot], outputs=voice_chatbot)
350
  voice_clear.click(lambda: [], outputs=voice_chatbot)
351
-
352
- with gr.Tab("🔍 Papers"):
353
- gr.Markdown("### Search latest research papers with verified working links")
354
  with gr.Row():
355
- search_input = gr.Textbox(placeholder="e.g. mechanical heart valve thrombogenicity", label="Research Topic", scale=4)
356
- search_btn = gr.Button("Search ➤", variant="primary", scale=1)
357
- search_output = gr.Textbox(label="Results — Verified Links Only", lines=18)
358
  search_btn.click(quick_search, inputs=search_input, outputs=search_output)
359
  search_input.submit(quick_search, inputs=search_input, outputs=search_output)
360
- gr.Markdown("**Try:** `TGT thrombogenicity` | `creatinine uPAD` | `bileaflet MHV PIV` | `CKD point-of-care`")
361
-
362
- with gr.Tab("🎨 Diagrams"):
363
- gr.Markdown("### Generate real visual diagrams of CardioLab equipment and systems")
364
  with gr.Row():
365
- diagram_input = gr.Textbox(placeholder="e.g. TGT setup | MCL PIV system | uPAD CKD | MHV bileaflet valve | CardioLab overview", label="What to diagram", scale=4)
366
- diagram_btn = gr.Button("Generate ➤", variant="primary", scale=1)
367
- diagram_output = gr.Image(label="Visual Diagram", type="pil")
368
  diagram_btn.click(generate_diagram, inputs=diagram_input, outputs=diagram_output)
369
- gr.Markdown("**Try these:** `TGT setup` | `MCL PIV system` | `uPAD CKD flow` | `MHV bileaflet valve` | `CardioLab overview`")
370
-
371
- with gr.Tab("📊 PIV"):
372
- gr.Markdown("### Analyze Particle Image Velocimetry data from Mock Circulatory Loop")
373
  with gr.Row():
374
  with gr.Column():
375
- v=gr.Number(label="Max Velocity (m/s)", value=1.8, info="Normal: 0.5-2.0 m/s")
376
- s=gr.Number(label="Wall Shear Stress (Pa)", value=6.5, info="Normal: below 5 Pa")
377
- h=gr.Number(label="Heart Rate (bpm)", value=72, info="Normal: 60-100 bpm")
378
- piv_btn = gr.Button("Analyze PIV ➤", variant="primary")
379
- with gr.Column():
380
- piv_out=gr.Textbox(label="Analysis Result", lines=8)
381
- piv_btn.click(piv_tool, inputs=[v,s,h], outputs=piv_out)
382
-
383
- with gr.Tab("🩸 TGT"):
384
- gr.Markdown("### Interpret Thrombogenicity Tester blood analysis results")
385
  with gr.Row():
386
  with gr.Column():
387
- t1=gr.Number(label="TAT (ng/mL)", value=18, info="Normal: below 8")
388
- t2=gr.Number(label="PF1.2 (nmol/L)", value=2.5, info="Normal: below 2.0")
389
- t3=gr.Number(label="Free Hemoglobin (mg/L)", value=60, info="Normal: below 20")
390
- t4=gr.Number(label="Platelet Count", value=140, info="Normal: above 150")
391
- t5=gr.Number(label="Time (minutes)", value=40)
392
- tgt_btn = gr.Button("Analyze TGT ➤", variant="primary")
393
- with gr.Column():
394
- out2=gr.Textbox(label="Analysis Result", lines=12)
395
- tgt_btn.click(tgt_tool, inputs=[t1,t2,t3,t4,t5], outputs=out2)
396
-
397
- with gr.Tab("🧪 uPAD"):
398
- gr.Markdown("### Analyze uPAD colorimetric result using Jaffe Reaction for CKD diagnosis")
399
  with gr.Row():
400
  with gr.Column():
401
- r=gr.Number(label="R (Red)", value=210, info="Range: 0-255")
402
- g=gr.Number(label="G (Green)", value=140, info="Range: 0-255")
403
- b=gr.Number(label="B (Blue)", value=80, info="Range: 0-255")
404
- upad_btn = gr.Button("Analyze uPAD ➤", variant="primary")
405
- with gr.Column():
406
- out3=gr.Textbox(label="CKD Analysis Result", lines=10)
407
- upad_btn.click(upad_tool, inputs=[r,g,b], outputs=out3)
408
-
409
- demo.launch()
 
1
  import gradio as gr
2
+ import os, requests, io
 
3
  import matplotlib
4
+ matplotlib.use('Agg')
5
  import matplotlib.pyplot as plt
6
  import matplotlib.patches as mpatches
7
  import numpy as np
8
  from groq import Groq
 
9
  from PIL import Image
10
 
11
+ GROQ_KEY = os.environ.get('GROQ_API_KEY', '')
12
+ KNOWHOW = 'MCL: Sylgard 184 PDMS 10:1 ratio 48hr cure green laser PIV 70bpm 5L/min. TGT: Arduino Uno Stepper Motor 150mL blood sampled at 0 20 40 60min measures TAT PF1.2 hemolysis platelets. uPAD: Jaffe reaction creatinine plus picric acid gives orange-red color normal 0.6-1.2 mg/dL CKD above 1.5. MHV: 27mm SJM Regent bileaflet also trileaflet monoleaflet pediatric.'
13
 
14
+ CSS = '''
 
 
 
 
 
 
 
15
  body, .gradio-container { background: #f0f4f8 !important; color: #1a202c !important; }
16
  .tab-nav { background: #ffffff !important; border-bottom: 2px solid #e2e8f0 !important; padding: 0 10px !important; }
17
+ .tab-nav button { background: #f7fafc !important; color: #2d3748 !important; border: 1px solid #e2e8f0 !important; border-radius: 8px 8px 0 0 !important; padding: 12px 18px !important; font-size: 0.9em !important; font-weight: 600 !important; margin-top: 6px !important; }
18
+ .tab-nav button:hover { background: #ebf4ff !important; color: #1a237e !important; }
19
+ .tab-nav button.selected { background: linear-gradient(135deg, #e63946, #c1121f) !important; color: #ffffff !important; font-weight: 700 !important; }
20
+ button.primary { background: linear-gradient(135deg, #e63946 0%, #c1121f 100%) !important; color: white !important; border: none !important; border-radius: 8px !important; font-weight: 700 !important; }
 
 
 
 
21
  button.secondary { background: #edf2f7 !important; color: #4a5568 !important; border: 1px solid #cbd5e0 !important; border-radius: 8px !important; }
22
+ textarea, input[type=number] { background: #f7fafc !important; color: #1a202c !important; border: 1px solid #cbd5e0 !important; border-radius: 8px !important; }
23
+ .message.user { background: linear-gradient(135deg, #e63946, #c1121f) !important; color: white !important; }
24
+ .message.bot { background: #ebf4ff !important; color: #1a202c !important; border: 1px solid #bee3f8 !important; }
25
+ label span { color: #2b6cb0 !important; font-weight: 600 !important; font-size: 0.85em !important; text-transform: uppercase !important; }
26
+ '''
27
 
28
  def get_pubmed(query, n=5):
29
  try:
30
+ r = requests.get('https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi',
31
+ params={'db':'pubmed','term':query+' AND (mechanical heart valve OR microfluidic OR CKD OR thrombogenicity)','retmax':n,'retmode':'json','sort':'date'},timeout=10)
32
+ ids = r.json()['esearchresult']['idlist']
33
+ if not ids: return ''
34
+ return chr(10).join(['https://pubmed.ncbi.nlm.nih.gov/'+i for i in ids])
35
+ except: return ''
 
36
 
37
  def get_scholar(query, n=5):
38
  try:
39
+ r = requests.get('https://api.semanticscholar.org/graph/v1/paper/search',
40
+ params={'query':query+' biomedical','limit':n,'fields':'title,year,url,citationCount'},timeout=10)
41
+ papers = r.json().get('data',[])
42
  out = []
43
  for p in papers:
44
+ url = p.get('url','')
45
+ if url: out.append(p.get('title','')[:80]+' ('+str(p.get('year',''))+') - '+str(p.get('citationCount',0))+' citations'+chr(10)+' '+url)
46
+ return chr(10).join(out)
47
+ except: return ''
 
 
 
 
48
 
49
  def quick_search(query):
50
+ if not query.strip(): return 'Please enter a research topic.'
51
  pubmed = get_pubmed(query, n=8)
52
  scholar = get_scholar(query, n=5)
53
+ return 'PUBMED RESULTS:'+chr(10)+pubmed+chr(10)+chr(10)+'SEMANTIC SCHOLAR:'+chr(10)+scholar
 
 
 
54
 
55
  def research_chat(message, history):
56
  if not GROQ_KEY:
57
+ history.append({'role':'user','content':message})
58
+ history.append({'role':'assistant','content':'Error: Add GROQ_API_KEY to Space Settings Secrets.'})
59
+ return '', history
60
  try:
61
  client = Groq(api_key=GROQ_KEY)
62
  pubmed = get_pubmed(message, n=3)
63
+ msgs = [{'role':'system','content':'You are CardioLab AI. Expert in MHV MCL PIV TGT uPAD CKD FSI. Remember full conversation. Never invent URLs. '+KNOWHOW}]
64
  for item in history:
65
+ if isinstance(item, dict): msgs.append({'role':item['role'],'content':item['content']})
66
+ msgs.append({'role':'user','content':message})
67
+ resp = client.chat.completions.create(model='llama-3.3-70b-versatile',messages=msgs,max_tokens=700)
 
68
  answer = resp.choices[0].message.content
69
+ if pubmed: answer += chr(10)+chr(10)+'PUBMED LINKS:'+chr(10)+pubmed
70
+ history.append({'role':'user','content':message})
71
+ history.append({'role':'assistant','content':answer})
72
+ return '', history
73
  except Exception as e:
74
+ history.append({'role':'user','content':message})
75
+ history.append({'role':'assistant','content':'Error: '+str(e)})
76
+ return '', history
77
 
78
  def voice_chat(audio, history):
79
  if audio is None:
80
+ history.append({'role':'assistant','content':'Please record your question first.'})
81
  return history
82
  try:
83
  client = Groq(api_key=GROQ_KEY)
84
+ with open(audio, 'rb') as f:
85
+ tx = client.audio.transcriptions.create(file=('audio.wav', f, 'audio/wav'), model='whisper-large-v3')
86
+ text = tx.text
87
+ msgs = [{'role':'system','content':'You are CardioLab AI. '+KNOWHOW}]
 
 
 
88
  for item in history:
89
+ if isinstance(item, dict): msgs.append({'role':item['role'],'content':item['content']})
90
+ msgs.append({'role':'user','content':text})
91
+ resp = client.chat.completions.create(model='llama-3.3-70b-versatile',messages=msgs,max_tokens=500)
92
+ history.append({'role':'user','content':'[Voice] '+text})
93
+ history.append({'role':'assistant','content':resp.choices[0].message.content})
 
 
94
  return history
95
  except Exception as e:
96
+ history.append({'role':'assistant','content':'Voice error: '+str(e)})
97
  return history
98
 
99
  def generate_diagram(topic):
100
+ t = topic.lower()
101
+ fig, ax = plt.subplots(figsize=(12, 8))
102
+ ax.set_xlim(0,12); ax.set_ylim(0,8); ax.axis('off')
103
+ fig.patch.set_facecolor('#0d1b3e'); ax.set_facecolor('#0d1b3e')
104
+ if 'tgt' in t or 'thrombogen' in t:
105
+ ax.set_title('Thrombogenicity Tester (TGT) - SJSU CardioLab', color='white', fontsize=14, fontweight='bold', pad=15)
106
+ boxes = [
107
+ (0.5, 6.0, 2.0, 0.9, '#e63946', 'Arduino Uno'),
108
+ (3.5, 6.0, 2.0, 0.9, '#4361ee', 'Motor Driver'),
109
+ (6.5, 6.0, 2.0, 0.9, '#2ecc71', 'Stepper Motor'),
110
+ (3.5, 3.5, 4.0, 2.0, '#1a2744', 'Test Chamber - 27mm SJM MHV - 150mL Blood'),
111
+ (0.5, 1.0, 2.0, 0.9, '#e67e22', 'Heska HT5 Analyzer'),
112
+ (9.0, 1.0, 2.0, 0.9, '#9b59b6', 'Data Logger'),
 
 
 
 
 
 
 
 
113
  ]
114
+ for x,y,w,h,c,lbl in boxes:
115
+ ax.add_patch(mpatches.FancyBboxPatch((x,y),w,h,boxstyle='round,pad=0.1',facecolor=c,edgecolor='white',linewidth=2))
116
+ ax.text(x+w/2, y+h/2, lbl, ha='center', va='center', color='white', fontsize=8, fontweight='bold')
117
+ for x1,y1,x2,y2 in [(2.5,6.45,3.5,6.45),(5.5,6.45,6.5,6.45),(7.5,6.0,7.5,5.5),(5.5,3.5,5.5,2.3),(4.0,3.5,2.0,1.9),(8.0,3.5,10.0,1.9)]:
118
+ ax.annotate('',xy=(x2,y2),xytext=(x1,y1),arrowprops=dict(arrowstyle='->',color='#7eb8f7',lw=2))
119
+ for x,y,txt in [(2.9,6.7,'Signal'),(6.0,6.7,'Drive'),(7.8,5.7,'Rotate'),(6.0,2.9,'Sample'),(2.8,2.7,'Analyze'),(9.2,2.7,'Record')]:
120
+ ax.text(x,y,txt,color='#a8b2d8',fontsize=7,ha='center')
121
+ ax.text(6, 0.4, 'Sample at: 0 min | 20 min | 40 min | 60 min', color='#e63946', fontsize=9, ha='center', fontweight='bold')
122
+ elif 'mcl' in t or 'piv' in t or 'circulatory' in t:
123
+ ax.set_title('Mock Circulatory Loop + PIV System - SJSU CardioLab', color='white', fontsize=13, fontweight='bold', pad=15)
124
+ lx = [2,4,6,8,10,10,8,6,4,2,2]; ly = [4,6,6.5,6,4,3,2,1.5,2,3,4]
125
+ ax.plot(lx,ly,color='#4361ee',linewidth=4)
126
+ for x,y,w,h,c,lbl in [(4.8,5.8,2.0,0.8,'#e63946','MHV Test Section'),(0.8,3.5,1.5,0.8,'#2ecc71','Pump'),(9.5,3.5,1.5,0.8,'#e67e22','Compliance'),(4.2,1.2,2.0,0.8,'#9b59b6','Reservoir')]:
127
+ ax.add_patch(mpatches.FancyBboxPatch((x,y),w,h,boxstyle='round,pad=0.1',facecolor=c,edgecolor='white',linewidth=2))
128
+ ax.text(x+w/2,y+h/2,lbl,ha='center',va='center',color='white',fontsize=7,fontweight='bold')
129
+ ax.add_patch(mpatches.FancyBboxPatch((4.8,7.4),2.0,0.5,boxstyle='round,pad=0.05',facecolor='#00cc44',edgecolor='white',linewidth=1))
130
+ ax.text(5.8,7.65,'GREEN LASER',ha='center',va='center',color='black',fontsize=8,fontweight='bold')
131
+ ax.annotate('',xy=(5.8,6.6),xytext=(5.8,7.4),arrowprops=dict(arrowstyle='->',color='#00ff44',lw=3))
132
+ ax.add_patch(mpatches.FancyBboxPatch((9.5,5.3),1.8,0.9,boxstyle='round,pad=0.05',facecolor='#1a2744',edgecolor='#7eb8f7',linewidth=2))
133
+ ax.text(10.4,5.75,'PIV Camera',ha='center',va='center',color='white',fontsize=7,fontweight='bold')
134
+ ax.text(6,0.4,'70 bpm | 5 L/min | 80-120 mmHg',color='#7eb8f7',fontsize=9,ha='center',fontweight='bold')
135
+ elif 'upad' in t or 'ckd' in t or 'creatinine' in t:
136
+ ax.set_title('uPAD Fabrication and CKD Detection - SJSU CardioLab', color='white', fontsize=13, fontweight='bold', pad=15)
137
+ steps = [('1.Whatman Paper','#4361ee'),('2.Wax Print','#e67e22'),('3.Heat 120C','#f1c40f'),('4.Add Reagent','#2ecc71'),('5.Apply Sample','#e63946'),('6.RGB Capture','#9b59b6')]
138
+ for i,(lbl,c) in enumerate(steps):
139
+ cx = 1.0+i*1.8
140
+ ax.add_patch(plt.Circle((cx,6.2),0.75,color=c,zorder=5))
141
+ ax.text(cx,6.2,lbl,ha='center',va='center',color='white',fontsize=6.5,fontweight='bold',zorder=6)
142
+ if i<len(steps)-1: ax.annotate('',xy=(cx+1.05,6.2),xytext=(cx+0.75,6.2),arrowprops=dict(arrowstyle='->',color='white',lw=2))
143
+ stages = [('Normal','<1.2','#27ae60'),('Borderline','1.2-1.5','#f1c40f'),('Stage 2','1.5-3.0','#e67e22'),('Stage 3-4','3.0-6.0','#e74c3c'),('Stage 5','>6.0','#922b21')]
144
+ for i,(s,r,c) in enumerate(stages):
145
+ ax.add_patch(mpatches.FancyBboxPatch((0.3+i*2.3,1.5),2.0,2.8,boxstyle='round,pad=0.1',facecolor=c,edgecolor='white',linewidth=2,alpha=0.85))
146
+ ax.text(1.3+i*2.3,3.1,s,ha='center',color='white',fontsize=9,fontweight='bold')
147
+ ax.text(1.3+i*2.3,2.6,r+' mg/dL',ha='center',color='white',fontsize=8)
148
+ ax.text(6,1.0,'Creatinine Level (mg/dL)',color='#7eb8f7',fontsize=9,ha='center',fontweight='bold')
149
+ elif 'mhv' in t or 'valve' in t:
150
+ ax.set_title('Mechanical Heart Valve (MHV) - 27mm SJM Regent Bileaflet', color='white', fontsize=13, fontweight='bold', pad=15)
151
+ ax.add_patch(plt.Circle((6,4),3.0,color='#2d3a5a',zorder=1))
152
+ ax.add_patch(plt.Circle((6,4),2.8,color='#1a2744',zorder=2))
153
+ ax.add_patch(plt.Circle((6,4),3.0,color='#7eb8f7',fill=False,linewidth=4,zorder=3))
154
+ ax.add_patch(mpatches.FancyBboxPatch((4.3,2.8),1.4,2.4,boxstyle='round,pad=0.1',facecolor='#e63946',edgecolor='white',linewidth=2,zorder=4))
155
+ ax.add_patch(mpatches.FancyBboxPatch((6.3,2.8),1.4,2.4,boxstyle='round,pad=0.1',facecolor='#e63946',edgecolor='white',linewidth=2,zorder=4))
156
+ ax.text(5.0,4.0,'Leaflet 1',ha='center',va='center',color='white',fontsize=8,fontweight='bold',zorder=5)
157
+ ax.text(7.0,4.0,'Leaflet 2',ha='center',va='center',color='white',fontsize=8,fontweight='bold',zorder=5)
158
+ ax.annotate('',xy=(6,7.5),xytext=(6,7.0),arrowprops=dict(arrowstyle='->',color='#4361ee',lw=4))
159
+ ax.text(6,7.7,'Blood Flow',color='#4361ee',ha='center',fontsize=10,fontweight='bold')
160
+ for i,(spec) in enumerate(['Diameter: 27mm','Material: Pyrolytic Carbon','Type: Bileaflet','Opening Angle: 85 deg']):
161
+ ax.text(0.3,2.0+i*0.7,'* '+spec,color='#a8b2d8',fontsize=8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  else:
163
+ ax.set_title('CardioLab AI - Research Overview - SJSU Biomedical Engineering', color='white', fontsize=12, fontweight='bold', pad=15)
164
+ for i,(x,c,title,items) in enumerate([(2.0,'#e63946','MHV + PIV',['MCL Loop','PIV Laser','FSI COMSOL']),(5.5,'#4361ee','Thrombosis',['TGT Circuit','TAT PF1.2','Hemolysis']),(9.0,'#2ecc71','CKD Diag.',['uPAD Paper','Jaffe Rxn','Creatinine'])]):
165
+ ax.add_patch(mpatches.FancyBboxPatch((x-1.2,4.5),2.8,3.0,boxstyle='round,pad=0.2',facecolor=c,edgecolor='white',linewidth=2,alpha=0.9))
166
+ ax.text(x+0.2,7.0,title,ha='center',color='white',fontsize=10,fontweight='bold')
167
+ for j,it in enumerate(items): ax.text(x+0.2,6.3-j*0.6,'* '+it,ha='center',color='white',fontsize=8)
168
+ ax.add_patch(mpatches.FancyBboxPatch((0.5,1.0),11.0,3.0,boxstyle='round,pad=0.2',facecolor='#1a2744',edgecolor='#7eb8f7',linewidth=2))
169
+ ax.text(6,3.7,'KEY EQUIPMENT',ha='center',color='#7eb8f7',fontsize=10,fontweight='bold')
170
+ equip = ['27mm SJM Regent MHV','Arduino Uno + Stepper Motor','Time-resolved PIV Green Laser','Sylgard 184 Transparent MCL','Heska Element HT5 Analyzer']
171
+ for i,eq in enumerate(equip): ax.text(1.0+(i%2)*6,3.0-(i//2)*0.7,'* '+eq,color='#e2e8f0',fontsize=8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  buf = io.BytesIO()
173
  plt.tight_layout()
174
+ plt.savefig(buf,format='png',facecolor=fig.get_facecolor(),bbox_inches='tight',dpi=120)
175
  buf.seek(0)
176
  img = Image.open(buf)
177
  plt.close()
178
  return img
179
 
180
  def piv_tool(velocity, shear, hr):
181
+ v = 'HIGH - stenosis risk' if float(velocity)>2.0 else 'NORMAL'
182
+ s = 'HIGH - thrombosis risk' if float(shear)>10 else 'ELEVATED' if float(shear)>5 else 'NORMAL'
183
+ hr_s = 'ABNORMAL' if float(hr)<60 or float(hr)>100 else 'NORMAL'
184
+ return 'PIV RESULTS'+chr(10)+'Velocity: '+str(velocity)+' m/s - '+v+chr(10)+'Shear: '+str(shear)+' Pa - '+s+chr(10)+'HR: '+str(hr)+' bpm - '+hr_s
 
 
 
 
185
 
186
  def tgt_tool(tat,pf12,hemo,platelets,time):
187
  risk=sum([float(tat)>15,float(pf12)>2.0,float(hemo)>50,float(platelets)<150])
188
+ r='HIGH THROMBOGENIC RISK' if risk>=3 else 'MODERATE RISK' if risk>=2 else 'LOW RISK'
189
+ return 'TGT ANALYSIS'+chr(10)+'Time: '+str(time)+' min'+chr(10)+'TAT: '+str(tat)+(' HIGH' if float(tat)>15 else ' NORMAL')+chr(10)+'PF1.2: '+str(pf12)+(' HIGH' if float(pf12)>2.0 else ' NORMAL')+chr(10)+'Hemo: '+str(hemo)+(' HIGH' if float(hemo)>50 else ' NORMAL')+chr(10)+'Platelets: '+str(platelets)+(' LOW' if float(platelets)<150 else ' NORMAL')+chr(10)+'RESULT: '+r
 
 
 
 
 
 
190
 
191
  def upad_tool(r,g,b):
192
  c=max(0,round(0.02*(float(r)-float(b))-0.5,2))
193
+ s='Normal' if c<1.2 else 'Borderline' if c<1.5 else 'Stage 2 CKD' if c<3.0 else 'Stage 3-4 CKD' if c<6.0 else 'Stage 5 CKD'
194
+ return 'uPAD RESULT'+chr(10)+'Creatinine: '+str(c)+' mg/dL'+chr(10)+'CKD Stage: '+s
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
196
+ with gr.Blocks(title='CardioLab AI', css=CSS) as demo:
197
+ gr.HTML('<div style="background:linear-gradient(135deg,#1a237e,#b71c1c);padding:25px;text-align:center;border-radius:12px 12px 0 0;"><div style="font-size:2.8em;font-weight:900;color:#fff;letter-spacing:3px;">❤️ CardioLab AI</div></div>')
198
  with gr.Tabs():
199
+ with gr.Tab('💬 Chat'):
200
+ chatbot = gr.Chatbot(label='', height=450)
201
  with gr.Row():
202
+ msg_box = gr.Textbox(placeholder='Ask anything about CardioLab research...', label='', lines=2, scale=4)
203
  with gr.Column(scale=1, min_width=100):
204
+ send_btn = gr.Button('Send', variant='primary')
205
+ clear_btn = gr.Button('Clear', variant='secondary')
206
  send_btn.click(research_chat, inputs=[msg_box, chatbot], outputs=[msg_box, chatbot])
207
  msg_box.submit(research_chat, inputs=[msg_box, chatbot], outputs=[msg_box, chatbot])
208
+ clear_btn.click(lambda: ([], ''), outputs=[chatbot, msg_box])
209
+ with gr.Tab('🎙️ Voice'):
210
+ gr.Markdown('### Speak your question - Groq Whisper AI')
211
+ voice_chatbot = gr.Chatbot(label='', height=350)
212
+ audio_input = gr.Audio(sources=['microphone'], type='filepath', label='Record Question')
 
213
  with gr.Row():
214
+ voice_btn = gr.Button('Ask by Voice', variant='primary')
215
+ voice_clear = gr.Button('Clear', variant='secondary')
216
  voice_btn.click(voice_chat, inputs=[audio_input, voice_chatbot], outputs=voice_chatbot)
217
  voice_clear.click(lambda: [], outputs=voice_chatbot)
218
+ with gr.Tab('🔍 Papers'):
219
+ gr.Markdown('### Search research papers - verified links only')
 
220
  with gr.Row():
221
+ search_input = gr.Textbox(placeholder='e.g. mechanical heart valve thrombogenicity', label='Research Topic', scale=4)
222
+ search_btn = gr.Button('Search', variant='primary', scale=1)
223
+ search_output = gr.Textbox(label='Verified Results', lines=18)
224
  search_btn.click(quick_search, inputs=search_input, outputs=search_output)
225
  search_input.submit(quick_search, inputs=search_input, outputs=search_output)
226
+ with gr.Tab('🎨 Diagrams'):
227
+ gr.Markdown('### Real visual diagrams - try: TGT setup | MCL PIV | uPAD CKD | MHV valve | CardioLab overview')
 
 
228
  with gr.Row():
229
+ diagram_input = gr.Textbox(placeholder='e.g. TGT setup', label='What to diagram', scale=4)
230
+ diagram_btn = gr.Button('Generate', variant='primary', scale=1)
231
+ diagram_output = gr.Image(label='Visual Diagram', type='pil')
232
  diagram_btn.click(generate_diagram, inputs=diagram_input, outputs=diagram_output)
233
+ with gr.Tab('📊 PIV'):
 
 
 
234
  with gr.Row():
235
  with gr.Column():
236
+ v=gr.Number(label='Max Velocity m/s', value=1.8)
237
+ s=gr.Number(label='Wall Shear Stress Pa', value=6.5)
238
+ h=gr.Number(label='Heart Rate bpm', value=72)
239
+ piv_out=gr.Textbox(label='Result', lines=5)
240
+ gr.Button('Analyze PIV', variant='primary').click(piv_tool,inputs=[v,s,h],outputs=piv_out)
241
+ with gr.Tab('🩸 TGT'):
 
 
 
 
242
  with gr.Row():
243
  with gr.Column():
244
+ t1=gr.Number(label='TAT ng/mL', value=18)
245
+ t2=gr.Number(label='PF1.2 nmol/L', value=2.5)
246
+ t3=gr.Number(label='Free Hemoglobin mg/L', value=60)
247
+ t4=gr.Number(label='Platelet Count', value=140)
248
+ t5=gr.Number(label='Time minutes', value=40)
249
+ out2=gr.Textbox(label='Result', lines=8)
250
+ gr.Button('Analyze TGT', variant='primary').click(tgt_tool,inputs=[t1,t2,t3,t4,t5],outputs=out2)
251
+ with gr.Tab('🧪 uPAD'):
 
 
 
 
252
  with gr.Row():
253
  with gr.Column():
254
+ r=gr.Number(label='R value', value=210)
255
+ g=gr.Number(label='G value', value=140)
256
+ b=gr.Number(label='B value', value=80)
257
+ out3=gr.Textbox(label='Result', lines=5)
258
+ gr.Button('Analyze uPAD', variant='primary').click(upad_tool,inputs=[r,g,b],outputs=out3)
259
+ demo.launch()