A search & browse interface for free Project Gutenberg books
Search instantly across titles, authors, subjects, and summaries, filter by mood, theme, difficulty, language, or era, or hit "Surprise me".
🌐 Live site · How it works · Project layout · Pipeline · Frontend
Every book card shows its cover, an LLM-generated hook, and tags, with a detail view linking straight to the free book on gutenberg.org.
M1: Currently covering the 1,000 most-downloaded books. Scaling to the full ~78,000‑book catalog is planned.
- 🔎 Full-text search: Find any book by title, author, subject, or description instantly via MiniSearch.
- 🎭 Filter by vibe: Mood, theme, difficulty, subject, language, era; facets that actually help you discover.
- 🎲 Surprise me: One click to a random book you might never have found.
- 🤖 LLM-enriched: Each book gets a one-line hook, mood, theme tags, and difficulty rating, generated by an LLM (via OpenRouter).
- 🗺️ 2D map: Books are projected into a visual 2D space. Similar books cluster together, and you can explore by proximity.
- 📱 No backend: Fully static site, deploys to GitHub Pages. No server to run.
- 🔁 Resumable pipeline: Every stage caches its output. Re‑run or resume after any interruption.
A Python pipeline builds the map data once, offline:
flowchart LR
A[Catalog] --> B[Excerpts]
B --> C[Embed]
C --> D[Layout]
D --> E[Enrich]
E --> F[Export]
F --> G[🧩 Frontend]
| Step | Module | What it does |
|---|---|---|
| Catalog | catalog.py |
Fetch book metadata (title, author, subjects, download counts) from the Gutendex API |
| Excerpts | excerpts.py |
Download each book's plain text, strip Gutenberg boilerplate, keep the first ~2,000 words |
| Embed | embed.py |
Embed title + subjects + excerpt locally with sentence-transformers: runs on-device, no API cost |
| Layout | layout.py |
Reduce embeddings to 2D with UMAP, group into clusters with HDBSCAN |
| Enrich | enrich.py |
An LLM (via OpenRouter) names each cluster and tags every book with mood, themes, difficulty, and a one-line hook |
| Export | export.py |
Write compact JSON consumed by the frontend |
The scraped metadata (catalog, LLM tags, exported book data) is committed to this repo, so the frontend runs without re-running the pipeline.
The frontend is a static site: Vite + TypeScript, no framework. Search is client‑side via MiniSearch. No server required; deploys to GitHub Pages on every push to main.
pipeline/ Python data pipeline (uv-managed)
├── pyproject.toml
├── src/gutenberg_galaxy/
│ ├── catalog.py Gutendex catalog fetch + cache
│ ├── excerpts.py Book text download + boilerplate stripping
│ ├── embed.py Local sentence-transformers embeddings
│ ├── layout.py UMAP projection + HDBSCAN clustering
│ ├── enrich.py LLM cluster labels + per-book tags
│ ├── export.py Writes web/public/data/*.json
│ └── openrouter.py Thin OpenRouter chat client
└── tests/ pytest suite (fixtures, unit tests)
web/ Vite + TypeScript frontend
├── package.json
├── src/
│ ├── grid.ts Book card grid
│ ├── filters.ts Facet filters, sorting, surprise-me
│ ├── search.ts MiniSearch full-text search
│ ├── card.ts Book detail panel
│ └── ...
└── public/data/ Pre-built JSON (committed)
docs/ Design spec and implementation plans
cd pipeline
uv sync
export OPENROUTER_API_KEY=... # required for the enrich stage
uv run python -m gutenberg_galaxy all # runs every stage
uv run python -m gutenberg_galaxy catalog # or run one stage at a time
uv run pytest # run testsEach stage caches its output under data/ and skips work already done, so the pipeline is safe to re‑run or resume after an interruption.
cd web
npm install
npm run devRequires web/public/data/books.json, produced by the export pipeline stage (already committed to the repo).
| Document | Description |
|---|---|
| Design spec | Approved design specification |
| M1 implementation plan | Plan for the M1 pipeline and map |
| Book finder plan | Broader book finder plan |
Contributions are welcome! Whether it's a bug fix, a new feature, or an improvement to the docs. Please feel free to open an issue or submit a pull request.
Please see CONTRIBUTING.md for guidelines.
- Fork the repo
- Set up the pipeline with
uv sync - Make your changes
- Run tests:
uv run pytest - Submit a PR: CI will run tests and deploy the preview
This project is licensed under the GNU Affero General Public License v3.0 (AGPL‑3.0). See LICENSE for details.
Built with ❤️ by @TMFNK