"""Gradio UI for the GEPARD TTS Space — layout, cheetah theme, event wiring. The interface is deliberately free of model code: it receives a fully wired ``synthesize_fn`` callable (already wrapped with the ZeroGPU decorator by ``app.py``) and only handles presentation. """ from __future__ import annotations from typing import Callable, List import gradio as gr from gepard_inference.engine import AppConfig MODE_PRESET = "Preset speaker" MODE_CLONE = "Clone a voice" # Cheetah palette: sunlit fur (yellow→orange), hunting-red accents, savanna green. _CSS = """ .gepard-header { text-align: center; padding: 1.1rem 0 0.4rem 0; } .gepard-header h1 { font-size: 2.6rem; font-weight: 800; margin: 0; background: linear-gradient(90deg, #eab308 0%, #f59e0b 30%, #ea580c 65%, #dc2626 100%); -webkit-background-clip: text; background-clip: text; color: transparent; letter-spacing: 0.04em; } .gepard-header p { margin: 0.35rem 0 0 0; font-size: 1.02rem; color: var(--body-text-color-subdued); } .gepard-header .stripe { height: 4px; width: 240px; margin: 0.8rem auto 0 auto; border-radius: 2px; background: repeating-linear-gradient( 90deg, #eab308 0 24px, #ea580c 24px 48px, #dc2626 48px 72px, #16a34a 72px 96px); } #generate-btn { background: linear-gradient(90deg, #f59e0b 0%, #ea580c 60%, #dc2626 100%); color: white; font-weight: 700; border: none; } #generate-btn:hover { filter: brightness(1.07); } /* Slimmer sliders in Generation settings: thinner track + smaller thumb. */ input[type="range"] { height: 4px; } input[type="range"]::-webkit-slider-runnable-track { height: 4px; } input[type="range"]::-moz-range-track { height: 4px; } input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; height: 13px; width: 13px; border-radius: 50%; margin-top: -4.5px; } input[type="range"]::-moz-range-thumb { height: 13px; width: 13px; border: none; border-radius: 50%; } """ def build_theme() -> gr.themes.Base: """Bright cheetah-colored theme: orange primary, green secondary, warm neutrals.""" return gr.themes.Soft( primary_hue=gr.themes.colors.orange, secondary_hue=gr.themes.colors.green, neutral_hue=gr.themes.colors.stone, font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"], ).set( slider_color="*primary_500", checkbox_background_color_selected="*secondary_500", button_secondary_background_fill="*secondary_100", block_title_text_color="*primary_600", accordion_text_color="*primary_600", ) class GepardInterface: """Builds the Gradio Blocks app for GEPARD. Args: config: Application config (slider defaults, speaker names, limits). speaker_names: Preset speaker display names for the dropdown. synthesize_fn: Callable with the signature ``(mode, speaker, ref_audio, text, temperature, top_k, max_frames, repetition_penalty, repetition_window) -> (sr, np.ndarray)``. CFG and stop-threshold are not UI knobs; they are driven by config defaults (+ the runner's CFG length gate). Wrapped with the ZeroGPU decorator by the caller. """ def __init__( self, config: AppConfig, speaker_names: List[str], synthesize_fn: Callable, ): self.config = config self.speaker_names = speaker_names self.synthesize_fn = synthesize_fn # ------------------------------------------------------------------ # Layout # ------------------------------------------------------------------ def build(self) -> gr.Blocks: """Assemble and return the Blocks app (call ``.launch()`` on it).""" d = self.config.defaults # Preset examples come from config (examples: section), already # validated against the speaker roster at load time. The table shows # the human-readable label; the label maps back to the speaker key. example_rows = [[ex.label, ex.text] for ex in self.config.examples] label_to_speaker = {ex.label: ex.speaker for ex in self.config.examples} def run_example(example_label: str, example_text: str): """Synthesize a preset example row with the default generation params. Clicking a row runs this once (in a ZeroGPU request context) and Gradio caches the audio, so later clicks just replay it. Returns the audio plus the resolved speaker key so the dropdown stays in sync. """ speaker_name = label_to_speaker.get(example_label, example_label) audio = self.synthesize_fn( MODE_PRESET, speaker_name, None, example_text, d.temperature, d.top_k, d.max_frames, d.repetition_penalty, d.repetition_window, ) return audio, speaker_name with gr.Blocks(theme=build_theme(), css=_CSS, title="GEPARD TTS") as demo: gr.HTML( """
Fast, spotted text-to-speech model with voice cloning