Update app.py
Browse files
app.py
CHANGED
@@ -1,134 +1,148 @@
|
|
1 |
# app.py
|
2 |
|
3 |
-
import os
|
4 |
-
import streamlit as st
|
5 |
-
import asyncio
|
6 |
from pathlib import Path
|
|
|
|
|
7 |
import pandas as pd
|
8 |
-
from fpdf import FPDF
|
9 |
import plotly.express as px
|
10 |
-
import
|
|
|
11 |
|
12 |
from mcp.orchestrator import orchestrate_search, answer_ai_question
|
13 |
-
from mcp.
|
14 |
-
from mcp.workspace import get_workspace, save_query
|
15 |
from mcp.knowledge_graph import build_agraph
|
16 |
-
from streamlit_agraph import agraph
|
17 |
|
18 |
ROOT = Path(__file__).parent
|
19 |
LOGO = ROOT / "assets" / "logo.png"
|
20 |
|
|
|
21 |
def generate_pdf(papers):
|
22 |
-
pdf = FPDF()
|
23 |
-
pdf.add_page()
|
24 |
-
pdf.set_font("Arial", size=12)
|
25 |
pdf.cell(200, 10, "MedGenesis AI - Search Results", ln=True, align="C")
|
26 |
pdf.ln(10)
|
27 |
for i, p in enumerate(papers, 1):
|
28 |
pdf.set_font("Arial", "B", 12)
|
29 |
pdf.multi_cell(0, 10, f"{i}. {p['title']}")
|
30 |
pdf.set_font("Arial", "", 10)
|
31 |
-
pdf.multi_cell(0, 8,
|
|
|
32 |
pdf.ln(2)
|
33 |
return pdf.output(dest="S").encode("latin-1")
|
34 |
|
|
|
35 |
def render_ui():
|
36 |
st.set_page_config(page_title="MedGenesis AI", layout="wide")
|
37 |
|
38 |
-
#
|
39 |
with st.sidebar:
|
40 |
st.header("ποΈ Workspace")
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
st.info("Run & save searches here.")
|
51 |
-
|
52 |
-
# Header and logo
|
53 |
col1, col2 = st.columns([0.15, 0.85])
|
54 |
with col1:
|
55 |
-
if LOGO.exists():
|
56 |
-
st.image(str(LOGO), width=100)
|
57 |
with col2:
|
58 |
-
st.markdown("## 𧬠MedGenesis AI
|
59 |
-
st.
|
60 |
|
61 |
st.markdown("---")
|
62 |
-
query = st.text_input("π
|
63 |
|
64 |
-
|
65 |
-
if st.button("Run Search π"):
|
66 |
-
with st.spinner("
|
67 |
results = asyncio.run(orchestrate_search(query))
|
68 |
-
|
69 |
|
70 |
-
|
71 |
-
tabs = st.tabs([
|
|
|
|
|
|
|
72 |
|
73 |
-
#
|
74 |
with tabs[0]:
|
75 |
-
st.header("π
|
76 |
for i, p in enumerate(results["papers"], 1):
|
77 |
st.markdown(f"**{i}. [{p['title']}]({p['link']})** \n*{p['authors']}* ({p['source']})")
|
78 |
-
st.markdown(f"<
|
|
|
79 |
if st.button("Save to Workspace"):
|
80 |
-
save_query(query, results)
|
81 |
-
|
82 |
df = pd.DataFrame(results["papers"])
|
83 |
-
st.download_button("
|
84 |
-
|
85 |
-
|
86 |
st.subheader("π§ UMLS Concepts")
|
87 |
for c in results["umls"]:
|
88 |
if c.get("cui"):
|
89 |
-
st.markdown(f"- **{c['name']}** (
|
|
|
90 |
st.subheader("π Drug Safety (OpenFDA)")
|
91 |
for d in results["drug_safety"]:
|
92 |
st.json(d)
|
93 |
-
|
|
|
94 |
st.info(results["ai_summary"])
|
95 |
|
96 |
-
#
|
97 |
with tabs[1]:
|
98 |
-
st.header("
|
99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
try:
|
101 |
-
nodes, edges,
|
102 |
-
if search_term
|
103 |
-
|
104 |
-
for
|
105 |
-
if
|
106 |
-
|
107 |
-
|
108 |
-
else:
|
109 |
-
node.color = "#ddd"
|
110 |
-
agraph(nodes=nodes, edges=edges, config=config)
|
111 |
except Exception as e:
|
112 |
-
st.
|
113 |
|
114 |
-
#
|
115 |
-
with tabs[
|
116 |
-
|
117 |
-
if
|
118 |
-
fig = px.histogram(pub_years, nbins=10, title="Publication Year Distribution")
|
119 |
-
st.plotly_chart(fig)
|
120 |
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
ai_ans = asyncio.run(answer_ai_question(follow_up, context=query))
|
128 |
-
st.write(ai_ans.get("answer", ai_ans))
|
129 |
|
130 |
-
|
131 |
-
|
132 |
|
|
|
133 |
if __name__ == "__main__":
|
134 |
render_ui()
|
|
|
1 |
# app.py
|
2 |
|
3 |
+
import asyncio, os, re
|
|
|
|
|
4 |
from pathlib import Path
|
5 |
+
|
6 |
+
import streamlit as st
|
7 |
import pandas as pd
|
|
|
8 |
import plotly.express as px
|
9 |
+
from fpdf import FPDF
|
10 |
+
from streamlit_agraph import agraph
|
11 |
|
12 |
from mcp.orchestrator import orchestrate_search, answer_ai_question
|
13 |
+
from mcp.workspace import get_workspace, save_query
|
|
|
14 |
from mcp.knowledge_graph import build_agraph
|
|
|
15 |
|
16 |
ROOT = Path(__file__).parent
|
17 |
LOGO = ROOT / "assets" / "logo.png"
|
18 |
|
19 |
+
# ------------------------- helpers -------------------------
|
20 |
def generate_pdf(papers):
|
21 |
+
pdf = FPDF(); pdf.add_page(); pdf.set_font("Arial", size=12)
|
|
|
|
|
22 |
pdf.cell(200, 10, "MedGenesis AI - Search Results", ln=True, align="C")
|
23 |
pdf.ln(10)
|
24 |
for i, p in enumerate(papers, 1):
|
25 |
pdf.set_font("Arial", "B", 12)
|
26 |
pdf.multi_cell(0, 10, f"{i}. {p['title']}")
|
27 |
pdf.set_font("Arial", "", 10)
|
28 |
+
pdf.multi_cell(0, 8,
|
29 |
+
f"Authors: {p['authors']}\nLink: {p['link']}\nSummary: {p['summary']}\n")
|
30 |
pdf.ln(2)
|
31 |
return pdf.output(dest="S").encode("latin-1")
|
32 |
|
33 |
+
# ------------------------- UI -------------------------
|
34 |
def render_ui():
|
35 |
st.set_page_config(page_title="MedGenesis AI", layout="wide")
|
36 |
|
37 |
+
# ----- Sidebar ----------
|
38 |
with st.sidebar:
|
39 |
st.header("ποΈ Workspace")
|
40 |
+
for i, it in enumerate(get_workspace(), 1):
|
41 |
+
with st.expander(f"{i}. {it['query']}"):
|
42 |
+
st.write("**AI Summary:**", it["result"]["ai_summary"])
|
43 |
+
df = pd.DataFrame(it["result"]["papers"])
|
44 |
+
st.download_button("CSV", df.to_csv(index=False), f"ws_{i}.csv", "text/csv")
|
45 |
+
if not get_workspace():
|
46 |
+
st.info("Run and save searches to populate workspace.")
|
47 |
+
|
48 |
+
# ----- Header ----------
|
|
|
|
|
|
|
49 |
col1, col2 = st.columns([0.15, 0.85])
|
50 |
with col1:
|
51 |
+
if LOGO.exists(): st.image(str(LOGO), width=100)
|
|
|
52 |
with col2:
|
53 |
+
st.markdown("## 𧬠MedGenesis AI")
|
54 |
+
st.write("*Unified PubMed, ArXiv, OpenFDA, UMLS, NCBI, DisGeNET, ClinicalTrials & GPT-4o*")
|
55 |
|
56 |
st.markdown("---")
|
57 |
+
query = st.text_input("π Biomedical research question:", placeholder="e.g. CRISPR glioblastoma")
|
58 |
|
59 |
+
# ------------- Search -------------
|
60 |
+
if st.button("Run Search π") and query:
|
61 |
+
with st.spinner("Collecting literature & biomedical dataβ¦"):
|
62 |
results = asyncio.run(orchestrate_search(query))
|
63 |
+
st.success("Completed!")
|
64 |
|
65 |
+
# -------- Tabs ---------
|
66 |
+
tabs = st.tabs([
|
67 |
+
"π Results", "𧬠Genes & Variants", "π Clinical Trials",
|
68 |
+
"πΊοΈ Graph", "π Visuals"
|
69 |
+
])
|
70 |
|
71 |
+
# -- Results ----------
|
72 |
with tabs[0]:
|
73 |
+
st.header("π Top Papers")
|
74 |
for i, p in enumerate(results["papers"], 1):
|
75 |
st.markdown(f"**{i}. [{p['title']}]({p['link']})** \n*{p['authors']}* ({p['source']})")
|
76 |
+
st.markdown(f"<span style='color:gray'>{p['summary']}</span>", unsafe_allow_html=True)
|
77 |
+
|
78 |
if st.button("Save to Workspace"):
|
79 |
+
save_query(query, results); st.success("Saved!")
|
80 |
+
|
81 |
df = pd.DataFrame(results["papers"])
|
82 |
+
st.download_button("CSV", df.to_csv(index=False), "results.csv", "text/csv")
|
83 |
+
st.download_button("PDF", generate_pdf(results["papers"]), "results.pdf", "application/pdf")
|
84 |
+
|
85 |
st.subheader("π§ UMLS Concepts")
|
86 |
for c in results["umls"]:
|
87 |
if c.get("cui"):
|
88 |
+
st.markdown(f"- **{c['name']}** (`{c['cui']}`)")
|
89 |
+
|
90 |
st.subheader("π Drug Safety (OpenFDA)")
|
91 |
for d in results["drug_safety"]:
|
92 |
st.json(d)
|
93 |
+
|
94 |
+
st.subheader("π€ AI Summary")
|
95 |
st.info(results["ai_summary"])
|
96 |
|
97 |
+
# -- Genes & Variants ----------
|
98 |
with tabs[1]:
|
99 |
+
st.header("𧬠Gene Associations (NCBI / DisGeNET)")
|
100 |
+
for g in results["genes"]:
|
101 |
+
st.write(f"- **{g.get('name', g.get('geneid'))}** β {g.get('description','')}")
|
102 |
+
if results["gene_disease"]:
|
103 |
+
st.markdown("#### DisGeNET Disease β Gene links")
|
104 |
+
st.json(results["gene_disease"][:15])
|
105 |
+
if results["mesh_definitions"]:
|
106 |
+
st.markdown("#### MeSH Definitions")
|
107 |
+
for d in results["mesh_definitions"]:
|
108 |
+
if d: st.write(f"- {d}")
|
109 |
+
|
110 |
+
# -- Clinical Trials ----------
|
111 |
+
with tabs[2]:
|
112 |
+
st.header("π Registered Clinical Trials")
|
113 |
+
for t in results["clinical_trials"]:
|
114 |
+
st.markdown(f"**{t['NCTId'][0]}** β {t['BriefTitle'][0]}")
|
115 |
+
st.write(f"Condition: {', '.join(t['Condition'])} | Phase: {t.get('Phase',[None])[0]} | Status: {t['OverallStatus'][0]}")
|
116 |
+
|
117 |
+
# -- Graph ----------
|
118 |
+
with tabs[3]:
|
119 |
+
search_term = st.text_input("Highlight node containing:", key="graphsearch")
|
120 |
try:
|
121 |
+
nodes, edges, cfg = build_agraph(results["papers"], results["umls"], results["drug_safety"])
|
122 |
+
if search_term:
|
123 |
+
pat = re.compile(re.escape(search_term), re.I)
|
124 |
+
for n in nodes:
|
125 |
+
if pat.search(n.label): n.color, n.size = "#f1c40f", max(n.size, 30)
|
126 |
+
else: n.color = "#ddd"
|
127 |
+
agraph(nodes=nodes, edges=edges, config=cfg)
|
|
|
|
|
|
|
128 |
except Exception as e:
|
129 |
+
st.error(f"Graph error: {e}")
|
130 |
|
131 |
+
# -- Visualizations ----------
|
132 |
+
with tabs[4]:
|
133 |
+
yrs = [p["published"] for p in results["papers"] if p.get("published")]
|
134 |
+
if yrs: st.plotly_chart(px.histogram(yrs, nbins=10, title="Publication Year"))
|
|
|
|
|
135 |
|
136 |
+
# -- Follow-up Q&A ----------
|
137 |
+
st.markdown("---")
|
138 |
+
q = st.text_input("Ask follow-up:", key="follow")
|
139 |
+
if st.button("Ask AI"):
|
140 |
+
ans = asyncio.run(answer_ai_question(q, context=query))
|
141 |
+
st.write(ans["answer"])
|
|
|
|
|
142 |
|
143 |
+
else:
|
144 |
+
st.info("Enter a question and press **Run Search π**")
|
145 |
|
146 |
+
# ------------- Run -------------
|
147 |
if __name__ == "__main__":
|
148 |
render_ui()
|