| import logging |
| log = logging.getLogger(__name__) |
|
|
| from typing import Any |
|
|
| import gradio as gr |
| from PIL import Image |
|
|
| |
| from src.agents.mise_en_place import identify_ingredients |
| |
| |
| |
| |
| from src.ui.components import ( |
| DishOptions, |
| IngredientChips, |
| NutritionGrid, |
| RecipeHero, |
| StepCard, |
| VerdictBadge, |
| recipe_to_state, |
| ) |
| from src.ui.theme import CSS, theme |
|
|
| def on_propose(fridge_image: Image.Image | None, state: dict | None) -> tuple[str, str, list[str], dict]: |
| """Photo β ingredients β 3 dish options.""" |
| state = state or {} |
| ingredients = identify_ingredients(fridge_image) |
| |
|
|
| |
| |
| |
| |
| |
| chips_html = IngredientChips.render({"have": ingredients, "missing": []}) |
| log.info(ingredients) |
| |
| |
| |
| return chips_html |
|
|
|
|
|
|
| |
| |
| |
| def build_ui() -> gr.Blocks: |
| initial_state: dict[str, Any] = {} |
|
|
| with gr.Blocks(title="Cook With Me") as demo: |
| gr.Markdown( |
| "# π² Cook With Me\n" |
| "_A multimodal sous-chef. See it. Plan it. Show it. Cook it._" |
| ) |
|
|
| state = gr.State(initial_state) |
|
|
| with gr.Tabs(): |
| |
| with gr.Tab("Cook"): |
| with gr.Row(): |
| with gr.Column(scale=1): |
| fridge_input = gr.Image( |
| label="πΈ Photo of your fridge or pantry", |
| type="pil", |
| height=320, |
| ) |
| propose_btn = gr.Button("What can I cook?", variant="primary") |
|
|
| gr.Markdown("### Ingredients I see") |
| chips = gr.HTML(IngredientChips.render({})) |
|
|
| gr.Markdown("### Pick a dish") |
| options = gr.HTML(DishOptions.render({})) |
| dish_radio = gr.Radio(choices=[], label="Choose one", interactive=True) |
|
|
| with gr.Accordion("Generation options", open=False): |
| illustrate_chk = gr.Checkbox(value=False, label="Render step images (FLUX, slow on CPU)") |
| narrate_chk = gr.Checkbox(value=False, label="Generate voice narration (VoxCPM2)") |
|
|
| cook_btn = gr.Button("Build recipe", variant="primary") |
|
|
| with gr.Column(scale=2): |
| hero = gr.HTML(RecipeHero.render({})) |
| steps_panel = gr.HTML(StepCard.render({})) |
| nutrition_panel = gr.HTML(NutritionGrid.render({"nutrition": {}})) |
|
|
| |
| with gr.Tab("Check Progress"): |
| gr.Markdown("Upload a photo of your pan or plate; the same vision model that planned your recipe will compare it against the target step.") |
| with gr.Row(): |
| with gr.Column(): |
| step_idx = gr.Number(value=1, precision=0, label="Active step #") |
| progress_input = gr.Image(label="πΈ Your pan / plate", type="pil", height=320) |
| validate_btn = gr.Button("How am I doing?", variant="primary") |
| with gr.Column(): |
| verdict_panel = gr.HTML(VerdictBadge.render({})) |
| verdict_audio = gr.Audio(label="Tip (voice)", autoplay=False) |
|
|
| |
| with gr.Tab("About"): |
| gr.Markdown( |
| """ |
| ### Models |
| - **Vision** β `openbmb/MiniCPM-V-4_6-gguf` via `llama-cpp-python` (~4.6B) |
| - **Planner** β `openbmb/MiniCPM-V-4-gguf` via `llama-cpp-python` (~4B) |
| - **Illustrator** β `black-forest-labs/FLUX.2-klein-9B` via `diffusers` (9B) |
| - **Narrator** β `openbmb/VoxCPM2` via `transformers` (~1B) |
| - **Retrieval** β `sentence-transformers/all-MiniLM-L6-v2` (22M) |
| **Total β 18.6B params** (β€ 32B requirement β). |
| ### Pipeline |
| ``` |
| Fridge photo β Vision β ingredients |
| β |
| βΌ |
| Planner (+ Kaggle retrieval) β Recipe JSON |
| β |
| βΌ |
| Illustrator (FLUX) β hero + per-step images |
| β |
| βΌ |
| Narrator (VoxCPM2) β MP3 per step |
| β |
| βΌ |
| Progress photo β Validator (same vision model) β go|wait|fix |
| ``` |
| ### Badges targeted |
| β Llama Champion Β· β Well-Tuned Β· β Off-Brand Β· β Sharing is Caring Β· β Field Notes |
| """ |
| ) |
|
|
| |
| propose_btn.click( |
| fn=on_propose, |
| inputs=[fridge_input, state], |
| |
| outputs=[chips], |
| ) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| return demo |
|
|
|
|
| if __name__ == "__main__": |
| demo = build_ui() |
| demo.launch( |
| server_name="0.0.0.0", |
| server_port=int(__import__("os").environ.get("PORT", 7860)), |
| show_error=True, |
| inbrowser=True, |
| theme=theme, |
| css=CSS |
| ) |