| import gradio as gr |
| import spaces |
| import torch |
| from diffusers import FluxPipeline |
| import random |
| import time |
|
|
| |
|
|
| SETTINGS = [ |
| "outer space", "a medieval tavern", "the bottom of the ocean", |
| "a Tokyo street at night", "a Victorian drawing room", |
| "an Ancient Egyptian tomb", "a pirate ship deck", "a haunted forest", |
| "the Arctic tundra", "a Paris sidewalk cafΓ©", "a Wild West saloon", |
| "a cyberpunk alley", "a Renaissance fair", "ancient jungle temple ruins", |
| "a cloud kingdom", "a submarine interior", "a 1920s jazz club", |
| "the Roman Colosseum", "a dragon's lair", "an enchanted library", |
| "a Mars colony", "a cozy hobbit hole", "a floating sky island", |
| "a neon-lit casino", "sunken Atlantis", "a volcano crater rim", |
| "a hedge maze", "a moon base", "an interdimensional rift", |
| "a hot air balloon over the Alps", |
| ] |
|
|
| OUTFITS = [ |
| "a pirate costume", "Victorian mourning wear", "a hazmat suit", |
| "a beekeeper suit", "an astronaut suit", "Renaissance knight armor", |
| "wizard robes", "a cowboy hat and spurs", "samurai armor", |
| "a ballerina tutu", "a heavy metal band t-shirt", |
| "a royal crown and velvet cape", "a chef's hat and apron", |
| "a detective trench coat", "a superhero cape", "full scuba gear", |
| "a tuxedo", "a Hawaiian shirt", "a ninja outfit", "a disco jumpsuit", |
| "a graduation cap and gown", "a viking helmet", |
| "full plate armor", "a lab coat and goggles", "a pharaoh's headdress", |
| "a clown costume", "a judge's wig and robes", |
| ] |
|
|
| MOODS = [ |
| "looking deeply contemplative", "appearing extremely suspicious", |
| "seemingly thrilled beyond reason", "looking absolutely baffled", |
| "radiating unearned confidence", "looking mildly judgmental", |
| "appearing philosophical", "seeming utterly delighted", |
| "looking deeply unimpressed", "appearing to have seen too much", |
| "radiating chaotic energy", "looking inexplicably regal", |
| "seemingly plotting something", "looking profoundly unbothered", |
| "appearing heroic", "looking vaguely menacing", |
| "seeming emotionally unavailable", "radiating main character energy", |
| "looking like they own the place", |
| ] |
|
|
| STYLES = [ |
| "oil painting", "watercolor illustration", "photorealistic photograph", |
| "vintage postcard", "pencil sketch", "impressionist painting", |
| "children's book illustration", "art nouveau poster", |
| "cinematic still", "gouache illustration", "linocut print", |
| "ukiyo-e woodblock print", "stained glass window", |
| "renaissance portrait", "propaganda poster style", |
| ] |
|
|
| MIN_WAIT = 45 |
| MAX_WAIT = 120 |
|
|
|
|
| def build_prompt(): |
| setting = random.choice(SETTINGS) |
| outfit = random.choice(OUTFITS) |
| mood = random.choice(MOODS) |
| style = random.choice(STYLES) |
| prompt = ( |
| f"a cute rubber duck wearing {outfit}, in {setting}, " |
| f"{mood}, {style}, highly detailed, charming, whimsical" |
| ) |
| def clean(s): |
| return s.replace("a ", "").replace("an ", "").replace("the ", "") |
| caption = f"{clean(outfit).title()} Β· {clean(setting).title()}" |
| return prompt, caption |
|
|
|
|
| |
|
|
| pipe = FluxPipeline.from_pretrained( |
| "black-forest-labs/FLUX.1-schnell", |
| torch_dtype=torch.bfloat16, |
| ) |
|
|
|
|
| @spaces.GPU(duration=60) |
| def generate_duck(): |
| pipe.to("cuda") |
| prompt, caption = build_prompt() |
| image = pipe( |
| prompt, |
| num_inference_steps=4, |
| guidance_scale=0.0, |
| height=512, |
| width=512, |
| ).images[0] |
| pipe.to("cpu") |
| torch.cuda.empty_cache() |
| return image, caption |
|
|
|
|
| |
|
|
| def tick(next_at, items): |
| now = time.time() |
| if now < next_at: |
| secs = int(next_at - now) |
| return next_at, items, f"β³ Next duck in ~**{secs}s** ... probably", items |
|
|
| image, caption = generate_duck() |
| new_items = [(image, caption)] + (items or []) |
| new_items = new_items[:12] |
| next_t = now + random.uniform(MIN_WAIT, MAX_WAIT) |
| status = f"π¦ A duck has appeared! Next one in ~{int(next_t - now)}s" |
| return next_t, new_items, status, new_items |
|
|
|
|
| |
|
|
| CSS = """ |
| @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Nunito:wght@400;600&display=swap'); |
| |
| body, .gradio-container { |
| background-color: #0d0d0d !important; |
| font-family: 'Nunito', sans-serif !important; |
| color: #e0e0e0 !important; |
| } |
| |
| #title { |
| text-align: center; |
| font-family: 'Fredoka One', cursive !important; |
| font-size: 3.2rem !important; |
| color: #FFD700 !important; |
| text-shadow: 0 0 24px rgba(255,215,0,0.35), 0 2px 4px rgba(0,0,0,0.5); |
| margin-bottom: 2px !important; |
| line-height: 1.1; |
| } |
| |
| #subtitle p { |
| text-align: center; |
| color: #888 !important; |
| font-size: 1rem !important; |
| font-style: italic; |
| margin-top: 2px !important; |
| } |
| |
| #status-bar { |
| background: #141414; |
| border: 1px solid #2a2a2a; |
| border-radius: 10px; |
| padding: 8px 20px; |
| margin: 12px auto; |
| max-width: 480px; |
| text-align: center; |
| } |
| |
| #status-bar p { |
| color: #FFD700 !important; |
| font-size: 0.9rem !important; |
| margin: 0 !important; |
| } |
| |
| #duck-gallery { |
| margin-top: 8px; |
| } |
| |
| #duck-gallery .grid-wrap { |
| background: transparent !important; |
| gap: 10px !important; |
| } |
| |
| #duck-gallery .thumbnail-item { |
| border-radius: 12px !important; |
| overflow: hidden; |
| border: 2px solid #1e1e1e !important; |
| transition: border-color 0.25s ease, transform 0.25s ease; |
| } |
| |
| #duck-gallery .thumbnail-item:hover { |
| border-color: #FFD700 !important; |
| transform: scale(1.02); |
| } |
| |
| #duck-gallery .caption-label { |
| background: rgba(0,0,0,0.75) !important; |
| color: #FFD700 !important; |
| font-size: 0.75rem !important; |
| font-family: 'Nunito', sans-serif !important; |
| } |
| |
| #footer-note p { |
| text-align: center; |
| color: #444; |
| font-size: 0.78rem; |
| margin-top: 16px; |
| } |
| |
| footer { display: none !important; } |
| """ |
|
|
| |
|
|
| with gr.Blocks(css=CSS, title="Ducks Happen π¦") as demo: |
|
|
| next_at = gr.State(value=time.time() + 8) |
| items = gr.State(value=[]) |
|
|
| gr.Markdown("# π¦ Ducks Happen", elem_id="title") |
| gr.Markdown( |
| "*Rubber ducks materialize here. There is nothing you can do about it.*", |
| elem_id="subtitle", |
| ) |
|
|
| status_md = gr.Markdown("β³ Preparing the first duck...", elem_id="status-bar") |
|
|
| gallery = gr.Gallery( |
| label=None, |
| show_label=False, |
| columns=3, |
| rows=2, |
| object_fit="cover", |
| height="auto", |
| elem_id="duck-gallery", |
| ) |
|
|
| gr.Markdown( |
| "<p>Built for the <a href='https://huggingface.co/build-small-hackathon' " |
| "style='color:#FFD700;'>Build Small Hackathon 2026</a> Β· " |
| "Powered by FLUX.1-schnell Β· π¦</p>", |
| elem_id="footer-note", |
| ) |
|
|
| timer = gr.Timer(5) |
| timer.tick( |
| fn=tick, |
| inputs=[next_at, items], |
| outputs=[next_at, items, status_md, gallery], |
| concurrency_limit=1, |
| ) |
|
|
| demo.launch() |
|
|