Update app.py
Browse files
app.py
CHANGED
|
@@ -2,8 +2,6 @@
|
|
| 2 |
|
| 3 |
import os
|
| 4 |
import streamlit as st
|
| 5 |
-
from fastapi import FastAPI
|
| 6 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
import asyncio
|
| 8 |
from pathlib import Path
|
| 9 |
import pandas as pd
|
|
@@ -20,27 +18,15 @@ from streamlit_agraph import agraph, Node, Edge, Config
|
|
| 20 |
ROOT = Path(__file__).parent
|
| 21 |
LOGO = ROOT / "assets" / "logo.png"
|
| 22 |
|
| 23 |
-
api = FastAPI(
|
| 24 |
-
title="MedGenesis MCP Server",
|
| 25 |
-
version="2.0.0",
|
| 26 |
-
description="MedGenesis AI β Unified Intelligence from PubMed, ArXiv, OpenFDA, UMLS, GPT-4o"
|
| 27 |
-
)
|
| 28 |
-
api.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
|
| 29 |
-
|
| 30 |
-
@api.post("/unified_search", response_model=UnifiedSearchResult)
|
| 31 |
-
async def unified_search_endpoint(data: UnifiedSearchInput):
|
| 32 |
-
return await orchestrate_search(data.query)
|
| 33 |
-
|
| 34 |
-
@api.post("/ask_ai")
|
| 35 |
-
async def ask_ai_endpoint(question: str, context: str = ""):
|
| 36 |
-
return await answer_ai_question(question, context)
|
| 37 |
-
|
| 38 |
def generate_pdf(papers):
|
| 39 |
-
pdf = FPDF()
|
|
|
|
|
|
|
| 40 |
pdf.cell(200, 10, "MedGenesis AI - Search Results", ln=True, align="C")
|
| 41 |
pdf.ln(10)
|
| 42 |
for i, p in enumerate(papers, 1):
|
| 43 |
-
pdf.set_font("Arial", "B", 12)
|
|
|
|
| 44 |
pdf.set_font("Arial", "", 10)
|
| 45 |
pdf.multi_cell(0, 8, f"Authors: {p['authors']}\nLink: {p['link']}\nSummary: {p['summary']}\n")
|
| 46 |
pdf.ln(2)
|
|
@@ -49,21 +35,17 @@ def generate_pdf(papers):
|
|
| 49 |
def render_ui():
|
| 50 |
st.set_page_config(page_title="MedGenesis AI", layout="wide")
|
| 51 |
|
| 52 |
-
# Sidebar workspace
|
| 53 |
with st.sidebar:
|
| 54 |
st.header("ποΈ Workspace")
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
st.info("Run & save searches here.")
|
| 65 |
-
|
| 66 |
-
# Header
|
| 67 |
col1, col2 = st.columns([0.15, 0.85])
|
| 68 |
with col1:
|
| 69 |
if LOGO.exists():
|
|
@@ -73,85 +55,69 @@ def render_ui():
|
|
| 73 |
st.caption("Built by Oluwafemi Idiakhoa β’ Hugging Face Spaces")
|
| 74 |
|
| 75 |
st.markdown("---")
|
| 76 |
-
query = st.text_input("π Enter
|
| 77 |
|
| 78 |
results = None
|
| 79 |
if st.button("Run Search π"):
|
| 80 |
-
with st.spinner("
|
| 81 |
results = asyncio.run(orchestrate_search(query))
|
| 82 |
-
st.success("
|
| 83 |
|
| 84 |
if results:
|
| 85 |
tabs = st.tabs(["π Results", "πΊοΈ Knowledge Graph", "π Visualizations"])
|
| 86 |
|
| 87 |
-
# Tab 1: Results
|
| 88 |
with tabs[0]:
|
| 89 |
-
st.header("π
|
| 90 |
for i, p in enumerate(results["papers"], 1):
|
| 91 |
-
st.markdown(f"**{i}. [{p['title']}]({p['link']})**
|
| 92 |
st.markdown(f"<div style='color:gray'>{p['summary']}</div>", unsafe_allow_html=True)
|
| 93 |
-
|
| 94 |
if st.button("Save to Workspace"):
|
| 95 |
save_query(query, results); st.success("Saved!")
|
| 96 |
-
|
| 97 |
df = pd.DataFrame(results["papers"])
|
| 98 |
-
st.download_button("π₯
|
| 99 |
pdf = generate_pdf(results["papers"])
|
| 100 |
-
st.download_button("π
|
| 101 |
-
|
| 102 |
st.subheader("π§ UMLS Concepts")
|
| 103 |
for c in results["umls"]:
|
| 104 |
if c.get("cui"):
|
| 105 |
-
st.markdown(f"- **{c['name']}** (CUI:
|
| 106 |
-
|
| 107 |
-
st.subheader("π Drug Safety (OpenFDA)")
|
| 108 |
for d in results["drug_safety"]:
|
| 109 |
st.json(d)
|
| 110 |
-
|
| 111 |
st.subheader("π€ AI Summary")
|
| 112 |
st.info(results["ai_summary"])
|
| 113 |
|
| 114 |
-
# Tab 2: Knowledge Graph with Search & Highlight
|
| 115 |
with tabs[1]:
|
| 116 |
-
st.header("
|
| 117 |
-
search_term = st.text_input("
|
| 118 |
try:
|
| 119 |
nodes, edges, config = build_agraph(results["papers"], results["umls"], results["drug_safety"])
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
pattern = re.compile(re.escape(search_term), re.IGNORECASE)
|
| 123 |
for node in nodes:
|
| 124 |
-
if
|
| 125 |
-
node.color = "#f1c40f"
|
| 126 |
-
node.size = max(node.size, 30)
|
| 127 |
else:
|
| 128 |
node.color = "#ddd"
|
| 129 |
agraph(nodes=nodes, edges=edges, config=config)
|
| 130 |
except Exception as e:
|
| 131 |
-
st.warning("
|
| 132 |
|
| 133 |
-
# Tab 3: Visualizations
|
| 134 |
with tabs[2]:
|
| 135 |
-
|
| 136 |
-
if
|
| 137 |
-
|
| 138 |
-
st.plotly_chart(fig)
|
| 139 |
|
| 140 |
-
# Follow-up Q&A
|
| 141 |
st.markdown("---")
|
| 142 |
-
st.subheader("π¬ Ask Follow-up
|
| 143 |
-
|
| 144 |
if st.button("Ask AI"):
|
| 145 |
with st.spinner("Thinking..."):
|
| 146 |
-
|
| 147 |
-
st.write(
|
| 148 |
|
| 149 |
st.markdown("---")
|
| 150 |
-
st.caption("β¨ Built by Oluwafemi Idiakhoa β’ Powered by
|
| 151 |
|
| 152 |
if __name__ == "__main__":
|
| 153 |
-
|
| 154 |
-
if "runserver" in sys.argv:
|
| 155 |
-
import uvicorn; uvicorn.run(api, host="0.0.0.0", port=7860)
|
| 156 |
-
else:
|
| 157 |
-
render_ui()
|
|
|
|
| 2 |
|
| 3 |
import os
|
| 4 |
import streamlit as st
|
|
|
|
|
|
|
| 5 |
import asyncio
|
| 6 |
from pathlib import Path
|
| 7 |
import pandas as pd
|
|
|
|
| 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, f"Authors: {p['authors']}\nLink: {p['link']}\nSummary: {p['summary']}\n")
|
| 32 |
pdf.ln(2)
|
|
|
|
| 35 |
def render_ui():
|
| 36 |
st.set_page_config(page_title="MedGenesis AI", layout="wide")
|
| 37 |
|
|
|
|
| 38 |
with st.sidebar:
|
| 39 |
st.header("ποΈ Workspace")
|
| 40 |
+
for i, item in enumerate(get_workspace(), 1):
|
| 41 |
+
with st.expander(f"{i}. {item['query']}"):
|
| 42 |
+
st.write("**AI Summary:**", item["result"]["ai_summary"])
|
| 43 |
+
st.write("**Top Paper:**", item["result"]["papers"][0]["title"] if item["result"]["papers"] else "None")
|
| 44 |
+
df = pd.DataFrame(item["result"]["papers"])
|
| 45 |
+
st.download_button("π₯ CSV", df.to_csv(index=False), f"workspace_{i}.csv", "text/csv")
|
| 46 |
+
if not get_workspace():
|
| 47 |
+
st.info("Run and save searches here.")
|
| 48 |
+
|
|
|
|
|
|
|
|
|
|
| 49 |
col1, col2 = st.columns([0.15, 0.85])
|
| 50 |
with col1:
|
| 51 |
if LOGO.exists():
|
|
|
|
| 55 |
st.caption("Built by Oluwafemi Idiakhoa β’ Hugging Face Spaces")
|
| 56 |
|
| 57 |
st.markdown("---")
|
| 58 |
+
query = st.text_input("π Enter research question:", placeholder="e.g. CRISPR treatments for glioblastoma")
|
| 59 |
|
| 60 |
results = None
|
| 61 |
if st.button("Run Search π"):
|
| 62 |
+
with st.spinner("Analyzing..."):
|
| 63 |
results = asyncio.run(orchestrate_search(query))
|
| 64 |
+
st.success("Search complete")
|
| 65 |
|
| 66 |
if results:
|
| 67 |
tabs = st.tabs(["π Results", "πΊοΈ Knowledge Graph", "π Visualizations"])
|
| 68 |
|
|
|
|
| 69 |
with tabs[0]:
|
| 70 |
+
st.header("π Papers")
|
| 71 |
for i, p in enumerate(results["papers"], 1):
|
| 72 |
+
st.markdown(f"**{i}. [{p['title']}]({p['link']})** *{p['authors']}*")
|
| 73 |
st.markdown(f"<div style='color:gray'>{p['summary']}</div>", unsafe_allow_html=True)
|
|
|
|
| 74 |
if st.button("Save to Workspace"):
|
| 75 |
save_query(query, results); st.success("Saved!")
|
|
|
|
| 76 |
df = pd.DataFrame(results["papers"])
|
| 77 |
+
st.download_button("π₯ CSV", df.to_csv(index=False), "results.csv", "text/csv")
|
| 78 |
pdf = generate_pdf(results["papers"])
|
| 79 |
+
st.download_button("π PDF", pdf, "results.pdf", "application/pdf")
|
|
|
|
| 80 |
st.subheader("π§ UMLS Concepts")
|
| 81 |
for c in results["umls"]:
|
| 82 |
if c.get("cui"):
|
| 83 |
+
st.markdown(f"- **{c['name']}** (CUI:{c['cui']}): {c.get('definition','')}")
|
| 84 |
+
st.subheader("π Drug Safety")
|
|
|
|
| 85 |
for d in results["drug_safety"]:
|
| 86 |
st.json(d)
|
|
|
|
| 87 |
st.subheader("π€ AI Summary")
|
| 88 |
st.info(results["ai_summary"])
|
| 89 |
|
|
|
|
| 90 |
with tabs[1]:
|
| 91 |
+
st.header("π Knowledge Graph")
|
| 92 |
+
search_term = st.text_input("Highlight node containing text:", value="")
|
| 93 |
try:
|
| 94 |
nodes, edges, config = build_agraph(results["papers"], results["umls"], results["drug_safety"])
|
| 95 |
+
if search_term:
|
| 96 |
+
pat = re.compile(re.escape(search_term), re.IGNORECASE)
|
|
|
|
| 97 |
for node in nodes:
|
| 98 |
+
if pat.search(node.label) or (hasattr(node, "tooltip") and pat.search(getattr(node, "tooltip", ""))):
|
| 99 |
+
node.color = "#f1c40f"; node.size = max(node.size, 30)
|
|
|
|
| 100 |
else:
|
| 101 |
node.color = "#ddd"
|
| 102 |
agraph(nodes=nodes, edges=edges, config=config)
|
| 103 |
except Exception as e:
|
| 104 |
+
st.warning("Graph unavailable: " + str(e))
|
| 105 |
|
|
|
|
| 106 |
with tabs[2]:
|
| 107 |
+
years = [p["published"] for p in results["papers"] if p.get("published")]
|
| 108 |
+
if years:
|
| 109 |
+
st.plotly_chart(px.histogram(years, title="Pub Year"))
|
|
|
|
| 110 |
|
|
|
|
| 111 |
st.markdown("---")
|
| 112 |
+
st.subheader("π¬ Ask AI a Follow-up")
|
| 113 |
+
fq = st.text_input("Ask based on above:", "")
|
| 114 |
if st.button("Ask AI"):
|
| 115 |
with st.spinner("Thinking..."):
|
| 116 |
+
ans = asyncio.run(answer_ai_question(fq, context=query))
|
| 117 |
+
st.write(ans.get("answer", ans))
|
| 118 |
|
| 119 |
st.markdown("---")
|
| 120 |
+
st.caption("β¨ Built by Oluwafemi Idiakhoa β’ Powered by Streamlit & AI")
|
| 121 |
|
| 122 |
if __name__ == "__main__":
|
| 123 |
+
render_ui()
|
|
|
|
|
|
|
|
|
|
|
|