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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +178 -172
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,197 +122,194 @@ 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.", 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,6 +318,19 @@ html_header = """
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,14 +346,38 @@ 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,42 +441,14 @@ with gr.Blocks(css=custom_css) as app:
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__":
 
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."
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
  :root {
131
+ --primary-color: #FF6F61; /* Warm coral */
132
+ --secondary-color: #4BB543; /* Fresh green */
133
+ --accent-color: #F0A500; /* Golden yellow */
134
+ --background-color: #F4F4F9; /* Light grayish background */
135
+ --text-color: #333333; /* Dark gray text */
136
+ --card-background: #FFFFFF; /* White for cards */
137
+ --border-radius: 12px;
138
+ --font-family: 'Poppins', sans-serif;
139
+ --box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 20px;
140
+ --hover-shadow: rgba(0, 0, 0, 0.15) 0px 8px 30px;
141
  }
142
  body {
143
+ font-family: var(--font-family);
144
+ background-color: var(--background-color);
145
+ color: var(--text-color);
146
+ margin: 0;
147
+ padding: 0;
148
  }
149
  .container {
150
+ max-width: 1200px;
151
+ margin: 0 auto;
152
+ padding: 20px;
153
  }
154
  .app-header {
155
+ background-color: var(--primary-color);
156
+ color: white;
157
+ padding: 60px 20px;
158
+ text-align: center;
159
+ border-radius: 0 0 30px 30px;
160
+ box-shadow: var(--box-shadow);
161
  }
162
  .app-title {
163
+ font-size: 2.8em;
164
+ font-weight: 700;
165
+ margin-bottom: 10px;
166
  }
167
  .app-subtitle {
168
+ font-size: 1.3em;
169
+ font-weight: 300;
170
+ max-width: 800px;
171
+ margin: 0 auto;
172
  }
173
  .input-section, .output-section {
174
+ background-color: var(--card-background);
175
+ border-radius: var(--border-radius);
176
+ padding: 30px;
177
+ box-shadow: var(--box-shadow);
178
+ margin-bottom: 30px;
179
  }
180
  .section-header {
181
+ font-size: 1.6em;
182
+ font-weight: 600;
183
+ color: var(--text-color);
184
+ margin-bottom: 20px;
185
+ border-bottom: 2px solid var(--primary-color);
186
+ padding-bottom: 10px;
187
  }
188
  .image-upload-container {
189
+ border: 2px dashed var(--secondary-color);
190
+ border-radius: var(--border-radius);
191
+ padding: 40px;
192
+ text-align: center;
193
+ background-color: rgba(75, 181, 67, 0.1);
194
+ transition: all 0.3s ease;
195
  }
196
  .image-upload-container:hover {
197
+ border-color: var(--primary-color);
198
+ background-color: rgba(255, 111, 97, 0.1);
199
  }
200
  button.primary-button {
201
+ background-color: var(--primary-color);
202
+ color: white;
203
+ border: none;
204
+ padding: 16px 32px;
205
+ border-radius: 6px;
206
+ font-size: 1.1em;
207
+ cursor: pointer;
208
+ transition: all 0.3s ease;
209
+ width: 100%;
210
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
211
  }
212
  button.primary-button:hover {
213
+ background-color: #E15F52;
214
+ box-shadow: var(--hover-shadow);
215
  }
216
  .gallery-container {
217
+ display: grid;
218
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
219
+ gap: 20px;
220
+ margin-top: 30px;
221
  }
222
  .gallery-item {
223
+ border-radius: var(--border-radius);
224
+ overflow: hidden;
225
+ box-shadow: var(--box-shadow);
226
+ transition: transform 0.3s ease;
227
+ aspect-ratio: 1 / 1;
228
+ object-fit: cover;
229
  }
230
  .gallery-item:hover {
231
+ transform: scale(1.05);
232
+ box-shadow: var(--hover-shadow);
233
  }
234
  .recipe-output {
235
+ font-size: 1.2em;
236
+ line-height: 1.7;
237
+ color: var(--text-color);
238
+ max-height: 600px;
239
+ overflow-y: auto;
240
+ padding-right: 15px;
241
  }
242
  .recipe-output h2 {
243
+ color: var(--primary-color);
244
+ margin-top: 30px;
245
+ font-size: 2em;
246
  }
247
  .recipe-output h3 {
248
+ color: var(--secondary-color);
249
+ font-size: 1.5em;
250
+ margin-top: 20px;
251
  }
252
  .loading-container {
253
+ display: flex;
254
+ flex-direction: column;
255
+ justify-content: center;
256
+ align-items: center;
257
+ position: fixed;
258
+ top: 0;
259
+ left: 0;
260
+ width: 100%;
261
+ height: 100%;
262
+ background-color: rgba(0, 0, 0, 0.5);
263
+ z-index: 1000;
264
+ opacity: 0;
265
+ visibility: hidden;
266
+ transition: opacity 0.3s ease, visibility 0.3s ease;
267
  }
268
  .loading-container.visible {
269
+ opacity: 1;
270
+ visibility: visible;
271
  }
272
  .loading-spinner {
273
+ border: 8px solid #f3f3f3;
274
+ border-top: 8px solid var(--primary-color);
275
+ border-radius: 50%;
276
+ width: 60px;
277
+ height: 60px;
278
+ animation: spin 60s linear infinite;
279
  }
280
  @keyframes spin {
281
+ 0% { transform: rotate(0deg); }
282
+ 100% { transform: rotate(360deg); }
283
  }
284
  .loading-text {
285
+ color: white;
286
+ font-size: 1.3em;
287
+ margin-top: 20px;
288
  }
289
  .footer {
290
+ background-color: var(--card-background);
291
+ padding: 40px 20px;
292
+ text-align: center;
293
+ color: var(--text-color);
294
+ font-size: 1.1em;
295
+ box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
296
  }
297
  .footer-content {
298
+ max-width: 800px;
299
+ margin: 0 auto;
300
  }
301
  .footer-brand {
302
+ font-weight: 700;
303
+ color: var(--primary-color);
304
  }
305
  .footer-links a {
306
+ color: var(--secondary-color);
307
+ text-decoration: none;
308
+ margin: 0 15px;
309
+ transition: color 0.3s ease;
310
  }
311
  .footer-links a:hover {
312
+ color: var(--primary-color);
313
  }
314
  """
315
 
 
318
  <div class="app-title">🍲 Visual Recipe Assistant</div>
319
  <div class="app-subtitle">Upload images of ingredients you have on hand and get personalized recipe suggestions powered by AI</div>
320
  </div>
321
+ <div id="loading-overlay" class="loading-container">
322
+ <div class="loading-spinner"></div>
323
+ <div class="loading-text">Generating your recipes...</div>
324
+ </div>
325
+ <script>
326
+ function showLoading() {
327
+ document.getElementById('loading-overlay').classList.add('visible');
328
+ }
329
+
330
+ function hideLoading() {
331
+ document.getElementById('loading-overlay').classList.remove('visible');
332
+ }
333
+ </script>
334
  """
335
 
336
  html_footer = """
 
346
  </div>
347
  </div>
348
  </div>
349
+ <script>
350
+ document.addEventListener('DOMContentLoaded', function() {
351
+ const submitBtn = document.querySelector('button.primary-button');
352
+ if (submitBtn) {
353
+ submitBtn.addEventListener('click', function() {
354
+ showLoading();
355
+ setTimeout(function() {
356
+ const output = document.querySelector('.recipe-output');
357
+ if (output && output.textContent.trim().length > 0) {
358
+ hideLoading();
359
+ } else {
360
+ const checkInterval = setInterval(function() {
361
+ if (output && output.textContent.trim().length > 0) {
362
+ hideLoading();
363
+ clearInterval(checkInterval);
364
+ }
365
+ }, 1000);
366
+ setTimeout(function() {
367
+ hideLoading();
368
+ clearInterval(checkInterval);
369
+ }, 30000);
370
+ }
371
+ }, 3000);
372
+ });
373
+ }
374
+ });
375
+ </script>
376
  """
377
 
378
  with gr.Blocks(css=custom_css) as app:
379
  gr.HTML(html_header)
380
 
 
 
 
381
  with gr.Row():
382
  with gr.Column(scale=1):
383
  with gr.Group(elem_classes="input-section"):
 
441
  gr.HTML('<h3 class="section-header">Your Personalized Recipes</h3>')
442
  output = gr.Markdown(elem_classes="recipe-output")
443
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  gr.HTML(html_footer)
445
+
 
446
  file_upload.change(fn=update_gallery, inputs=file_upload, outputs=image_input)
447
 
 
448
  submit_button.click(
 
 
 
 
 
 
 
449
  fn=process_recipe_request,
450
  inputs=[api_key_input, file_upload, num_recipes, dietary_restrictions, cuisine_preference],
451
+ outputs=output
 
 
 
 
452
  )
453
 
454
  if __name__ == "__main__":