Cook_with_a_LLM / app.py
FredinVΓ‘zquez
adding dynamic image
ba424d1
Raw
History Blame
6.86 kB
import logging
log = logging.getLogger(__name__)
from typing import Any
import gradio as gr
from PIL import Image
# from src import config
from src.agents.mise_en_place import identify_ingredients
# from src.agents.progress_validator import validate
# from src.agents.recipe_planner import plan_recipe, propose_dishes
# from src.data.nutrition import compute_nutrition
# from src.pipeline import Recipe
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)
# options = propose_dishes(ingredients)
# state.update({
# "ingredients_have": ingredients,
# "ingredients_missing": [],
# "options": [o.model_dump() for o in options],
# })
chips_html = IngredientChips.render({"have": ingredients, "missing": []})
log.info(ingredients)
# options_html = DishOptions.render({"options": state["options"]})
# radio_choices = [o.name for o in options]
# return chips_html, options_html, gr.update(choices=radio_choices, value=radio_choices[0] if radio_choices else None), state
return chips_html
# ----------------
# UI definition
# ----------------
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():
# --- Tab 1: Cook ------------------------------------------------
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": {}}))
# --- Tab 2: Check Progress -------------------------------------
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)
# --- Tab 3: About ----------------------------------------------
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
"""
)
# Wire callbacks ----------------------------------------------------
propose_btn.click(
fn=on_propose,
inputs=[fridge_input, state],
# outputs=[chips, options, dish_radio, state],
outputs=[chips],
)
# cook_btn.click(
# fn=on_pick_dish,
# inputs=[state, dish_radio, illustrate_chk, narrate_chk],
# outputs=[hero, steps_panel, nutrition_panel, chips, state],
# )
# validate_btn.click(
# fn=on_validate,
# inputs=[state, step_idx, progress_input],
# outputs=[verdict_panel, verdict_audio],
# )
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
)