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 /services/ai_service.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 'services/ai_service.py')
| -rw-r--r-- | services/ai_service.py | 71 |
1 files changed, 69 insertions, 2 deletions
diff --git a/services/ai_service.py b/services/ai_service.py index f128b4a..4b54c15 100644 --- a/services/ai_service.py +++ b/services/ai_service.py @@ -50,7 +50,8 @@ Respond ONLY with this JSON structure: "name": "...", "meal_type": "breakfast", "ingredients": [{{"name": "...", "quantity": 1.0, "unit": "cup"}}], - "instructions": "Step 1... Step 2...", + "description": "1-2 sentence description of the dish's flavor and appeal", + "instructions": "1. ... 2. ... (4-8 numbered steps, 1-2 sentences each)", "time_minutes": 20, "serves": 2 }} @@ -204,7 +205,8 @@ Respond ONLY with this JSON: "name": "...", "meal_type": "{meal_type}", "ingredients": [{{"name": "...", "quantity": 1.0, "unit": "cup"}}], - "instructions": "Step 1... Step 2...", + "description": "1-2 sentence description of the dish's flavor and appeal", + "instructions": "1. ... 2. ... (4-8 numbered steps, 1-2 sentences each)", "time_minutes": 30, "serves": 2 }} @@ -262,3 +264,68 @@ Be conversational, helpful, and concise. Reference specific ingredients and reci loop = asyncio.get_running_loop() return await loop.run_in_executor(None, lambda: _chat_sync(messages, json_format=False)) + + +async def parse_recipe_from_text(text: str) -> dict: + """Ask Ollama to parse unstructured recipe text into our standard recipe schema.""" + user_message = f"""Parse this recipe text and extract structured data. + +RECIPE TEXT: +{text[:4000]} + +Respond ONLY with this JSON: +{{ + "recipe": {{ + "name": "...", + "meal_type": "dinner", + "ingredients": [{{"name": "...", "quantity": 1.0, "unit": "cup"}}], + "description": "1-2 sentence description of the dish's flavor and appeal", + "instructions": "1. ... 2. ... (4-8 numbered steps, 1-2 sentences each)", + "time_minutes": 30, + "serves": 2 + }} +}} +meal_type must be one of: breakfast, lunch, dinner, snack""" + + messages = [ + {"role": "system", "content": settings.system_prompt}, + {"role": "user", "content": user_message}, + ] + return await _chat(messages) + + +async def generate_single_recipe(description: str, existing_names: list, pantry_context: dict) -> dict: + """Generate one recipe matching a user description.""" + user_message = f"""Suggest ONE recipe based on this description: "{description}" + +CURRENT PANTRY: +{json.dumps(pantry_context["available_ingredients"], indent=2)} + +ALREADY IN PLAN (do not suggest these): +{json.dumps(existing_names, indent=2)} + +GUIDELINES: +- Match the description as closely as possible +- Do NOT limit yourself to only pantry ingredients +- Under 60 minutes +- Must not be any dish already in the plan + +Respond ONLY with this JSON: +{{ + "recipe": {{ + "name": "...", + "description": "1-2 sentence description of the dish's flavor and appeal", + "meal_type": "dinner", + "ingredients": [{{"name": "...", "quantity": 1.0, "unit": "cup"}}], + "instructions": "1. ... 2. ... (4-8 numbered steps, 1-2 sentences each)", + "time_minutes": 30, + "serves": 2 + }} +}} +meal_type must be one of: breakfast, lunch, dinner, snack""" + + messages = [ + {"role": "system", "content": settings.system_prompt}, + {"role": "user", "content": user_message}, + ] + return await _chat(messages) |
