Local-only reading companion: import HTML books, read with context-aware AI chat, and collect notes.
Architecture is session + tools + persistence, not CRUD pages. Runtime: @mariozechner/pi-agent-core + @mariozechner/pi-ai.
Two cores:
- Agent runtime — Pi coding-agent SDK on the server (
packages/server). Tools, sessions, SQLite. - PWA — Vite + React (
packages/web). Read, select passages, chat, notes.
Shared TypeScript contracts live in packages/shared.
No cloud, no account, no telemetry. Data stays in the Docker volume quantum-data.
This phase freezes agent book/article generation (boundary → outline → generate_section). Re-enable with QUANTUM_GENERATION_ENABLED=1. The happy path is manual HTML import.
Chinese translation: README.zh-CN.md.
Docker Compose is the only supported preview path. After docker compose up --build, open:
| Surface | URL |
|---|---|
| PWA | http://127.0.0.1:43127 |
| API / health | http://127.0.0.1:43127/api/health or http://127.0.0.1:43128/api/health |
Do not treat pnpm --filter @quantum/web dev as acceptance.
Compose publishes loopback only: 127.0.0.1:43127:80 and 127.0.0.1:43128:43128. QUANTUM_HOST defaults to 127.0.0.1. Do not publish a bare :43128 (that would listen on all interfaces and skip nginx). Intentional LAN/public bind must set QUANTUM_HOST and rewrite ports: explicitly.
SQLite and exports live in quantum-data (QUANTUM_DATA_DIR=/data in the server container). Stop with Ctrl+C or docker compose down. The volume survives down; wipe it with docker compose down -v.
Compose sets QUANTUM_API_TOKEN=local-preview on server + nginx only. That default is a weak password for loopback preview. Change it before any LAN or public bind (QUANTUM_API_TOKEN=… docker compose up --build). An empty/unset token leaves the HTTP gate off — treat that the same way: loopback only.
When the token is set, every /api/* except GET /api/health needs a matching Authorization: Bearer <token> or X-Quantum-Token: <token> (reads, writes, SSE, exports). Health stays open and does not dump config.
The browser must not hold the token. There is no VITE_* bake-in and no login UI. Nginx injects X-Quantum-Token when proxying /api to :43128. The Vite dev/preview proxy does the same from process.env.QUANTUM_API_TOKEN. Hitting :43128 directly bypasses nginx, so you must send the header yourself.
Needs Docker Engine and the Compose plugin.
git clone <this-repo>
cd deep-learn
docker compose up --buildFirst boot pulls the runtime image and creates quantum-data. Ready when :43127 serves the PWA and /api/health returns {"ok":true,"name":"quantum",...}.
Compose services: server (Hono on 127.0.0.1:43128, also publishes PWA 127.0.0.1:43127 → :80) and web (nginx + built PWA). web uses network_mode: service:server so /api and SSE proxy to 127.0.0.1:43128 on the PWA origin, with nginx injecting the preview token.
corepack enable
pnpm install
pnpm --filter @quantum/shared build
pnpm --filter @quantum/server build
pnpm --filter @quantum/server start
# other terminal
pnpm --filter @quantum/web devWeb unit tests (no model, no Docker):
pnpm --filter @quantum/web test
pnpm --filter @quantum/web buildpackages/shared tool names, events, DTOs
packages/server Pi agent, tools, SQLite, SSE, export, HTML import
packages/web PWA (Shelf / Learn / Notes / Me)
docs/ IA, backend-baseline-v0.5, two-cores acceptance, hand-click
browser :43127 → nginx / vite (PWA)
→ /api proxy → hono :43128
├─ GET /api/health
├─ GET|POST /api/topics
├─ POST /api/topics/import-html
├─ GET|POST /api/session …
├─ tools: append_note, get_section, … (generation tools frozen by default)
└─ better-sqlite3 → /data/quantum.db
Book switch / import HTML / organize / export. No reading body as the primary surface; no notes dump.
Import: 书架 → 导入 HTML (file or paste). Server POST /api/topics/import-html splits by h1/h2 into chapters and enters learning.
Read the current book section. Left outline, right AI session. Select a passage in the body; the session sends it as context. Generation cards stay available only when QUANTUM_GENERATION_ENABLED=1.
Notes for the current book only (AI append_note sediment). Book switch and body reading live on Shelf / Learn.
Model proxy and interface language. Do not change Me beyond the shared tab shell in this refactor.
- First visit: follow the browser language (
en*→ English,zh*→ Chinese). Anything else falls back to zh-CN. - After you pick a language, it is stored in
localStorage(quantum.locale).
The agent does not have a separate language switch. Replies follow the language of the user's message. The UI toggle does not inject a forced locale into model prompts.
Server APIs have no locale field. Stub/coach copy follows the user's input language; PWA empty states and chrome are translated on the client. No server locale is needed.
Open Me → Model proxy, or pass through Compose:
QUANTUM_MODEL_PROVIDERQUANTUM_MODEL_IDQUANTUM_MODEL_BASE_URL(OpenAI-compatible proxy)QUANTUM_MODEL_API_KEY
Keys stay in SQLite (/data/quantum.db). They are never written to chat, SSE, logs, or tool arguments. Without a key, the local stub coach still runs the real tool loop.
- Local-only. The runtime never phones home.
scope_in/scope_outstay as the user wrote them.- Notes belong to the current topic. Refuse + reflow beats grounding when the turn is out of scope.
- PWA → Books → right drawer → New topic. Stub walks eight dimensions (motivation → outcome → success evidence → prior → prereqs → scope_in → exclude → depth → load).
- After
boundary_finalized, confirm the independent boundary card on Learn, then the outline, then enterlearning. - Follow-ups in the session should show a citation row when the section has body. Notes come only from
append_note. - Export
md | html | epubfrom Books; expect a real download (export_ready). - In learning, hit
scope_out(for example exclude “string theory”, then ask to cover it) → short refuse + pull back to the current leaf, no note.
Hand-click path: docs/hand-click-five-steps.md. Contract: docs/backend-baseline-v0.5.md. Cores: docs/cores.md.
MIT — Copyright (c) 2026 Jiamo.
See CHANGELOG.md.