Skip to content

Commit 39b108d

Browse files
authored
Merge pull request #10 from Integration-Automation/dev
User-facing doc refresh: default-on Chrome IEEE/Scholar + GUI Settings additions
2 parents ee5dd28 + d87869b commit 39b108d

22 files changed

Lines changed: 363 additions & 73 deletions

README.md

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,12 @@ the template for any multi-paper search. The zh-tw companion is at
155155

156156
- **Eleven pluggable sources**: `arxiv`, `semantic_scholar`, `openalex`,
157157
`pubmed`, `acm` (Crossref-scoped), `dblp`, `crossref` (unscoped),
158-
`openaire`, `springer` (needs API key), `ieee` (API key or opt-in
159-
scrape), `scholar` (opt-in scrape). Each lives in `sources/<name>/`
160-
behind a `Fetcher` adapter. A top-tier-venue whitelist filters results
161-
to flagship CS conferences/journals plus Nature/Science/PNAS by
162-
default; pass `--all-venues` to disable.
158+
`openaire`, `springer` (needs API key), `ieee` (default-on via visible
159+
Chrome; API key adds official Xplore API), `scholar` (default-on via
160+
visible Chrome). Each lives in `sources/<name>/` behind a `Fetcher`
161+
adapter. A top-tier-venue whitelist filters results to flagship CS
162+
conferences/journals plus Nature/Science/PNAS by default; pass
163+
`--all-venues` to disable.
163164
- **Single-paper mode**: paste an arXiv ID, arXiv URL, DOI, PMID, or IEEE
164165
document URL — AutoPaperToPPT resolves it via the right source and
165166
emits the same export bundle. Useful for paper reading notes and thesis
@@ -204,11 +205,27 @@ the template for any multi-paper search. The zh-tw companion is at
204205
and passes it to `export`.
205206
- **Python pipeline (`--enrich`)** — the CLI calls Anthropic's API
206207
itself; default model `claude-opus-4-7`.
208+
- **Visible-Chrome publisher flows**: Scholar SERP, IEEE `/rest/search`,
209+
and every paywalled-PDF download (ieeexplore / dl.acm / link.springer
210+
/ sciencedirect / wiley / oup / nature / science / …) run inside a
211+
real visible Chrome session via `selenium`. The user solves captcha
212+
/ completes SSO in the live window once; `AUTOPAPERTOPPT_CHROME_PROFILE_DIR`
213+
persists the cookies across runs.
214+
- **LLM-as-agent flow** (`scripts/llm_*.py`): when the LLM in your editor
215+
wants to drive the browser itself (rather than let `asyncio.gather` do
216+
it), `scripts/llm_driven_search.py` opens Chrome on Scholar + IEEE,
217+
`scripts/llm_download_pdfs.py` walks an xlsx and downloads every paper
218+
in one Chrome session (IEEE / ACM / Springer / arXiv / ACL Anthology /
219+
NeurIPS / OpenReview), and `scripts/regen_*.py` shows the worked
220+
pattern for hand-authoring a rich `PaperSummary` per paper.
221+
- **OA PDF resolver**: post-dedup, every paper without `pdf_url`
222+
goes through Unpaywall → S2 `openAccessPdf` → arXiv title search →
223+
CORE.ac.uk (when keys are set). Typical lift on IEEE / ACM / Springer
224+
/ Elsevier-heavy queries: 40-70 percentage points.
207225
- **Safety by default**: HTTPS-only HTTP transport, per-source rate
208226
limit (token bucket), `defusedxml` for any XML payload,
209227
path-traversal-safe export paths, no `eval` / `exec` / `pickle` on
210-
user input. Scholar and IEEE scraping are off by default (env-var
211-
opt-in).
228+
user input.
212229

213230
## Quick start
214231

@@ -268,10 +285,12 @@ py -m autopapertoppt --paper "https://arxiv.org/abs/1706.03762" `
268285
| `--filename-stem` | Override the generated filename stem. |
269286
| `--no-abstract` | Omit abstract content from exports. |
270287
| `--lang` / `-l` | Deck language: one of 14 — `en`, `zh-tw`, `zh-cn`, `ja`, `es`, `fr`, `de`, `ko`, `pt`, `ru`, `it`, `vi`, `hi`, `id`. Default `en`. |
271-
| `--enrich` | Download PDF + Anthropic-summarise. Needs `ANTHROPIC_API_KEY` and `[intelligence]` extra. |
272-
| `--lightweight` | Force the abstract-only deck even when `ANTHROPIC_API_KEY` is set. |
288+
| `--enrich` | Fail-loud variant of auto-enrich. Needs `ANTHROPIC_API_KEY` and `[intelligence]` extra. (Auto-enrich is default when the key is set.) |
289+
| `--lightweight` | Skip enrichment + force the abstract-only deck. Use only for quick / unattended runs; **when an LLM agent is driving, prefer the LLM-as-agent flow** below. |
273290
| `--llm-model` | Override default `claude-opus-4-7` for enrichment. |
274-
| `--all-venues` | Disable the top-tier whitelist (default keeps flagship CS venues + Nature / Science / PNAS / CACM / LNCS). |
291+
| `--no-pdf` | Skip the automatic PDF download. Also disables the per-paper PPT gate (no PDF → no full content). |
292+
| `--no-oa-resolve` | Skip the post-dedup OA PDF resolver (Unpaywall + S2 + arXiv + CORE.ac.uk). |
293+
| `--top-tier-only` | Restrict results to arXiv + a curated CS-flagship whitelist (S&P, CCS, NDSS, USENIX Security, NeurIPS, ICML, ICSE, …). Off by default. |
275294
| `--paywall-threshold` | Fraction of paywalled results that triggers the confirmation prompt. Default 0.30. |
276295
| `--yes` | Skip the paywall prompt and proceed. |
277296
| `--max-slides` | Per-paper slide cap (default 25; pass 0 for unlimited). |
@@ -283,20 +302,42 @@ py -m autopapertoppt --paper "https://arxiv.org/abs/1706.03762" `
283302
|---|---|---|
284303
| `ANTHROPIC_API_KEY` | `--enrich` | LLM auth. Not needed for the LLM-as-agent path over MCP. |
285304
| `AUTOPAPERTOPPT_LLM_MODEL` | `--enrich` | Override the default `claude-opus-4-7`. |
286-
| `AUTOPAPERTOPPT_S2_API_KEY` | Semantic Scholar | Higher rate limit. Optional. |
305+
| `AUTOPAPERTOPPT_S2_API_KEY` | Semantic Scholar + OA resolver | Higher rate limit; also used by the OA resolver's S2 `openAccessPdf` step. Free key at <https://www.semanticscholar.org/product/api>. |
287306
| `AUTOPAPERTOPPT_NCBI_API_KEY` | PubMed | Raises NCBI's anonymous limit (3/s) to 10/s. Optional. |
288-
| `AUTOPAPERTOPPT_CONTACT_EMAIL` | PubMed, ACM, Crossref, OpenAlex | Puts requests into Crossref's polite pool. |
307+
| `AUTOPAPERTOPPT_CONTACT_EMAIL` | PubMed, ACM, Crossref, OpenAlex, **Unpaywall** | Polite-pool tag + enables the OA resolver's Unpaywall step (biggest PDF-coverage win for IEEE / ACM / Springer / Elsevier-paywalled papers; typical lift 40-70 pp). |
289308
| `AUTOPAPERTOPPT_IEEE_API_KEY` | IEEE (API path) | Official IEEE Xplore API; surfaces `pdf_url` for in-scope papers. |
290-
| `AUTOPAPERTOPPT_ENABLE_IEEE_SCRAPING` | IEEE (scrape path) | `=1` opts into scraping. Not needed when the API key is set. |
309+
| `AUTOPAPERTOPPT_DISABLE_IEEE_SCRAPING` | IEEE | **IEEE is default-ON via visible Chrome.** Set `=1` to opt out (e.g. CI without Chrome). The httpx scrape branch only runs as a fallback when WebRunner is unavailable. |
291310
| `AUTOPAPERTOPPT_CROSSREF_PLUS_TOKEN` | ACM, Crossref | Crossref Plus subscriber token (Bearer header). Optional. |
292-
| `AUTOPAPERTOPPT_SPRINGER_API_KEY` | Springer | Required; free key from <https://dev.springernature.com/>. Plugin is silently skipped without it. |
293-
| `AUTOPAPERTOPPT_ENABLE_SCHOLAR_SCRAPING` | Google Scholar | `=1` opts into scraping. Off by default — Scholar ToS forbids scraping. |
311+
| `AUTOPAPERTOPPT_SPRINGER_API_KEY` | Springer | Required; free key from <https://dev.springernature.com/>. Plugin raises `ConfigError` without it. |
312+
| `AUTOPAPERTOPPT_DISABLE_SCHOLAR_SCRAPING` | Google Scholar | **Scholar is default-ON via visible Chrome.** Set `=1` to opt out (Google's ToS forbids automated access — default-on for coverage, opt-out to avoid captcha / IP-block risk). |
313+
| `AUTOPAPERTOPPT_CHROME_PROFILE_DIR` | Scholar + IEEE + paywalled-PDF downloads | Persistent Chrome `--user-data-dir`. Set this and complete VPN / SSO / Google sign-in once; subsequent runs inherit the cookies so IEEE returns paywalled metadata and Scholar serves un-throttled SERPs. |
314+
| `AUTOPAPERTOPPT_DISABLE_WEBRUNNER` | Scholar + IEEE + paywalled-PDF downloads | `=1` forces the httpx paths instead of driving real Chrome. Useful for CI / Docker without a Chrome binary; otherwise leave unset. |
315+
| `AUTOPAPERTOPPT_CORE_API_KEY` | OA resolver | Free key from <https://core.ac.uk/services/api>. Enables the CORE.ac.uk lookup step (200M+ institutional / regional OA items). Other OA strategies (Unpaywall, S2, arXiv) still run without it. |
294316
| `AUTOPAPERTOPPT_PDF_COOKIES_FILE` | PDF downloader | Netscape `cookies.txt`. Off by default. Use only with publishers you have institutional rights to. |
295317
| `AUTOPAPERTOPPT_LOG_LEVEL` | logger | `INFO` default; `DEBUG` for verbose tracing. |
296318

297319
Defaults: `--query``pptx,xlsx,bib`. `--paper``pptx,bib`. Always
298320
overridable with explicit `--export`.
299321

322+
## LLM-as-agent flow
323+
324+
When an LLM in your editor (Claude Code, Cursor, Aider, Codex CLI, …)
325+
wants to drive the publisher browser itself — pick URLs, inspect the
326+
returned DOM, decide which papers to dig into — five scripts under
327+
`scripts/` cover the canonical path:
328+
329+
| Script | What it does |
330+
|---|---|
331+
| `scripts/llm_driven_search.py "<query>"` | Boots visible Chrome, navigates Scholar SERP for the query, JS-fetches IEEE `/rest/search` from inside the IEEE origin, dumps SERP HTML + IEEE JSON to `exports/_llm_scratch/`. |
332+
| `scripts/llm_parse_results.py` | Reads the dumped artefacts, runs the project's parsers, dedups + ranks + exports `.xlsx` + `.md` for the LLM to inspect. |
333+
| `scripts/llm_download_pdfs.py <xlsx>` | Walks the xlsx, dispatches each row to the right per-publisher downloader (IEEE / ACM / Springer / arXiv / ACL Anthology / NeurIPS / OpenReview) in ONE Chrome session. Idempotent: papers with a valid `<id>.pdf` already on disk skip immediately. |
334+
| `scripts/llm_download_{ieee,acm,springer}_pdf.py <id>` | Single-paper variants for iterating on selectors / debugging one entry. |
335+
| `scripts/regen_*.py` | Worked example of hand-authored rich `PaperSummary` per paper → rich-tier `.pptx`. Look at `scripts/regen_speculative_decoding_zh_tw.py` for the canonical shape. |
336+
337+
Full end-to-end runbook (search → rich deck) lives in
338+
`.claude/agents/paper-summary-author.md` — open it before starting a
339+
new query so the LLM can run the flow without pausing for user input.
340+
300341
## MCP server
301342

302343
Register with Claude Code:

autopapertoppt/gui/i18n.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,54 @@
960960
"hi": "कुकीज़ फ़ाइल चुनें",
961961
"id": "Pilih berkas cookies",
962962
},
963+
"settings.core_key": {
964+
"en": "CORE.ac.uk API key (OA resolver)",
965+
"zh-tw": "CORE.ac.uk API 金鑰(OA 解析器)",
966+
"zh-cn": "CORE.ac.uk API 密钥(OA 解析器)",
967+
"ja": "CORE.ac.uk API キー(OA リゾルバ)",
968+
"es": "Clave API de CORE.ac.uk (resolver OA)",
969+
"fr": "Clé API CORE.ac.uk (résolveur OA)",
970+
"de": "CORE.ac.uk API-Schlüssel (OA-Resolver)",
971+
"ko": "CORE.ac.uk API 키 (OA 리졸버)",
972+
"pt": "Chave de API CORE.ac.uk (resolvedor OA)",
973+
"ru": "Ключ API CORE.ac.uk (резолвер OA)",
974+
"it": "Chiave API CORE.ac.uk (risolutore OA)",
975+
"vi": "Khóa API CORE.ac.uk (bộ phân giải OA)",
976+
"hi": "CORE.ac.uk API कुंजी (OA रिज़ॉल्वर)",
977+
"id": "Kunci API CORE.ac.uk (resolver OA)",
978+
},
979+
"settings.chrome_profile_dir": {
980+
"en": "Chrome profile directory (Scholar / IEEE / paywalled PDFs)",
981+
"zh-tw": "Chrome 設定檔資料夾 (Scholar / IEEE / 付費 PDF)",
982+
"zh-cn": "Chrome 配置文件目录 (Scholar / IEEE / 付费 PDF)",
983+
"ja": "Chrome プロファイルディレクトリ (Scholar / IEEE / 有料 PDF)",
984+
"es": "Directorio de perfil de Chrome (Scholar / IEEE / PDFs de pago)",
985+
"fr": "Répertoire de profil Chrome (Scholar / IEEE / PDFs payants)",
986+
"de": "Chrome-Profilverzeichnis (Scholar / IEEE / kostenpflichtige PDFs)",
987+
"ko": "Chrome 프로필 디렉터리 (Scholar / IEEE / 유료 PDF)",
988+
"pt": "Diretório do perfil do Chrome (Scholar / IEEE / PDFs pagos)",
989+
"ru": "Каталог профиля Chrome (Scholar / IEEE / платные PDF)",
990+
"it": "Directory del profilo Chrome (Scholar / IEEE / PDF a pagamento)",
991+
"vi": "Thư mục hồ sơ Chrome (Scholar / IEEE / PDF trả phí)",
992+
"hi": "Chrome प्रोफ़ाइल निर्देशिका (Scholar / IEEE / पेड PDF)",
993+
"id": "Direktori profil Chrome (Scholar / IEEE / PDF berbayar)",
994+
},
995+
"settings.chrome_profile_dialog_title": {
996+
"en": "Choose Chrome profile directory",
997+
"zh-tw": "選擇 Chrome 設定檔資料夾",
998+
"zh-cn": "选择 Chrome 配置文件目录",
999+
"ja": "Chrome プロファイルディレクトリを選択",
1000+
"es": "Elija el directorio de perfil de Chrome",
1001+
"fr": "Choisir le répertoire de profil Chrome",
1002+
"de": "Chrome-Profilverzeichnis auswählen",
1003+
"ko": "Chrome 프로필 디렉터리 선택",
1004+
"pt": "Escolha o diretório do perfil do Chrome",
1005+
"ru": "Выберите каталог профиля Chrome",
1006+
"it": "Scegli la directory del profilo Chrome",
1007+
"vi": "Chọn thư mục hồ sơ Chrome",
1008+
"hi": "Chrome प्रोफ़ाइल निर्देशिका चुनें",
1009+
"id": "Pilih direktori profil Chrome",
1010+
},
9631011
}
9641012

9651013

autopapertoppt/gui/pages/settings.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@
6161
"AUTOPAPERTOPPT_CROSSREF_PLUS_TOKEN",
6262
"settings.crossref_token",
6363
),
64+
(
65+
"api/core",
66+
"AUTOPAPERTOPPT_CORE_API_KEY",
67+
"settings.core_key",
68+
),
6469
(
6570
"contact/email",
6671
"AUTOPAPERTOPPT_CONTACT_EMAIL",
@@ -70,6 +75,8 @@
7075

7176
_COOKIES_KEY: Final[str] = "pdf/cookies_file"
7277
_COOKIES_ENV: Final[str] = "AUTOPAPERTOPPT_PDF_COOKIES_FILE"
78+
_CHROME_PROFILE_KEY: Final[str] = "browser/chrome_profile_dir"
79+
_CHROME_PROFILE_ENV: Final[str] = "AUTOPAPERTOPPT_CHROME_PROFILE_DIR"
7380
_UI_LANG_KEY: Final[str] = "ui/language"
7481

7582
_ORG: Final[str] = "AutoPaperToPPT"
@@ -103,6 +110,11 @@ def apply_saved_env() -> None:
103110
os.environ[_COOKIES_ENV] = cookies
104111
else:
105112
os.environ.pop(_COOKIES_ENV, None)
113+
chrome_profile = store.value(_CHROME_PROFILE_KEY, "", type=str)
114+
if chrome_profile:
115+
os.environ[_CHROME_PROFILE_ENV] = chrome_profile
116+
else:
117+
os.environ.pop(_CHROME_PROFILE_ENV, None)
106118

107119

108120
def saved_ui_language(default: str = "en") -> str:
@@ -164,6 +176,22 @@ def _build_ui(self) -> None:
164176
cookies_layout.addWidget(browse)
165177
outer.addWidget(cookies_row)
166178

179+
chrome_row = QWidget(self)
180+
chrome_layout = QHBoxLayout(chrome_row)
181+
chrome_layout.setContentsMargins(0, 0, 0, 0)
182+
self._chrome_profile_input = QLineEdit(self)
183+
self._chrome_profile_input.setReadOnly(True)
184+
chrome_browse = QPushButton(
185+
t("settings.browse_button", self._ui_language), self,
186+
)
187+
chrome_browse.clicked.connect(self._on_browse_chrome_profile)
188+
chrome_layout.addWidget(
189+
QLabel(t("settings.chrome_profile_dir", self._ui_language)),
190+
)
191+
chrome_layout.addWidget(self._chrome_profile_input, stretch=1)
192+
chrome_layout.addWidget(chrome_browse)
193+
outer.addWidget(chrome_row)
194+
167195
save_row = QHBoxLayout()
168196
save_row.addStretch(1)
169197
save_button = QPushButton(t("settings.save_button", self._ui_language), self)
@@ -186,6 +214,16 @@ def _on_browse_cookies(self) -> None:
186214
if path:
187215
self._cookies_input.setText(path)
188216

217+
def _on_browse_chrome_profile(self) -> None:
218+
start = self._chrome_profile_input.text() or str(Path.home())
219+
path = QFileDialog.getExistingDirectory(
220+
self,
221+
t("settings.chrome_profile_dialog_title", self._ui_language),
222+
start,
223+
)
224+
if path:
225+
self._chrome_profile_input.setText(path)
226+
189227
def _on_save(self) -> None:
190228
store = settings_store()
191229
for key, env_var, _label in _SETTINGS_FIELDS:
@@ -201,6 +239,12 @@ def _on_save(self) -> None:
201239
os.environ[_COOKIES_ENV] = cookies
202240
else:
203241
os.environ.pop(_COOKIES_ENV, None)
242+
chrome_profile = self._chrome_profile_input.text().strip()
243+
store.setValue(_CHROME_PROFILE_KEY, chrome_profile)
244+
if chrome_profile:
245+
os.environ[_CHROME_PROFILE_ENV] = chrome_profile
246+
else:
247+
os.environ.pop(_CHROME_PROFILE_ENV, None)
204248
lang = self._ui_lang_combo.currentData() or "en"
205249
store.setValue(_UI_LANG_KEY, lang)
206250
store.sync()
@@ -213,6 +257,9 @@ def _load_from_store(self) -> None:
213257
for key, _env_var, _label in _SETTINGS_FIELDS:
214258
self._field_inputs[key].setText(store.value(key, "", type=str))
215259
self._cookies_input.setText(store.value(_COOKIES_KEY, "", type=str))
260+
self._chrome_profile_input.setText(
261+
store.value(_CHROME_PROFILE_KEY, "", type=str),
262+
)
216263
saved_lang = store.value(_UI_LANG_KEY, self._ui_language, type=str)
217264
for idx in range(self._ui_lang_combo.count()):
218265
if self._ui_lang_combo.itemData(idx) == saved_lang:
@@ -227,6 +274,9 @@ def field_input(self, qsettings_key: str) -> QLineEdit:
227274
def cookies_input(self) -> QLineEdit:
228275
return self._cookies_input
229276

277+
def chrome_profile_input(self) -> QLineEdit:
278+
return self._chrome_profile_input
279+
230280
def status_text(self) -> str:
231281
return self._status_label.text()
232282

0 commit comments

Comments
 (0)