import os, uuid, random, json from dataclasses import dataclass from typing import Dict, List, Tuple import numpy as np import pandas as pd import gradio as gr from pydub import AudioSegment import logging from time import sleep from simple_salesforce import Salesforce from dotenv import load_dotenv from datetime import datetime import tempfile # Set up logging for debugging with timestamp 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") # "tiny"|"base"|"small"|"medium" if USE_ASR: import whisper _whisper_model = whisper.load_model(WHISPER_MODEL_NAME) # ======================= LANGUAGES ================================ lang_map = { "English": "en", "Hindi": "hi", "Telugu": "te", "Spanish": "es", } # =================== TRANSLATION TEMPLATES ======================== def get_output_template(lang: str) -> str: templates = { "English": ( "🌾 Personalized Weekly Diet Plan\n" "────────────────────────────\n" "👤 Age: {age} | Gender: {gender}\n" "📏 Height: {height} cm | ⚖️ Weight: {weight} kg\n" "💼 Occupation: {occupation}\n" "🏃 Activity: {activity_level} | 🎯 Goal: {goal}\n" "⚕️ Health: {health_conditions}\n" "🍽️ Preferences: {dietary_preferences}\n" "⚠️ Allergies: {allergies}\n" "💰 Budget: ₹{budget}/day\n" ), "Hindi": ( "🌾 साप्ताहिक व्यक्तिगत आहार योजना\n" "────────────────────────────\n" "👤 आयु: {age} | लिंग: {gender}\n" "📏 ऊँचाई: {height} सेमी | ⚖️ वज़न: {weight} किग्रा\n" "💼 पेशा: {occupation}\n" "🏃 गतिविधि: {activity_level} | 🎯 लक्ष्य: {goal}\n" "⚕️ स्वास्थ्य: {health_conditions}\n" "🍽️ पसंद: {dietary_preferences}\n" "⚠️ एलर्जी: {allergies}\n" "💰 बजट: ₹{budget}/दिन\n" ), "Telugu": ( "🌾 వారాంత వ్యక్తిగత ఆహార ప్రణాళిక\n" "────────────────────────────\n" "👤 వయసు: {age} | లింగం: {gender}\n" "📏 ఎత్తు: {height} సెం.మీ | ⚖️ బరువు: {weight} కి.గ్రా\n" "💼 వృత్తి: {occupation}\n" "🏃 చర్య స్థాయి: {activity_level} | 🎯 లక్ష్యం: {goal}\n" "⚕️ ఆరోగ్యం: {health_conditions}\n" "🍽️ అభిరుచులు: {dietary_preferences}\n" "⚠️ అలర్జీలు: {allergies}\n" "💰 బడ్జెట్: ₹{budget}/రోజు\n" ), "Spanish": ( "🌾 Plan de dieta semanal personalizado\n" "────────────────────────────\n" "👤 Edad: {age} | Género: {gender}\n" "📏 Altura: {height} cm | ⚖️ Peso: {weight} kg\n" "💼 Ocupación: {occupation}\n" "🏃 Actividad: {activity_level} | 🎯 Objetivo: {goal}\n" "⚕️ Salud: {health_conditions}\n" "🍽️ Preferencias: {dietary_preferences}\n" "⚠️ Alergias: {allergies}\n" "💰 Presupuesto: ₹{budget}/día\n" ), } return templates.get(lang, templates["English"]) # ================== LOCAL FOOD DATABASE =========================== FOODS = { "grains": [ {"name": "rice", "kcal": 130, "protein": 2.7, "carbs": 28, "fat": 0.3}, {"name": "wheat roti", "kcal": 110, "protein": 3.0, "carbs": 18, "fat": 2.0}, {"name": "millet (jowar)", "kcal": 119, "protein": 3.5, "carbs": 23, "fat": 1.0}, {"name": "oats", "kcal": 389, "protein": 16.9, "carbs": 66, "fat": 6.9}, ], "vegetables": [ {"name": "spinach", "kcal": 23, "protein": 2.9, "carbs": 3.6, "fat": 0.4, "vitc": 28}, {"name": "carrot", "kcal": 41, "protein": 0.9, "carbs": 10, "fat": 0.2, "vitc": 6}, {"name": "beetroot", "kcal": 43, "protein": 1.6, "carbs": 10, "fat": 0.2, "vitc": 4}, {"name": "cabbage", "kcal": 25, "protein": 1.3, "carbs": 6, "fat": 0.1, "vitc": 36}, {"name": "tomato", "kcal": 18, "protein": 0.9, "carbs": 3.9, "fat": 0.2, "vitc": 14}, ], "proteins": [ {"name": "lentils (dal)", "kcal": 116, "protein": 9.0, "carbs": 20, "fat": 0.4, "veg": True}, {"name": "chickpeas", "kcal": 164, "protein": 8.9, "carbs": 27, "fat": 2.6, "veg": True}, {"name": "paneer", "kcal": 265, "protein": 18, "carbs": 6, "fat": 20, "veg": True}, {"name": "eggs", "kcal": 155, "protein": 13, "carbs": 1.1, "fat": 11}, {"name": "chicken", "kcal": 239, "protein": 27, "carbs": 0, "fat": 14}, {"name": "tofu", "kcal": 76, "protein": 8, "carbs": 2, "fat": 4.8, "veg": True}, {"name": "fish", "kcal": 206, "protein": 22, "carbs": 0, "fat": 12}, ], "fruits": [ {"name": "banana", "kcal": 89, "protein": 1.1, "carbs": 23, "fat": 0.3, "vitc": 9}, {"name": "orange", "kcal": 47, "protein": 0.9, "carbs": 12, "fat": 0.1, "vitc": 53}, {"name": "apple", "kcal": 52, "protein": 0.3, "carbs": 14, "fat": 0.2, "vitc": 5}, {"name": "guava", "kcal": 68, "protein": 2.6, "carbs": 14, "fat": 1, "vitc": 228}, ], "fats": [ {"name": "groundnut oil", "kcal": 884, "protein": 0, "carbs": 0, "fat": 100}, {"name": "ghee", "kcal": 900, "protein": 0, "carbs": 0, "fat": 100}, {"name": "olive oil", "kcal": 884, "protein": 0, "carbs": 0, "fat": 100}, ], "dairy": [ {"name": "milk", "kcal": 60, "protein": 3.2, "carbs": 5, "fat": 3.3}, {"name": "curd", "kcal": 61, "protein": 3.5, "carbs": 4.7, "fat": 3.3}, {"name": "buttermilk", "kcal": 40, "protein": 3.3, "carbs": 4.8, "fat": 1.0}, ], } # ========= SIMPLE TRANSLATIONS FOR FOOD NAMES (demo) ============= FOOD_NAME_I18N = { "Hindi": { "rice": "चावल", "wheat roti": "गेहूँ रोटी", "millet (jowar)": "ज्वार", "oats": "ओट्स", "spinach": "पालक", "carrot": "गाजर", "beetroot": "चुकंदर", "cabbage": "पत्ता गोभी", "tomato": "टमाटर", "lentils (dal)": "दाल", "chickpeas": "काबुली चना", "paneer": "पनीर", "eggs": "अंडे", "chicken": "चिकन", "tofu": "टोफू", "fish": "मछली", "banana": "केला", "orange": "संतरा", "apple": "सेब", "guava": "अमरूद", "groundnut oil": "मूंगफली तेल", "ghee": "घी", "olive oil": "जैतून तेल", "milk": "दूध", "curd": "दही", "buttermilk": "छाछ", }, "Telugu": { "rice": "బియ్యం", "wheat roti": "గోధుమ రొట్టి", "millet (jowar)": "జొన్న", "oats": "ఓట్స్", "spinach": "పాలకూర", "carrot": "క్యారెట్", "beetroot": "బీట్‌రూట్", "cabbage": "కాబేజీ", "tomato": "టమోటా", "lentils (dal)": "పప్పు", "chickpeas": "సెనగలు", "paneer": "పనీర్", "eggs": "గుడ్లు", "chicken": "చికెన్", "tofu": "టోఫు", "fish": "చేప", "banana": "అరటి", "orange": "నారింజ", "apple": "ఆపిల్", "guava": "జామ", "groundnut oil": "పల్లీల నూనె", "ghee": "నెయ్యి", "olive oil": "ఆలివ్ ఆయిల్", "milk": "పాలు", "curd": "పెరుగు", "buttermilk": "మజ్జిగ", }, "Spanish": { "rice": "arroz", "wheat roti": "roti de trigo", "millet (jowar)": "mijo", "oats": "avena", "spinach": "espinaca", "carrot": "zanahoria", "beetroot": "remolacha", "cabbage": "col", "tomato": "tomate", "lentils (dal)": "lentejas", "chickpeas": "garbanzos", "paneer": "paneer", "eggs": "huevos", "chicken": "pollo", "tofu": "tofu", "fish": "pescado", "banana": "plátano", "orange": "naranja", "apple": "manzana", "guava": "guayaba", "groundnut oil": "aceite de cacahuete", "ghee": "ghee", "olive oil": "aceite de oliva", "milk": "leche", "curd": "yogur", "buttermilk": "suero de leche", } } def tfood(name: str, lang: str) -> str: return FOOD_NAME_I18N.get(lang, {}).get(name, name) # =================== HEALTH MATH (BMR/TDEE) ======================= def mifflin_st_jeor_bmr(sex: str, weight: float, height: float, age: int) -> float: if sex.lower().startswith("m"): return 10*weight + 6.25*height - 5*age + 5 elif sex.lower().startswith("f"): return 10*weight + 6.25*height - 5*age - 161 else: return 10*weight + 6.25*height - 5*age - 78 def activity_factor(level: str) -> float: return {"Low": 1.2, "Moderate": 1.55, "High": 1.725}.get(level, 1.4) def goal_adjustment(goal: str) -> float: return {"Lose": 0.85, "Maintain": 1.0, "Gain": 1.15}.get(goal, 1.0) def macro_targets(kcal: float, health_notes: str) -> Tuple[float, float, float]: p_pct, c_pct, f_pct = 0.25, 0.50, 0.25 ln = health_notes.lower() if "diabetes" in ln or "sugar" in ln: c_pct = 0.40; f_pct = 0.30; p_pct = 0.30 if "kidney" in ln or "renal" in ln: p_pct = 0.18; c_pct = 0.52; f_pct = 0.30 if "cholesterol" in ln or "lipid" in ln: f_pct = 0.20; c_pct = 0.55; p_pct = 0.25 protein_g = (kcal * p_pct) / 4.0 carbs_g = (kcal * c_pct) / 4.0 fat_g = (kcal * f_pct) / 9.0 return protein_g, carbs_g, fat_g # ===================== ASR HELPERS ================================= def transcribe_audio(path: str, lang_ui: str) -> str: if not USE_ASR or not path or not os.path.exists(path): return "" tmp = f"tmp_{uuid.uuid4().hex}.wav" try: seg = AudioSegment.from_file(path) seg = seg.set_frame_rate(16000).set_channels(1).set_sample_width(2) seg.export(tmp, format="wav") result = _whisper_model.transcribe(tmp, language=lang_map.get(lang_ui, "en"), fp16=False) return result.get("text", "").strip() except Exception as e: return f"(ASR error: {e})" finally: try: if os.path.exists(tmp): os.remove(tmp) except: pass # ===================== PLAN GENERATION ============================= @dataclass class Person: age: int gender: str weight: float height: float occupation: str activity: str goal: str budget: float health: str prefs: str allergies: str lang: str def choose_foods_for_day(veg: bool, allergies: List[str], rng: random.Random): def ok(item): nm = item["name"].lower() return all(a not in nm for a in allergies) grains = [f for f in FOODS["grains"] if ok(f)] proteins = [f for f in FOODS["proteins"] if ok(f) and (veg is False or f.get("veg", False))] vegetables = [f for f in FOODS["vegetables"] if ok(f)] fruits = [f for f in FOODS["fruits"] if ok(f)] dairy = [f for f in FOODS["dairy"] if ok(f)] fats = [f for f in FOODS["fats"] if ok(f)] grain = rng.choice(grains) if grains else FOODS["grains"][0] protein = rng.choice(proteins) if proteins else FOODS["proteins"][0] vegs = rng.sample(vegetables, k=min(2, len(vegetables))) if vegetables else [FOODS["vegetables"][0]] fruit = rng.choice(fruits) if fruits else FOODS["fruits"][0] dairy_pick = rng.choice(dairy) if dairy else FOODS["dairy"][0] fat_pick = rng.choice(fats) if fats else FOODS["fats"][0] return grain, protein, vegs, fruit, dairy_pick, fat_pick def meal_strings(lang: str, grain, protein, vegs, fruit, dairy, fat): b = f"{grain['name']} porridge + {dairy['name']}, {fruit['name']}" l = f"{protein['name']} + {grain['name']} + {vegs[0]['name']}" d = f"{vegs[0]['name']} curry + {vegs[1]['name'] if len(vegs)>1 else fruit['name']} salad + {protein['name']}" b = " + ".join([tfood(part.strip(), lang) for part in b.split("+")]) l = " + ".join([tfood(part.strip(), lang) for part in l.split("+")]) d = " + ".join([tfood(part.strip(), lang) for part in d.split("+")]) return b, l, d def scale_to_targets(meal_df: pd.DataFrame, target_kcal: float, rng: random.Random) -> pd.DataFrame: df = meal_df.copy() df["grams"] = 100.0 current_kcal = df["kcal"].sum() if current_kcal == 0: return df factor = target_kcal / current_kcal factor = max(0.6, min(1.8, factor)) factor *= rng.uniform(0.95, 1.05) df["grams"] *= factor for col in ["kcal", "protein", "carbs", "fat"]: df[col] = df[col] * df["grams"] / 100.0 return df def build_week_plan(p: Person, seed: int = 1337): rng = random.Random(seed) bmr = mifflin_st_jeor_bmr(p.gender, p.weight, p.height, p.age) tdee = bmr * activity_factor(p.activity) target_kcal = tdee * goal_adjustment(p.goal) protein_g, carbs_g, fat_g = macro_targets(target_kcal, p.health or "") kcal_split = np.array([0.30, 0.40, 0.30]) kcal_meals = (kcal_split * target_kcal).tolist() veg = ("veg" in (p.prefs or "").lower()) and ("non-veg" not in (p.prefs or "").lower()) allergies = [a.strip().lower() for a in (p.allergies or "").split(",") if a.strip()] rows = [] readable_summary = [] for day in range(1, 8): grain, protein, vegs, fruit, dairy, fat = choose_foods_for_day(veg, allergies, rng) breakfast_items = [grain, dairy, fruit] lunch_items = [protein, grain, vegs[0]] dinner_items = [vegs[0], vegs[1] if len(vegs) > 1 else fruit, protein] def items_to_df(items): return pd.DataFrame([{k: it.get(k, 0) if k != "name" else it["name"] for k in ["name", "kcal", "protein", "carbs", "fat"]} for it in items]) bdf = scale_to_targets(items_to_df(breakfast_items), kcal_meals[0], rng) ldf = scale_to_targets(items_to_df(lunch_items), kcal_meals[1], rng) ddf = scale_to_targets(items_to_df(dinner_items), kcal_meals[2], rng) def summarize(df): return { "kcal": round(df["kcal"].sum(), 0), "protein": round(df["protein"].sum(), 1), "carbs": round(df["carbs"].sum(), 1), "fat": round(df["fat"].sum(), 1), "desc": ", ".join([tfood(n, p.lang) for n in df["name"].tolist()]) } bsum, lsum, dsum = summarize(bdf), summarize(ldf), summarize(ddf) daily = { "Day": f"Day {day}", "Breakfast": bsum["desc"], "B_kcal": bsum["kcal"], "B_protein": bsum["protein"], "B_carbs": bsum["carbs"], "B_fat": bsum["fat"], "Lunch": lsum["desc"], "L_kcal": lsum["kcal"], "L_protein": lsum["protein"], "L_carbs": lsum["carbs"], "L_fat": lsum["fat"], "Dinner": dsum["desc"], "D_kcal": dsum["kcal"], "D_protein": dsum["protein"], "D_carbs": dsum["carbs"], "D_fat": dsum["fat"], "Total_kcal": round(bsum["kcal"] + lsum["kcal"] + dsum["kcal"], 0), "Total_protein": round(bsum["protein"] + lsum["protein"] + dsum["protein"], 1), "Total_carbs": round(bsum["carbs"] + lsum["carbs"] + dsum["carbs"], 1), "Total_fat": round(bsum["fat"] + lsum["fat"] + dsum["fat"], 1), } rows.append(daily) readable_summary.append(f"\n📅 Day {day} Nutrition Summary") readable_summary.append("-" * 50) readable_summary.append(f"Breakfast: {bsum['desc']}") readable_summary.append(f" - Calories: {bsum['kcal']} kcal | Protein: {bsum['protein']}g | Carbs: {bsum['carbs']}g | Fat: {bsum['fat']}g") readable_summary.append(f"Lunch: {lsum['desc']}") readable_summary.append(f" - Calories: {lsum['kcal']} kcal | Protein: {lsum['protein']}g | Carbs: {lsum['carbs']}g | Fat: {lsum['fat']}g") readable_summary.append(f"Dinner: {dsum['desc']}") readable_summary.append(f" - Calories: {dsum['kcal']} kcal | Protein: {dsum['protein']}g | Carbs: {dsum['carbs']}g | Fat: {dsum['fat']}g") readable_summary.append(f"Total Daily: {daily['Total_kcal']} kcal | Protein: {daily['Total_protein']}g | Carbs: {daily['Total_carbs']}g | Fat: {daily['Total_fat']}g") readable_summary.append("-" * 50) week_df = pd.DataFrame(rows) header = get_output_template(p.lang).format( age=p.age, gender=p.gender, height=p.height, weight=p.weight, occupation=p.occupation, activity_level=p.activity, goal=p.goal, health_conditions=p.health or "None", dietary_preferences=p.prefs or "None", allergies=p.allergies or "None", budget=p.budget, ) targets = ( f"🎯 Targets (approx.): {int(target_kcal)} kcal/day | " f"Protein ~ {int(protein_g)} g | Carbs ~ {int(carbs_g)} g | Fat ~ {int(fat_g)} g" ) readable_summary = "\n".join(readable_summary) return header + "\n" + targets + "\n\n" + readable_summary, week_df # ===================== SALESFORCE INTEGRATION ===================== load_dotenv() SF_USERNAME = os.getenv("SF_USERNAME") SF_PASSWORD = os.getenv("SF_PASSWORD") SF_TOKEN = os.getenv("SF_SECURITY_TOKEN") SF_INSTANCE_URL = os.getenv("SF_INSTANCE_URL", "https://sathkruthatechnologysoluti4-dev-ed.develop.my.salesforce.com") def connect_to_salesforce(max_retries=5): global sf for attempt in range(max_retries): try: if not all([SF_USERNAME, SF_PASSWORD, SF_TOKEN]): return False, "❌ Missing Salesforce credentials. Please set SF_USERNAME, SF_PASSWORD, and SF_TOKEN as HF Secrets." sf = Salesforce( username=SF_USERNAME, password=SF_PASSWORD, security_token=SF_TOKEN, instance_url=SF_INSTANCE_URL ) logger.info("✅ Connected to Salesforce at %s", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) return True, "" except Exception as e: logger.error("❌ Salesforce connection attempt %d failed: %s", attempt + 1, str(e)) if attempt < max_retries - 1: sleep(2 ** attempt) # Exponential backoff else: return False, f"❌ All connection attempts failed: {e}" return False, "" def check_field_accessibility(object_name: str, field_name: str) -> bool: try: describe_result = sf.__getattr__(object_name).describe() field_info = next((f for f in describe_result["fields"] if f["name"] == field_name), None) if field_info: logger.info("Field %s.%s accessibility: read=%s, write=%s", object_name, field_name, field_info.get("readable"), field_info.get("updateable")) return field_info.get("updateable", False) logger.warning("Field %s.%s not found in object description", object_name, field_name) return False except Exception as e: logger.error("❌ Failed to check field accessibility for %s.%s: %s", object_name, field_name, str(e)) return False def prepare_salesforce_data(header: str, week_df: pd.DataFrame) -> Dict: nutritional_data = week_df.to_dict(orient="records") record = { "Name": f"Diet Plan {uuid.uuid4().hex[:6]}", "NutritionalSummary__c": json.dumps(nutritional_data)[:130000], "PlanDate__c": pd.Timestamp.now().strftime('%Y-%m-%d'), "Meals__c": header[:32000], } logger.info("Salesforce fields being sent: %s", record.keys()) # Debug log return record def create_local_food_source_record(diet_plan_id: str, local_foods: Dict) -> str: try: # Sample geolocation (latitude, longitude) location = { "latitude": random.uniform(10.0, 40.0), "longitude": random.uniform(-130.0, -70.0) } # Random US coordinates # Picklist value type_value = random.choice(["Farmers Market", "CSA", "Food Pantry"]) # Check if DietPlan__c and Location__c fields are accessible diet_plan_accessible = check_field_accessibility("LocalFoodSource__c", "DietPlan__c") location_accessible = check_field_accessibility("LocalFoodSource__c", "Location__c") if not diet_plan_accessible: logger.warning("⚠️ Field 'DietPlan__c' not found or not writable on LocalFoodSource__c, skipping lookup.") if not location_accessible: logger.warning("⚠️ Field 'Location__c' not writable on LocalFoodSource__c. Check field-level security.") record = {} if diet_plan_accessible: record["DietPlan__c"] = diet_plan_id # Lookup to DietPlan__c if location_accessible: record["Location__c"] = location # Pass as JSON object record["Type__c"] = type_value record["AvailableFoods__c"] = json.dumps(local_foods)[:131072] # Store FOODS as JSON logger.info("Attempting to create LocalFoodSource with fields: %s", record.keys()) res = sf.LocalFoodSource__c.create(record) if res.get('success'): logger.info("✅ Saved LocalFoodSource record (Id: %s) at %s", res.get('id'), datetime.now().strftime('%Y-%m-%d %H:%M:%S')) return f"✅ Saved LocalFoodSource (Id: {res.get('id')})" else: logger.error("❌ Failed to save LocalFoodSource: %s", res.get('errors')) return f"❌ Failed to save LocalFoodSource: {res.get('errors')}" except Exception as e: logger.error("❌ Failed to save LocalFoodSource: %s", str(e)) return f"❌ Failed to save LocalFoodSource: {str(e)}" def create_feedback_record(diet_plan_id: str, feedback: str) -> str: try: record = { "DietPlan__c": diet_plan_id, "Comments__c": feedback[:32000] if feedback else "No feedback provided", } res = sf.Feedback__c.create(record) if res.get('success'): logger.info("✅ Saved Feedback record (Id: %s) at %s", res.get('id'), datetime.now().strftime('%Y-%m-%d %H:%M:%S')) return f"✅ Saved Feedback (Id: {res.get('id')})" else: logger.error("❌ Failed to save Feedback: %s", res.get('errors')) return f"❌ Failed to save Feedback: {res.get('errors')}" except Exception as e: logger.error("❌ Failed to save Feedback: %s", str(e)) return f"❌ Failed to save Feedback: {str(e)}" def save_plan_to_salesforce(person: Person, header: str, week_df: pd.DataFrame, feedback: str = "") -> str: success, msg = connect_to_salesforce() if not success: return msg try: # Check field accessibility before creating if not check_field_accessibility("DietPlan__c", "Name"): return "❌ Insufficient permissions to update 'Name' field on DietPlan__c. Please check field-level security with your Salesforce admin." # Create DietPlan record diet_plan_record = prepare_salesforce_data(header, week_df) res = sf.DietPlan__c.create(diet_plan_record) if not res.get('success'): logger.error("❌ Salesforce create failed: %s", res.get('errors')) return f"❌ Failed to save in Salesforce: {res.get('errors')}" diet_plan_id = res.get('id') logger.info("✅ Saved DietPlan record (Id: %s) at %s", diet_plan_id, datetime.now().strftime('%Y-%m-%d %H:%M:%S')) # Attempt to create LocalFoodSource record local_food_source_status = create_local_food_source_record(diet_plan_id, FOODS) # Attempt to create Feedback record feedback_status = create_feedback_record(diet_plan_id, feedback) return f"✅ Saved DietPlan (Id: {diet_plan_id})\n{local_food_source_status}\n{feedback_status}" except Exception as e: logger.error("❌ Failed to save in Salesforce: %s", str(e)) return f"❌ Failed to save in Salesforce: {str(e)}" # ===================== PIPELINE (with audio and feedback) ====================== def full_pipeline( health_audio, prefs_audio, allergy_audio, age, gender, weight, height, occupation, activity, goal, budget, lang, health_text_ui, prefs_text_ui, allergy_text_ui, seed, feedback ): logger.info("Pipeline started at %s", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) trans_health = transcribe_audio(health_audio, lang) if health_audio else "" trans_prefs = transcribe_audio(prefs_audio, lang) if prefs_audio else "" trans_allergy = transcribe_audio(allergy_audio, lang) if allergy_audio else "" health = (health_text_ui or "").strip() or trans_health prefs = (prefs_text_ui or "").strip() or trans_prefs allergy = (allergy_text_ui or "").strip() or trans_allergy p = Person( age=int(age), gender=gender, weight=float(weight), height=float(height), occupation=occupation or "-", activity=activity, goal=goal, budget=float(budget), health=health, prefs=prefs, allergies=allergy, lang=lang ) header, week_df = build_week_plan(p, seed=int(seed or 1337)) # Save CSV to a temporary file for Gradio with tempfile.NamedTemporaryFile(delete=False, suffix=".csv") as tmp_file: week_df.to_csv(tmp_file.name, index=False, encoding="utf-8") csv_path = tmp_file.name sf_status = save_plan_to_salesforce(p, header, week_df, feedback) logger.info("Pipeline completed at %s", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) return header + "\n\n" + sf_status, week_df, csv_path, trans_health, trans_prefs, trans_allergy # ============================= UI ================================= with gr.Blocks(theme=gr.themes.Soft(primary_hue="green")) as demo: gr.Markdown("## 🥗 Personalized Diet Plan Generator (Offline, Multilingual)") with gr.Row(): lang_sel = gr.Dropdown(choices=list(lang_map.keys()), value="English", label="Language") with gr.Row(): age = gr.Number(value=30, label="Age") gender = gr.Radio(["Male", "Female", "Other"], value="Male", label="Gender") height = gr.Number(value=170, label="Height (cm)") weight = gr.Number(value=70, label="Weight (kg)") with gr.Row(): occupation = gr.Textbox(label="Occupation", placeholder="e.g., Student / Desk job / Field work") activity = gr.Radio(["Low", "Moderate", "High"], value="Moderate", label="Activity Level") goal = gr.Radio(["Lose", "Maintain", "Gain"], value="Maintain", label="Goal") budget = gr.Number(value=200, label="Daily Budget (₹)") seed = gr.Number(value=1337, label="Random Seed (for reproducible plans)") gr.Markdown("### Optional: Type or Speak your health info, preferences, and allergies") with gr.Row(): health_text = gr.Textbox(label="Health conditions (typed)", placeholder="e.g., Diabetes, high cholesterol") prefs_text = gr.Textbox(label="Dietary preferences (typed)", placeholder="e.g., Veg, Non-veg, High protein") allergy_text = gr.Textbox(label="Allergies (typed, comma-separated)", placeholder="e.g., peanut, milk") with gr.Row(): health_audio = gr.Audio(label="🎤 Speak Health Conditions (optional)", type="filepath") prefs_audio = gr.Audio(label="🎤 Speak Dietary Preferences (optional)", type="filepath") allergy_audio = gr.Audio(label="🎤 Speak Allergies (optional)", type="filepath") btn = gr.Button("🎯 Generate 7-Day Plan with Feedback") header_out = gr.Markdown() week_table = gr.Dataframe(wrap=True, interactive=False, label="Weekly Plan (scroll sideways for macros)") csv_file = gr.File(label="Download CSV") with gr.Accordion("ASR transcription (from voice inputs)", open=False): asr_h = gr.Textbox(label="Health (ASR)") asr_p = gr.Textbox(label="Preferences (ASR)") asr_a = gr.Textbox(label="Allergies (ASR)") with gr.Row(): feedback_input = gr.Textbox(label="Feedback", placeholder="Provide your feedback on the diet plan", lines=3) btn.click( full_pipeline, inputs=[ health_audio, prefs_audio, allergy_audio, age, gender, weight, height, occupation, activity, goal, budget, lang_sel, health_text, prefs_text, allergy_text, seed, feedback_input ], outputs=[header_out, week_table, csv_file, asr_h, asr_p, asr_a] ) if __name__ == "__main__": demo.launch()