Ace / acestep /gradio_ui /interfaces /__init__.py
Transfer Bot
Moved to Hugging Face automatically
fa1466c
Raw
History Blame Contribute Delete
25.3 kB
"""
Gradio UI Components Module
Contains all Gradio interface component definitions and layouts
"""
import gradio as gr
from acestep.gradio_ui.i18n import get_i18n, t
from acestep.gradio_ui.interfaces.dataset import create_dataset_section
from acestep.gradio_ui.interfaces.generation import create_generation_section
from acestep.gradio_ui.interfaces.result import create_results_section
from acestep.gradio_ui.interfaces.training import create_training_section
from acestep.gradio_ui.events import setup_event_handlers, setup_training_event_handlers
# =====================================================================================
# ACE STUDIO β€” Premium visual skin
# A full "recording studio" reskin of the Gradio interface: animated aurora backdrop,
# glassmorphic panels, neon accent system, cinematic hero banner and a restyled
# control surface (tabs / buttons / sliders / dataframes / audio players / accordions).
# Nothing about the underlying components, event wiring or business logic is touched β€”
# this only changes how the existing sections *look*.
# =====================================================================================
STUDIO_CSS = """
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500;600&display=swap');
:root {
--studio-bg-0: #05060a;
--studio-bg-1: #0b0e17;
--studio-bg-2: #10141f;
--studio-panel: rgba(20, 24, 38, 0.62);
--studio-panel-border: rgba(148, 163, 255, 0.16);
--studio-line: rgba(255, 255, 255, 0.08);
--studio-text: #eef0ff;
--studio-text-dim: #9aa1c4;
--neon-a: #7c5cff; /* violet */
--neon-b: #22d3ee; /* cyan */
--neon-c: #ff5cb3; /* magenta */
--neon-d: #34e89e; /* mint */
--studio-radius: 16px;
--studio-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);
}
/* ---------- Base canvas ---------- */
.gradio-container {
font-family: 'Inter', 'Space Grotesk', system-ui, sans-serif !important;
max-width: 1400px !important;
background:
radial-gradient(ellipse 60% 45% at 12% -10%, rgba(124, 92, 255, 0.28), transparent 60%),
radial-gradient(ellipse 55% 40% at 100% 0%, rgba(34, 211, 238, 0.20), transparent 60%),
radial-gradient(ellipse 70% 50% at 50% 110%, rgba(255, 92, 179, 0.14), transparent 60%),
linear-gradient(180deg, var(--studio-bg-0) 0%, var(--studio-bg-1) 45%, var(--studio-bg-2) 100%) !important;
background-attachment: fixed !important;
color: var(--studio-text) !important;
position: relative;
}
/* faint animated scan-grid over everything, purely decorative */
.gradio-container::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
background-image:
linear-gradient(rgba(148, 163, 255, 0.045) 1px, transparent 1px),
linear-gradient(90deg, rgba(148, 163, 255, 0.045) 1px, transparent 1px);
background-size: 42px 42px;
mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, black 10%, transparent 75%);
animation: studio-grid-drift 26s linear infinite;
}
@keyframes studio-grid-drift {
0% { background-position: 0 0, 0 0; }
100% { background-position: 84px 84px, 84px 84px; }
}
body, .gradio-container, .dark { color-scheme: dark !important; }
/* ---------- Typography ---------- */
.gradio-container h1, .gradio-container h2, .gradio-container h3, .gradio-container h4 {
font-family: 'Space Grotesk', sans-serif !important;
letter-spacing: 0.2px;
}
.gradio-container h3, .gradio-container h4 {
color: var(--studio-text) !important;
position: relative;
padding-left: 14px;
margin: 18px 0 10px 0 !important;
}
.gradio-container h3::before, .gradio-container h4::before {
content: "";
position: absolute;
left: 0; top: 4px; bottom: 4px;
width: 4px;
border-radius: 3px;
background: linear-gradient(180deg, var(--neon-a), var(--neon-b));
box-shadow: 0 0 10px rgba(124, 92, 255, 0.6);
}
.gradio-container hr {
border: none;
height: 1px;
margin: 22px 0;
background: linear-gradient(90deg, transparent, var(--neon-a) 20%, var(--neon-b) 50%, var(--neon-c) 80%, transparent);
opacity: 0.55;
}
/* ---------- Hero ---------- */
.studio-hero {
position: relative;
z-index: 1;
text-align: center;
padding: 40px 24px 28px 24px;
margin-bottom: 22px;
}
.studio-hero-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 16px;
border-radius: 999px;
background: rgba(124, 92, 255, 0.12);
border: 1px solid rgba(124, 92, 255, 0.4);
color: #cfd3ff;
font-size: 12.5px;
font-weight: 600;
letter-spacing: 0.6px;
text-transform: uppercase;
margin-bottom: 18px;
}
.studio-hero-badge .dot {
width: 7px; height: 7px; border-radius: 50%;
background: var(--neon-d);
box-shadow: 0 0 8px var(--neon-d), 0 0 16px var(--neon-d);
animation: studio-pulse 1.6s ease-in-out infinite;
}
@keyframes studio-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.75); }
}
.studio-hero h1.studio-title {
font-size: clamp(34px, 5.2vw, 58px) !important;
font-weight: 700 !important;
margin: 0 0 10px 0 !important;
line-height: 1.08;
background: linear-gradient(100deg, #ffffff 0%, var(--neon-b) 35%, var(--neon-a) 65%, var(--neon-c) 100%);
background-size: 200% auto;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: studio-shimmer 7s ease-in-out infinite;
}
@keyframes studio-shimmer {
0%, 100% { background-position: 0% center; }
50% { background-position: 100% center; }
}
.studio-hero p.studio-subtitle {
font-size: 16.5px;
color: var(--studio-text-dim);
max-width: 620px;
margin: 0 auto 22px auto;
font-weight: 500;
}
.studio-cta {
background: linear-gradient(120deg, rgba(124,92,255,0.22), rgba(34,211,238,0.18));
border: 1px solid rgba(124, 92, 255, 0.45);
padding: 12px 22px;
border-radius: 14px;
display: inline-block;
margin: 4px auto 20px auto;
box-shadow: 0 0 24px rgba(124, 92, 255, 0.18);
}
.studio-cta span { color: #f1f2ff; font-size: 14.5px; }
.studio-cta a {
color: var(--neon-b) !important;
font-weight: 700;
text-decoration: none;
border-bottom: 1px dashed var(--neon-b);
}
.studio-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin: 4px 0 26px 0; }
.studio-links a {
color: var(--studio-text) !important;
text-decoration: none !important;
font-size: 13px;
font-weight: 600;
padding: 8px 16px;
border-radius: 999px;
background: var(--studio-panel);
border: 1px solid var(--studio-panel-border);
transition: all 0.25s ease;
}
.studio-links a:hover {
border-color: var(--neon-b);
color: #fff !important;
box-shadow: 0 0 16px rgba(34, 211, 238, 0.35);
transform: translateY(-2px);
}
.studio-feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
gap: 14px;
max-width: 1100px;
margin: 6px auto 0 auto;
}
.studio-feature-card {
background: var(--studio-panel);
border: 1px solid var(--studio-panel-border);
border-radius: var(--studio-radius);
padding: 18px 16px;
text-align: left;
backdrop-filter: blur(10px);
transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}
.studio-feature-card:hover {
transform: translateY(-4px);
border-color: rgba(124, 92, 255, 0.55);
box-shadow: var(--studio-shadow);
}
.studio-feature-icon { font-size: 22px; margin-bottom: 8px; display: block; }
.studio-feature-card b { color: #fff; font-size: 14.5px; display: block; margin-bottom: 4px; }
.studio-feature-card span { color: var(--studio-text-dim); font-size: 12.5px; line-height: 1.4; }
/* ---------- Minimal in-app top bar ---------- */
.studio-topbar {
display: flex;
align-items: center;
gap: 14px;
padding: 10px 4px 18px 4px;
margin-bottom: 6px;
border-bottom: 1px solid var(--studio-line);
}
.studio-back {
color: var(--studio-text-dim) !important;
text-decoration: none !important;
font-size: 13px;
font-weight: 600;
padding: 6px 14px;
border-radius: 999px;
border: 1px solid var(--studio-panel-border);
background: var(--studio-panel);
transition: all 0.2s ease;
}
.studio-back:hover { color: #fff !important; border-color: var(--neon-b); }
.studio-topbar-title {
font-family: 'Space Grotesk', sans-serif;
font-weight: 700;
font-size: 15px;
color: var(--studio-text);
opacity: 0.85;
}
/* =====================================================================
LANDING PAGE β€” a distinct "front door" screen shown before the studio.
Console / mixing-desk identity: warm near-black, amber + teal, an
animated EQ meter as the signature element, and channel-strip cards
that describe what the tool actually does.
===================================================================== */
#landing-page.block, #landing-page {
background: transparent !important;
border: none !important;
box-shadow: none !important;
backdrop-filter: none !important;
padding: 0 !important;
}
.landing-wrap {
position: relative;
background:
radial-gradient(ellipse 70% 45% at 50% -10%, rgba(255, 180, 84, 0.12), transparent 60%),
radial-gradient(ellipse 60% 40% at 90% 5%, rgba(43, 217, 185, 0.09), transparent 60%),
linear-gradient(180deg, #0c0a08 0%, #141110 100%);
border: 1px solid rgba(243, 237, 228, 0.08);
border-radius: 22px;
padding: 56px 32px 8px 32px;
text-align: center;
overflow: hidden;
}
.landing-eyebrow {
font-family: 'IBM Plex Mono', monospace;
font-size: 12.5px; letter-spacing: 1.6px; text-transform: uppercase;
color: #2bd9b9; display: inline-flex; align-items: center; gap: 10px; margin-bottom: 22px;
}
.landing-eyebrow::before, .landing-eyebrow::after { content: ""; width: 26px; height: 1px; background: #1c6d5e; }
.landing-title {
font-family: 'Space Grotesk', sans-serif; font-weight: 700;
font-size: clamp(32px, 5.4vw, 56px); line-height: 1.06; margin: 0 0 16px 0;
color: #f3ede4; letter-spacing: -0.5px;
}
.landing-title .accent { color: #ffb454; }
.landing-tagline { font-size: 16.5px; color: #a99f8e; max-width: 560px; margin: 0 auto 30px auto; line-height: 1.55; }
.landing-eq { display: flex; align-items: flex-end; justify-content: center; gap: 5px; height: 52px; margin: 0 auto 6px auto; }
.landing-eq span {
width: 5px; border-radius: 3px;
background: linear-gradient(180deg, #ffb454, #2bd9b9);
animation: landing-eqbounce 1.2s ease-in-out infinite;
opacity: 0.85;
}
@keyframes landing-eqbounce { 0%, 100% { height: 10%; } 50% { height: 100%; } }
/* the "Enter Studio" call to action is a REAL Gradio button (so Python can react to it),
styled here to look like part of the console rather than a default Gradio button */
#enter-studio-btn { max-width: 300px; margin: 8px auto 6px auto !important; }
#enter-studio-btn button {
background: linear-gradient(120deg, #ffb454, #ffcd82) !important;
color: #1a1208 !important;
font-family: 'Space Grotesk', sans-serif !important;
font-weight: 700 !important;
font-size: 16px !important;
border-radius: 11px !important;
padding: 14px 10px !important;
border: none !important;
box-shadow: 0 10px 30px rgba(255, 180, 84, 0.22) !important;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
#enter-studio-btn button:hover { transform: translateY(-2px); box-shadow: 0 14px 36px rgba(255, 180, 84, 0.32) !important; }
.landing-note { margin: 10px 0 0 0; font-size: 12.5px; color: #6f6558; font-family: 'IBM Plex Mono', monospace; }
.landing-note a { color: #2bd9b9; text-decoration: none; border-bottom: 1px dashed #1c6d5e; }
.landing-rack {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 2px;
background: rgba(243, 237, 228, 0.08); border: 1px solid rgba(243, 237, 228, 0.08);
border-radius: 16px; overflow: hidden; margin: 34px 0 26px 0;
}
.landing-strip { background: #1b1714; padding: 24px 18px; display: flex; flex-direction: column; gap: 12px; min-height: 195px; text-align: left; transition: background 0.25s ease; }
.landing-strip:hover { background: #211c18; }
.landing-strip .ch-num { font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: #6f6558; letter-spacing: 1px; }
.landing-strip .ch-icon { font-size: 20px; }
.landing-strip h4 { font-family: 'Space Grotesk', sans-serif; font-size: 14.5px; margin: 0; color: #f3ede4; font-weight: 600; padding-left: 0 !important; }
.landing-strip h4::before { display: none !important; }
.landing-strip p { font-size: 12.5px; color: #a99f8e; line-height: 1.5; margin: 0; }
.landing-fader { height: 3px; background: rgba(243, 237, 228, 0.16); border-radius: 2px; position: relative; margin-top: auto; }
.landing-fader i { position: absolute; top: 50%; width: 10px; height: 10px; border-radius: 50%; background: #ffb454; transform: translate(-50%, -50%); box-shadow: 0 0 10px rgba(255, 180, 84, 0.5); }
.landing-footer {
display: flex; align-items: center; justify-content: center; gap: 18px; flex-wrap: wrap;
padding: 22px 0 30px 0; font-size: 12px; font-family: 'IBM Plex Mono', monospace; color: #6f6558;
}
.landing-footer a { text-decoration: none; color: #6f6558; transition: color 0.2s ease; }
.landing-footer a:hover { color: #ffb454; }
#studio-back-btn button {
background: var(--studio-panel) !important;
border: 1px solid var(--studio-panel-border) !important;
color: var(--studio-text-dim) !important;
font-size: 13px !important;
font-weight: 600 !important;
border-radius: 999px !important;
box-shadow: none !important;
}
#studio-back-btn button:hover { color: #fff !important; border-color: var(--neon-b) !important; }
@media (max-width: 860px) { .landing-rack { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .landing-rack { grid-template-columns: 1fr; } .landing-wrap { padding: 40px 18px 4px 18px; } }
/* legacy hooks kept so nothing that references them elsewhere breaks */
.main-header { text-align: center; margin-bottom: 0.5rem; }
.section-header {
background: linear-gradient(90deg, var(--neon-a), var(--neon-b));
color: #06070d;
font-weight: 700;
padding: 10px 14px;
border-radius: 10px;
margin: 10px 0;
box-shadow: 0 4px 18px rgba(124, 92, 255, 0.3);
}
/* ---------- Panels / groups / accordions ---------- */
.gradio-container .block, .gradio-container .form, .gradio-container .gr-group, .gradio-container fieldset {
background: var(--studio-panel) !important;
border: 1px solid var(--studio-panel-border) !important;
border-radius: var(--studio-radius) !important;
backdrop-filter: blur(10px);
}
.gradio-container .tabitem { border: none !important; background: transparent !important; }
.gradio-container .tab-nav {
border: none !important;
gap: 6px;
background: rgba(255,255,255,0.03);
padding: 6px;
border-radius: 14px;
}
.gradio-container .tab-nav button {
border-radius: 10px !important;
border: 1px solid transparent !important;
color: var(--studio-text-dim) !important;
font-weight: 600 !important;
transition: all 0.2s ease;
}
.gradio-container .tab-nav button.selected {
background: linear-gradient(120deg, rgba(124,92,255,0.28), rgba(34,211,238,0.2)) !important;
color: #fff !important;
border-color: rgba(124, 92, 255, 0.5) !important;
box-shadow: 0 0 14px rgba(124, 92, 255, 0.25);
}
/* ---------- Buttons ---------- */
.gradio-container button.primary, .gradio-container .primary {
background: linear-gradient(120deg, var(--neon-a), var(--neon-b)) !important;
border: none !important;
color: #06070d !important;
font-weight: 700 !important;
box-shadow: 0 4px 20px rgba(124, 92, 255, 0.35);
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.gradio-container button.primary:hover { transform: translateY(-1px); box-shadow: 0 6px 26px rgba(34, 211, 238, 0.45); }
.gradio-container button.secondary {
background: rgba(255,255,255,0.05) !important;
border: 1px solid var(--studio-panel-border) !important;
color: var(--studio-text) !important;
}
.gradio-container button.secondary:hover { border-color: var(--neon-b) !important; color: #fff !important; }
/* ---------- Inputs / sliders / dataframe / audio ---------- */
.gradio-container input, .gradio-container textarea, .gradio-container select {
background: rgba(0,0,0,0.28) !important;
border: 1px solid var(--studio-panel-border) !important;
color: var(--studio-text) !important;
border-radius: 10px !important;
}
.gradio-container input:focus, .gradio-container textarea:focus {
border-color: var(--neon-b) !important;
box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.18) !important;
}
.gradio-container input[type=range] { accent-color: var(--neon-a); }
.gradio-container .wrap.svelte-1cl284s, .gradio-container table {
background: rgba(0,0,0,0.18) !important;
border-radius: 12px !important;
}
.gradio-container audio, .gradio-container video {
border-radius: 12px !important;
border: 1px solid var(--studio-panel-border) !important;
}
/* scrollbars */
.gradio-container ::-webkit-scrollbar { width: 9px; height: 9px; }
.gradio-container ::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--neon-a), var(--neon-b));
border-radius: 6px;
}
.gradio-container ::-webkit-scrollbar-track { background: rgba(255,255,255,0.03); }
.lm-hints-row { align-items: stretch; }
.lm-hints-col { display: flex; }
.lm-hints-col > div { flex: 1; display: flex; }
.lm-hints-btn button { height: 100%; width: 100%; }
@media (max-width: 640px) {
.studio-feature-grid { grid-template-columns: 1fr; }
}
"""
def build_landing_hero_html(title: str) -> str:
"""Top half of the landing screen: eyebrow, title, tagline, animated EQ meter."""
delays = [0.0, 0.15, 0.3, 0.1, 0.45, 0.25, 0.55, 0.05, 0.35, 0.2, 0.5, 0.4]
bars = "".join(f'<span style="animation-delay:{d}s"></span>' for d in delays)
return f"""
<div class="landing-wrap">
<div class="landing-eyebrow">ZeroGPU &middot; Real-Time Inference</div>
<h1 class="landing-title">Compose music<br>at the speed of <span class="accent">thought</span>.</h1>
<p class="landing-tagline">{title} turns a short description, lyric or reference track into a finished, studio-quality song β€” open source, powered by ACE-Step v1.5.</p>
<div class="landing-eq" aria-hidden="true">{bars}</div>
</div>
"""
def build_landing_rack_html() -> str:
"""Bottom half of the landing screen: channel-strip feature rack + footer links."""
return """
<div class="landing-wrap" style="padding-top:8px;">
<p class="landing-note">
Running on <a href="https://huggingface.co/collections/ACE-Step/ace-step-15" target="_blank">Hugging Face ZeroGPU</a>
β€” first generation may take a little longer while a GPU is allocated.
</p>
<div class="landing-rack">
<div class="landing-strip">
<div class="ch-num">CH.01</div>
<div class="ch-icon">🎼</div>
<h4>Text &rarr; Music</h4>
<p>Describe a genre, mood or write full lyrics β€” get back a complete, mixed track in seconds.</p>
<div class="landing-fader"><i style="left:78%"></i></div>
</div>
<div class="landing-strip">
<div class="ch-num">CH.02</div>
<div class="ch-icon">πŸŽ™οΈ</div>
<h4>Reference-Guided</h4>
<p>Upload a reference recording to steer vocals, instrumentation and style with precision.</p>
<div class="landing-fader"><i style="left:55%"></i></div>
</div>
<div class="landing-strip">
<div class="ch-num">CH.03</div>
<div class="ch-icon">🧠</div>
<h4>LoRA Fine-Tuning</h4>
<p>Build a custom dataset and train your own LoRA adapter directly inside the Space.</p>
<div class="landing-fader"><i style="left:40%"></i></div>
</div>
<div class="landing-strip">
<div class="ch-num">CH.04</div>
<div class="ch-icon">⚑</div>
<h4>ZeroGPU Powered</h4>
<p>Serverless, on-demand GPU inference β€” fast, free and fully open source.</p>
<div class="landing-fader"><i style="left:92%"></i></div>
</div>
</div>
<div class="landing-footer">
<span>ACE-Step v1.5 &middot; Open Source Music Generation</span>
<a href="https://huggingface.co/collections/ACE-Step/ace-step-15" target="_blank">Hugging Face</a>
<a href="https://modelscope.cn/models/ACE-Step/ACE-Step-v1-5" target="_blank">ModelScope</a>
<a href="https://github.com/ACE-Step/ACE-Step-1.5" target="_blank">GitHub</a>
<a href="https://discord.gg/PeWDxrkdj7" target="_blank">Discord</a>
<a href="https://arxiv.org/abs/2602.00744" target="_blank">Technical Report</a>
</div>
</div>
"""
def build_studio_header_html(title: str) -> str:
"""Small in-app title shown next to the 'Home' button once inside the studio."""
return f'<span class="studio-topbar-title">{title}</span>'
def create_gradio_interface(dit_handler, llm_handler, dataset_handler, init_params=None, language='en') -> gr.Blocks:
"""
Create Gradio interface
Args:
dit_handler: DiT handler instance
llm_handler: LM handler instance
dataset_handler: Dataset handler instance
init_params: Dictionary containing initialization parameters and state.
If None, service will not be pre-initialized.
language: UI language code ('en', 'zh', 'ja', default: 'en')
Returns:
Gradio Blocks instance
"""
# Initialize i18n with selected language
i18n = get_i18n(language)
with gr.Blocks(
title=t("app.title"),
theme=gr.themes.Soft(),
css=STUDIO_CSS
) as demo:
# -----------------------------------------------------------------
# Landing screen β€” shown first. Purely presentational (HTML) plus one
# real Gradio button so a click can be handled in Python to switch
# views. This lives in the SAME app/URL as the studio (no separate
# server route), which keeps it 100% compatible with HF ZeroGPU's
# startup scan (that scan hooks demo.launch() directly).
# -----------------------------------------------------------------
with gr.Column(visible=True, elem_id="landing-page") as landing_page:
gr.HTML(build_landing_hero_html(t("app.title")))
enter_btn = gr.Button("Enter the Studio β†’", elem_id="enter-studio-btn", variant="primary")
gr.HTML(build_landing_rack_html())
# -----------------------------------------------------------------
# Studio screen β€” the real app, hidden until "Enter the Studio" is clicked.
# -----------------------------------------------------------------
with gr.Column(visible=False, elem_id="studio-page") as studio_page:
with gr.Row(elem_classes=["studio-topbar"]):
back_btn = gr.Button("← Home", elem_id="studio-back-btn", size="sm", scale=0)
gr.HTML(build_studio_header_html(t("app.title")))
# Dataset Explorer Section
dataset_section = create_dataset_section(dataset_handler)
# Generation Section (pass init_params and language to support pre-initialization)
generation_section = create_generation_section(dit_handler, llm_handler, init_params=init_params, language=language)
# Results Section
results_section = create_results_section(dit_handler)
# Training Section (LoRA training and dataset builder)
# Pass init_params to support hiding in service mode
training_section = create_training_section(dit_handler, llm_handler, init_params=init_params)
# Connect event handlers (pass init_params for multi-model support)
setup_event_handlers(demo, dit_handler, llm_handler, dataset_handler, dataset_section, generation_section, results_section, init_params=init_params)
# Connect training event handlers
setup_training_event_handlers(demo, dit_handler, llm_handler, training_section)
# Wire the landing <-> studio view switch
enter_btn.click(
fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
inputs=None,
outputs=[landing_page, studio_page],
)
back_btn.click(
fn=lambda: (gr.update(visible=True), gr.update(visible=False)),
inputs=None,
outputs=[landing_page, studio_page],
)
return demo