Turn sources into understanding.
LearnForge is a self-hosted study notebook (inspired by Google NotebookLM). You collect materials in a workspace, then chat with them and generate study tools — summaries, mind maps, quizzes, and flashcards — with answers grounded in your sources.
- Workspaces — One notebook per topic, course, or exam. Search, rename, or delete them from the home page.
- Many source types — PDF, pasted text, web links, YouTube videos (captions), and VTT caption files.
- Grounded chat — Ask questions and get streaming answers with clickable citations. Click a citation to see the original chunk (PDF page, video timestamp, URL, or transcript cue).
- Optional web search — Turn on the globe in chat when your sources are not enough (uses Tavily).
- Learn (Studio) — Generate a summary, mind map, quiz, or flashcards from ready sources. Pick which sources to include; quizzes and flashcards let you set how many items.
- Source viewer — Open a source to inspect it; PDFs can be viewed in-app when the file is stored.
- Sign in — Email + password, or Google / GitHub OAuth.
- Themes — Dark by default, with a light/dark toggle that remembers your preference.
Open the app and go to /login.
- Create an account with email and password, or
- Sign in with Google or GitHub, or
- After seeding (see Setup), click Use demo account (
testuser@email.com/12345678) to explore sample workspaces.
On the home page, click New workspace and give it a name — for example “Intro to ML” or “Exam revision”. Open a workspace to start adding materials.
In the Sources tab, click add source and choose a type:
| Type | What to provide |
|---|---|
Upload a .pdf (max 20MB) |
|
| Text | Paste notes or excerpts |
| Web link | A public article or page URL |
| YouTube | A video URL that has captions |
| VTT | Upload a caption / subtitle file |
Wait until the status is ready (green). Indexing can take a few seconds. Chat and Learn tools only use sources that have finished indexing.
Type a question in the chat panel. Answers are based on the workspace’s indexed sources and include numbered citations.
- Click a citation badge to open the Source Viewer on the cited passage.
- Toggle the globe if you want live web results alongside your sources (needs
TAVILY_API_KEY).
Open the Learn tab and generate:
- Summary — a markdown overview of the selected sources
- Mind map — a visual map of the main ideas
- Quiz — multiple-choice questions you can take in the app
- Flashcards — cards you can flip through
Choose which ready sources to include. Generated items stay in the workspace so you can reopen them later.
- Rename or delete a workspace from the home page or the workspace header menu.
- Re-index or delete a source from its card if content failed or you want a fresh index.
- Node.js 18+
- A Neon Postgres database
- A Qdrant Cloud cluster (free tier works)
- An OpenAI API key
- A Firecrawl API key (required for web-link sources)
- Optional: Tavily (chat web search), Vercel Blob (store uploaded PDFs/VTTs), Google/GitHub OAuth apps
git clone <this-repo>
cd learnforge
npm install
cp .env.example .env
# fill in DATABASE_URL, QDRANT_URL, QDRANT_API_KEY, OPENAI_API_KEY,
# FIRECRAWL_API_KEY (required for web links), JWT_SECRET
npm run db:push # creates tables in Neon via Drizzle
npm run db:seed # optional: populate demo account (needs OpenAI + Firecrawl + Qdrant)
npm run dev # http://localhost:3000Then sign up on /login, create a workspace, add a source, and start asking questions.
Demo data: after npm run db:seed, click Use demo account on /login. Re-run npm run db:seed anytime to reset and rebuild that account’s workspaces, sources, and Learn artifacts (takes a few minutes).
| Variable | Description |
|---|---|
DATABASE_URL |
Neon Postgres connection string |
QDRANT_URL |
Qdrant Cloud cluster URL |
QDRANT_API_KEY |
Qdrant Cloud API key |
OPENAI_API_KEY |
OpenAI API key |
OPENAI_EMBEDDING_MODEL |
default text-embedding-3-small |
OPENAI_CHAT_MODEL |
default gpt-4o-mini |
FIRECRAWL_API_KEY |
Required for URL sources |
TAVILY_API_KEY |
Optional. Chat web-search toggle |
BLOB_READ_WRITE_TOKEN |
Optional. Store uploaded PDFs and VTT files |
JWT_SECRET |
Random string for signing session cookies |
NEXT_PUBLIC_APP_URL |
Optional. Canonical app URL for OAuth callbacks |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
Optional. Google sign-in |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET |
Optional. GitHub sign-in |
Built with Next.js (App Router), Postgres on Neon, Qdrant Cloud for vectors, OpenAI for embeddings + chat, and LangChain for chunking.
┌─────────────┐ ┌──────────────────────┐ ┌────────────────────┐
│ Next.js │ │ Neon Postgres │ │ Qdrant Cloud │
│ App Router │◄────►│ users / workspaces / │ │ 1 collection per │
│ (UI + API) │ │ sources / messages │ │ user, filtered by │
│ │ │ studio artifacts │ │ workspaceId/sourceId│
└──────┬───────┘ └──────────────────────┘ └─────────┬───────────┘
│ ▲
│ extract → chunk (LangChain) → embed (OpenAI) │
└────────────────────────────────────────────────────────┘
- Postgres stores accounts, workspaces, source status, chat history, and Learn artifacts.
- Qdrant stores vectors plus a small payload (chunk text, source id, page/timestamp).
- Isolation: each user gets a Qdrant collection (
user_<uuid>). Search is always filtered byworkspaceId.
Ingestion runs as an async job from POST /api/sources; the UI polls until the source is ready.
Indexing: extract text (Firecrawl for web links) → split into ~800-character chunks → embed with OpenAI → upsert into Qdrant → status ready.
Chat: embed the question → search Qdrant in the current workspace → stream an answer that cites numbered excerpts. The model is instructed to stay grounded in those excerpts and to treat source text as data, not as instructions.
- Rate limits on workspace creation, uploads, and chat
- File size (20MB) and MIME allow-list
- Length caps on pasted text and questions
- SSRF guard on URL/YouTube sources (blocks private/local addresses)
- Session cookie (JWT) plus ownership checks on every API route
- Qdrant cleanup: deleting a workspace cascades in Postgres but does not always remove leftover vectors. Call
deleteSourceChunksper source before deleting workspaces in production. - Rate limiting is in-memory per server instance — fine for a single process, not for many replicas.
- Web search and file uploads need
TAVILY_API_KEYandBLOB_READ_WRITE_TOKENrespectively; without them those paths will fail or skip storage.
Dark mode is the default. The palette uses deep ink tones with a warm saffron accent. Cards use rounded corners, soft shadows, and thin borders; the header uses a frosted-glass blur. The sun/moon toggle switches themes immediately and remembers your preference.