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()