Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from dataclasses import dataclass, field | |
| from typing import Callable, List, Optional | |
| 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 = "" | |
| 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 = " ยท ".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", | |
| ), | |
| } | |