MalikShehram commited on
Commit
79b585a
Β·
verified Β·
1 Parent(s): 32b5f22

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -20
app.py CHANGED
@@ -38,19 +38,105 @@ index = None
38
  # PDF FUNCTIONS
39
  # =========================
40
 
41
- def download_arxiv_pdf(arxiv_id):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  try:
43
- arxiv_id = arxiv_id.strip()
44
- url = f"https://arxiv.org/pdf/{arxiv_id}.pdf"
45
- response = requests.get(url, timeout=30)
46
- response.raise_for_status()
47
- file_path = f"/tmp/{arxiv_id}.pdf"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  with open(file_path, "wb") as f:
49
- f.write(response.content)
50
- return file_path
 
 
51
  except Exception as e:
52
  print(f"PDF download error: {e}")
53
- return None
54
 
55
  def extract_text_from_pdf(pdf_path):
56
  doc = fitz.open(pdf_path)
@@ -98,19 +184,24 @@ def build_vector_store(sections_dict):
98
  index = faiss.IndexFlatL2(dim)
99
  index.add(embeddings)
100
 
101
- def load_paper(arxiv_id):
102
  global sections
103
- if not arxiv_id or not arxiv_id.strip():
104
- return gr.update(choices=[]), "⚠️ Please enter a valid arXiv ID"
105
- pdf_path = download_arxiv_pdf(arxiv_id)
106
  if pdf_path is None:
107
- return gr.update(choices=[]), "❌ Could not download paper. Check the arXiv ID and try again."
 
 
 
 
108
  text = extract_text_from_pdf(pdf_path)
109
  sections = extract_sections(text)
110
  if not sections:
111
- return gr.update(choices=[]), "⚠️ Paper loaded but no sections could be detected."
112
  build_vector_store(sections)
113
- return gr.update(choices=list(sections.keys())), f"βœ… Paper loaded β€” {len(sections)} sections indexed and ready"
 
114
 
115
  def summarize_section(section_title):
116
  try:
@@ -552,7 +643,7 @@ with gr.Blocks(title="ArXiv Research Assistant") as demo:
552
  <span class="badge">🧠 LLaMA 3.3 70B</span>
553
  <span class="badge">πŸŽ™οΈ Whisper ASR</span>
554
  <span class="badge">πŸ” FAISS Semantic Search</span>
555
- <span class="badge">πŸ“„ PyMuPDF</span>
556
  </div>
557
  </div>
558
  """)
@@ -562,14 +653,14 @@ with gr.Blocks(title="ArXiv Research Assistant") as demo:
562
  gr.HTML('<div class="sec-label">01 &nbsp; Load Paper</div>')
563
  with gr.Row(equal_height=True):
564
  arxiv_input = gr.Textbox(
565
- label="arXiv Paper ID",
566
- placeholder="e.g. 2305.10601 Β· 2312.00752 Β· 1706.03762",
567
  scale=5,
568
  )
569
  load_btn = gr.Button("πŸš€ Load Paper", variant="primary", scale=1, min_width=150)
570
 
571
  status = gr.Markdown(
572
- value="*Enter an arXiv ID above and click **Load Paper** to begin.*",
573
  elem_id="status-box",
574
  )
575
 
 
38
  # PDF FUNCTIONS
39
  # =========================
40
 
41
+ def doi_to_pdf_url(doi):
42
+ """
43
+ Given a DOI, try multiple strategies to find a downloadable PDF:
44
+ 1. Unpaywall API β€” finds legal open-access PDFs for any DOI (no key needed)
45
+ 2. Direct arXiv β€” if the DOI belongs to an arXiv paper
46
+ 3. Europe PMC β€” broad biomedical / life-science coverage
47
+ Returns (pdf_url, paper_title).
48
+ """
49
+ doi = doi.strip()
50
+ # Strip common DOI prefixes so bare DOI always works
51
+ for prefix in ("https://doi.org/", "http://doi.org/", "doi:", "DOI:"):
52
+ if doi.startswith(prefix):
53
+ doi = doi[len(prefix):]
54
+ break
55
+
56
+ title = None
57
+
58
+ # ── Strategy 1: Unpaywall (free, ~85% OA coverage) ──────────────
59
  try:
60
+ email = "research@assistant.app"
61
+ r = requests.get(
62
+ f"https://api.unpaywall.org/v2/{doi}?email={email}", timeout=15
63
+ )
64
+ if r.status_code == 200:
65
+ data = r.json()
66
+ title = data.get("title")
67
+ best = data.get("best_oa_location")
68
+ if best:
69
+ pdf_url = best.get("url_for_pdf") or best.get("url")
70
+ if pdf_url:
71
+ return pdf_url, title
72
+ for loc in data.get("oa_locations", []):
73
+ pdf_url = loc.get("url_for_pdf") or loc.get("url")
74
+ if pdf_url:
75
+ return pdf_url, title
76
+ except Exception as e:
77
+ print(f"Unpaywall error: {e}")
78
+
79
+ # ── Strategy 2: arXiv DOI pattern ───────────────────────────────
80
+ try:
81
+ arxiv_match = re.search(r"arXiv[\.:](\d{4}\.\d{4,5})", doi, re.IGNORECASE)
82
+ if arxiv_match:
83
+ arxiv_id = arxiv_match.group(1)
84
+ return f"https://arxiv.org/pdf/{arxiv_id}.pdf", title
85
+ except Exception as e:
86
+ print(f"arXiv DOI parse error: {e}")
87
+
88
+ # ── Strategy 3: Europe PMC ───────────────────────────────────────
89
+ try:
90
+ r = requests.get(
91
+ f"https://www.ebi.ac.uk/europepmc/webservices/rest/search"
92
+ f"?query=DOI:{doi}&format=json&resultType=core",
93
+ timeout=15,
94
+ )
95
+ if r.status_code == 200:
96
+ results = r.json().get("resultList", {}).get("result", [])
97
+ if results:
98
+ item = results[0]
99
+ title = title or item.get("title")
100
+ pmcid = item.get("pmcid")
101
+ if pmcid:
102
+ pdf_url = (
103
+ f"https://europepmc.org/backend/ptpmcrender.fcgi"
104
+ f"?accid={pmcid}&blobtype=pdf"
105
+ )
106
+ return pdf_url, title
107
+ except Exception as e:
108
+ print(f"Europe PMC error: {e}")
109
+
110
+ return None, title
111
+
112
+
113
+ def download_pdf_from_doi(doi):
114
+ """Resolve DOI β†’ PDF URL β†’ download to /tmp. Returns (file_path, paper_title)."""
115
+ try:
116
+ pdf_url, title = doi_to_pdf_url(doi)
117
+ if not pdf_url:
118
+ return None, title
119
+
120
+ safe_name = re.sub(r"[^\w\-]", "_", doi)[:60]
121
+ file_path = f"/tmp/{safe_name}.pdf"
122
+
123
+ headers = {"User-Agent": "Mozilla/5.0 (ResearchAssistant/1.0)"}
124
+ r = requests.get(pdf_url, timeout=40, headers=headers, allow_redirects=True)
125
+ r.raise_for_status()
126
+
127
+ # Verify it's actually a PDF
128
+ if b"%PDF" not in r.content[:16]:
129
+ print(f"Response is not a PDF from {pdf_url}")
130
+ return None, title
131
+
132
  with open(file_path, "wb") as f:
133
+ f.write(r.content)
134
+
135
+ return file_path, title
136
+
137
  except Exception as e:
138
  print(f"PDF download error: {e}")
139
+ return None, None
140
 
141
  def extract_text_from_pdf(pdf_path):
142
  doc = fitz.open(pdf_path)
 
184
  index = faiss.IndexFlatL2(dim)
185
  index.add(embeddings)
186
 
187
+ def load_paper(doi):
188
  global sections
189
+ if not doi or not doi.strip():
190
+ return gr.update(choices=[]), "⚠️ Please enter a valid DOI"
191
+ pdf_path, title = download_pdf_from_doi(doi)
192
  if pdf_path is None:
193
+ return gr.update(choices=[]), (
194
+ "❌ Could not find or download a PDF for this DOI.\n\n"
195
+ "**Tips:** Make sure the paper is open-access. "
196
+ "Try formats like `10.1038/s41586-021-03819-2` or paste the full `https://doi.org/...` URL."
197
+ )
198
  text = extract_text_from_pdf(pdf_path)
199
  sections = extract_sections(text)
200
  if not sections:
201
+ return gr.update(choices=[]), "⚠️ PDF downloaded but no sections could be detected."
202
  build_vector_store(sections)
203
+ label = f"βœ… **{title}**\n\n{len(sections)} sections indexed and ready" if title else f"βœ… Paper loaded β€” {len(sections)} sections indexed and ready"
204
+ return gr.update(choices=list(sections.keys())), label
205
 
206
  def summarize_section(section_title):
207
  try:
 
643
  <span class="badge">🧠 LLaMA 3.3 70B</span>
644
  <span class="badge">πŸŽ™οΈ Whisper ASR</span>
645
  <span class="badge">πŸ” FAISS Semantic Search</span>
646
+ <span class="badge">πŸ”— DOI Resolver</span>
647
  </div>
648
  </div>
649
  """)
 
653
  gr.HTML('<div class="sec-label">01 &nbsp; Load Paper</div>')
654
  with gr.Row(equal_height=True):
655
  arxiv_input = gr.Textbox(
656
+ label="Paper DOI",
657
+ placeholder="e.g. 10.1038/s41586-021-03819-2 or https://doi.org/10.48550/arXiv.1706.03762",
658
  scale=5,
659
  )
660
  load_btn = gr.Button("πŸš€ Load Paper", variant="primary", scale=1, min_width=150)
661
 
662
  status = gr.Markdown(
663
+ value="*Enter a paper DOI above and click **Load Paper** to begin.*",
664
  elem_id="status-box",
665
  )
666