File size: 5,255 Bytes
03037be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import streamlit as st
import requests

import os
API_BASE = os.getenv("API_URL", "http://127.0.0.1:8000")

st.set_page_config(page_title="PaperLens", page_icon="πŸ”", layout="wide")

# ── INIT SESSION STATE β€” MUST BE FIRST ───────────
if "summaries" not in st.session_state:
    st.session_state.summaries = {}
if "search_results" not in st.session_state:
    st.session_state.search_results = None
if "last_query" not in st.session_state:
    st.session_state.last_query = ""

# ── HELPERS ──────────────────────────────────────
def search_papers(query: str, top_k: int = 10):
    try:
        resp = requests.get(f"{API_BASE}/search", params={"query": query, "top_k": top_k}, timeout=30)
        return resp.json() if resp.status_code == 200 else None
    except Exception as e:
        st.error(f"Search failed: {e}")
        return None

def get_summary(paper_id: str):
    try:
        resp = requests.get(f"{API_BASE}/summarize/{paper_id}", timeout=30)
        return resp.json() if resp.status_code == 200 else None
    except Exception as e:
        st.error(f"Summary failed: {e}")
        return None

def check_api():
    try:
        resp = requests.get(f"{API_BASE}/health", timeout=5)
        if resp.status_code == 200:
            return True, resp.json()
        return False, {}
    except Exception:
        return False, {}

# ── HEADER ───────────────────────────────────────
st.markdown("# πŸ” PaperLens")
st.markdown("**Semantic search engine for research papers β€” find papers by meaning, not keywords**")
st.divider()

# ── API STATUS ───────────────────────────────────
is_up, health = check_api()
if is_up:
    st.markdown(
        f" **Online**  |  "
        f"**{health.get('paper_indexed', 0)} papers**  |  "
        f"**all-MiniLM-L6-v2**  |  "
        f"**FAISS IVFFlat**  |  "
        f"**Groq LLaMA-3.3-70b**",
        unsafe_allow_html=True
    )
else:
    st.error("❌ API offline β€” run: uvicorn src.api.main:app --reload")
    st.stop()

st.divider()

# ── SEARCH BAR ───────────────────────────────────
col1, col2 = st.columns([4, 1])
with col1:
    query = st.text_input("Search", placeholder="e.g. transformer attention mechanism...", label_visibility="collapsed")
with col2:
    top_k = st.selectbox("Results", [5, 10, 20], index=1, label_visibility="collapsed")

search_clicked = st.button("πŸ” Search", type="primary", use_container_width=True)

# ── SAVE RESULTS ON SEARCH ───────────────────────
if search_clicked and query.strip():
    with st.spinner("Searching..."):
        data = search_papers(query, top_k)
    st.session_state.search_results = data
    st.session_state.last_query = query

# ── DISPLAY RESULTS FROM SESSION STATE ───────────
data  = st.session_state.search_results
query = st.session_state.last_query

if data and data.get("results"):
    st.markdown(f"**{data['total']} results** for *\"{query}\"* β€” `{data['latency_ms']}ms`")
    st.divider()

    for i, paper in enumerate(data["results"]):
        col1, col2 = st.columns([5, 1])
        with col1:
            st.markdown(f"### {i+1}. {paper['title']}")
        with col2:
            st.markdown(f"**Score:** `{paper['score']:.3f}`")

        st.markdown(
            f"{paper['authors'][:80]}{'...' if len(paper['authors']) > 80 else ''}  "
            f"|  {paper.get('year', 'N/A')}  |  `{paper.get('field', '')}`"
        )

        with st.expander("Abstract"):
            st.write(paper["abstract"])

        if st.button("πŸ€– AI Summary", key=f"sum_{paper['id']}_{i}"):
            with st.spinner("Groq LLaMA-3 reading the paper..."):
                summary = get_summary(paper["id"])
            if summary:
                st.session_state.summaries[paper["id"]] = summary

        if paper["id"] in st.session_state.summaries:
            s = st.session_state.summaries[paper["id"]]
            st.success(f"Generated in {s['latency_ms']}ms  |  Tokens: {s['tokens_used']}")
            st.markdown(s["summary"])

        st.markdown(f"[View on arXiv]({paper['url']})")
        st.divider()

# ── SIDEBAR ──────────────────────────────────────
with st.sidebar:
    st.markdown("## About PaperLens")
    st.markdown("""
    **Stack:**
    - sentence-transformers
    - FAISS IVFFlat
    - FastAPI + LRU cache
    - Groq LLaMA-3.3-70b
    - Streamlit
    
    **Benchmarks:**
    - 956 papers indexed
    - p50 latency: 0.02ms
    - QPS: 76,540
    - Cache: 1738x speedup
    """)
    st.divider()
    st.markdown("**Try these:**")
    st.code("transformer attention")
    st.code("reinforcement learning")
    st.code("computer vision CNN")
    st.code("large language models")
    st.divider()
    st.markdown("Built by [Venkatesh P](https://linkedin.com/in/venkatesh-ml)")