Spaces:
Running
Running
File size: 8,642 Bytes
ea36801 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | import gradio as gr
import os, requests
from groq import Groq
GROQ_KEY = os.environ.get("GROQ_API_KEY","")
client = Groq(api_key=GROQ_KEY)
KNOWHOW = """
SJSU CardioLab Know-How:
MCL: Sylgard 184 PDMS 10:1 ratio 48hr cure, green laser PIV, 70bpm 5L/min flow
TGT: Arduino Uno + Stepper Motor, 150mL blood, sample 0/20/40/60min, TAT PF1.2 hemolysis platelets
uPAD: Jaffe reaction creatinine + picric acid = orange-red, normal 0.6-1.2 mg/dL, CKD above 1.5
FSI: COMSOL ALE mesh, blood 1060 kg/m3, 0.0035 Pa.s, St Jude geometry
MHV: 27mm SJM Regent, bileaflet trileaflet monoleaflet pediatric
CKD Stages: 1 below 1.5, 2 1.5-3.0, 3-4 3.0-6.0, 5 above 6.0 mg/dL
Equipment: Heska HT5, time-resolved PIV, Tygon tubing, Arduino
13 Projects: MCL/PIV, TGT, FSI simulation, uPAD CKD diagnostics
"""
def search_pubmed(query, n=3):
try:
r = requests.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi",
params={"db":"pubmed","term":query,"retmax":n,"retmode":"json","sort":"date"}, timeout=10)
ids = r.json()["esearchresult"]["idlist"]
if not ids: return [], ""
r2 = requests.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi",
params={"db":"pubmed","id":",".join(ids),"retmode":"xml","rettype":"abstract"}, timeout=10)
import xmltodict
data = xmltodict.parse(r2.content)
articles = data.get("PubmedArticleSet",{}).get("PubmedArticle",[])
if isinstance(articles, dict): articles = [articles]
real_links = []
context = ""
for a in articles[:n]:
try:
c = a["MedlineCitation"]
title = str(c["Article"]["ArticleTitle"])
abstract = c["Article"].get("Abstract",{}).get("AbstractText","")
if isinstance(abstract, list): abstract = " ".join([str(x) for x in abstract])
if isinstance(abstract, dict): abstract = str(abstract.get("#text",""))
pmid = str(c["PMID"]["#text"] if isinstance(c["PMID"],dict) else c["PMID"])
real_url = "https://pubmed.ncbi.nlm.nih.gov/" + pmid
real_links.append("- " + title[:100] + "\n " + real_url)
context += "[PubMed:" + pmid + "] " + title + ". " + str(abstract)[:300] + "\n"
except: continue
return real_links, context
except: return [], ""
def search_scholar(query, n=3):
try:
r = requests.get("https://api.semanticscholar.org/graph/v1/paper/search",
params={"query":query,"limit":n,"fields":"title,abstract,year,url"}, timeout=10)
papers = r.json().get("data",[])
real_links = []
context = ""
for p in papers:
title = p.get("title","")
abstract = (p.get("abstract") or "")[:300]
year = str(p.get("year",""))
url = p.get("url","")
if url:
real_links.append("- " + title[:100] + " (" + year + ")\n " + url)
context += "[Scholar " + year + "] " + title + ". " + abstract + "\n"
return real_links, context
except: return [], ""
def ask_with_memory(message, history):
if not GROQ_KEY:
return "Error: GROQ_API_KEY not set in Space secrets."
# Build messages with full history for memory
messages = [
{
"role": "system",
"content": """You are CardioLab AI built on Biomni from Stanford SNAP Lab.
Expert in SJSU Biomedical Engineering research.
You remember everything said in this conversation.
NEVER invent paper titles or URLs.
ONLY cite papers from the search results provided.
CARDIOLAB KNOW-HOW:
""" + KNOWHOW
}
]
# Add chat history — new Gradio format uses dicts
for msg in history:
if isinstance(msg, dict):
messages.append({"role": msg["role"], "content": msg["content"]})
else:
# fallback for tuple format
messages.append({"role": "user", "content": str(msg[0])})
messages.append({"role": "assistant", "content": str(msg[1])})
# Search papers
cardio_query = message + " mechanical heart valve OR microfluidic OR CKD creatinine OR PIV OR thrombogenicity"
pubmed_links, pubmed_context = search_pubmed(cardio_query, n=3)
scholar_links, scholar_context = search_scholar(message + " biomedical", n=3)
sources = pubmed_context + scholar_context
messages.append({
"role": "user",
"content": message + "\n\nReal papers (ONLY use these):\n" + sources[:3000]
})
response = client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=messages,
max_tokens=800
)
answer = response.choices[0].message.content
links = ""
if pubmed_links:
links += "\n\n📚 VERIFIED PUBMED LINKS:\n" + "\n".join(pubmed_links[:3])
if scholar_links:
links += "\n\n🎓 VERIFIED SCHOLAR LINKS:\n" + "\n".join(scholar_links[:3])
return answer + links
def piv_tool(velocity, shear, hr):
v = "HIGH - stenosis risk" if float(velocity)>2.0 else "NORMAL"
s = "HIGH - thrombosis risk" if float(shear)>10 else "ELEVATED - monitor" if float(shear)>5 else "NORMAL"
return "Velocity: "+str(velocity)+" m/s - "+v+"\nShear: "+str(shear)+" Pa - "+s+"\nHeart Rate: "+str(hr)+" bpm"
def tgt_tool(tat, pf12, hemo, platelets, time):
risk = sum([float(tat)>15, float(pf12)>2.0, float(hemo)>50, float(platelets)<150])
overall = "HIGH THROMBOGENIC RISK" if risk>=3 else "MODERATE RISK" if risk>=2 else "LOW RISK"
return "TAT:"+str(tat)+" PF1.2:"+str(pf12)+" Hemo:"+str(hemo)+" Platelets:"+str(platelets)+"\nTime:"+str(time)+"min\nResult: "+overall
def upad_tool(r, g, b):
creatinine = max(0, round(0.02*(float(r)-float(b))-0.5, 2))
stage = "Normal" if creatinine<1.2 else "Borderline" if creatinine<1.5 else "Stage 2 CKD" if creatinine<3.0 else "Stage 3-4 CKD" if creatinine<6.0 else "Stage 5 CKD"
return "Creatinine: "+str(creatinine)+" mg/dL\nStage: "+stage+"\nConfirm with: Heska Element HT5"
with gr.Blocks(title="CardioLab AI - SJSU") as demo:
gr.Markdown("# CardioLab AI Agent")
gr.Markdown("### SJSU Biomedical Engineering | Biomni + Llama 70B + Chat Memory + PubMed")
gr.Markdown("GitHub: github.com/pranatechsol/Cardio-Lab-Ai")
with gr.Tab("Research Chat"):
gr.Markdown("### Chat with memory — remembers your full conversation like ChatGPT")
chatbot = gr.Chatbot(
label="CardioLab AI",
height=500,
type="messages"
)
msg = gr.Textbox(
label="Your message",
placeholder="Ask anything about CardioLab research...",
lines=2
)
with gr.Row():
send = gr.Button("Send", variant="primary")
clear = gr.Button("Clear Chat")
def respond(message, history):
bot_message = ask_with_memory(message, history)
history.append({"role": "user", "content": message})
history.append({"role": "assistant", "content": bot_message})
return "", history
send.click(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])
msg.submit(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])
clear.click(lambda: [], None, chatbot)
with gr.Tab("PIV Analysis"):
gr.Markdown("### Analyze PIV flow data from Mock Circulatory Loop")
v = gr.Number(label="Max Velocity m/s", value=1.8)
s = gr.Number(label="Shear Stress Pa", value=6.5)
h = gr.Number(label="Heart Rate bpm", value=72)
out = gr.Textbox(label="Result", lines=4)
gr.Button("Analyze PIV").click(piv_tool, inputs=[v,s,h], outputs=out)
with gr.Tab("TGT Results"):
gr.Markdown("### Interpret Thrombogenicity Tester blood results")
t1 = gr.Number(label="TAT", value=18)
t2 = gr.Number(label="PF1.2", value=2.5)
t3 = gr.Number(label="Free Hemoglobin mg/L", value=60)
t4 = gr.Number(label="Platelet Count", value=140)
t5 = gr.Number(label="Time minutes", value=40)
out2 = gr.Textbox(label="Result", lines=5)
gr.Button("Analyze TGT").click(tgt_tool, inputs=[t1,t2,t3,t4,t5], outputs=out2)
with gr.Tab("uPAD CKD"):
gr.Markdown("### Analyze uPAD colorimetric result - Jaffe Reaction")
r = gr.Number(label="R value", value=210)
g = gr.Number(label="G value", value=140)
b = gr.Number(label="B value", value=80)
out3 = gr.Textbox(label="Result", lines=4)
gr.Button("Analyze uPAD").click(upad_tool, inputs=[r,g,b], outputs=out3)
demo.launch()
|