shukdevdatta123 commited on
Commit
e208e50
Β·
verified Β·
1 Parent(s): b5068f0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +374 -79
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, image, num_recipes=3, dietary_restrictions="None"):
39
  """
40
- Get recipe suggestions based on the uploaded image of ingredients
41
  """
42
- if not api_key or not image:
43
- return "Please provide both an API key and an image of ingredients."
44
 
45
- # Save the uploaded image
46
- image_path = save_uploaded_image(image)
47
- if not image_path:
48
- return "Failed to process the uploaded image."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 image of ingredients and suggest creative, detailed recipes.
 
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 this image, suggest {num_recipes} creative and delicious recipes.
69
  Dietary restrictions to consider: {dietary_restrictions}
70
- Please be specific about what ingredients you can identify in the image and creative with your recipe suggestions."""
 
 
 
 
 
 
 
 
 
 
 
 
 
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 file
101
- try:
102
- os.unlink(image_path)
103
- except:
104
- pass
 
105
 
106
  return response.choices[0].message.content
107
 
108
  except Exception as e:
109
- # Clean up the temporary file in case of error
110
- try:
111
- os.unlink(image_path)
112
- except:
113
- pass
 
114
  return f"Error: {str(e)}"
115
 
116
- # Create the Gradio interface
117
- with gr.Blocks(title="Visual Recipe Assistant") as app:
118
- gr.Markdown("# 🍲 Visual Recipe Assistant")
119
- gr.Markdown("Upload an image of ingredients you have, and get creative recipe suggestions!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
  with gr.Row():
122
  with gr.Column(scale=1):
123
- api_key_input = gr.Textbox(
124
- label="Together API Key",
125
- placeholder="Enter your Together API key here...",
126
- type="password"
127
- )
128
- image_input = gr.Image(
129
- label="Upload Image of Ingredients",
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
- dietary_restrictions = gr.Dropdown(
142
- choices=["None", "Vegetarian", "Vegan", "Gluten-Free", "Dairy-Free", "Low-Carb", "Keto"],
143
- value="None",
144
- label="Dietary Restrictions"
 
 
 
 
 
 
145
  )
146
-
147
- submit_button = gr.Button("Get Recipe Suggestions", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- with gr.Column(scale=2):
150
- output = gr.Markdown(label="Recipe Suggestions")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
  # Set up the submission action
153
  submit_button.click(
154
- fn=get_recipe_suggestions,
155
- inputs=[api_key_input, image_input, num_recipes, dietary_restrictions],
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__":