Spaces:
Running
Running
| 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() | |