import random
import gradio as gr
import spaces
import torch
from inference import generate as nf2_generate
from inference import load
from prompt_check import check_prompt
from prompt_edit import edit_prompt
MAX_SEED = 2**31 - 1
pipe = load()
pipe.to("cuda")
RESOLUTIONS = {
"Square · 1:1": (1024, 1024),
"Portrait · 2:3": (832, 1216),
"Landscape · 3:2": (1216, 832),
"Cinematic · 16:9": (1360, 768),
"Square · 2K": (2048, 2048),
"Portrait · 2K": (1344, 2048),
"Landscape · 2K": (2048, 1344),
"Cinematic · 2K": (2048, 1152),
}
EXAMPLE_PROMPTS = [
[
"Subject:\n"
"A hyper-detailed close-up portrait of a young red-haired woman with fair freckled skin, long loose curls.\n"
"Scene:\n"
"Her face half-veiled by overlapping cherry blossom branches; petals brushing her temple and casting delicate, "
"fluttering shadows over her eyes. Blossoms framing the face. Shallow DOF.\n"
"Lighting:\n"
"Ethereal backlighting with soft god rays filtering through pink blooms; subtle fill to keep facial detail and "
"gentle rim to separate hair from background.\n"
"Style:\n"
"Natural rendering\u2014fine eyelash strands, faint capillaries on eyelids, satin lip balm highlight, micro skin "
"texture, petals with fine veins and a soft velvety sheen, pastel bokeh background with filmic grain."
],
[
"Candid snapshot of an ordinary middle-aged French man with a long face, three-day stubble, messy "
"light-brown hair and a slightly crooked nose, sitting alone at a small wooden table in a dimly lit Paris "
"bistro, glass of red wine in front of him, shot with a compact point-and-shoot film camera, gentle flash "
"falloff, documentary realism, visible grain."
],
["immense rocket launch exhaust as seen from extremely close up"],
[
"Subject:\n"
"A close-up of a German Shepherd's head, black-and-tan coat, upright ears, dark almond-shaped eyes and a moist "
"nose; only its head and shoulders are in frame. Its head is turned fully to the right of the frame, gaze fixed "
"on a distant point far to the right, not looking at the camera, with a focused expression.\n"
"Action:\n"
"It sits upright among the wheat ears, ears pointed toward where it is looking, gazing motionless to the right.\n"
"Scene:\n"
"An endless golden wheat field with great bright cumulus clouds on the horizon; extremely shallow depth of "
"field, the surroundings present only as blurred patches of colour and light.\n"
"Color:\n"
"Golden yellow and bright cloud white.\n"
"Composition:\n"
"Vertical close-up, the head filling two-thirds of the frame height, subject on the left third line, generous "
"empty space on the right where it is looking; background fully out of focus.\n"
"Medium:\n"
"Cinematic film still.\n"
"Medium details:\n"
"Mamiya 7 II, 80mm f/4, Kodak Portra 160; open bright daylight breaking through the clouds; clear, luminous, "
"airy image; soft highlights that never clip, light and airy shadows; shallow depth of field with focus on the "
"eyes; fine film grain; nothing but the golden wheat field in frame, no buildings, vehicles or man-made objects."
],
[
"3D rendered matte black designer toy figure, stylized round anthropomorphic shape, "
"backward black baseball cap, oversized gold-rimmed aviator sunglasses, white traditional "
"line-art tattoos of tiger and bird on torso, black studded belt with gold buckle, smooth "
"vinyl texture, studio lighting, solid vibrant blue background, high contrast minimal composition"
],
[
"A minimalist flat-color illustration of a person wading through expansive shallow ocean waves "
"beneath a pale peach sky. The dark-skinned figure, wearing an orange swim cap, light blue top, and "
"bright green shorts, steps carefully through knee-deep water. The ocean is rendered in muted mint "
"green with delicate, thin black linework detailing the continuous ripples and gentle whitecaps. "
"Soft pinkish-peach reflections echo the sky on the water's surface. The high-angle wide perspective "
"emphasizes the vast negative space of the water, utilizing a clean ligne claire drawing aesthetic "
"with a subtle paper texture."
],
[
"high-fashion editorial portrait of a young East Asian woman, short choppy platinum blonde bob "
"with heavy bangs, looking over her bare shoulder to the right, lips playfully pursed, wearing a "
"structured black top with an architectural protruding bust detail and thin straps, delicate gold "
"hoop earrings, arm bent with hand resting on hip, warm skin tones, solid striking crimson red "
"background, soft directional studio lighting, cinematic color palette, medium close-up shot"
],
[
"A side view of a heavily modified, vintage American muscle car performing a burnout. The car is a 1968-1970 "
"Dodge Charger, but in a state of disrepair - showing significant rust, faded paint (a mix of teal/blue and "
"white on the roof), and missing trim. The hood is open, revealing a large, powerful engine with multiple "
"carburetors. Thick white tire smoke is billowing from the rear tires, obscuring the lower portion of the car."
],
[
"A surreal retro-futuristic space scene features liquid chrome forming an abstract face merging "
"with a glowing planetary horizon. The foreground is dominated by swirling, highly reflective "
"metallic fluid that distorts into a stylized, melting facial profile with deep shadows and bright "
"silver highlights. This undulating chrome form rests against the curved, atmospheric edge of a "
"massive planet bathed in a soft electric blue and purple glow. Set against a deep black starfield, "
"the artwork employs a vintage 1980s airbrush aesthetic with smooth gradients, ethereal lighting, "
"and high-contrast metallic rendering."
],
["film still, astronaut in a dense tropical jungle"],
[
"A tiny, vibrant blue cardinal perches delicately on a slender diagonal branch amid lush green lobed "
"leaves and small round buds. The bird has soft, finely textured sapphire-blue plumage, a distinctive pointed "
"crest, deep indigo wings, glossy black eyes, a small coral-pink conical beak, and delicate dark feet firmly "
"gripping the wood. Its head is slightly tilted, giving it a curious and gentle expression. In this macro "
"wildlife photograph, an extremely shallow depth of field sharply focuses on the bird's face, capturing the "
"intricate details of its delicate feathers. The deep emerald-green background dissolves into a smooth, creamy "
"bokeh, illuminated by soft, diffused natural lighting that enhances the rich blue tones of the plumage and the "
"vibrant surrounding foliage."
],
[
"Subject:\n"
"A birch wood; in the foreground two birch trees lean diagonally into the frame from the left, their bark "
"peeling in papery black-and-white layers; beneath the trees, moss-covered boulders and a pool of still water.\n"
"Morning mist hangs between the trees, the distant trunks fading one by one into the fog.\n"
"A late-winter dawn, no wind, the water as still as a mirror, reflecting the trunks.\n"
"Color:\n"
"The yellow-green of the moss and the cold grey-white of the mist.\n"
"Composition:\n"
"The near birch trunks crossing the picture, the still water in the lower right.\n"
"Medium:\n"
"Landscape photograph.\n"
"Soft diffused light; every layer of birch bark and every fleck of lichen sharp, the fuzz of the moss visible; "
"the mist lowers the contrast layer by layer into the distance."
],
]
EXAMPLE_LABELS = [
"Cherry blossom portrait",
"Paris bistro",
"Rocket exhaust",
"Shepherd in wheat",
"Designer toy",
"Ligne claire beach",
"Editorial portrait",
"Muscle car burnout",
"Liquid chrome",
"Astronaut in jungle",
"Blue Cardinal",
"Misty birch wood",
]
S, P, L, C = "Square · 1:1", "Portrait · 2:3", "Landscape · 3:2", "Cinematic · 16:9"
EXAMPLE_FORMATS = [P, L, P, P, S, C, S, L, S, S, S, L]
def gpu_duration(prompt, steps=2, width=1024, height=1024, *args, **kwargs):
# measured on ZeroGPU: about 1.3 s + 0.6 s per step for every megapixel, plus ~4 s on a cold start;
# reserve about twice the cold worst case
megapixels = int(width) * int(height) / 2**20
return int(6 + megapixels * (3 + 1.5 * int(steps)))
@spaces.GPU(duration=gpu_duration)
def generate(prompt, steps=2, width=1024, height=1024, seed=2026, randomize=True, progress=gr.Progress(track_tqdm=True)):
if not prompt or not prompt.strip():
raise gr.Error("Enter a prompt to generate an image.")
if randomize:
seed = random.randint(0, MAX_SEED)
seed = int(seed)
if check_prompt(pipe.text_encoder, pipe.tokenizer, prompt) != "allow":
raise gr.Error("This prompt can't be generated in this demo. Please change it and try again.")
try:
image = nf2_generate(pipe, prompt, int(steps), int(height), int(width), seed)
except RuntimeError as exc:
torch.cuda.empty_cache()
raise gr.Error(
f"Generation failed at {int(width)}×{int(height)}, most likely out of GPU memory. Try a 1024 format."
) from exc
return image, seed
def edit_gpu_duration(prompt, command):
# measured on the 4090: 1.1 s for a 1250-char rewrite (prompt-lookup decoding); reserve ~3x plus a cold start
return int(8 + len(prompt) / 400)
@spaces.GPU(duration=edit_gpu_duration)
def rewrite_prompt(prompt, command):
return edit_prompt(pipe.text_encoder, pipe.tokenizer, prompt, command)
def apply_command(prompt, command):
"""Returns the rewritten prompt and the cleared command."""
if not prompt or not prompt.strip():
raise gr.Error("Write a prompt first, then give a command to change it.")
# no letters in any script ("?", "...", "123") is not an instruction: the LLM would invent an edit for it
if not command or not any(ch.isalpha() for ch in command):
raise gr.Error('Type a command first, e.g. "make it a film still".')
rewritten = rewrite_prompt(prompt, command)
# raising (not a toast + skip) keeps the chained Generate from running on a failed edit
if not rewritten or rewritten == prompt.strip():
raise gr.Error("The command did not change the prompt. Try putting it differently.")
return rewritten, ""
EXAMPLE_STEPS, EXAMPLE_SEED = 2, 2026
@spaces.GPU(duration=30)
def render_example(text, width, height):
return nf2_generate(pipe, text, EXAMPLE_STEPS, height, width, EXAMPLE_SEED)
# every example at every format, keyed by (prompt, width, height)
example_cache = {
(text, *size): render_example(text, *size) for (text,) in EXAMPLE_PROMPTS for size in RESOLUTIONS.values()
}
def cached_example(prompt, steps, width, height, seed):
if seed is None or int(steps) != EXAMPLE_STEPS or int(seed) != EXAMPLE_SEED:
return None
return example_cache.get((prompt, int(width), int(height)))
def show_example(i):
text, fmt = EXAMPLE_PROMPTS[i][0], EXAMPLE_FORMATS[i]
return text, fmt, EXAMPLE_STEPS, *RESOLUTIONS[fmt], EXAMPLE_SEED, example_cache[(text, *RESOLUTIONS[fmt])]
def on_resolution_change(label, prompt, steps, seed):
w, h = RESOLUTIONS[label]
image = cached_example(prompt, steps, w, h, seed)
return gr.update(value=w), gr.update(value=h), gr.update() if image is None else image
STAGE_HTML = """
"""
)
with gr.Row(equal_height=False):
with gr.Column(scale=5, elem_id="controls"):
prompt = gr.Textbox(label="Prompt", lines=3, placeholder="Describe your image.", autofocus=True, elem_id="prompt")
run = gr.Button("Generate", variant="primary", elem_id="generate-btn")
# refine after a result: the command rewrites the prompt, then generates
with gr.Column(elem_id="command-wrap"):
command = gr.Textbox(
label="Command", lines=1, max_lines=1, placeholder='e.g. "make it a film still" or "cat → zebra"',
submit_btn="Apply", elem_id="command",
)
resolution = gr.Radio(list(RESOLUTIONS.keys()), value="Square · 1:1", label="Resolution", elem_id="resolution", elem_classes="seg")
steps = gr.Radio([1, 2, 3, 4], value=2, label="Steps", elem_classes="seg")
with gr.Accordion("Advanced", open=False, elem_id="advanced"):
with gr.Row():
width = gr.Slider(512, 2048, value=1024, step=16, label="Width")
height = gr.Slider(512, 2048, value=1024, step=16, label="Height")
with gr.Row(elem_id="seed-row"):
seed = gr.Number(value=2026, minimum=0, maximum=MAX_SEED, precision=0, label="Seed", interactive=False, elem_id="seed")
randomize = gr.Checkbox(value=True, label="Randomize seed", elem_id="randomize")
with gr.Column(scale=6, elem_id="stage"):
output = gr.Image(label="Result", show_label=False, format="png", elem_id="result-image")
gr.HTML(STAGE_HTML, elem_id="stage-overlay")
gr.HTML('
Examples
', elem_id="examples-head")
with gr.Row(elem_id="examples"):
example_btns = [gr.Button(label, elem_classes="example") for label in EXAMPLE_LABELS]
# autofocus is for desktops; on a phone it would raise the keyboard on load
demo.load(None, None, None, js="() => { if (window.innerWidth < 768) setTimeout(() => { const t = document.querySelector('#prompt textarea'); t && t.blur(); }, 400); }")
resolution.change(on_resolution_change, [resolution, prompt, steps, seed], [width, height, output])
randomize.change(lambda r: gr.update(interactive=not r), randomize, seed)
inputs = [prompt, steps, width, height, seed, randomize]
# on a phone the stage is below the controls: bring it into view when a run starts
TO_STAGE = "const s = document.getElementById('stage'); if (s) { let a = document.getElementById('stage-anchor'); if (!a) { a = document.createElement('div'); a.id = 'stage-anchor'; s.prepend(a); } a.scrollIntoView({ behavior: 'smooth', block: 'start' }); }"
SCROLL_JS = "(...args) => { if (window.innerWidth < 768 && args[0] && args[0].trim()) { setTimeout(() => { " + TO_STAGE + " }, 250); } return args; }"
run.click(generate, inputs, [output, seed], js=SCROLL_JS)
# an example's image is ready at once: bring the stage to the top of the view
STAGE_JS = "() => { " + TO_STAGE + " }"
for i, btn in enumerate(example_btns):
btn.click(lambda i=i: show_example(i), None, [prompt, resolution, steps, width, height, seed, output], js=STAGE_JS)
prompt.submit(generate, inputs, [output, seed], js=SCROLL_JS)
# command: the rewrite replaces the prompt, then the image is regenerated
command.submit(apply_command, [prompt, command], [prompt, command]).success(generate, inputs, [output, seed], js=SCROLL_JS)
# client-side rendering: server-side rendering races the API-info fetch on Spaces and flashes an error page
demo.launch(theme=theme, css=CSS, head=HEAD, ssr_mode=False)