MaheshP98's picture
Update app.py
f229e3d verified
import os
import io
import uuid
import random
import json
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
import numpy as np
import pandas as pd
import gradio as gr
from pydub import AudioSegment
import logging
import whisper
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# ============ FAST, OFFLINE ASR (OpenAI Whisper local) ============
USE_ASR = True
WHISPER_MODEL_NAME = os.getenv("WHISPER_MODEL", "base") # Options: "tiny"|"base"|"small"|"medium"
if USE_ASR:
try:
_whisper_model = whisper.load_model(WHISPER_MODEL_NAME)
logger.info(f"Whisper model {WHISPER_MODEL_NAME} loaded successfully.")
except Exception as e:
logger.error(f"Failed to load Whisper model: {str(e)}")
raise
# ======================= LANGUAGES ================================
lang_map = {
"English": "en",
"Hindi": "hi",
"Telugu": "te",
"Spanish": "es",
"Tamil": "ta",
"Kannada": "kn",
"Bengali": "bn",
}
# =================== TRANSLATION TEMPLATES ========================
def get_output_template(lang: str) -> str:
templates = {
"English": (
"<div style='font-family: \"Open Sans\", sans-serif; background: #f8f9fa; padding: 20px; border-radius: 15px; "
"box-shadow: 0 6px 12px rgba(0,0,0,0.1); max-width: 900px; margin: 0 auto;'>"
"<h2 style='color: #28a745; text-align: center; margin-bottom: 20px; font-size: 28px;'>🌾 Your Personalized Weekly Diet Plan</h2>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 5px solid #17a2b8;'>"
"<h3 style='color: #17a2b8; margin-bottom: 10px; font-size: 20px;'>👤 Your Profile</h3>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>Age:</strong> {age} years</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>Gender:</strong> {gender}</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>Height:</strong> {height} cm</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>Weight:</strong> {weight} kg</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>Occupation:</strong> {occupation}</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>Activity Level:</strong> {activity_level}</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>Goal:</strong> {goal}</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>BMI:</strong> {bmi:.1f}</p>"
"</div>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 5px solid #dc3545;'>"
"<h3 style='color: #dc3545; margin-bottom: 10px; font-size: 20px;'>⚕️ Health Conditions</h3>"
"<p style='margin: 5px 0; font-size: 16px;'>{health_conditions}</p>"
"</div>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 5px solid #ffc107;'>"
"<h3 style='color: #ffc107; margin-bottom: 10px; font-size: 20px;'>🍽️ Dietary Preferences</h3>"
"<p style='margin: 5px 0; font-size: 16px;'>{dietary_preferences}</p>"
"</div>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 5px solid #fd7e14;'>"
"<h3 style='color: #fd7e14; margin-bottom: 10px; font-size: 20px;'>⚠️ Allergies</h3>"
"<p style='margin: 5px 0; font-size: 16px;'>{allergies}</p>"
"</div>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 5px solid #6f42c1;'>"
"<h3 style='color: #6f42c1; margin-bottom: 10px; font-size: 20px;'>💰 Budget</h3>"
"<p style='margin: 5px 0; font-size: 16px;'>₹{budget} per day</p>"
"</div>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-top: 20px; border-left: 5px solid #28a745;'>"
"<h3 style='color: #28a745; margin-bottom: 10px; font-size: 20px;'>📋 Your Weekly Meal Plan</h3>"
"{formatted_plan}"
"</div>"
"<div style='text-align: center; margin-top: 20px; font-size: 14px; color: #6c757d;'>"
"Note: This plan is for informational purposes. Consult a nutritionist for personalized advice."
"</div>"
"</div>"
),
"Hindi": (
"<div style='font-family: \"Open Sans\", sans-serif; background: #f8f9fa; padding: 20px; border-radius: 15px; "
"box-shadow: 0 6px 12px rgba(0,0,0,0.1); max-width: 900px; margin: 0 auto;'>"
"<h2 style='color: #28a745; text-align: center; margin-bottom: 20px; font-size: 28px;'>🌾 आपकी व्यक्तिगत साप्ताहिक आहार योजना</h2>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 5px solid #17a2b8;'>"
"<h3 style='color: #17a2b8; margin-bottom: 10px; font-size: 20px;'>👤 आपकी प्रोफ़ाइल</h3>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>आयु:</strong> {age} वर्ष</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>लिंग:</strong> {gender}</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>ऊँचाई:</strong> {height} सेमी</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>वज़न:</strong> {weight} किग्रा</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>पेशा:</strong> {occupation}</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>गतिविधि स्तर:</strong> {activity_level}</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>लक्ष्य:</strong> {goal}</p>"
"<p style='margin: 5px 0; font-size: 16px;'><strong>बीएमआई:</strong> {bmi:.1f}</p>"
"</div>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 5px solid #dc3545;'>"
"<h3 style='color: #dc3545; margin-bottom: 10px; font-size: 20px;'>⚕️ स्वास्थ्य स्थितियाँ</h3>"
"<p style='margin: 5px 0; font-size: 16px;'>{health_conditions}</p>"
"</div>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 5px solid #ffc107;'>"
"<h3 style='color: #ffc107; margin-bottom: 10px; font-size: 20px;'>🍽️ पसंद</h3>"
"<p style='margin: 5px 0; font-size: 16px;'>{dietary_preferences}</p>"
"</div>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 5px solid #fd7e14;'>"
"<h3 style='color: #fd7e14; margin-bottom: 10px; font-size: 20px;'>⚠️ एलर्जी</h3>"
"<p style='margin: 5px 0; font-size: 16px;'>{allergies}</p>"
"</div>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 5px solid #6f42c1;'>"
"<h3 style='color: #6f42c1; margin-bottom: 10px; font-size: 20px;'>💰 बजट</h3>"
"<p style='margin: 5px 0; font-size: 16px;'>₹{budget} प्रति दिन</p>"
"</div>"
"<div style='background: #ffffff; padding: 15px; border-radius: 10px; margin-top: 20px; border-left: 5px solid #28a745;'>"
"<h3 style='color: #28a745; margin-bottom: 10px; font-size: 20px;'>📋 आपकी साप्ताहिक भोजन योजना</h3>"
"{formatted_plan}"
"</div>"
"<div style='text-align: center; margin-top: 20px; font-size: 14px; color: #6c757d;'>"
"नोट: यह योजना सूचनात्मक उद्देश्यों के लिए है। व्यक्तिगत सलाह के लिए पोषण विशेषज्ञ से परामर्श करें।"
"</div>"
"</div>"
),
}
return templates.get(lang, templates["English"])
# ================== LOCAL FOOD DATABASE ===========================
FOODS = {
"grains": [
{"name": "rice", "kcal": 130, "protein": 2.7, "carbs": 28, "fat": 0.3, "price_per_100g": 5},
{"name": "wheat", "kcal": 340, "protein": 12, "carbs": 71, "fat": 1.5, "price_per_100g": 6},
{"name": "millet", "kcal": 378, "protein": 11, "carbs": 73, "fat": 4.2, "price_per_100g": 7},
{"name": "barley", "kcal": 354, "protein": 12, "carbs": 73, "fat": 2.3, "price_per_100g": 8},
{"name": "quinoa", "kcal": 120, "protein": 4.1, "carbs": 21, "fat": 1.9, "price_per_100g": 15},
{"name": "oats", "kcal": 389, "protein": 13, "carbs": 66, "fat": 7, "price_per_100g": 12},
{"name": "brown rice", "kcal": 123, "protein": 2.7, "carbs": 25, "fat": 1, "price_per_100g": 10},
],
"fruits": [
{"name": "apple", "kcal": 52, "protein": 0.3, "carbs": 14, "fat": 0.2, "price_per_100g": 15},
{"name": "banana", "kcal": 89, "protein": 1.1, "carbs": 23, "fat": 0.3, "price_per_100g": 10},
{"name": "orange", "kcal": 47, "protein": 0.9, "carbs": 12, "fat": 0.1, "price_per_100g": 12},
{"name": "papaya", "kcal": 43, "protein": 0.5, "carbs": 11, "fat": 0.3, "price_per_100g": 8},
{"name": "guava", "kcal": 68, "protein": 2.6, "carbs": 14, "fat": 0.9, "price_per_100g": 10},
{"name": "mango", "kcal": 60, "protein": 0.8, "carbs": 15, "fat": 0.4, "price_per_100g": 20},
{"name": "grapes", "kcal": 69, "protein": 0.7, "carbs": 18, "fat": 0.2, "price_per_100g": 25},
],
"vegetables": [
{"name": "carrot", "kcal": 41, "protein": 0.9, "carbs": 10, "fat": 0.2, "price_per_100g": 5},
{"name": "tomato", "kcal": 18, "protein": 0.9, "carbs": 3.9, "fat": 0.2, "price_per_100g": 4},
{"name": "spinach", "kcal": 23, "protein": 2.9, "carbs": 3.6, "fat": 0.4, "price_per_100g": 6},
{"name": "onion", "kcal": 40, "protein": 1.1, "carbs": 9, "fat": 0.1, "price_per_100g": 3},
{"name": "beetroot", "kcal": 43, "protein": 1.6, "carbs": 10, "fat": 0.2, "price_per_100g": 5},
{"name": "bitter gourd", "kcal": 17, "protein": 1, "carbs": 3.7, "fat": 0.2, "price_per_100g": 4},
{"name": "bottle gourd", "kcal": 14, "protein": 0.6, "carbs": 3.4, "fat": 0.1, "price_per_100g": 3},
{"name": "cucumber", "kcal": 15, "protein": 0.7, "carbs": 3.6, "fat": 0.1, "price_per_100g": 2},
{"name": "broccoli", "kcal": 34, "protein": 3, "carbs": 7, "fat": 0.4, "price_per_100g": 10},
],
"proteins": [
{"name": "lentils", "kcal": 116, "protein": 9, "carbs": 20, "fat": 0.4, "price_per_100g": 8},
{"name": "eggs", "kcal": 155, "protein": 13, "carbs": 1.1, "fat": 11, "price_per_100g": 15},
{"name": "chicken", "kcal": 239, "protein": 27, "carbs": 0, "fat": 14, "price_per_100g": 25},
{"name": "tofu", "kcal": 76, "protein": 8, "carbs": 1.9, "fat": 4.8, "price_per_100g": 12},
{"name": "fish", "kcal": 206, "protein": 22, "carbs": 0, "fat": 12, "price_per_100g": 30},
{"name": "chickpeas", "kcal": 164, "protein": 9, "carbs": 27, "fat": 2.6, "price_per_100g": 10},
{"name": "paneer", "kcal": 265, "protein": 18, "carbs": 1.2, "fat": 20, "price_per_100g": 20},
],
"dairy": [
{"name": "milk", "kcal": 42, "protein": 3.4, "carbs": 5, "fat": 1, "price_per_100g": 6},
{"name": "curd", "kcal": 98, "protein": 11, "carbs": 3.4, "fat": 4.3, "price_per_100g": 8},
{"name": "buttermilk", "kcal": 40, "protein": 3.3, "carbs": 4.8, "fat": 0.9, "price_per_100g": 5},
{"name": "paneer", "kcal": 265, "protein": 18, "carbs": 1.2, "fat": 20, "price_per_100g": 20},
{"name": "cheese", "kcal": 403, "protein": 23, "carbs": 0.3, "fat": 33, "price_per_100g": 30},
{"name": "yogurt", "kcal": 61, "protein": 10, "carbs": 3.6, "fat": 0.4, "price_per_100g": 10},
],
"spices": [
{"name": "turmeric", "kcal": 312, "protein": 9.7, "carbs": 67, "fat": 3.3, "price_per_100g": 15},
{"name": "cumin", "kcal": 375, "protein": 18, "carbs": 44, "fat": 22, "price_per_100g": 20},
{"name": "coriander", "kcal": 298, "protein": 12, "carbs": 55, "fat": 17, "price_per_100g": 18},
{"name": "fennel", "kcal": 345, "protein": 16, "carbs": 52, "fat": 15, "price_per_100g": 22},
{"name": "cardamom", "kcal": 311, "protein": 11, "carbs": 68, "fat": 7, "price_per_100g": 25},
{"name": "cinnamon", "kcal": 247, "protein": 4, "carbs": 80, "fat": 1.2, "price_per_100g": 30},
],
}
# ======================= ACTIVITY LEVELS ==========================
ACTIVITY_LEVELS = ["Low", "Moderate", "High", "Very High"]
# ========================= GOALS ==================================
GOALS = ["Lose Weight", "Maintain Weight", "Gain Weight", "Build Muscle"]
# =================== GENERATION FUNCTIONS =========================
@dataclass
class Meal:
name: str
kcal: float
protein: float
carbs: float
fat: float
cost: float
def generate_meal(food_type: str, num_foods: int = 2, budget: int = 200) -> Meal:
"""Generate a meal by randomly selecting foods within budget constraints."""
available_foods = [f for f in FOODS[food_type] if f["price_per_100g"] * 100 <= budget]
if not available_foods:
available_foods = FOODS[food_type] # Fallback if budget too low
foods = random.choices(available_foods, k=num_foods)
kcal = sum(f["kcal"] for f in foods) / len(foods)
protein = sum(f["protein"] for f in foods) / len(foods)
carbs = sum(f["carbs"] for f in foods) / len(foods)
fat = sum(f["fat"] for f in foods) / len(foods)
cost = sum(f["price_per_100g"] for f in foods) / len(foods)
names = ", ".join(f["name"].capitalize() for f in foods)
return Meal(names, kcal, protein, carbs, fat, cost)
def generate_daily_meals(lang: str, budget: int) -> Dict[str, Meal]:
"""Generate meals for a day based on budget."""
breakfast = generate_meal("grains", num_foods=2, budget=budget)
lunch = generate_meal("proteins", num_foods=2, budget=budget)
dinner = generate_meal("vegetables", num_foods=2, budget=budget)
snack = generate_meal("fruits", num_foods=1, budget=budget // 2) # Half budget for snack
return {
"breakfast": breakfast,
"lunch": lunch,
"dinner": dinner,
"snack": snack,
}
def generate_weekly_plan(lang: str, budget: int) -> Dict[str, Dict[str, Meal]]:
"""Generate a full weekly meal plan."""
plan = {}
for day in range(7):
day_name = f"Day {day + 1} ({['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][day]})"
plan[day_name] = generate_daily_meals(lang, budget)
return plan
# =================== TRANSCRIPTION FUNCTION =======================
def transcribe_audio(audio_path: str, lang: str) -> str:
"""Transcribe audio input using Whisper model."""
if not audio_path or not os.path.exists(audio_path):
return "❌ No audio file found. Please upload or record audio."
try:
temp_wav = f"temp_{uuid.uuid4().hex}.wav"
audio = AudioSegment.from_file(audio_path)
audio = audio.set_frame_rate(16000).set_channels(1)
audio.export(temp_wav, format="wav")
result = _whisper_model.transcribe(temp_wav, language=lang_map.get(lang, "en"))
os.remove(temp_wav)
text = result.get("text", "").strip()
return text if text else "❌ Could not understand the speech. Please try again."
except Exception as e:
logger.error(f"Transcription error: {str(e)}")
return "❌ Transcription failed due to an error."
# =================== DIET PLAN GENERATION ========================
def generate_diet_plan(
lang: str, age: int, gender: str, height: float, weight: float,
occupation: str, activity_level: str, goal: str, budget: int,
health_conditions_typed: str, dietary_preferences_typed: str, allergies_typed: str,
health_conditions_audio: Optional[str], dietary_preferences_audio: Optional[str], allergies_audio: Optional[str],
feedback: Optional[str] = None
) -> str:
"""Generate a personalized diet plan with validation and feedback."""
# Input validation
if not (1 <= age <= 120):
return "<p style='color: red; font-family: \"Open Sans\", sans-serif;'>Error: Age must be between 1 and 120 years.</p>"
if not (30 <= weight <= 200):
return "<p style='color: red; font-family: \"Open Sans\", sans-serif;'>Error: Weight must be between 30 and 200 kg.</p>"
if not (100 <= height <= 250):
return "<p style='color: red; font-family: \"Open Sans\", sans-serif;'>Error: Height must be between 100 and 250 cm.</p>"
if not (100 <= budget <= 1000):
return "<p style='color: red; font-family: \"Open Sans\", sans-serif;'>Error: Budget must be between ₹100 and ₹1000.</p>"
# Process audio or typed input
health_conditions = transcribe_audio(health_conditions_audio, lang) if health_conditions_audio else health_conditions_typed or "None"
dietary_preferences = transcribe_audio(dietary_preferences_audio, lang) if dietary_preferences_audio else dietary_preferences_typed or "None"
allergies = transcribe_audio(allergies_audio, lang) if allergies_audio else allergies_typed or "None"
# Generate plan
plan = generate_weekly_plan(lang, budget)
template = get_output_template(lang)
formatted_plan = "\n".join(
f"<div style='margin-bottom: 15px;'><strong>{day}</strong><br>"
f"🌞 Breakfast: {meals['breakfast'].name} ({meals['breakfast'].kcal:.0f} kcal, ₹{meals['breakfast'].cost:.0f})<br>"
f"🍲 Lunch: {meals['lunch'].name} ({meals['lunch'].kcal:.0f} kcal, ₹{meals['lunch'].cost:.0f})<br>"
f"🍽️ Dinner: {meals['dinner'].name} ({meals['dinner'].kcal:.0f} kcal, ₹{meals['dinner'].cost:.0f})<br>"
f"🍎 Snack: {meals['snack'].name} ({meals['snack'].kcal:.0f} kcal, ₹{meals['snack'].cost:.0f})<br>"
f"</div>" for day, meals in plan.items()
)
bmi = weight / (height / 100) ** 2
# Log feedback if provided
if feedback:
logger.info(f"User feedback: {feedback}")
return template.format(
age=age, gender=gender, height=height, weight=weight, occupation=occupation,
activity_level=activity_level, goal=goal, health_conditions=health_conditions,
dietary_preferences=dietary_preferences, allergies=allergies, budget=budget, bmi=bmi,
formatted_plan=formatted_plan
)
# =================== GRADIO UI ===================================
def create_ui() -> gr.Blocks:
with gr.Blocks(theme=gr.themes.Soft()) as app:
gr.Markdown("# Personalized Diet Plan Generator (Offline, Multilingual)")
lang = gr.Dropdown(label="Language", choices=list(lang_map.keys()), value="English")
with gr.Row():
age = gr.Number(label="Age", value=30, minimum=1, maximum=120)
gender = gr.Radio(label="Gender", choices=["Male", "Female", "Other"], value="Other")
with gr.Row():
weight = gr.Number(label="Weight (kg)", value=65, minimum=30, maximum=200)
height = gr.Number(label="Height (cm)", value=170, minimum=100, maximum=250)
occupation = gr.Textbox(label="Occupation", placeholder="e.g. Teacher, Engineer")
activity = gr.Dropdown(label="Activity Level", choices=ACTIVITY_LEVELS, value="Moderate")
goal = gr.Dropdown(label="Goal", choices=GOALS, value="Maintain")
budget = gr.Slider(label="Daily Budget (₹)", minimum=100, maximum=1000, step=50, value=200)
health_typed = gr.Textbox(label="Health conditions (Typed)", placeholder="e.g. Diabetes, high cholesterol")
dietary_typed = gr.Textbox(label="Dietary preferences (Typed)", placeholder="e.g. Veg, Non-Veg, High Protein")
allergies_typed = gr.Textbox(label="Allergies (Typed, comma-separated)", placeholder="e.g. Peanut, Milk")
gr.Markdown("Optional: Type or Speak your health info, preferences, and allergies.")
with gr.Row():
health_audio = gr.Audio(label="Speak Health Conditions (optional)", source="microphone", type="filepath")
dietary_audio = gr.Audio(label="Speak Dietary Preferences (optional)", source="microphone", type="filepath")
allergy_audio = gr.Audio(label="Speak Allergies (optional)", source="microphone", type="filepath")
feedback = gr.Textbox(label="Feedback (Optional)", placeholder="Share your thoughts on the plan...")
submit = gr.Button("Generate Diet Plan")
output = gr.HTML(label="Your Personalized Diet Plan")
submit.click(
fn=generate_diet_plan,
inputs=[lang, age, gender, height, weight, occupation, activity, goal, budget, health_typed, dietary_typed, allergies_typed, health_audio, dietary_audio, allergy_audio, feedback],
outputs=output
)
return app
# === Run the App ===
if __name__ == "__main__":
app = create_ui()
app.launch(server_name="0.0.0.0", server_port=7860, share=True)