Spaces:
Running
Running
import base64 | |
import streamlit as st | |
from openai import OpenAI | |
import os | |
# Load environment variables | |
from dotenv import load_dotenv | |
load_dotenv() | |
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) | |
# 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(""" | |
<style> | |
.big-font { | |
font-size:20px !important; | |
color: #2C3E50; | |
} | |
.highlight-box { | |
background-color: #F5F5F5; /* Soft light gray */ | |
border-radius: 10px; | |
padding: 20px; | |
margin-bottom: 20px; | |
color: #333333; /* Very dark gray for text */ | |
border: 1px solid #E0E0E0; /* Subtle border */ | |
} | |
.highlight-box h1 { | |
color: #1A5F7A; /* Deep teal for main heading */ | |
font-size: 24px; | |
margin-bottom: 15px; | |
} | |
.highlight-box h2 { | |
color: #2C7DA0; /* Slightly lighter teal for subheadings */ | |
font-size: 20px; | |
margin-top: 15px; | |
margin-bottom: 10px; | |
} | |
.highlight-box h3 { | |
color: #468FAF; /* Even lighter teal for smaller headings */ | |
font-size: 18px; | |
margin-top: 10px; | |
margin-bottom: 8px; | |
} | |
.highlight-box p { | |
color: #333333; /* Dark gray for paragraphs */ | |
line-height: 1.6; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Initialize session state | |
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 | |
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 analyze_and_generate_recipe(uploaded_image, available_equipment=None): | |
"""Analyze food image and generate recipe in a single LLM call""" | |
with st.spinner('π³ Analyzing your delicious image and generating recipe...'): | |
try: | |
# Prepare the system and user messages | |
messages = [ | |
{ | |
"role": "system", | |
"content": """You are a professional chef and food analyst. | |
When analyzing a food image, provide a comprehensive recipe 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,{uploaded_image}"} | |
}, | |
{ | |
"type": "text", | |
"text": f"""Analyze this food image and generate a detailed recipe. | |
{'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 | |
- Precise, step-by-step instructions | |
- Difficulty level | |
- Estimated cooking time | |
Use markdown formatting for clear presentation.""" | |
} | |
] | |
} | |
] | |
# Make the LLM call | |
response = client.chat.completions.create( | |
model="gpt-4o-mini", | |
messages=messages | |
) | |
return response.choices[0].message.content | |
except Exception as e: | |
st.error(f"Error analyzing image and generating recipe: {e}") | |
return None | |
def refine_recipe(original_recipe, user_refinement): | |
"""Refine the recipe based on user input""" | |
try: | |
response = client.chat.completions.create( | |
model="gpt-4o-mini", | |
messages=[ | |
{ | |
"role": "system", | |
"content": "You are a professional chef who can modify recipes based on specific user preferences." | |
}, | |
{ | |
"role": "user", | |
"content": f"""Original Recipe: | |
{original_recipe} | |
User Refinement Request: {user_refinement} | |
Please modify the recipe according to the user's preferences. | |
Provide the updated recipe with clear instructions, | |
maintaining the original recipe's core structure.""" | |
} | |
] | |
) | |
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 Decoder") | |
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) | |
# Image Upload and Analysis Section | |
st.markdown("### πΈ Upload Your Food Image", unsafe_allow_html=True) | |
uploaded_file = st.file_uploader("Choose an image...", type=['jpg', 'jpeg', 'png']) | |
# 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_column_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 | |
) | |
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"<div class='highlight-box'>{recipe}</div>", 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 | |
) | |
if refined_recipe: | |
# Display the refined recipe | |
st.markdown("### π½οΈ Refined Recipe", unsafe_allow_html=True) | |
st.markdown(f"<div class='highlight-box'>{refined_recipe}</div>", 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_column_width=True) |