Spaces:
Sleeping
Sleeping
| """ | |
| Recipe Processing API for Hugging Face | |
| This is the main application file for the recipe processing and product matching | |
| system. It provides a FastAPI application that can be deployed on Hugging Face Spaces. | |
| """ | |
| import os | |
| import json | |
| import logging | |
| from fastapi import FastAPI, HTTPException, File, UploadFile, Form, Body | |
| from fastapi.responses import JSONResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel, Field | |
| from typing import Optional, Dict, Any, List | |
| from recipe_processor import RecipeProcessor | |
| from product_retriever_improved import ProductRetriever | |
| import uvicorn | |
| # Set up logging | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(levelname)s - %(message)s' | |
| ) | |
| # Create FastAPI app | |
| app = FastAPI( | |
| title="Recipe Processing API", | |
| description="API for processing recipes and matching ingredients with products", | |
| version="1.0.0", | |
| ) | |
| # Add CORS middleware | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # Allow all origins | |
| allow_credentials=True, | |
| allow_methods=["*"], # Allow all methods | |
| allow_headers=["*"], # Allow all headers | |
| ) | |
| # Define API models | |
| class ProcessRecipeRequest(BaseModel): | |
| recipe_data: Dict[str, Any] = Field(..., description="Recipe data to process") | |
| woolworths_api_key: Optional[str] = Field(None, description="Woolworths API key") | |
| coles_api_key: Optional[str] = Field(None, description="Coles API key") | |
| summarize: bool = Field(True, description="Whether to summarize recipe text") | |
| add_products: bool = Field(True, description="Whether to add product matches") | |
| class SupabaseSettings(BaseModel): | |
| supabase_url: str = Field(..., description="Supabase URL") | |
| supabase_key: str = Field(..., description="Supabase API key") | |
| class ProcessMealPlanRequest(BaseModel): | |
| supabase_url: str = Field(..., description="Supabase URL") | |
| supabase_key: str = Field(..., description="Supabase API key") | |
| session_id: Optional[str] = Field(None, description="Session ID (optional, if None gets latest)") | |
| woolworths_api_key: Optional[str] = Field(None, description="Woolworths API key") | |
| coles_api_key: Optional[str] = Field(None, description="Coles API key") | |
| summarize: bool = Field(True, description="Whether to summarize recipe text") | |
| add_products: bool = Field(True, description="Whether to add product matches") | |
| # API endpoints | |
| async def root(): | |
| """API root endpoint""" | |
| return { | |
| "message": "Recipe Processing API is running", | |
| "endpoints": [ | |
| "/api/process_recipe", | |
| "/api/process_meal_plan", | |
| "/api/get_meal_plans" | |
| ] | |
| } | |
| async def process_recipe(request: ProcessRecipeRequest): | |
| """ | |
| Process a recipe and optionally add product matches | |
| """ | |
| try: | |
| # Initialize the recipe processor | |
| recipe_processor = RecipeProcessor() | |
| # Process the recipe | |
| processed_recipe = recipe_processor.process_recipe( | |
| request.recipe_data, | |
| summarize=request.summarize | |
| ) | |
| # Add product matches if requested | |
| if request.add_products and (request.woolworths_api_key or request.coles_api_key): | |
| # Initialize product retriever | |
| product_retriever = ProductRetriever( | |
| woolworths_api_key=request.woolworths_api_key, | |
| coles_api_key=request.coles_api_key | |
| ) | |
| # Add product matches | |
| processed_recipe = product_retriever.add_product_matches_to_recipe(processed_recipe) | |
| return processed_recipe | |
| except Exception as e: | |
| logging.error(f"Error processing recipe: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def process_meal_plan(request: ProcessMealPlanRequest): | |
| """ | |
| Process a meal plan from Supabase and optionally add product matches | |
| """ | |
| try: | |
| # Import Supabase client | |
| try: | |
| from supabase import create_client | |
| except ImportError: | |
| raise HTTPException(status_code=500, detail="Supabase library not installed") | |
| # Initialize Supabase client | |
| supabase = create_client(request.supabase_url, request.supabase_key) | |
| # Get meal plan | |
| if request.session_id: | |
| response = supabase.table("meal_plans").select("*").eq("id", request.session_id).execute() | |
| else: | |
| # Get the latest meal plan | |
| response = supabase.table("meal_plans").select("*").order("created_at", desc=True).limit(1).execute() | |
| if not response.data: | |
| error_msg = "No meal plan found" + (f" for session {request.session_id}" if request.session_id else "") | |
| raise HTTPException(status_code=404, detail=error_msg) | |
| # Get the meal plan data | |
| meal_plan = response.data[0] | |
| session_id = meal_plan.get("id", "") | |
| # Initialize recipe processor | |
| recipe_processor = RecipeProcessor() | |
| # Create result structure | |
| result = { | |
| "session_id": session_id, | |
| "processed": {} | |
| } | |
| # Process each meal type | |
| for meal_type in ["breakfast", "lunch", "dinner"]: | |
| if meal_type not in meal_plan: | |
| continue | |
| meal_data = meal_plan[meal_type] | |
| # Parse JSON if it's a string | |
| if isinstance(meal_data, str): | |
| try: | |
| meal_data = json.loads(meal_data) | |
| except json.JSONDecodeError: | |
| logging.error(f"Invalid JSON in {meal_type}") | |
| continue | |
| # Process this meal | |
| processed_meal = recipe_processor.process_recipe( | |
| meal_data, | |
| summarize=request.summarize | |
| ) | |
| # Add to result | |
| result["processed"][meal_type] = processed_meal | |
| # Add product matches if requested | |
| if request.add_products and (request.woolworths_api_key or request.coles_api_key): | |
| # Initialize product retriever | |
| product_retriever = ProductRetriever( | |
| woolworths_api_key=request.woolworths_api_key, | |
| coles_api_key=request.coles_api_key | |
| ) | |
| # Add product matches | |
| result = product_retriever.add_product_matches_to_meal_plan(result) | |
| # Generate combined result | |
| combined_result = {} | |
| meal_plans = {} | |
| for meal_type, meal_data in result["processed"].items(): | |
| # Extract the data we need | |
| meal_result = { | |
| 'recipe': meal_data.get('recipe', ''), | |
| 'summarized_recipe': '' | |
| } | |
| # Get summarized recipe (combine description and instructions) | |
| processed = meal_data.get('processed', {}) | |
| description = processed.get('description', {}).get('summarized', '') | |
| instructions = processed.get('instructions', {}).get('summarized', '') | |
| if description or instructions: | |
| meal_result['summarized_recipe'] = f"{description}\n\n{instructions}".strip() | |
| # Get ingredients | |
| meal_result['ingredients'] = processed.get('ingredients', []) | |
| # Add to meal plans | |
| meal_plans[meal_type] = meal_result | |
| # Add to result | |
| combined_result['meal_plans'] = meal_plans | |
| combined_result['session_id'] = session_id | |
| return combined_result | |
| except Exception as e: | |
| logging.error(f"Error processing meal plan: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def get_meal_plans(settings: SupabaseSettings, limit: int = 10): | |
| """Get list of available meal plans from Supabase""" | |
| try: | |
| # Import Supabase client | |
| try: | |
| from supabase import create_client | |
| except ImportError: | |
| raise HTTPException(status_code=500, detail="Supabase library not installed") | |
| # Initialize Supabase client | |
| supabase = create_client(settings.supabase_url, settings.supabase_key) | |
| # Get meal plans, most recent first | |
| response = supabase.table("meal_plans").select("id,created_at").order("created_at", desc=True).limit( | |
| limit).execute() | |
| if not response.data: | |
| return {"meal_plans": []} | |
| return {"meal_plans": response.data} | |
| except Exception as e: | |
| logging.error(f"Error getting meal plans: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # Main function to run the server directly (for development) | |
| if __name__ == "__main__": | |
| port = int(os.environ.get("PORT", 7860)) # Hugging Face Spaces uses port 7860 | |
| uvicorn.run("app:app", host="0.0.0.0", port=port) |