""" app.py -- Streamlit UI for the AcademiQ PDF -> Summary -> Knowledge Graph project ========================================================================== Owner: Aparna (UI layout, file upload, results display) This file ONLY handles the interface. All the pipeline / model logic lives in models.py, which currently returns demo placeholder data. When the real models are ready, nothing in this file needs to change. Run locally (Windows): py -m pip install -r requirements.txt py -m streamlit run app.py """ import html import streamlit as st import pandas as pd import models # our pipeline (placeholders for now) # --------------------------------------------------------------------------- # Page config -- must be the first Streamlit call # --------------------------------------------------------------------------- st.set_page_config( page_title=" AcademiQ ", page_icon="πŸ“š", layout="wide", ) # --------------------------------------------------------------------------- # Session state: keep results so the tabs survive Streamlit's reruns # --------------------------------------------------------------------------- if "results" not in st.session_state: st.session_state.results = None # --------------------------------------------------------------------------- # Helper: turn text + entities into highlighted HTML # (Iva) # --------------------------------------------------------------------------- def render_highlighted(text: str, entities: list) -> str: pieces = [] cursor = 0 for ent in entities: # plain text before this entity pieces.append(html.escape(text[cursor:ent["start"]])) color = models.LABEL_COLORS.get(ent["label"], models.LABEL_COLORS["ENTITY"]) chunk = html.escape(text[ent["start"]:ent["end"]]) pieces.append( f'{chunk}' f'' f'{ent["label"]}' ) cursor = ent["end"] pieces.append(html.escape(text[cursor:])) body = "".join(pieces) return ( f'
{body}
' ) def label_legend() -> str: chips = [] for label, color in models.LABEL_COLORS.items(): if label == "ENTITY": continue chips.append( f'{label}' ) return '
' + "".join(chips) + "
" # --------------------------------------------------------------------------- # Sidebar: pipeline overview, settings, team credits # --------------------------------------------------------------------------- with st.sidebar: st.header("βš™οΈ Settings") demo_mode = st.toggle( "Demo mode (placeholder models)", value=True, help="ON: instant fake results, no model downloads. " "OFF: requires the real models wired into models.py.", ) st.caption("Summary length (passed to the summariser)") max_len = st.slider("Max length", 60, 250, 130, step=10) min_len = st.slider("Min length", 10, 100, 30, step=5) st.divider() st.subheader("πŸ”— Pipeline") st.markdown( "1. **PDF β†’ text** Β· PyMuPDF\n" "2. **Summary** Β· BART\n" "3. **Entities** Β· DistilBERT NER\n" "4. **Relations** Β· DistilBERT RE\n" "5. **Graph** Β· NetworkX" ) st.divider() st.subheader("πŸ‘₯ Introducing our Team") st.markdown( "- **Jordan** β€” PDF ingestion and extraction using PyMuPDF\n" "- **Varsha** β€” Summarization BART-large\n" "- **Damir** - DistilBERT NER model\n" "- **Andy** - Handling Post-processing\n" "- **Aparna** - UI, upload, results\n" "- **Iva** - styling, NER highlighting, summary view\n" "-**Temirlan** - Integration and Testing" ) # --------------------------------------------------------------------------- # Header # --------------------------------------------------------------------------- st.title("πŸ“š AcademiQ Research Paper β†’ Knowledge Graph") st.markdown( "Upload an academic PDF. The app summarises it, pulls out key entities and " "their relationships, and builds a knowledge graph." ) if demo_mode: st.info( "πŸ§ͺ **Demo mode is ON** β€” results are placeholders so the interface " "runs instantly. Turn it off once the real models are connected in " "`models.py`.", icon="πŸ§ͺ", ) # --------------------------------------------------------------------------- # Input section # --------------------------------------------------------------------------- st.subheader("1 Β· Upload") col_up, col_sample = st.columns([3, 1]) with col_up: uploaded = st.file_uploader("Choose a PDF", type=["pdf"], label_visibility="collapsed") with col_sample: use_sample = st.checkbox("Use sample paper", value=not bool(uploaded)) ready = bool(uploaded) or use_sample analyze = st.button("πŸš€ Analyze paper", type="primary", disabled=not ready, use_container_width=True) # --------------------------------------------------------------------------- # Run the pipeline # --------------------------------------------------------------------------- if analyze: # Step 0: get the raw text if uploaded is not None: raw_text = models.extract_text_from_pdf(uploaded.getvalue()) else: raw_text = models.SAMPLE_TEXT with st.status("Running the pipeline...", expanded=True) as status: st.write("πŸ“ Summarising...") summary_out = models.summarize(raw_text, max_len=max_len, min_len=min_len) summary = summary_out["summary"] st.write("🏷️ Extracting entities (NER)...") entities = models.ner(summary) st.write("πŸ”— Extracting relations...") relations = models.extract_relations(summary, entities) st.write("πŸ•ΈοΈ Building knowledge graph...") graph_fig = models.build_graph(relations, entities) status.update(label="Done!", state="complete", expanded=False) st.session_state.results = { "raw_text": raw_text, "summary": summary, "metrics": summary_out["metrics"], "entities": entities, "relations": relations, "graph_fig": graph_fig, } # --------------------------------------------------------------------------- # Results # --------------------------------------------------------------------------- res = st.session_state.results if res: st.subheader("2 Β· Results") # quick metrics row m1, m2, m3, m4 = st.columns(4) m1.metric("Entities", len(res["entities"])) m2.metric("Relations", len(res["relations"])) m3.metric("Graph nodes", len({r["head"] for r in res["relations"]} | {r["tail"] for r in res["relations"]})) m4.metric("Compression", res["metrics"]["Compression"]) tab_text, tab_sum, tab_ent, tab_rel, tab_graph = st.tabs( ["πŸ“„ Extracted Text", "πŸ“ Summary", "🏷️ Entities", "πŸ”— Relations", "πŸ•ΈοΈ Knowledge Graph"] ) with tab_text: st.caption(f"{len(res['raw_text'].split())} words extracted from the PDF") st.text_area("Raw text", res["raw_text"], height=320, label_visibility="collapsed") with tab_sum: st.markdown(f"> {res['summary']}") st.divider() cols = st.columns(len(res["metrics"])) for col, (k, v) in zip(cols, res["metrics"].items()): col.metric(k, v) st.caption("ROUGE scores are placeholders until evaluated against reference abstracts.") with tab_ent: st.markdown(label_legend(), unsafe_allow_html=True) st.markdown(render_highlighted(res["summary"], res["entities"]), unsafe_allow_html=True) st.divider() df_ent = pd.DataFrame(res["entities"])[["text", "label", "score"]] df_ent.columns = ["Entity", "Type", "Confidence"] st.dataframe(df_ent, use_container_width=True, hide_index=True) with tab_rel: df_rel = pd.DataFrame(res["relations"]) df_rel.columns = ["Head", "Relation", "Tail", "Confidence"] st.dataframe(df_rel, use_container_width=True, hide_index=True) st.caption("Each row is a (head β†’ relation β†’ tail) triple feeding the graph.") with tab_graph: st.pyplot(res["graph_fig"], use_container_width=True) st.caption("Nodes coloured by entity type; arrows show the extracted relations.") else: st.caption("Upload a PDF (or tick *Use sample paper*) and press **Analyze**.")