Saicharan21 commited on
Commit
1992158
Β·
verified Β·
1 Parent(s): 961f82f

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +348 -141
app.py CHANGED
@@ -3,218 +3,425 @@ import os
3
  import requests
4
  from groq import Groq
5
 
6
- GROQ_KEY = os.environ.get('GROQ_API_KEY', '')
7
-
8
- KNOWHOW = ('SJSU CardioLab: '
9
- 'MCL: Sylgard 184 PDMS 10:1 ratio 48hr cure green laser PIV 70bpm 5L/min. '
10
- 'TGT: Arduino Uno Stepper Motor 150mL blood sampled at 0 20 40 60min measures TAT PF1.2 hemolysis platelets. '
11
- 'uPAD: Jaffe reaction creatinine plus picric acid gives orange-red color normal 0.6-1.2 mg/dL CKD above 1.5. '
12
- 'MHV: 27mm SJM Regent bileaflet also trileaflet monoleaflet pediatric. '
13
- 'Equipment: Heska HT5 hematology analyzer time-resolved PIV Tygon tubing Arduino Uno.')
14
-
15
- CSS = (
16
- 'body, .gradio-container { background: #0a0f1e !important; }'
17
- '.tab-nav button { color: #a8b2d8 !important; background: transparent !important; font-weight: 600 !important; padding: 10px 20px !important; border: none !important; border-bottom: 3px solid transparent !important; }'
18
- '.tab-nav button.selected { color: #e63946 !important; border-bottom: 3px solid #e63946 !important; }'
19
- '.tab-nav button:hover { color: #ffffff !important; background: #1a2744 !important; }'
20
- 'h1 { color: #e63946 !important; font-size: 2.8em !important; font-weight: 900 !important; text-align: center !important; padding: 25px 0 !important; }'
21
- 'h3, label, p, span, div { color: #ffffff !important; }'
22
- '.message { color: #ffffff !important; font-size: 1em !important; }'
23
- '.bot { background: #1a2744 !important; color: #ffffff !important; }'
24
- '.user { color: #ffffff !important; }'
25
- 'strong, b { color: #ffd700 !important; }'
26
- 'textarea, input[type=number], input[type=text] { background: #1a2744 !important; color: #e2e8f0 !important; border: 1px solid #4361ee !important; border-radius: 8px !important; }'
27
- 'button.primary { background: linear-gradient(135deg, #e63946 0%, #c1121f 100%) !important; color: white !important; border: none !important; border-radius: 8px !important; }'
28
- 'button.secondary { background: #1d3461 !important; color: #a8b2d8 !important; border: 1px solid #4361ee !important; border-radius: 8px !important; }'
29
- '.block { background: #0d1b3e !important; border: 1px solid #1e3a6e !important; border-radius: 12px !important; }'
30
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  def get_pubmed(query, n=5):
33
  try:
34
- r = requests.get('https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi',
35
- params={'db':'pubmed','term':query+' AND (mechanical heart valve OR microfluidic OR CKD OR thrombogenicity)',
36
- 'retmax':n,'retmode':'json','sort':'date'},timeout=10)
37
- ids = r.json()['esearchresult']['idlist']
38
- if not ids: return ''
39
- return chr(10).join(['https://pubmed.ncbi.nlm.nih.gov/'+i for i in ids])
40
- except: return ''
41
 
42
  def get_scholar(query, n=5):
43
  try:
44
- r = requests.get('https://api.semanticscholar.org/graph/v1/paper/search',
45
- params={'query':query+' biomedical','limit':n,'fields':'title,year,url,citationCount'},timeout=10)
46
- papers = r.json().get('data',[])
47
  out = []
48
  for p in papers:
49
- title = p.get('title','')
50
- year = str(p.get('year',''))
51
- url = p.get('url','')
52
- citations = str(p.get('citationCount',0))
53
  if url:
54
- out.append(title[:80]+' ('+year+') - '+citations+' citations'+chr(10)+' '+url)
55
  return chr(10)+chr(10).join(out)
56
- except: return ''
57
 
58
  def quick_search(query):
59
- if not query.strip(): return 'Please enter a research topic.'
60
  pubmed = get_pubmed(query, n=8)
61
  scholar = get_scholar(query, n=5)
62
- result = 'PAPERS FOR: ' + query + chr(10)+chr(10)
63
- result += 'PUBMED (verified links):'+chr(10)+pubmed+chr(10)+chr(10)
64
- result += 'SEMANTIC SCHOLAR:'+chr(10)+scholar
65
  return result
66
 
67
  def research_chat(message, history):
68
  if not GROQ_KEY:
69
- history.append({'role':'user','content':message})
70
- history.append({'role':'assistant','content':'Error: Add GROQ_API_KEY to Space Settings Secrets tab.'})
71
- return '', history
72
  try:
73
  client = Groq(api_key=GROQ_KEY)
74
  pubmed = get_pubmed(message, n=3)
75
- system_msg = 'You are CardioLab AI assistant. Expert in MHV MCL PIV TGT uPAD CKD FSI. Remember full conversation. Never invent URLs. ' + KNOWHOW
76
- msgs = [{'role':'system','content':system_msg}]
77
  for item in history:
78
  if isinstance(item, dict):
79
- msgs.append({'role':item['role'],'content':item['content']})
80
- msgs.append({'role':'user','content':message})
81
- resp = client.chat.completions.create(model='llama-3.3-70b-versatile',messages=msgs,max_tokens=700)
82
  answer = resp.choices[0].message.content
83
- if pubmed: answer += chr(10)+chr(10)+'PUBMED LINKS:'+chr(10)+pubmed
84
- history.append({'role':'user','content':message})
85
- history.append({'role':'assistant','content':answer})
86
- return '', history
87
  except Exception as e:
88
- history.append({'role':'user','content':message})
89
- history.append({'role':'assistant','content':'Error: '+str(e)})
90
- return '', history
91
 
92
  def voice_chat(audio, history):
93
  if audio is None:
94
- history.append({'role':'assistant','content':'Please record your question first.'})
95
  return history
96
  try:
97
  client = Groq(api_key=GROQ_KEY)
98
- with open(audio, 'rb') as f:
99
  transcription = client.audio.transcriptions.create(
100
- file=('audio.wav', f, 'audio/wav'),
101
- model='whisper-large-v3'
102
  )
103
  text = transcription.text
104
- system_msg = 'You are CardioLab AI assistant. Expert in MHV MCL PIV TGT uPAD CKD FSI. ' + KNOWHOW
105
- msgs = [{'role':'system','content':system_msg}]
106
  for item in history:
107
  if isinstance(item, dict):
108
- msgs.append({'role':item['role'],'content':item['content']})
109
- msgs.append({'role':'user','content':text})
110
- resp = client.chat.completions.create(model='llama-3.3-70b-versatile',messages=msgs,max_tokens=500)
111
  answer = resp.choices[0].message.content
112
- history.append({'role':'user','content':'[Voice]: '+text})
113
- history.append({'role':'assistant','content':answer})
114
  return history
115
  except Exception as e:
116
- history.append({'role':'assistant','content':'Voice error: '+str(e)})
117
  return history
118
 
119
  def generate_diagram(topic):
120
- if not topic.strip(): return 'Please enter a topic.'
121
  try:
122
  client = Groq(api_key=GROQ_KEY)
123
- msgs = [{'role':'system','content':'You are a biomedical engineering diagram expert for SJSU CardioLab. Generate a detailed ASCII diagram or technical schematic based on the topic. Make it clear, labeled, and useful for researchers.'}]
124
- msgs.append({'role':'user','content':'Create a detailed labeled diagram for: '+topic})
125
- resp = client.chat.completions.create(model='llama-3.3-70b-versatile',messages=msgs,max_tokens=600)
126
  return resp.choices[0].message.content
127
  except Exception as e:
128
- return 'Error: '+str(e)
129
 
130
  def piv_tool(velocity, shear, hr):
131
- v = 'HIGH - stenosis risk' if float(velocity)>2.0 else 'NORMAL'
132
- s = 'HIGH - thrombosis risk' if float(shear)>10 else 'ELEVATED' if float(shear)>5 else 'NORMAL'
133
- hr_s = 'ABNORMAL' if float(hr)<60 or float(hr)>100 else 'NORMAL'
134
- return 'VELOCITY: '+str(velocity)+' m/s - '+v+chr(10)+'SHEAR: '+str(shear)+' Pa - '+s+chr(10)+'HEART RATE: '+str(hr)+' bpm - '+hr_s
 
 
 
 
135
 
136
  def tgt_tool(tat,pf12,hemo,platelets,time):
137
  risk=sum([float(tat)>15,float(pf12)>2.0,float(hemo)>50,float(platelets)<150])
138
- r='HIGH THROMBOGENIC RISK' if risk>=3 else 'MODERATE RISK' if risk>=2 else 'LOW RISK'
139
- return ('TIME: '+str(time)+' min'+chr(10)+
140
- 'TAT: '+str(tat)+(' - HIGH' if float(tat)>15 else ' - NORMAL')+chr(10)+
141
- 'PF1.2: '+str(pf12)+(' - HIGH' if float(pf12)>2.0 else ' - NORMAL')+chr(10)+
142
- 'HEMOGLOBIN: '+str(hemo)+(' - HIGH' if float(hemo)>50 else ' - NORMAL')+chr(10)+
143
- 'PLATELETS: '+str(platelets)+(' - LOW' if float(platelets)<150 else ' - NORMAL')+chr(10)+chr(10)+
144
- 'OVERALL: '+r)
 
 
 
145
 
146
  def upad_tool(r,g,b):
147
  c=max(0,round(0.02*(float(r)-float(b))-0.5,2))
148
- if c<1.2: s='Normal - No CKD'
149
- elif c<1.5: s='Borderline - Monitor'
150
- elif c<3.0: s='Stage 2 CKD'
151
- elif c<6.0: s='Stage 3-4 CKD'
152
- else: s='Stage 5 CKD - Kidney Failure'
153
- return 'RGB: R='+str(r)+' G='+str(g)+' B='+str(b)+chr(10)+'CREATININE: '+str(c)+' mg/dL'+chr(10)+'CKD STAGE: '+s+chr(10)+'Confirm with: Heska Element HT5'
 
 
 
 
 
 
 
 
 
154
 
155
- with gr.Blocks(title='CardioLab AI', css=CSS) as demo:
156
- gr.Markdown('# CardioLab AI')
 
 
 
 
 
157
 
158
  with gr.Tabs():
159
- with gr.Tab('Chat'):
160
- chatbot = gr.Chatbot(label='', height=450)
161
  with gr.Row():
162
- msg_box = gr.Textbox(placeholder='Ask anything about CardioLab research...', label='', lines=2, scale=4)
163
- with gr.Column(scale=1):
164
- send_btn = gr.Button('Send', variant='primary')
165
- clear_btn = gr.Button('Clear', variant='secondary')
 
 
 
 
 
166
  send_btn.click(research_chat, inputs=[msg_box, chatbot], outputs=[msg_box, chatbot])
167
  msg_box.submit(research_chat, inputs=[msg_box, chatbot], outputs=[msg_box, chatbot])
168
- clear_btn.click(lambda: ([], ''), outputs=[chatbot, msg_box])
169
 
170
- with gr.Tab('Voice'):
171
- gr.Markdown('### Speak your question - powered by Groq Whisper')
172
- voice_chatbot = gr.Chatbot(label='Voice Chat', height=350)
173
- audio_input = gr.Audio(sources=['microphone'], type='filepath', label='Record your question')
174
  with gr.Row():
175
- voice_btn = gr.Button('Ask by Voice', variant='primary')
176
- voice_clear = gr.Button('Clear', variant='secondary')
177
  voice_btn.click(voice_chat, inputs=[audio_input, voice_chatbot], outputs=voice_chatbot)
178
  voice_clear.click(lambda: [], outputs=voice_chatbot)
179
 
180
- with gr.Tab('Paper Search'):
181
- search_input = gr.Textbox(placeholder='e.g. mechanical heart valve thrombogenicity', label='Research topic')
182
- search_btn = gr.Button('Search Papers', variant='primary')
183
- search_output = gr.Textbox(label='Results', lines=20)
 
 
 
 
 
 
184
  search_btn.click(quick_search, inputs=search_input, outputs=search_output)
185
  search_input.submit(quick_search, inputs=search_input, outputs=search_output)
 
186
 
187
- with gr.Tab('Diagram Generator'):
188
- gr.Markdown('### Generate technical diagrams and schematics for CardioLab')
189
- gr.Markdown('Examples: MCL setup | TGT circuit | uPAD fabrication flow | PIV system | valve design')
190
- diagram_input = gr.Textbox(placeholder='e.g. Mock Circulatory Loop setup with PIV system', label='What to diagram')
191
- diagram_btn = gr.Button('Generate Diagram', variant='primary')
192
- diagram_output = gr.Textbox(label='Diagram', lines=20)
 
 
 
 
193
  diagram_btn.click(generate_diagram, inputs=diagram_input, outputs=diagram_output)
 
194
 
195
- with gr.Tab('PIV Analysis'):
 
196
  with gr.Row():
197
  with gr.Column():
198
- v=gr.Number(label='Max Velocity m/s', value=1.8)
199
- s=gr.Number(label='Wall Shear Stress Pa', value=6.5)
200
- h=gr.Number(label='Heart Rate bpm', value=72)
201
- piv_out=gr.Textbox(label='Result', lines=5)
202
- gr.Button('Analyze PIV', variant='primary').click(piv_tool,inputs=[v,s,h],outputs=piv_out)
203
-
204
- with gr.Tab('TGT Results'):
205
- t1=gr.Number(label='TAT ng/mL', value=18)
206
- t2=gr.Number(label='PF1.2 nmol/L', value=2.5)
207
- t3=gr.Number(label='Free Hemoglobin mg/L', value=60)
208
- t4=gr.Number(label='Platelet Count', value=140)
209
- t5=gr.Number(label='Time minutes', value=40)
210
- out2=gr.Textbox(label='Result', lines=8)
211
- gr.Button('Analyze TGT', variant='primary').click(tgt_tool,inputs=[t1,t2,t3,t4,t5],outputs=out2)
212
-
213
- with gr.Tab('uPAD CKD'):
214
- r=gr.Number(label='R value', value=210)
215
- g=gr.Number(label='G value', value=140)
216
- b=gr.Number(label='B value', value=80)
217
- out3=gr.Textbox(label='Result', lines=6)
218
- gr.Button('Analyze uPAD', variant='primary').click(upad_tool,inputs=[r,g,b],outputs=out3)
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
  demo.launch()
 
3
  import requests
4
  from groq import Groq
5
 
6
+ GROQ_KEY = os.environ.get("GROQ_API_KEY", "")
7
+
8
+ KNOWHOW = ("SJSU CardioLab: "
9
+ "MCL: Sylgard 184 PDMS 10:1 ratio 48hr cure green laser PIV 70bpm 5L/min. "
10
+ "TGT: Arduino Uno Stepper Motor 150mL blood sampled at 0 20 40 60min measures TAT PF1.2 hemolysis platelets. "
11
+ "uPAD: Jaffe reaction creatinine plus picric acid gives orange-red color normal 0.6-1.2 mg/dL CKD above 1.5. "
12
+ "MHV: 27mm SJM Regent bileaflet also trileaflet monoleaflet pediatric. "
13
+ "Equipment: Heska HT5 hematology analyzer time-resolved PIV Tygon tubing Arduino Uno.")
14
+
15
+ CSS = """
16
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;900&display=swap');
17
+
18
+ * { font-family: 'Inter', sans-serif !important; box-sizing: border-box; }
19
+
20
+ body, .gradio-container {
21
+ background: #050d1a !important;
22
+ color: #ffffff !important;
23
+ }
24
+
25
+ .gradio-container {
26
+ max-width: 1200px !important;
27
+ margin: 0 auto !important;
28
+ padding: 0 !important;
29
+ }
30
+
31
+ /* HEADER */
32
+ .main-header {
33
+ background: linear-gradient(135deg, #0d1b3e 0%, #1a0533 50%, #0d1b3e 100%);
34
+ border-bottom: 3px solid #e63946;
35
+ padding: 30px 20px;
36
+ text-align: center;
37
+ margin-bottom: 0;
38
+ }
39
+
40
+ /* TABS - ALWAYS VISIBLE */
41
+ .tab-nav {
42
+ background: #0d1b3e !important;
43
+ border-bottom: 2px solid #1e3a6e !important;
44
+ padding: 0 10px !important;
45
+ display: flex !important;
46
+ gap: 5px !important;
47
+ overflow-x: auto !important;
48
+ }
49
+
50
+ .tab-nav button {
51
+ background: #132340 !important;
52
+ color: #a0b3d6 !important;
53
+ border: 1px solid #1e3a6e !important;
54
+ border-radius: 8px 8px 0 0 !important;
55
+ padding: 12px 20px !important;
56
+ font-size: 0.9em !important;
57
+ font-weight: 600 !important;
58
+ cursor: pointer !important;
59
+ transition: all 0.2s !important;
60
+ white-space: nowrap !important;
61
+ margin-top: 8px !important;
62
+ }
63
+
64
+ .tab-nav button:hover {
65
+ background: #1e3a6e !important;
66
+ color: #ffffff !important;
67
+ border-color: #4361ee !important;
68
+ }
69
+
70
+ .tab-nav button.selected {
71
+ background: linear-gradient(135deg, #e63946, #c1121f) !important;
72
+ color: #ffffff !important;
73
+ border-color: #e63946 !important;
74
+ font-weight: 700 !important;
75
+ }
76
+
77
+ /* CONTENT AREA */
78
+ .tabitem {
79
+ background: #0a1628 !important;
80
+ padding: 20px !important;
81
+ border: 1px solid #1e3a6e !important;
82
+ border-top: none !important;
83
+ }
84
+
85
+ /* INPUTS */
86
+ textarea, input[type=number], input[type=text] {
87
+ background: #132340 !important;
88
+ color: #ffffff !important;
89
+ border: 1px solid #2d4a8a !important;
90
+ border-radius: 8px !important;
91
+ font-size: 0.95em !important;
92
+ }
93
+
94
+ textarea:focus, input:focus {
95
+ border-color: #e63946 !important;
96
+ outline: none !important;
97
+ }
98
+
99
+ textarea::placeholder {
100
+ color: #5a7aaa !important;
101
+ }
102
+
103
+ /* BUTTONS */
104
+ button.primary, .btn-primary {
105
+ background: linear-gradient(135deg, #e63946 0%, #c1121f 100%) !important;
106
+ color: white !important;
107
+ border: none !important;
108
+ border-radius: 8px !important;
109
+ font-weight: 700 !important;
110
+ padding: 12px 24px !important;
111
+ font-size: 0.95em !important;
112
+ cursor: pointer !important;
113
+ transition: transform 0.1s !important;
114
+ }
115
+
116
+ button.primary:hover {
117
+ transform: translateY(-1px) !important;
118
+ box-shadow: 0 4px 15px rgba(230, 57, 70, 0.4) !important;
119
+ }
120
+
121
+ button.secondary {
122
+ background: #1d3461 !important;
123
+ color: #a0b3d6 !important;
124
+ border: 1px solid #2d4a8a !important;
125
+ border-radius: 8px !important;
126
+ font-weight: 600 !important;
127
+ }
128
+
129
+ /* LABELS */
130
+ label span, label, .label-wrap span {
131
+ color: #7eb8f7 !important;
132
+ font-weight: 600 !important;
133
+ font-size: 0.85em !important;
134
+ text-transform: uppercase !important;
135
+ letter-spacing: 0.5px !important;
136
+ }
137
+
138
+ /* MARKDOWN TEXT */
139
+ .md h1, .md h2, .md h3 { color: #ffffff !important; }
140
+ .md p, .md li { color: #a0b3d6 !important; }
141
+
142
+ /* CHATBOT */
143
+ .chatbot {
144
+ background: #0a1628 !important;
145
+ border: 1px solid #1e3a6e !important;
146
+ border-radius: 12px !important;
147
+ }
148
+
149
+ .message.user {
150
+ background: linear-gradient(135deg, #e63946, #c1121f) !important;
151
+ color: #ffffff !important;
152
+ border-radius: 12px 12px 4px 12px !important;
153
+ }
154
+
155
+ .message.bot {
156
+ background: #132340 !important;
157
+ color: #e2e8f0 !important;
158
+ border: 1px solid #2d4a8a !important;
159
+ border-radius: 12px 12px 12px 4px !important;
160
+ }
161
+
162
+ /* RESULT BOXES */
163
+ .gr-box, .block {
164
+ background: #0d1b3e !important;
165
+ border: 1px solid #1e3a6e !important;
166
+ border-radius: 12px !important;
167
+ }
168
+
169
+ /* NUMBERS */
170
+ input[type=number] {
171
+ color: #7eb8f7 !important;
172
+ font-size: 1.1em !important;
173
+ font-weight: 600 !important;
174
+ }
175
+
176
+ /* INFO TEXT */
177
+ .info {
178
+ color: #5a7aaa !important;
179
+ font-size: 0.8em !important;
180
+ }
181
+
182
+ /* SCROLLBAR */
183
+ ::-webkit-scrollbar { width: 6px; }
184
+ ::-webkit-scrollbar-track { background: #0a1628; }
185
+ ::-webkit-scrollbar-thumb { background: #2d4a8a; border-radius: 3px; }
186
+ """
187
 
188
  def get_pubmed(query, n=5):
189
  try:
190
+ r = requests.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi",
191
+ params={"db":"pubmed","term":query+" AND (mechanical heart valve OR microfluidic OR CKD OR thrombogenicity)",
192
+ "retmax":n,"retmode":"json","sort":"date"},timeout=10)
193
+ ids = r.json()["esearchresult"]["idlist"]
194
+ if not ids: return ""
195
+ return chr(10).join(["https://pubmed.ncbi.nlm.nih.gov/"+i for i in ids])
196
+ except: return ""
197
 
198
  def get_scholar(query, n=5):
199
  try:
200
+ r = requests.get("https://api.semanticscholar.org/graph/v1/paper/search",
201
+ params={"query":query+" biomedical","limit":n,"fields":"title,year,url,citationCount"},timeout=10)
202
+ papers = r.json().get("data",[])
203
  out = []
204
  for p in papers:
205
+ title = p.get("title","")
206
+ year = str(p.get("year",""))
207
+ url = p.get("url","")
208
+ citations = str(p.get("citationCount",0))
209
  if url:
210
+ out.append(title[:80]+" ("+year+") - "+citations+" citations"+chr(10)+" "+url)
211
  return chr(10)+chr(10).join(out)
212
+ except: return ""
213
 
214
  def quick_search(query):
215
+ if not query.strip(): return "Please enter a research topic."
216
  pubmed = get_pubmed(query, n=8)
217
  scholar = get_scholar(query, n=5)
218
+ result = "PAPERS FOR: " + query + chr(10)+chr(10)
219
+ result += "PUBMED (verified links):"+chr(10)+pubmed+chr(10)+chr(10)
220
+ result += "SEMANTIC SCHOLAR:"+chr(10)+scholar
221
  return result
222
 
223
  def research_chat(message, history):
224
  if not GROQ_KEY:
225
+ history.append({"role":"user","content":message})
226
+ history.append({"role":"assistant","content":"Error: Add GROQ_API_KEY to Space Settings Secrets tab."})
227
+ return "", history
228
  try:
229
  client = Groq(api_key=GROQ_KEY)
230
  pubmed = get_pubmed(message, n=3)
231
+ system_msg = "You are CardioLab AI assistant. Expert in MHV MCL PIV TGT uPAD CKD FSI. Remember full conversation. Never invent URLs. " + KNOWHOW
232
+ msgs = [{"role":"system","content":system_msg}]
233
  for item in history:
234
  if isinstance(item, dict):
235
+ msgs.append({"role":item["role"],"content":item["content"]})
236
+ msgs.append({"role":"user","content":message})
237
+ resp = client.chat.completions.create(model="llama-3.3-70b-versatile",messages=msgs,max_tokens=700)
238
  answer = resp.choices[0].message.content
239
+ if pubmed: answer += chr(10)+chr(10)+"PUBMED LINKS:"+chr(10)+pubmed
240
+ history.append({"role":"user","content":message})
241
+ history.append({"role":"assistant","content":answer})
242
+ return "", history
243
  except Exception as e:
244
+ history.append({"role":"user","content":message})
245
+ history.append({"role":"assistant","content":"Error: "+str(e)})
246
+ return "", history
247
 
248
  def voice_chat(audio, history):
249
  if audio is None:
250
+ history.append({"role":"assistant","content":"Please record your question first."})
251
  return history
252
  try:
253
  client = Groq(api_key=GROQ_KEY)
254
+ with open(audio, "rb") as f:
255
  transcription = client.audio.transcriptions.create(
256
+ file=("audio.wav", f, "audio/wav"),
257
+ model="whisper-large-v3"
258
  )
259
  text = transcription.text
260
+ msgs = [{"role":"system","content":"You are CardioLab AI assistant. Expert in MHV MCL PIV TGT uPAD CKD FSI. "+KNOWHOW}]
 
261
  for item in history:
262
  if isinstance(item, dict):
263
+ msgs.append({"role":item["role"],"content":item["content"]})
264
+ msgs.append({"role":"user","content":text})
265
+ resp = client.chat.completions.create(model="llama-3.3-70b-versatile",messages=msgs,max_tokens=500)
266
  answer = resp.choices[0].message.content
267
+ history.append({"role":"user","content":"[Voice] "+text})
268
+ history.append({"role":"assistant","content":answer})
269
  return history
270
  except Exception as e:
271
+ history.append({"role":"assistant","content":"Voice error: "+str(e)})
272
  return history
273
 
274
  def generate_diagram(topic):
275
+ if not topic.strip(): return "Please enter a topic."
276
  try:
277
  client = Groq(api_key=GROQ_KEY)
278
+ msgs = [{"role":"system","content":"You are a biomedical engineering expert for SJSU CardioLab. Generate a detailed ASCII diagram or technical schematic. Make it clear, labeled, and useful for researchers."}]
279
+ msgs.append({"role":"user","content":"Create a detailed labeled diagram for: "+topic})
280
+ resp = client.chat.completions.create(model="llama-3.3-70b-versatile",messages=msgs,max_tokens=600)
281
  return resp.choices[0].message.content
282
  except Exception as e:
283
+ return "Error: "+str(e)
284
 
285
  def piv_tool(velocity, shear, hr):
286
+ v = "HIGH - stenosis risk" if float(velocity)>2.0 else "NORMAL"
287
+ s = "HIGH - thrombosis risk" if float(shear)>10 else "ELEVATED - monitor" if float(shear)>5 else "NORMAL"
288
+ hr_s = "ABNORMAL" if float(hr)<60 or float(hr)>100 else "NORMAL"
289
+ return ("PIV ANALYSIS RESULTS"+chr(10)+
290
+ "━━━━━━━━━━━━━━━━━━━━"+chr(10)+
291
+ "Velocity: "+str(velocity)+" m/s β†’ "+v+chr(10)+
292
+ "Shear: "+str(shear)+" Pa β†’ "+s+chr(10)+
293
+ "Heart Rate: "+str(hr)+" bpm β†’ "+hr_s)
294
 
295
  def tgt_tool(tat,pf12,hemo,platelets,time):
296
  risk=sum([float(tat)>15,float(pf12)>2.0,float(hemo)>50,float(platelets)<150])
297
+ r="HIGH THROMBOGENIC RISK" if risk>=3 else "MODERATE RISK" if risk>=2 else "LOW RISK"
298
+ return ("TGT BLOOD ANALYSIS"+chr(10)+
299
+ "━━━━━━━━━━━━━━━━━━━━"+chr(10)+
300
+ "Time: "+str(time)+" min"+chr(10)+
301
+ "TAT: "+str(tat)+" β†’ "+("HIGH" if float(tat)>15 else "NORMAL")+chr(10)+
302
+ "PF1.2: "+str(pf12)+" β†’ "+("HIGH" if float(pf12)>2.0 else "NORMAL")+chr(10)+
303
+ "Hemoglobin: "+str(hemo)+" β†’ "+("HIGH" if float(hemo)>50 else "NORMAL")+chr(10)+
304
+ "Platelets: "+str(platelets)+" β†’ "+("LOW" if float(platelets)<150 else "NORMAL")+chr(10)+
305
+ "━━━━━━━━━━━━━━━━━━━━"+chr(10)+
306
+ "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)+
316
+ "━━━━━━━━━━━━━━━━━━━━"+chr(10)+
317
+ "RGB Input: R="+str(r)+" G="+str(g)+" B="+str(b)+chr(10)+
318
+ "Orange Score: "+str(int(float(r)-float(b)))+chr(10)+
319
+ "Creatinine: "+str(c)+" mg/dL"+chr(10)+
320
+ "━━━━━━━━━━━━━━━━━━━━"+chr(10)+
321
+ "CKD STAGE: "+s+chr(10)+
322
+ "Confirm with: Heska Element HT5")
323
+
324
+ with gr.Blocks(title="CardioLab AI", css=CSS) as demo:
325
 
326
+ gr.HTML("""
327
+ <div class="main-header">
328
+ <div style="font-size:2.8em; font-weight:900; color:#e63946; letter-spacing:3px; text-shadow: 0 0 30px rgba(230,57,70,0.5);">
329
+ ❀️ CardioLab AI
330
+ </div>
331
+ </div>
332
+ """)
333
 
334
  with gr.Tabs():
335
+ with gr.Tab("πŸ’¬ Chat"):
336
+ chatbot = gr.Chatbot(label="", height=450)
337
  with gr.Row():
338
+ msg_box = gr.Textbox(
339
+ placeholder="Ask anything about CardioLab research...",
340
+ label="",
341
+ lines=2,
342
+ scale=4
343
+ )
344
+ with gr.Column(scale=1, min_width=100):
345
+ send_btn = gr.Button("Send ➀", variant="primary")
346
+ clear_btn = gr.Button("Clear", variant="secondary")
347
  send_btn.click(research_chat, inputs=[msg_box, chatbot], outputs=[msg_box, chatbot])
348
  msg_box.submit(research_chat, inputs=[msg_box, chatbot], outputs=[msg_box, chatbot])
349
+ clear_btn.click(lambda: ([], ""), outputs=[chatbot, msg_box])
350
 
351
+ with gr.Tab("πŸŽ™οΈ Voice"):
352
+ gr.Markdown("### Speak your research question β€” powered by Groq Whisper AI")
353
+ voice_chatbot = gr.Chatbot(label="", height=350)
354
+ audio_input = gr.Audio(sources=["microphone"], type="filepath", label="Record Question")
355
  with gr.Row():
356
+ voice_btn = gr.Button("Ask by Voice ➀", variant="primary")
357
+ voice_clear = gr.Button("Clear", variant="secondary")
358
  voice_btn.click(voice_chat, inputs=[audio_input, voice_chatbot], outputs=voice_chatbot)
359
  voice_clear.click(lambda: [], outputs=voice_chatbot)
360
 
361
+ with gr.Tab("πŸ” Papers"):
362
+ gr.Markdown("### Search latest research papers with verified working links")
363
+ with gr.Row():
364
+ search_input = gr.Textbox(
365
+ placeholder="e.g. mechanical heart valve thrombogenicity",
366
+ label="Research Topic",
367
+ scale=4
368
+ )
369
+ search_btn = gr.Button("Search ➀", variant="primary", scale=1)
370
+ search_output = gr.Textbox(label="Results β€” Verified Links Only", lines=18)
371
  search_btn.click(quick_search, inputs=search_input, outputs=search_output)
372
  search_input.submit(quick_search, inputs=search_input, outputs=search_output)
373
+ gr.Markdown("**Try:** `mechanical heart valve PIV` | `creatinine uPAD microfluidic` | `bileaflet MHV thrombogenicity`")
374
 
375
+ with gr.Tab("🎨 Diagrams"):
376
+ gr.Markdown("### Generate technical diagrams and schematics for CardioLab")
377
+ with gr.Row():
378
+ diagram_input = gr.Textbox(
379
+ placeholder="e.g. Mock Circulatory Loop with PIV system",
380
+ label="What to diagram",
381
+ scale=4
382
+ )
383
+ diagram_btn = gr.Button("Generate ➀", variant="primary", scale=1)
384
+ diagram_output = gr.Textbox(label="Technical Diagram", lines=18)
385
  diagram_btn.click(generate_diagram, inputs=diagram_input, outputs=diagram_output)
386
+ gr.Markdown("**Try:** `TGT circuit diagram` | `uPAD fabrication steps` | `PIV optical setup` | `MCL flow loop`")
387
 
388
+ with gr.Tab("πŸ“Š PIV"):
389
+ gr.Markdown("### Analyze Particle Image Velocimetry data from Mock Circulatory Loop")
390
  with gr.Row():
391
  with gr.Column():
392
+ v=gr.Number(label="Max Velocity (m/s)", value=1.8, info="Normal range: 0.5 - 2.0 m/s")
393
+ s=gr.Number(label="Wall Shear Stress (Pa)", value=6.5, info="Normal: below 5 Pa")
394
+ h=gr.Number(label="Heart Rate (bpm)", value=72, info="Normal: 60 - 100 bpm")
395
+ piv_btn = gr.Button("Analyze PIV Data ➀", variant="primary")
396
+ with gr.Column():
397
+ piv_out=gr.Textbox(label="Analysis Result", lines=8)
398
+ piv_btn.click(piv_tool, inputs=[v,s,h], outputs=piv_out)
399
+
400
+ with gr.Tab("🩸 TGT"):
401
+ gr.Markdown("### Interpret Thrombogenicity Tester blood analysis results")
402
+ with gr.Row():
403
+ with gr.Column():
404
+ t1=gr.Number(label="TAT (ng/mL)", value=18, info="Normal: below 8")
405
+ t2=gr.Number(label="PF1.2 (nmol/L)", value=2.5, info="Normal: below 2.0")
406
+ t3=gr.Number(label="Free Hemoglobin (mg/L)", value=60, info="Normal: below 20")
407
+ t4=gr.Number(label="Platelet Count (10Β³/ΞΌL)", value=140, info="Normal: above 150")
408
+ t5=gr.Number(label="Time (minutes)", value=40)
409
+ tgt_btn = gr.Button("Analyze TGT Results ➀", variant="primary")
410
+ with gr.Column():
411
+ out2=gr.Textbox(label="Analysis Result", lines=12)
412
+ tgt_btn.click(tgt_tool, inputs=[t1,t2,t3,t4,t5], outputs=out2)
413
+
414
+ with gr.Tab("πŸ§ͺ uPAD"):
415
+ gr.Markdown("### Analyze uPAD colorimetric result using Jaffe Reaction for CKD diagnosis")
416
+ with gr.Row():
417
+ with gr.Column():
418
+ gr.Markdown("**Enter RGB values from 64x64 pixel detection zone**")
419
+ r=gr.Number(label="R (Red)", value=210, info="Range: 0 - 255")
420
+ g=gr.Number(label="G (Green)", value=140, info="Range: 0 - 255")
421
+ b=gr.Number(label="B (Blue)", value=80, info="Range: 0 - 255")
422
+ upad_btn = gr.Button("Analyze uPAD ➀", variant="primary")
423
+ with gr.Column():
424
+ out3=gr.Textbox(label="CKD Analysis Result", lines=10)
425
+ upad_btn.click(upad_tool, inputs=[r,g,b], outputs=out3)
426
 
427
  demo.launch()