PratikBuilds's picture
Polish Cloud Parade public labels
88daeb1 verified
Raw
History Blame Contribute Delete
24.7 kB
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"""
<article>
<span>Float {index}</span>
<strong>{escape(item.split('.')[0])}</strong>
<p>{escape(item)}</p>
</article>
"""
for index, item in enumerate(floats, 1)
)
steps = "".join(
f'<i style="left:{x}px; top:{y}px; animation-delay:{index * 220}ms;"></i>'
for index, (x, y) in enumerate(points)
)
provider = str(trace.get("provider") or req.provider)
return f"""
<section class="parade-cabinet" style="--cabinet:{color}; --cabinet-ink:{ink}; --cabinet-bg:{dark};">
<header>
<span>{escape(public_mode(trace))} parade / {escape(public_provider(provider))}</span>
<h2>{escape(title)}</h2>
<p>{escape(req.town)} / {escape(req.weather)} / led by {escape(req.marshal)}</p>
</header>
<div class="route-stage" aria-label="Animated parade route">
<svg viewBox="0 0 900 470" role="img">
<rect x="34" y="42" width="832" height="382" rx="14"></rect>
<polyline points="{route}"></polyline>
<text x="70" y="82">Route Cabinet</text>
<text x="620" y="405">Final corner</text>
</svg>
<b class="sun"></b>
<div class="route-steps">{steps}</div>
<div class="float one">1</div>
<div class="float two">2</div>
<div class="float three">3</div>
</div>
<div class="float-grid">{float_cards}</div>
<footer>
<strong>Crowd chant</strong>
<p>"{escape(chant)}"</p>
</footer>
</section>
"""
def poster_html(req: ParadeRequest, plan: str, trace: dict[str, object]) -> str:
title = extract_title(plan, req)
chant = extract_chant(plan)
return f"""
<section class="poster-card">
<span>Share Poster</span>
<h2>{escape(title)}</h2>
<p>{escape(req.weather)} in {escape(req.town)}. Grand marshal: {escape(req.marshal)}.</p>
<strong>"{escape(chant)}"</strong>
<em>{escape(public_mode(trace))} parade / {escape(public_provider(req.provider))}</em>
</section>
"""
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()