File size: 8,248 Bytes
4372b0a 7e2c73b 4372b0a 7e2c73b 4372b0a 7e2c73b 4372b0a 7e2c73b b6ee928 517de74 5e95a20 7e2c73b d26962d 517de74 7e2c73b 5e95a20 94febc8 0f74db4 978c4cf 4372b0a a4f7e5c 7e2c73b a4f7e5c 7e2c73b 5e95a20 7e2c73b a4f7e5c 42d374e 7e2c73b b6ee928 4372b0a b6ee928 a4f7e5c b6ee928 7e2c73b 3987ef0 a4f7e5c 7e2c73b 978c4cf 7e2c73b 4372b0a 5e95a20 4372b0a 0f74db4 5e95a20 42d374e 7e2c73b 4372b0a 42d374e 7e2c73b a4f7e5c 5e95a20 a4f7e5c 7e2c73b a4f7e5c 4372b0a 5e95a20 4372b0a 5e95a20 4372b0a c1cd51c 7e2c73b c1cd51c 5e95a20 4372b0a 5e95a20 4372b0a 5e95a20 4372b0a 5e95a20 4372b0a 5e95a20 4372b0a 5e95a20 4372b0a 5e95a20 4372b0a 5e95a20 4372b0a 0f74db4 94febc8 7e2c73b c1cd51c 4372b0a 5e95a20 4372b0a 5e95a20 4372b0a 5e95a20 4372b0a 5e95a20 7e2c73b d26962d 4372b0a 5e95a20 a4f7e5c 5e95a20 7e2c73b a4f7e5c 7e2c73b d26962d 5e95a20 4372b0a 5e95a20 4372b0a 5e95a20 7e2c73b d26962d 5e95a20 4372b0a 5e95a20 4372b0a 5e95a20 7e2c73b a4f7e5c 5e95a20 a4f7e5c 7e2c73b 5e95a20 4372b0a 5e95a20 a4f7e5c 5e95a20 d26962d 978c4cf 4372b0a 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 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
#!/usr/bin/env python3
# MedGenesis AI Β· CPU-only Streamlit app (OpenAI / Gemini)
# ββ Streamlit telemetry dir fix βββββββββββββββββββββββββββββββββββββββ
import os, pathlib
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)
# ββ Std-lib / third-party imports ββββββββββββββββββββββββββββββββββββ
import asyncio, re
from pathlib import Path
import streamlit as st
import pandas as pd
import plotly.express as px
from fpdf import FPDF # classic FPDF β Latin-1 only
from streamlit_agraph import agraph
# ββ Internal helpers ββββββββββββββββββββββββββββββββββββββββββββββββ
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"
# ββ PDF export helper (UTF-8 β Latin-1 βsafeβ) ββββββββββββββββββββββ
def _latin1_safe(txt: str) -> str:
"""Return text that FPDF(latin-1) can embed; replace unknown chars."""
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"])
# ββ Main UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def render_ui():
st.set_page_config("MedGenesis AI", layout="wide")
_workspace_sidebar()
# Header
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")
# Alert check
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
# 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.success(f"Completed with **{res['llm_used'].title()}**")
tabs = st.tabs(["Results", "Genes", "Trials", "Graph",
"Metrics", "Visuals"])
# Results
with tabs[0]:
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"])
# Genes
with tabs[1]:
st.header("Gene / Variant signals")
for g in res["genes"]:
st.write(f"- **{g.get('name', g.get('geneid'))}** "
f"{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)
# Trials
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'][0]}** β {t['BriefTitle'][0]}")
st.write(f"Phase {t.get('Phase', [''])[0]} "
f"| Status {t['OverallStatus'][0]}")
# Graph
with tabs[3]:
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
with tabs[4]:
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
with tabs[5]:
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"))
# Follow-up Q-A
st.markdown("---")
follow = st.text_input("Ask follow-up:")
if st.button("Ask AI"):
ans = asyncio.run(answer_ai_question(follow,
context=query,
llm=llm))
st.write(ans["answer"])
else:
st.info("Enter a question and press **Run Search π**")
# entry-point
if __name__ == "__main__":
render_ui()
|