File size: 8,870 Bytes
140bf8b a31351f 140bf8b 517de74 994feb6 5e95a20 f9a0bdb 39219c6 517de74 8ab7297 94febc8 8ab7297 2b2ae99 c17cf5b 140bf8b 64163be 140bf8b f9a0bdb 978c4cf 4372b0a a4f7e5c 140bf8b 39219c6 a31351f f9a0bdb 7e2c73b 140bf8b 7e2c73b 994feb6 7e2c73b 8ab7297 7e2c73b 5e95a20 7e2c73b 140bf8b f9a0bdb c17cf5b 39219c6 994feb6 7e2c73b 140bf8b 8ab7297 b6ee928 8ab7297 4372b0a b6ee928 140bf8b 39219c6 8ab7297 a4f7e5c fe00e4d a31351f 140bf8b fe00e4d 994feb6 140bf8b fe00e4d 8ab7297 140bf8b 994feb6 a31351f f9a0bdb 140bf8b 64163be f9a0bdb 8ab7297 140bf8b 39219c6 140bf8b 39219c6 140bf8b 39219c6 8ab7297 39219c6 140bf8b 8ab7297 f9a0bdb 140bf8b 76418d6 39219c6 76418d6 8ab7297 76418d6 140bf8b 8ab7297 140bf8b 64163be 76418d6 140bf8b 8ab7297 140bf8b 76418d6 140bf8b 76418d6 140bf8b 8ab7297 140bf8b 76418d6 140bf8b 76418d6 140bf8b 76418d6 64163be 76418d6 defde16 76418d6 defde16 140bf8b defde16 140bf8b defde16 76418d6 64163be 76418d6 140bf8b 8ab7297 f352c67 c17cf5b 140bf8b 64163be 76418d6 8ab7297 140bf8b 8ab7297 f352c67 8ab7297 140bf8b 8ab7297 64163be 8ab7297 140bf8b 76418d6 64163be 8ab7297 76418d6 8ab7297 76418d6 64163be 8ab7297 140bf8b 76418d6 8ab7297 76418d6 64163be 76418d6 c17cf5b 76418d6 f9a0bdb c17cf5b 5e95a20 a31351f |
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 259 260 261 262 263 |
#!/usr/bin/env python3
"""
MedGenesis AI – Streamlit front-end (v3)
--------------------------------------
Supports **OpenAI** and **Gemini** engines and the enriched backend
payload introduced in orchestrator v3:
• papers, umls, drug_safety, genes, mesh_defs, gene_disease,
clinical_trials, variants, ai_summary
Tabs:
Results | Genes | Trials | Variants | Graph | Metrics | Visuals
"""
import os
import pathlib
import asyncio
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
# Streamlit telemetry directory → /tmp
os.environ.update({
"STREAMLIT_DATA_DIR": "/tmp/.streamlit",
"XDG_STATE_HOME": "/tmp",
"STREAMLIT_BROWSER_GATHERUSAGESTATS": "false",
})
pathlib.Path("/tmp/.streamlit").mkdir(parents=True, exist_ok=True)
ROOT = Path(__file__).parent
LOGO = ROOT / "assets" / "logo.png"
def _latin1_safe(txt: str) -> str:
"""Coerce UTF-8 → Latin-1 with replacement (for FPDF)."""
return txt.encode("latin-1", "replace").decode("latin-1")
def _pdf(papers: list[dict]) -> bytes:
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.get('title','')}"))
pdf.set_font("Helvetica", "", 9)
body = (
f"{p.get('authors','')}\n"
f"{p.get('summary','')}\n"
f"{p.get('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")
# Session-state defaults
defaults = dict(
query_result=None,
followup_input="",
followup_response=None,
last_query="",
last_llm="openai",
)
for k, v in defaults.items():
st.session_state.setdefault(k, v)
_workspace_sidebar()
# Header
col1, col2 = st.columns([0.15, 0.85])
with col1:
if LOGO.exists():
st.image(str(LOGO), width=105)
with col2:
st.markdown("## 🧬 **MedGenesis AI**")
st.caption("Multi-source biomedical assistant · OpenAI / Gemini")
# Controls
engine = st.radio("LLM engine", ["openai", "gemini"], horizontal=True)
query = st.text_input("Enter biomedical question", placeholder="e.g. CRISPR glioblastoma therapy")
# Alerts
if get_workspace():
try:
alerts = asyncio.run(check_alerts([w["query"] for w in get_workspace()]))
if alerts:
with st.sidebar:
st.subheader("🔔 New papers")
for q, lnks in alerts.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=engine))
st.session_state.update(
query_result=res,
last_query=query,
last_llm=engine,
followup_input="",
followup_response=None,
)
st.success(f"Completed with **{res['llm_used'].title()}**")
res = st.session_state.query_result
if not res:
st.info("Enter a question and press **Run Search 🚀**")
return
# 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.get('title','')}]({p.get('link','')})** *{p.get('authors','')}*")
st.write(p.get('summary',''))
c1, c2 = st.columns(2)
with c1:
st.download_button("CSV", pd.DataFrame(res['papers']).to_csv(index=False), "papers.csv", "text/csv")
with c2:
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.get('name','')}** ({c.get('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")
valid_genes = [g for g in res['genes'] if isinstance(g, dict)]
if valid_genes:
for g in valid_genes:
sym = g.get('symbol') or g.get('name') or ''
st.write(f"- **{sym}**")
else:
st.info("No gene signals returned.")
mesh_list = [d for d in res['mesh_defs'] if isinstance(d, str) and d]
if mesh_list:
st.markdown("### MeSH definitions")
for d in mesh_list:
st.write(f"- {d}")
gene_disease = [d for d in res['gene_disease'] if isinstance(d, dict)]
if gene_disease:
st.markdown("### DisGeNET links")
st.json(gene_disease[:15])
# --- Trials tab ---
with tabs[2]:
st.header("Clinical trials")
trials = res['clinical_trials']
if not trials:
st.info(
"No trials found. Try a disease name (e.g. ‘Breast Neoplasms’) "
"or specific drug (e.g. ‘Pembrolizumab’)."
)
else:
for t in trials:
st.markdown(
f"**{t.get('nctId','')}** – {t.get('briefTitle','')} "
f"Phase {t.get('phase','?')} | Status {t.get('status','?')}"
)
# --- Variants tab ---
with tabs[3]:
st.header("Cancer variants (cBioPortal)")
variants = res['variants']
if not variants:
st.info(
"No variants found. Try a well-known gene symbol like ‘TP53’ or ‘BRCA1’."
)
else:
st.json(variants[:30])
# --- Graph tab ---
with tabs[4]:
nodes, edges, cfg = build_agraph(res['papers'], res['umls'], res['drug_safety'])
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') for p in res['papers'] if p.get('published')]
if years:
st.plotly_chart(px.histogram(years, nbins=12, title="Publication Year"))
# Follow-up QA (outside tabs)
st.markdown("---")
input_col, button_col = st.columns([4, 1])
with input_col:
followup = st.text_input("Ask follow-up question:", key="followup_input")
with button_col:
if st.button("Ask AI"):
if followup.strip():
with st.spinner("Querying LLM …"):
ans = asyncio.run(
answer_ai_question(
question=followup,
context=st.session_state.last_query,
llm=st.session_state.last_llm,
)
)
st.session_state.followup_response = ans.get('answer', '')
else:
st.warning("Please type a question first.")
if st.session_state.followup_response:
st.write(st.session_state.followup_response)
if __name__ == "__main__":
render_ui()
|