shukdevdatta123 commited on
Commit
5389033
·
verified ·
1 Parent(s): 6a8c21a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +137 -349
app.py CHANGED
@@ -1,10 +1,13 @@
1
  import gradio as gr
2
  import base64
 
 
 
 
3
  import os
 
4
  import tempfile
5
  import uuid
6
- from together import Together
7
- from PIL import Image
8
 
9
  def encode_image_to_base64(image_path):
10
  """Convert image to base64 encoding"""
@@ -67,7 +70,7 @@ def analyze_single_image(client, img_path):
67
  return f"Error analyzing image: {str(e)}"
68
 
69
  def get_recipe_suggestions(api_key, images, num_recipes=3, dietary_restrictions="None", cuisine_preference="Any"):
70
- """Get recipe suggestions based on uploaded images of ingredients"""
71
  if not api_key:
72
  return "Please provide your Together API key."
73
 
@@ -129,8 +132,8 @@ def get_recipe_suggestions(api_key, images, num_recipes=3, dietary_restrictions=
129
  {"role": "system", "content": system_prompt},
130
  {"role": "user", "content": user_prompt}
131
  ],
132
- max_tokens=3000,
133
- temperature=0.6
134
  )
135
 
136
  for img_path in image_paths:
@@ -155,414 +158,198 @@ def get_recipe_suggestions(api_key, images, num_recipes=3, dietary_restrictions=
155
  pass
156
  return f"Error: {str(e)}"
157
 
158
- # Enhanced Custom CSS
159
  custom_css = """
160
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
161
-
162
  :root {
163
- --primary-color: #6366f1;
164
- --primary-dark: #4f46e5;
165
- --secondary-color: #10b981;
166
- --accent-color: #f59e0b;
167
- --background-color: #1f2937;
168
- --card-background: rgba(31, 41, 55, 0.8);
169
- --text-color: #f3f4f6;
170
- --border-radius: 16px;
171
- --glass-background: rgba(255, 255, 255, 0.1);
172
- --glass-border: rgba(255, 255, 255, 0.2);
173
- --font-family: 'Inter', sans-serif;
174
  }
175
-
176
  body {
177
  font-family: var(--font-family);
178
- background: linear-gradient(135deg, #111827 0%, #1f2937 100%);
179
  color: var(--text-color);
180
  margin: 0;
181
  padding: 0;
182
- min-height: 100vh;
183
  }
184
-
185
  .container {
186
- max-width: 1400px;
187
  margin: 0 auto;
188
- padding: 40px 20px;
189
  }
190
-
191
  .app-header {
 
 
 
192
  text-align: center;
193
- margin-bottom: 60px;
194
- padding: 60px 20px;
195
- background: var(--glass-background);
196
- border-radius: var(--border-radius);
197
- backdrop-filter: blur(10px);
198
- border: 1px solid var(--glass-border);
199
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
200
  }
201
-
202
  .app-title {
203
- font-size: 3.2em;
 
204
  font-weight: 700;
205
- margin-bottom: 15px;
206
- background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
207
- -webkit-background-clip: text;
208
- -webkit-text-fill-color: transparent;
209
  }
210
-
211
  .app-subtitle {
212
  font-size: 1.2em;
213
- opacity: 0.85;
214
- max-width: 800px;
215
- margin: 0 auto;
216
- line-height: 1.6;
217
  font-weight: 300;
 
 
218
  }
219
-
220
  .input-section, .output-section {
221
- background: var(--card-background);
222
  border-radius: var(--border-radius);
223
- padding: 40px;
224
- margin-bottom: 40px;
225
- backdrop-filter: blur(10px);
226
- border: 1px solid var(--glass-border);
227
- transition: transform 0.3s ease, box-shadow 0.3s ease;
228
- }
229
-
230
- .input-section:hover, .output-section:hover {
231
- transform: translateY(-5px);
232
- box-shadow: 0 12px 24px rgba(0, 0, 0, 0.3);
233
  }
234
-
235
  .section-header {
236
- color: var(--primary-color);
237
- font-size: 1.8em;
238
- margin-bottom: 25px;
239
  font-weight: 600;
240
- display: flex;
241
- align-items: center;
242
- }
243
-
244
- .section-header i {
245
- margin-right: 12px;
246
- font-size: 1.3em;
247
  }
248
-
249
  .image-upload-container {
250
  border: 2px dashed var(--secondary-color);
251
  border-radius: var(--border-radius);
252
  padding: 40px;
253
  text-align: center;
254
- margin-bottom: 30px;
255
- background: var(--glass-background);
256
  transition: all 0.3s ease;
257
  }
258
-
259
  .image-upload-container:hover {
260
  border-color: var(--primary-color);
261
- background: rgba(99, 102, 241, 0.05);
262
  }
263
-
264
- .image-upload-icon {
265
- font-size: 3.5em;
266
- color: var(--secondary-color);
267
- margin-bottom: 15px;
268
- }
269
-
270
- .image-upload-text {
271
- color: var(--text-color);
272
- font-weight: 500;
273
- opacity: 0.9;
274
- }
275
-
276
  button.primary-button {
277
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
278
  color: white;
279
  border: none;
280
- padding: 16px 32px;
281
- border-radius: 50px;
282
- font-size: 1.2em;
283
  cursor: pointer;
284
  transition: all 0.3s ease;
285
- box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
286
- font-weight: 600;
287
  width: 100%;
288
- margin-top: 30px;
289
  }
290
-
291
  button.primary-button:hover {
292
- transform: translateY(-2px);
293
- box-shadow: 0 8px 20px rgba(99, 102, 241, 0.6);
294
- }
295
-
296
- button.primary-button:active {
297
- transform: translateY(0);
298
- box-shadow: 0 2px 10px rgba(99, 102, 241, 0.4);
299
- }
300
-
301
- .gradio-slider.svelte-17l1npl {
302
- margin-bottom: 30px;
303
- }
304
-
305
- .recipe-card {
306
- border-left: 4px solid var(--accent-color);
307
- padding: 20px;
308
- background: var(--glass-background);
309
- margin-bottom: 20px;
310
- border-radius: 0 var(--border-radius) var(--border-radius) 0;
311
- transition: all 0.3s ease;
312
- }
313
-
314
- .recipe-card:hover {
315
- transform: translateX(5px);
316
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
317
  }
318
-
319
- .recipe-title {
320
- color: var(--primary-color);
321
- font-size: 1.6em;
322
- margin-bottom: 15px;
323
- font-weight: 600;
324
- }
325
-
326
- .footer {
327
- text-align: center;
328
- margin-top: 80px;
329
- padding: 40px 0;
330
- color: var(--text-color);
331
- font-size: 1em;
332
- background: var(--card-background);
333
- border-radius: var(--border-radius);
334
- border: 1px solid var(--glass-border);
335
- }
336
-
337
- .footer-content {
338
- max-width: 800px;
339
- margin: 0 auto;
340
- }
341
-
342
- .footer-brand {
343
- font-weight: 600;
344
- color: var(--primary-color);
345
- }
346
-
347
- .footer-links {
348
- margin-top: 20px;
349
- }
350
-
351
- .footer-links a {
352
- color: var(--secondary-color);
353
- margin: 0 15px;
354
- text-decoration: none;
355
- transition: color 0.3s ease;
356
- }
357
-
358
- .footer-links a:hover {
359
- color: var(--primary-color);
360
- }
361
-
362
- .input-group {
363
- margin-bottom: 30px;
364
- }
365
-
366
- .input-group label {
367
- display: block;
368
- margin-bottom: 12px;
369
- font-weight: 500;
370
- color: var(--text-color);
371
- font-size: 1.1em;
372
- }
373
-
374
  .gallery-container {
375
  display: grid;
376
- grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
377
- gap: 20px;
378
  margin-top: 20px;
379
  }
380
-
381
  .gallery-item {
382
- border-radius: var(--border-radius);
383
  overflow: hidden;
384
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
385
  transition: transform 0.3s ease;
386
- aspect-ratio: 1 / 1;
387
  }
388
-
389
  .gallery-item:hover {
390
  transform: scale(1.05);
391
  }
392
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  .loading-container {
 
 
 
 
394
  position: fixed;
395
  top: 0;
396
  left: 0;
397
  width: 100%;
398
  height: 100%;
399
- background: rgba(17, 24, 39, 0.9);
400
- display: flex;
401
- justify-content: center;
402
- align-items: center;
403
  z-index: 1000;
404
  opacity: 0;
405
  visibility: hidden;
406
  transition: opacity 0.3s ease, visibility 0.3s ease;
407
  }
408
-
409
  .loading-container.visible {
410
  opacity: 1;
411
  visibility: visible;
412
  }
413
-
414
  .loading-spinner {
 
 
 
415
  width: 60px;
416
  height: 60px;
417
- border: 6px solid var(--glass-background);
418
- border-top: 6px solid var(--primary-color);
419
- border-radius: 50%;
420
  animation: spin 1s linear infinite;
421
  }
422
-
423
- .loading-text {
424
- position: absolute;
425
- margin-top: 80px;
426
- color: var(--text-color);
427
- font-size: 1.2em;
428
- font-weight: 500;
429
- }
430
-
431
  @keyframes spin {
432
  0% { transform: rotate(0deg); }
433
  100% { transform: rotate(360deg); }
434
  }
435
-
436
- .recipe-output {
437
- max-height: 900px;
438
- overflow-y: auto;
439
- padding-right: 20px;
440
- line-height: 1.7;
441
- }
442
-
443
- .recipe-output::-webkit-scrollbar {
444
- width: 10px;
445
- }
446
-
447
- .recipe-output::-webkit-scrollbar-track {
448
- background: var(--glass-background);
449
- border-radius: 10px;
450
- }
451
-
452
- .recipe-output::-webkit-scrollbar-thumb {
453
- background: var(--primary-color);
454
- border-radius: 10px;
455
- }
456
-
457
- .recipe-output h2 {
458
- color: var(--primary-color);
459
- border-bottom: 2px solid var(--secondary-color);
460
- padding-bottom: 12px;
461
- font-size: 2em;
462
- margin-top: 40px;
463
- }
464
-
465
- .recipe-output h3 {
466
- color: var(--secondary-color);
467
- font-size: 1.5em;
468
- margin-top: 30px;
469
  }
470
-
471
- input[type="password"], input[type="text"] {
472
- border: 1px solid var(--glass-border);
473
- background: var(--glass-background);
474
- border-radius: var(--border-radius);
475
- padding: 14px;
476
- font-size: 1em;
477
- width: 100%;
478
  color: var(--text-color);
479
- transition: all 0.3s ease;
480
- }
481
-
482
- input[type="password"]:focus, input[type="text"]:focus {
483
- border-color: var(--primary-color);
484
- outline: none;
485
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
486
- }
487
-
488
- select {
489
- appearance: none;
490
- background: var(--glass-background) 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='%236366f1' 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;
491
- border: 1px solid var(--glass-border);
492
- border-radius: var(--border-radius);
493
- padding: 14px 45px 14px 14px;
494
  font-size: 1em;
495
- width: 100%;
496
- color: var(--text-color);
497
- transition: all 0.3s ease;
498
- }
499
-
500
- select:focus {
501
- border-color: var(--primary-color);
502
- outline: none;
503
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
504
  }
505
-
506
- @media (max-width: 768px) {
507
- .app-title {
508
- font-size: 2.5em;
509
- }
510
-
511
- .app-subtitle {
512
- font-size: 1.1em;
513
- }
514
-
515
- .input-section, .output-section {
516
- padding: 25px;
517
- }
518
-
519
- .section-header {
520
- font-size: 1.6em;
521
- }
522
-
523
- button.primary-button {
524
- padding: 14px 25px;
525
- font-size: 1.1em;
526
- }
527
  }
528
-
529
- @media (max-width: 480px) {
530
- .app-title {
531
- font-size: 2em;
532
- }
533
-
534
- .app-subtitle {
535
- font-size: 1em;
536
- }
537
-
538
- .input-section, .output-section {
539
- padding: 20px;
540
- }
541
-
542
- .section-header {
543
- font-size: 1.4em;
544
- }
545
  }
546
-
547
- .gradio-container {
548
- max-width: 100% !important;
 
 
549
  }
550
-
551
- footer.footer-links {
552
- display: none !important;
553
  }
554
  """
555
 
556
- # Custom HTML header
557
  html_header = """
558
  <div class="app-header">
559
  <div class="app-title">🍲 Visual Recipe Assistant</div>
560
- <div class="app-subtitle">Transform your ingredients into gourmet dishes with AI-powered recipe suggestions</div>
561
  </div>
562
  <div id="loading-overlay" class="loading-container">
563
- <div class="loading-spinner">
564
- <div class="loading-text">Crafting your recipes...</div>
565
- </div>
566
  </div>
567
  <script>
568
  function showLoading() {
@@ -572,38 +359,15 @@ html_header = """
572
  function hideLoading() {
573
  document.getElementById('loading-overlay').classList.remove('visible');
574
  }
575
-
576
- document.addEventListener('DOMContentLoaded', function() {
577
- const submitBtn = document.querySelector('button.primary-button');
578
- const output = document.querySelector('.recipe-output');
579
-
580
- if (submitBtn && output) {
581
- submitBtn.addEventListener('click', function() {
582
- showLoading();
583
-
584
- const observer = new MutationObserver(function(mutations) {
585
- if (output.textContent.trim().length > 0) {
586
- hideLoading();
587
- observer.disconnect();
588
- }
589
- });
590
-
591
- observer.observe(output, { childList: true, characterData: true, subtree: true });
592
-
593
- setTimeout(hideLoading, 30000);
594
- });
595
- }
596
- });
597
  </script>
598
  """
599
 
600
- # Custom HTML footer
601
  html_footer = """
602
  <div class="footer">
603
  <div class="footer-content">
604
  <p><span class="footer-brand">🍲 Visual Recipe Assistant</span></p>
605
  <p>Powered by Meta's Llama-Vision-Free Model & Together AI</p>
606
- <p>Create culinary masterpieces from your pantry</p>
607
  <div class="footer-links">
608
  <a href="#" target="_blank">How It Works</a>
609
  <a href="#" target="_blank">Privacy Policy</a>
@@ -611,25 +375,50 @@ html_footer = """
611
  </div>
612
  </div>
613
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
  """
615
 
616
- # Create the Gradio interface
617
  with gr.Blocks(css=custom_css) as app:
618
  gr.HTML(html_header)
619
 
620
  with gr.Row():
621
  with gr.Column(scale=1):
622
  with gr.Group(elem_classes="input-section"):
623
- gr.HTML('<h3 class="section-header"><i class="icon">🔑</i> API Configuration</h3>')
624
  api_key_input = gr.Textbox(
625
  label="Together API Key",
626
- placeholder="Enter your Together API key...",
627
  type="password",
628
- elem_classes="input-group",
629
- info="Your API key is used only for this session and remains private."
630
  )
631
 
632
- gr.HTML('<h3 class="section-header"><i class="icon">📷</i> Upload Ingredients</h3>')
633
  file_upload = gr.File(
634
  label="Upload images of ingredients",
635
  file_types=["image"],
@@ -646,7 +435,7 @@ with gr.Blocks(css=custom_css) as app:
646
  visible=False
647
  )
648
 
649
- gr.HTML('<h3 class="section-header"><i class="icon">⚙️</i> Recipe Preferences</h3>')
650
  with gr.Row():
651
  num_recipes = gr.Slider(
652
  minimum=1,
@@ -678,7 +467,7 @@ with gr.Blocks(css=custom_css) as app:
678
 
679
  with gr.Column(scale=1):
680
  with gr.Group(elem_classes="output-section"):
681
- gr.HTML('<h3 class="section-header"><i class="icon">🍽️</i> Your Personalized Recipes</h3>')
682
  output = gr.Markdown(elem_classes="recipe-output")
683
 
684
  gr.HTML(html_footer)
@@ -693,7 +482,6 @@ with gr.Blocks(css=custom_css) as app:
693
  def process_recipe_request(api_key, files, num_recipes, dietary_restrictions, cuisine_preference):
694
  if not files:
695
  return "Please upload at least one image of ingredients."
696
-
697
  images = [file.name for file in files]
698
  return get_recipe_suggestions(api_key, images, num_recipes, dietary_restrictions, cuisine_preference)
699
 
 
1
  import gradio as gr
2
  import base64
3
+ import requests
4
+ import io
5
+ from PIL import Image
6
+ import json
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"""
 
70
  return f"Error analyzing image: {str(e)}"
71
 
72
  def get_recipe_suggestions(api_key, images, num_recipes=3, dietary_restrictions="None", cuisine_preference="Any"):
73
+ """Get recipe suggestions based on the uploaded images of ingredients"""
74
  if not api_key:
75
  return "Please provide your Together API key."
76
 
 
132
  {"role": "system", "content": system_prompt},
133
  {"role": "user", "content": user_prompt}
134
  ],
135
+ max_tokens=2048,
136
+ temperature=0.7
137
  )
138
 
139
  for img_path in image_paths:
 
158
  pass
159
  return f"Error: {str(e)}"
160
 
 
161
  custom_css = """
162
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
 
163
  :root {
164
+ --primary-color: #E67E22;
165
+ --primary-dark: #D35400;
166
+ --secondary-color: #2ECC71;
167
+ --accent-color: #F1C40F;
168
+ --background-color: #FFFFFF;
169
+ --text-color: #4A4A4A;
170
+ --card-background: #F8F9FA;
171
+ --border-radius: 10px;
172
+ --font-family: 'Poppins', sans-serif;
 
 
173
  }
 
174
  body {
175
  font-family: var(--font-family);
176
+ background-color: var(--background-color);
177
  color: var(--text-color);
178
  margin: 0;
179
  padding: 0;
 
180
  }
 
181
  .container {
182
+ max-width: 1200px;
183
  margin: 0 auto;
184
+ padding: 20px;
185
  }
 
186
  .app-header {
187
+ background-color: var(--primary-color);
188
+ color: white;
189
+ padding: 40px 20px;
190
  text-align: center;
191
+ border-radius: 0 0 20px 20px;
192
+ box-shadow: 0 4px 10px rgba(0,0,0,0.1);
 
 
 
 
 
193
  }
 
194
  .app-title {
195
+ font-size: 2.5em;
196
+ margin-bottom: 10px;
197
  font-weight: 700;
 
 
 
 
198
  }
 
199
  .app-subtitle {
200
  font-size: 1.2em;
 
 
 
 
201
  font-weight: 300;
202
+ max-width: 600px;
203
+ margin: 0 auto;
204
  }
 
205
  .input-section, .output-section {
206
+ background-color: var(--card-background);
207
  border-radius: var(--border-radius);
208
+ padding: 30px;
209
+ box-shadow: 0 4px 15px rgba(0,0,0,0.05);
210
+ margin-bottom: 30px;
 
 
 
 
 
 
 
211
  }
 
212
  .section-header {
213
+ font-size: 1.5em;
 
 
214
  font-weight: 600;
215
+ color: var(--text-color);
216
+ margin-bottom: 20px;
217
+ border-bottom: 2px solid var(--primary-color);
218
+ padding-bottom: 10px;
 
 
 
219
  }
 
220
  .image-upload-container {
221
  border: 2px dashed var(--secondary-color);
222
  border-radius: var(--border-radius);
223
  padding: 40px;
224
  text-align: center;
225
+ background-color: rgba(46, 204, 113, 0.05);
 
226
  transition: all 0.3s ease;
227
  }
 
228
  .image-upload-container:hover {
229
  border-color: var(--primary-color);
230
+ background-color: rgba(230, 126, 34, 0.05);
231
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  button.primary-button {
233
+ background-color: var(--primary-color);
234
  color: white;
235
  border: none;
236
+ padding: 15px 30px;
237
+ border-radius: 5px;
238
+ font-size: 1.1em;
239
  cursor: pointer;
240
  transition: all 0.3s ease;
241
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
 
242
  width: 100%;
 
243
  }
 
244
  button.primary-button:hover {
245
+ background-color: var(--primary-dark);
246
+ box-shadow: 0 4px 10px rgba(0,0,0,0.15);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  .gallery-container {
249
  display: grid;
250
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
251
+ gap: 15px;
252
  margin-top: 20px;
253
  }
 
254
  .gallery-item {
255
+ border-radius: 8px;
256
  overflow: hidden;
257
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
258
  transition: transform 0.3s ease;
 
259
  }
 
260
  .gallery-item:hover {
261
  transform: scale(1.05);
262
  }
263
+ .recipe-output {
264
+ font-size: 1.1em;
265
+ line-height: 1.6;
266
+ color: var(--text-color);
267
+ max-height: 600px;
268
+ overflow-y: auto;
269
+ padding-right: 10px;
270
+ }
271
+ .recipe-output h2 {
272
+ color: var(--primary-color);
273
+ margin-top: 30px;
274
+ font-size: 1.8em;
275
+ }
276
+ .recipe-output h3 {
277
+ color: var(--secondary-color);
278
+ font-size: 1.4em;
279
+ margin-top: 20px;
280
+ }
281
  .loading-container {
282
+ display: flex;
283
+ flex-direction: column;
284
+ justify-content: center;
285
+ align-items: center;
286
  position: fixed;
287
  top: 0;
288
  left: 0;
289
  width: 100%;
290
  height: 100%;
291
+ background-color: rgba(0, 0, 0, 0.5);
 
 
 
292
  z-index: 1000;
293
  opacity: 0;
294
  visibility: hidden;
295
  transition: opacity 0.3s ease, visibility 0.3s ease;
296
  }
 
297
  .loading-container.visible {
298
  opacity: 1;
299
  visibility: visible;
300
  }
 
301
  .loading-spinner {
302
+ border: 8px solid #f3f3f3;
303
+ border-top: 8px solid var(--primary-color);
304
+ border-radius: 50%;
305
  width: 60px;
306
  height: 60px;
 
 
 
307
  animation: spin 1s linear infinite;
308
  }
 
 
 
 
 
 
 
 
 
309
  @keyframes spin {
310
  0% { transform: rotate(0deg); }
311
  100% { transform: rotate(360deg); }
312
  }
313
+ .loading-text {
314
+ color: white;
315
+ font-size: 1.2em;
316
+ margin-top: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  }
318
+ .footer {
319
+ background-color: var(--card-background);
320
+ padding: 40px 20px;
321
+ text-align: center;
 
 
 
 
322
  color: var(--text-color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  font-size: 1em;
324
+ box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
 
 
 
 
 
 
 
 
325
  }
326
+ .footer-content {
327
+ max-width: 800px;
328
+ margin: 0 auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  }
330
+ .footer-brand {
331
+ font-weight: 700;
332
+ color: var(--primary-color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  }
334
+ .footer-links a {
335
+ color: var(--secondary-color);
336
+ text-decoration: none;
337
+ margin: 0 15px;
338
+ transition: color 0.3s ease;
339
  }
340
+ .footer-links a:hover {
341
+ color: var(--primary-color);
 
342
  }
343
  """
344
 
 
345
  html_header = """
346
  <div class="app-header">
347
  <div class="app-title">🍲 Visual Recipe Assistant</div>
348
+ <div class="app-subtitle">Upload images of ingredients you have on hand and get personalized recipe suggestions powered by AI</div>
349
  </div>
350
  <div id="loading-overlay" class="loading-container">
351
+ <div class="loading-spinner"></div>
352
+ <div class="loading-text">Generating your recipes...</div>
 
353
  </div>
354
  <script>
355
  function showLoading() {
 
359
  function hideLoading() {
360
  document.getElementById('loading-overlay').classList.remove('visible');
361
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  </script>
363
  """
364
 
 
365
  html_footer = """
366
  <div class="footer">
367
  <div class="footer-content">
368
  <p><span class="footer-brand">🍲 Visual Recipe Assistant</span></p>
369
  <p>Powered by Meta's Llama-Vision-Free Model & Together AI</p>
370
+ <p>Upload multiple ingredient images for more creative recipe combinations</p>
371
  <div class="footer-links">
372
  <a href="#" target="_blank">How It Works</a>
373
  <a href="#" target="_blank">Privacy Policy</a>
 
375
  </div>
376
  </div>
377
  </div>
378
+ <script>
379
+ document.addEventListener('DOMContentLoaded', function() {
380
+ const submitBtn = document.querySelector('button.primary-button');
381
+ if (submitBtn) {
382
+ submitBtn.addEventListener('click', function() {
383
+ showLoading();
384
+ setTimeout(function() {
385
+ const output = document.querySelector('.recipe-output');
386
+ if (output && output.textContent.trim().length > 0) {
387
+ hideLoading();
388
+ } else {
389
+ const checkInterval = setInterval(function() {
390
+ if (output && output.textContent.trim().length > 0) {
391
+ hideLoading();
392
+ clearInterval(checkInterval);
393
+ }
394
+ }, 1000);
395
+ setTimeout(function() {
396
+ hideLoading();
397
+ clearInterval(checkInterval);
398
+ }, 30000);
399
+ }
400
+ }, 3000);
401
+ });
402
+ }
403
+ });
404
+ </script>
405
  """
406
 
 
407
  with gr.Blocks(css=custom_css) as app:
408
  gr.HTML(html_header)
409
 
410
  with gr.Row():
411
  with gr.Column(scale=1):
412
  with gr.Group(elem_classes="input-section"):
413
+ gr.HTML('<h3 class="section-header">API Configuration</h3>')
414
  api_key_input = gr.Textbox(
415
  label="Together API Key",
416
+ placeholder="Enter your Together API key here...",
417
  type="password",
418
+ elem_classes="input-group"
 
419
  )
420
 
421
+ gr.HTML('<h3 class="section-header">Upload Ingredients</h3>')
422
  file_upload = gr.File(
423
  label="Upload images of ingredients",
424
  file_types=["image"],
 
435
  visible=False
436
  )
437
 
438
+ gr.HTML('<h3 class="section-header">Recipe Preferences</h3>')
439
  with gr.Row():
440
  num_recipes = gr.Slider(
441
  minimum=1,
 
467
 
468
  with gr.Column(scale=1):
469
  with gr.Group(elem_classes="output-section"):
470
+ gr.HTML('<h3 class="section-header">Your Personalized Recipes</h3>')
471
  output = gr.Markdown(elem_classes="recipe-output")
472
 
473
  gr.HTML(html_footer)
 
482
  def process_recipe_request(api_key, files, num_recipes, dietary_restrictions, cuisine_preference):
483
  if not files:
484
  return "Please upload at least one image of ingredients."
 
485
  images = [file.name for file in files]
486
  return get_recipe_suggestions(api_key, images, num_recipes, dietary_restrictions, cuisine_preference)
487