A small RAG demo: a chat assistant that answers questions from a markdown
corpus the studio owns, with [1]-style citations that deep-link to the exact
paragraph in the source document. There is no client data in the repo. The
corpus under corpus/ is the studio's own handbook (services, pricing, FAQ,
onboarding, security, stack), MIT-licensed alongside the code.
The demo runs end-to-end on a fresh laptop with no API keys and no external
services. Set OPENAI_API_KEY later to swap the templated mock answers for
real model output.
git clone https://github.com/Rapitzo/studio-knowledge-chat.git
cd studio-knowledge-chat
npm install
npm run ingest
npm run devOpen http://localhost:3000, click through /dev-login, pick a user, and ask
the chat a question. Citations link to /sources/<slug>#<anchor> and scroll
the right section into view.
No
.env.localis required for a first run. Defaults take over: dev login, mock-mode AI, JSON-on-disk index. Copy.env.exampleto.env.localonly when you want to switch on a real provider or magic-link auth.
| Name | Required? | What it enables |
|---|---|---|
AUTH_MODE |
optional (dev) |
dev (default) uses /dev-login cookie. magic-link uses NextAuth + SMTP. |
OPENAI_API_KEY |
optional | Real AI answers + real embeddings. If unset, the app boots in mock mode. |
ANTHROPIC_API_KEY |
optional | Use Claude for chat (set AI_PROVIDER=anthropic). |
XAI_API_KEY |
optional | Use Grok for chat (set AI_PROVIDER=xai). |
VOYAGE_API_KEY |
optional | Use Voyage for embeddings (set EMBEDDINGS_PROVIDER=voyage). |
AI_PROVIDER |
optional | openai | anthropic | xai | ollama | lmstudio | claude-code. |
EMBEDDINGS_PROVIDER |
optional | openai | ollama | voyage. |
OLLAMA_BASE_URL |
optional | Use a local Ollama for chat or embeddings (no key required). |
LMSTUDIO_BASE_URL |
optional | Use LM Studio locally (no key required). |
CLAUDE_CODE_BIN |
optional | Use the Claude Code CLI as the chat provider. |
INDEX_PATH |
optional | Override the JSON vector-store path (default data/index.json). |
CORPUS_DIR |
optional | Override the markdown corpus path (default corpus). |
DATABASE_URL |
only for magic-link | Postgres URL. Required when AUTH_MODE=magic-link. |
EMAIL_SERVER |
only for magic-link | Nodemailer SMTP URL. Required when AUTH_MODE=magic-link. |
EMAIL_FROM |
only for magic-link | Verified sender address. Required when AUTH_MODE=magic-link. |
NEXTAUTH_URL |
only for magic-link | Public base URL. |
NEXTAUTH_SECRET |
only for magic-link | NextAuth signing secret. openssl rand -base64 32. |
DEMO_ALLOWLIST |
only for magic-link | Comma-separated emails. Empty = anyone with a valid magic link. |
Default AUTH_MODE=dev:
- Go to http://localhost:3000/dev-login (or click the sign-in link from
/). - Pick one of three pre-seeded users —
alice@example.local,bob@example.local, orstudio@example.local. - A
dev-sessioncookie is set in your browser. The cookie expires after 24 hours. Sign out with the button in the chat header.
No email is sent, no DB row is written, nothing leaves your laptop.
To switch on the production magic-link flow, set AUTH_MODE=magic-link in
.env.local plus the DATABASE_URL, EMAIL_SERVER, EMAIL_FROM,
NEXTAUTH_URL, and NEXTAUTH_SECRET env vars. Visit /login instead of
/dev-login. The magic-link code path stays committed; nothing was deleted.
None required. The retrieval index is a JSON file on disk
(data/index.json), loaded into memory at server start. npm run ingest
walks corpus/*.md, chunks each file at ~600 tokens with 80 tokens of
overlap, embeds each chunk, and writes the index file. In mock mode
(no AI key set) the embedding is a deterministic local hash, so identical
input always lands at the same vector and the kNN search still works.
- Replace the markdown under
corpus/with your own files. One file per logical document. The filename (sans.md) becomes the slug used in citation links. npm run ingest— re-embeds only the chunks whose content hash changed.- Restart
npm run dev. The chat picks up the new corpus immediately.
If you want a tighter sign-in gate in magic-link mode, set DEMO_ALLOWLIST
to a comma-separated list of approved emails.
- Retrieval.
scripts/ingest.tschunkscorpus/*.mdinto ~600-token windows with 80-token overlap, embeds each chunk with the configured provider (or a deterministic local hash in mock mode), and writesdata/index.json. The store keeps documents, chunks, and embeddings in a single JSON file. - Chat.
app/api/chat/route.tsembeds the user's question, runs cosine kNN over the JSON store (top 6 chunks), and either streams a real model answer (when a key is set) or returns a templated mock answer that still cites the retrieved chunks. The first line of the response is a JSON payload with citation metadata; the rest is the assistant text. - Citations.
lib/citations.tsparses[n]markers in the assistant output and turns each into a link to/sources/<slug>#<anchor>. The source page renders the markdown with anchored sections so the link lands on the right paragraph.
MIT. See LICENSE. The corpus under corpus/ is also MIT and is
studio-owned content. There is no client data in this repo.