--- 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 model `gpt-5.4`) - **Deploy:** Root [`Dockerfile`](Dockerfile) builds 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/ └── # 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: ```bash 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: ```bash 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). 1. **Create the bucket** (one-time, only if it doesn't already exist): ```bash hf buckets create neongeckocom/AdvisoryBuilderWorkshop-storage --private ``` 2. **Attach 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: ```bash hf spaces volumes add neongeckocom/AdvisoryBuilderWorkshop \ --bucket hf://buckets/neongeckocom/AdvisoryBuilderWorkshop-storage:/data ``` 3. **Match the mount path with `DATA_DIR`.** The Docker image bakes `DATA_DIR=/data` so it works out of the box. If you choose a different mount path, override the env var in **Settings → Repository secrets** (or **Variables**) so `DATA_DIR` matches. 4. **Set the rest of the secrets** in **Settings → Repository secrets** at minimum: - `OPENAI_API_KEY` — required for chat, wizards, and transcription - `JWT_SECRET_KEY` — any long random string (signs auth tokens) - `DEFAULT_LLM_MODEL` — optional; defaults to `gpt-5.4` - `DATA_DIR` — only if your bucket mount path is **not** `/data` 5. Optional secrets: - `GOOGLE_SERVICE_ACCOUNT_JSON`, `GOOGLE_SPREADSHEET_ID` — Google Sheets submission pipeline - `ADMIN_NOTIFY_EMAIL`, `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_FROM` — email on submit 6. **Push the new image** (or wait for the GitHub-connected Space to rebuild). On startup the app creates `cu_student_helper.db` and `user_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/health` continues to return 200 so the Space stays "Running". ### Create the Space from the Hub CLI (optional) ```bash 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`): ```powershell .\scripts\dev-docker.ps1 ``` Or without the script (set `ENV_FILE_PATH` to your shared env file first): ```bash 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: ```powershell .\scripts\dev-docker.ps1 ``` ### Option B — Processes on your machine (no containers) 1. **Backend** — `.\scripts\start-local-backend.ps1` loads `~/.secrets/shared.env` and starts uvicorn with reload. Or manually: ```powershell $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 8000 ``` On first startup the app creates `./data/cu_student_helper.db` and `./data/user_uploads/` next to the project (override with `DATA_DIR=…` in shared.env). 2. **Frontend** — second terminal: ```bash cd frontend npm install npm run dev ``` Open http://localhost:5173 — Vite proxies `/api` to `http://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](.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/` 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`](Dockerfile) (includes `npm run build` + FastAPI serving static UI on port **7860**): ```bash 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 ```bash 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.