| import gradio as gr |
| import base64 |
| import requests |
| import io |
| from PIL import Image |
| import json |
| import os |
| from together import Together |
| import tempfile |
| import uuid |
|
|
| def encode_image_to_base64(image_path): |
| """Convert image to base64 encoding""" |
| with open(image_path, "rb") as image_file: |
| return base64.b64encode(image_file.read()).decode('utf-8') |
|
|
| def save_uploaded_image(image): |
| """Save uploaded image to a temporary file and return the path""" |
| if image is None: |
| return None |
| |
| with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp_file: |
| if isinstance(image, dict) and "path" in image: |
| |
| with open(image["path"], "rb") as img_file: |
| temp_file.write(img_file.read()) |
| elif isinstance(image, Image.Image): |
| |
| image.save(temp_file.name, format="JPEG") |
| else: |
| |
| try: |
| Image.open(image).save(temp_file.name, format="JPEG") |
| except Exception: |
| return None |
| |
| return temp_file.name |
|
|
| def analyze_single_image(client, img_path): |
| """Analyze a single image to identify ingredients""" |
| system_prompt = """You are a culinary expert AI assistant that specializes in identifying ingredients in images. |
| Your task is to analyze the provided image and list all the food ingredients you can identify. |
| Be specific and detailed about what you see. Only list ingredients, don't suggest recipes yet.""" |
| |
| user_prompt = "Please identify all the food ingredients visible in this image. List each ingredient on a new line." |
| |
| |
| try: |
| with open(img_path, "rb") as image_file: |
| |
| base64_image = base64.b64encode(image_file.read()).decode('utf-8') |
| |
| |
| content = [ |
| {"type": "text", "text": user_prompt}, |
| { |
| "type": "image_url", |
| "image_url": { |
| "url": f"data:image/jpeg;base64,{base64_image}" |
| } |
| } |
| ] |
| |
| response = client.chat.completions.create( |
| model="meta-llama/Llama-Vision-Free", |
| messages=[ |
| { |
| "role": "system", |
| "content": system_prompt |
| }, |
| { |
| "role": "user", |
| "content": content |
| } |
| ], |
| max_tokens=500, |
| temperature=0.2 |
| ) |
| |
| return response.choices[0].message.content |
| except Exception as e: |
| return f"Error analyzing image: {str(e)}" |
|
|
| def get_recipe_suggestions(api_key, images, num_recipes=3, dietary_restrictions="None", cuisine_preference="Any"): |
| """ |
| Get recipe suggestions based on the uploaded images of ingredients |
| """ |
| if not api_key: |
| return "Please provide your Together API key." |
| |
| if not images or len(images) == 0 or all(img is None for img in images): |
| return "Please upload at least one image of ingredients." |
| |
| |
| valid_images = [img for img in images if img is not None] |
| |
| if len(valid_images) == 0: |
| return "No valid images were uploaded. Please try again." |
| |
| |
| image_paths = [] |
| for img in valid_images: |
| img_path = save_uploaded_image(img) |
| if img_path: |
| image_paths.append(img_path) |
| |
| if not image_paths: |
| return "Failed to process the uploaded images." |
| |
| try: |
| |
| client = Together(api_key=api_key) |
| |
| |
| all_ingredients = [] |
| for img_path in image_paths: |
| ingredients_text = analyze_single_image(client, img_path) |
| all_ingredients.append(ingredients_text) |
| |
| |
| combined_ingredients = "\n\n".join([f"Image {i+1} ingredients:\n{ingredients}" |
| for i, ingredients in enumerate(all_ingredients)]) |
| |
| |
| system_prompt = """You are a culinary expert AI assistant that specializes in creating recipes based on available ingredients. |
| You will be provided with lists of ingredients identified from multiple images. Your task is to suggest creative, |
| detailed recipes that use as many of the identified ingredients as possible. |
| |
| For each recipe suggestion, include: |
| 1. Recipe name |
| 2. Brief description of the dish |
| 3. Complete ingredients list (including estimated quantities and any additional staple ingredients that might be needed) |
| 4. Step-by-step cooking instructions |
| 5. Approximate cooking time |
| 6. Difficulty level (Easy, Medium, Advanced) |
| 7. Nutritional highlights |
| |
| Consider any dietary restrictions and cuisine preferences mentioned by the user.""" |
| |
| user_prompt = f"""Based on the following ingredients identified from multiple images, suggest {num_recipes} creative and delicious recipes. |
| |
| {combined_ingredients} |
| |
| Dietary restrictions to consider: {dietary_restrictions} |
| Cuisine preference: {cuisine_preference} |
| |
| Please be creative with your recipe suggestions and try to use ingredients from multiple images if possible.""" |
| |
| |
| response = client.chat.completions.create( |
| model="meta-llama/Llama-Vision-Free", |
| messages=[ |
| { |
| "role": "system", |
| "content": system_prompt |
| }, |
| { |
| "role": "user", |
| "content": user_prompt |
| } |
| ], |
| max_tokens=2048, |
| temperature=0.7 |
| ) |
| |
| |
| for img_path in image_paths: |
| try: |
| os.unlink(img_path) |
| except: |
| pass |
| |
| |
| result = "## 📋 Ingredients Identified\n\n" |
| result += combined_ingredients |
| result += "\n\n---\n\n" |
| result += "## 🍽️ Recipe Suggestions\n\n" |
| result += response.choices[0].message.content |
| |
| return result |
| |
| except Exception as e: |
| |
| for img_path in image_paths: |
| try: |
| os.unlink(img_path) |
| except: |
| pass |
| return f"Error: {str(e)}" |
|
|
| |
| custom_css = """ |
| @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); |
| |
| :root { |
| --primary-color: #FF6B6B; |
| --primary-dark: #e55858; |
| --secondary-color: #4ECDC4; |
| --accent-color: #FFD166; |
| --background-color: #f8f9fa; |
| --text-color: #212529; |
| --card-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); |
| --hover-shadow: 0 12px 28px rgba(0, 0, 0, 0.15); |
| --border-radius: 12px; |
| --font-family: 'Poppins', sans-serif; |
| } |
| |
| body { |
| font-family: var(--font-family); |
| background-color: var(--background-color); |
| color: var(--text-color); |
| margin: 0; |
| padding: 0; |
| } |
| |
| .container { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 20px; |
| } |
| |
| .app-header { |
| text-align: center; |
| margin-bottom: 40px; |
| padding: 50px 20px; |
| background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); |
| border-radius: var(--border-radius); |
| color: white; |
| box-shadow: var(--card-shadow); |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .app-header::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path fill="rgba(255,255,255,0.05)" d="M30,20 L70,20 L50,50 Z"></path></svg>') repeat; |
| opacity: 0.3; |
| } |
| |
| .app-title { |
| font-size: 3.5em; |
| margin-bottom: 15px; |
| font-weight: 700; |
| text-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| position: relative; |
| } |
| |
| .app-subtitle { |
| font-size: 1.3em; |
| opacity: 0.9; |
| max-width: 700px; |
| margin: 0 auto; |
| line-height: 1.6; |
| font-weight: 300; |
| position: relative; |
| } |
| |
| .input-section, .output-section { |
| background-color: white; |
| border-radius: var(--border-radius); |
| padding: 30px; |
| box-shadow: var(--card-shadow); |
| margin-bottom: 30px; |
| transition: all 0.3s ease; |
| border: 1px solid rgba(0,0,0,0.05); |
| } |
| |
| .input-section:hover, .output-section:hover { |
| box-shadow: var(--hover-shadow); |
| transform: translateY(-5px); |
| } |
| |
| .section-header { |
| color: var(--primary-color); |
| margin-top: 0; |
| font-size: 1.7em; |
| border-bottom: 2px solid var(--secondary-color); |
| padding-bottom: 15px; |
| margin-bottom: 25px; |
| font-weight: 600; |
| display: flex; |
| align-items: center; |
| } |
| |
| .section-header i { |
| margin-right: 10px; |
| font-size: 1.2em; |
| } |
| |
| .image-upload-container { |
| border: 3px dashed var(--secondary-color); |
| border-radius: var(--border-radius); |
| padding: 30px; |
| text-align: center; |
| margin-bottom: 25px; |
| transition: all 0.3s ease; |
| background-color: rgba(78, 205, 196, 0.05); |
| position: relative; |
| } |
| |
| .image-upload-container:hover { |
| border-color: var(--primary-color); |
| background-color: rgba(255, 107, 107, 0.05); |
| transform: translateY(-3px); |
| } |
| |
| .image-upload-icon { |
| font-size: 3em; |
| color: var(--secondary-color); |
| margin-bottom: 15px; |
| } |
| |
| .image-upload-text { |
| color: #666; |
| font-weight: 500; |
| } |
| |
| button.primary-button { |
| background: linear-gradient(135deg, var(--primary-color) 0%, #FF8E8E 100%); |
| color: white; |
| border: none; |
| padding: 15px 30px; |
| border-radius: 50px; |
| font-size: 1.2em; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| box-shadow: 0 4px 10px rgba(255, 107, 107, 0.3); |
| font-weight: 600; |
| display: block; |
| width: 100%; |
| margin-top: 30px; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| button.primary-button:hover { |
| transform: translateY(-3px); |
| box-shadow: 0 8px 15px rgba(255, 107, 107, 0.4); |
| background: linear-gradient(135deg, #FF8E8E 0%, var(--primary-dark) 100%); |
| } |
| |
| button.primary-button:active { |
| transform: translateY(1px); |
| box-shadow: 0 2px 5px rgba(255, 107, 107, 0.4); |
| } |
| |
| button.primary-button::after { |
| content: ''; |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| width: 5px; |
| height: 5px; |
| background: rgba(255, 255, 255, 0.5); |
| opacity: 0; |
| border-radius: 100%; |
| transform: scale(1, 1) translate(-50%, -50%); |
| transform-origin: 50% 50%; |
| } |
| |
| button.primary-button:focus:not(:active)::after { |
| animation: ripple 1s ease-out; |
| } |
| |
| @keyframes ripple { |
| 0% { |
| transform: scale(0, 0); |
| opacity: 0.5; |
| } |
| 100% { |
| transform: scale(100, 100); |
| opacity: 0; |
| } |
| } |
| |
| .gradio-slider.svelte-17l1npl { |
| margin-bottom: 25px; |
| } |
| |
| .recipe-card { |
| border-left: 5px solid var(--accent-color); |
| padding: 20px; |
| background-color: #f9f9f9; |
| margin-bottom: 20px; |
| border-radius: 0 var(--border-radius) var(--border-radius) 0; |
| transition: all 0.3s ease; |
| } |
| |
| .recipe-card:hover { |
| transform: translateX(5px); |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); |
| } |
| |
| .recipe-title { |
| color: var(--primary-color); |
| font-size: 1.5em; |
| margin-bottom: 10px; |
| font-weight: 600; |
| } |
| |
| .footer { |
| text-align: center; |
| margin-top: 60px; |
| padding: 30px 0; |
| color: #6c757d; |
| font-size: 1em; |
| background-color: #f1f3f5; |
| border-radius: var(--border-radius); |
| box-shadow: inset 0 2px 10px rgba(0,0,0,0.05); |
| } |
| |
| .footer-content { |
| max-width: 700px; |
| margin: 0 auto; |
| } |
| |
| .footer-brand { |
| font-weight: 600; |
| color: var(--primary-color); |
| } |
| |
| .footer-links { |
| margin-top: 20px; |
| } |
| |
| .footer-links a { |
| color: var(--secondary-color); |
| margin: 0 10px; |
| text-decoration: none; |
| transition: color 0.3s ease; |
| } |
| |
| .footer-links a:hover { |
| color: var(--primary-color); |
| text-decoration: underline; |
| } |
| |
| .icon { |
| color: var(--primary-color); |
| margin-right: 10px; |
| font-size: 1.2em; |
| } |
| |
| .input-group { |
| margin-bottom: 25px; |
| } |
| |
| .input-group label { |
| display: block; |
| margin-bottom: 10px; |
| font-weight: 600; |
| color: var(--text-color); |
| font-size: 1.1em; |
| } |
| |
| .gallery-container { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); |
| gap: 20px; |
| margin-top: 20px; |
| } |
| |
| .gallery-item { |
| border-radius: var(--border-radius); |
| overflow: hidden; |
| box-shadow: var(--card-shadow); |
| transition: transform 0.3s ease; |
| aspect-ratio: 1 / 1; |
| object-fit: cover; |
| } |
| |
| .gallery-item:hover { |
| transform: scale(1.05); |
| box-shadow: var(--hover-shadow); |
| } |
| |
| /* Loading Spinner */ |
| .loading-container { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0, 0, 0, 0.7); |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| z-index: 1000; |
| opacity: 0; |
| visibility: hidden; |
| transition: opacity 0.3s ease, visibility 0.3s ease; |
| } |
| |
| .loading-container.visible { |
| opacity: 1; |
| visibility: visible; |
| } |
| |
| .loading-spinner { |
| width: 80px; |
| height: 80px; |
| border-radius: 50%; |
| position: relative; |
| animation: spin 1.2s linear infinite; |
| } |
| |
| .loading-spinner::before, |
| .loading-spinner::after { |
| content: ''; |
| position: absolute; |
| border-radius: 50%; |
| } |
| |
| .loading-spinner::before { |
| width: 100%; |
| height: 100%; |
| background: linear-gradient(to right, var(--primary-color) 0%, transparent 100%); |
| animation: spin 2s linear infinite; |
| } |
| |
| .loading-spinner::after { |
| width: 75%; |
| height: 75%; |
| background-color: rgba(0, 0, 0, 0.7); |
| top: 12.5%; |
| left: 12.5%; |
| } |
| |
| .loading-text { |
| position: absolute; |
| bottom: -40px; |
| color: white; |
| font-size: 1.2em; |
| font-weight: 500; |
| } |
| |
| @keyframes spin { |
| 0% { |
| transform: rotate(0deg); |
| } |
| 100% { |
| transform: rotate(360deg); |
| } |
| } |
| |
| .recipe-output { |
| max-height: 800px; |
| overflow-y: auto; |
| padding-right: 15px; |
| line-height: 1.6; |
| } |
| |
| .recipe-output::-webkit-scrollbar { |
| width: 8px; |
| } |
| |
| .recipe-output::-webkit-scrollbar-track { |
| background: #f1f1f1; |
| border-radius: 10px; |
| } |
| |
| .recipe-output::-webkit-scrollbar-thumb { |
| background: var(--secondary-color); |
| border-radius: 10px; |
| } |
| |
| .recipe-output::-webkit-scrollbar-thumb:hover { |
| background: var(--primary-color); |
| } |
| |
| .recipe-output h2 { |
| color: var(--primary-color); |
| border-bottom: 2px solid var(--secondary-color); |
| padding-bottom: 10px; |
| font-size: 1.8em; |
| margin-top: 30px; |
| } |
| |
| .recipe-output h3 { |
| color: var(--secondary-color); |
| font-size: 1.4em; |
| margin-top: 25px; |
| } |
| |
| /* Form inputs styling */ |
| input[type="password"], input[type="text"] { |
| border: 2px solid #e9ecef; |
| border-radius: var(--border-radius); |
| padding: 15px; |
| font-size: 1em; |
| width: 100%; |
| transition: all 0.3s ease; |
| box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); |
| } |
| |
| input[type="password"]:focus, input[type="text"]:focus { |
| border-color: var(--secondary-color); |
| outline: none; |
| box-shadow: 0 0 0 3px rgba(78, 205, 196, 0.2); |
| } |
| |
| /* Custom dropdown styling */ |
| select { |
| appearance: none; |
| background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23FF6B6B' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E") no-repeat right 15px center; |
| border: 2px solid #e9ecef; |
| border-radius: var(--border-radius); |
| padding: 15px 45px 15px 15px; |
| font-size: 1em; |
| width: 100%; |
| transition: all 0.3s ease; |
| box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); |
| } |
| |
| select:focus { |
| border-color: var(--secondary-color); |
| outline: none; |
| box-shadow: 0 0 0 3px rgba(78, 205, 196, 0.2); |
| } |
| |
| /* Tooltip styling */ |
| .tooltip { |
| position: relative; |
| display: inline-block; |
| cursor: pointer; |
| } |
| |
| .tooltip .tooltiptext { |
| visibility: hidden; |
| width: 250px; |
| background-color: #333; |
| color: #fff; |
| text-align: center; |
| border-radius: 6px; |
| padding: 10px; |
| position: absolute; |
| z-index: 1; |
| bottom: 125%; |
| left: 50%; |
| margin-left: -125px; |
| opacity: 0; |
| transition: opacity 0.3s; |
| font-size: 0.9em; |
| box-shadow: 0 5px 15px rgba(0,0,0,0.2); |
| } |
| |
| .tooltip .tooltiptext::after { |
| content: ""; |
| position: absolute; |
| top: 100%; |
| left: 50%; |
| margin-left: -5px; |
| border-width: 5px; |
| border-style: solid; |
| border-color: #333 transparent transparent transparent; |
| } |
| |
| .tooltip:hover .tooltiptext { |
| visibility: visible; |
| opacity: 1; |
| } |
| |
| /* Media queries for responsive design */ |
| @media (max-width: 768px) { |
| .app-title { |
| font-size: 2.5em; |
| } |
| |
| .app-subtitle { |
| font-size: 1.1em; |
| } |
| |
| .input-section, .output-section { |
| padding: 20px; |
| } |
| |
| .section-header { |
| font-size: 1.5em; |
| } |
| |
| button.primary-button { |
| padding: 12px 25px; |
| font-size: 1.1em; |
| } |
| } |
| |
| @media (max-width: 480px) { |
| .app-title { |
| font-size: 2em; |
| } |
| |
| .app-subtitle { |
| font-size: 1em; |
| } |
| |
| .input-section, .output-section { |
| padding: 15px; |
| } |
| |
| .section-header { |
| font-size: 1.3em; |
| } |
| } |
| |
| /* Remove Gradio branding */ |
| .gradio-container { |
| max-width: 100% !important; |
| } |
| |
| footer.footer-links { |
| display: none !important; |
| } |
| """ |
|
|
| |
| html_header = """ |
| <div class="app-header"> |
| <div class="app-title">🍲 Visual Recipe Assistant</div> |
| <div class="app-subtitle">Upload images of ingredients you have on hand and get personalized recipe suggestions powered by AI</div> |
| </div> |
| |
| <div id="loading-overlay" class="loading-container"> |
| <div class="loading-spinner"> |
| <div class="loading-text">Generating your recipes...</div> |
| </div> |
| </div> |
| |
| <script> |
| // JavaScript to handle loading state |
| function showLoading() { |
| document.getElementById('loading-overlay').classList.add('visible'); |
| } |
| |
| function hideLoading() { |
| document.getElementById('loading-overlay').classList.remove('visible'); |
| } |
| </script> |
| """ |
|
|
| |
| html_footer = """ |
| <div class="footer"> |
| <div class="footer-content"> |
| <p><span class="footer-brand">🍲 Visual Recipe Assistant</span></p> |
| <p>Powered by Meta's Llama-Vision-Free Model & Together AI</p> |
| <p>Upload multiple ingredient images for more creative recipe combinations</p> |
| <div class="footer-links"> |
| <a href="#" target="_blank">How It Works</a> |
| <a href="#" target="_blank">Privacy Policy</a> |
| <a href="#" target="_blank">Contact Us</a> |
| </div> |
| </div> |
| </div> |
| |
| <script> |
| // Add event listener to the submit button |
| document.addEventListener('DOMContentLoaded', function() { |
| const submitBtn = document.querySelector('button.primary-button'); |
| if (submitBtn) { |
| submitBtn.addEventListener('click', function() { |
| showLoading(); |
| |
| // Hide loading after the response is received (this is approximate) |
| setTimeout(function() { |
| const output = document.querySelector('.recipe-output'); |
| if (output && output.textContent.trim().length > 0) { |
| hideLoading(); |
| } else { |
| // Check every second until content appears |
| const checkInterval = setInterval(function() { |
| if (output && output.textContent.trim().length > 0) { |
| hideLoading(); |
| clearInterval(checkInterval); |
| } |
| }, 1000); |
| |
| // Fallback: hide after 30 seconds regardless |
| setTimeout(function() { |
| hideLoading(); |
| clearInterval(checkInterval); |
| }, 30000); |
| } |
| }, 3000); |
| }); |
| } |
| }); |
| </script> |
| """ |
|
|
| |
| with gr.Blocks(css=custom_css) as app: |
| gr.HTML(html_header) |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| with gr.Group(elem_classes="input-section"): |
| gr.HTML('<h3 class="section-header"><i class="icon">🔑</i> API Configuration</h3>') |
| api_key_input = gr.Textbox( |
| label="Together API Key", |
| placeholder="Enter your Together API key here...", |
| type="password", |
| elem_classes="input-group", |
| info="Your API key will remain private and is used only for this session." |
| ) |
| |
| gr.HTML('<h3 class="section-header"><i class="icon">📷</i> Upload Ingredients</h3>') |
| |
| file_upload = gr.File( |
| label="Upload images of ingredients", |
| file_types=["image"], |
| file_count="multiple", |
| elem_classes="image-upload-container" |
| ) |
| |
| image_input = gr.Gallery( |
| label="Uploaded Ingredients", |
| elem_id="ingredient-gallery", |
| columns=3, |
| rows=2, |
| height="auto", |
| visible=False |
| ) |
| |
| gr.HTML('<h3 class="section-header"><i class="icon">⚙️</i> Recipe Preferences</h3>') |
| with gr.Row(): |
| num_recipes = gr.Slider( |
| minimum=1, |
| maximum=5, |
| value=3, |
| step=1, |
| label="Number of Recipe Suggestions", |
| elem_classes="input-group" |
| ) |
| |
| with gr.Row(): |
| with gr.Column(): |
| dietary_restrictions = gr.Dropdown( |
| choices=["None", "Vegetarian", "Vegan", "Gluten-Free", "Dairy-Free", "Low-Carb", "Keto", "Paleo"], |
| value="None", |
| label="Dietary Restrictions", |
| elem_classes="input-group" |
| ) |
| |
| with gr.Column(): |
| cuisine_preference = gr.Dropdown( |
| choices=["Any", "Italian", "Asian", "Mexican", "Mediterranean", "Indian", "American", "French", "Middle Eastern"], |
| value="Any", |
| label="Cuisine Preference", |
| elem_classes="input-group" |
| ) |
| |
| submit_button = gr.Button("Get Recipe Suggestions", elem_classes="primary-button") |
| |
| with gr.Column(scale=1): |
| with gr.Group(elem_classes="output-section"): |
| gr.HTML('<h3 class="section-header"><i class="icon">🍽️</i> Your Personalized Recipes</h3>') |
| output = gr.Markdown(elem_classes="recipe-output") |
| |
| gr.HTML(html_footer) |
| |
| |
| def update_gallery(files): |
| if not files: |
| return gr.Gallery.update(visible=False) |
| return gr.Gallery.update(value=[file.name for file in files], visible=True) |
| |
| file_upload.change(fn=update_gallery, inputs=file_upload, outputs=image_input) |
| |
| |
| def process_recipe_request(api_key, files, num_recipes, dietary_restrictions, cuisine_preference): |
| if not files: |
| return "Please upload at least one image of ingredients." |
| |
| |
| images = [file.name for file in files] |
| return get_recipe_suggestions(api_key, images, num_recipes, dietary_restrictions, cuisine_preference) |
| |
| |
| submit_button.click( |
| fn=process_recipe_request, |
| inputs=[api_key_input, file_upload, num_recipes, dietary_restrictions, cuisine_preference], |
| outputs=output |
| ) |
|
|
| |
| if __name__ == "__main__": |
| app.launch() |