vision-base / core /apps.py
SPP
fix: real example photos, drop language dropdown, 2 examples per app
789c5b7
Raw
History Blame Contribute Delete
20.3 kB
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'<div style="border-left:5px solid {border_color};padding:20px 24px;'
f'background:{bg};border-radius:8px;font-family:system-ui,sans-serif">'
f"{body}</div>"
)
def _animated(html: str) -> str:
return f'<div class="result-reveal">{html}</div>'
def _error_html(msg: str) -> str:
return (
'<div style="border-left:4px solid #ef5350;padding:16px 20px;background:#fff8f8;'
'border-radius:8px;font-family:system-ui,sans-serif;display:flex;gap:12px;align-items:flex-start">'
'<span style="font-size:24px;line-height:1.2">โš ๏ธ</span>'
'<div><div style="font-weight:700;color:#c62828;margin-bottom:4px">Something went wrong</div>'
f'<div style="color:#555;font-size:14px">{msg}</div></div>'
'</div>'
)
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'<span style="display:inline-block;background:#ef5350;color:white;'
f'padding:3px 10px;border-radius:12px;margin:3px 3px 3px 0;font-size:13px">{a}</span>'
for a in allergens
)
if not chips:
chips = '<span style="color:#757575;font-style:italic">None detected</span>'
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 = " &nbsp;ยท&nbsp; ".join(dietary_icons) if dietary_icons else "No special designations"
prep_section = (
f'<div style="margin-top:14px">'
f'<b style="color:#555">Preparation:</b> '
f'<span style="color:#333">{prep}</span></div>'
if prep else ""
)
body = (
f'<div style="background:{verdict_bg};border-radius:10px;padding:16px 20px;margin-bottom:16px;'
f'display:flex;align-items:center;gap:16px">'
f'<span style="font-size:52px;line-height:1">{verdict_icon}</span>'
f'<div>'
f'<div style="font-size:11px;font-weight:700;color:{verdict_color};opacity:.75;'
f'text-transform:uppercase;letter-spacing:1.5px;margin-bottom:2px">Safety Verdict</div>'
f'<div style="font-size:22px;font-weight:800;color:{verdict_color}">{verdict}</div>'
f'</div>'
f'</div>'
f'<div style="margin-bottom:12px"><b style="color:#555">Allergens found:</b><br/>{chips}</div>'
f'<div><b style="color:#555">Dietary:</b> {dietary_text}</div>'
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'<span style="background:{color}18;color:{color};border:1px solid {color}44;'
f'padding:2px 8px;border-radius:10px;font-size:12px;'
f'margin:2px 2px 2px 0;display:inline-block">{ing}</span>'
for ing in uses
)
_fallback_uses = '<span style="color:#aaa;font-size:12px">โ€”</span>'
cards_html += (
f'<div style="flex:1;min-width:160px;background:{bg};'
f'border:2px solid {color}44;border-radius:10px;padding:16px">'
f'<div style="font-size:24px;margin-bottom:6px">{emoji}</div>'
f'<div style="font-weight:700;font-size:15px;color:{color};margin-bottom:10px">{name}</div>'
f'<div style="font-size:11px;color:#757575;margin-bottom:5px;text-transform:uppercase;'
f'letter-spacing:.5px">Uses</div>'
f'<div>{uses_chips or _fallback_uses}</div>'
f'</div>'
)
available_text = ", ".join(available) if available else "Nothing detected"
use_soon_chips = "".join(
f'<span style="background:#fff3e0;color:#e65100;border:1px solid #ff980044;'
f'padding:3px 10px;border-radius:10px;font-size:13px;'
f'margin:3px 3px 3px 0;display:inline-block">โš  {item}</span>'
for item in use_soon
)
use_soon_section = (
f'<div style="margin-top:20px;padding:14px 16px;background:#fff8e1;'
f'border-radius:8px;border-left:4px solid #ffa000">'
f'<div style="font-weight:600;color:#e65100;margin-bottom:8px">โฐ Use these soon</div>'
f'<div>{use_soon_chips}</div>'
f'</div>'
if use_soon else ""
)
html = (
f'<div style="font-family:system-ui,sans-serif">'
f'<div style="font-size:12px;color:#757575;margin-bottom:14px">'
f'Spotted: {available_text}</div>'
f'<div style="display:flex;gap:12px;flex-wrap:wrap">{cards_html}</div>'
f'{use_soon_section}'
f'</div>'
)
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'<div style="margin-top:20px;padding:12px 16px;background:rgba(255,255,255,0.1);'
f'border-radius:6px;font-style:italic;font-size:14px;color:#f0d080">'
f'โœฆ {fortune} โœฆ</div>'
if fortune else ""
)
html = (
'<div style="background:linear-gradient(135deg,#1a1a2e 0%,#16213e 50%,#0f3460 100%);'
'color:#e8d5a3;border-radius:14px;padding:32px 36px;font-family:Georgia,serif;'
'border:1px solid #e0c97f44;min-height:180px">'
f'<div style="text-align:center;font-size:13px;letter-spacing:3px;color:#c9a84c;'
f'margin-bottom:16px;text-transform:uppercase">โœฆ The Oracle Speaks โœฆ</div>'
f'<div style="font-size:20px;font-weight:bold;color:#f0d080;'
f'text-align:center;margin-bottom:16px">{title}</div>'
f'<div style="font-size:16px;line-height:1.9;color:#e0cfa0;white-space:pre-line">{body}</div>'
f'{fortune_html}'
f'<div style="text-align:center;margin-top:20px;font-size:22px;color:#c9a84c">โŸก</div>'
'</div>'
)
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'<li style="margin:8px 0;color:#333;font-size:14px">{step}</li>'
for step in (steps if isinstance(steps, list) else [steps])
)
html = (
f'<div style="font-family:system-ui,sans-serif;max-width:620px">'
f'<div style="background:{color};color:white;padding:14px 20px;'
f'border-radius:8px 8px 0 0;display:flex;align-items:center;gap:12px">'
f'<span style="font-size:22px">โš ๏ธ</span>'
f'<div><div style="font-size:18px;font-weight:700">{code}</div>'
f'<div style="font-size:12px;opacity:.85;text-transform:uppercase;letter-spacing:1px">'
f'Severity: {severity}</div></div>'
f'</div>'
f'<div style="border:2px solid {color};border-top:none;border-radius:0 0 8px 8px;'
f'padding:18px 20px;background:{bg}">'
f'<div style="font-size:13px;font-weight:600;color:#555;margin-bottom:4px">LIKELY CAUSE</div>'
f'<p style="margin:0 0 16px;font-size:15px;color:#222">{cause}</p>'
f'<div style="font-size:13px;font-weight:600;color:#555;margin-bottom:6px">FIX STEPS</div>'
f'<ol style="margin:0;padding-left:22px">{steps_html or "<li>No steps available.</li>"}</ol>'
f'</div></div>'
)
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",
),
}