import base64 import io import time import streamlit as st from openai import OpenAI import os from PIL import Image from utils import pprint, getFontsUrl # Load environment variables from dotenv import load_dotenv load_dotenv() client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) # model = "gpt-4o-mini" model = "gpt-4o" # Set up page configuration st.set_page_config( page_title="Magic Recipe Decoder 🍽️", page_icon="🥘", layout="wide", initial_sidebar_state="expanded" ) # Custom CSS for styling st.markdown( f""" """ """ """, unsafe_allow_html=True ) # Initialize session state if "ipAddress" not in st.session_state: st.session_state.ipAddress = st.context.headers.get("x-forwarded-for") if 'cooking_equipment' not in st.session_state: st.session_state.cooking_equipment = { 'Stove': True, 'Oven': False, 'Microwave': False, 'Blender': False, 'Pressure Cooker': False } if 'original_recipe' not in st.session_state: st.session_state.original_recipe = None # Add language selection to session state if 'recipe_language' not in st.session_state: st.session_state.recipe_language = 'English' def google_image_search(query): """Placeholder for image search - you'll need to implement actual image search API""" return "https://via.placeholder.com/300x200.png?text=" + query.replace(" ", "+") def resize_image(image_base64, max_size=1024): """ Resize an image from base64 to max dimension of 1024 pixels while maintaining aspect ratio Args: image_base64 (str): Base64 encoded image max_size (int): Maximum dimension for the image Returns: str: Resized image as base64 encoded string """ # Decode base64 image image_bytes = base64.b64decode(image_base64) # Log original image size original_size = len(image_bytes) # Open image with Pillow img = Image.open(io.BytesIO(image_bytes)) # Calculate resize ratio width, height = img.size resize_ratio = min(max_size / width, max_size / height) # If image is already smaller than max_size, return original if resize_ratio >= 1: pprint({ "function": "resize_image", "result": "no_resize_needed", "original_size_bytes": original_size, "original_size_kb": round(original_size / 1024) }) return image_base64 # Calculate new dimensions new_width = int(width * resize_ratio) new_height = int(height * resize_ratio) # Resize image resized_img = img.resize((new_width, new_height), Image.LANCZOS) # Convert back to base64 buffered = io.BytesIO() resized_img.save(buffered, format=img.format) resized_bytes = buffered.getvalue() resized_base64 = base64.b64encode(resized_bytes).decode('utf-8') # Log resized image size resized_size = len(resized_bytes) pprint({ "function": "resize_image", "original_size_kb": round(original_size / 1024), "resized_size_kb": round(resized_size / 1024), "size_reduction_percentage": round(((original_size - resized_size) / original_size) * 100) }) return resized_base64 def analyze_and_generate_recipe(uploaded_image, available_equipment=None, language='English'): """Analyze food image and generate recipe in a single LLM call""" progress_stages = [ {"message": "🔍 Scanning the delicious image...", "progress": 10}, {"message": "🧐 Identifying culinary ingredients...", "progress": 30}, {"message": "🍳 Consulting virtual chef's expertise...", "progress": 50}, {"message": "📝 Crafting personalized recipe...", "progress": 70}, {"message": "🌟 Finalizing gourmet instructions...", "progress": 90} ] # Create a progress bar progress_bar = st.progress(0) status_text = st.empty() try: # Update progress stages for stage in progress_stages: status_text.text(stage["message"]) progress_bar.progress(stage["progress"]) time.sleep(1) # Short delay between stages # Resize the image before sending to LLM resized_image_base64 = resize_image(uploaded_image) # Prepare the system and user messages messages = [ { "role": "system", "content": f"""You are a professional chef and food analyst. When analyzing a food image, provide a comprehensive recipe in {language} that considers: 1. Detailed food description 2. Complete ingredient list 3. Cooking method 4. Step-by-step instructions""" }, { "role": "user", "content": [ { "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{resized_image_base64}"} }, { "type": "text", "text": f"""Analyze this food image and generate a detailed recipe in {language}. {'Available cooking equipment: ' + ', '.join(available_equipment) if available_equipment else 'No equipment restrictions'} If specific equipment is available, prioritize cooking methods that use those tools. Provide: - Detailed food description - Ingredient list - Cooking method adapted to available equipment - Difficulty level - Estimated cooking time - Precise, step-by-step instructions Use markdown formatting for clear presentation.""" } ] } ] # Log API call details pprint({ "function": "analyze_and_generate_recipe", "model": model, "language": language, "available_equipment": available_equipment }) # Make the LLM call status_text.text("🚀 Generating recipe with AI...") progress_bar.progress(95) response = client.chat.completions.create( model=model, messages=messages ) # Final progress update status_text.text("✅ Recipe generated successfully!") progress_bar.progress(100) # Clear the progress bar and status text after a short delay time.sleep(1) progress_bar.empty() status_text.empty() # Log response details pprint({ "function": "analyze_and_generate_recipe_response", "tokens_used": response.usage.total_tokens if response.usage else None, "response_length": len(response.choices[0].message.content) }) return response.choices[0].message.content except Exception as e: # Clear progress indicators in case of error progress_bar.empty() status_text.empty() st.error(f"Error analyzing image and generating recipe: {e}") return None def refine_recipe(original_recipe, user_refinement, language='English'): """Refine the recipe based on user input""" try: # Log API call details pprint({ "function": "refine_recipe", "model": model, "language": language, "user_refinement_length": len(user_refinement) }) response = client.chat.completions.create( model=model, messages=[ { "role": "system", "content": f"You are a professional chef who can modify recipes based on specific user preferences. Respond in {language}." }, { "role": "user", "content": f"""Original Recipe: {original_recipe} User Refinement Request: {user_refinement} Please modify the recipe according to the user's preferences in {language}. Provide the updated recipe with clear instructions, maintaining the original recipe's core structure.""" } ] ) # Log response details pprint({ "function": "refine_recipe_response", "tokens_used": response.usage.total_tokens if response.usage else None, "response_length": len(response.choices[0].message.content) }) return response.choices[0].message.content except Exception as e: st.error(f"Error refining recipe: {e}") return None # Main Streamlit App st.title("🥘 Magic Recipe") st.markdown("*Discover the secrets behind your favorite dishes!*", unsafe_allow_html=True) # Sidebar for Cooking Equipment st.sidebar.header("🔧 Cooking Equipment") st.sidebar.markdown("Check the equipment you have available:") for equipment, available in st.session_state.cooking_equipment.items(): st.session_state.cooking_equipment[equipment] = st.sidebar.checkbox(equipment, value=available) # Language Selection st.sidebar.header("🌐 Recipe Language") st.sidebar.markdown("Choose your preferred language:") # Top 5 Indian languages + English languages = [ 'English', 'Hindi', 'Hinglish', 'Bengali', 'Telugu', 'Marathi', 'Tamil' ] st.session_state.recipe_language = st.sidebar.selectbox( "Select Recipe Language", languages, index=0 ) # Image Upload and Analysis Section st.markdown("### 📸 Upload Your Food Image", unsafe_allow_html=True) # Add camera input option img_source = st.radio("Choose image source:", ["Upload from device", "Take a photo"]) if img_source == "Upload from device": uploaded_file = st.file_uploader("Choose an image...", type=['jpg', 'jpeg', 'png']) else: uploaded_file = st.camera_input( "Take a photo of your dish", help="Please hold your device vertically for best results", # Set aspect ratio to portrait (3:4) key="portrait_camera", args={ "landscape": False, # Force portrait mode "aspectRatio": 3 / 4 # Portrait aspect ratio } ) # Food Analysis and Recipe Generation if uploaded_file is not None: # Display uploaded image col1, col2 = st.columns(2) with col1: st.image(uploaded_file, caption='Uploaded Image', use_container_width=True) with col2: # Checkbox to use available cooking equipment use_available_equipment = st.checkbox("Use only available cooking equipment", value=False) # Prepare available equipment list if checkbox is selected available_equipment = [] if use_available_equipment: available_equipment = [ equip for equip, available in st.session_state.cooking_equipment.items() if available ] # Analyze and Generate Recipe if st.button("Generate Recipe"): # Analyze image and generate recipe image_base64 = base64.b64encode(uploaded_file.getvalue()).decode('utf-8') recipe = analyze_and_generate_recipe( image_base64, available_equipment if use_available_equipment else None, st.session_state.recipe_language ) if recipe: # Store original recipe in session state st.session_state.original_recipe = recipe # Display the generated recipe st.markdown("### 🍳 Generated Recipe", unsafe_allow_html=True) st.markdown(f"
{recipe}
", unsafe_allow_html=True) # Recipe Refinement Section if st.session_state.original_recipe: st.markdown("### 🧑‍🍳 Refine Your Recipe", unsafe_allow_html=True) # Refinement Prompt user_refinement = st.text_input("Want to modify the recipe? Add your preferences here:") if st.button("Refine Recipe"): if user_refinement: # Refine the recipe with st.spinner('🔪 Refining your recipe...'): refined_recipe = refine_recipe( st.session_state.original_recipe, user_refinement, st.session_state.recipe_language ) if refined_recipe: # Display the refined recipe st.markdown("### 🍽️ Refined Recipe", unsafe_allow_html=True) st.markdown(f"
{refined_recipe}
", unsafe_allow_html=True) else: st.warning("Please enter refinement preferences.") # # Visual References # st.markdown("### 🖼️ Visual References", unsafe_allow_html=True) # if st.session_state.original_recipe: # food_name = st.session_state.original_recipe.split('\n')[0] # image_urls = [google_image_search(food_name) for _ in range(3)] # cols = st.columns(3) # for i, url in enumerate(image_urls): # cols[i].image(url, use_container_width=True)