# 🩺 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:@postgres:5432/pedquiz SECRET_KEY= # Redis REDIS_URL=redis://redis:6379/0 # LLM (for question extraction) LITELLM_MODEL=openai/claude-haiku-4.5 LITELLM_API_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= # AWS (for Polly TTS + Bedrock embedding fallback) AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_REGION=us-east-1 AWS_BEDROCK_REGION=us-east-1 # ElevenLabs TTS (optional) ELEVENLABS_API_KEY= # Google Cloud TTS (optional) GOOGLE_TTS_API_KEY= # Email MAIL_SERVER=mail.smtp2go.com MAIL_PORT=587 MAIL_USERNAME= MAIL_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/` | `ELEVENLABS_API_KEY` | | Google Cloud | `google/` | `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