No description
Find a file
Daniel 287d3a72f4 Fix question bank: select-all, bulk category, page size, category delete
Bugs fixed:
- 'Select all' now fetches ALL matching IDs from server (GET /questions/bank/ids)
  not just the currently loaded page — works for 268 questions
- Bulk category assign: fixed 'Remove category' value ('0' → 'remove'),
  now shows error message on failure instead of silent fail
- Search mode (Keyword/Semantic/Hybrid) moved to a clearly labelled dropdown,
  visually separated from category filter chips — eliminates confusion
- Category chips now have a 'Filter:' label to distinguish from other buttons

New features:
- Page size selector: 50 / 100 / 200 / All — loads the requested count in one shot
- Bulk actions moved to its own clearly labelled bar (only shown for moderators)
- Category delete with questions: prompts user to choose target category before deleting
  (uses new move_to param on DELETE /question-categories/{id})
- GET /questions/bank/ids endpoint for server-side select-all

Backend:
- DELETE /question-categories/{id}?move_to={id}: move questions to another category
  before deletion instead of always leaving them uncategorized

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 02:55:25 +02:00
backend Fix question bank: select-all, bulk category, page size, category delete 2026-04-01 02:55:25 +02:00
frontend Fix question bank: select-all, bulk category, page size, category delete 2026-04-01 02:55:25 +02:00
.gitignore Initial commit: PDF Quiz Generator app 2026-03-30 20:04:53 +00:00
docker-compose.yml Better fonts, hamburger mobile nav, jobs indicator, warm theme refinement 2026-04-01 02:14:18 +02:00
README.md Major platform update: pgvector search, multi-provider TTS, settings page, CLI 2026-03-31 18:03:10 +02:00

🩺 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

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:

# 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

# 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)

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