**Caching.** `/uploads` answered `private, no-store` for everything, so a page of forty thumbnails re-fetched forty images every time it was drawn. A derivative may now be kept for a week by the browser that asked for it — `private`, never a shared cache, because a shared cache in front of access-controlled images is how one learner is served another's figure. It is safe to keep because it cannot change: `thumbs/256/<key>` is made once from an immutable original. Originals still say no-store. **The embedding model is env-only.** Every vector in the database came from it, and vectors from different models are not comparable — change it and search returns noise until 3,000 questions, 334 articles and every card have been re-embedded. The settings page now shows it as text with Test and Regenerate beside it, and the API refuses a change rather than ignoring one, naming `LITELLM_EMBEDDING_MODEL` in the refusal. **The figure audit retries and gives up.** Its second run met a proxy outage and reported all 327 figures unreadable, having changed nothing but spent the time. Three tries each with backoff now, and it aborts after twelve consecutive failures: a run that says "everything is unreadable" has told you nothing. **`.env.example` is complete.** It listed 23 of the 53 settings; it now lists all of them, grouped, each with the default it falls back to and — where it matters — what happens if it is wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
107 lines
4.9 KiB
Text
107 lines
4.9 KiB
Text
# Every setting the backend reads, with the default it falls back to. Blank
|
|
# means "off" or "not configured" throughout — nothing here has a secret in it,
|
|
# and nothing here is required except the database, the secret key and a model.
|
|
|
|
# ── Database and sessions ──────────────────────────────────────────────
|
|
DATABASE_URL=postgresql://quiz:quiz@postgres:5432/quiz
|
|
SECRET_KEY=change-me-to-a-random-secret-key-in-production
|
|
ALGORITHM=HS256
|
|
ACCESS_TOKEN_EXPIRE_MINUTES=1440
|
|
|
|
# Optional bootstrap admin. Leave blank to use first-user-becomes-admin.
|
|
DEFAULT_ADMIN_EMAIL=
|
|
DEFAULT_ADMIN_PASSWORD=
|
|
|
|
# Redis: Celery's broker, the rate limiters, job progress and site settings.
|
|
REDIS_URL=redis://redis:6379/0
|
|
|
|
# ── Models ─────────────────────────────────────────────────────────────
|
|
# Everything goes through one LiteLLM proxy. Per-job models (extraction, the
|
|
# tutor, TTS, and so on) are chosen by an administrator in Settings → AI
|
|
# models; this is the fallback when a job has no model of its own.
|
|
LITELLM_MODEL=gpt-4o-mini
|
|
LITELLM_API_KEY=your-api-key-here
|
|
LITELLM_API_BASE=
|
|
|
|
# The embedding model, and the one model setting that is NOT editable in the
|
|
# interface. Every stored vector was produced by it, and vectors from different
|
|
# models are not comparable — change this and search returns noise until every
|
|
# question, article and card has been re-embedded. That is a deployment, so it
|
|
# lives here. `EMBEDDING_DIMENSIONS` must match what the model returns.
|
|
LITELLM_EMBEDDING_MODEL=
|
|
EMBEDDING_DIMENSIONS=1024
|
|
|
|
# The cross-encoder that reorders search results. Unset, unreachable or
|
|
# malformed and the fused ranking is returned untouched — never fewer results.
|
|
LITELLM_RERANK_MODEL=cohere-rerank-v4.0-pro
|
|
|
|
# Direct provider keys, used only where the proxy does not carry the service.
|
|
OPENAI_API_KEY=
|
|
ELEVENLABS_API_KEY=
|
|
GOOGLE_TTS_API_KEY=
|
|
AWS_ACCESS_KEY_ID=
|
|
AWS_SECRET_ACCESS_KEY=
|
|
AWS_REGION=us-east-1
|
|
AWS_BEDROCK_REGION=us-east-1
|
|
|
|
# Self-hosted speech, for TTS and dictation without leaving the machine.
|
|
LOCAL_SPEECH_GATEWAY_URL=http://local-speech-gateway:8110
|
|
|
|
# ── Where the site lives ───────────────────────────────────────────────
|
|
# Used in the links inside emails, so a wrong value sends people nowhere.
|
|
APP_URL=https://pedshub.com
|
|
LOG_LEVEL=INFO
|
|
|
|
# ── Storage ────────────────────────────────────────────────────────────
|
|
# `local` keeps uploads on the volume at UPLOAD_DIR; `s3` puts them in a
|
|
# bucket and reads them back through it. Thumbnails live beside the original
|
|
# either way.
|
|
STORAGE_BACKEND=local
|
|
UPLOAD_DIR=/app/uploads
|
|
MAX_UPLOAD_SIZE=524288000
|
|
S3_ENDPOINT_URL=http://minio:9000
|
|
S3_ACCESS_KEY=
|
|
S3_SECRET_KEY=
|
|
S3_BUCKET=pedshub-media
|
|
S3_REGION=us-east-1
|
|
|
|
# Page chunks for extraction context.
|
|
CHROMA_PERSIST_DIR=/app/chroma_data
|
|
|
|
# ── Email ──────────────────────────────────────────────────────────────
|
|
# With MAIL_USERNAME or MAIL_FROM blank, mail is logged instead of sent — which
|
|
# also means sign-in codes and verification links go nowhere.
|
|
MAIL_USERNAME=your-email@example.com
|
|
MAIL_PASSWORD=your-app-password
|
|
MAIL_FROM=your-email@example.com
|
|
MAIL_PORT=587
|
|
MAIL_SERVER=smtp.gmail.com
|
|
MAIL_STARTTLS=true
|
|
MAIL_SSL_TLS=false
|
|
# Where the contact form's messages land.
|
|
ADMIN_EMAIL=
|
|
|
|
# ── Bot protection ─────────────────────────────────────────────────────
|
|
# Cap, self-hosted beside the app. Leave the secret blank to disable the
|
|
# challenge; sign-up then accepts submissions without one.
|
|
CAP_SECRET_KEY=
|
|
CAP_SITE_KEY=
|
|
CAP_API_URL=http://cap:3000
|
|
|
|
# ── Single sign-on ─────────────────────────────────────────────────────
|
|
# Any OIDC provider. Blank means the site uses its own accounts only.
|
|
OIDC_PROVIDER_URL=
|
|
OIDC_CLIENT_ID=
|
|
OIDC_CLIENT_SECRET=
|
|
OIDC_SCOPES=openid email profile
|
|
OIDC_PROVIDER_NAME=SSO
|
|
|
|
# ── Live sessions ──────────────────────────────────────────────────────
|
|
BBB_SERVER_URL=
|
|
BBB_SECRET=
|
|
|
|
# ── Clinical corpus (read-only) ────────────────────────────────────────
|
|
# A separate Milvus collection the assistant may query but never write to.
|
|
CLINICAL_MILVUS_URI=
|
|
CLINICAL_MILVUS_TOKEN=
|
|
CLINICAL_MILVUS_COLLECTION=mcp_bge_m3_1024
|