import hashlib import json import math import os import random import re import urllib.error import urllib.request import wave from dataclasses import asdict, dataclass from html import escape from pathlib import Path import gradio as gr try: from huggingface_hub import InferenceClient except Exception: # pragma: no cover - dependency is available in normal runtime InferenceClient = None APP_TITLE = "Cloud Parade Cabinet" CLOUD_MODEL = os.getenv("PARADE_MODEL", "Qwen/Qwen2.5-7B-Instruct") OPENBMB_MODEL = os.getenv("OPENBMB_MODEL", "openbmb/MiniCPM4-8B") NVIDIA_MODEL = os.getenv("NVIDIA_MODEL", "nvidia/llama-3.1-nemotron-nano-8b-v1") NVIDIA_API_URL = "https://integrate.api.nvidia.com/v1/chat/completions" SPACE_URL = os.getenv("PARADE_SPACE_URL", "https://huggingface.co/spaces/build-small-hackathon/cloud-parade-cabinet") MAX_NEW_TOKENS = int(os.getenv("PARADE_MAX_NEW_TOKENS", "180")) PROVIDERS = [ "Hugging Face: Qwen 7B", "OpenBMB: MiniCPM4 8B", "NVIDIA: Nemotron Nano 8B", "Practice writer", ] PUBLIC_PROVIDERS = { "Hugging Face: Qwen 7B": "Cloud parade voice", "OpenBMB: MiniCPM4 8B": "Mini cabinet voice", "NVIDIA: Nemotron Nano 8B": "Brass cabinet voice", "Practice writer": "Cabinet practice voice", } PROVIDER_CHOICES = [ ("Cloud parade voice", "Hugging Face: Qwen 7B"), ("Mini cabinet voice", "OpenBMB: MiniCPM4 8B"), ("Brass cabinet voice", "NVIDIA: Nemotron Nano 8B"), ("Cabinet practice voice", "Practice writer"), ] WEATHERS = [ "paper rain that apologizes", "sideways sunshine", "fog shaped like old applause", "tiny hailstones with opinions", "moonlight stuck in traffic", ] MARSHALS = [ "a nervous umbrella", "a brass thimble", "the mayor's missing shoe", "a lantern with stage fright", "a soup spoon in formal gloves", ] TOWNS = [ "Turnip Junction", "Little Static", "Button-on-the-Hill", "North Crumb", "The Fourth Drawer", ] TROUBLES = [ "the parade route forgot its own corners", "all floats must travel backward for one block", "the crowd only cheers in whispers", "confetti is legally considered weather", "the final float refuses to be last", ] COLORS = { "ticket yellow": "#fff2bd", "mint night": "#0f5b52", "tomato band": "#c9513f", "ink blue": "#223f6c", } LOG_PATH = Path(__file__).resolve().parent / "PARADE_LOG.md" MEDIA_DIR = Path(__file__).resolve().parent / "media" @dataclass(frozen=True) class ParadeRequest: town: str weather: str marshal: str trouble: str color: str = "ticket yellow" energy: int = 4 provider: str = PROVIDERS[0] cloud_mode: bool = True def clean_text(value: str) -> str: return re.sub(r"\s+", " ", str(value or "")).strip() def seed_for(req: ParadeRequest) -> int: raw = json.dumps(asdict(req), sort_keys=True) return int(hashlib.sha256(raw.encode("utf-8")).hexdigest()[:12], 16) def prompt_for(req: ParadeRequest) -> str: return f""" Write a miniature parade plan for a strange toy called Cloud Parade Cabinet. Keep it under 120 words. Use crisp, playful, human-readable copy. The parade must have: - a parade title - three floats with short names and one visible action each - one crowd chant in quotes - a finale that changes the route Town: {req.town} Weather: {req.weather} Grand marshal: {req.marshal} Trouble: {req.trouble} Energy: {req.energy}/5 """.strip() def public_provider(provider: str) -> str: return PUBLIC_PROVIDERS.get(provider, provider) def public_mode(trace: dict[str, object]) -> str: return "Live" if trace.get("mode") == "cloud" else "Practice" def fallback_parade(req: ParadeRequest) -> str: rng = random.Random(seed_for(req)) verbs = ["wobbles", "bows", "zigzags", "sparkles", "argues politely", "turns left twice"] float_names = [ f"{req.marshal.title()} Baton Cart", f"{req.weather.title()} Wagon", f"{rng.choice(['Pocket Drum', 'Lantern Choir', 'Button Brigade', 'Crumb Engine'])}", ] chant = rng.choice( [ "Left foot, cloud foot, cabinet door!", "Tiny street, louder feet!", "Bring the corner back!", "No float left behind!", ] ) finale = rng.choice( [ "The route folds into a postcard and opens one block east.", "The last float becomes the first and the crowd follows the correction.", "A chalk arrow sneezes, sending everyone through the narrowest alley.", ] ) return ( f"{req.town} Cloud Parade. " f"Float 1: {float_names[0]} {rng.choice(verbs)} under {req.weather}. " f"Float 2: {float_names[1]} {rng.choice(verbs)} while {req.trouble}. " f"Float 3: {float_names[2]} {rng.choice(verbs)} beside the curb. " f'The crowd chants "{chant}" ' f"Finale: {finale}" ) def call_cloud_parade(req: ParadeRequest) -> tuple[str, dict[str, object]]: trace = {"mode": "fallback", "provider": req.provider, "model": None, "error": None} if not req.cloud_mode or req.provider == "Practice writer": trace["error"] = "live writer disabled" return fallback_parade(req), trace if req.provider == "NVIDIA: Nemotron Nano 8B": return call_nvidia_parade(req, trace) return call_hf_parade(req, trace) def call_hf_parade(req: ParadeRequest, trace: dict[str, object]) -> tuple[str, dict[str, object]]: token = os.getenv("HF_API_KEY") or os.getenv("HF_TOKEN") model = OPENBMB_MODEL if req.provider == "OpenBMB: MiniCPM4 8B" else CLOUD_MODEL if not token: trace["error"] = "HF_API_KEY or HF_TOKEN is not set" return fallback_parade(req), trace if InferenceClient is None: trace["error"] = "huggingface_hub InferenceClient is unavailable" return fallback_parade(req), trace try: client = InferenceClient(api_key=token) response = client.chat_completion( model=model, messages=[ {"role": "system", "content": "You write tiny, strange, delightful toy text. Avoid explaining yourself."}, {"role": "user", "content": prompt_for(req)}, ], max_tokens=MAX_NEW_TOKENS, temperature=0.92, top_p=0.9, ) text = clean_text(response.choices[0].message.content) if not text: raise RuntimeError("empty cloud response") trace.update({"mode": "cloud", "model": model, "error": None}) return text, trace except Exception as exc: trace["error"] = str(exc)[:220] return fallback_parade(req), trace def call_nvidia_parade(req: ParadeRequest, trace: dict[str, object]) -> tuple[str, dict[str, object]]: token = os.getenv("NVIDIA_API_KEY") if not token: trace["error"] = "NVIDIA_API_KEY is not set" return fallback_parade(req), trace payload = { "model": NVIDIA_MODEL, "messages": [ {"role": "system", "content": "You write tiny, strange, delightful toy text. Avoid explaining yourself."}, {"role": "user", "content": prompt_for(req)}, ], "max_tokens": MAX_NEW_TOKENS, "temperature": 0.85, "top_p": 0.9, "stream": False, } request = urllib.request.Request( NVIDIA_API_URL, data=json.dumps(payload).encode("utf-8"), headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json", "Accept": "application/json", }, method="POST", ) try: with urllib.request.urlopen(request, timeout=45) as response: data = json.loads(response.read().decode("utf-8")) text = clean_text(data["choices"][0]["message"]["content"]) if not text: raise RuntimeError("empty NVIDIA response") trace.update({"mode": "cloud", "model": NVIDIA_MODEL, "error": None}) return text, trace except (urllib.error.HTTPError, urllib.error.URLError, KeyError, IndexError, json.JSONDecodeError, TimeoutError, RuntimeError) as exc: trace["error"] = str(exc)[:220] return fallback_parade(req), trace def split_floats(plan: str) -> list[str]: normalized = re.sub(r"\*\*", "", plan) parts = re.split(r"(?i)(?:\bFloat\s*)?\b[123]\s*[\).:-]\s*", normalized) floats = [] for part in parts[1:]: cleaned = clean_text(part) cleaned = re.split(r"(?i)\b(?:The crowd chants|Crowd chant|Finale)\b", cleaned)[0].strip(" :-") if cleaned: floats.append(cleaned) if len(floats) == 3: break if not floats: parts = re.split(r"(?i)\bFloat\s*\d\s*:\s*", normalized) floats = [clean_text(part) for part in parts[1:4]] while len(floats) < 3: floats.append(["Pocket Drum salutes.", "Lantern Choir glows.", "Crumb Engine turns left."][len(floats)]) return [item[:150] for item in floats] def extract_title(plan: str, req: ParadeRequest) -> str: first = clean_text(re.sub(r"[*#`]", "", plan)).split(".")[0].strip('" ') first = re.sub(r"(?i)^title\s*:\s*", "", first).strip() if 8 <= len(first) <= 72: return first return f"{req.town} Cloud Parade" def extract_chant(plan: str) -> str: match = re.search(r'"([^"]{4,90})"', plan) if match: return match.group(1) match = re.search(r"(?i)chant\s*[:\-]\s*([^\.]{4,90})", plan) if match: return clean_text(match.group(1)).strip('" ') return "Tiny street, louder feet!" def route_points(req: ParadeRequest) -> list[tuple[int, int]]: rng = random.Random(seed_for(req)) points = [(90, 310)] x, y = points[0] for _ in range(4): x += rng.randint(120, 190) y += rng.choice([-70, -35, 35, 70]) y = max(115, min(395, y)) points.append((x, y)) return points def parade_html(req: ParadeRequest, plan: str, trace: dict[str, object]) -> str: color = COLORS.get(req.color, COLORS["ticket yellow"]) dark = "#12221f" if req.color != "ticket yellow" else "#fff8df" ink = "#fff8df" if req.color != "ticket yellow" else "#1d2421" points = route_points(req) route = " ".join(f"{x},{y}" for x, y in points) floats = split_floats(plan) title = extract_title(plan, req) chant = extract_chant(plan) float_cards = "".join( f"""
Float {index} {escape(item.split('.')[0])}

{escape(item)}

""" for index, item in enumerate(floats, 1) ) steps = "".join( f'' for index, (x, y) in enumerate(points) ) provider = str(trace.get("provider") or req.provider) return f"""
{escape(public_mode(trace))} parade / {escape(public_provider(provider))}

{escape(title)}

{escape(req.town)} / {escape(req.weather)} / led by {escape(req.marshal)}

Route Cabinet Final corner
{steps}
1
2
3
{float_cards}
""" def poster_html(req: ParadeRequest, plan: str, trace: dict[str, object]) -> str: title = extract_title(plan, req) chant = extract_chant(plan) return f"""
Share Poster

{escape(title)}

{escape(req.weather)} in {escape(req.town)}. Grand marshal: {escape(req.marshal)}.

"{escape(chant)}" {escape(public_mode(trace))} parade / {escape(public_provider(req.provider))}
""" def caption_for(req: ParadeRequest, plan: str) -> str: title = extract_title(plan, req) chant = extract_chant(plan) return ( f"{APP_TITLE}: {title}. {req.marshal} leads {req.weather} through {req.town}. " f"Chant: \"{chant}\" #BuildSmallHackathon #Gradio" )[:280] def render_parade_audio(req: ParadeRequest, plan: str) -> str: MEDIA_DIR.mkdir(exist_ok=True) title = extract_title(plan, req) chant = extract_chant(plan) digest = hashlib.sha256(f"{seed_for(req)}::{title}::{chant}".encode("utf-8")).hexdigest()[:12] path = MEDIA_DIR / f"parade_{digest}.wav" if path.exists(): return str(path) sample_rate = 22050 duration = 5.4 total = int(sample_rate * duration) rng = random.Random(int(digest, 16)) base_notes = [261.63, 293.66, 329.63, 392.00, 440.00, 523.25] melody = [rng.choice(base_notes) * rng.choice([0.75, 1.0, 1.25]) for _ in range(12)] energy = max(1, min(5, int(req.energy))) beat_gap = max(0.18, 0.42 - energy * 0.035) frames = bytearray() for index in range(total): t = index / sample_rate step_phase = (t % beat_gap) / beat_gap drum = math.exp(-step_phase * 18.0) * 0.42 drum *= math.sin(2 * math.pi * (78 + energy * 9) * t) note_index = min(len(melody) - 1, int(t / duration * len(melody))) note = melody[note_index] note_phase = (t % (duration / len(melody))) / (duration / len(melody)) bell_env = max(0.0, 1.0 - note_phase) ** 2.4 bell = bell_env * 0.26 * ( math.sin(2 * math.pi * note * t) + 0.42 * math.sin(2 * math.pi * note * 2.01 * t) ) crowd = 0.035 * math.sin(2 * math.pi * (rng.choice([5.2, 6.1, 7.3])) * t) sample = max(-0.95, min(0.95, drum + bell + crowd)) value = int(sample * 32767) frames.extend(value.to_bytes(2, "little", signed=True)) with wave.open(str(path), "wb") as wav: wav.setnchannels(1) wav.setsampwidth(2) wav.setframerate(sample_rate) wav.writeframes(frames) return str(path) def log_entry(req: ParadeRequest, plan: str, trace: dict[str, object]) -> str: return "\n".join( [ "## Parade Run", f"- town: {req.town}", f"- weather: {req.weather}", f"- marshal: {req.marshal}", f"- trouble: {req.trouble}", f"- run: {public_mode(trace)}", f"- writer: {public_provider(str(trace.get('provider') or req.provider))}", f"- title: {extract_title(plan, req)}", ] ) def build_parade(town, weather, marshal, trouble, color, energy, provider, cloud_mode, history): req = ParadeRequest( town=town, weather=weather, marshal=marshal, trouble=trouble, color=color, energy=int(energy), provider=provider, cloud_mode=bool(cloud_mode), ) plan, trace = call_cloud_parade(req) history = list(history or []) history.append({"request": asdict(req), "trace": trace, "title": extract_title(plan, req), "plan": plan}) history = history[-6:] return ( parade_html(req, plan, trace), poster_html(req, plan, trace), render_parade_audio(req, plan), caption_for(req, plan), "\n\n".join(log_entry(req, item["plan"], item["trace"]) for item in reversed(history)), history, trace, ) def random_setup(): rng = random.SystemRandom() return ( rng.choice(TOWNS), rng.choice(WEATHERS), rng.choice(MARSHALS), rng.choice(TROUBLES), rng.choice(list(COLORS)), rng.randint(2, 5), ) CSS = """ :root { --ink: #1d2421; --paper: #fff8df; --mint: #0f5b52; --clay: #c9513f; --gold: #f1b84b; --blue: #223f6c; } .gradio-container { background: linear-gradient(135deg, rgba(15, 91, 82, 0.14), transparent 36%), linear-gradient(315deg, rgba(201, 81, 63, 0.12), transparent 30%), var(--paper); color: var(--ink) !important; } #cloud-parade-shell { max-width: 1180px; margin: 0 auto; } #cloud-parade-title h1 { margin-bottom: 0; color: var(--ink); font-size: clamp(2rem, 4vw, 4.2rem); letter-spacing: 0; } #cloud-parade-title p { max-width: 760px; color: #31524c; font-weight: 800; } .control-card { position: sticky; top: 12px; padding: 14px; border-radius: 8px; border: 2px solid rgba(29, 36, 33, 0.16); background: #242528; color: #fff8df; } .control-card label, .control-card .label-wrap, .control-card button, .control-card button span { color: #fff8df !important; } .control-card textarea, .control-card input, .control-card [role="textbox"] { color: #fff8df !important; } .parade-cabinet { border: 2px solid var(--ink); border-radius: 8px; overflow: hidden; background: var(--cabinet-bg); color: var(--cabinet-ink); box-shadow: 0 22px 48px rgba(29, 36, 33, 0.16); } .parade-cabinet header { padding: 22px; background: var(--cabinet); border-bottom: 2px solid var(--ink); } .parade-cabinet header span, .poster-card span { display: block; font-size: 0.72rem; font-weight: 900; letter-spacing: 0.14em; text-transform: uppercase; } .parade-cabinet h2 { margin: 5px 0; color: var(--cabinet-ink) !important; font-size: clamp(1.6rem, 3vw, 3rem); letter-spacing: 0; } .route-stage { position: relative; min-height: 440px; background: radial-gradient(circle at 82% 18%, rgba(241, 184, 75, 0.38), transparent 18%), linear-gradient(180deg, rgba(255,255,255,0.08), transparent); } .route-stage svg { width: 100%; height: 440px; display: block; } .route-stage rect { fill: rgba(255, 248, 223, 0.15); stroke: rgba(255, 248, 223, 0.45); stroke-width: 3; } .route-stage polyline { fill: none; stroke: var(--gold); stroke-width: 13; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 36 16; animation: route-march 2.8s linear infinite; } .route-stage text { fill: var(--cabinet-ink); font-weight: 900; letter-spacing: 0; } .route-steps i { position: absolute; width: 18px; height: 18px; border-radius: 50%; background: var(--clay); border: 2px solid var(--cabinet-ink); animation: step-pop 1.4s ease-in-out infinite; } .float { position: absolute; display: grid; place-items: center; width: 58px; height: 48px; border: 3px solid var(--ink); border-radius: 8px; background: var(--paper); color: var(--ink); font-weight: 900; box-shadow: 0 8px 0 rgba(0,0,0,0.16); } .float.one { left: 14%; top: 58%; animation: float-one 6s ease-in-out infinite; } .float.two { left: 43%; top: 30%; animation: float-two 6.6s ease-in-out infinite; } .float.three { left: 71%; top: 58%; animation: float-three 7.2s ease-in-out infinite; } .float-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; padding: 12px; } .float-grid article { min-height: 152px; padding: 13px; border: 2px solid rgba(255, 248, 223, 0.24); border-radius: 8px; background: rgba(255, 248, 223, 0.09); color: var(--cabinet-ink); } .float-grid span { color: var(--gold); font-size: 0.7rem; font-weight: 900; text-transform: uppercase; } .float-grid strong, .float-grid p { display: block; color: var(--cabinet-ink) !important; overflow-wrap: anywhere; } .float-grid strong { margin: 5px 0; font-size: 1rem; } .parade-cabinet footer { padding: 18px 22px; border-top: 2px solid rgba(255, 248, 223, 0.24); background: rgba(0,0,0,0.18); } .parade-cabinet footer strong, .parade-cabinet footer p { display: block; margin: 0; color: var(--cabinet-ink) !important; } .parade-cabinet footer p { margin-top: 6px; font-size: 1.25rem; font-weight: 900; } .poster-card { padding: 26px; border: 2px solid var(--ink); border-radius: 8px; background: linear-gradient(135deg, rgba(15, 91, 82, 0.12), transparent 40%), var(--paper); color: var(--ink); } .poster-card h2 { margin: 5px 0 8px; color: var(--ink) !important; font-size: clamp(1.5rem, 3vw, 2.6rem); } .poster-card p, .poster-card strong, .poster-card em { display: block; color: #31524c !important; font-weight: 900; } .poster-card strong { margin: 14px 0; color: var(--clay) !important; font-size: 1.2rem; } .log-box textarea, .caption-box textarea { font-family: ui-monospace, Consolas, monospace !important; } @keyframes route-march { to { stroke-dashoffset: -52; } } @keyframes step-pop { 0%, 100% { transform: scale(0.85); } 50% { transform: scale(1.22); } } @keyframes float-one { 50% { transform: translate(210px, -140px) rotate(-3deg); } } @keyframes float-two { 50% { transform: translate(180px, 100px) rotate(3deg); } } @keyframes float-three { 50% { transform: translate(80px, -80px) rotate(-2deg); } } @media (max-width: 820px) { .float-grid { grid-template-columns: 1fr; } .control-card { position: static; } } @media (prefers-reduced-motion: reduce) { .route-stage polyline, .route-steps i, .float { animation: none; } } """ def initial_state(): req = ParadeRequest( town=TOWNS[1], weather=WEATHERS[0], marshal=MARSHALS[0], trouble=TROUBLES[0], color="mint night", energy=4, provider="Practice writer", cloud_mode=False, ) plan = fallback_parade(req) trace = {"mode": "fallback", "provider": req.provider, "model": None, "error": "ready state"} return ( parade_html(req, plan, trace), poster_html(req, plan, trace), render_parade_audio(req, plan), caption_for(req, plan), log_entry(req, plan, trace), ) initial_parade, initial_poster, initial_audio, initial_caption, initial_log = initial_state() with gr.Blocks(css=CSS, theme=gr.themes.Base(), title=APP_TITLE) as demo: with gr.Column(elem_id="cloud-parade-shell"): gr.Markdown( """ # Cloud Parade Cabinet Build a tiny impossible parade. Pick the ingredients, open the cabinet, and watch the route come alive. """, elem_id="cloud-parade-title", ) with gr.Row(equal_height=False): with gr.Column(scale=1, elem_classes="control-card"): cloud_mode = gr.Checkbox(value=True, label="Let the cabinet write live") town = gr.Dropdown(TOWNS, value=TOWNS[1], label="Town") weather = gr.Dropdown(WEATHERS, value=WEATHERS[0], label="Parade weather") marshal = gr.Dropdown(MARSHALS, value=MARSHALS[0], label="Grand marshal") trouble = gr.Dropdown(TROUBLES, value=TROUBLES[0], label="Street trouble") color = gr.Radio(list(COLORS), value="mint night", label="Cabinet color") energy = gr.Slider(1, 5, value=4, step=1, label="Parade energy") provider = gr.Radio(PROVIDER_CHOICES, value=PROVIDERS[0], label="Parade voice") with gr.Row(): randomize = gr.Button("Fresh setup") run = gr.Button("Open cabinet", variant="primary") with gr.Column(scale=2): parade = gr.HTML(value=initial_parade) with gr.Row(): poster = gr.HTML(value=initial_poster) with gr.Column(): audio = gr.Audio(value=initial_audio, label="Parade sound", type="filepath", elem_classes="sound-box") caption = gr.Textbox(value=initial_caption, label="Post caption", lines=5, show_copy_button=True, elem_classes="caption-box") log = gr.Textbox(value=initial_log, label="Keepsake log", lines=10, show_copy_button=True, elem_classes="log-box") trace = gr.JSON(label="Run details", visible=False) history = gr.State([]) randomize.click(random_setup, outputs=[town, weather, marshal, trouble, color, energy], queue=False) run.click( build_parade, inputs=[town, weather, marshal, trouble, color, energy, provider, cloud_mode, history], outputs=[parade, poster, audio, caption, log, history, trace], ) demo.queue(max_size=8, default_concurrency_limit=1) if __name__ == "__main__": demo.launch()