title: AI Expert Advisory Builder's Workshop
emoji: 🎓
colorFrom: indigo
colorTo: blue
sdk: docker
pinned: false
app_port: 7860
CU-Student-AIProject-Helper
Recruitment and tooling site for non-technical CU students joining a Collaborative Conversational AI (CCAI) advisory-panel project: long-form landing page, optional accounts, persona/advisor forms and interactive GPT chats (with mic input and read-aloud), and submission hooks for Google Sheets / email.
Stack
- Frontend: React (Vite + TypeScript), React Router
- Backend: FastAPI, SQLite (via
aiosqlite) on a Hugging Face Storage Bucket mount, OpenAI API (OPENAI_API_KEY, default modelgpt-5.4) - Deploy: Root
Dockerfilebuilds a single image (API + static SPA) for Hugging Face Spaces or similar
Server-side user data (SQLite on a Hugging Face Storage Bucket)
All durable user data lives on a single Hugging Face Storage Bucket mounted into the Space at ${DATA_DIR} (default /data). One bucket holds both the SQLite database and the raw bytes of any uploaded RAG documents, so a single hf buckets sync salvages the entire user state — no MongoDB, no Atlas, no third-party service.
${DATA_DIR}/
├── cu_student_helper.db # SQLite: users, profiles, artifacts, document metadata
└── user_uploads/
└── <oid_hex> # one file per upload, named by the doc's ObjectId
When users sign in, the browser merges local workshop data with the server and keeps it in sync.
| Store (SQLite table) | Purpose |
|---|---|
users |
Account records (email, bcrypt password hash, name). UNIQUE constraint on email. |
user_profiles |
persona_draft / advisor_draft (in-progress wizards) and creations (saved Persona + Advisor Panel records, same shape as local creations_v1). |
generated_artifacts |
Generated prompts or panel packs; each row references a workshop entity via source_type (persona | advisor_panel) and source_client_id (the UUID used in the UI). |
user_documents |
Metadata for uploaded files (filename, size_bytes, owner_type + owner_client_id, plus the gridfs_id ObjectId that names the bytes file under user_uploads/). |
Internally backend/app/db.py exposes a tiny Motor-shaped facade (find_one, find().sort(), insert_one, update_one, delete_one, delete_many, find_one_and_delete, an aggregation pipeline, plus an open_download_stream / upload_from_stream "bucket" for binaries) so the routers and services were left almost untouched.
Relevant HTTP routes: GET/PUT /api/users/me/profile, GET/POST/DELETE /api/users/me/generated-artifacts, GET/POST/DELETE /api/users/me/documents (+ GET .../documents/{id}/file to download).
Backing up / salvaging user data before any redeploy
Pushing a new Docker image rebuilds the container but leaves the Storage Bucket untouched, so existing accounts and uploads survive automatically. Even so, take a snapshot before any risky change with the Hugging Face CLI:
pip install --upgrade huggingface_hub
hf auth login # or: export HF_TOKEN=hf_xxx
hf buckets sync hf://buckets/neongeckocom/AdvisoryBuilderWorkshop-storage ./backup
You now have ./backup/cu_student_helper.db (open with sqlite3 ./backup/cu_student_helper.db ".tables" to inspect users, profiles, artifacts, document metadata) plus ./backup/user_uploads/ containing the raw bytes of every uploaded file. To restore the same state into a new (or the same) bucket:
hf buckets sync ./backup hf://buckets/neongeckocom/AdvisoryBuilderWorkshop-storage
(The hf command — formerly huggingface-cli — ships with huggingface_hub. See the Storage Buckets docs for credentials and per-bucket ACL options.)
Hugging Face Spaces
A private HF Storage Bucket — neongeckocom/AdvisoryBuilderWorkshop-storage — is the durable home for users, profiles, generated artifacts, document metadata, and the bytes of every uploaded RAG file. The Space mounts it at /data (a config knob, not a hard-coded path — see step 3).
Create the bucket (one-time, only if it doesn't already exist):
hf buckets create neongeckocom/AdvisoryBuilderWorkshop-storage --privateAttach the bucket to the Space at
/data. Either via the Hub UI (Space → Settings → Storage Buckets → Add bucket, mount path/data, read+write) or with the CLI:hf spaces volumes add neongeckocom/AdvisoryBuilderWorkshop \ --bucket hf://buckets/neongeckocom/AdvisoryBuilderWorkshop-storage:/dataMatch the mount path with
DATA_DIR. The Docker image bakesDATA_DIR=/dataso it works out of the box. If you choose a different mount path, override the env var in Settings → Repository secrets (or Variables) soDATA_DIRmatches.Set the rest of the secrets in Settings → Repository secrets at minimum:
OPENAI_API_KEY— required for chat, wizards, and transcriptionJWT_SECRET_KEY— any long random string (signs auth tokens)DEFAULT_LLM_MODEL— optional; defaults togpt-5.4DATA_DIR— only if your bucket mount path is not/data
Optional secrets:
GOOGLE_SERVICE_ACCOUNT_JSON,GOOGLE_SPREADSHEET_ID— Google Sheets submission pipelineADMIN_NOTIFY_EMAIL,SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASSWORD,SMTP_FROM— email on submit
Push the new image (or wait for the GitHub-connected Space to rebuild). On startup the app creates
cu_student_helper.dbanduser_uploads/on the bucket if they don't yet exist.
Secrets are injected as environment variables at runtime; do not commit secrets to git. Locally, use ~/.secrets/shared.env (see below). CORS_ORIGINS=* is baked into the Docker image for HF.
No Storage Bucket attached? The first request that touches the database returns HTTP 503 with the message "Account sign-in is unavailable right now (database). … make sure the Hugging Face Storage Bucket is attached and DATA_DIR points at the mount path (default /data)." Health check
/api/healthcontinues to return 200 so the Space stays "Running".
Create the Space from the Hub CLI (optional)
pip install huggingface_hub
huggingface-cli repo create neongeckocom/AdvisoryBuilderWorkshop --type space --space-sdk docker --public --exist-ok
Then add the Space as a git remote and push, or use Settings → Connect to GitHub.
Local development (hot reload)
Persistence is just a SQLite file plus a directory of uploads under ./data/, so no extra database service is required.
Option A — Docker Compose (API reload + Vite HMR)
From the repo root, with ~/.secrets/shared.env configured (at least OPENAI_API_KEY, JWT_SECRET_KEY):
.\scripts\dev-docker.ps1
Or without the script (set ENV_FILE_PATH to your shared env file first):
docker compose up --build
| Service | URL | Notes |
|---|---|---|
| UI (Vite) | http://localhost:5173 | Hot reload (HMR); proxies /api → backend |
| API | http://localhost:8000 | uvicorn --reload; OpenAPI at http://localhost:8000/docs |
| Persistence | ./data/ (mounted to /app/data) |
cu_student_helper.db + user_uploads/ |
Compose sets SERVE_FRONTEND_STATIC=false so FastAPI does not serve frontend/dist — use the Vite dev server. The frontend service uses polling so file watches work on Docker Desktop (Windows/macOS).
PowerShell helper:
.\scripts\dev-docker.ps1
Option B — Processes on your machine (no containers)
Backend —
.\scripts\start-local-backend.ps1loads~/.secrets/shared.envand starts uvicorn with reload. Or manually:$env:ENV_FILE_PATH = "$HOME\.secrets\shared.env" cd backend python -m venv .venv .venv\Scripts\activate pip install -r requirements.txt uvicorn app.main:app --reload --host 0.0.0.0 --port 8000On first startup the app creates
./data/cu_student_helper.dband./data/user_uploads/next to the project (override withDATA_DIR=…in shared.env).Frontend — second terminal:
cd frontend npm install npm run devOpen http://localhost:5173 — Vite proxies
/apitohttp://127.0.0.1:8000.
Without mounting static, the API exposes OpenAPI at /docs on port 8000.
Configuration
Secrets live in ~/.secrets/shared.env (Windows: C:\Users\dream\.secrets\shared.env), shared across your projects. See .env.example for the variable list.
| Variable | Purpose |
|---|---|
OPENAI_API_KEY |
Required for chat, wizards, and transcription |
DEFAULT_LLM_MODEL |
Default LLM everywhere (default gpt-5.4) |
TRANSCRIBE_MODEL |
OpenAI speech-to-text model (default whisper-1) |
DATA_DIR |
Persistence root: cu_student_helper.db + user_uploads/<oid> live here. Default ./data locally; set to /data (or wherever the HF Storage Bucket is mounted) on Spaces |
JWT_SECRET_KEY |
Sign auth tokens (use a long random value in production) |
CORS_ORIGINS |
Frontend origins (dev: http://localhost:5173) |
SERVE_FRONTEND_STATIC |
true (default): serve frontend/dist from FastAPI. false: use Vite dev server only (hot reload). |
GOOGLE_* / SMTP |
Optional submission pipeline (see app/routers/submit.py) |
Speech: Mic input is sent to POST /api/transcribe (OpenAI Whisper). "Read aloud" uses the browser Web Speech API (no extra key).
Production image (HF / single container)
Build the root Dockerfile (includes npm run build + FastAPI serving static UI on port 7860):
docker build -t cu-student-helper .
docker run --rm -p 7860:7860 --env-file "$HOME/.secrets/shared.env" cu-student-helper
Open http://localhost:7860 — API docs are disabled when static files are mounted (see app/main.py).
GitHub
git remote add origin https://github.com/YOUR_ORG/CU-Student-AIProject-Helper.git
git add -A && git commit -m "Initial import"
git push -u origin main
License
Project scaffold for educational use; adapt as needed for your program.