From 3de7c5eed5ba262abf0d746211e33800db6d66df Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Fri, 8 May 2026 03:24:36 -0700 Subject: Add recipe suggestions, chat tab, and major workflow improvements - Replace 7-day grid menu with browsable recipe suggestion cards (swap, remove, make this) - Add Chat tab: conversational AI with full pantry/menu/grocery context - Grocery list works without a menu (pantry-only mode) - Individual grocery checkboxes auto-add items to pantry - Swap modal with optional preference input - User notes textarea on menu and grocery generation - Clear button for menu and grocery list - LLM notes/summary displayed after generation, persisted to DB - Favicon linked in HTML - Category dropdown styled for dark theme - System prompt configurable via SYSTEM_PROMPT in .env - Fix startup error (ollama_timeout default), DB migration for menu_plans.notes - Simplify: batch N+1 queries, extract _current_monday(), merge chat sync fns, asyncio.get_running_loop(), fix currentGroceryId bug, cap chat history Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'CLAUDE.md') diff --git a/CLAUDE.md b/CLAUDE.md index 5da8209..b2f68b7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ uvicorn main:app --reload --port 8080 open http://localhost:8000/docs ``` -There are no tests. The SQLite database (`chef.db`) is auto-created on first startup via `Base.metadata.create_all()` in the lifespan handler — no migrations needed. +There are no tests. The SQLite database (`chef.db`) is auto-created on first startup via `Base.metadata.create_all()` in the lifespan handler — no migrations needed for new installs. The lifespan handler also runs a one-time `ALTER TABLE menu_plans ADD COLUMN notes TEXT` migration for existing databases that predate that column. ## Architecture @@ -27,17 +27,31 @@ Single-process FastAPI app. All state lives in SQLite. Ollama runs as a separate **Request flow for AI endpoints:** 1. Router calls `pantry_service.build_pantry_context(db)` to snapshot the current pantry + recent meal history into a plain dict 2. That dict is passed to an `ai_service` function which builds a prompt and calls Ollama synchronously via `run_in_executor` (keeps the async event loop unblocked during the 15–120s generation) -3. Ollama always returns JSON (`format="json"` is set on every call); the response is `json.loads()`'d and returned directly — no intermediate parsing layer +3. Structured AI endpoints (menu, grocery, swap) use `format="json"` and return parsed dicts. The chat endpoint uses plain text — no JSON parsing. 4. The router saves AI output into the DB and returns both the DB record and the raw AI response to the frontend **Route registration order matters:** `app.include_router(...)` calls happen before `app.mount("/", StaticFiles(...))`. Reversing this breaks all API routes — the static catch-all intercepts them first. **JSON columns:** `recipes.ingredients`, `menu_plans.plan`, and `grocery_lists.items` are stored as JSON strings in `Text` columns. Always `json.dumps()` before saving and `json.loads()` before using. -**`menu_plans.plan` structure:** `{"monday": {"breakfast": , "lunch": , "dinner": }, ...}`. The `POST /api/menus/generate` response also includes `week_plan` with full recipe details (name, ingredients, instructions) — the frontend uses this for display without a second fetch. On page reload only the plan with IDs is available, so `GET /api/recipes` is needed to look up names. +**`menu_plans.plan` structure:** A flat JSON array of recipe IDs — `[1, 4, 7, ...]`. The `POST /api/menus/generate` response also includes `recipes` with full recipe details (name, ingredients, instructions) — the frontend uses this for immediate display. On page reload only the plan IDs are available, so `GET /api/recipes` is needed to hydrate names and details. **`meal_ingredients.ingredient_name` is denormalized** — it stores a copy of the name string rather than a FK to `ingredients`. This preserves meal history when pantry items are deleted. **`grocery.py` exports two routers:** `router` (prefix `/api/grocery`) and `ai_router` (prefix `/api/ai`). Both are included in `main.py`. -**Config** is loaded from `.env` via `pydantic-settings`. Key vars: `OLLAMA_HOST`, `MODEL_NAME`, `DATABASE_URL`. Change `MODEL_NAME` to switch Ollama models (e.g. `mistral`, `llama3.1`). +**`_current_monday()` helper** is defined in both `routers/menus.py` and `routers/grocery.py`. It returns the ISO date of the current week's Monday and is used by every endpoint that scopes data to the current week. + +**`ai_service._chat_sync(messages, json_format=True)`** is the single sync Ollama call. Pass `json_format=False` for the chat endpoint which returns plain text. All async public functions call `run_in_executor` wrapping this to avoid blocking the event loop. + +**Config** is loaded from `.env` via `pydantic-settings`. Key vars: + +| Var | Default | Purpose | +|-----|---------|---------| +| `OLLAMA_HOST` | `http://localhost:11434` | Ollama server URL | +| `MODEL_NAME` | — | Which Ollama model to use (e.g. `llama3.1`, `mistral`) | +| `DATABASE_URL` | `sqlite:///./chef.db` | SQLAlchemy DB URL | +| `OLLAMA_TIMEOUT` | `120` | Seconds before Ollama call times out | +| `SYSTEM_PROMPT` | *(see config.py)* | System prompt prepended to all structured AI calls | + +Change `MODEL_NAME` to switch models. Edit `SYSTEM_PROMPT` to change the AI's persona and priorities for menu/grocery/swap generation (does not affect the chat tab's system prompt, which is built dynamically with kitchen context). -- cgit v1.3-2-g0d8e