import os
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
import spaces # must come before torch
import html as html_lib
import re
import tempfile
import time
from threading import Thread
import gradio as gr
import torch
from PIL import Image
from transformers import AutoModelForImageTextToText, AutoProcessor, TextIteratorStreamer
MODEL_ID = "microsoft/AesCode-8B"
processor = AutoProcessor.from_pretrained(MODEL_ID)
model = AutoModelForImageTextToText.from_pretrained(
MODEL_ID, dtype=torch.bfloat16, attn_implementation="sdpa"
).to("cuda")
model.eval()
# ---------------------------------------------------------------------------
# Prompting — copied from github.com/microsoft/AesCode inference/prompts.py
# ---------------------------------------------------------------------------
SYSTEM_PROMPT = (
"You create polished standalone HTML/CSS infographics, slides, and editorial layouts. "
"You receive a generation prompt and a reference image. "
"The generation prompt is the primary contract: follow it exactly wherever it specifies required text, charts, tables, or data. "
"The reference image is visual guidance only: reuse strong ideas in hierarchy, palette, density, composition, and typography, but do not treat it as a screenshot to copy. "
"Its visible text, numbers, and labels may be incomplete, inaccurate, or stylistically decorative, so do not trust them over the prompt or inline data contracts. "
"HTML output constraints: "
"(1) Produce exactly one complete document with all CSS in a single inline "
""
)
if re.search(r"
]*>", doc, flags=re.I):
doc = re.sub(r"(]*>)", lambda m: m.group(1) + fit, doc, count=1, flags=re.I)
else:
doc = fit + doc
srcdoc = html_lib.escape(doc, quote=True)
return (
f''
f'
'
)
PLACEHOLDER = (
'{}
'
)
def _duration(prompt, reference_image=None, canvas=CANVAS_CHOICES[0], max_new_tokens=6144, *args, **kwargs):
# measured ~30 tok/s on ZeroGPU (5.3k tokens in 181s incl. warm-up) + ~15% margin
return min(300, int((10 + int(max_new_tokens) / 32) * 1.15))
@spaces.GPU(duration=_duration)
def generate(
prompt: str,
reference_image: Image.Image | None = None,
canvas: str = CANVAS_CHOICES[0],
max_new_tokens: int = 6144,
temperature: float = 0.8,
top_p: float = 0.95,
seed: int = 0,
):
"""Generate an aesthetic HTML/CSS infographic (slide, poster, dashboard) with AesCode-8B.
Args:
prompt: Detailed content prompt: title, sections, exact text, chart/table data.
reference_image: Optional reference image used as layout/style guidance.
canvas: Canvas size of the root artboard; "Auto" uses the reference image size (or 1280×720).
max_new_tokens: Maximum number of tokens to generate.
temperature: Sampling temperature.
top_p: Nucleus sampling top-p.
seed: Random seed.
Returns:
Rendered preview (HTML iframe), the generated HTML source, a downloadable .html file and the model's design note.
"""
if not prompt or not prompt.strip():
raise gr.Error("Please enter a content prompt.")
if reference_image is not None and not isinstance(reference_image, Image.Image):
reference_image = Image.open(reference_image)
if reference_image is not None:
reference_image = reference_image.convert("RGB")
w, h = resolve_canvas(canvas, reference_image)
user_text = build_user_text(prompt, w, h, reference_image is not None)
content = []
if reference_image is not None:
content.append({"type": "image", "image": reference_image})
content.append({"type": "text", "text": user_text})
messages = [
{"role": "system", "content": [{"type": "text", "text": SYSTEM_PROMPT}]},
{"role": "user", "content": content},
]
inputs = processor.apply_chat_template(
messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt"
).to(model.device)
torch.manual_seed(int(seed))
streamer = TextIteratorStreamer(processor.tokenizer, skip_prompt=True, skip_special_tokens=True)
gen_kwargs = dict(
**inputs,
streamer=streamer,
max_new_tokens=int(max_new_tokens),
do_sample=True,
temperature=float(temperature),
top_p=float(top_p),
top_k=0,
)
thread = Thread(target=model.generate, kwargs=gen_kwargs)
t0 = time.perf_counter()
thread.start()
text = ""
n = 0
for chunk in streamer:
text += chunk
n += 1
if n % 40 == 0:
yield (
gr.update(value=PLACEHOLDER.format(f"Writing HTML… {time.perf_counter() - t0:.0f}s")),
gr.update(value=text),
gr.update(),
gr.update(),
)
thread.join()
elapsed = time.perf_counter() - t0
print(f"[aescode] generated {len(text)} chars in {elapsed:.1f}s")
doc = extract_html(text)
m = re.search(r"```html", text, flags=re.I)
note = text[: m.start()].strip() if m else ""
note = (note + f"\n\n*Generated in {elapsed:.0f}s.*").strip()
if doc is None:
yield (
PLACEHOLDER.format("The model did not return an HTML document — see the raw output."),
text,
None,
note,
)
return
path = tempfile.NamedTemporaryFile(suffix=".html", delete=False, prefix="aescode_").name
with open(path, "w", encoding="utf-8") as f:
f.write(doc)
yield render_iframe(doc, w, h), doc, path, note
HOSPITAL_PROMPT = open("examples/hospital_dashboard_prompt.txt", encoding="utf-8").read()
EV_IDEA = (
"A quarterly sales dashboard for a European EV brand. Show revenue and units delivered for the "
"last four quarters, a regional breakdown across Germany, France, Norway and the Netherlands, and a "
"table of the top five models. Keep it clean and corporate, dark text on a light background."
)
CSS = """
#col-container { max-width: 1280px; margin: 0 auto; }
.dark .gradio-container { color: var(--body-text-color); }
"""
with gr.Blocks(title="AesCode-8B") as demo:
with gr.Column(elem_id="col-container"):
gr.Markdown(
"# AesCode-8B · Aesthetic HTML/CSS infographics\n"
"Describe a slide, poster or dashboard (text, sections, chart & table data) and optionally add a "
"reference image for layout and style. [AesCode-8B](https://huggingface.co/microsoft/AesCode-8B) "
"writes a complete, editable HTML/CSS page (charts via ECharts), rendered below in a sandboxed frame. "
"[Code](https://github.com/microsoft/AesCode)"
)
with gr.Row():
with gr.Column(scale=3):
prompt = gr.Textbox(
label="Content prompt",
lines=8,
placeholder="Create a landscape dashboard titled \"…\". Section 1: … Render this as an ECharts bar chart with values [ … ].",
)
with gr.Column(scale=2):
reference_image = gr.Image(label="Reference image (optional)", type="pil", height=230)
with gr.Row():
canvas = gr.Dropdown(CANVAS_CHOICES, value=CANVAS_CHOICES[0], label="Canvas size", scale=3)
run = gr.Button("Generate", variant="primary", scale=1)
with gr.Accordion("Advanced settings", open=False):
with gr.Row():
max_new_tokens = gr.Slider(1024, 8192, value=6144, step=256, label="Max new tokens")
temperature = gr.Slider(0.1, 1.2, value=0.8, step=0.05, label="Temperature")
top_p = gr.Slider(0.5, 1.0, value=0.95, step=0.01, label="Top-p")
seed = gr.Number(value=0, precision=0, label="Seed")
preview = gr.HTML(PLACEHOLDER.format("The rendered page will appear here."), label="Rendered page")
note = gr.Markdown()
with gr.Accordion("HTML source", open=False):
code = gr.Code(language="html", label="Generated HTML", max_lines=40)
html_file = gr.File(label="Download .html")
outputs = [preview, code, html_file, note]
run.click(
generate,
inputs=[prompt, reference_image, canvas, max_new_tokens, temperature, top_p, seed],
outputs=outputs,
api_name="generate",
)
gr.Examples(
label="With reference image (from the AesCode repo)",
examples=[[HOSPITAL_PROMPT, "examples/hospital_dashboard_reference.png"]],
inputs=[prompt, reference_image],
outputs=outputs,
fn=generate,
cache_examples=True,
cache_mode="lazy",
)
gr.Examples(
label="Prompt only",
examples=[[EV_IDEA], [HOSPITAL_PROMPT]],
inputs=[prompt],
outputs=outputs,
fn=generate,
cache_examples=True,
cache_mode="lazy",
)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)