An asynchronous, multi-agent study pipeline that ingests PDF textbooks, maps their structure with Gemini, and warms exam-ready summaries with Groq — modelled on how a CPU cache works.
- What is CacheCourse?
- How it works
- System Architecture
- Directory Structure & Module Guide
- Tech Stack
- Prerequisites
- Environment Variables
- Installation
- Running the Application
- API Reference
- Database Schema
- LangGraph Pipeline
- Academic Chatbot and RAG Engine
- Frontend Design System
- Cache Status Model
- Rate Limiting and Retries
- Security
- Contributing
CacheCourse is a full-stack AI study tool that borrows its vocabulary and mental model directly from CPU caching:
| CPU Cache concept | CacheCourse equivalent |
|---|---|
| Cold cache (compulsory miss) | Section exists but no summary yet |
| Cache warming | Groq is actively generating the summary |
| Cache hit | Summary is ready and served instantly |
| Memory hierarchy | Chapter → Subtopic → Leaf section |
| Eviction / writeback | Re-processing a failed section |
You upload a PDF textbook. Gemini reads the entire document and extracts its chapter/subtopic/section hierarchy into a structured JSON tree. Every exam-relevant leaf section is then queued through a LangGraph state machine that calls Groq's llama-3.1-8b-instant to generate:
- A core concept summary (2–3 sentences)
- Key definitions as term/definition pairs
- Critical formulas as a list
- One high-yield exam question with a model answer
Results are persisted incrementally to MongoDB so the React UI updates in real-time as each section warms — you never wait for the whole document to finish.
┌─────────────┐ PDF upload ┌──────────────────┐
┌ Browser │ ─────────────────▶ │ FastAPI /upload │
│ (React 18) │ └────────┬─────────┘
└─────────────┘ │ 202 Accepted (non-blocking)
▲ │
│ poll /documents/:id ▼
│ every 5 s ┌──────────────────────────────┐
│ │ BackgroundTask │
│ │ │
│ │ 1. Gemini 2.0 Flash │
│ │ PDF → JSON tree │
│ │ (google-genai SDK) │
│ │ │
│ │ 2. LangGraph StateGraph │
│ │ ┌─────────┐ │
│ │ │iterator │ │
│ │ └────┬────┘ │
│ │ │ leaf queue │
│ │ ┌────▼──────────┐ │
│ │ │ summarizer │◀─┐ │
│ │ │ (Groq LLM) │ │ │
│ │ └────┬──────────┘ │ │
│ │ │ more leaves? │ │
│ │ └─────────────┘ │
│ │ │
│ │ 3. Each leaf → MongoDB │
│ │ cache_status: hit │
└─────────────────────────────┴──────────────────────────────┘
The CacheCourse application is split into a decoupled client-server architecture:
-
Client-Side (React 18 + Vite):
- Uses
react-router-domfor application routing (Landing Page, Auth Forms, Dashboard feeds, and Split-screen Study workspaces). - Global states are separated: authentication details live in a standard React Context, while documents, loading states, and current active nodes reside in a high-performance Zustand store (
docStore). - CSS modules isolate styles for components, preventing stylesheet collisions and supporting micro-animations like the signature
cache-hit-revealanimation.
- Uses
-
Server-Side API Gateway (FastAPI):
- Exposes public and protected endpoints behind JWT stateless token-verification routes.
- Leverages
uvicornas the ASGI application server and uses async database lifespans to initialize collections and create database indexes.
-
External Integrations & Workers:
- Google Gemini 2.0 Flash: Processes uploaded PDFs on demand via the modern
google-genaiSDK to output structural JSON matching the textbook outlines. - LangGraph StateGraph: Orchestrates pipeline jobs. It utilizes stateless retry policies with exponential backoffs to query the Groq Llama API to compile chapter summaries, definitions, and formulas.
- Motor (MongoDB Client): Performs async database executions, indexing user profiles and tracking individual document states, tree outlines, and leaf page content.
- Google Gemini 2.0 Flash: Processes uploaded PDFs on demand via the modern
The full codebase is structured as follows. Click on any file path to navigate directly to it in your local workspace:
CacheCourse/
├── server/ FastAPI backend service
│ ├── app/ Core application package
│ │ ├── core/ Security & Auth utilities
│ │ │ └── auth.py JWT logic & password hashing
│ │ ├── models/ Pydantic validation schemas
│ │ │ ├── user.py Schemas for user registration & login
│ │ │ └── document.py User document & JSON tree outlines
│ │ ├── services/ External AI provider APIs
│ │ │ └── gemini.py PDF uploading & structure parsing
│ │ ├── pipeline/ LangGraph orchestration
│ │ │ └── graph.py State machine, iterator & summarizer nodes
│ │ ├── routers/ FastAPI route endpoints
│ │ │ ├── auth.py /register and /login endpoints
│ │ │ └── documents.py /upload, document details, & RAG /chat endpoints
│ │ ├── config.py pydantic-settings environment loader
│ │ └── database.py Motor async MongoDB connection client
│ │ # Convenience shortcuts (Interface wrappers)
│ │ ├── auth.py Shortcut to core auth helpers
│ │ ├── models.py Shortcut to models schemas
│ │ ├── gemini_service.py Shortcut to gemini service
│ │ └── graph.py Shortcut to LangGraph graph builders
│ └── main.py FastAPI server creation & database lifespans
│
└── client/ React frontend application
└── src/ Application source code
├── lib/ Custom axios network client
│ └── api.js Axios instance with JWT interceptors
├── context/ Context wrappers
│ └── AuthContext.jsx React Auth state & session handler
├── store/ Zustand state management
│ └── docStore.js Zustand document list & upload states
├── components/ Reusable UI elements
│ ├── ProtectedRoute.jsx Guard for private pages
│ ├── UploadZone.jsx Drag-and-drop PDF component
│ ├── UploadZone.module.css Styles for uploads
│ ├── TreeOutline.jsx Recursive outlines navigation
│ ├── TreeOutline.module.css Styles for tree navigation
│ ├── SummaryPanel.jsx Reveals summaries, definition lists & formulas
│ ├── SummaryPanel.module.css CSS with custom cache-hit-reveal animation
│ ├── ChatPanel.jsx Chat interface panel with textbook assistant
│ └── ChatPanel.module.css Styles for chatbot panels
├── pages/ Router navigation page view roots
│ ├── LandingPage.jsx Animated hero landing with SVG schematic tree
│ ├── LandingPage.module.css Styles for landing page
│ ├── AuthPage.jsx Login / Register unified entry form
│ ├── AuthPage.module.css Styles for auth page
│ ├── DashboardPage.jsx Displays document feeds and upload zones
│ ├── DashboardPage.module.css Styles for dashboard page
│ ├── StudyPage.jsx Split-screen workspace (tree + details panel + chatbot)
│ └── StudyPage.module.css Styles for study page
├── App.jsx Application router & paths registry
├── App.css Generic base rules
├── index.css Global CSS containing variables & typography definitions
└── main.jsx React app launcher
- server/app/core/auth.py: Handles stateless JWT token encoding, decoding, token expiration, password salting/hashing via
passlib[bcrypt], and FastAPI dependency injection (get_current_user) to authenticate incoming HTTP requests. - server/app/models/user.py: Validates credentials for registration (
UserRegister) and authentication logins (UserLogin). - server/app/models/document.py: Represents the recursive nodes of the textbook structure (
TreeNode), the document summary metadata (DocumentListItem), and full details containing nested summaries (DocumentOut).
- server/app/config.py: Automatically reads variables from
server/.envwithpydantic-settings. Config values are validated and cached to prevent redundant disk access. - server/app/database.py: Establishes async MongoDB connections using
motor.motor_asyncio. Creates unique indexes on application startup (e.g.,users.emailanddocuments.user_id). - server/app/services/gemini.py: Handles textbook processing using the official
google-genaiSDK. It uploads the raw PDF to Google AI Studio and invokes Gemini 2.0 Flash with a structured instruction to return a fully formatted outline tree matching the Pydantic structure. - server/app/pipeline/graph.py: Implements the stateful
StateGraphworker using LangGraph. After the PDF tree structure is created, the graph collects all exam-relevant leaf sections and loops over them, calling the Groq API (llama-3.1-8b-instant) to "warm" each node with structured core concepts, formulas, and mock questions.
- server/app/routers/auth.py: Defines the
/api/registerand/api/loginendpoints. - server/app/routers/documents.py: Manages documents (uploading PDFs, listing documents, fetching outline detail states) and handles chatbot requests by scanning the pages collection and calling the LLM context.
- server/main.py: Sets up the FastAPI application context, registers routers, handles CORS validation, and initializes/terminates the database motor client connections.
- client/src/lib/api.js: A preconfigured Axios client that intercepts outgoing HTTP requests to append the
Authorization: Bearer <token>header if a user is logged in. It also detects401 Unauthorizedresponses to clear local storage and redirect the user back to the login screen. - client/src/context/AuthContext.jsx: Tracks active user states and sessions, exposing
login,register, andlogouthooks to the components. - client/src/store/docStore.js: A global state store using Zustand. Avoids prop-drilling by managing uploaded documents lists, processing status, and the currently active node details.
- client/src/pages/LandingPage.jsx: Implements a beautiful drafting-table styled landing page featuring a custom SVG schematic diagram of the cache states (Cold, Warming, Hit) to explain the core CPU-cache analogy.
- client/src/components/TreeOutline.jsx: Renders a collapsible tree outline of the textbook structure. Highlights the cache state of each node (Cold vs. Warming vs. Hit) and lets users click exam-relevant leaves.
- client/src/components/SummaryPanel.jsx: Renders summaries, key terms, equations, and mock questions. Uses the
cache-hit-revealanimation to transition newly warmed sections into display. - client/src/components/ChatPanel.jsx: Renders an inline chat window. Users can ask queries about specific nodes or the whole textbook, generating real-time responses from the RAG engine.
- client/src/components/UploadZone.jsx: A drag-and-drop zone that handles uploading PDF textbooks to the backend server with dynamic progress bars.
| Layer | Technology | Purpose |
|---|---|---|
| Framework | FastAPI 0.115 + uvicorn | Async HTTP, dependency injection |
| Database | MongoDB Atlas via motor 3.6 | Async document store |
| Auth | PyJWT 2.10 + passlib[bcrypt] | Stateless JWT, bcrypt password hashing |
| Config | pydantic-settings 2.6 | Typed env var loading with .env support |
| AI — structure | google-genai 1.0 | Modern Gemini SDK (not deprecated generativeai) |
| AI — summaries | langchain-groq 0.2 | Groq LLM calls via LangChain |
| Orchestration | langgraph 0.2 | Stateful multi-node pipeline with RetryPolicy |
| File upload | python-multipart | Multipart form parsing |
| Layer | Technology | Purpose |
|---|---|---|
| Framework | React 18 + Vite 5 | Component UI + fast HMR dev server |
| Routing | react-router-dom v6 | Client-side routing with nested routes |
| HTTP | axios 1.7 | JWT interceptor, 401 auto-redirect |
| State | React Context + Zustand 5 | Auth state (Context), doc state (Zustand) |
| Icons | lucide-react 0.447 | Crisp SVG icon set |
| Fonts | Space Grotesk, Inter, JetBrains Mono | Display, body, and monospace faces |
| Tool | Minimum version | Notes |
|---|---|---|
| Python | 3.12+ | 3.14 works; uses set | None union syntax |
| Node.js | 18 LTS+ | Required for Vite + npm |
| MongoDB Atlas | any | Free M0 tier works fine |
| Gemini API key | — | Google AI Studio → aistudio.google.com |
| Groq API key | — | console.groq.com |
Note:
uvis used in the server directory for virtual environment management. If you don't have it, usepython -m venv .venvinstead.
Copy server/.env.example → server/.env and fill in every value:
# MongoDB Atlas connection string
MONGODB_URI=mongodb+srv://<user>:<password>@<cluster>.mongodb.net/?retryWrites=true&w=majority
# Database name (created automatically)
DB_NAME=cachecourse
# JWT signing secret — generate with: python -c "import secrets; print(secrets.token_hex(32))"
SECRET_KEY=change_me_to_a_long_random_string_at_least_32_chars
# Google AI Studio key (for Gemini 2.0 Flash)
GEMINI_API_KEY=your_gemini_api_key_here
# Groq Cloud key (for llama-3.1-8b-instant)
GROQ_API_KEY=your_groq_api_key_here
# Maximum allowed PDF size in megabytes
MAX_PDF_SIZE_MB=50| Variable | Required | Default | Description |
|---|---|---|---|
MONGODB_URI |
✅ | — | Atlas connection string |
DB_NAME |
❌ | cachecourse |
MongoDB database name |
SECRET_KEY |
✅ | — | 32+ char random string for JWT signing |
GEMINI_API_KEY |
✅ | — | Google AI Studio API key |
GROQ_API_KEY |
✅ | — | Groq Cloud API key |
MAX_PDF_SIZE_MB |
❌ | 50 |
Upload size limit |
# Base URL for the FastAPI backend (no trailing slash)
VITE_API_BASE_URL=http://localhost:8000In development the Vite proxy (
vite.config.js) forwards/apirequests tohttp://localhost:8000, so this variable is only needed for production builds.
# 1. Navigate to server directory
cd server
# 2. Create and activate virtual environment
uv venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
# 3. Install all dependencies
pip install -r requirements.txt
# 4. Create your env file
copy .env.example .env # Windows
# cp .env.example .env # macOS / Linux
# 5. Fill in your real values in .envfastapi==0.115.5
uvicorn[standard]==0.32.1
motor==3.6.0
pydantic==2.10.3
pydantic-settings==2.6.1
PyJWT==2.10.1
passlib[bcrypt]==1.7.4
bcrypt==4.2.1
python-multipart==0.0.18
langgraph==0.2.60
langchain-core==0.3.27
langchain-groq==0.2.3
langchain-google-genai==2.0.8
google-genai==1.0.0
groq==0.13.0
pymongo==4.10.1
# 1. Navigate to client directory
cd client
# 2. Install Node dependencies
npm install
# 3. (Optional) set env vars for local dev
copy .env.local.example .env.local # or just edit .env.local directly{
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.2",
"axios": "^1.7.7",
"lucide-react": "^0.447.0",
"zustand": "^5.0.0"
}
}Terminal 1 — Backend
cd server
.venv\Scripts\activate
uvicorn main:app --reload --port 8000The API is now live at http://localhost:8000. Interactive docs at http://localhost:8000/docs.
Terminal 2 — Frontend
cd client
npm run devThe React app is now at http://localhost:5173. The Vite proxy forwards all /api/* requests to the FastAPI server, so there are no CORS issues in dev.
# Backend — run without --reload, behind a reverse proxy (nginx / Caddy)
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
# Frontend — build static assets, serve from CDN or reverse proxy
cd client
npm run build # outputs to client/dist/All protected routes require the Authorization: Bearer <token> header.
| Method | Endpoint | Auth | Body | Response |
|---|---|---|---|---|
POST |
/api/register |
❌ | { email, password } |
{ access_token, token_type, user_id, email } |
POST |
/api/login |
❌ | { email, password } |
{ access_token, token_type, user_id, email } |
| Method | Endpoint | Auth | Body | Response |
|---|---|---|---|---|
POST |
/api/upload |
✅ | multipart/form-data (field: file) |
{ document_id, status } — 202 Accepted |
GET |
/api/documents |
✅ | — | DocumentListItem[] (no tree_data) |
GET |
/api/documents/:id |
✅ | — | Full DocumentOut with tree_data |
POST |
/api/documents/:id/chat |
✅ | { message: string, history: ChatMessage[] } |
{ response: string, citations: string } |
| Method | Endpoint | Response |
|---|---|---|
GET |
/health |
{ "status": "ok" } |
| Status | Meaning |
|---|---|
processing_structure |
Gemini is reading the PDF and extracting the hierarchy |
processing_summaries |
LangGraph is warming leaf sections with Groq |
complete |
All exam-relevant sections have been summarized |
failed |
An unrecoverable error occurred (see error field) |
{
"_id": "ObjectId",
"email": "string (unique indexed)",
"hashed_password": "string (bcrypt)",
"created_at": "ISODate"
}{
"_id": "ObjectId",
"user_id": "string (indexed)",
"pdf_name": "string",
"upload_date": "ISODate",
"status": "processing_structure | processing_summaries | complete | failed",
"error": "string | null",
"tree_data": [
{
"id": "string (unique within doc)",
"title": "string",
"level": 1,
"page_start": 1,
"page_end": 24,
"is_exam_relevant": true,
"cache_status": "cold | warming | hit",
"summary": null,
"children": [
{
"id": "string",
"title": "string",
"level": 2,
"page_start": 3,
"page_end": 10,
"is_exam_relevant": true,
"cache_status": "hit",
"summary": {
"core_concept": "string",
"key_definitions": [{ "term": "string", "definition": "string" }],
"critical_formulas": ["string"],
"exam_question": {
"question": "string",
"answer": "string"
}
},
"children": []
}
]
}
]
}Indexes created automatically on startup:
users.email(unique),documents.user_id.
The pipeline lives in server/app/pipeline/graph.py and runs entirely in the background after upload.
┌──────────────┐
START ─────────▶│ iterator │
└──────┬───────┘
│ Walk tree, collect all
│ is_exam_relevant=true leaves
│ Set status = processing_summaries
▼
┌──────────────┐ queue not empty
│ summarizer │◀──────────────────┐
│ (Groq LLM) │ │
└──────┬───────┘ │
│ │
┌─────▼──────┐ ┌──────────┴──────────┐
│ complete? │──yes──▶│ complete node │
└─────┬──────┘ │ status = complete │
│ no │ or failed │
└───────────────┘ │
▼
END
RetryPolicy(
max_attempts=5,
initial_interval=2.0, # seconds before first retry
backoff_factor=2.0, # doubles each retry: 2 → 4 → 8 → 16 → 32 s
max_interval=60.0, # caps at 60 s
retry_on=Exception, # catches 429, 5xx, timeouts, JSON parse errors
){
"core_concept": "2–3 sentence explanation",
"key_definitions": [{ "term": "...", "definition": "..." }],
"critical_formulas": ["formula or principle as plain string"],
"exam_question": {
"question": "high-yield exam question",
"answer": "model answer"
}
}The chatbot feature inside the Study Workspace allows users to hold dynamic, context-aware conversations with their uploaded documents.
┌──────────────┐ Post Chat Message ┌────────────────────────┐
│ Browser │ ────────────────────────▶ │ POST /documents/:id/ │
│ (ChatPanel) │ │ chat │
└──────────────┘ └───────────┬────────────┘
▲ │
│ │ 1. Fetch PDF pages
│ ▼
│ ┌────────────────────────┐
│ │ MongoDB database │
│ └───────────┬────────────┘
│ │
│ │ 2. Scrape matching context
│ ▼
│ ┌────────────────────────┐
│ │ BM25-like Scorer │
│ │ (Term overlaps) │
│ └───────────┬────────────┘
│ │
│ │ 3. Build detailed prompt
│ ▼
│ ┌────────────────────────┐
│ │ Groq Llama 3.3 (70B) │
│ └───────────┬────────────┘
│ │
│ 5. Returns formatted response & citations │ 4. Fallback (if Groq fails)
│ ▼
└─────────────────────────────────────────────── Gemini 2.5 Flash
- Retrieval: When a query is made, the backend fetches the stored raw text pages of the document from the MongoDB collection.
- Scoring & Context Formulation: It scores each page using a simple, token-overlap BM25-like matcher:
- Excludes short words (< 4 chars).
- Counts match frequencies of each term.
- Slices the top 5 most relevant pages and formats them as standard context blocks.
- Agent Framing: The chatbot agent acts as an encouraging, empathetic, and thorough academic mentor. It uses positive reinforcement to comfort the student, and outputs detailed structural markdown (including lists, bold sections, and citations like
pp. 12, 15). - Resilient Gateway Execution:
- Primary Model:
llama-3.3-70b-versatileon Groq (temperature0.4, max tokens2048). - Fallback Model:
gemini-2.5-flashon Google Gemini (triggered if Groq meets rate limits or network issues).
- Primary Model:
| Token | Hex | Usage |
|---|---|---|
--bg-void |
#05060a |
Page background |
--bg-deep |
#0b0d14 |
Header, sidebar |
--bg-surface |
#181c2a |
Cards, inputs |
--accent-plasma |
#6c63ff |
Primary CTA, active states |
--accent-hit |
#00e5a0 |
Cache hit indicator, formulas |
--accent-warm |
#f59e0b |
Warming / in-progress state |
--accent-cold |
#475569 |
Cold / unprocessed indicator |
--accent-danger |
#ef4444 |
Error states |
| Role | Font | Usage |
|---|---|---|
| Display | Space Grotesk | Headings, brand name, section titles |
| Body | Inter | Paragraphs, labels, UI copy |
| Mono | JetBrains Mono | Badges, metadata, formulas, hints |
When a leaf section finishes processing and the user clicks it, the summary panel plays cache-hit-reveal — a scale-up + glow animation that mimics data appearing in L1 cache. Every other animation in the UI is deliberately restrained to keep this moment memorable.
@keyframes cache-hit-reveal {
0% { opacity: 0; transform: scale(0.95); box-shadow: none; }
60% { box-shadow: 0 0 20px rgba(0, 229, 160, 0.2); }
100% { opacity: 1; transform: scale(1); }
}- All interactive elements have
idattributes,aria-label, andaria-expanded/aria-currentwhere applicable - Keyboard navigation supported across the tree outline and auth forms
prefers-reduced-motionmedia query disables all animations globally when enabled
Upload complete
│
▼
[cold] ─────── Gemini extracts tree ──────▶ tree_data written to MongoDB
│
LangGraph picks up leaf
│
▼
[warming] ← set before Groq call
│
Groq returns JSON
│
▼
[hit] ───▶ summary saved, UI updates
The frontend polls GET /api/documents/:id every 5 seconds while the document is in a processing state and updates the tree outline indicators in real-time without a full page reload.
| Provider | Model | Default TPM | Mitigation |
|---|---|---|---|
| Groq | llama-3.1-8b-instant | 6 000 tokens/min (free tier) | LangGraph RetryPolicy with exponential backoff |
| Gemini | gemini-2.5-flash | 15 RPM (free tier) | Single call per upload; no retry needed for structure extraction |
For high-volume deployments, switch the Groq node to use a langchain_groq model with langchain_core's built-in rate-limit aware callbacks, or route through LiteLLM for provider-agnostic load balancing.
- Passwords hashed with bcrypt (cost factor 12 via passlib default) — raw passwords are never logged or stored
- JWTs are signed with
HS256and expire in 7 days by default (ACCESS_TOKEN_EXPIRE_MINUTES=10080) - The
SECRET_KEYmust be at least 32 random bytes — generate one with:python -c "import secrets; print(secrets.token_hex(32))"
- MongoDB queries always filter by
user_idto prevent cross-user data access - PDF size is capped at
MAX_PDF_SIZE_MB(default 50 MB) before bytes are read into memory - CORS is locked to
localhost:5173andlocalhost:3000in development — tighten to your production origin before deploying .envis in.gitignore— never commit real credentials
- Fork the repo
- Create a feature branch:
git checkout -b feature/my-change - Make your changes — keep backend and frontend concerns separated
- Run the server:
uvicorn main:app --reloadand verify the/docsUI - Run the client:
npm run devand verify your UI changes - Open a pull request with a clear description of what changed and why