pdf-quiz-generator/README.md
Daniel 47ba213ae3 Major platform update: pgvector search, multi-provider TTS, settings page, CLI
Features:
- Hybrid semantic + keyword quiz search (pgvector HNSW + PostgreSQL ILIKE)
- AWS Bedrock Titan Embed V2 embeddings via LiteLLM proxy (0.71 cosine sim)
- Multi-provider TTS: OpenAI, AWS Polly (neural), ElevenLabs, Google Cloud TTS
- Unified Settings page (profile, theme, Nextcloud integration, admin shortcuts)
- Good morning/afternoon greeting on dashboard
- manage.py CLI: reset-password, list-users, reembed
- Email verification enforced: register no longer returns JWT for unverified users
- Quiz search with debounced input, semantic/keyword/title modes, highlighted snippets
- TTS button: loading/playing states, voice selector locked during playback
- TTS auto-stops when navigating between questions
- Footer added; mobile quiz nav overflow fixed; markdown theme body selector fixed
- OpenAI Alloy as default TTS voice; favicon added
- SMTP configured via smtp2go; password reset rate limiting (3/hour)
- PostgreSQL upgraded to pgvector/pgvector:pg16

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:03:10 +02:00

172 lines
6 KiB
Markdown

# 🩺 PedQuiz
AI-powered pediatric knowledge quiz platform. Upload PDF study materials, automatically extract MCQ questions with AI, and take quizzes with text-to-speech support and semantic search.
## Features
- **PDF → Quiz**: Upload PREP PDFs, AI extracts questions, answers, and explanations
- **Quiz Modes**: Study (instant feedback) and Exam (timed, scored)
- **Text-to-Speech**: OpenAI TTS, AWS Polly, ElevenLabs — voice selection per quiz
- **Semantic Search**: pgvector + AWS Titan Embed — finds questions by meaning, not just keywords
- **Email Verification**: Required before first login; password reset via email
- **Role system**: Admin / Moderator / User
- **Nextcloud Integration**: Browse and import PDFs from your Nextcloud in Upload page
- **Themes**: Default and Markdown (GitHub-style)
## Stack
| Layer | Tech |
|---|---|
| Frontend | React + React Router, plain CSS, Nginx |
| Backend | FastAPI, SQLAlchemy, PostgreSQL 16 + pgvector |
| AI/LLM | LiteLLM proxy (Claude, Gemini, GPT, Bedrock) |
| Embeddings | AWS Bedrock Titan Embed V2 via LiteLLM proxy |
| Document vectors | ChromaDB |
| TTS | OpenAI, AWS Polly, ElevenLabs |
| Queue | Celery + Redis |
| Email | SMTP (smtp2go) |
## Quick Start
```bash
git clone https://github.com/ifedan-ed/pdf-quiz-generator.git
cd pdf-quiz-generator
# Configure environment
cp backend/.env.example backend/.env # edit with your keys
docker compose up -d
```
Frontend available at `http://localhost:8081`. The first registered user becomes admin automatically.
## Environment Variables
Create `backend/.env`:
```env
# Database
DATABASE_URL=postgresql://pedquiz:<password>@postgres:5432/pedquiz
SECRET_KEY=<random-32-char-string>
# Redis
REDIS_URL=redis://redis:6379/0
# LLM (for question extraction)
LITELLM_MODEL=openai/claude-haiku-4.5
LITELLM_API_KEY=<your-litellm-or-openai-key>
LITELLM_API_BASE=https://your-litellm-proxy.com # or leave empty for direct OpenAI
LITELLM_EMBEDDING_MODEL=openai/titan-embed-v2
# OpenAI (for TTS — uses api.openai.com directly, not proxy)
OPENAI_API_KEY=<openai-api-key>
# AWS (for Polly TTS + Bedrock embedding fallback)
AWS_ACCESS_KEY_ID=<key>
AWS_SECRET_ACCESS_KEY=<secret>
AWS_REGION=us-east-1
AWS_BEDROCK_REGION=us-east-1
# ElevenLabs TTS (optional)
ELEVENLABS_API_KEY=<key>
# Google Cloud TTS (optional)
GOOGLE_TTS_API_KEY=<key>
# Email
MAIL_SERVER=mail.smtp2go.com
MAIL_PORT=587
MAIL_USERNAME=<smtp2go-username>
MAIL_PASSWORD=<smtp2go-password>
MAIL_FROM=noreply@yourdomain.com
MAIL_STARTTLS=true
# App
APP_URL=https://your-domain.com
UPLOAD_DIR=/app/uploads
MAX_UPLOAD_SIZE=524288000
CHROMA_PERSIST_DIR=/app/chroma_data
```
## CLI Management
```bash
# Reset a user's password (e.g. locked-out admin)
docker compose exec backend python manage.py reset-password admin@example.com NewPassword123
# List all users and verification status
docker compose exec backend python manage.py list-users
# Regenerate all question embeddings (e.g. after switching embedding model)
docker compose exec backend python manage.py reembed
```
## Architecture
```
Browser
Nginx (frontend + API proxy)
├─► React SPA (static)
└─► FastAPI backend
├─ PostgreSQL (pgvector) ← users, quizzes, questions + embeddings
├─ ChromaDB ← document page chunks for quiz generation
├─ Redis ← Celery task queue
├─ Celery workers ← background PDF processing, emails
├─ LiteLLM proxy ← Claude/Gemini/GPT for extraction + embeddings
├─ AWS Bedrock ← Polly TTS, Titan embed fallback
└─ OpenAI ← TTS (direct, not via proxy)
```
### Search
Quiz search uses **hybrid retrieval**:
1. **Semantic** — embed the query with Titan Embed V2, cosine similarity against all questions via pgvector HNSW index
2. **Keyword** — PostgreSQL `ILIKE` on question text and options
3. Results merged and ranked — semantic matches shown first by score, keyword-only matches appended
### TTS Providers
| Provider | Model IDs | Key Needed |
|---|---|---|
| OpenAI | `tts-1:alloy`, `tts-1:nova`, `tts-1:echo`, `tts-1:shimmer`, `tts-1:onyx`, `tts-1:fable`, `tts-1-hd:*` | `OPENAI_API_KEY` |
| AWS Polly | `polly/Joanna`, `polly/Matthew`, `polly/Amy`, `polly/Brian` | `AWS_ACCESS_KEY_ID` + `polly:SynthesizeSpeech` IAM permission |
| ElevenLabs | `elevenlabs/<voice-id>` | `ELEVENLABS_API_KEY` |
| Google Cloud | `google/<voice-name>` | `GOOGLE_TTS_API_KEY` |
## Project Structure
```
├── backend/
│ ├── app/
│ │ ├── main.py # App startup, seeding, pgvector setup
│ │ ├── config.py # Settings (pydantic-settings)
│ │ ├── models/ # SQLAlchemy models
│ │ ├── routers/ # FastAPI route handlers
│ │ ├── services/
│ │ │ ├── ai_service.py # LLM extraction + TTS
│ │ │ ├── embedding_service.py # pgvector embeddings
│ │ │ ├── vector_service.py # ChromaDB (document pages)
│ │ │ ├── quiz_service.py # Quiz creation pipeline
│ │ │ └── email_service.py # Email templates + sending
│ │ └── utils/
│ ├── manage.py # CLI: reset-password, list-users, reembed
│ ├── requirements.txt
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── pages/ # Dashboard, Quiz, Results, Settings, Search …
│ │ ├── components/ # Navbar, LineChart
│ │ └── context/ # AuthContext, ThemeContext
│ ├── nginx.conf
│ └── Dockerfile
└── docker-compose.yml
```
## Deployment Notes
- The frontend Nginx only binds to `127.0.0.1:8081` — put a reverse proxy (Caddy/Nginx) in front for HTTPS
- PostgreSQL data is persisted in the `postgres_data` Docker volume — back it up regularly
- Uploads live in `uploads_data` volume — includes extracted question images
- Set `APP_URL` to your public domain so verification/reset email links work