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