from __future__ import annotations
import html
import os
import re
# Disable Gradio's Node SSR sidecar BEFORE importing gradio. SSR (a separate Node proxy that
# server-side-renders the page) was interfering with the dynamically-injected iframe preview;
# pure client-side rendering is reliable for live HTML. On the HF Space THIS env var is what
# counts โ the SDK launches the `app` object itself, so the launch(ssr_mode=...) at the bottom
# only affects local runs.
os.environ.setdefault("GRADIO_SSR_MODE", "false")
import gradio as gr
from instructions.toy_maker import toy_maker
# Import the model layer EAGERLY at startup. HF ZeroGPU only detects @spaces.GPU functions
# that are registered while the app module is importing โ a lazy/in-function import means the
# decorated `generate` is never seen at startup ("No @spaces.GPU function detected"). The
# try/except keeps app.py importable locally (no torch/llama-cpp/spaces installed); on the
# Space the deps exist, the import succeeds, and ZeroGPU registers the GPU function.
try:
from model import generate as model_generate
except Exception: # surfaced in logs below
import sys
import traceback
print("[app] model layer not available โ using fallback replies. Reason:", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
model_generate = None
APP_TITLE = "Amazing Digital Pet Dentures โ HTML Toy Maker"
APP_CSS = ""
# How many recent messages to keep (and send to the model). Bounds both the context window
# and the ~5 MB localStorage cap (each assistant turn carries a full HTML doc).
MAX_MESSAGES = 8
WELCOME_MESSAGE = [
{
"role": "assistant",
"content": "Hi! I'm the dentures ๐ฆท โ describe anything (a game, a widget, a "
"visualizer, a clockโฆ) and I'll build it as a live HTML toy. "
"Hit ๐งน New session to start over.",
}
]
# ---- HTML extraction -------------------------------------------------------------------
_THINK_RE = re.compile(r".*?", re.IGNORECASE | re.DOTALL)
def _strip_fences(text: str) -> str:
"""Remove ``` code fences but keep their contents."""
text = re.sub(r"```[a-zA-Z0-9]*\n?", "", text or "")
return text.replace("```", "")
def best_html(text: str | None) -> str | None:
"""Slice out the real HTML document: from the LAST (or ) to the
LAST . The real doc is generated AFTER any reasoning, so taking the last opener
avoids reasoning that merely *mentions* tags (which produced broken fragments before).
"""
if not text:
return None
low = text.lower()
start = low.rfind("")
if end == -1 or end <= start:
return None
doc = text[start:end + len("