diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-09 02:31:10 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-09 02:31:10 -0700 |
| commit | 360eadf78fb001e947f3850603152adc413bb3a8 (patch) | |
| tree | 2d67065890ce195bc2c0f2f6e430e86c241b2424 /routers/menus.py | |
| parent | aba03fd72df5729a86d21c6866761b43a8abad68 (diff) | |
Recipe detail page, menu revamp, and UX improvements
- Add recipe detail page (recipe.html) with full ingredients and instructions
- Simplify menu tab: cards show name + description only, click through for full recipe
- Add description field to Recipe model with DB migration
- Add AI-generated descriptions to menu, swap, and import prompts
- Add single dish by description (POST /api/menus/current/recipes)
- Add grocery item delete without pantry add (DELETE /api/grocery/{id}/items)
- Persist grocery checked state server-side (PATCH /api/grocery/{id}/check-item)
- Hash-based tab routing — refresh stays on current tab
- Logo branding in header and favicon
- Dark theme fixes: URL/text inputs, amber accent, muted danger/warning colors
- Markdown rendering in chat (bold, italic, code blocks, lists, headers)
- Fix instruction step splitting for inline-numbered steps (1. 2. 3.)
- Import recipe from URL with JSON-LD structured data + AI fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'routers/menus.py')
| -rw-r--r-- | routers/menus.py | 68 |
1 files changed, 67 insertions, 1 deletions
diff --git a/routers/menus.py b/routers/menus.py index 9ec1d1d..db56d73 100644 --- a/routers/menus.py +++ b/routers/menus.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta, date import json from database import get_db -from schemas import MenuPlanRead +from schemas import MenuPlanRead, RecipeRead from models import MenuPlan, Recipe from services import pantry_service, ai_service from config import settings @@ -78,6 +78,7 @@ async def generate_menu(request: dict = None, db: Session = Depends(get_db)): estimated_time_minutes=recipe_data.get("time_minutes", 30), servings=recipe_data.get("serves", 2), source="ai", + description=recipe_data.get("description", ""), ) db.add(new_recipe) db.flush() @@ -234,6 +235,71 @@ async def swap_recipe_in_menu(recipe_id: int, request: dict = Body(default={}), } +@router.post("/current/recipes") +async def add_recipe_to_menu(request: dict = Body(default={}), db: Session = Depends(get_db)): + """Generate and add a single recipe by description.""" + description = (request.get("description") or "").strip() + if not description: + raise HTTPException(status_code=400, detail="Description is required") + + monday = _current_monday() + menu_plan = db.query(MenuPlan).filter(MenuPlan.week_start == monday).first() + + # Get existing recipe names to avoid duplicates + existing_names = [] + if menu_plan: + try: + plan_ids = json.loads(menu_plan.plan) + existing = db.query(Recipe).filter(Recipe.id.in_(plan_ids)).all() if plan_ids else [] + existing_names = [r.name for r in existing] + except Exception: + pass + + pantry_context = pantry_service.build_pantry_context(db) + + try: + result = await ai_service.generate_single_recipe(description, existing_names, pantry_context) + except ValueError as e: + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=503, detail=f"Ollama error: {str(e)}") + + recipe_data = result.get("recipe", {}) + if not recipe_data.get("name"): + raise HTTPException(status_code=503, detail="AI returned invalid recipe") + + new_recipe = Recipe( + name=recipe_data["name"], + description=recipe_data.get("description", ""), + meal_type=recipe_data.get("meal_type", "dinner"), + ingredients=json.dumps(recipe_data.get("ingredients", [])), + instructions=recipe_data.get("instructions", ""), + estimated_time_minutes=recipe_data.get("time_minutes"), + servings=recipe_data.get("serves", 2), + source="ai", + ) + db.add(new_recipe) + db.flush() + + if menu_plan: + try: + plan_ids = json.loads(menu_plan.plan) + except Exception: + plan_ids = [] + plan_ids.append(new_recipe.id) + menu_plan.plan = json.dumps(plan_ids) + else: + menu_plan = MenuPlan( + week_start=monday, + plan=json.dumps([new_recipe.id]), + ) + db.add(menu_plan) + + db.commit() + db.refresh(new_recipe) + return {"recipe": RecipeRead.from_orm(new_recipe)} + + @router.delete("/{id}") async def delete_menu(id: int, db: Session = Depends(get_db)): """Delete a menu plan.""" |
