Chat2Study turns long AI chats into structured study assets:
- preserved artifacts (HTML, text, screenshot, PDF)
- vector-indexed chunks for semantic search
- rendered markdown study notes
- interactive visual concept maps
You give it a chat URL you are authorized to access. Chat2Study captures the page, stores artifacts, indexes the content, and lets you search or ask grounded questions over it. For dense technical content it generates study notes and concept-map style visual notes.
-
JWT-based auth (register, login, protected routes) with per-user chat scoping
-
Next.js 15 App Router frontend with nav, login/register pages
-
FastAPI backend with full REST API
-
PostgreSQL + pgvector persistence
-
MinIO-backed artifact storage
-
Playwright capture pipeline
-
LangGraph 10-node ingestion workflow
-
Multi-provider abstraction (Ollama, OpenAI, Anthropic, Google)
-
Semantic chunk retrieval
-
Grounded Q&A
-
Markdown study notes with proper rendering +
.mddownload -
Visual notes with dagre-layouted, draggable React Flow graph
-
GitHub Actions CI
-
Async ingestion (202 + job polling) with background execution
- Distributed worker deployment (Celery / queue-based workers in
workers/) - Browser extension ingestion
- Shareable study pages
- Citations in notes and Q&A answers
- Production cloud deployment guide
- Next.js 15, React 19, TypeScript
- Tailwind CSS v4
- React Flow (
@xyflow/react) + dagre layout react-markdown+remark-gfm
- FastAPI, SQLAlchemy 2, Alembic, Psycopg 3
- LangChain, LangGraph
python-jose(JWT),bcrypt(passwords)
- PostgreSQL + pgvector
- Redis (configured, used for future queue)
- MinIO (S3-compatible local object storage)
- Docker / Docker Compose
- Playwright (Chromium)
- Google Gemini (recommended free option)
- OpenAI, Anthropic, Ollama (all supported)
- pnpm, uv, Ruff, GitHub Actions
[ Browser ]
|
v
[ Next.js App Router ] ──── JWT cookie auth ────
|
v (REST + Bearer token)
[ FastAPI ]
|
├── PostgreSQL + pgvector (chats, chunks, notes, jobs, users)
├── MinIO / S3 (raw_html, visible_text, screenshot, PDF)
└── LangGraph ingestion workflow
├── Playwright capture
├── Artifact persistence
├── Chunk + embed (Google / OpenAI / Ollama)
├── Complexity scoring
├── Study notes generation (LLM)
└── Visual notes generation (LLM → React Flow)
- Node.js 22+
- pnpm 10+
- Python 3.11+
- uv
- Docker + Docker Compose
- A Google AI Studio API key (free) or Ollama for fully local use
git clone https://github.com/YOUR_USERNAME/chat2study.git
cd chat2studycp .env.example .envOpen .env and fill in at minimum:
# Free option — get your key at aistudio.google.com/apikey
GOOGLE_API_KEY=your-key-here
DEFAULT_CHAT_PROVIDER=google
DEFAULT_EMBEDDING_PROVIDER=google
GOOGLE_CHAT_MODEL=gemini-2.5-flash
GOOGLE_EMBEDDING_MODEL=models/gemini-embedding-001
# Auth secret — generate with: python3 -c "import secrets; print(secrets.token_hex(32))"
SECRET_KEY=change-meFor the web app:
cp apps/web/.env.local.example apps/web/.env.localpnpm infra:upThis starts PostgreSQL + pgvector, Redis, and MinIO.
| Service | URL |
|---|---|
| Web app | http://localhost:3000 |
| API + Swagger | http://localhost:8000/docs |
| MinIO console | http://localhost:9001 (minioadmin / minioadmin) |
cd apps/api
uv sync
uv run playwright install chromium
uv run alembic upgrade head
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000From the repo root:
pnpm install
pnpm web:devOpen http://localhost:3000 — you'll be redirected to /login. Register an account and you're in.
The chat and embedding providers are independently configurable via env vars. No code changes needed.
Get a key at aistudio.google.com/apikey — no credit card required.
GOOGLE_API_KEY=your-key
DEFAULT_CHAT_PROVIDER=google
DEFAULT_EMBEDDING_PROVIDER=google
GOOGLE_CHAT_MODEL=gemini-2.5-flash
GOOGLE_EMBEDDING_MODEL=models/gemini-embedding-001ANTHROPIC_API_KEY=sk-ant-...
DEFAULT_CHAT_PROVIDER=anthropic
ANTHROPIC_CHAT_MODEL=claude-haiku-4-5-20251001
# Use an embedding provider separately (Google or OpenAI) — Anthropic has no embeddings API
DEFAULT_EMBEDDING_PROVIDER=googleOPENAI_API_KEY=sk-...
DEFAULT_CHAT_PROVIDER=openai
DEFAULT_EMBEDDING_PROVIDER=openai
OPENAI_CHAT_MODEL=gpt-4o-mini
OPENAI_EMBEDDING_MODEL=text-embedding-3-smallollama serve
ollama pull llama3.1:8b # recommended minimum for notes generation
ollama pull nomic-embed-textDEFAULT_CHAT_PROVIDER=ollama
DEFAULT_EMBEDDING_PROVIDER=ollama
OLLAMA_CHAT_MODEL=llama3.1:8b
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
OLLAMA_BASE_URL=http://localhost:11434Note: If you switch embedding models after indexing chats, re-index existing chats via
POST /api/v1/chats/{id}/index— vectors from different models are not compatible.
PROJECT_NAME=Chat2Study
APP_ENV=development # set to "production" to enable strict config checks + disable /docs
LOG_LEVEL=INFO
SECRET_KEY= # JWT signing secret (required in production — generate a random hex string)
JWT_ALGORITHM=HS256
JWT_EXPIRE_DAYS=7
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000DATABASE_URL=postgresql+psycopg://chat2study:chat2study@localhost:5432/chat2studyS3_ENDPOINT=http://localhost:9000
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
S3_BUCKET=chat2study
S3_FORCE_PATH_STYLE=trueDEFAULT_CHAT_PROVIDER=google # google | openai | anthropic | ollama
DEFAULT_EMBEDDING_PROVIDER=google # google | openai | ollama
GOOGLE_API_KEY=
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GOOGLE_CHAT_MODEL=gemini-2.5-flash
GOOGLE_EMBEDDING_MODEL=models/gemini-embedding-001
OPENAI_CHAT_MODEL=gpt-4o-mini
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
ANTHROPIC_CHAT_MODEL=claude-haiku-4-5-20251001
OLLAMA_CHAT_MODEL=llama3.1:8b
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
OLLAMA_BASE_URL=http://localhost:11434EMBEDDING_DIMENSIONS=768
CHUNK_SIZE=1200
CHUNK_OVERLAP=200
RETRIEVAL_TOP_K=5
NOTES_CONTEXT_CHAR_LIMIT=16000PLAYWRIGHT_HEADLESS=true
LOCAL_ARTIFACT_STAGING_DIR=.cache/artifactsThe pipeline is a 10-node LangGraph state machine:
- load_chat — fetch chat + source metadata from DB
- select_providers — resolve chat and embedding providers from config
- plan_capture — choose browser strategy (authenticated for Claude.ai / ChatGPT / Gemini, generic otherwise)
- execute_capture — Playwright headless capture (HTML, text, screenshot, PDF)
- persist_artifacts — upload all artifacts to MinIO
- index_chat — chunk text, embed with configured provider, store in pgvector
- classify_complexity_seed — score content density to decide whether to auto-generate notes
- generate_study_notes — LLM markdown notes (if complexity threshold met)
- generate_visual_notes — LLM JSON graph for React Flow (if threshold met)
- build_result_payload — assemble final workflow result
Notes auto-generation can be triggered manually from the UI or API regardless of complexity score.
All routes require a Authorization: Bearer <token> header except /auth/register and /auth/login.
POST /api/v1/auth/register { email, name, password } → { access_token }
POST /api/v1/auth/login { email, password } → { access_token }
GET /api/v1/auth/me → UserResponse
GET /api/v1/health liveness (public)
GET /api/v1/health/ready readiness — checks DB + object storage (public)
GET /api/v1/providers provider summary (auth required)
POST /api/v1/chats create a chat record
GET /api/v1/chats list recent chats (up to 50)
GET /api/v1/chats/{id} get a single chat
POST /api/v1/chats/{id}/ingest queue ingestion (returns 202 + job immediately)
GET /api/v1/jobs/{id} poll job status until completed/failed
GET /api/v1/chats/{id}/artifacts
GET /api/v1/chats/{id}/artifacts/{artifact_id}/download → { "url": presigned S3 URL }
POST /api/v1/chats/{id}/index re-index (use after switching embedding providers)
POST /api/v1/chats/{id}/search { query, top_k } → semantic chunk results
POST /api/v1/chats/{id}/ask { question, top_k } → grounded LLM answer
GET /api/v1/chats/{id}/notes
POST /api/v1/chats/{id}/notes/generate
POST /api/v1/chats/{id}/visual-notes/generate
GET /api/v1/chats/{id}/notes/download → .md file download
JWT-based auth. Token is stored in a cookie and sent as a Bearer header on all API requests. All other routes require a valid token (enforced by Next.js middleware).
- Ingest form with progress feedback
- Recent chats list with color-coded status badges
- Links to chat detail pages
- Study notes rendered with
react-markdown(headers, code blocks, tables, lists) - Download study notes as
.mdfile - Generate study notes / visual map buttons with spinner feedback
- Visual concept map: dagre-layouted, color-coded by node kind, draggable
- Semantic search and grounded Q&A panel
- Artifact browser with download links
# Web build (includes type checking)
pnpm web:build
# API lint
cd apps/api && uv run ruff check .
# API format
cd apps/api && uv run ruff format .
# New migration after model changes
uv run alembic revision --autogenerate -m "description"
uv run alembic upgrade headGitHub Actions (.github/workflows/ci.yml) runs on push and PRs:
- API: dependency install → Ruff lint → import check
- Web: dependency install → production build
Dockerfiles exist for both services:
docker build -f apps/api/Dockerfile -t chat2study-api .
docker build -f apps/web/Dockerfile -t chat2study-web .This app cannot be deployed as a single unit because Playwright requires a persistent Linux environment with Chromium — incompatible with serverless platforms.
| Component | Recommended host | Notes |
|---|---|---|
| Next.js frontend | Vercel | Zero-config, native Next.js support |
| FastAPI backend | Railway / Render / Fly.io | Needs Dockerfile, persistent process for Playwright |
| PostgreSQL | Neon (free) or Railway | Enable the pgvector extension |
| MinIO / S3 | Cloudflare R2 (free 10 GB) or AWS S3 | Set S3_FORCE_PATH_STYLE=false for real S3 |
| Redis | Upstash (free tier) | Used for future background jobs |
Environment variables to change for production:
APP_ENV=production # enables strict startup checks, disables /docs
SECRET_KEY=<strong random secret>
DATABASE_URL=<managed postgres URL>
REDIS_URL=<upstash redis URL>
S3_ENDPOINT=<r2 or s3 endpoint>
S3_ACCESS_KEY=<real credentials>
S3_SECRET_KEY=<real credentials>
S3_FORCE_PATH_STYLE=false
CORS_ORIGINS=https://your-frontend-domain.comThe complexity scorer may have rated the page as too simple. Use the Generate buttons in the UI or call the API endpoints directly — they always run regardless of score.
Some pages never reach networkidle (analytics, websockets). The capture waits for domcontentloaded, tries load, tries networkidle briefly, then continues regardless.
Vectors from different models live in different spaces and are not compatible. Search and Q&A keep working after a provider switch because queries are embedded with the provider each chat was originally indexed with. To actually move a chat to the new provider, re-index it:
curl -X POST http://localhost:8000/api/v1/chats/CHAT_ID/index \
-H "Authorization: Bearer YOUR_TOKEN"When creating Next.js dynamic routes in zsh, quote paths with square brackets:
mkdir -p 'apps/web/app/chats/[chatId]'- Background worker deployment (async ingestion with Celery)
- Per-user chat scoping
- Browser extension for one-click ingestion
- Shareable study pages
- Citations in answers and notes
- Export notes as PDF / slides
- Production deployment guide
Released under the MIT License. See LICENSE for the full text.