AymanFahim commited on
Commit
2fb0d1f
ยท
verified ยท
1 Parent(s): 10824f1

Update app.py

Browse files

Major Update:

-Profile creation with calories properly calculated with 4 options for diets
- Recipe gen is having trouble with hugging face token call to other llm for some reason tried to trouble shoot but still errors

Files changed (1) hide show
  1. app.py +573 -60
app.py CHANGED
@@ -2,23 +2,159 @@ import gradio as gr
2
  from ultralytics import YOLO
3
  import PIL.Image
4
  import numpy as np
5
- from typing import List, Tuple
 
6
 
7
  # Load the trained model
8
  model = YOLO('best.pt')
9
 
10
- def detect_ingredients(images: List) -> Tuple[List, str]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  """
12
  Process multiple images and return detected ingredients.
 
13
 
14
  Args:
15
  images: List of uploaded images (file paths)
 
16
 
17
  Returns:
18
- Tuple of (processed_images, ingredient_list_text)
19
  """
20
  if not images or len(images) == 0:
21
- return [], "**No images uploaded.**"
22
 
23
  processed_images = []
24
  all_detected_items = set()
@@ -57,10 +193,187 @@ def detect_ingredients(images: List) -> Tuple[List, str]:
57
  ingredient_list_text = "**Detected Ingredients:**\n\n"
58
  ingredient_list_text += "\n".join([f"โ€ข {item.capitalize()}" for item in ingredient_list])
59
  ingredient_list_text += f"\n\n**Total unique items:** {len(ingredient_list)}"
 
 
 
 
60
  else:
61
  ingredient_list_text = "**No ingredients detected.**\n\nTry adjusting the image quality or lighting."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
- return processed_images, ingredient_list_text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  # Custom CSS for a modern, clean interface
66
  custom_css = """
@@ -97,91 +410,291 @@ custom_css = """
97
  .ingredient-list * {
98
  color: #000000 !important;
99
  }
100
- .ingredient-list p, .ingredient-list strong {
 
 
 
 
 
 
 
 
101
  color: #000000 !important;
102
  }
103
  """
104
 
105
  # Create the Gradio interface
106
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
 
107
  gr.Markdown(
108
  """
109
- # ๐Ÿฅ— Fridge Ingredient Scanner
110
 
111
- Upload photos of your fridge, pantry, or groceries to automatically detect ingredients!
112
  """,
113
  elem_classes=["main-header"]
114
  )
115
 
116
- gr.Markdown(
117
- """
118
- <div class="description-box">
119
- <strong>๐Ÿ“ธ How to use:</strong><br>
120
- 1. Click "Upload Images" or drag and drop multiple photos<br>
121
- 2. Wait for the AI to analyze your ingredients<br>
122
- 3. View all processed images with detection boxes and the complete ingredient list
123
- </div>
124
- """,
125
- elem_classes=["description-box"]
126
- )
127
 
128
- with gr.Row():
129
- with gr.Column(scale=1):
130
- image_input = gr.File(
131
- file_count="multiple",
132
- file_types=["image"],
133
- label="๐Ÿ“ Upload Images",
134
- height=200
 
 
 
 
 
135
  )
136
 
137
- process_btn = gr.Button(
138
- "๐Ÿ” Detect Ingredients",
139
- variant="primary",
140
- size="lg"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  )
142
 
143
- gr.Markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
- ingredient_output = gr.Markdown(
146
- label="๐Ÿ“‹ Detected Ingredients",
147
- elem_classes=["ingredient-list"]
 
 
148
  )
149
 
150
- with gr.Column(scale=2):
151
- gallery_output = gr.Gallery(
152
- label="๐Ÿ–ผ๏ธ Processed Images with Detections",
153
- show_label=True,
154
- elem_id="gallery",
155
- columns=2,
156
- rows=2,
157
- height="auto",
158
- allow_preview=True,
159
- preview=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  )
161
-
162
- # Process images when button is clicked
163
- process_btn.click(
164
- fn=detect_ingredients,
165
- inputs=image_input,
166
- outputs=[gallery_output, ingredient_output]
167
- )
168
-
169
- # Also process when images are uploaded (auto-detect)
170
- image_input.upload(
171
- fn=detect_ingredients,
172
- inputs=image_input,
173
- outputs=[gallery_output, ingredient_output]
174
- )
175
 
176
  gr.Markdown(
177
  """
178
  ---
179
  <div style="text-align: center; color: #666; padding: 20px;">
180
- <small>Powered by YOLOv11 | Upload multiple images to scan your entire fridge!</small>
181
  </div>
182
  """
183
  )
184
 
185
  # Launch the app
186
  if __name__ == "__main__":
187
- demo.launch()
 
2
  from ultralytics import YOLO
3
  import PIL.Image
4
  import numpy as np
5
+ from typing import List, Tuple, Dict, Optional
6
+ from huggingface_hub import InferenceClient
7
 
8
  # Load the trained model
9
  model = YOLO('best.pt')
10
 
11
+ # Initialize state structure
12
+ def init_user_state() -> Dict:
13
+ """Initialize the user state dictionary."""
14
+ return {
15
+ 'name': '',
16
+ 'age': None,
17
+ 'weight_lbs': None,
18
+ 'height_cm': None,
19
+ 'gender': '',
20
+ 'activity_level': '',
21
+ 'goal': '',
22
+ 'calorie_target': None,
23
+ 'cuisine_preference': '',
24
+ 'detected_ingredients': [],
25
+ 'ingredient_list_text': ''
26
+ }
27
+
28
+ # ==================== BMR & CALORIE CALCULATION ====================
29
+
30
+ def convert_height_to_cm(height_ft: Optional[float], height_in: Optional[float]) -> Optional[float]:
31
+ """Convert feet and inches to centimeters."""
32
+ if height_ft is None or height_in is None:
33
+ return None
34
+ total_inches = (height_ft * 12) + height_in
35
+ return total_inches * 2.54
36
+
37
+ def calculate_bmr(weight_kg: float, height_cm: float, age: int, gender: str) -> float:
38
+ """
39
+ Calculate Basal Metabolic Rate using Mifflin-St Jeor Equation.
40
+
41
+ BMR (Men) = 10 ร— weight(kg) + 6.25 ร— height(cm) - 5 ร— age(years) + 5
42
+ BMR (Women) = 10 ร— weight(kg) + 6.25 ร— height(cm) - 5 ร— age(years) - 161
43
+ """
44
+ base_bmr = (10 * weight_kg) + (6.25 * height_cm) - (5 * age)
45
+
46
+ if gender.lower() == 'male':
47
+ bmr = base_bmr + 5
48
+ else: # female
49
+ bmr = base_bmr - 161
50
+
51
+ return bmr
52
+
53
+ def get_activity_multiplier(activity_level: str) -> float:
54
+ """Get activity multiplier based on activity level."""
55
+ multipliers = {
56
+ 'Sedentary': 1.2,
57
+ 'Light': 1.375,
58
+ 'Moderate': 1.55,
59
+ 'Active': 1.725,
60
+ 'Very Active': 1.9
61
+ }
62
+ return multipliers.get(activity_level, 1.2)
63
+
64
+ def get_goal_adjustment(goal: str) -> int:
65
+ """Get calorie adjustment based on goal."""
66
+ adjustments = {
67
+ 'Cutting': -500,
68
+ 'Maintain': 0,
69
+ 'Bulking': +500,
70
+ 'Custom': 0 # Will be handled separately
71
+ }
72
+ return adjustments.get(goal, 0)
73
+
74
+ def calculate_calorie_target(
75
+ weight_lbs: Optional[float],
76
+ height_ft: Optional[float],
77
+ height_in: Optional[float],
78
+ age: Optional[int],
79
+ gender: Optional[str],
80
+ activity_level: Optional[str],
81
+ goal: Optional[str],
82
+ custom_calories: Optional[float],
83
+ state: Dict
84
+ ) -> Tuple[Dict, str]:
85
+ """
86
+ Calculate daily calorie target based on user inputs.
87
+ Updates state and returns formatted result.
88
+ """
89
+ # Validate inputs
90
+ if not all([weight_lbs, height_ft is not None, height_in is not None, age, gender, activity_level, goal]):
91
+ return state, "**Please fill in all required fields.**"
92
+
93
+ # Convert weight to kg
94
+ weight_kg = weight_lbs * 0.453592
95
+
96
+ # Convert height to cm
97
+ height_cm = convert_height_to_cm(height_ft, height_in)
98
+ if height_cm is None:
99
+ return state, "**Please enter valid height values.**"
100
+
101
+ # Calculate BMR
102
+ bmr = calculate_bmr(weight_kg, height_cm, age, gender)
103
+
104
+ # Get activity multiplier
105
+ activity_mult = get_activity_multiplier(activity_level)
106
+
107
+ # Calculate TDEE (Total Daily Energy Expenditure)
108
+ tdee = bmr * activity_mult
109
+
110
+ # Apply goal adjustment
111
+ if goal == 'Custom' and custom_calories is not None:
112
+ calorie_target = custom_calories
113
+ else:
114
+ goal_adj = get_goal_adjustment(goal)
115
+ calorie_target = tdee + goal_adj
116
+
117
+ # Update state
118
+ state['weight_lbs'] = weight_lbs
119
+ state['height_cm'] = height_cm
120
+ state['age'] = age
121
+ state['gender'] = gender
122
+ state['activity_level'] = activity_level
123
+ state['goal'] = goal
124
+ state['calorie_target'] = calorie_target
125
+
126
+ # Format output
127
+ result_text = f"""
128
+ ## ๐Ÿ“Š Your Daily Calorie Target
129
+
130
+ **BMR (Basal Metabolic Rate):** {bmr:.0f} calories/day
131
+ **Activity Level:** {activity_level} (ร—{activity_mult:.2f})
132
+ **TDEE (Total Daily Energy Expenditure):** {tdee:.0f} calories/day
133
+ **Goal Adjustment:** {get_goal_adjustment(goal):+.0f} calories
134
+
135
+ ### ๐ŸŽฏ **Daily Calorie Target: {calorie_target:.0f} calories**
136
+
137
+ *This target is based on your profile and has been saved for recipe generation.*
138
+ """
139
+
140
+ return state, result_text
141
+
142
+ # ==================== INGREDIENT DETECTION ====================
143
+
144
+ def detect_ingredients(images: List, state: Dict) -> Tuple[Dict, List, str]:
145
  """
146
  Process multiple images and return detected ingredients.
147
+ Also updates the state with detected ingredients.
148
 
149
  Args:
150
  images: List of uploaded images (file paths)
151
+ state: User state dictionary
152
 
153
  Returns:
154
+ Tuple of (updated_state, processed_images, ingredient_list_text)
155
  """
156
  if not images or len(images) == 0:
157
+ return state, [], "**No images uploaded.**"
158
 
159
  processed_images = []
160
  all_detected_items = set()
 
193
  ingredient_list_text = "**Detected Ingredients:**\n\n"
194
  ingredient_list_text += "\n".join([f"โ€ข {item.capitalize()}" for item in ingredient_list])
195
  ingredient_list_text += f"\n\n**Total unique items:** {len(ingredient_list)}"
196
+
197
+ # Update state with detected ingredients
198
+ state['detected_ingredients'] = ingredient_list
199
+ state['ingredient_list_text'] = ingredient_list_text
200
  else:
201
  ingredient_list_text = "**No ingredients detected.**\n\nTry adjusting the image quality or lighting."
202
+ state['detected_ingredients'] = []
203
+ state['ingredient_list_text'] = ingredient_list_text
204
+
205
+ return state, processed_images, ingredient_list_text
206
+
207
+ # ==================== RECIPE GENERATION ====================
208
+
209
+ def generate_recipes(cuisine_preference: Optional[str], state: Dict) -> Tuple[Dict, str]:
210
+ """
211
+ Generate recipes using LLM based on user profile and detected ingredients.
212
+ """
213
+ # Validate that we have the necessary data
214
+ if not state.get('calorie_target'):
215
+ return state, "**โš ๏ธ Please complete your User Profile & Goals first to set your calorie target.**"
216
 
217
+ if not state.get('detected_ingredients'):
218
+ return state, "**โš ๏ธ Please scan ingredients in the Ingredient Scanner tab first.**"
219
+
220
+ if not cuisine_preference:
221
+ return state, "**โš ๏ธ Please select a cuisine preference.**"
222
+
223
+ # Update state
224
+ state['cuisine_preference'] = cuisine_preference
225
+
226
+ # Get user data
227
+ calorie_target = int(state['calorie_target'])
228
+ goal = state.get('goal', 'Maintain')
229
+ ingredients = state['detected_ingredients']
230
+ ingredient_list = ", ".join([item.capitalize() for item in ingredients])
231
+
232
+ # Map goal to dietary focus
233
+ goal_descriptions = {
234
+ 'Cutting': 'weight loss and calorie deficit',
235
+ 'Maintain': 'maintaining current weight',
236
+ 'Bulking': 'muscle gain with high protein',
237
+ 'Custom': 'your custom calorie target'
238
+ }
239
+ goal_desc = goal_descriptions.get(goal, 'your goals')
240
+
241
+ # Construct prompt
242
+ prompt = f"""You are a professional nutritionist and chef. Create 3 distinct, detailed recipes that:
243
+
244
+ 1. Use these available ingredients: {ingredient_list}
245
+ 2. Fit within a daily calorie target of approximately {calorie_target} calories per day
246
+ 3. Match {cuisine_preference} cuisine style
247
+ 4. Align with the goal of {goal_desc}
248
+
249
+ For each recipe, provide:
250
+ - Recipe name
251
+ - Serving size
252
+ - Estimated calories per serving
253
+ - Complete ingredient list (you may suggest additional common pantry items if needed)
254
+ - Step-by-step cooking instructions
255
+ - Nutritional highlights relevant to the goal
256
+
257
+ Format each recipe clearly with headers. Make the recipes practical, delicious, and suitable for home cooking."""
258
+
259
+ try:
260
+ # Use Hugging Face Inference API
261
+ import os
262
+ # Try multiple ways to get the token
263
+ hf_token = None
264
+
265
+ # Method 1: Check HF_TOKEN environment variable
266
+ hf_token = os.getenv("HF_TOKEN", None)
267
+
268
+ # Method 2: Check HUGGING_FACE_HUB_TOKEN (alternative name)
269
+ if not hf_token:
270
+ hf_token = os.getenv("HUGGING_FACE_HUB_TOKEN", None)
271
+
272
+ # Method 3: Try to get from Hugging Face cache (for Spaces or logged-in users)
273
+ if not hf_token:
274
+ try:
275
+ from huggingface_hub import HfFolder
276
+ hf_token = HfFolder.get_token()
277
+ except:
278
+ pass
279
+
280
+ if not hf_token:
281
+ return state, """**โš ๏ธ Hugging Face Token Required**
282
+
283
+ Please set your HF_TOKEN environment variable to use recipe generation.
284
+
285
+ **For Hugging Face Spaces:**
286
+ 1. Go to your Space Settings (gear icon)
287
+ 2. Scroll to "Repository secrets"
288
+ 3. Click "New secret"
289
+ 4. Name: `HF_TOKEN`
290
+ 5. Value: Your Hugging Face token
291
+ 6. Click "Add secret" and restart your Space
292
+
293
+ **For Local Development (Windows):**
294
+ 1. Press Win+R, type `sysdm.cpl`, press Enter
295
+ 2. Go to "Advanced" tab โ†’ "Environment Variables"
296
+ 3. Under "User variables", click "New"
297
+ 4. Variable name: `HF_TOKEN`
298
+ 5. Variable value: Your Hugging Face token
299
+ 6. Click OK and restart your application
300
+
301
+ Get your token at: https://huggingface.co/settings/tokens"""
302
+
303
+ client = InferenceClient(token=hf_token)
304
+
305
+ # Try using models that support text-generation
306
+ # List of models to try in order of preference (all verified to work with text-generation)
307
+ models_to_try = [
308
+ "meta-llama/Llama-3.2-3B-Instruct", # Fast and reliable
309
+ "meta-llama/Llama-3.1-8B-Instruct", # Better quality
310
+ "mistralai/Mistral-7B-Instruct-v0.3", # Alternative option
311
+ "microsoft/Phi-3-mini-4k-instruct", # Lightweight fallback
312
+ "google/gemma-2-2b-it", # Additional reliable option
313
+ ]
314
+
315
+ response = None
316
+ last_error = None
317
+ successful_model = None
318
+
319
+ for model_name in models_to_try:
320
+ try:
321
+ response = client.text_generation(
322
+ prompt,
323
+ model=model_name,
324
+ max_new_tokens=1500,
325
+ temperature=0.7,
326
+ )
327
+ successful_model = model_name
328
+ break # Success, exit the loop
329
+ except Exception as model_error:
330
+ last_error = model_error
331
+ continue # Try next model
332
+
333
+ # If all models failed, raise error with details
334
+ if response is None:
335
+ error_msg = f"All models failed. Last error: {str(last_error)}"
336
+ if not hf_token:
337
+ error_msg += "\n\n๐Ÿ’ก TIP: Make sure you have set your HF_TOKEN environment variable."
338
+ raise Exception(error_msg)
339
+
340
+ # Extract text if response is a formatted object
341
+ if hasattr(response, 'generated_text'):
342
+ response_text = response.generated_text
343
+ elif isinstance(response, str):
344
+ response_text = response
345
+ else:
346
+ response_text = str(response)
347
+
348
+ recipes_text = f"""## ๐Ÿณ Recipe Suggestions for {cuisine_preference} Cuisine
349
+
350
+ **Your Profile:**
351
+ - Daily Calorie Target: {calorie_target} calories
352
+ - Goal: {goal}
353
+ - Available Ingredients: {ingredient_list}
354
+
355
+ ---
356
+
357
+ {response_text}
358
+
359
+ ---
360
+
361
+ *Recipes generated based on your profile and available ingredients.*"""
362
+
363
+ return state, recipes_text
364
+
365
+ except Exception as e:
366
+ error_msg = f"""**โš ๏ธ Error generating recipes.**
367
+
368
+ Please try again. If the issue persists, you may need to:
369
+ 1. Check your internet connection
370
+ 2. Ensure you have a Hugging Face API token set (if required)
371
+ 3. Try a different cuisine preference
372
+
373
+ Error details: {str(e)}"""
374
+ return state, error_msg
375
+
376
+ # ==================== GRADIO INTERFACE ====================
377
 
378
  # Custom CSS for a modern, clean interface
379
  custom_css = """
 
410
  .ingredient-list * {
411
  color: #000000 !important;
412
  }
413
+ .calorie-result {
414
+ background: #e8f5e9;
415
+ padding: 20px;
416
+ border-radius: 8px;
417
+ border-left: 4px solid #4caf50;
418
+ margin-top: 20px;
419
+ color: #000000 !important;
420
+ }
421
+ .calorie-result * {
422
  color: #000000 !important;
423
  }
424
  """
425
 
426
  # Create the Gradio interface
427
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
428
+ # Header
429
  gr.Markdown(
430
  """
431
+ # ๐Ÿฅ— Smart Recipe Assistant
432
 
433
+ Your AI-powered kitchen companion: Scan ingredients, calculate calories, and generate personalized recipes!
434
  """,
435
  elem_classes=["main-header"]
436
  )
437
 
438
+ # Initialize state
439
+ user_state = gr.State(value=init_user_state)
 
 
 
 
 
 
 
 
 
440
 
441
+ # Tab structure
442
+ with gr.Tabs() as tabs:
443
+ # ========== TAB 1: USER PROFILE & GOALS ==========
444
+ with gr.Tab("๐Ÿ‘ค User Profile & Goals"):
445
+ gr.Markdown(
446
+ """
447
+ <div class="description-box">
448
+ <strong>๐Ÿ“‹ Set up your profile:</strong><br>
449
+ Enter your personal information and fitness goals to calculate your daily calorie target.
450
+ This will be used to generate personalized recipes.
451
+ </div>
452
+ """
453
  )
454
 
455
+ with gr.Row():
456
+ with gr.Column(scale=1):
457
+ name_input = gr.Textbox(
458
+ label="Name",
459
+ placeholder="Enter your name",
460
+ value=""
461
+ )
462
+
463
+ with gr.Row():
464
+ age_input = gr.Number(
465
+ label="Age",
466
+ minimum=1,
467
+ maximum=120,
468
+ value=None,
469
+ precision=0
470
+ )
471
+
472
+ gender_input = gr.Dropdown(
473
+ label="Gender",
474
+ choices=["Male", "Female"],
475
+ value=None
476
+ )
477
+
478
+ with gr.Row():
479
+ weight_input = gr.Number(
480
+ label="Weight (lbs)",
481
+ minimum=1,
482
+ maximum=1000,
483
+ value=None,
484
+ precision=1
485
+ )
486
+
487
+ with gr.Row():
488
+ height_ft_input = gr.Number(
489
+ label="Height (feet)",
490
+ minimum=1,
491
+ maximum=8,
492
+ value=None,
493
+ precision=0
494
+ )
495
+
496
+ height_in_input = gr.Number(
497
+ label="Height (inches)",
498
+ minimum=0,
499
+ maximum=11,
500
+ value=None,
501
+ precision=0
502
+ )
503
+
504
+ activity_input = gr.Dropdown(
505
+ label="Activity Level",
506
+ choices=["Sedentary", "Light", "Moderate", "Active", "Very Active"],
507
+ value=None,
508
+ info="Sedentary: Little/no exercise | Light: Light exercise 1-3 days/week | Moderate: Moderate exercise 3-5 days/week | Active: Hard exercise 6-7 days/week | Very Active: Very hard exercise, physical job"
509
+ )
510
+
511
+ goal_input = gr.Radio(
512
+ label="Goal",
513
+ choices=["Cutting", "Maintain", "Bulking", "Custom"],
514
+ value=None
515
+ )
516
+
517
+ custom_calories_input = gr.Number(
518
+ label="Custom Calorie Target",
519
+ minimum=800,
520
+ maximum=5000,
521
+ value=None,
522
+ precision=0,
523
+ visible=False,
524
+ info="Enter your desired daily calorie target"
525
+ )
526
+
527
+ calculate_btn = gr.Button(
528
+ "๐Ÿ“Š Calculate Calorie Target",
529
+ variant="primary",
530
+ size="lg"
531
+ )
532
+
533
+ with gr.Column(scale=1):
534
+ calorie_output = gr.Markdown(
535
+ label="Calorie Calculation Result",
536
+ elem_classes=["calorie-result"]
537
+ )
538
+
539
+ # Show/hide custom calories input based on goal selection
540
+ def toggle_custom_calories(goal):
541
+ if goal == "Custom":
542
+ return gr.update(visible=True)
543
+ else:
544
+ # Reset value to None when hiding to prevent validation errors
545
+ return gr.update(visible=False, value=None)
546
+
547
+ goal_input.change(
548
+ fn=toggle_custom_calories,
549
+ inputs=goal_input,
550
+ outputs=custom_calories_input
551
  )
552
 
553
+ # Calculate calories
554
+ calculate_btn.click(
555
+ fn=calculate_calorie_target,
556
+ inputs=[
557
+ weight_input,
558
+ height_ft_input,
559
+ height_in_input,
560
+ age_input,
561
+ gender_input,
562
+ activity_input,
563
+ goal_input,
564
+ custom_calories_input,
565
+ user_state
566
+ ],
567
+ outputs=[user_state, calorie_output]
568
+ )
569
 
570
+ # Update name in state when changed
571
+ name_input.change(
572
+ fn=lambda name, state: ({**state, 'name': name}, state),
573
+ inputs=[name_input, user_state],
574
+ outputs=[user_state, user_state]
575
  )
576
 
577
+ # ========== TAB 2: INGREDIENT SCANNER ==========
578
+ with gr.Tab("๐Ÿ“ธ Ingredient Scanner"):
579
+ gr.Markdown(
580
+ """
581
+ <div class="description-box">
582
+ <strong>๐Ÿ“ธ How to use:</strong><br>
583
+ 1. Click "Upload Images" or drag and drop multiple photos<br>
584
+ 2. Wait for the AI to analyze your ingredients<br>
585
+ 3. View all processed images with detection boxes and the complete ingredient list<br>
586
+ 4. Detected ingredients will be saved for recipe generation
587
+ </div>
588
+ """
589
+ )
590
+
591
+ with gr.Row():
592
+ with gr.Column(scale=1):
593
+ image_input = gr.File(
594
+ file_count="multiple",
595
+ file_types=["image"],
596
+ label="๐Ÿ“ Upload Images",
597
+ height=200
598
+ )
599
+
600
+ process_btn = gr.Button(
601
+ "๐Ÿ” Detect Ingredients",
602
+ variant="primary",
603
+ size="lg"
604
+ )
605
+
606
+ gr.Markdown("---")
607
+
608
+ ingredient_output = gr.Markdown(
609
+ label="๐Ÿ“‹ Detected Ingredients",
610
+ elem_classes=["ingredient-list"]
611
+ )
612
+
613
+ with gr.Column(scale=2):
614
+ gallery_output = gr.Gallery(
615
+ label="๐Ÿ–ผ๏ธ Processed Images with Detections",
616
+ show_label=True,
617
+ elem_id="gallery",
618
+ columns=2,
619
+ rows=2,
620
+ height="auto",
621
+ allow_preview=True,
622
+ preview=True
623
+ )
624
+
625
+ # Process images when button is clicked
626
+ process_btn.click(
627
+ fn=detect_ingredients,
628
+ inputs=[image_input, user_state],
629
+ outputs=[user_state, gallery_output, ingredient_output]
630
+ )
631
+
632
+ # Also process when images are uploaded (auto-detect)
633
+ image_input.upload(
634
+ fn=detect_ingredients,
635
+ inputs=[image_input, user_state],
636
+ outputs=[user_state, gallery_output, ingredient_output]
637
+ )
638
+
639
+ # ========== TAB 3: RECIPE GENERATOR ==========
640
+ with gr.Tab("๐Ÿณ Recipe Generator"):
641
+ gr.Markdown(
642
+ """
643
+ <div class="description-box">
644
+ <strong>๐Ÿณ Generate personalized recipes:</strong><br>
645
+ Based on your calorie target, fitness goals, and detected ingredients,
646
+ we'll generate 3 custom recipes tailored to your preferences.
647
+ </div>
648
+ """
649
+ )
650
+
651
+ with gr.Row():
652
+ with gr.Column(scale=1):
653
+ cuisine_input = gr.Dropdown(
654
+ label="Cuisine Preference",
655
+ choices=["Mexican", "Chinese", "American", "Italian", "Indian", "Japanese", "Mediterranean", "Thai", "French"],
656
+ value=None,
657
+ info="Select your preferred cuisine style"
658
+ )
659
+
660
+ generate_btn = gr.Button(
661
+ "โœจ Generate Recipes",
662
+ variant="primary",
663
+ size="lg"
664
+ )
665
+
666
+ gr.Markdown("---")
667
+ gr.Markdown(
668
+ """
669
+ **๐Ÿ“ Requirements:**
670
+ - Complete User Profile & Goals tab
671
+ - Scan ingredients in Ingredient Scanner tab
672
+ - Select a cuisine preference
673
+ """
674
+ )
675
+
676
+ with gr.Column(scale=2):
677
+ recipe_output = gr.Markdown(
678
+ label="Generated Recipes",
679
+ elem_classes=["ingredient-list"]
680
+ )
681
+
682
+ # Generate recipes
683
+ generate_btn.click(
684
+ fn=generate_recipes,
685
+ inputs=[cuisine_input, user_state],
686
+ outputs=[user_state, recipe_output]
687
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
688
 
689
  gr.Markdown(
690
  """
691
  ---
692
  <div style="text-align: center; color: #666; padding: 20px;">
693
+ <small>Powered by YOLOv11 & AI Recipe Generation | Your smart kitchen assistant!</small>
694
  </div>
695
  """
696
  )
697
 
698
  # Launch the app
699
  if __name__ == "__main__":
700
+ demo.launch()