Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import google.generativeai as genai | |
| import json | |
| import os | |
| from typing import List, Dict, Any | |
| import re | |
| # Configure page | |
| st.set_page_config( | |
| page_title="π³ AI Recipe Generator", | |
| page_icon="π³", | |
| layout="wide", | |
| initial_sidebar_state="collapsed" | |
| ) | |
| # Custom CSS for better styling | |
| st.markdown(""" | |
| <style> | |
| .main-header { | |
| text-align: center; | |
| padding: 2rem 0; | |
| background: linear-gradient(90deg, #ff6b6b, #4ecdc4); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| font-size: 3rem; | |
| font-weight: bold; | |
| margin-bottom: 2rem; | |
| } | |
| .recipe-card { | |
| background: #f8f9fa; | |
| padding: 1.5rem; | |
| border-radius: 10px; | |
| border-left: 4px solid #4ecdc4; | |
| margin: 1rem 0; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| .recipe-title { | |
| color: #2c3e50; | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| margin-bottom: 1rem; | |
| } | |
| .time-badge { | |
| background: #e74c3c; | |
| color: white; | |
| padding: 0.3rem 0.8rem; | |
| border-radius: 15px; | |
| font-size: 0.8rem; | |
| margin-right: 0.5rem; | |
| } | |
| .tip-box { | |
| background: #fff3cd; | |
| border: 1px solid #ffeaa7; | |
| padding: 1rem; | |
| border-radius: 5px; | |
| margin-top: 1rem; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| class RecipeRAG: | |
| """Recipe Retrieval-Augmented Generation System""" | |
| def __init__(self): | |
| self.api_key = None | |
| self.model = None | |
| self.sample_recipes = self.load_sample_recipes() | |
| def load_sample_recipes(self) -> List[Dict]: | |
| """Load sample recipe database for RAG retrieval""" | |
| return [ | |
| { | |
| "name": "Classic Scrambled Eggs", | |
| "ingredients": ["eggs", "butter", "salt", "pepper", "milk"], | |
| "category": "breakfast" | |
| }, | |
| { | |
| "name": "Tomato Garlic Pasta", | |
| "ingredients": ["pasta", "tomato", "garlic", "olive oil", "basil"], | |
| "category": "dinner" | |
| }, | |
| { | |
| "name": "French Omelette", | |
| "ingredients": ["eggs", "butter", "herbs", "cheese", "salt"], | |
| "category": "breakfast" | |
| }, | |
| { | |
| "name": "Garlic Butter Rice", | |
| "ingredients": ["rice", "garlic", "butter", "onion", "herbs"], | |
| "category": "side" | |
| }, | |
| { | |
| "name": "Simple Fried Rice", | |
| "ingredients": ["rice", "eggs", "soy sauce", "garlic", "vegetables"], | |
| "category": "main" | |
| }, | |
| { | |
| "name": "Tomato Onion Salad", | |
| "ingredients": ["tomato", "onion", "olive oil", "vinegar", "herbs"], | |
| "category": "salad" | |
| }, | |
| { | |
| "name": "Garlic Roasted Vegetables", | |
| "ingredients": ["vegetables", "garlic", "olive oil", "herbs", "salt"], | |
| "category": "side" | |
| } | |
| ] | |
| def setup_gemini(self, api_key: str) -> bool: | |
| """Initialize Gemini API""" | |
| try: | |
| genai.configure(api_key=api_key) | |
| self.model = genai.GenerativeModel('gemini-1.5-flash') | |
| self.api_key = api_key | |
| return True | |
| except Exception as e: | |
| st.error(f"Failed to initialize Gemini API: {str(e)}") | |
| return False | |
| def retrieve_relevant_recipes(self, user_ingredients: List[str]) -> List[Dict]: | |
| """RAG Step 1: Retrieve relevant recipes based on ingredients""" | |
| user_ingredients = [ing.lower().strip() for ing in user_ingredients] | |
| relevant_recipes = [] | |
| for recipe in self.sample_recipes: | |
| # Calculate ingredient overlap | |
| recipe_ingredients = [ing.lower() for ing in recipe["ingredients"]] | |
| overlap = len(set(user_ingredients) & set(recipe_ingredients)) | |
| if overlap > 0: | |
| recipe_score = overlap / len(recipe_ingredients) | |
| relevant_recipes.append({ | |
| **recipe, | |
| "relevance_score": recipe_score, | |
| "matching_ingredients": overlap | |
| }) | |
| # Sort by relevance and return top matches | |
| relevant_recipes.sort(key=lambda x: x["relevance_score"], reverse=True) | |
| return relevant_recipes[:3] # Return top 3 matches | |
| def generate_recipes_with_gemini(self, user_ingredients: List[str], relevant_recipes: List[Dict]) -> List[Dict]: | |
| """RAG Step 2: Use Gemini to generate complete recipes based on retrieved context""" | |
| ingredients_text = ", ".join(user_ingredients) | |
| context_recipes = "\n".join([f"- {r['name']}: {', '.join(r['ingredients'])}" | |
| for r in relevant_recipes]) | |
| prompt = f""" | |
| Based on the following ingredients: {ingredients_text} | |
| Context from similar recipes: | |
| {context_recipes} | |
| Generate 4 complete recipes that can be made using the given ingredients. For each recipe, provide: | |
| 1. Recipe Name | |
| 2. Complete ingredient list (including quantities) | |
| 3. Step-by-step cooking instructions (numbered) | |
| 4. Preparation time (in minutes) | |
| 5. Cooking time (in minutes) | |
| 6. A helpful cooking tip or variation | |
| Format your response as JSON with this structure: | |
| {{ | |
| "recipes": [ | |
| {{ | |
| "name": "Recipe Name", | |
| "ingredients_with_quantities": ["2 eggs", "1 tbsp butter", "..."], | |
| "instructions": ["Step 1: ...", "Step 2: ...", "..."], | |
| "prep_time": 5, | |
| "cook_time": 10, | |
| "tip": "Helpful tip here" | |
| }} | |
| ] | |
| }} | |
| Make sure recipes are practical and can actually be made with the provided ingredients. | |
| """ | |
| try: | |
| response = self.model.generate_content(prompt) | |
| # Extract JSON from response | |
| response_text = response.text.strip() | |
| # Try to find JSON in the response | |
| json_match = re.search(r'\{.*\}', response_text, re.DOTALL) | |
| if json_match: | |
| json_text = json_match.group() | |
| recipes_data = json.loads(json_text) | |
| return recipes_data.get("recipes", []) | |
| else: | |
| # Fallback: parse response manually | |
| return self.parse_text_response(response_text) | |
| except Exception as e: | |
| st.error(f"Error generating recipes: {str(e)}") | |
| return [] | |
| def parse_text_response(self, text: str) -> List[Dict]: | |
| """Fallback parser for non-JSON responses""" | |
| recipes = [] | |
| # This is a simple fallback - in production you'd want more robust parsing | |
| lines = text.split('\n') | |
| current_recipe = {} | |
| for line in lines: | |
| line = line.strip() | |
| if 'Recipe:' in line or 'Name:' in line: | |
| if current_recipe: | |
| recipes.append(current_recipe) | |
| current_recipe = {"name": line.split(':')[-1].strip()} | |
| elif 'Prep time:' in line: | |
| current_recipe["prep_time"] = 10 # Default fallback | |
| elif 'Cook time:' in line: | |
| current_recipe["cook_time"] = 15 # Default fallback | |
| return recipes[:4] if recipes else [] | |
| def main(): | |
| # Header | |
| st.markdown('<h1 class="main-header">π³ AI Recipe Generator</h1>', unsafe_allow_html=True) | |
| st.markdown("### Transform your ingredients into delicious recipes using AI magic! β¨") | |
| # Initialize RAG system | |
| if 'rag_system' not in st.session_state: | |
| st.session_state.rag_system = RecipeRAG() | |
| rag_system = st.session_state.rag_system | |
| # API Key input | |
| with st.sidebar: | |
| st.header("π Configuration") | |
| api_key = st.text_input( | |
| "Google Gemini API Key", | |
| type="password", | |
| help="Get your API key from Google AI Studio" | |
| ) | |
| if api_key and api_key != st.session_state.get('current_api_key'): | |
| if rag_system.setup_gemini(api_key): | |
| st.session_state.current_api_key = api_key | |
| st.success("β API key configured successfully!") | |
| else: | |
| st.error("β Invalid API key") | |
| # Main interface | |
| col1, col2 = st.columns([2, 1]) | |
| with col1: | |
| st.markdown("#### π Enter Your Ingredients") | |
| ingredients_input = st.text_input( | |
| "", | |
| placeholder="e.g., onion, tomato, garlic, eggs, rice", | |
| help="Enter ingredients separated by commas" | |
| ) | |
| with col2: | |
| st.markdown("#### π Generate Recipes") | |
| generate_button = st.button( | |
| "Generate Recipes", | |
| type="primary", | |
| use_container_width=True | |
| ) | |
| # Validation and processing | |
| if generate_button: | |
| if not api_key: | |
| st.error("β οΈ Please enter your Google Gemini API key in the sidebar first!") | |
| st.info("π‘ Get your free API key from [Google AI Studio](https://makersuite.google.com/app/apikey)") | |
| return | |
| if not ingredients_input.strip(): | |
| st.error("β οΈ Please enter some ingredients first!") | |
| return | |
| # Process ingredients | |
| user_ingredients = [ing.strip() for ing in ingredients_input.split(',') if ing.strip()] | |
| if len(user_ingredients) < 2: | |
| st.error("β οΈ Please enter at least 2 ingredients for better recipe suggestions!") | |
| return | |
| # Show loading | |
| with st.spinner("π€ AI is cooking up some amazing recipes for you..."): | |
| # RAG Step 1: Retrieve relevant recipes | |
| relevant_recipes = rag_system.retrieve_relevant_recipes(user_ingredients) | |
| # RAG Step 2: Generate recipes with Gemini | |
| generated_recipes = rag_system.generate_recipes_with_gemini(user_ingredients, relevant_recipes) | |
| # Display results | |
| if generated_recipes: | |
| st.markdown("---") | |
| st.markdown("## π½οΈ Your Personalized Recipes") | |
| for i, recipe in enumerate(generated_recipes, 1): | |
| with st.expander(f"π Recipe {i}: {recipe.get('name', 'Delicious Recipe')}", expanded=i==1): | |
| # Recipe header with timing | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.markdown(f"**β±οΈ Prep:** {recipe.get('prep_time', 10)} mins") | |
| with col2: | |
| st.markdown(f"**π₯ Cook:** {recipe.get('cook_time', 15)} mins") | |
| with col3: | |
| total_time = recipe.get('prep_time', 10) + recipe.get('cook_time', 15) | |
| st.markdown(f"**β° Total:** {total_time} mins") | |
| st.markdown("---") | |
| # Ingredients | |
| st.markdown("#### π Ingredients:") | |
| ingredients = recipe.get('ingredients_with_quantities', recipe.get('ingredients', [])) | |
| for ingredient in ingredients: | |
| st.markdown(f"β’ {ingredient}") | |
| # Instructions | |
| st.markdown("#### π¨βπ³ Instructions:") | |
| instructions = recipe.get('instructions', []) | |
| for j, instruction in enumerate(instructions, 1): | |
| st.markdown(f"**{j}.** {instruction}") | |
| # Tip | |
| tip = recipe.get('tip', 'Enjoy your cooking!') | |
| if tip: | |
| st.markdown(f""" | |
| <div class="tip-box"> | |
| <strong>π‘ Pro Tip:</strong> {tip} | |
| </div> | |
| """, unsafe_allow_html=True) | |
| else: | |
| st.error("π Couldn't generate recipes. Please try again with different ingredients!") | |
| # Footer | |
| st.markdown("---") | |
| st.markdown(""" | |
| <div style="text-align: center; color: #666; padding: 2rem;"> | |
| Made with β€οΈ using Streamlit and Google Gemini Pro API<br> | |
| π Happy Cooking! π | |
| </div> | |
| """, unsafe_allow_html=True) | |
| if __name__ == "__main__": | |
| main() |