import gradio as gr from dataclasses import dataclass, field from typing import Callable, List, Optional @dataclass class InputSpec: multi_image: bool = False text_label: Optional[str] = None text_placeholder: str = "" dropdown_choices: Optional[List[str]] = None dropdown_label: str = "" dropdown_default: str = "" @dataclass class AppSpec: app_id: str title: str tagline: str # instruction_fn(*extra_inputs) -> str (extra_inputs = text + dropdown, if present) instruction_fn: Callable output_mode: str # "text" | "json" input_spec: InputSpec max_tokens: int # floor 512 (oracle) or 1024 (JSON apps) examples: List[List] # Gradio examples rows; each row matches inputs order # render_fn(result: str|dict|list) -> list[gr.update], one per output_components entry render_fn: Callable output_components: List[str] = field(default_factory=lambda: ["html"]) theme_color: str = "#5c6bc0" do_sample: bool = False temperature: float = 0.7 # ───────────────────────────────────────────────────────────────────────────── # Shared render helpers # ───────────────────────────────────────────────────────────────────────────── def _card(body: str, border_color: str = "#5c6bc0", bg: str = "#fafafa") -> str: return ( f'
' f"{body}
" ) def _animated(html: str) -> str: return f'
{html}
' def _error_html(msg: str) -> str: return ( '
' '⚠️' '
Something went wrong
' f'
{msg}
' '
' ) def _error_updates(n_components: int, msg: str) -> list: """n_components gr.update objects; error goes in slot 0 (always an html component).""" updates = [gr.update(value=_error_html(msg))] updates += [gr.update()] * (n_components - 1) return updates # ───────────────────────────────────────────────────────────────────────────── # 1. allergen_lens # ───────────────────────────────────────────────────────────────────────────── _ALLERGEN_SCHEMA = ( '{"allergens": ["peanuts", "..."], ' '"dietary": {"vegetarian": true, "vegan": false, "gluten_free": false}, ' '"prep_instructions": "...", ' '"verdict": "Safe to eat" | "Contains allergens" | "Check carefully"}' ) def _allergen_instruction(allergies: str = "") -> str: prompt = ( "You are a food-safety assistant. Analyze this food product label image.\n" "Extract ALL allergen information (common allergens: peanuts, tree nuts, milk, eggs, " "wheat/gluten, soy, fish, shellfish, sesame).\n" "Determine vegetarian / vegan / gluten-free status.\n" "Extract preparation instructions if visible.\n" ) if allergies.strip(): prompt += ( f"\nThe user has these dietary restrictions / allergies: {allergies.strip()}\n" "Set 'verdict' to 'Contains allergens' if any of the user's allergens are present, " "'Safe to eat' if none are present, or 'Check carefully' if unsure.\n" ) else: prompt += "\nSet 'verdict' to a general safety summary.\n" prompt += f"\nOutput schema: {_ALLERGEN_SCHEMA}" return prompt def _allergen_render(data) -> list: if isinstance(data, dict) and "error" in data: return [gr.update(value=_error_html(data.get("raw", data["error"])))] verdict = data.get("verdict", "Unknown") allergens = data.get("allergens", []) dietary = data.get("dietary", {}) prep = data.get("prep_instructions", "") vl = verdict.lower() if "safe" in vl and not allergens: verdict_icon, border, verdict_bg, verdict_color = "✅", "#2e7d32", "#e8f5e9", "#2e7d32" elif "check" in vl or "carefully" in vl: verdict_icon, border, verdict_bg, verdict_color = "⚠️", "#f57f17", "#fff8e1", "#e65100" else: verdict_icon, border, verdict_bg, verdict_color = "🚫", "#c62828", "#ffebee", "#c62828" chips = "".join( f'{a}' for a in allergens ) if not chips: chips = 'None detected' dietary_icons = [] if dietary.get("vegetarian"): dietary_icons.append("🥬 Vegetarian") if dietary.get("vegan"): dietary_icons.append("🌱 Vegan") if dietary.get("gluten_free"): dietary_icons.append("✓ Gluten-Free") dietary_text = "  ·  ".join(dietary_icons) if dietary_icons else "No special designations" prep_section = ( f'
' f'Preparation: ' f'{prep}
' if prep else "" ) body = ( f'
' f'{verdict_icon}' f'
' f'
Safety Verdict
' f'
{verdict}
' f'
' f'
' f'
Allergens found:
{chips}
' f'
Dietary: {dietary_text}
' f'{prep_section}' ) return [gr.update(value=_animated(_card(body, border)))] # ───────────────────────────────────────────────────────────────────────────── # 2. fridge_dinner # ───────────────────────────────────────────────────────────────────────────── _FRIDGE_SCHEMA = ( '{"available": ["eggs", "spinach", "cheddar", "..."], ' '"dinners": [' ' {"name": "Spinach Omelette", "uses": ["eggs", "spinach", "cheddar"]}, ' ' {"name": "...", "uses": [...]}, ' ' {"name": "...", "uses": [...]}' '], ' '"use_soon": ["open yogurt", "wilted lettuce", "..."]}' ) _DINNER_PALETTE = [ ("#1565c0", "#e3f2fd", "🍳"), ("#2e7d32", "#e8f5e9", "🥘"), ("#6a1b9a", "#f3e5f5", "🍲"), ] def _fridge_instruction() -> str: return ( "You are a creative home chef. Carefully look at this photo of an open fridge or pantry.\n" "1. List ALL visible ingredients and food items you can identify " "(be specific: 'Greek yogurt' not 'dairy', '2% milk' not 'milk').\n" "2. Suggest EXACTLY THREE dinner ideas using ONLY what is visible — " "no grocery run required. For each dinner, list which visible ingredients it uses.\n" "3. Flag any items that look like they should be used soon: " "opened packages, produce past peak freshness, leftovers, etc.\n" f"Output schema (JSON only): {_FRIDGE_SCHEMA}" ) def _fridge_render(data) -> list: if isinstance(data, dict) and "error" in data: return [gr.update(value=_error_html(data.get("raw", data["error"])))] available = data.get("available", []) dinners = data.get("dinners", []) use_soon = data.get("use_soon", []) # Three dinner cards cards_html = "" for i, dinner in enumerate(dinners[:3]): name = dinner.get("name", f"Dinner {i + 1}") if isinstance(dinner, dict) else str(dinner) uses = dinner.get("uses", []) if isinstance(dinner, dict) else [] color, bg, emoji = _DINNER_PALETTE[i % len(_DINNER_PALETTE)] uses_chips = "".join( f'{ing}' for ing in uses ) _fallback_uses = '' cards_html += ( f'
' f'
{emoji}
' f'
{name}
' f'
Uses
' f'
{uses_chips or _fallback_uses}
' f'
' ) available_text = ", ".join(available) if available else "Nothing detected" use_soon_chips = "".join( f'⚠ {item}' for item in use_soon ) use_soon_section = ( f'
' f'
⏰ Use these soon
' f'
{use_soon_chips}
' f'
' if use_soon else "" ) html = ( f'
' f'
' f'Spotted: {available_text}
' f'
{cards_html}
' f'{use_soon_section}' f'
' ) return [gr.update(value=_animated(html))] # ───────────────────────────────────────────────────────────────────────────── # 3. object_oracle # ───────────────────────────────────────────────────────────────────────────── def _oracle_instruction() -> str: return ( "You are the Object Oracle — a mystical seer who reveals the hidden essence of things.\n" "Look at this image and deliver a tarot-style poetic reading.\n" "Rules:\n" "- Clearly reference what is VISIBLY in the photo (name the object, scene, or subject).\n" "- Speak in vivid, poetic language with a mystical/spiritual tone.\n" "- Give the reading a title (e.g. 'The Wandering Teapot', 'Spirit of the Forgotten Key').\n" "- 3-5 sentences maximum. Lead with the title on its own line.\n" "- End with a one-line fortune or warning (prefix with 'Fortune:').\n" "Do NOT mention that you are an AI or a language model." ) def _oracle_render(text: str) -> list: lines = text.strip().split("\n", 1) title = lines[0].strip().strip("*#_") if lines else "The Oracle Speaks" body = lines[1].strip() if len(lines) > 1 else text.strip() fortune = "" if "Fortune:" in body: parts = body.rsplit("Fortune:", 1) body = parts[0].strip() fortune = parts[1].strip() fortune_html = ( f'
' f'✦ {fortune} ✦
' if fortune else "" ) html = ( '
' f'
✦ The Oracle Speaks ✦
' f'
{title}
' f'
{body}
' f'{fortune_html}' f'
' '
' ) return [gr.update(value=_animated(html))] # ───────────────────────────────────────────────────────────────────────────── # 4. whats_that_error # ───────────────────────────────────────────────────────────────────────────── _ERROR_SCHEMA = ( '{"detected_code": "E01", "likely_cause": "...", ' '"fix_steps": ["Step 1: ...", "Step 2: ..."], "severity": "low|medium|high|critical"}' ) _SEVERITY_STYLE = { "low": ("#2e7d32", "#e8f5e9"), "medium": ("#e65100", "#fff3e0"), "high": ("#c62828", "#ffebee"), "critical": ("#6a1b9a", "#f3e5f5"), } def _error_instruction(appliance: str = "") -> str: device_hint = f" on a '{appliance.strip()}'" if appliance.strip() else "" return ( f"You are an appliance and electronics repair expert.\n" f"Analyze this photo of an error screen, display panel, or error code{device_hint}.\n" f"Identify the error code or fault indicator shown.\n" f"Provide: the exact detected code/indicator, the most likely root cause, " f"clear numbered fix steps a non-expert can follow, and severity level.\n" f"If no code is visible, describe what you see and give general troubleshooting steps.\n" f"Output schema: {_ERROR_SCHEMA}" ) def _error_render(data) -> list: if isinstance(data, dict) and "error" in data: return [gr.update(value=_error_html(data.get("raw", data["error"])))] code = data.get("detected_code", "Unknown") cause = data.get("likely_cause", "") steps = data.get("fix_steps", []) severity = str(data.get("severity", "medium")).lower() color, bg = _SEVERITY_STYLE.get(severity, ("#555", "#f5f5f5")) steps_html = "".join( f'
  • {step}
  • ' for step in (steps if isinstance(steps, list) else [steps]) ) html = ( f'
    ' f'
    ' f'⚠️' f'
    {code}
    ' f'
    ' f'Severity: {severity}
    ' f'
    ' f'
    ' f'
    LIKELY CAUSE
    ' f'

    {cause}

    ' f'
    FIX STEPS
    ' f'
      {steps_html or "
    1. No steps available.
    2. "}
    ' f'
    ' ) return [gr.update(value=_animated(html))] # ───────────────────────────────────────────────────────────────────────────── # APP_REGISTRY # ───────────────────────────────────────────────────────────────────────────── APP_REGISTRY: dict[str, AppSpec] = { "allergen_lens": AppSpec( app_id="allergen_lens", title="🔍 Allergen Lens", tagline="Never guess at a food label again — snap it and know instantly if it's safe for you.", instruction_fn=_allergen_instruction, output_mode="json", input_spec=InputSpec( text_label="My allergies / dietary restrictions (optional)", text_placeholder="e.g. peanuts, dairy, gluten", ), max_tokens=1024, examples=[ ["examples/allergen_label.jpg", "peanuts, dairy"], ["examples/allergen_label2.jpg", ""], ], render_fn=_allergen_render, output_components=["html"], theme_color="#2e7d32", ), "fridge_dinner": AppSpec( app_id="fridge_dinner", title="🍽️ Fridge Dinner", tagline="No idea what to cook? Photo your fridge and get three dinners you can make right now.", instruction_fn=_fridge_instruction, output_mode="json", input_spec=InputSpec(), max_tokens=1024, examples=[ ["examples/fridge_dinner.jpg"], ["examples/fridge_dinner2.jpg"], ], render_fn=_fridge_render, output_components=["html"], theme_color="#2e7d32", ), "object_oracle": AppSpec( app_id="object_oracle", title="🔮 Object Oracle", tagline="Every object has a story. Point your camera and receive its mystical reading.", instruction_fn=_oracle_instruction, output_mode="text", input_spec=InputSpec(), max_tokens=512, examples=[ ["examples/oracle_object.jpg"], ["examples/oracle_object2.jpg"], ], render_fn=_oracle_render, output_components=["html"], theme_color="#4a148c", do_sample=True, temperature=0.85, ), "whats_that_error": AppSpec( app_id="whats_that_error", title="🛠️ What's That Error?", tagline="Error codes explained in plain English — no manual, no guesswork.", instruction_fn=_error_instruction, output_mode="json", input_spec=InputSpec( text_label="Appliance or device name (optional)", text_placeholder="e.g. Samsung washing machine, iPhone, HP printer", ), max_tokens=1024, examples=[ ["examples/error_screen.jpg", ""], ["examples/error_screen2.jpg", "LG dishwasher"], ], render_fn=_error_render, output_components=["html"], theme_color="#c62828", ), }