Sumanth0301's picture
Update app.py
51726ea verified
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()