File size: 10,764 Bytes
f9a0bdb 517de74 994feb6 5e95a20 d26962d f9a0bdb 517de74 f9a0bdb 94febc8 f9a0bdb 2b2ae99 f9a0bdb fe00e4d f9a0bdb 978c4cf 4372b0a a4f7e5c f9a0bdb 7e2c73b f9a0bdb 7e2c73b 994feb6 7e2c73b f9a0bdb 7e2c73b fe00e4d 5e95a20 7e2c73b f9a0bdb 994feb6 7e2c73b f9a0bdb 994feb6 b6ee928 4372b0a b6ee928 a4f7e5c b6ee928 f9a0bdb 36986a7 a4f7e5c fe00e4d f9a0bdb fe00e4d 994feb6 fbb4b8d f9a0bdb 4372b0a fe00e4d 994feb6 f9a0bdb fe00e4d f9a0bdb fe00e4d f9a0bdb fe00e4d f9a0bdb 994feb6 5e95a20 fe00e4d |
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 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
#!/usr/bin/env python3
# app.py β MedGenesis AI Β· Streamlit front-end (v3)
# ---------------------------------------------------
# β’ Dual-LLM selector (OpenAI | Gemini)
# β’ Robust PDF export (all Unicode β Latin-1 safe)
# β’ Lazy session-state handling so a failed background
# request never kills the whole app.
# β’ New βVariantsβ tab (cBioPortal) + null-safe βGraphβ
# and βMetricsβ using the patched helpers.
import os, pathlib, asyncio, re
from pathlib import Path
import streamlit as st
import pandas as pd
import plotly.express as px
from streamlit_agraph import agraph
from fpdf import FPDF
from mcp.orchestrator import orchestrate_search, answer_ai_question
from mcp.workspace import get_workspace, save_query
from mcp.knowledge_graph import build_agraph
from mcp.graph_metrics import build_nx, get_top_hubs, get_density
# ββ Streamlit telemetry dir fix βββββββββββββββββββββββββββββββββββββ
os.environ["STREAMLIT_DATA_DIR"] = "/tmp/.streamlit"
os.environ["XDG_STATE_HOME"] = "/tmp"
os.environ["STREAMLIT_BROWSER_GATHERUSAGESTATS"] = "false"
pathlib.Path("/tmp/.streamlit").mkdir(parents=True, exist_ok=True)
ROOT = Path(__file__).parent
LOGO = ROOT / "assets" / "logo.png"
# ββ PDF export helper (robust to ALL Unicode) βββββββββββββββββββββββ
def _latin1_safe(txt: str) -> str:
return txt.encode("latin-1", "replace").decode("latin-1")
def _pdf(papers):
pdf = FPDF()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.add_page()
pdf.set_font("Helvetica", size=11)
pdf.cell(200, 8, _latin1_safe("MedGenesis AI β Results"), ln=True, align="C")
pdf.ln(3)
for i, p in enumerate(papers, 1):
pdf.set_font("Helvetica", "B", 11)
pdf.multi_cell(0, 7, _latin1_safe(f"{i}. {p['title']}"))
pdf.set_font("Helvetica", "", 9)
body = (
f"{p['authors']}\n"
f"{p['summary']}\n"
f"{p['link']}\n"
)
pdf.multi_cell(0, 6, _latin1_safe(body))
pdf.ln(1)
return pdf.output(dest="S").encode("latin-1", "replace")
# ββ Sidebar workspace βββββββββββββββββββββββββββββββββββββββββββββββ
def _workspace_sidebar():
with st.sidebar:
st.header("ποΈ Workspace")
ws = get_workspace()
if not ws:
st.info("Run a search then press **Save** to populate this list.")
return
for i, item in enumerate(ws, 1):
with st.expander(f"{i}. {item['query']}"):
st.write(item["result"]["ai_summary"])
# ββ UI main routine βββββββββββββββββββββββββββββββββββββββββββββββββ
def render_ui():
st.set_page_config("MedGenesis AI", layout="wide")
# Session-state defaults
for key, default in {
"query_result" : None,
"last_query" : "",
"last_llm" : "openai",
"followup_input" : "",
"followup_response": None,
}.items():
if key not in st.session_state:
st.session_state[key] = default
_workspace_sidebar()
# Header block
c1, c2 = st.columns([0.15, 0.85])
with c1:
if LOGO.exists():
st.image(str(LOGO), width=105)
with c2:
st.markdown("## 𧬠**MedGenesis AI**")
st.caption("Multi-source biomedical assistant β OpenAI / Gemini")
# Controls
llm = st.radio("LLM engine", ["openai", "gemini"],
horizontal=True, index=0)
query = st.text_input("Enter biomedical question",
placeholder="e.g. CRISPR glioblastoma therapy")
# Run search
if st.button("Run Search π") and query:
with st.spinner("Collecting literature & biomedical data β¦"):
res = asyncio.run(orchestrate_search(query, llm=llm))
st.session_state.query_result = res
st.session_state.last_query = query
st.session_state.last_llm = llm
st.session_state.followup_input = ""
st.session_state.followup_response = None
res = st.session_state.query_result
if res:
# Guard against missing keys
for key in (
"papers", "umls", "drug_safety", "genes", "mesh_defs",
"gene_disease", "clinical_trials", "variants"
):
res.setdefault(key, [])
# -------------- TABS -------------------------------------------------
tabs = st.tabs([
"Results", "Genes", "Trials", "Variants",
"Graph", "Metrics", "Visuals"
])
# ββ Results tab βββββββββββββββββββββββββββββββββββββββββββββββββββββ
with tabs[0]:
st.subheader("Literature")
for i, p in enumerate(res["papers"], 1):
st.markdown(f"**{i}. [{p['title']}]({p['link']})** *{p['authors']}*")
st.write(p["summary"])
col1, col2 = st.columns(2)
with col1:
st.download_button(
"CSV",
pd.DataFrame(res["papers"]).to_csv(index=False),
"papers.csv",
"text/csv",
)
with col2:
st.download_button(
"PDF",
_pdf(res["papers"]),
"papers.pdf",
"application/pdf",
)
if st.button("πΎ Save"):
save_query(st.session_state.last_query, res)
st.success("Saved to workspace")
st.subheader("UMLS concepts")
for c in res["umls"]:
if c.get("cui"):
st.write(f"- **{c['name']}** ({c['cui']})")
st.subheader("OpenFDA safety signals")
for d in res["drug_safety"]:
st.json(d)
st.subheader("AI summary")
st.info(res["ai_summary"])
# ββ Genes tab βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
with tabs[1]:
st.header("Gene / Variant signals")
for g in res["genes"]:
lab = g.get("name") or g.get("symbol") or g.get("geneid")
st.write(f"- **{lab}**")
if res["gene_disease"]:
st.markdown("### DisGeNET associations")
st.json(res["gene_disease"][:15])
if res["mesh_defs"]:
st.markdown("### MeSH definitions")
for d in res["mesh_defs"]:
if d:
st.write("-", d)
# ββ Trials tab ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
with tabs[2]:
st.header("Clinical trials")
if not res["clinical_trials"]:
st.info("No trials (rate-limited or none found).")
for t in res["clinical_trials"]:
st.markdown(f"**{t['nctId']}** β {t['briefTitle']}")
st.write(f"Phase {t.get('phase')} | Status {t.get('status')}")
# ββ Variants tab ββββββββββββββββββββββββββββββββββββββββββββββββββββ
with tabs[3]:
st.header("Cancer variants (cBioPortal)")
if not res["variants"]:
st.info("No variant data.")
else:
st.json(res["variants"][:50])
# ββ Graph tab βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
with tabs[4]:
nodes, edges, cfg = build_agraph(
res["papers"], res["umls"], res["drug_safety"]
)
hl = st.text_input("Highlight node:", key="hl")
if hl:
pat = re.compile(re.escape(hl), re.I)
for n in nodes:
n.color = "#f1c40f" if pat.search(n.label) else "#d3d3d3"
agraph(nodes, edges, cfg)
# ββ Metrics tab βββββββββββββββββββββββββββββββββββββββββββββββββββββ
with tabs[5]:
G = build_nx(
[n.__dict__ for n in nodes],
[e.__dict__ for e in edges],
)
st.metric("Density", f"{get_density(G):.3f}")
st.markdown("**Top hubs**")
for nid, sc in get_top_hubs(G):
lab = next((n.label for n in nodes if n.id == nid), nid)
st.write(f"- {lab} {sc:.3f}")
# ββ Visuals tab ββββββββββββββββββββββββββββββββββββββββββββββββββββ
with tabs[6]:
years = [p.get("published", "")[:4] for p in res["papers"] if p.get("published")]
if years:
st.plotly_chart(px.histogram(years, nbins=12,
title="Publication Year"))
# ββ Follow-up Q-A block ββββββββββββββββββββββββββββββββββββββββββββ
st.markdown("---")
st.text_input("Ask follow-up question:", key="followup_input")
def _on_ask():
q = st.session_state.followup_input.strip()
if not q:
st.warning("Please type a question first.")
return
with st.spinner("Querying LLM β¦"):
ans = asyncio.run(
answer_ai_question(
q,
context=st.session_state.last_query,
llm=st.session_state.last_llm,
)
)
st.session_state.followup_response = ans["answer"]
st.button("Ask AI", on_click=_on_ask)
if st.session_state.followup_response:
st.write(st.session_state.followup_response)
else:
st.info("Enter a question and press **Run Search π**")
if __name__ == "__main__":
render_ui()
|