A research workspace — grounded answers, inline citations, zero bluffing.
Attach sources. Ask questions. Get answers generated only from what you attached — every claim carries a citation, and out-of-context questions get an honest "I don't know" instead of an invented one.
| 📎 Attach anything | Web pages, PDFs / DOCX, YouTube videos, pasted text — each source is parsed, chunked, and embedded into a private vector index. |
| 💬 Chat with your sources | Answers are streamed token-by-token, built only from your attached material, with inline [1]-style citations that resolve to the exact passage. |
| 🧠 Studio — one-click artifacts | Mindmap, Quiz, Summary, Deep-research report, and Fact-check generated from the same index, with the same citation discipline. |
| 🚫 The no-bluff contract | If the sources don't cover it, the model says "I don't know about this" — it never fills gaps with plausible-sounding text. |
| 🔐 Google sign-in | One click, no passwords. Notebooks, sources, chats, and artifacts are saved server-side, scoped to your account, and enforced with Postgres row-level security. |
| 🪟 Three resizable panes | Sources · Chat · Studio — each drag-resizable, built for the way you actually research. |
OpenbookLM doesn't use one big model for everything. It's a cost-sane pipeline: a fast, cheap model runs the high-volume orchestration steps, and the heavyweight reasoning model is reserved for where reasoning actually matters — talking to you.
| Stage | Model | Why |
|---|---|---|
| Reranking (precision relevance filter) | 🌟 Google Gemini 3.1 Flash | Judging 15 vector-search candidates against a query is a high-volume, low-stakes call — a fast thinking model does it cheaply at ~1000× lower cost per token than the frontier model. |
| Studio artifacts (mindmaps, quizzes, summaries, fact-checks) | 🌟 Google Gemini 3.1 Flash | Structured extraction and formatting over retrieved chunks — speed and JSON reliability beat frontier reasoning here. |
| Chat generation (the answers you read) | 🧠 Claude Opus 5 | The main chatting model. Grounded synthesis, careful citation placement, and honest refusals are exactly what a frontier reasoning model is for. |
| Embeddings | 🔷 Cohere embed-english-v3.0 (1024-dim) |
Purpose-built retrieval embeddings, batched 96-per-request into pgvector. |
| Document parsing | 🦙 LlamaParse | Layout-aware PDF/DOCX → markdown, so tables and structure survive ingestion. |
The split is deliberate: Gemini 3.1 Flash orchestrates, Claude Opus 5 conversates. Bulk pipeline steps never touch the expensive model, and the expensive model never wastes its reasoning on filtering chores.
flowchart TB
subgraph CLIENT["🖥️ Next.js 15 · React 19 · Tailwind 4"]
UI["Three-pane UI<br/>Sources · Chat · Studio"]
end
subgraph INGEST["📥 Ingestion Pipeline — /api/ingest"]
direction TB
P1["1 · EXTRACT<br/>🦙 LlamaParse (PDF/DOCX)<br/>Jina Reader (URLs)<br/>YouTube transcripts"]
P2["2 · CHUNK<br/>Structural markdown splitting<br/>sections → paragraphs → ~800 chars"]
P3["3 · EMBED<br/>🔷 Cohere embed-v3 · 1024-dim<br/>batched ×96"]
P1 --> P2 --> P3
end
subgraph DB["🗄️ Supabase Postgres + pgvector"]
SRC["sources<br/>(RLS-scoped)"]
CHUNK["chunks<br/>+ embeddings"]
MSG["chat_messages"]
NB["notebooks"]
end
subgraph CHAT["💬 Chat Pipeline — /api/chat"]
direction TB
S["1 · SEARCH<br/>embed query → match_chunks (top-15)"]
R["2 · RERANK<br/>🌟 Gemini 3.1 Flash precision filter → top-5"]
G["3 · GENERATE<br/>🧠 Claude Opus 5 · streamed · cited"]
S --> R --> G
end
subgraph STUDIO["🧠 Studio — /api/studio"]
T["Task configs: retrieval query,<br/>K values, output format<br/>→ 🌟 Gemini 3.1 Flash"]
end
UI --> INGEST
UI --> CHAT
UI --> STUDIO
INGEST --> CHUNK
CHAT --> S
S --> CHUNK
STUDIO --> CHUNK
G --> MSG
R --> G
Every answer — chat or studio — walks the same 3-stage RAG path:
1 · Search. Your question is embedded with Cohere (search_query mode) and
matched against the chunks of your selected sources via the match_chunks
Postgres function (cosine similarity, top-15).
2 · Rerank. Gemini 3.1 Flash receives all 15 candidates and returns only the ids that are strictly relevant to the query — a precision filter that cuts noise before it can reach the context window. If the model's reply can't be parsed, the system degrades gracefully to similarity order; reranking is an optimization, never a single point of failure.
3 · Generate. The surviving chunks are numbered, wrapped in a strict grounded-generation prompt, and streamed to Claude Opus 5. The prompt enforces the rules that define the product:
- Answer only from the numbered chunks — no outside knowledge, ever.
- Cite claims inline:
[1],[2][3], right after the sentence they support. - If the context doesn't cover it, refuse — plainly, with a suggestion of what to attach or ask instead.
- Never mention chunks, retrieval, embeddings, or the pipeline.
The finished answer is persisted server-side (with its citation chunk ids),
so a flaky browser session can never lose your history. Chunk ids ride back on
the X-Citation-Chunk-Ids header so the UI can render hoverable citation chips
that open the exact source passage.
Adding a source triggers the background pipeline in /api/ingest
(maxDuration = 300 — big PDFs take a while):
- Extract — routed by source type:
- PDF / DOCX → LlamaParse (upload → poll job → fetch markdown), with layout-aware parsing so structure survives.
- YouTube → real transcript fetching with browser-like headers, timeouts, retries, and actionable failure messages ("creator disabled captions" vs. "YouTube is blocking this server"). A YouTube link pasted into the generic URL field is auto-detected and re-routed to the transcript path.
- URL → Jina Reader (
r.jina.ai) for clean markdown extraction. - Text → used as-is.
- Chunk — structural splitting: markdown headings first (sections stay together), then paragraphs, then sentence-boundary packing into ~800-char chunks. A title is derived from the content when possible.
- Embed — Cohere
embed-english-v3.0, 1024 dimensions, auto-batched past the 96-input request limit. - Index — previous chunks for the source are deleted and replaced
(re-ingestion is idempotent), then the source flips to
ready.
Sources carry a status chip — ready / processing / failed — with failure
messages written for humans, not stack traces.
Grounded by construction, not by hope. The no-bluff contract isn't a polite request in a prompt — it's the product's spine. Empty retrieval short-circuits to a canned refusal before any model is called. The system prompt forbids outside knowledge. Citations must resolve to retrievable passages.
Cheap steps are cheap. The pipeline spends tokens like they're scoped: Gemini 3.1 Flash filters and formats, Cohere embeds, and only the final answer — the part you actually read — goes to Claude Opus 5.
Degrade, never die. Every model call is guarded: unparsable reranker output falls back to similarity order, an empty completion surfaces actionable config diagnostics (with a masked key, never the secret), interrupted streams say so, and JSON-straying quiz output is handed back as markdown rather than lost.
The server owns the truth. Chat history, ownership checks, and persistence all happen server-side with the service role — after verifying the notebook belongs to the caller. Row-level security covers everything the browser can reach directly. A dropped connection costs you nothing.
| Layer | Tech |
|---|---|
| Framework | Next.js 15 (App Router, Route Handlers, streaming Responses) |
| UI | React 19 · Tailwind CSS 4 · lucide-react · react-resizable-panels |
| Auth | Supabase Auth — Google OAuth, SSR session cookies |
| Database | Supabase Postgres + pgvector, RLS-scoped |
| Retrieval | Cohere embed-english-v3.0 · cosine-similarity match_chunks RPC |
| Orchestration | Gemini 3.1 Flash (reranking + studio) via its OpenAI-compatible endpoint |
| Chat model | Claude Opus 5 |
| Parsing | LlamaParse · Jina Reader · youtube-transcript |
| Route | What it does |
|---|---|
POST /api/ingest |
Full extraction → chunk → embed → index pipeline (JSON or multipart) |
POST /api/chat |
Search → rerank → streamed grounded generation, server-side persistence |
POST /api/studio |
Task-configured retrieval + generation (mindmap / quiz / summary / fact-check / deep) |
GET /api/chunks |
Fetch chunk contents for citation rendering |
GET /auth/callback |
Supabase OAuth callback |
1 · Install dependencies
npm install2 · Configure environment — create .env.local:
# Supabase — auth, database, vector store
NEXT_PUBLIC_SUPABASE_URL=https://<project>.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key>
SUPABASE_SERVICE_ROLE_KEY=<service-role-key> # server-side only, never NEXT_PUBLIC
# Retrieval embeddings
COHERE_API_KEY=<cohere-key>
# PDF / DOCX parsing
LLAMA_CLOUD_API_KEY=<llamaparse-key>
# LLM orchestration (Gemini OpenAI-compatible endpoint)
GEMINI_API_KEY=<gemini-key>
GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
GEMINI_MODEL=<your chat model>3 · Database — the project expects a Supabase Postgres database with
pgvector enabled and notebooks, sources, chunks, and chat_messages
tables plus the match_chunks RPC, all protected by row-level security.
4 · Run
npm run dev # http://localhost:3000
npm run build # production buildOpenbookLM — answers you can trust, because you can check them.
⭐ Star it if it made your research easier


