Commit graph

14 commits

Author SHA1 Message Date
Daniel
0489bb49a1 Redesign Settings page — responsive grid, abridged docs, less scroll
- Page max-width 1100px (from 600px) for desktop side-by-side layout
- CSS grid auto-fits cards at minmax(320px, 1fr) — 1 col mobile, 2-3 cols desktop
- Cards align to top (alignItems: start) so short ones don't stretch
- Password change hidden behind a <details> summary to reduce scroll
- Notifications + Appearance + Admin compacted (smaller padding, tighter layout)
- Documents section: shows 5 most recent, 'Show all N' button expands inline,
  'Show less' collapses — no separate page needed
- Long filenames truncate with ellipsis
- Removed redundant descriptive text, shortened labels

Mobile: single column stack, preserves all functionality.
Desktop: 2-3 columns depending on viewport width.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 20:24:41 +02:00
Daniel
699cbabfcb Add OIDC/SSO login, reminder fixes, user notification settings
- Add generic OIDC/SSO support (configurable via env vars)
- Admin can enable SSO-only mode (disables password login)
- SSO callback auto-creates and verifies users
- Login page shows SSO button when configured, hides password form in SSO-only mode
- Fix reminders: skip course quizzes and deleted quizzes
- Don't create reminders for course quiz attempts
- Add user reminder opt-out toggle in Settings > Notifications
- Scheduler checks user opt-out before sending emails

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 04:10:15 +02:00
Daniel
695392f022 Remove HIBP breached password warnings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 02:24:24 +02:00
Daniel
affd2fc116 Add admin guide, document CLI commands, consolidate account into settings
- Add list-docs, fix-stuck-docs, reprocess-doc CLI commands
- Create ADMIN.md with deployment, CLI, database, and troubleshooting docs
- Add HIBP warning to Settings page password change
- Remove separate Account nav link (already in Settings)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:39:58 +02:00
Daniel
8137d5b20c Decouple course quizzes, replace passlib, add security hardening
- Fully decouple course quizzes from main quiz system (hidden from
  dashboard stats, history, search, results page)
- Course quiz results show "Back to Course" instead of retake/delete
- Add allow_review toggle for course creators to control answer review
- Show quiz title on course page, hide pool size from students
- Add course thumbnails to browse cards
- Replace passlib with bcrypt directly (compatible with existing hashes)
- Add HIBP breached password warnings on register/reset/change password
- Add CLI management tools (reset-password, set-role, stats, etc.)
- Fix quiz PATCH endpoint: ownership check instead of moderator-only
- Add max_length validation on course/module/lesson titles
- Fix score display bug on results page (0 of N when review disabled)
- Fix question count on course quiz start (show per-attempt, not pool)
- Improve suspend warning for timed course quizzes with max attempts
- Clean up validation error messages (show "Invalid email" not Pydantic dump)
- Add DDL migration for allow_review column

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 21:31:05 +02:00
Daniel
975a31fb01 TeachChat: model selector, proxy routing fix, titan seed cleanup
- teach.py: use _proxy_model() + pass api_key/api_base from settings (fixes LiteLLM provider error for openrouter/bedrock models)
- teach.py: accept model_id in ChatRequest so frontend can select model
- main.py: remove titan-embed-v2 from general seed, auto-delete legacy entry on startup
- main.py: kill stale idle-in-transaction DB connections at startup to prevent DDL lock hangs
- main.py: set lock_timeout=10s on DDL connection as fast-fail safety net
- TeachChat.jsx: fetch /teach/models, show selector dropdown in header when >1 model available

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:37:35 +02:00
Daniel
12d99d3609 Add switchable embedding model, Polly toggle, job cancellation, and UI fixes
Embedding:
- Embedding model now configurable via Admin UI (More tab) or LITELLM_EMBEDDING_MODEL env
- Calls LiteLLM proxy directly via httpx (bypasses LiteLLM library param validation)
- Passes dimensions=1024 to proxy; Redis setting overrides env var
- Default model: ge-gemini-embedding-001 (Gemini AI Studio, 1024-dim)
- Test button in admin UI to verify model works
- Fixed vector_service to use httpx + Redis model (was broken with non-prefixed model names)

Polly:
- Global enable/disable toggle in Admin → More settings (stored in Redis)
- /tts/voices filters out polly/* when disabled
- /tts/speak rejects polly requests when disabled

Job cancellation:
- POST /quizzes/job/{job_id}/cancel endpoint
- Cancel button on JobsPage for running jobs
- Celery task checks Redis status at each chunk boundary and exits cleanly
- Fixes DB lock on restart caused by cancelled jobs leaving open transactions

Admin UI:
- Settings tab renamed to "More" (heading: More Settings)
- Model row overflow fixed (minWidth: 0 + ellipsis on model_id)
- Embedding model search shows all proxy models (no auto-filter by "embed")
- Navbar correctly excludes cancelled/failed jobs from "extracting" count

README:
- Added Rebuild & Restart section with commands
- Updated embedding model reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 20:44:11 +02:00
Daniel
1343eb5801 Fix mobile touch, hide docs for users, timer save/restore, Nextcloud server-side
Mobile touch fix:
- Added touch-action: manipulation and -webkit-tap-highlight-color: transparent
  to .question-card .option CSS — prevents mobile browsers from interfering
  with tap events (double-tap zoom, highlight flash, delayed clicks)

Documents hidden for regular users:
- Dashboard: stats card hides Documents count for non-moderators
- Dashboard: document list section completely hidden for non-moderators
- Documents API not called for non-moderators (saves a request)

Timer improvements:
- Custom timer input on ModeSelectScreen — set any number of minutes
  for exam mode (overrides the quiz default, or add time to untimed quizzes)
- timeLeft saved to Redis progress every 1.5s (alongside answers, mode, voice)
- On resume: timer restarts from the saved remaining time
- Timer auto-submits when it reaches 0 (already worked, now also works on resume)
- Progress bar shows remaining time relative to what was left at resume

Nextcloud settings — server-side storage:
- GET /auth/me/settings — load user settings from Redis
- PUT /auth/me/settings — save settings to Redis (linked to user_id)
- SettingsPage loads Nextcloud config from server on mount
- Saves to BOTH server (cross-browser persistent) and localStorage (for UploadPage)
- Clear function wipes both server and localStorage
- Switching browsers and logging in restores Nextcloud settings

Also fixed: saved.attemptId → saved.attempt_id in ModeSelectScreen resume check

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:39:43 +02:00
Daniel
b859d441eb Add extraction modes; fix resume; hide Nextcloud/Upload for users; delete PREP 2013 questions
Extraction modes (no restart needed — code ready for next Celery deploy):
- New QuizCreate.extraction_mode field: standard|questions_only|two_step|regex|ai_decide
- extraction_modes.py: independent implementations that don't touch standard path
  - questions_only: extract Q+options, correct_answer="PENDING" for manual fill
  - two_step: separate answer key section scan + phase1/2/3 matching
  - regex: AI detects answer pattern, generates regex, applies to full doc
  - ai_decide: AI reads samples from start+end and picks strategy
- DocumentDetailPage: Extraction Mode dropdown with description per mode
- quiz_tasks.py: routes to correct mode, standard path completely unchanged

Database:
- Deleted 11 orphaned questions from PREP 2013 extraction (quiz 12 was already deleted)
- 268 questions remaining (all PREP 2012)

UI fixes:
- Nextcloud section in Settings now only shown to moderators/admins
  (regular users can't upload PDFs so they don't need Nextcloud)
- Upload PDF already hidden in navbar for non-moderators (confirmed correct)
- Resume quiz: now async — study mode quiz data loaded BEFORE showing quiz
  so correct_answer is available immediately for feedback
- Resume saves and restores voice selection
- voice field added to ProgressSave schema and Redis storage
- Progress save dependency includes selectedVoice

Attempts:
- POST /attempts/start: reuses existing incomplete attempt by default (fresh=false)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 12:43:24 +02:00
Daniel
45add79db3 Fix quiz delete (500 error), add trash bin, quiz resume, hide/publish, remove JS dialogs
Critical bug fix:
- DELETE /quizzes/{id} was returning 500 due to {\"quiz_id\": None} update using
  the DB column name instead of Python attribute name (source_quiz_id).
  Fixed with {QuestionModel.source_quiz_id: None, synchronize_session=False}

Extraction fix:
- Two-phase extraction was incorrectly triggering for PREP 2012/2014 format
  documents that have 'Preferred Response:' in their explanation text.
  Fix: check for inline 'Correct Answer:' first — if found, always use standard
  extraction regardless of 'Preferred Response:' appearing elsewhere.

Quiz trash bin:
- DELETE /quizzes/{id} now soft-deletes (sets deleted_at)
- GET /quizzes/trash — list deleted quizzes (moderator)
- PATCH /quizzes/{id}/restore — restore from trash
- DELETE /quizzes/{id}/permanent — permanent delete (must be in trash first)
- TrashPage.jsx — accessible via Settings → Admin → Trash

Hide/publish quizzes:
- is_published column on quizzes (1=visible, 0=hidden)
- PATCH /quizzes/{id}/publish?published=false — hide from regular users
- Moderators see all quizzes; regular users only see published
- 👁/🙈 toggle button per quiz card (moderators only)

Quiz progress resume (cross-browser via Redis):
- POST /attempts/progress — save {answers, current_idx, mode} to Redis (7 days)
- GET /attempts/progress?quiz_id=N — retrieve saved progress
- DELETE /attempts/progress/{quiz_id} — clear on submit
- QuizPage auto-saves to Redis every 1.5s (debounced) while in progress
- ModeSelectScreen loads saved progress from server, shows Resume button
- Works across browsers, devices, and after logout

Delete attempt:
- DELETE /attempts/{id} — user can delete own attempt + clears reminders for that quiz

ConfirmButton component:
- Replaces all window.confirm() / window.prompt() across the app
- Double-click pattern: first click shows [Confirm] [Cancel] inline
- Applied to: QuizzesPage, DocumentDetailPage, QuizEditPage, QuestionBankPage

Category delete (QuestionBankPage):
- window.prompt() replaced with inline modal dialog with select dropdown
- User chooses where to move questions before deletion

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 04:51:41 +02:00
Daniel
a5021a3241 UI overhaul, bug fixes, section delete, category at extraction
Themes / CSS:
- Warm Brown theme: parchment cream background, dark brown navbar, Georgia serif font
  beautiful and easy on eyes for reading medical content
- Default theme: slightly warmer bg, softer card shadows, 1200px container (less boxy)
- Navbar: sticky, height-fixed, overflow-x scroll on mobile (no wrapping), smaller text on mobile
- Mobile container: 14px padding for more space

Bug fixes:
- Login page: axios 401 interceptor no longer triggers window.location.href for auth endpoints
  (was causing immediate page reload before error message was visible)
- Dashboard quizzes stat now shows distinct quizzes attempted (not created by user)
- PostgreSQL email collation: ALTER users.email to COLLATE "C" in migration
  (prevents B-tree index corruption with en_US.utf8 locale causing user lookups to fail)
- get_current_user: verifies email on ALL protected endpoints, returns 403 with X-Unverified header
  Frontend auto-logs out and redirects to /login where resend option is shown

DocumentDetailPage:
- Delete Section button with confirmation on each section row
- Create question category inline from dropdown (+ New button → prompt)
- Question Bank Category dropdown now shows question count

Dashboard:
- total_quizzes stat counts distinct attempted quizzes (not user-created quizzes)

Backend:
- DELETE /documents/{id}/sections/{section_id} endpoint added

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 01:12:19 +02:00
Daniel
9af63e67b5 Major: categories, question bank, security fixes, mobile layout, UX improvements
Security:
- Nginx: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, CSP, Referrer-Policy, Permissions-Policy headers
- Redis-backed login rate limiting (survives container restarts)
- Admin litellm/models endpoint: api_key moved from GET query param to POST body
- Nextcloud credentials moved from localStorage to sessionStorage (cleared on tab close)

UX / Layout:
- Login: unverified users see inline "Resend verification email" option
- QuizPage mobile: TTS Listen button on its own row below question text
- QuizPage mobile: Voice selector on its own row in header card (not squashed with timer)
- QuizEditPage: scroll position preserved after saving a question edit

Quiz Categories:
- New QuizCategory model + quiz_categories table
- category_id column added to quizzes table
- GET/POST/DELETE /api/categories endpoints
- Quizzes grouped by category in QuizzesPage; moderators can assign via 🏷 menu
- Uncategorized section shown when categories exist

Question Bank:
- GET /api/questions/bank — search all questions across quizzes
- POST /api/questions/from-bank — create new quiz from selected questions (copies, originals untouched)
- QuestionBankPage: search, checkbox select, study modal, create quiz form
- "Question Bank" link added to Navbar

Search:
- "View all N questions →" button expands to full question list
- Each question has a Study button opening in-place modal with study mode
- Summary view shows 2 questions per quiz with Study button

Extraction prompt:
- Stronger emphasis on correct_answer field with step-by-step letter → full text example
- Explicit instruction never to store just the letter

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 20:08:05 +02:00
Daniel
84f5927e5d Add Nextcloud integration, TTS key fix, Settings/Upload improvements
- Nextcloud backend proxy (WebDAV PROPFIND/GET) avoiding CORS
  - /api/nextcloud/test — credential validation
  - /api/nextcloud/files — browse folders and list PDFs
  - /api/nextcloud/download — stream file for upload
- UploadPage: Local/Nextcloud tabs, inline Nextcloud file browser
- SettingsPage: Test Connection button with live feedback
- AWS Polly Joanna (neural) set as default TTS voice
- TTS stops on question navigation AND answer selection (key prop fix)
- Dashboard: Good morning/afternoon/evening greeting with first name
- Favicon: 🩺 emoji SVG
- Navbar: cleaned up — username removed, ⚙ Settings link added
- manage.py CLI: reset-password, list-users, reembed commands

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:17:16 +02:00
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