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 "- No steps available.
"}
'
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",
),
}