Update app.py
Browse files
app.py
CHANGED
@@ -7,6 +7,7 @@ import json
|
|
7 |
import os
|
8 |
from together import Together
|
9 |
import tempfile
|
|
|
10 |
|
11 |
def encode_image_to_base64(image_path):
|
12 |
"""Convert image to base64 encoding"""
|
@@ -35,17 +36,31 @@ def save_uploaded_image(image):
|
|
35 |
|
36 |
return temp_file.name
|
37 |
|
38 |
-
def get_recipe_suggestions(api_key,
|
39 |
"""
|
40 |
-
Get recipe suggestions based on the uploaded
|
41 |
"""
|
42 |
-
if not api_key
|
43 |
-
return "Please provide
|
44 |
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
try:
|
51 |
# Initialize Together client with the provided API key
|
@@ -53,7 +68,8 @@ def get_recipe_suggestions(api_key, image, num_recipes=3, dietary_restrictions="
|
|
53 |
|
54 |
# Create the prompt for the model
|
55 |
system_prompt = """You are a culinary expert AI assistant that specializes in creating recipes based on available ingredients.
|
56 |
-
Analyze the provided
|
|
|
57 |
For each recipe suggestion, include:
|
58 |
1. Recipe name
|
59 |
2. Brief description of the dish
|
@@ -63,13 +79,25 @@ def get_recipe_suggestions(api_key, image, num_recipes=3, dietary_restrictions="
|
|
63 |
6. Difficulty level (Easy, Medium, Advanced)
|
64 |
7. Nutritional highlights
|
65 |
|
66 |
-
Consider any dietary restrictions mentioned by the user."""
|
67 |
|
68 |
-
user_prompt = f"""Based on the ingredients shown in
|
69 |
Dietary restrictions to consider: {dietary_restrictions}
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
-
# Create message with image
|
73 |
response = client.chat.completions.create(
|
74 |
model="meta-llama/Llama-Vision-Free",
|
75 |
messages=[
|
@@ -79,95 +107,362 @@ def get_recipe_suggestions(api_key, image, num_recipes=3, dietary_restrictions="
|
|
79 |
},
|
80 |
{
|
81 |
"role": "user",
|
82 |
-
"content":
|
83 |
-
{
|
84 |
-
"type": "text",
|
85 |
-
"text": user_prompt
|
86 |
-
},
|
87 |
-
{
|
88 |
-
"type": "image_url",
|
89 |
-
"image_url": {
|
90 |
-
"url": f"file://{image_path}"
|
91 |
-
}
|
92 |
-
}
|
93 |
-
]
|
94 |
}
|
95 |
],
|
96 |
max_tokens=2048,
|
97 |
temperature=0.7
|
98 |
)
|
99 |
|
100 |
-
# Clean up the temporary
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
|
|
105 |
|
106 |
return response.choices[0].message.content
|
107 |
|
108 |
except Exception as e:
|
109 |
-
# Clean up the temporary
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
|
|
114 |
return f"Error: {str(e)}"
|
115 |
|
116 |
-
#
|
117 |
-
|
118 |
-
|
119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
|
121 |
with gr.Row():
|
122 |
with gr.Column(scale=1):
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
type="filepath"
|
131 |
-
)
|
132 |
-
|
133 |
-
with gr.Row():
|
134 |
-
num_recipes = gr.Slider(
|
135 |
-
minimum=1,
|
136 |
-
maximum=5,
|
137 |
-
value=3,
|
138 |
-
step=1,
|
139 |
-
label="Number of Recipe Suggestions"
|
140 |
)
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
label="
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
)
|
146 |
-
|
147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
|
149 |
-
with gr.Column(scale=
|
150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
|
152 |
# Set up the submission action
|
153 |
submit_button.click(
|
154 |
-
fn=
|
155 |
-
inputs=[api_key_input,
|
156 |
outputs=output
|
157 |
)
|
158 |
-
|
159 |
-
gr.Markdown("""
|
160 |
-
## How to Use
|
161 |
-
1. Enter your Together API key
|
162 |
-
2. Upload an image of ingredients you have available
|
163 |
-
3. Adjust the number of recipes you'd like to receive
|
164 |
-
4. Select any dietary restrictions
|
165 |
-
5. Click "Get Recipe Suggestions"
|
166 |
-
|
167 |
-
## About
|
168 |
-
This app uses the Llama-Vision-Free multimodal model from Meta to analyze images of ingredients
|
169 |
-
and suggest creative recipes based on what it identifies.
|
170 |
-
""")
|
171 |
|
172 |
# Launch the app
|
173 |
if __name__ == "__main__":
|
|
|
7 |
import os
|
8 |
from together import Together
|
9 |
import tempfile
|
10 |
+
import uuid
|
11 |
|
12 |
def encode_image_to_base64(image_path):
|
13 |
"""Convert image to base64 encoding"""
|
|
|
36 |
|
37 |
return temp_file.name
|
38 |
|
39 |
+
def get_recipe_suggestions(api_key, images, num_recipes=3, dietary_restrictions="None", cuisine_preference="Any"):
|
40 |
"""
|
41 |
+
Get recipe suggestions based on the uploaded images of ingredients
|
42 |
"""
|
43 |
+
if not api_key:
|
44 |
+
return "Please provide your Together API key."
|
45 |
|
46 |
+
if not images or len(images) == 0 or all(img is None for img in images):
|
47 |
+
return "Please upload at least one image of ingredients."
|
48 |
+
|
49 |
+
# Filter out None values
|
50 |
+
valid_images = [img for img in images if img is not None]
|
51 |
+
|
52 |
+
if len(valid_images) == 0:
|
53 |
+
return "No valid images were uploaded. Please try again."
|
54 |
+
|
55 |
+
# Save all uploaded images
|
56 |
+
image_paths = []
|
57 |
+
for img in valid_images:
|
58 |
+
img_path = save_uploaded_image(img)
|
59 |
+
if img_path:
|
60 |
+
image_paths.append(img_path)
|
61 |
+
|
62 |
+
if not image_paths:
|
63 |
+
return "Failed to process the uploaded images."
|
64 |
|
65 |
try:
|
66 |
# Initialize Together client with the provided API key
|
|
|
68 |
|
69 |
# Create the prompt for the model
|
70 |
system_prompt = """You are a culinary expert AI assistant that specializes in creating recipes based on available ingredients.
|
71 |
+
Analyze the provided images of ingredients and suggest creative, detailed recipes that use as many of the shown ingredients as possible.
|
72 |
+
|
73 |
For each recipe suggestion, include:
|
74 |
1. Recipe name
|
75 |
2. Brief description of the dish
|
|
|
79 |
6. Difficulty level (Easy, Medium, Advanced)
|
80 |
7. Nutritional highlights
|
81 |
|
82 |
+
Consider any dietary restrictions and cuisine preferences mentioned by the user."""
|
83 |
|
84 |
+
user_prompt = f"""Based on the ingredients shown in these images, suggest {num_recipes} creative and delicious recipes.
|
85 |
Dietary restrictions to consider: {dietary_restrictions}
|
86 |
+
Cuisine preference: {cuisine_preference}
|
87 |
+
Please be specific about what ingredients you can identify in the images and creative with your recipe suggestions. Try to use ingredients from all images if possible."""
|
88 |
+
|
89 |
+
# Create message with multiple images
|
90 |
+
content = [{"type": "text", "text": user_prompt}]
|
91 |
+
|
92 |
+
# Add all images to the content
|
93 |
+
for img_path in image_paths:
|
94 |
+
content.append({
|
95 |
+
"type": "image_url",
|
96 |
+
"image_url": {
|
97 |
+
"url": f"file://{img_path}"
|
98 |
+
}
|
99 |
+
})
|
100 |
|
|
|
101 |
response = client.chat.completions.create(
|
102 |
model="meta-llama/Llama-Vision-Free",
|
103 |
messages=[
|
|
|
107 |
},
|
108 |
{
|
109 |
"role": "user",
|
110 |
+
"content": content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
}
|
112 |
],
|
113 |
max_tokens=2048,
|
114 |
temperature=0.7
|
115 |
)
|
116 |
|
117 |
+
# Clean up the temporary files
|
118 |
+
for img_path in image_paths:
|
119 |
+
try:
|
120 |
+
os.unlink(img_path)
|
121 |
+
except:
|
122 |
+
pass
|
123 |
|
124 |
return response.choices[0].message.content
|
125 |
|
126 |
except Exception as e:
|
127 |
+
# Clean up the temporary files in case of error
|
128 |
+
for img_path in image_paths:
|
129 |
+
try:
|
130 |
+
os.unlink(img_path)
|
131 |
+
except:
|
132 |
+
pass
|
133 |
return f"Error: {str(e)}"
|
134 |
|
135 |
+
# Custom CSS for a more appealing interface
|
136 |
+
custom_css = """
|
137 |
+
:root {
|
138 |
+
--primary-color: #FF6B6B;
|
139 |
+
--secondary-color: #4ECDC4;
|
140 |
+
--accent-color: #FFD166;
|
141 |
+
--background-color: #f8f9fa;
|
142 |
+
--text-color: #212529;
|
143 |
+
--card-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
144 |
+
--border-radius: 10px;
|
145 |
+
--font-family: 'Poppins', sans-serif;
|
146 |
+
}
|
147 |
+
|
148 |
+
body {
|
149 |
+
font-family: var(--font-family);
|
150 |
+
background-color: var(--background-color);
|
151 |
+
color: var(--text-color);
|
152 |
+
}
|
153 |
+
|
154 |
+
.container {
|
155 |
+
max-width: 1200px;
|
156 |
+
margin: 0 auto;
|
157 |
+
padding: 20px;
|
158 |
+
}
|
159 |
+
|
160 |
+
.app-header {
|
161 |
+
text-align: center;
|
162 |
+
margin-bottom: 30px;
|
163 |
+
padding: 30px 0;
|
164 |
+
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
165 |
+
border-radius: var(--border-radius);
|
166 |
+
color: white;
|
167 |
+
box-shadow: var(--card-shadow);
|
168 |
+
}
|
169 |
+
|
170 |
+
.app-title {
|
171 |
+
font-size: 3em;
|
172 |
+
margin-bottom: 10px;
|
173 |
+
font-weight: bold;
|
174 |
+
}
|
175 |
+
|
176 |
+
.app-subtitle {
|
177 |
+
font-size: 1.2em;
|
178 |
+
opacity: 0.9;
|
179 |
+
max-width: 700px;
|
180 |
+
margin: 0 auto;
|
181 |
+
}
|
182 |
+
|
183 |
+
.input-section, .output-section {
|
184 |
+
background-color: white;
|
185 |
+
border-radius: var(--border-radius);
|
186 |
+
padding: 25px;
|
187 |
+
box-shadow: var(--card-shadow);
|
188 |
+
margin-bottom: 20px;
|
189 |
+
}
|
190 |
+
|
191 |
+
.input-section h3, .output-section h3 {
|
192 |
+
color: var(--primary-color);
|
193 |
+
margin-top: 0;
|
194 |
+
font-size: 1.5em;
|
195 |
+
border-bottom: 2px solid var(--secondary-color);
|
196 |
+
padding-bottom: 10px;
|
197 |
+
margin-bottom: 20px;
|
198 |
+
}
|
199 |
+
|
200 |
+
.image-upload-container {
|
201 |
+
border: 2px dashed var(--secondary-color);
|
202 |
+
border-radius: var(--border-radius);
|
203 |
+
padding: 20px;
|
204 |
+
text-align: center;
|
205 |
+
margin-bottom: 20px;
|
206 |
+
transition: all 0.3s ease;
|
207 |
+
}
|
208 |
+
|
209 |
+
.image-upload-container:hover {
|
210 |
+
border-color: var(--primary-color);
|
211 |
+
background-color: rgba(255, 107, 107, 0.05);
|
212 |
+
}
|
213 |
+
|
214 |
+
button.primary-button {
|
215 |
+
background: linear-gradient(135deg, var(--primary-color) 0%, #FF8E8E 100%);
|
216 |
+
color: white;
|
217 |
+
border: none;
|
218 |
+
padding: 12px 25px;
|
219 |
+
border-radius: 30px;
|
220 |
+
font-size: 1.1em;
|
221 |
+
cursor: pointer;
|
222 |
+
transition: all 0.3s ease;
|
223 |
+
box-shadow: 0 2px 5px rgba(255, 107, 107, 0.3);
|
224 |
+
font-weight: bold;
|
225 |
+
display: block;
|
226 |
+
width: 100%;
|
227 |
+
margin-top: 20px;
|
228 |
+
}
|
229 |
+
|
230 |
+
button.primary-button:hover {
|
231 |
+
transform: translateY(-2px);
|
232 |
+
box-shadow: 0 4px 8px rgba(255, 107, 107, 0.4);
|
233 |
+
background: linear-gradient(135deg, #FF8E8E 0%, var(--primary-color) 100%);
|
234 |
+
}
|
235 |
+
|
236 |
+
.gradio-slider.svelte-17l1npl {
|
237 |
+
margin-bottom: 20px;
|
238 |
+
}
|
239 |
+
|
240 |
+
.tab-nav {
|
241 |
+
background-color: var(--secondary-color);
|
242 |
+
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
243 |
+
}
|
244 |
+
|
245 |
+
.tab-nav button {
|
246 |
+
color: white;
|
247 |
+
font-weight: bold;
|
248 |
+
}
|
249 |
+
|
250 |
+
.recipe-card {
|
251 |
+
border-left: 5px solid var(--accent-color);
|
252 |
+
padding: 15px;
|
253 |
+
background-color: #f9f9f9;
|
254 |
+
margin-bottom: 15px;
|
255 |
+
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
256 |
+
}
|
257 |
+
|
258 |
+
.recipe-title {
|
259 |
+
color: var(--primary-color);
|
260 |
+
font-size: 1.3em;
|
261 |
+
margin-bottom: 5px;
|
262 |
+
}
|
263 |
+
|
264 |
+
.footer {
|
265 |
+
text-align: center;
|
266 |
+
margin-top: 40px;
|
267 |
+
color: #6c757d;
|
268 |
+
font-size: 0.9em;
|
269 |
+
}
|
270 |
+
|
271 |
+
.icon {
|
272 |
+
color: var(--primary-color);
|
273 |
+
margin-right: 5px;
|
274 |
+
}
|
275 |
+
|
276 |
+
.input-group {
|
277 |
+
margin-bottom: 20px;
|
278 |
+
}
|
279 |
+
|
280 |
+
.input-group label {
|
281 |
+
display: block;
|
282 |
+
margin-bottom: 8px;
|
283 |
+
font-weight: 600;
|
284 |
+
color: var(--text-color);
|
285 |
+
}
|
286 |
+
|
287 |
+
.gallery-item {
|
288 |
+
border-radius: var(--border-radius);
|
289 |
+
overflow: hidden;
|
290 |
+
box-shadow: var(--card-shadow);
|
291 |
+
transition: transform 0.3s ease;
|
292 |
+
}
|
293 |
+
|
294 |
+
.gallery-item:hover {
|
295 |
+
transform: scale(1.02);
|
296 |
+
}
|
297 |
+
|
298 |
+
.loading-spinner {
|
299 |
+
text-align: center;
|
300 |
+
padding: 20px;
|
301 |
+
}
|
302 |
+
|
303 |
+
/* Responsive styles */
|
304 |
+
@media (max-width: 768px) {
|
305 |
+
.app-title {
|
306 |
+
font-size: 2em;
|
307 |
+
}
|
308 |
+
|
309 |
+
.input-section, .output-section {
|
310 |
+
padding: 15px;
|
311 |
+
}
|
312 |
+
}
|
313 |
+
|
314 |
+
/* Custom styling for the API key input */
|
315 |
+
input[type="password"] {
|
316 |
+
border: 2px solid #e9ecef;
|
317 |
+
border-radius: var(--border-radius);
|
318 |
+
padding: 10px 15px;
|
319 |
+
font-size: 1em;
|
320 |
+
width: 100%;
|
321 |
+
transition: border-color 0.3s ease;
|
322 |
+
}
|
323 |
+
|
324 |
+
input[type="password"]:focus {
|
325 |
+
border-color: var(--secondary-color);
|
326 |
+
outline: none;
|
327 |
+
}
|
328 |
+
|
329 |
+
/* Custom dropdown styling */
|
330 |
+
select {
|
331 |
+
appearance: none;
|
332 |
+
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 10px center;
|
333 |
+
border: 2px solid #e9ecef;
|
334 |
+
border-radius: var(--border-radius);
|
335 |
+
padding: 10px 40px 10px 15px;
|
336 |
+
font-size: 1em;
|
337 |
+
width: 100%;
|
338 |
+
transition: border-color 0.3s ease;
|
339 |
+
}
|
340 |
+
|
341 |
+
select:focus {
|
342 |
+
border-color: var(--secondary-color);
|
343 |
+
outline: none;
|
344 |
+
}
|
345 |
+
|
346 |
+
/* Remove Gradio branding */
|
347 |
+
.gradio-container {
|
348 |
+
max-width: 100% !important;
|
349 |
+
}
|
350 |
+
|
351 |
+
.footer-logo, .footer-links {
|
352 |
+
display: none !important;
|
353 |
+
}
|
354 |
+
"""
|
355 |
+
|
356 |
+
# Custom HTML header
|
357 |
+
html_header = """
|
358 |
+
<div class="app-header">
|
359 |
+
<div class="app-title">π² Visual Recipe Assistant</div>
|
360 |
+
<div class="app-subtitle">Upload images of ingredients you have on hand and get personalized recipe suggestions powered by AI</div>
|
361 |
+
</div>
|
362 |
+
"""
|
363 |
+
|
364 |
+
# Custom HTML footer
|
365 |
+
html_footer = """
|
366 |
+
<div class="footer">
|
367 |
+
<p>π§ͺ Powered by Meta's Llama-Vision-Free Model & Together AI</p>
|
368 |
+
<p>πΈ Upload multiple ingredient images for more creative recipe combinations</p>
|
369 |
+
</div>
|
370 |
+
"""
|
371 |
+
|
372 |
+
# Create the Gradio interface with improved design
|
373 |
+
with gr.Blocks(css=custom_css) as app:
|
374 |
+
gr.HTML(html_header)
|
375 |
|
376 |
with gr.Row():
|
377 |
with gr.Column(scale=1):
|
378 |
+
with gr.Box(elem_classes="input-section"):
|
379 |
+
gr.HTML("<h3>π API Configuration</h3>")
|
380 |
+
api_key_input = gr.Textbox(
|
381 |
+
label="Together API Key",
|
382 |
+
placeholder="Enter your Together API key here...",
|
383 |
+
type="password",
|
384 |
+
elem_classes="input-group"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
385 |
)
|
386 |
+
|
387 |
+
gr.HTML("<h3>π· Upload Ingredients</h3>")
|
388 |
+
image_input = gr.Gallery(
|
389 |
+
label="",
|
390 |
+
elem_id="ingredient-gallery",
|
391 |
+
elem_classes="gallery-container",
|
392 |
+
columns=3,
|
393 |
+
rows=2,
|
394 |
+
height="auto",
|
395 |
+
object_fit="contain"
|
396 |
)
|
397 |
+
|
398 |
+
# Use File component to handle multiple image uploads
|
399 |
+
file_upload = gr.File(
|
400 |
+
label="Upload images of ingredients",
|
401 |
+
file_types=["image"],
|
402 |
+
file_count="multiple",
|
403 |
+
elem_classes="image-upload-container"
|
404 |
+
)
|
405 |
+
|
406 |
+
gr.HTML("<h3>βοΈ Recipe Preferences</h3>")
|
407 |
+
with gr.Row():
|
408 |
+
num_recipes = gr.Slider(
|
409 |
+
minimum=1,
|
410 |
+
maximum=5,
|
411 |
+
value=3,
|
412 |
+
step=1,
|
413 |
+
label="Number of Recipe Suggestions",
|
414 |
+
elem_classes="input-group"
|
415 |
+
)
|
416 |
+
|
417 |
+
with gr.Row():
|
418 |
+
with gr.Column():
|
419 |
+
dietary_restrictions = gr.Dropdown(
|
420 |
+
choices=["None", "Vegetarian", "Vegan", "Gluten-Free", "Dairy-Free", "Low-Carb", "Keto", "Paleo"],
|
421 |
+
value="None",
|
422 |
+
label="Dietary Restrictions",
|
423 |
+
elem_classes="input-group"
|
424 |
+
)
|
425 |
+
|
426 |
+
with gr.Column():
|
427 |
+
cuisine_preference = gr.Dropdown(
|
428 |
+
choices=["Any", "Italian", "Asian", "Mexican", "Mediterranean", "Indian", "American", "French", "Middle Eastern"],
|
429 |
+
value="Any",
|
430 |
+
label="Cuisine Preference",
|
431 |
+
elem_classes="input-group"
|
432 |
+
)
|
433 |
+
|
434 |
+
submit_button = gr.Button("Get Recipe Suggestions", elem_classes="primary-button")
|
435 |
|
436 |
+
with gr.Column(scale=1):
|
437 |
+
with gr.Box(elem_classes="output-section"):
|
438 |
+
gr.HTML("<h3>π½οΈ Your Personalized Recipes</h3>")
|
439 |
+
output = gr.Markdown(elem_classes="recipe-output")
|
440 |
+
|
441 |
+
gr.HTML(html_footer)
|
442 |
+
|
443 |
+
# Handle file uploads to display in gallery
|
444 |
+
def update_gallery(files):
|
445 |
+
if not files:
|
446 |
+
return None
|
447 |
+
return [file.name for file in files]
|
448 |
+
|
449 |
+
file_upload.change(fn=update_gallery, inputs=file_upload, outputs=image_input)
|
450 |
+
|
451 |
+
# Handle recipe generation
|
452 |
+
def process_recipe_request(api_key, files, num_recipes, dietary_restrictions, cuisine_preference):
|
453 |
+
if not files:
|
454 |
+
return "Please upload at least one image of ingredients."
|
455 |
+
|
456 |
+
# Get actual image files from the uploaded files
|
457 |
+
images = [file.name for file in files]
|
458 |
+
return get_recipe_suggestions(api_key, images, num_recipes, dietary_restrictions, cuisine_preference)
|
459 |
|
460 |
# Set up the submission action
|
461 |
submit_button.click(
|
462 |
+
fn=process_recipe_request,
|
463 |
+
inputs=[api_key_input, file_upload, num_recipes, dietary_restrictions, cuisine_preference],
|
464 |
outputs=output
|
465 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
466 |
|
467 |
# Launch the app
|
468 |
if __name__ == "__main__":
|