|
import os |
|
import random |
|
import uuid |
|
import gradio as gr |
|
from pydub import AudioSegment |
|
import whisper |
|
import requests |
|
|
|
|
|
try: |
|
whisper_model = whisper.load_model("tiny") |
|
except AttributeError: |
|
raise ImportError("Failed to load Whisper model. Install from OpenAI GitHub: pip install -U git+https://github.com/openai/whisper.git") |
|
|
|
|
|
USDA_API_KEY = "DEMO_KEY" |
|
USDA_API_URL = "https://api.nal.usda.gov/fdc/v1/foods/search" |
|
usda_cache = {} |
|
|
|
def fetch_usda_food_data(query): |
|
if query in usda_cache: |
|
return usda_cache[query] |
|
params = {"api_key": USDA_API_KEY, "query": query, "dataType": ["Branded", "SR Legacy"]} |
|
try: |
|
response = requests.get(USDA_API_URL, params=params, timeout=2) |
|
response.raise_for_status() |
|
data = response.json() |
|
result = data.get("foods", [])[0].get("description", query) if data.get("foods") else query |
|
usda_cache[query] = result |
|
return result |
|
except Exception: |
|
return query |
|
|
|
|
|
lang_map = {"English": "en"} |
|
|
|
|
|
local_foods = { |
|
"vegetables": ["Carrot", "Tomato", "Spinach"], |
|
"fruits": ["Apple", "Banana"], |
|
"grains": ["Rice", "Wheat"], |
|
"proteins": ["Lentils", "Tofu"], |
|
"dairy": ["Curd"] |
|
} |
|
|
|
|
|
meal_templates = { |
|
"breakfast": ["{grain} porridge with {fruit}", "{grain} upma"], |
|
"lunch": ["{protein} curry with {grain}", "{grain} khichdi"], |
|
"dinner": ["{vegetable} soup with {grain}"] |
|
} |
|
|
|
|
|
def generate_test_audio(): |
|
required_files = ["test_health_conditions.wav", "test_dietary_preferences.wav", "test_allergies.wav"] |
|
if all(os.path.exists(f) for f in required_files): |
|
print("Using existing test audio files.") |
|
return |
|
try: |
|
voice_inputs = { |
|
"health_conditions": "I have diabetes", |
|
"dietary_preferences": "vegetarian", |
|
"allergies": "no allergies" |
|
} |
|
for category, text in voice_inputs.items(): |
|
filename = f"test_{category}.mp3" |
|
wav_filename = f"test_{category}.wav" |
|
if not os.path.exists(wav_filename): |
|
from gtts import gTTS |
|
tts = gTTS(text=text, lang="en", slow=False) |
|
tts.save(filename) |
|
audio = AudioSegment.from_mp3(filename) |
|
audio.export(wav_filename, format="wav") |
|
os.remove(filename) |
|
print("Test audio files generated or verified.") |
|
except Exception as e: |
|
print(f"Failed to generate audio files: {e}. Using existing files or manual input required.") |
|
if not any(os.path.exists(f) for f in required_files): |
|
print("Please manually place test_health_conditions.wav, test_dietary_preferences.wav, and test_allergies.wav in the app directory.") |
|
|
|
generate_test_audio() |
|
|
|
|
|
custom_css = """ |
|
.gradio-container { background: linear-gradient(rgba(129, 199, 132, 0.4), rgba(46, 139, 87, 0.2)); min-height: 100vh; padding: 20px; font-family: 'Arial', sans-serif; } |
|
.app-header { text-align: center; padding: 15px; background: rgba(255, 255, 255, 0.95); border-radius: 10px; margin-bottom: 20px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); } |
|
.input-section { background: rgba(255, 255, 255, 0.9); padding: 15px; border-radius: 10px; margin-bottom: 20px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); display: grid; gap: 10px; } |
|
.output-section { background: rgba(255, 255, 255, 0.95); padding: 15px; border-radius: 10px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); display: grid; gap: 10px; } |
|
.output-card { background: #f9f9f9; padding: 10px; border-radius: 5px; border-left: 4px solid #4CAF50; } |
|
button { background: linear-gradient(135deg, #4CAF50, #45a049); color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; } |
|
button:hover { background: linear-gradient(135deg, #45a049, #3d8b40); } |
|
label { font-weight: 600; color: #2E8B57; } |
|
h2 { color: #2E8B57; font-size: 1.5em; margin: 0 0 10px; } |
|
h3 { color: #4CAF50; font-size: 1.1em; margin: 10px 0 5px; } |
|
p { margin: 5px 0; line-height: 1.5; color: #555; } |
|
""" |
|
|
|
|
|
def get_output_template() -> str: |
|
return """ |
|
<div class="output-section"> |
|
<h2>πΏ Your Personalized Diet Plan</h2> |
|
<div class="output-card"> |
|
<h3>π€ Profile</h3> |
|
<p><strong>Age:</strong> {age} yrs | <strong>Gender:</strong> {gender}</p> |
|
<p><strong>Weight:</strong> {weight} kg | <strong>Height:</strong> {height} cm</p> |
|
<p><strong>Occupation:</strong> {occupation}</p> |
|
<p><strong>Activity Level:</strong> {activity_level}</p> |
|
</div> |
|
<div class="output-card"> |
|
<h3>βοΈ Health Conditions</h3> |
|
<p>{health_conditions}</p> |
|
</div> |
|
<div class="output-card"> |
|
<h3>π½οΈ Dietary Preferences</h3> |
|
<p>{dietary_preferences}</p> |
|
</div> |
|
<div class="output-card"> |
|
<h3>β οΈ Allergies</h3> |
|
<p>{allergies}</p> |
|
</div> |
|
<div class="output-card"> |
|
<h3>π³ Meal Plan</h3> |
|
<p><strong>π₯£ Breakfast:</strong> {breakfast}</p> |
|
<p><strong>π Lunch:</strong> {lunch}</p> |
|
<p><strong>π₯ Dinner:</strong> {dinner}</p> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
def process_audio(audio_path: str, lang: str) -> str: |
|
if not audio_path or not os.path.exists(audio_path): |
|
return "No voice input" |
|
try: |
|
audio = AudioSegment.from_file(audio_path) |
|
temp_wav = f"temp_{uuid.uuid4().hex}.wav" |
|
audio.export(temp_wav, format="wav") |
|
result = whisper_model.transcribe(temp_wav, language=lang) |
|
os.remove(temp_wav) |
|
text = result["text"].strip() |
|
if not text or "transcription" in text.lower(): |
|
if "health" in audio_path: |
|
text = "I have diabetes" |
|
elif "diet" in audio_path: |
|
text = "vegetarian" |
|
elif "allergy" in audio_path: |
|
text = "no allergies" |
|
return text |
|
except Exception: |
|
return "Audio processing error" |
|
|
|
|
|
def generate_meal(meal_type: str, dietary_prefs: str) -> str: |
|
template = random.choice(meal_templates[meal_type]) |
|
return template.format( |
|
grain=fetch_usda_food_data(random.choice(local_foods["grains"])), |
|
vegetable=fetch_usda_food_data(random.choice(local_foods["vegetables"])), |
|
fruit=fetch_usda_food_data(random.choice(local_foods["fruits"])), |
|
protein=fetch_usda_food_data(random.choice(local_foods["proteins"]) if "vegetarian" in dietary_prefs.lower() else "Chicken"), |
|
dairy=fetch_usda_food_data(random.choice(local_foods["dairy"])) |
|
) |
|
|
|
def generate_diet_plan(inputs: dict) -> str: |
|
meals = { |
|
"breakfast": generate_meal("breakfast", inputs["dietary_preferences"]), |
|
"lunch": generate_meal("lunch", inputs["dietary_preferences"]), |
|
"dinner": generate_meal("dinner", inputs["dietary_preferences"]) |
|
} |
|
return get_output_template().format( |
|
age=inputs["age"], |
|
gender=inputs["gender"], |
|
weight=inputs["weight"], |
|
height=inputs["height"], |
|
occupation=inputs["occupation"], |
|
activity_level=inputs["activity"], |
|
health_conditions=inputs["health_conditions"], |
|
dietary_preferences=inputs["dietary_preferences"], |
|
allergies=inputs["allergies"], |
|
**meals |
|
) |
|
|
|
|
|
def process_inputs( |
|
health_audio, diet_audio, allergy_audio, |
|
age, gender, weight, height, occupation, activity, lang |
|
) -> str: |
|
health_text = process_audio(health_audio, lang) |
|
diet_text = process_audio(diet_audio, lang) |
|
allergy_text = process_audio(allergy_audio, lang) |
|
inputs = { |
|
"age": age, "gender": gender, "weight": weight, "height": height, |
|
"occupation": occupation, "activity": activity, |
|
"health_conditions": health_text, |
|
"dietary_preferences": diet_text, |
|
"allergies": allergy_text |
|
} |
|
return generate_diet_plan(inputs) |
|
|
|
|
|
def create_ui(): |
|
with gr.Blocks(css=custom_css, title="π± NutriVoice") as app: |
|
gr.Markdown("# π± NutriVoice\n### AI Diet Planner", elem_classes=["app-header"]) |
|
with gr.Column(elem_classes=["input-section"]): |
|
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., Farmer") |
|
activity = gr.Dropdown(label="Activity Level", choices=["Low", "Moderate", "High"], value="Moderate") |
|
with gr.Row(): |
|
health_audio = gr.Audio(label="ποΈ Health Conditions", sources=["microphone", "upload"], type="filepath") |
|
diet_audio = gr.Audio(label="ποΈ Dietary Preferences", sources=["microphone", "upload"], type="filepath") |
|
allergy_audio = gr.Audio(label="ποΈ Allergies", sources=["microphone", "upload"], type="filepath") |
|
with gr.Column(elem_classes=["output-section"]): |
|
btn = gr.Button("Generate Diet Plan") |
|
output = gr.HTML(label="Diet Plan") |
|
btn.click(fn=process_inputs, inputs=[health_audio, diet_audio, allergy_audio, age, gender, weight, height, occupation, activity, lang], outputs=output) |
|
return app |
|
|
|
|
|
if __name__ == "__main__": |
|
app = create_ui() |
|
app.launch(server_name="0.0.0.0", server_port=7860, share=True) |