shukdevdatta123 commited on
Commit
2c61df6
·
verified ·
1 Parent(s): 1fde736

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +172 -206
app.py CHANGED
@@ -17,11 +17,11 @@ def encode_image_to_base64(image_path):
17
  def analyze_single_image(client, img_path):
18
  """Analyze a single image to identify ingredients"""
19
  system_prompt = """You are a culinary expert AI assistant that specializes in identifying ingredients in images.
20
- Your task is to analyze the provided image and list all the food ingredients you can identify.
21
- Be specific and detailed about what you see. Only list ingredients, don't suggest recipes yet."""
22
-
23
  user_prompt = "Please identify all the food ingredients visible in this image. List each ingredient on a new line."
24
-
25
  try:
26
  with open(img_path, "rb") as image_file:
27
  base64_image = base64.b64encode(image_file.read()).decode('utf-8')
@@ -54,10 +54,10 @@ def get_recipe_suggestions(api_key, image_paths, num_recipes=3, dietary_restrict
54
  """Get recipe suggestions based on the uploaded images of ingredients"""
55
  if not api_key:
56
  return "Please provide your Together API key."
57
-
58
  if not image_paths or len(image_paths) == 0:
59
  return "Please upload at least one image of ingredients."
60
-
61
  try:
62
  client = Together(api_key=api_key)
63
 
@@ -122,222 +122,197 @@ def update_gallery(files):
122
  def process_recipe_request(api_key, files, num_recipes, dietary_restrictions, cuisine_preference):
123
  """Process the recipe request with uploaded files"""
124
  if not files:
125
- return "Please upload at least one image of ingredients."
126
- return get_recipe_suggestions(api_key, files, num_recipes, dietary_restrictions, cuisine_preference)
 
 
 
127
 
128
  custom_css = """
129
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
130
-
131
  :root {
132
- --primary-color: #FF6F61; /* Warm coral */
133
- --secondary-color: #4BB543; /* Fresh green */
134
- --accent-color: #F0A500; /* Golden yellow */
135
- --background-color: #F4F4F9; /* Light grayish background */
136
- --text-color: #333333; /* Dark gray text */
137
- --card-background: #FFFFFF; /* White for cards */
138
- --border-radius: 12px;
139
- --font-family: 'Poppins', sans-serif;
140
- --box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 20px;
141
- --hover-shadow: rgba(0, 0, 0, 0.15) 0px 8px 30px;
142
  }
143
-
144
  body {
145
- font-family: var(--font-family);
146
- background-color: var(--background-color);
147
- color: var(--text-color);
148
- margin: 0;
149
- padding: 0;
150
  }
151
-
152
  .container {
153
- max-width: 1200px;
154
- margin: 0 auto;
155
- padding: 20px;
156
  }
157
-
158
  .app-header {
159
- background-color: var(--primary-color);
160
- color: white;
161
- padding: 60px 20px;
162
- text-align: center;
163
- border-radius: 0 0 30px 30px;
164
- box-shadow: var(--box-shadow);
165
  }
166
-
167
  .app-title {
168
- font-size: 2.8em;
169
- font-weight: 700;
170
- margin-bottom: 10px;
171
  }
172
-
173
  .app-subtitle {
174
- font-size: 1.3em;
175
- font-weight: 300;
176
- max-width: 800px;
177
- margin: 0 auto;
178
  }
179
-
180
  .input-section, .output-section {
181
- background-color: var(--card-background);
182
- border-radius: var(--border-radius);
183
- padding: 30px;
184
- box-shadow: var(--box-shadow);
185
- margin-bottom: 30px;
186
  }
187
-
188
  .section-header {
189
- font-size: 1.6em;
190
- font-weight: 600;
191
- color: var(--text-color);
192
- margin-bottom: 20px;
193
- border-bottom: 2px solid var(--primary-color);
194
- padding-bottom: 10px;
195
  }
196
-
197
  .image-upload-container {
198
- border: 2px dashed var(--secondary-color);
199
- border-radius: var(--border-radius);
200
- padding: 40px;
201
- text-align: center;
202
- background-color: rgba(75, 181, 67, 0.1);
203
- transition: all 0.3s ease;
204
  }
205
-
206
  .image-upload-container:hover {
207
- border-color: var(--primary-color);
208
- background-color: rgba(255, 111, 97, 0.1);
209
  }
210
-
211
  button.primary-button {
212
- background-color: var(--primary-color);
213
- color: white;
214
- border: none;
215
- padding: 16px 32px;
216
- border-radius: 6px;
217
- font-size: 1.1em;
218
- cursor: pointer;
219
- transition: all 0.3s ease;
220
- width: 100%;
221
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
222
  }
223
-
224
  button.primary-button:hover {
225
- background-color: #E15F52;
226
- box-shadow: var(--hover-shadow);
227
  }
228
-
229
  .gallery-container {
230
- display: grid;
231
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
232
- gap: 20px;
233
- margin-top: 30px;
234
  }
235
-
236
  .gallery-item {
237
- border-radius: var(--border-radius);
238
- overflow: hidden;
239
- box-shadow: var(--box-shadow);
240
- transition: transform 0.3s ease;
241
- aspect-ratio: 1 / 1;
242
- object-fit: cover;
243
  }
244
-
245
  .gallery-item:hover {
246
- transform: scale(1.05);
247
- box-shadow: var(--hover-shadow);
248
  }
249
-
250
  .recipe-output {
251
- font-size: 1.2em;
252
- line-height: 1.7;
253
- color: var(--text-color);
254
- max-height: 600px;
255
- overflow-y: auto;
256
- padding-right: 15px;
257
  }
258
-
259
  .recipe-output h2 {
260
- color: var(--primary-color);
261
- margin-top: 30px;
262
- font-size: 2em;
263
  }
264
-
265
  .recipe-output h3 {
266
- color: var(--secondary-color);
267
- font-size: 1.5em;
268
- margin-top: 20px;
269
  }
270
-
271
  .loading-container {
272
- display: flex;
273
- flex-direction: column;
274
- justify-content: center;
275
- align-items: center;
276
- position: fixed;
277
- top: 0;
278
- left: 0;
279
- width: 100%;
280
- height: 100%;
281
- background-color: rgba(0, 0, 0, 0.5);
282
- z-index: 1000;
283
- opacity: 0;
284
- visibility: hidden;
285
- transition: opacity 0.3s ease, visibility 0.3s ease;
286
  }
287
-
288
  .loading-container.visible {
289
- opacity: 1;
290
- visibility: visible;
291
  }
292
-
293
  .loading-spinner {
294
- border: 8px solid #f3f3f3;
295
- border-top: 8px solid var(--primary-color);
296
- border-radius: 50%;
297
- width: 60px;
298
- height: 60px;
299
- animation: spin 1s linear infinite;
300
  }
301
-
302
  @keyframes spin {
303
- 0% { transform: rotate(0deg); }
304
- 100% { transform: rotate(360deg); }
305
  }
306
-
307
  .loading-text {
308
- color: white;
309
- font-size: 1.3em;
310
- margin-top: 20px;
311
  }
312
-
313
  .footer {
314
- background-color: var(--card-background);
315
- padding: 40px 20px;
316
- text-align: center;
317
- color: var(--text-color);
318
- font-size: 1.1em;
319
- box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
320
  }
321
-
322
  .footer-content {
323
- max-width: 800px;
324
- margin: 0 auto;
325
  }
326
-
327
  .footer-brand {
328
- font-weight: 700;
329
- color: var(--primary-color);
330
  }
331
-
332
  .footer-links a {
333
- color: var(--secondary-color);
334
- text-decoration: none;
335
- margin: 0 15px;
336
- transition: color 0.3s ease;
337
  }
338
-
339
  .footer-links a:hover {
340
- color: var(--primary-color);
341
  }
342
  """
343
 
@@ -346,19 +321,6 @@ html_header = """
346
  <div class="app-title">🍲 Visual Recipe Assistant</div>
347
  <div class="app-subtitle">Upload images of ingredients you have on hand and get personalized recipe suggestions powered by AI</div>
348
  </div>
349
- <div id="loading-overlay" class="loading-container">
350
- <div class="loading-spinner"></div>
351
- <div class="loading-text">Generating your recipes...</div>
352
- </div>
353
- <script>
354
- function showLoading() {
355
- document.getElementById('loading-overlay').classList.add('visible');
356
- }
357
-
358
- function hideLoading() {
359
- document.getElementById('loading-overlay').classList.remove('visible');
360
- }
361
- </script>
362
  """
363
 
364
  html_footer = """
@@ -374,38 +336,14 @@ html_footer = """
374
  </div>
375
  </div>
376
  </div>
377
- <script>
378
- document.addEventListener('DOMContentLoaded', function() {
379
- const submitBtn = document.querySelector('button.primary-button');
380
- if (submitBtn) {
381
- submitBtn.addEventListener('click', function() {
382
- showLoading();
383
- setTimeout(function() {
384
- const output = document.querySelector('.recipe-output');
385
- if (output && output.textContent.trim().length > 0) {
386
- hideLoading();
387
- } else {
388
- const checkInterval = setInterval(function() {
389
- if (output && output.textContent.trim().length > 0) {
390
- hideLoading();
391
- clearInterval(checkInterval);
392
- }
393
- }, 1000);
394
- setTimeout(function() {
395
- hideLoading();
396
- clearInterval(checkInterval);
397
- }, 30000);
398
- }
399
- }, 3000);
400
- });
401
- }
402
- });
403
- </script>
404
  """
405
 
406
  with gr.Blocks(css=custom_css) as app:
407
  gr.HTML(html_header)
408
 
 
 
 
409
  with gr.Row():
410
  with gr.Column(scale=1):
411
  with gr.Group(elem_classes="input-section"):
@@ -469,14 +407,42 @@ with gr.Blocks(css=custom_css) as app:
469
  gr.HTML('<h3 class="section-header">Your Personalized Recipes</h3>')
470
  output = gr.Markdown(elem_classes="recipe-output")
471
 
472
- gr.HTML(html_footer)
 
 
 
 
 
 
 
 
 
 
 
 
 
473
 
 
 
 
474
  file_upload.change(fn=update_gallery, inputs=file_upload, outputs=image_input)
475
 
 
476
  submit_button.click(
 
 
 
 
 
 
 
477
  fn=process_recipe_request,
478
  inputs=[api_key_input, file_upload, num_recipes, dietary_restrictions, cuisine_preference],
479
- outputs=output
 
 
 
 
480
  )
481
 
482
  if __name__ == "__main__":
 
17
  def analyze_single_image(client, img_path):
18
  """Analyze a single image to identify ingredients"""
19
  system_prompt = """You are a culinary expert AI assistant that specializes in identifying ingredients in images.
20
+ Your task is to analyze the provided image and list all the food ingredients you can identify.
21
+ Be specific and detailed about what you see. Only list ingredients, don't suggest recipes yet."""
22
+
23
  user_prompt = "Please identify all the food ingredients visible in this image. List each ingredient on a new line."
24
+
25
  try:
26
  with open(img_path, "rb") as image_file:
27
  base64_image = base64.b64encode(image_file.read()).decode('utf-8')
 
54
  """Get recipe suggestions based on the uploaded images of ingredients"""
55
  if not api_key:
56
  return "Please provide your Together API key."
57
+
58
  if not image_paths or len(image_paths) == 0:
59
  return "Please upload at least one image of ingredients."
60
+
61
  try:
62
  client = Together(api_key=api_key)
63
 
 
122
  def process_recipe_request(api_key, files, num_recipes, dietary_restrictions, cuisine_preference):
123
  """Process the recipe request with uploaded files"""
124
  if not files:
125
+ return "Please upload at least one image of ingredients.", gr.update(value=False)
126
+
127
+ recipes = get_recipe_suggestions(api_key, files, num_recipes, dietary_restrictions, cuisine_preference)
128
+ # Return both the recipe result and False to indicate loading is complete
129
+ return recipes, gr.update(value=False)
130
 
131
  custom_css = """
132
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
 
133
  :root {
134
+ --primary-color: #FF6F61; /* Warm coral */
135
+ --secondary-color: #4BB543; /* Fresh green */
136
+ --accent-color: #F0A500; /* Golden yellow */
137
+ --background-color: #F4F4F9; /* Light grayish background */
138
+ --text-color: #333333; /* Dark gray text */
139
+ --card-background: #FFFFFF; /* White for cards */
140
+ --border-radius: 12px;
141
+ --font-family: 'Poppins', sans-serif;
142
+ --box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 20px;
143
+ --hover-shadow: rgba(0, 0, 0, 0.15) 0px 8px 30px;
144
  }
 
145
  body {
146
+ font-family: var(--font-family);
147
+ background-color: var(--background-color);
148
+ color: var(--text-color);
149
+ margin: 0;
150
+ padding: 0;
151
  }
 
152
  .container {
153
+ max-width: 1200px;
154
+ margin: 0 auto;
155
+ padding: 20px;
156
  }
 
157
  .app-header {
158
+ background-color: var(--primary-color);
159
+ color: white;
160
+ padding: 60px 20px;
161
+ text-align: center;
162
+ border-radius: 0 0 30px 30px;
163
+ box-shadow: var(--box-shadow);
164
  }
 
165
  .app-title {
166
+ font-size: 2.8em;
167
+ font-weight: 700;
168
+ margin-bottom: 10px;
169
  }
 
170
  .app-subtitle {
171
+ font-size: 1.3em;
172
+ font-weight: 300;
173
+ max-width: 800px;
174
+ margin: 0 auto;
175
  }
 
176
  .input-section, .output-section {
177
+ background-color: var(--card-background);
178
+ border-radius: var(--border-radius);
179
+ padding: 30px;
180
+ box-shadow: var(--box-shadow);
181
+ margin-bottom: 30px;
182
  }
 
183
  .section-header {
184
+ font-size: 1.6em;
185
+ font-weight: 600;
186
+ color: var(--text-color);
187
+ margin-bottom: 20px;
188
+ border-bottom: 2px solid var(--primary-color);
189
+ padding-bottom: 10px;
190
  }
 
191
  .image-upload-container {
192
+ border: 2px dashed var(--secondary-color);
193
+ border-radius: var(--border-radius);
194
+ padding: 40px;
195
+ text-align: center;
196
+ background-color: rgba(75, 181, 67, 0.1);
197
+ transition: all 0.3s ease;
198
  }
 
199
  .image-upload-container:hover {
200
+ border-color: var(--primary-color);
201
+ background-color: rgba(255, 111, 97, 0.1);
202
  }
 
203
  button.primary-button {
204
+ background-color: var(--primary-color);
205
+ color: white;
206
+ border: none;
207
+ padding: 16px 32px;
208
+ border-radius: 6px;
209
+ font-size: 1.1em;
210
+ cursor: pointer;
211
+ transition: all 0.3s ease;
212
+ width: 100%;
213
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
214
  }
 
215
  button.primary-button:hover {
216
+ background-color: #E15F52;
217
+ box-shadow: var(--hover-shadow);
218
  }
 
219
  .gallery-container {
220
+ display: grid;
221
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
222
+ gap: 20px;
223
+ margin-top: 30px;
224
  }
 
225
  .gallery-item {
226
+ border-radius: var(--border-radius);
227
+ overflow: hidden;
228
+ box-shadow: var(--box-shadow);
229
+ transition: transform 0.3s ease;
230
+ aspect-ratio: 1 / 1;
231
+ object-fit: cover;
232
  }
 
233
  .gallery-item:hover {
234
+ transform: scale(1.05);
235
+ box-shadow: var(--hover-shadow);
236
  }
 
237
  .recipe-output {
238
+ font-size: 1.2em;
239
+ line-height: 1.7;
240
+ color: var(--text-color);
241
+ max-height: 600px;
242
+ overflow-y: auto;
243
+ padding-right: 15px;
244
  }
 
245
  .recipe-output h2 {
246
+ color: var(--primary-color);
247
+ margin-top: 30px;
248
+ font-size: 2em;
249
  }
 
250
  .recipe-output h3 {
251
+ color: var(--secondary-color);
252
+ font-size: 1.5em;
253
+ margin-top: 20px;
254
  }
 
255
  .loading-container {
256
+ display: flex;
257
+ flex-direction: column;
258
+ justify-content: center;
259
+ align-items: center;
260
+ position: fixed;
261
+ top: 0;
262
+ left: 0;
263
+ width: 100%;
264
+ height: 100%;
265
+ background-color: rgba(0, 0, 0, 0.5);
266
+ z-index: 1000;
267
+ opacity: 0;
268
+ visibility: hidden;
269
+ transition: opacity 0.3s ease, visibility 0.3s ease;
270
  }
 
271
  .loading-container.visible {
272
+ opacity: 1;
273
+ visibility: visible;
274
  }
 
275
  .loading-spinner {
276
+ border: 8px solid #f3f3f3;
277
+ border-top: 8px solid var(--primary-color);
278
+ border-radius: 50%;
279
+ width: 60px;
280
+ height: 60px;
281
+ animation: spin 1s linear infinite;
282
  }
 
283
  @keyframes spin {
284
+ 0% { transform: rotate(0deg); }
285
+ 100% { transform: rotate(360deg); }
286
  }
 
287
  .loading-text {
288
+ color: white;
289
+ font-size: 1.3em;
290
+ margin-top: 20px;
291
  }
 
292
  .footer {
293
+ background-color: var(--card-background);
294
+ padding: 40px 20px;
295
+ text-align: center;
296
+ color: var(--text-color);
297
+ font-size: 1.1em;
298
+ box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
299
  }
 
300
  .footer-content {
301
+ max-width: 800px;
302
+ margin: 0 auto;
303
  }
 
304
  .footer-brand {
305
+ font-weight: 700;
306
+ color: var(--primary-color);
307
  }
 
308
  .footer-links a {
309
+ color: var(--secondary-color);
310
+ text-decoration: none;
311
+ margin: 0 15px;
312
+ transition: color 0.3s ease;
313
  }
 
314
  .footer-links a:hover {
315
+ color: var(--primary-color);
316
  }
317
  """
318
 
 
321
  <div class="app-title">🍲 Visual Recipe Assistant</div>
322
  <div class="app-subtitle">Upload images of ingredients you have on hand and get personalized recipe suggestions powered by AI</div>
323
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  """
325
 
326
  html_footer = """
 
336
  </div>
337
  </div>
338
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  """
340
 
341
  with gr.Blocks(css=custom_css) as app:
342
  gr.HTML(html_header)
343
 
344
+ # Add a state variable to track loading state
345
+ is_loading = gr.State(False)
346
+
347
  with gr.Row():
348
  with gr.Column(scale=1):
349
  with gr.Group(elem_classes="input-section"):
 
407
  gr.HTML('<h3 class="section-header">Your Personalized Recipes</h3>')
408
  output = gr.Markdown(elem_classes="recipe-output")
409
 
410
+ # Loading overlay with spinner
411
+ with gr.Row(visible=False) as loading_overlay:
412
+ gr.HTML("""
413
+ <div style="display: flex; flex-direction: column; justify-content: center; align-items: center; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 1000;">
414
+ <div style="border: 8px solid #f3f3f3; border-top: 8px solid #FF6F61; border-radius: 50%; width: 60px; height: 60px; animation: spin 1s linear infinite;"></div>
415
+ <div style="color: white; font-size: 1.3em; margin-top: 20px;">Generating your recipes...</div>
416
+ </div>
417
+ <style>
418
+ @keyframes spin {
419
+ 0% { transform: rotate(0deg); }
420
+ 100% { transform: rotate(360deg); }
421
+ }
422
+ </style>
423
+ """)
424
 
425
+ gr.HTML(html_footer)
426
+
427
+ # Update gallery when files are uploaded
428
  file_upload.change(fn=update_gallery, inputs=file_upload, outputs=image_input)
429
 
430
+ # Show loading overlay when submit button is clicked
431
  submit_button.click(
432
+ fn=lambda: True,
433
+ outputs=is_loading
434
+ ).then(
435
+ fn=lambda x: gr.update(visible=x),
436
+ inputs=is_loading,
437
+ outputs=loading_overlay
438
+ ).then(
439
  fn=process_recipe_request,
440
  inputs=[api_key_input, file_upload, num_recipes, dietary_restrictions, cuisine_preference],
441
+ outputs=[output, is_loading]
442
+ ).then(
443
+ fn=lambda x: gr.update(visible=x),
444
+ inputs=is_loading,
445
+ outputs=loading_overlay
446
  )
447
 
448
  if __name__ == "__main__":