Complete guide for setting up and testing the two new features: ingredient autocomplete and GLM-OCR receipt scanning. A developer joining the project or picking up after a gap should be able to follow this from scratch.
Two independent features that share a common data foundation:
-
Ingredient Autocomplete — The Pantry page now has a Google-style dropdown that suggests TheMealDB canonical ingredient names as you type. Ensures consistent naming for recipe matching.
- Backend:
GET /ingredientsendpoint - Frontend:
AutocompleteInputcomponent +useMealDbIngredientshook
- Backend:
-
GLM-OCR Receipt Scanning — Receipt image upload now uses GLM-OCR (Zhipu MaaS) instead of Google Cloud Vision. Single-pass: the model reads the receipt and normalizes ingredient names to TheMealDB canonical form in one API call. Replaces the old 3-step pipeline (OCR → regex parse → fuzzy normalize).
Both features use server/app/services/ingredient_loader.py, which fetches and caches the TheMealDB ingredient list. One cached list, two consumers.
┌─────────────────────────────────────────────────────────┐
│ GatorChef Backend │
│ │
│ GET /ingredients ──→ ingredient_loader ──→ TheMealDB │
│ │ │
│ POST /upload/receipt ──→ glm_ocr_service │
│ │ (uses ingredient list │
│ │ as prompt context) │
│ └──→ Zhipu MaaS (GLM-OCR) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ GatorChef Frontend │
│ │
│ Pantry page │
│ ├── useMealDbIngredients() ──→ GET /ingredients │
│ └── AutocompleteInput (Radix Popover) │
│ ├── Add BottomSheet │
│ └── Edit BottomSheet │
└─────────────────────────────────────────────────────────┘
server/
app/
routes/
ingredients.py ← GET /ingredients endpoint
upload.py ← POST /upload/receipt (rewritten)
services/
ingredient_loader.py ← shared TheMealDB cache
glm_ocr_service.py ← GLM-OCR extraction logic
glm_ocr_config.yaml ← GITIGNORED — your API key goes here
client/
src/
components/
AutocompleteInput.tsx ← Radix Popover combobox component
hooks/
useMealDbIngredients.ts ← fetch + cache hook
pages/
Pantry.tsx ← modified to use AutocompleteInput
- Python 3.11+
- Node.js 18+
- Firebase service account JSON (for Firestore auth) — in
secret/folder or viaGOOGLE_APPLICATION_CREDENTIALSenv var - Zhipu API key (free from https://open.bigmodel.cn) — required only for GLM-OCR receipt scanning
git clone <repo-url>
cd repo-b
# Backend
cd server
pip install -r requirements.txt
cd ..
# Frontend
cd client
npm install
cd ..Create server/app/services/glm_ocr_config.yaml (this file is gitignored):
pipeline:
maas:
enabled: true
api_key: "your-zhipu-api-key"Get your key at https://open.bigmodel.cn → API Keys.
Alternatively, point the env var GLM_OCR_CONFIG to a config file elsewhere:
export GLM_OCR_CONFIG=/path/to/my_config.yamlThe frontend needs Firebase credentials to handle user login. Create client/.env by copying .env.example:
VITE_API_BASE_URL=http://127.0.0.1:8000
VITE_FIREBASE_API_KEY=
VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=
ALL OF THESE MAP firebaseConfig.apiKey → VITE_FIREBASE_API_KEY firebaseConfig.authDomain → VITE_FIREBASE_AUTH_DOMAIN firebaseConfig.projectId → VITE_FIREBASE_PROJECT_ID firebaseConfig.storageBucket → VITE_FIREBASE_STORAGE_BUCKET firebaseConfig.messagingSenderId → VITE_FIREBASE_MESSAGING_SENDER_ID firebaseConfig.appId → VITE_FIREBASE_APP_ID
Where to get these values: Firebase Console → your project → Project Settings (gear icon) → General → scroll to Your apps → select the web app → SDK setup and configuration → copy the firebaseConfig object. Each field maps directly to a VITE_FIREBASE_* var.
These values are NOT secret. Unlike the backend service account JSON, these are public client-side identifiers — safe to put in
.env. Firebase security is enforced by Auth rules and Firestore rules, not by hiding these values.
After creating client/.env, restart Vite (CTRL+C then npm run dev) — it only reads .env on startup.
The backend needs a Firebase service account JSON to verify auth tokens and write to Firestore. This IS a private key — keep it out of git.
Place the JSON file in the project secret/ folder (gatorchef/secret/), or set the env var.
If you want to use a backend .env file, create server/.env and add:
GOOGLE_APPLICATION_CREDENTIALS=C:\Users\alejm\Downloads\gatorchef-c7c69-firebase-adminsdk-fbsvc-791b86a30d.jsonThen start the backend from the server folder.
Alternatively, set the env var directly:
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.jsonWhere to get this: Firebase Console → Project Settings → Service accounts tab → Generate new private key → download the JSON.
# Terminal 1 — Backend
cd server
uvicorn app.main:app --reload --port 8000
"NOTE:
If that still fails after installing, use the module form which always works:
python -m uvicorn app.main:app --reload --port 8000"
# Terminal 2 — Frontend
cd client
npm run dev| Service | URL |
|---|---|
| Frontend | http://localhost:8080 |
| Backend | http://localhost:8000 |
| API docs (Swagger) | http://localhost:8000/docs |
- Open the app at http://localhost:8080 and log in.
- Navigate to Pantry.
- Tap the "+" button to open Add Ingredient.
- Start typing
chick— a dropdown appears with Chicken, Chicken Breast, Chicken Thighs, etc. - Use arrow keys or click to select an item.
- Press Enter or tap "Add to Pantry".
- Verify in Firestore: the item name matches the exact TheMealDB casing.
curl http://localhost:8000/ingredients
# Returns: ["Chicken", "Chicken Breast", "Garlic", ...]
# ~600 items, no auth required- Check the browser console for a "Failed to load ingredient suggestions" warning.
- Verify
GET http://localhost:8000/ingredientsreturns a JSON array. - Confirm
ingredient_loadercan reach TheMealDB (check server logs for network errors).
- Ensure
glm_ocr_config.yamlexists and has a valid API key. - Log in to the app.
- Use the scan button (barcode icon) on the Pantry page.
- Upload a receipt image (JPEG or PNG, max 10 MB).
- Verify that items appear in the pantry with canonical TheMealDB names.
Get a Firebase ID token first, then:
curl -X POST http://localhost:8000/upload/receipt \
-H "Authorization: Bearer YOUR_FIREBASE_TOKEN" \
-F "file=@/path/to/receipt.jpg"Expected response:
{
"parsed_items": [
{ "id": "pantry_abc123", "name": "Chicken Breast", "category": "Produce", "expiry": "unknown" }
]
}| Issue | Cause | Fix |
|---|---|---|
Missing Firebase env var for apiKey |
client/.env doesn't exist |
Copy .env.example → .env, fill in Firebase config from Firebase Console, restart Vite |
422 Unprocessable Entity on upload |
python-multipart not installed |
pip install python-multipart |
503 glmocr package is not installed |
glmocr not installed |
pip install glmocr |
| Dropdown never appears | /ingredients returning an error |
Check server logs, verify TheMealDB is reachable |
502 Bad Gateway from /ingredients |
TheMealDB temporarily unreachable | Transient — will resolve; app falls back to plain text input |
| GLM-OCR returns an empty list | API key missing or invalid | Check glm_ocr_config.yaml has the correct key |
RuntimeError: No Firebase credentials |
Missing service account | Set GOOGLE_APPLICATION_CREDENTIALS or place JSON in secret/ |
Why module-level cache (not Redis or a database)? The ingredient list is ~600 static names that rarely change and are needed on every request. A module-level singleton is the simplest correct solution at this scale.
Why no auth on GET /ingredients?
The TheMealDB ingredient list is public data. Requiring auth would add latency with no security benefit.
Why Radix Popover (not a custom dropdown)? It is already installed, handles portal rendering and z-index stacking correctly inside Framer Motion modals (BottomSheet), and provides correct ARIA attributes out of the box.
Why GLM-OCR instead of improving the regex normalizer? The hardcoded abbreviation list (~52 entries) would never cover real-world receipt variation. GLM-OCR understands context, handles multilingual text, and requires zero ongoing maintenance.