File size: 7,530 Bytes
4372b0a 7e2c73b 4372b0a a7d3db7 d55cbab 4372b0a b6ee928 517de74 5e95a20 a7d3db7 d26962d 517de74 fbb4b8d d55cbab 94febc8 fbb4b8d d55cbab 978c4cf 4372b0a a4f7e5c 7e2c73b a4f7e5c 7e2c73b fbb4b8d 7e2c73b 5e95a20 7e2c73b fbb4b8d 7e2c73b a4f7e5c 7e2c73b b6ee928 4372b0a b6ee928 a4f7e5c b6ee928 3987ef0 a4f7e5c fbb4b8d d55cbab fbb4b8d 7e2c73b 978c4cf 4372b0a 5e95a20 4372b0a 0f74db4 5e95a20 42d374e fbb4b8d a7d3db7 42d374e a4f7e5c 5e95a20 a4f7e5c 7e2c73b fbb4b8d a4f7e5c 4372b0a 5e95a20 4372b0a fbb4b8d d55cbab 5e95a20 d55cbab 5e95a20 fbb4b8d a7d3db7 fbb4b8d a7d3db7 fbb4b8d d26962d 978c4cf 5e95a20 40a1d7a |
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 |
#!/usr/bin/env python3
# MedGenesis AI Β· CPU-only Streamlit app (OpenAI / Gemini)
import os, pathlib
# ββ 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)
import asyncio, re
from pathlib import Path
import streamlit as st
import pandas as pd
import plotly.express as px
from fpdf import FPDF
from streamlit_agraph import agraph
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
from mcp.alerts import check_alerts
ROOT = Path(__file__).parent
LOGO = ROOT / "assets" / "logo.png"
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{p['summary']}\n{p['link']}\n"
pdf.multi_cell(0, 6, _latin1_safe(body))
pdf.ln(1)
return pdf.output(dest="S").encode("latin-1", "replace")
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"])
def render_ui():
st.set_page_config("MedGenesis AI", layout="wide")
# Persist follow-up input
if "followup_input" not in st.session_state:
st.session_state.followup_input = ""
_workspace_sidebar()
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")
llm = st.radio("LLM engine", ["openai", "gemini"], horizontal=True)
query = st.text_input("Enter biomedical question", placeholder="e.g. CRISPR glioblastoma therapy")
if get_workspace():
try:
news = asyncio.run(check_alerts([w["query"] for w in get_workspace()]))
if news:
with st.sidebar:
st.subheader("π New papers")
for q, lnks in news.items():
st.write(f"**{q}** β {len(lnks)} new")
except Exception:
pass
# Trigger search
if st.button("Run Search π") and query:
with st.spinner("Collecting literature & biomedical data β¦"):
res = asyncio.run(orchestrate_search(query, llm=llm))
st.success(f"Completed with **{res['llm_used'].title()}**")
st.session_state.query_result = res
st.session_state.followup_input = ""
else:
res = st.session_state.get("query_result", None)
if res:
tabs = st.tabs(["Results", "Genes", "Trials", "Graph", "Metrics", "Visuals"])
with tabs[0]: # Results
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(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")
for d in res["drug_safety"]:
st.json(d)
st.subheader("AI summary")
st.info(res["ai_summary"])
with tabs[1]: # Genes
st.header("Gene / Variant signals")
for g in res["genes"]:
st.write(f"- **{g.get('name', g.get('geneid'))}** {g.get('description', '')}")
if res["gene_disease"]:
st.markdown("### DisGeNET links")
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)
with tabs[2]: # Trials
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'][0]}** β {t['BriefTitle'][0]}")
st.write(f"Phase {t.get('Phase',[''])[0]} | Status {t['OverallStatus'][0]}")
with tabs[3]: # Graph
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)
with tabs[4]: # Metrics
nodes, edges, _ = build_agraph(res["papers"], res["umls"], res["drug_safety"])
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}")
with tabs[5]: # Visuals
years = [p["published"] for p in res["papers"] if p.get("published")]
if years:
st.plotly_chart(px.histogram(years, nbins=12, title="Publication Year"))
st.markdown("---")
follow = st.text_input(
"Ask followβup question:",
value=st.session_state.followup_input,
key="followup_input"
)
if st.button("Ask AI"):
st.session_state.followup_input = follow
if follow.strip():
with st.spinner("Generating AI response..."):
ans = asyncio.run(answer_ai_question(
follow, context=query, llm=llm))
st.write(ans["answer"])
else:
st.warning("Please type a follow-up question before submitting.")
else:
st.info("Enter a question and press **Run Search π**")
if __name__ == "__main__":
render_ui()
|