TLDR is a web app for browsing, searching, reading, and annotating academic PDFs.
This repository contains the running backend service (Fastify + SQLite) and the web UI served by that backend.
- Split-view reading UI: paper list on the left, PDF reader on the right.
- Search papers by title, author, or keyword.
- Restore reading position (page + normalized scroll).
- Stream PDFs with HTTP range support.
- Create highlights and notes.
- Ask AI questions about a highlight and keep the conversation history.
- Delete highlights and delete papers (including associated notes/highlights and on-disk PDF file).
- Import PDFs from URLs.
When a paper is added/imported, TLDR extracts metadata using a layered approach:
- GROBID header parsing (if
GROBID_URLis configured). - Embedded PDF metadata (
dc:title,dc:creator, etc.). - First-page text/layout heuristics.
- Filename fallback.
This improves title/author quality across mixed PDF sources.
TLDR can upload PDFs to Gemini Files and store gemini_file_id per paper.
POST /api/highlights/:highlightId/ask-aiuses the stored Gemini file ID plus highlight context.- Background backfill periodically uploads missing/stale papers when
GEMINI_API_KEYis present. - Use
npm run gemini:uploadto backfill existing records manually.
- Node.js (LTS recommended)
- SQLite (embedded via
better-sqlite3) - Optional: GROBID service (for higher-quality metadata extraction)
- Optional: Gemini API key (for AI and Gemini Files)
TLDR_SERVICE_ROOT: service root; also used to find.env.TLDR_DB_PATH: SQLite DB file path (example:./data/tldr.sqlite).TLDR_DOCUMENT_ROOT: root folder used to resolve storedpdf_pathvalues.TLDR_PORT: API server port (default:3000).TLDR_HOST: bind address (default:0.0.0.0).TLDR_DEFAULT_USER_NAME: display name in notes UI.GROBID_URL: GROBID endpoint (example:http://localhost:8070).GEMINI_API_KEY: Gemini API key.GEMINI_MODEL: Gemini model name (default:gemini-2.5-flash).GEMINI_BACKFILL_INTERVAL_MINUTES: background upload interval.
Use .env.example as the baseline local config:
TLDR_SERVICE_ROOT=.
TLDR_DB_PATH=./data/tldr.sqlite
TLDR_DOCUMENT_ROOT=./data/papersnpm install
npm run devnpm run buildexport TLDR_SERVICE_ROOT=/path/to/tldr-service
./deploy.shOn first deploy (or when dependencies change), deploy.sh runs npm ci --omit=dev in the service root.
cd "$TLDR_SERVICE_ROOT"
node dist/server.jsThe server loads .env from:
$TLDR_SERVICE_ROOT/.env(ifTLDR_SERVICE_ROOTis set)./.env(current working directory)- module root
.env
npm run dev: run server in watch mode.npm run build: compile TypeScript todist/.npm run start: run compiled server.npm run reindex:metadata: re-extract and refresh metadata.npm run gemini:upload: upload PDFs and populategemini_file_id.npm run notes:clear: clear stored notes/highlights data.npm run migrate:gemini-id: run Gemini ID migration script.
GET /api/papers?q=...&sort=last_viewed: list/search papers.GET /api/papers/:paperId: paper details + reading position.PUT /api/papers/:paperId/reading-position: update reading position.POST /api/papers: add a paper from an existing relativepdfPath.POST /api/papers/import: download/import a paper from URL.DELETE /api/papers/:paperId: delete paper + related data + PDF file.GET /api/papers/:paperId/file: stream PDF.GET /api/papers/:paperId/highlights: list highlights for a paper.POST /api/papers/:paperId/highlights: create a highlight.GET /api/highlights/:highlightId: get one highlight.DELETE /api/highlights/:highlightId: delete one highlight.POST /api/highlights/:highlightId/notes: add note to highlight.POST /api/highlights/:highlightId/ask-ai: ask AI using highlight + Gemini file.
pdf_pathvalues are resolved relative toTLDR_DOCUMENT_ROOT.last_scroll_yis normalized to0..1within the last viewed page.