summaryrefslogtreecommitdiff
path: root/routers/menus.py
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-09 02:31:10 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-09 02:31:10 -0700
commit360eadf78fb001e947f3850603152adc413bb3a8 (patch)
tree2d67065890ce195bc2c0f2f6e430e86c241b2424 /routers/menus.py
parentaba03fd72df5729a86d21c6866761b43a8abad68 (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.py68
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."""