shukdevdatta123 commited on
Commit
71af48d
·
verified ·
1 Parent(s): 31502ca

Delete v1.txt

Browse files
Files changed (1) hide show
  1. v1.txt +0 -825
v1.txt DELETED
@@ -1,825 +0,0 @@
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"""
14
- with open(image_path, "rb") as image_file:
15
- return base64.b64encode(image_file.read()).decode('utf-8')
16
-
17
- def save_uploaded_image(image):
18
- """Save uploaded image to a temporary file and return the path"""
19
- if image is None:
20
- return None
21
-
22
- with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp_file:
23
- if isinstance(image, dict) and "path" in image: # Gradio returns image as dict
24
- # Copy the uploaded image to our temporary file
25
- with open(image["path"], "rb") as img_file:
26
- temp_file.write(img_file.read())
27
- elif isinstance(image, Image.Image):
28
- # If it's a PIL Image, save it
29
- image.save(temp_file.name, format="JPEG")
30
- else:
31
- # Try to handle other formats
32
- try:
33
- Image.open(image).save(temp_file.name, format="JPEG")
34
- except Exception:
35
- return None
36
-
37
- return temp_file.name
38
-
39
- def analyze_single_image(client, img_path):
40
- """Analyze a single image to identify ingredients"""
41
- system_prompt = """You are a culinary expert AI assistant that specializes in identifying ingredients in images.
42
- Your task is to analyze the provided image and list all the food ingredients you can identify.
43
- Be specific and detailed about what you see. Only list ingredients, don't suggest recipes yet."""
44
-
45
- user_prompt = "Please identify all the food ingredients visible in this image. List each ingredient on a new line."
46
-
47
- # First, convert the image to base64
48
- try:
49
- with open(img_path, "rb") as image_file:
50
- # Read the binary data and encode as base64
51
- base64_image = base64.b64encode(image_file.read()).decode('utf-8')
52
-
53
- # Create message with the image properly formatted
54
- content = [
55
- {"type": "text", "text": user_prompt},
56
- {
57
- "type": "image_url",
58
- "image_url": {
59
- "url": f"data:image/jpeg;base64,{base64_image}"
60
- }
61
- }
62
- ]
63
-
64
- response = client.chat.completions.create(
65
- model="meta-llama/Llama-Vision-Free",
66
- messages=[
67
- {
68
- "role": "system",
69
- "content": system_prompt
70
- },
71
- {
72
- "role": "user",
73
- "content": content
74
- }
75
- ],
76
- max_tokens=500,
77
- temperature=0.2
78
- )
79
-
80
- return response.choices[0].message.content
81
- except Exception as e:
82
- return f"Error analyzing image: {str(e)}"
83
-
84
- def get_recipe_suggestions(api_key, images, num_recipes=3, dietary_restrictions="None", cuisine_preference="Any"):
85
- """
86
- Get recipe suggestions based on the uploaded images of ingredients
87
- """
88
- if not api_key:
89
- return "Please provide your Together API key."
90
-
91
- if not images or len(images) == 0 or all(img is None for img in images):
92
- return "Please upload at least one image of ingredients."
93
-
94
- # Filter out None values
95
- valid_images = [img for img in images if img is not None]
96
-
97
- if len(valid_images) == 0:
98
- return "No valid images were uploaded. Please try again."
99
-
100
- # Save all uploaded images
101
- image_paths = []
102
- for img in valid_images:
103
- img_path = save_uploaded_image(img)
104
- if img_path:
105
- image_paths.append(img_path)
106
-
107
- if not image_paths:
108
- return "Failed to process the uploaded images."
109
-
110
- try:
111
- # Initialize Together client with the provided API key
112
- client = Together(api_key=api_key)
113
-
114
- # First, analyze each image separately to identify ingredients
115
- all_ingredients = []
116
- for img_path in image_paths:
117
- ingredients_text = analyze_single_image(client, img_path)
118
- all_ingredients.append(ingredients_text)
119
-
120
- # Combine all ingredients into one list
121
- combined_ingredients = "\n\n".join([f"Image {i+1} ingredients:\n{ingredients}"
122
- for i, ingredients in enumerate(all_ingredients)])
123
-
124
- # Now generate recipes based on all identified ingredients
125
- system_prompt = """You are a culinary expert AI assistant that specializes in creating recipes based on available ingredients.
126
- You will be provided with lists of ingredients identified from multiple images. Your task is to suggest creative,
127
- detailed recipes that use as many of the identified ingredients as possible.
128
-
129
- For each recipe suggestion, include:
130
- 1. Recipe name
131
- 2. Brief description of the dish
132
- 3. Complete ingredients list (including estimated quantities and any additional staple ingredients that might be needed)
133
- 4. Step-by-step cooking instructions
134
- 5. Approximate cooking time
135
- 6. Difficulty level (Easy, Medium, Advanced)
136
- 7. Nutritional highlights
137
-
138
- Consider any dietary restrictions and cuisine preferences mentioned by the user."""
139
-
140
- user_prompt = f"""Based on the following ingredients identified from multiple images, suggest {num_recipes} creative and delicious recipes.
141
-
142
- {combined_ingredients}
143
-
144
- Dietary restrictions to consider: {dietary_restrictions}
145
- Cuisine preference: {cuisine_preference}
146
-
147
- Please be creative with your recipe suggestions and try to use ingredients from multiple images if possible."""
148
-
149
- # Generate recipe suggestions based on all identified ingredients
150
- response = client.chat.completions.create(
151
- model="meta-llama/Llama-Vision-Free",
152
- messages=[
153
- {
154
- "role": "system",
155
- "content": system_prompt
156
- },
157
- {
158
- "role": "user",
159
- "content": user_prompt
160
- }
161
- ],
162
- max_tokens=2048,
163
- temperature=0.7
164
- )
165
-
166
- # Clean up the temporary files
167
- for img_path in image_paths:
168
- try:
169
- os.unlink(img_path)
170
- except:
171
- pass
172
-
173
- # Add information about the ingredients identified
174
- result = "## 📋 Ingredients Identified\n\n"
175
- result += combined_ingredients
176
- result += "\n\n---\n\n"
177
- result += "## 🍽️ Recipe Suggestions\n\n"
178
- result += response.choices[0].message.content
179
-
180
- return result
181
-
182
- except Exception as e:
183
- # Clean up the temporary files in case of error
184
- for img_path in image_paths:
185
- try:
186
- os.unlink(img_path)
187
- except:
188
- pass
189
- return f"Error: {str(e)}"
190
-
191
- # Enhanced Custom CSS for a more appealing interface
192
- custom_css = """
193
- @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
194
- :root {
195
- --primary-color: #FF6B6B;
196
- --primary-dark: #e55858;
197
- --secondary-color: #4ECDC4;
198
- --accent-color: #FFD166;
199
- --background-color: #f8f9fa;
200
- --text-color: #212529;
201
- --card-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
202
- --hover-shadow: 0 12px 28px rgba(0, 0, 0, 0.15);
203
- --border-radius: 12px;
204
- --font-family: 'Poppins', sans-serif;
205
- }
206
- body {
207
- font-family: var(--font-family);
208
- background-color: var(--background-color);
209
- color: var(--text-color);
210
- margin: 0;
211
- padding: 0;
212
- }
213
- .container {
214
- max-width: 1200px;
215
- margin: 0 auto;
216
- padding: 20px;
217
- }
218
- .app-header {
219
- text-align: center;
220
- margin-bottom: 40px;
221
- padding: 50px 20px;
222
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
223
- border-radius: var(--border-radius);
224
- color: white;
225
- box-shadow: var(--card-shadow);
226
- position: relative;
227
- overflow: hidden;
228
- }
229
- .app-header::before {
230
- content: '';
231
- position: absolute;
232
- top: 0;
233
- left: 0;
234
- right: 0;
235
- bottom: 0;
236
- background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path fill="rgba(255,255,255,0.05)" d="M30,20 L70,20 L50,50 Z"></path></svg>') repeat;
237
- opacity: 0.3;
238
- }
239
- .app-title {
240
- font-size: 3.5em;
241
- margin-bottom: 15px;
242
- font-weight: 700;
243
- text-shadow: 0 2px 4px rgba(0,0,0,0.1);
244
- position: relative;
245
- }
246
- .app-subtitle {
247
- font-size: 1.3em;
248
- opacity: 0.9;
249
- max-width: 700px;
250
- margin: 0 auto;
251
- line-height: 1.6;
252
- font-weight: 300;
253
- position: relative;
254
- }
255
- .input-section, .output-section {
256
- background-color: white;
257
- border-radius: var(--border-radius);
258
- padding: 30px;
259
- box-shadow: var(--card-shadow);
260
- margin-bottom: 30px;
261
- transition: all 0.3s ease;
262
- border: 1px solid rgba(0,0,0,0.05);
263
- }
264
- .input-section:hover, .output-section:hover {
265
- box-shadow: var(--hover-shadow);
266
- transform: translateY(-5px);
267
- }
268
- .section-header {
269
- color: var(--primary-color);
270
- margin-top: 0;
271
- font-size: 1.7em;
272
- border-bottom: 2px solid var(--secondary-color);
273
- padding-bottom: 15px;
274
- margin-bottom: 25px;
275
- font-weight: 600;
276
- display: flex;
277
- align-items: center;
278
- }
279
- .section-header i {
280
- margin-right: 10px;
281
- font-size: 1.2em;
282
- }
283
- .image-upload-container {
284
- border: 3px dashed var(--secondary-color);
285
- border-radius: var(--border-radius);
286
- padding: 30px;
287
- text-align: center;
288
- margin-bottom: 25px;
289
- transition: all 0.3s ease;
290
- background-color: rgba(78, 205, 196, 0.05);
291
- position: relative;
292
- }
293
- .image-upload-container:hover {
294
- border-color: var(--primary-color);
295
- background-color: rgba(255, 107, 107, 0.05);
296
- transform: translateY(-3px);
297
- }
298
- .image-upload-icon {
299
- font-size: 3em;
300
- color: var(--secondary-color);
301
- margin-bottom: 15px;
302
- }
303
- .image-upload-text {
304
- color: #666;
305
- font-weight: 500;
306
- }
307
- button.primary-button {
308
- background: linear-gradient(135deg, var(--primary-color) 0%, #FF8E8E 100%);
309
- color: white;
310
- border: none;
311
- padding: 15px 30px;
312
- border-radius: 50px;
313
- font-size: 1.2em;
314
- cursor: pointer;
315
- transition: all 0.3s ease;
316
- box-shadow: 0 4px 10px rgba(255, 107, 107, 0.3);
317
- font-weight: 600;
318
- display: block;
319
- width: 100%;
320
- margin-top: 30px;
321
- position: relative;
322
- overflow: hidden;
323
- }
324
- button.primary-button:hover {
325
- transform: translateY(-3px);
326
- box-shadow: 0 8px 15px rgba(255, 107, 107, 0.4);
327
- background: linear-gradient(135deg, #FF8E8E 0%, var(--primary-dark) 100%);
328
- }
329
- button.primary-button:active {
330
- transform: translateY(1px);
331
- box-shadow: 0 2px 5px rgba(255, 107, 107, 0.4);
332
- }
333
- button.primary-button::after {
334
- content: '';
335
- position: absolute;
336
- top: 50%;
337
- left: 50%;
338
- width: 5px;
339
- height: 5px;
340
- background: rgba(255, 255, 255, 0.5);
341
- opacity: 0;
342
- border-radius: 100%;
343
- transform: scale(1, 1) translate(-50%, -50%);
344
- transform-origin: 50% 50%;
345
- }
346
- button.primary-button:focus:not(:active)::after {
347
- animation: ripple 1s ease-out;
348
- }
349
- @keyframes ripple {
350
- 0% {
351
- transform: scale(0, 0);
352
- opacity: 0.5;
353
- }
354
- 100% {
355
- transform: scale(100, 100);
356
- opacity: 0;
357
- }
358
- }
359
- .gradio-slider.svelte-17l1npl {
360
- margin-bottom: 25px;
361
- }
362
- .recipe-card {
363
- border-left: 5px solid var(--accent-color);
364
- padding: 20px;
365
- background-color: #f9f9f9;
366
- margin-bottom: 20px;
367
- border-radius: 0 var(--border-radius) var(--border-radius) 0;
368
- transition: all 0.3s ease;
369
- }
370
- .recipe-card:hover {
371
- transform: translateX(5px);
372
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
373
- }
374
- .recipe-title {
375
- color: var(--primary-color);
376
- font-size: 1.5em;
377
- margin-bottom: 10px;
378
- font-weight: 600;
379
- }
380
- .footer {
381
- text-align: center;
382
- margin-top: 60px;
383
- padding: 30px 0;
384
- color: #6c757d;
385
- font-size: 1em;
386
- background-color: #f1f3f5;
387
- border-radius: var(--border-radius);
388
- box-shadow: inset 0 2px 10px rgba(0,0,0,0.05);
389
- }
390
- .footer-content {
391
- max-width: 700px;
392
- margin: 0 auto;
393
- }
394
- .footer-brand {
395
- font-weight: 600;
396
- color: var(--primary-color);
397
- }
398
- .footer-links {
399
- margin-top: 20px;
400
- }
401
- .footer-links a {
402
- color: var(--secondary-color);
403
- margin: 0 10px;
404
- text-decoration: none;
405
- transition: color 0.3s ease;
406
- }
407
- .footer-links a:hover {
408
- color: var(--primary-color);
409
- text-decoration: underline;
410
- }
411
- .icon {
412
- color: var(--primary-color);
413
- margin-right: 10px;
414
- font-size: 1.2em;
415
- }
416
- .input-group {
417
- margin-bottom: 25px;
418
- }
419
- .input-group label {
420
- display: block;
421
- margin-bottom: 10px;
422
- font-weight: 600;
423
- color: var(--text-color);
424
- font-size: 1.1em;
425
- }
426
- .gallery-container {
427
- display: grid;
428
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
429
- gap: 20px;
430
- margin-top: 20px;
431
- }
432
- .gallery-item {
433
- border-radius: var(--border-radius);
434
- overflow: hidden;
435
- box-shadow: var(--card-shadow);
436
- transition: transform 0.3s ease;
437
- aspect-ratio: 1 / 1;
438
- object-fit: cover;
439
- }
440
- .gallery-item:hover {
441
- transform: scale(1.05);
442
- box-shadow: var(--hover-shadow);
443
- }
444
- /* Loading Spinner */
445
- .loading-container {
446
- position: fixed;
447
- top: 0;
448
- left: 0;
449
- width: 100%;
450
- height: 100%;
451
- background-color: rgba(0, 0, 0, 0.7);
452
- display: flex;
453
- justify-content: center;
454
- align-items: center;
455
- z-index: 1000;
456
- opacity: 0;
457
- visibility: hidden;
458
- transition: opacity 0.3s ease, visibility 0.3s ease;
459
- }
460
- .loading-container.visible {
461
- opacity: 1;
462
- visibility: visible;
463
- }
464
- .loading-spinner {
465
- width: 80px;
466
- height: 80px;
467
- border-radius: 50%;
468
- position: relative;
469
- animation: spin 1.2s linear infinite;
470
- }
471
- .loading-spinner::before,
472
- .loading-spinner::after {
473
- content: '';
474
- position: absolute;
475
- border-radius: 50%;
476
- }
477
- .loading-spinner::before {
478
- width: 100%;
479
- height: 100%;
480
- background: linear-gradient(to right, var(--primary-color) 0%, transparent 100%);
481
- animation: spin 2s linear infinite;
482
- }
483
- .loading-spinner::after {
484
- width: 75%;
485
- height: 75%;
486
- background-color: rgba(0, 0, 0, 0.7);
487
- top: 12.5%;
488
- left: 12.5%;
489
- }
490
- .loading-text {
491
- position: absolute;
492
- bottom: -40px;
493
- color: white;
494
- font-size: 1.2em;
495
- font-weight: 500;
496
- }
497
- @keyframes spin {
498
- 0% {
499
- transform: rotate(0deg);
500
- }
501
- 100% {
502
- transform: rotate(360deg);
503
- }
504
- }
505
- .recipe-output {
506
- max-height: 800px;
507
- overflow-y: auto;
508
- padding-right: 15px;
509
- line-height: 1.6;
510
- }
511
- .recipe-output::-webkit-scrollbar {
512
- width: 8px;
513
- }
514
- .recipe-output::-webkit-scrollbar-track {
515
- background: #f1f1f1;
516
- border-radius: 10px;
517
- }
518
- .recipe-output::-webkit-scrollbar-thumb {
519
- background: var(--secondary-color);
520
- border-radius: 10px;
521
- }
522
- .recipe-output::-webkit-scrollbar-thumb:hover {
523
- background: var(--primary-color);
524
- }
525
- .recipe-output h2 {
526
- color: var(--primary-color);
527
- border-bottom: 2px solid var(--secondary-color);
528
- padding-bottom: 10px;
529
- font-size: 1.8em;
530
- margin-top: 30px;
531
- }
532
- .recipe-output h3 {
533
- color: var(--secondary-color);
534
- font-size: 1.4em;
535
- margin-top: 25px;
536
- }
537
- /* Form inputs styling */
538
- input[type="password"], input[type="text"] {
539
- border: 2px solid #e9ecef;
540
- border-radius: var(--border-radius);
541
- padding: 15px;
542
- font-size: 1em;
543
- width: 100%;
544
- transition: all 0.3s ease;
545
- box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
546
- }
547
- input[type="password"]:focus, input[type="text"]:focus {
548
- border-color: var(--secondary-color);
549
- outline: none;
550
- box-shadow: 0 0 0 3px rgba(78, 205, 196, 0.2);
551
- }
552
- /* Custom dropdown styling */
553
- select {
554
- appearance: none;
555
- 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 15px center;
556
- border: 2px solid #e9ecef;
557
- border-radius: var(--border-radius);
558
- padding: 15px 45px 15px 15px;
559
- font-size: 1em;
560
- width: 100%;
561
- transition: all 0.3s ease;
562
- box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
563
- }
564
- select:focus {
565
- border-color: var(--secondary-color);
566
- outline: none;
567
- box-shadow: 0 0 0 3px rgba(78, 205, 196, 0.2);
568
- }
569
- /* Tooltip styling */
570
- .tooltip {
571
- position: relative;
572
- display: inline-block;
573
- cursor: pointer;
574
- }
575
- .tooltip .tooltiptext {
576
- visibility: hidden;
577
- width: 250px;
578
- background-color: #333;
579
- color: #fff;
580
- text-align: center;
581
- border-radius: 6px;
582
- padding: 10px;
583
- position: absolute;
584
- z-index: 1;
585
- bottom: 125%;
586
- left: 50%;
587
- margin-left: -125px;
588
- opacity: 0;
589
- transition: opacity 0.3s;
590
- font-size: 0.9em;
591
- box-shadow: 0 5px 15px rgba(0,0,0,0.2);
592
- }
593
- .tooltip .tooltiptext::after {
594
- content: "";
595
- position: absolute;
596
- top: 100%;
597
- left: 50%;
598
- margin-left: -5px;
599
- border-width: 5px;
600
- border-style: solid;
601
- border-color: #333 transparent transparent transparent;
602
- }
603
- .tooltip:hover .tooltiptext {
604
- visibility: visible;
605
- opacity: 1;
606
- }
607
- /* Media queries for responsive design */
608
- @media (max-width: 768px) {
609
- .app-title {
610
- font-size: 2.5em;
611
- }
612
-
613
- .app-subtitle {
614
- font-size: 1.1em;
615
- }
616
-
617
- .input-section, .output-section {
618
- padding: 20px;
619
- }
620
-
621
- .section-header {
622
- font-size: 1.5em;
623
- }
624
-
625
- button.primary-button {
626
- padding: 12px 25px;
627
- font-size: 1.1em;
628
- }
629
- }
630
- @media (max-width: 480px) {
631
- .app-title {
632
- font-size: 2em;
633
- }
634
-
635
- .app-subtitle {
636
- font-size: 1em;
637
- }
638
-
639
- .input-section, .output-section {
640
- padding: 15px;
641
- }
642
-
643
- .section-header {
644
- font-size: 1.3em;
645
- }
646
- }
647
- /* Remove Gradio branding */
648
- .gradio-container {
649
- max-width: 100% !important;
650
- }
651
- footer.footer-links {
652
- display: none !important;
653
- }
654
- """
655
-
656
- # Custom HTML header with icons and improved design
657
- html_header = """
658
- <div class="app-header">
659
- <div class="app-title">🍲 Visual Recipe Assistant</div>
660
- <div class="app-subtitle">Upload images of ingredients you have on hand and get personalized recipe suggestions powered by AI</div>
661
- </div>
662
- <div id="loading-overlay" class="loading-container">
663
- <div class="loading-spinner">
664
- <div class="loading-text">Generating your recipes...</div>
665
- </div>
666
- </div>
667
- <script>
668
- // JavaScript to handle loading state
669
- function showLoading() {
670
- document.getElementById('loading-overlay').classList.add('visible');
671
- }
672
-
673
- function hideLoading() {
674
- document.getElementById('loading-overlay').classList.remove('visible');
675
- }
676
- </script>
677
- """
678
-
679
- # Custom HTML footer with improved design
680
- html_footer = """
681
- <div class="footer">
682
- <div class="footer-content">
683
- <p><span class="footer-brand">🍲 Visual Recipe Assistant</span></p>
684
- <p>Powered by Meta's Llama-Vision-Free Model & Together AI</p>
685
- <p>Upload multiple ingredient images for more creative recipe combinations</p>
686
- <div class="footer-links">
687
- <a href="#" target="_blank">How It Works</a>
688
- <a href="#" target="_blank">Privacy Policy</a>
689
- <a href="#" target="_blank">Contact Us</a>
690
- </div>
691
- </div>
692
- </div>
693
- <script>
694
- // Add event listener to the submit button
695
- document.addEventListener('DOMContentLoaded', function() {
696
- const submitBtn = document.querySelector('button.primary-button');
697
- if (submitBtn) {
698
- submitBtn.addEventListener('click', function() {
699
- showLoading();
700
-
701
- // Hide loading after the response is received (this is approximate)
702
- setTimeout(function() {
703
- const output = document.querySelector('.recipe-output');
704
- if (output && output.textContent.trim().length > 0) {
705
- hideLoading();
706
- } else {
707
- // Check every second until content appears
708
- const checkInterval = setInterval(function() {
709
- if (output && output.textContent.trim().length > 0) {
710
- hideLoading();
711
- clearInterval(checkInterval);
712
- }
713
- }, 1000);
714
-
715
- // Fallback: hide after 30 seconds regardless
716
- setTimeout(function() {
717
- hideLoading();
718
- clearInterval(checkInterval);
719
- }, 30000);
720
- }
721
- }, 3000);
722
- });
723
- }
724
- });
725
- </script>
726
- """
727
-
728
- # Create the Gradio interface with improved design
729
- with gr.Blocks(css=custom_css) as app:
730
- gr.HTML(html_header)
731
-
732
- with gr.Row():
733
- with gr.Column(scale=1):
734
- with gr.Group(elem_classes="input-section"):
735
- gr.HTML('<h3 class="section-header"><i class="icon">🔑</i> API Configuration</h3>')
736
- api_key_input = gr.Textbox(
737
- label="Together API Key",
738
- placeholder="Enter your Together API key here...",
739
- type="password",
740
- elem_classes="input-group",
741
- info="Your API key will remain private and is used only for this session."
742
- )
743
-
744
- gr.HTML('<h3 class="section-header"><i class="icon">📷</i> Upload Ingredients</h3>')
745
- # Use File component to handle multiple image uploads
746
- file_upload = gr.File(
747
- label="Upload images of ingredients",
748
- file_types=["image"],
749
- file_count="multiple",
750
- elem_classes="image-upload-container"
751
- )
752
-
753
- image_input = gr.Gallery(
754
- label="Uploaded Ingredients",
755
- elem_id="ingredient-gallery",
756
- columns=3,
757
- rows=2,
758
- height="auto",
759
- visible=False
760
- )
761
-
762
- gr.HTML('<h3 class="section-header"><i class="icon">⚙️</i> Recipe Preferences</h3>')
763
- with gr.Row():
764
- num_recipes = gr.Slider(
765
- minimum=1,
766
- maximum=5,
767
- value=3,
768
- step=1,
769
- label="Number of Recipe Suggestions",
770
- elem_classes="input-group"
771
- )
772
-
773
- with gr.Row():
774
- with gr.Column():
775
- dietary_restrictions = gr.Dropdown(
776
- choices=["None", "Vegetarian", "Vegan", "Gluten-Free", "Dairy-Free", "Low-Carb", "Keto", "Paleo"],
777
- value="None",
778
- label="Dietary Restrictions",
779
- elem_classes="input-group"
780
- )
781
-
782
- with gr.Column():
783
- cuisine_preference = gr.Dropdown(
784
- choices=["Any", "Italian", "Asian", "Mexican", "Mediterranean", "Indian", "American", "French", "Middle Eastern"],
785
- value="Any",
786
- label="Cuisine Preference",
787
- elem_classes="input-group"
788
- )
789
-
790
- submit_button = gr.Button("Get Recipe Suggestions", elem_classes="primary-button")
791
-
792
- with gr.Column(scale=1):
793
- with gr.Group(elem_classes="output-section"):
794
- gr.HTML('<h3 class="section-header"><i class="icon">🍽️</i> Your Personalized Recipes</h3>')
795
- output = gr.Markdown(elem_classes="recipe-output")
796
-
797
- gr.HTML(html_footer)
798
-
799
- # Handle file uploads to display in gallery
800
- def update_gallery(files):
801
- if not files:
802
- return gr.Gallery.update(visible=False)
803
- return gr.Gallery.update(value=[file.name for file in files], visible=True)
804
-
805
- file_upload.change(fn=update_gallery, inputs=file_upload, outputs=image_input)
806
-
807
- # Handle recipe generation
808
- def process_recipe_request(api_key, files, num_recipes, dietary_restrictions, cuisine_preference):
809
- if not files:
810
- return "Please upload at least one image of ingredients."
811
-
812
- # Get actual image files from the uploaded files
813
- images = [file.name for file in files]
814
- return get_recipe_suggestions(api_key, images, num_recipes, dietary_restrictions, cuisine_preference)
815
-
816
- # Set up the submission action
817
- submit_button.click(
818
- fn=process_recipe_request,
819
- inputs=[api_key_input, file_upload, num_recipes, dietary_restrictions, cuisine_preference],
820
- outputs=output
821
- )
822
-
823
- # Launch the app
824
- if __name__ == "__main__":
825
- app.launch()