| import logging |
|
|
| import gradio as gr |
|
|
| from recipe import RecipeRequest, generate_recipe |
| from shopping_list import ( |
| MAX_BASKET_SIZE, |
| build_shopping_list, |
| format_ingredient_name, |
| get_ingredient_choices, |
| ) |
|
|
| logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s") |
| logger = logging.getLogger(__name__) |
|
|
| logger.info("Loading Epicure Core ingredient list...") |
| INGREDIENT_CHOICES = list(get_ingredient_choices()) |
| logger.info("Loaded %d ingredients", len(INGREDIENT_CHOICES)) |
|
|
|
|
| def _format_steps(steps: list[str]) -> str: |
| if not steps: |
| return "" |
| return "\n".join(f"{index}. {step}" for index, step in enumerate(steps, start=1)) |
|
|
|
|
| def _format_ingredients(names: list[str]) -> str: |
| if not names: |
| return "" |
| return "\n".join(f"- {name}" for name in names) |
|
|
|
|
| def generate(ingredients: list[str]): |
| loading_btn = gr.Button(value="Generating...", interactive=False) |
| ready_btn = gr.Button(value="Generate", interactive=True) |
|
|
| yield loading_btn, "", "", "", "" |
|
|
| if not ingredients: |
| yield ready_btn, "", "", "", "Please select at least one ingredient." |
| return |
|
|
| shopping = build_shopping_list(ingredients) |
| if not shopping.resolved: |
| message = "Could not map any ingredients to the Epicure vocabulary." |
| if shopping.unresolved: |
| message += f" Skipped: {', '.join(shopping.unresolved)}." |
| yield ready_btn, "", "", "", message |
| return |
|
|
| original_display = [format_ingredient_name(key) for key in shopping.resolved] |
| recipe = generate_recipe( |
| RecipeRequest( |
| original_ingredients=original_display, |
| suggested_ingredients=[suggestion.name for suggestion in shopping.suggestions], |
| ) |
| ) |
| if not recipe.validation_ok: |
| yield ( |
| ready_btn, |
| "", |
| "", |
| "", |
| recipe.error_message or "Something went wrong generating the recipe.", |
| ) |
| return |
|
|
| yield ( |
| ready_btn, |
| f"## {recipe.title}", |
| _format_ingredients(recipe.ingredients_used), |
| _format_steps(recipe.steps), |
| "", |
| ) |
|
|
|
|
| EPICURE_CITATION = """ |
| --- |
| **References** |
| |
| ¹ Radzikowski & Chen (2026). *Epicure: Navigating the Emergent Geometry of Food Ingredient Embeddings*. [arXiv:2605.22391](https://arxiv.org/abs/2605.22391). Model: [Kaikaku/epicure-core](https://huggingface.co/Kaikaku/epicure-core). |
| """ |
|
|
| with gr.Blocks(title="Meal Prep") as demo: |
| gr.Markdown("# Meal Prep") |
| gr.Markdown( |
| "Select ingredients from the Epicure Core vocabulary, then click " |
| "Generate to get a recipe. Similar-ingredient suggestions use " |
| "[Epicure Core](https://huggingface.co/Kaikaku/epicure-core)¹." |
| ) |
|
|
| ingredients = gr.Dropdown( |
| choices=INGREDIENT_CHOICES, |
| label="Ingredients", |
| multiselect=True, |
| max_choices=MAX_BASKET_SIZE, |
| filterable=True, |
| ) |
| generate_btn = gr.Button("Generate", variant="primary") |
|
|
| recipe_title = gr.Markdown(label="Recipe") |
| ingredients_used = gr.Markdown(label="Ingredients used") |
| recipe_steps = gr.Markdown(label="Steps") |
| error_message = gr.Markdown() |
|
|
| generate_btn.click( |
| generate, |
| inputs=ingredients, |
| outputs=[generate_btn, recipe_title, ingredients_used, recipe_steps, error_message], |
| show_progress="full", |
| ) |
|
|
| gr.Markdown(EPICURE_CITATION) |
|
|
| demo.launch() |
|
|