|
|
|
|
|
|
|
|
import spaces |
|
|
import gradio as gr |
|
|
|
|
|
from transformers import pipeline |
|
|
from diffusers import StableDiffusionPipeline |
|
|
import torch |
|
|
import requests |
|
|
import re |
|
|
from reportlab.lib.pagesizes import letter |
|
|
from reportlab.pdfgen import canvas |
|
|
from tempfile import NamedTemporaryFile |
|
|
from gtts import gTTS |
|
|
|
|
|
|
|
|
USDA_API_KEY = "gcwe1gXmMveg7buqddggl6wAZa7Sd7wrZV87P31z" |
|
|
USDA_SEARCH_URL = "https://api.nal.usda.gov/fdc/v1/foods/search" |
|
|
|
|
|
def get_nutrients(ingredient): |
|
|
params = {"api_key": USDA_API_KEY, "query": ingredient, "pageSize": 1} |
|
|
try: |
|
|
res = requests.get(USDA_SEARCH_URL, params=params) |
|
|
data = res.json() |
|
|
if 'foods' in data and len(data['foods']) > 0: |
|
|
return { |
|
|
n['nutrientName']: n['value'] for n in data['foods'][0].get('foodNutrients', []) |
|
|
if n.get('nutrientName') |
|
|
} |
|
|
return {} |
|
|
except: |
|
|
return {} |
|
|
|
|
|
def get_calories(ingredient): |
|
|
nutrients = get_nutrients(ingredient) |
|
|
return nutrients.get("Energy", 0.0) |
|
|
|
|
|
|
|
|
recipe_model = pipeline("text-generation", model="samdak93/qrit-2") |
|
|
|
|
|
|
|
|
image_model = None |
|
|
recipe_model_gpu = None |
|
|
|
|
|
|
|
|
forbidden = ["pork", "bacon", "ham", "lard", "gelatin", "alcohol", "beer", "wine", "rum", "whiskey", "vodka", "gin"] |
|
|
|
|
|
def parse_ingredients(text): |
|
|
match = re.search(r"(?i)ingredients:(.*?)(\n|directions:|instructions:|$)", text, re.DOTALL) |
|
|
if match: |
|
|
ingredients_block = match.group(1).strip() |
|
|
return [line.strip("- *") for line in ingredients_block.split("\n") if line.strip()] |
|
|
return [] |
|
|
|
|
|
def generate_recipe(key_ingredient): |
|
|
for _ in range(5): |
|
|
prompt = f"Create a halal recipe under 2000 calories that includes {key_ingredient}.\nIngredients:" |
|
|
out = recipe_model(prompt, max_length=300, num_return_sequences=1)[0]['generated_text'] |
|
|
if any(h in out.lower() for h in forbidden): |
|
|
continue |
|
|
|
|
|
ingredients = parse_ingredients(out) |
|
|
total_cal = sum(get_calories(i) for i in ingredients) |
|
|
if total_cal <= 2000 and total_cal > 0: |
|
|
return out, ingredients, total_cal |
|
|
return "No valid recipe.", [], 0 |
|
|
|
|
|
def get_nutrient_breakdown(ingredients): |
|
|
breakdown = {} |
|
|
for ing in ingredients: |
|
|
breakdown[ing] = get_nutrients(ing) |
|
|
return breakdown |
|
|
|
|
|
@spaces.GPU(duration=120) |
|
|
def load_image_model(): |
|
|
global image_model |
|
|
if image_model is None: |
|
|
image_model = StableDiffusionPipeline.from_pretrained( |
|
|
"runwayml/stable-diffusion-v1-5", |
|
|
torch_dtype=torch.float16 |
|
|
).to("cuda") |
|
|
return image_model |
|
|
|
|
|
@spaces.GPU(duration=120) |
|
|
def generate_image(prompt): |
|
|
pipe = load_image_model() |
|
|
with torch.autocast("cuda"): |
|
|
image = pipe(prompt).images[0] |
|
|
return image |
|
|
|
|
|
def export_pdf(recipe_text, nutrients): |
|
|
temp_pdf = NamedTemporaryFile(delete=False, suffix=".pdf") |
|
|
c = canvas.Canvas(temp_pdf.name, pagesize=letter) |
|
|
width, height = letter |
|
|
|
|
|
c.setFont("Helvetica", 12) |
|
|
c.drawString(50, height - 50, "Generated Halal Recipe") |
|
|
|
|
|
text_obj = c.beginText(50, height - 70) |
|
|
for line in recipe_text.split('\n'): |
|
|
text_obj.textLine(line) |
|
|
c.drawText(text_obj) |
|
|
|
|
|
y = text_obj.getY() - 20 |
|
|
c.drawString(50, y, "Nutrient Breakdown:") |
|
|
y -= 20 |
|
|
for ing, data in nutrients.items(): |
|
|
c.drawString(50, y, f"- {ing}") |
|
|
y -= 15 |
|
|
for k, v in data.items(): |
|
|
c.drawString(70, y, f"{k}: {v}") |
|
|
y -= 12 |
|
|
y -= 5 |
|
|
if y < 100: |
|
|
c.showPage() |
|
|
y = height - 50 |
|
|
c.save() |
|
|
return temp_pdf.name |
|
|
|
|
|
def generate_audio(recipe_text): |
|
|
tts = gTTS(text=recipe_text) |
|
|
temp_audio = NamedTemporaryFile(delete=False, suffix=".mp3") |
|
|
tts.save(temp_audio.name) |
|
|
return temp_audio.name |
|
|
|
|
|
def app(key_ingredient): |
|
|
recipe_text, ingredients, calories = generate_recipe(key_ingredient) |
|
|
if not ingredients: |
|
|
return "No valid recipe generated.", None, None, None, None |
|
|
|
|
|
title_match = re.search(r"(?i)^title: (.+)$", recipe_text, re.MULTILINE) |
|
|
title = title_match.group(1).strip() if title_match else f"Dish with {key_ingredient}" |
|
|
|
|
|
image = generate_image(f"A delicious {title}, beautifully plated") |
|
|
nutrients = get_nutrient_breakdown(ingredients) |
|
|
pdf_path = export_pdf(recipe_text, nutrients) |
|
|
audio_path = generate_audio(recipe_text) |
|
|
|
|
|
display_text = f"{recipe_text}\n\nEstimated Calories: {int(calories)} kcal" |
|
|
return display_text, image, nutrients, pdf_path, audio_path |
|
|
|
|
|
|
|
|
with gr.Blocks() as demo: |
|
|
gr.Markdown("## 🕌 Halal Recipe Generator - Advanced Edition") |
|
|
with gr.Row(): |
|
|
key_input = gr.Textbox(label="Key Ingredient") |
|
|
submit_btn = gr.Button("Generate Recipe") |
|
|
|
|
|
output_text = gr.Textbox(label="Recipe Text") |
|
|
output_img = gr.Image(label="Generated Dish") |
|
|
output_table = gr.JSON(label="Nutrient Breakdown") |
|
|
output_pdf = gr.File(label="Download Recipe PDF") |
|
|
output_audio = gr.Audio(label="Recipe Narration", autoplay=False) |
|
|
|
|
|
submit_btn.click(app, inputs=key_input, |
|
|
outputs=[output_text, output_img, output_table, output_pdf, output_audio]) |
|
|
|
|
|
demo.launch(share=True, pwa=True, debug=True) |
|
|
|
|
|
|