summaryrefslogtreecommitdiff
path: root/services/ai_service.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 /services/ai_service.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 'services/ai_service.py')
-rw-r--r--services/ai_service.py71
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)