shukdevdatta123 commited on
Commit
229a60d
Β·
verified Β·
1 Parent(s): 3a148b5

Create v1.txt

Browse files
Files changed (1) hide show
  1. v1.txt +825 -0
v1.txt ADDED
@@ -0,0 +1,825 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()