Files
playbook/antigravity-awesome-skills/skills/huggingface-spaces/references/gradio.md
T
2026-07-01 16:02:41 +00:00

172 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gradio for Spaces
Patterns and quirks specific to running Gradio inside a Space. Assumes you're already comfortable with stock Gradio components and `gr.Blocks` / `gr.Interface`.
For deeper Gradio API guidance — components, layouts, event listeners, chatbots, the Gradio 5→6 migration — use the dedicated `huggingface-gradio` skill. Install it with `hf skills add huggingface-gradio` (add `--claude --global` to also install for Claude Code, user-level).
For ZeroGPU-specific decorator + worker semantics, see [`zerogpu.md`](zerogpu.md).
## Themes and layout
- Default theme preference: `gr.themes.Soft()`. Alternatives: no theme, or `gr.themes.Citrus()`. Pick once and don't over-style.
- For apps that don't need full-width, constrain with CSS so they're readable on 4K displays:
```python
CSS = """
#col-container { max-width: 1100px; margin: 0 auto; }
.dark .gradio-container { color: var(--body-text-color); }
"""
with gr.Blocks(theme=gr.themes.Soft(), css=CSS) as demo: ...
```
The dark-mode override fixes a recurring Gradio bug where dark text inherits unset colors.
- Width-cap with `!important` if Gradio 6's own breakpoints fight you — target `main`, `.gradio-container`, and the inner fillable wrapper, otherwise the width caps but goes flush-left.
## Minimal layout most demos converge to
```python
with gr.Row():
prompt = gr.Textbox(show_label=False, placeholder="…", container=False, scale=4)
run = gr.Button("Run", variant="primary", scale=1)
output = gr.Image(...)
with gr.Accordion("Advanced settings", open=False):
...
```
`container=False` on the inline Textbox removes the default outer border for a tighter look.
## `gr.Markdown` for the intro
Be succinct. Title, one-line description, a links section. Don't over-explain how it works — that's what the model card is for.
## `gr.Examples`
The pattern that doesn't trip people up:
```python
gr.Examples(
examples=[
["a cat sitting on a windowsill", 42],
["mountains at sunset, photorealistic", 7],
],
inputs=[prompt, seed],
outputs=output,
fn=generate,
cache_examples=True,
cache_mode="lazy",
)
```
Why these flags:
- `cache_examples=True` makes example clicks instant (no re-inference per visitor).
- **`cache_mode="lazy"`** — caches on first user click for each example. **Required on ZeroGPU** (Gradio's default on ZeroGPU is already lazy via `GRADIO_CACHE_MODE=lazy`). Eager would pre-run every example at app startup, but ZeroGPU has no GPU attached at startup — it'd fail and burn the creator's daily quota.
- `cache_examples=True` silently disables `run_on_click` / `run_examples_on_click`. If your app relies on click-only behavior, set `cache_examples=False`.
The cache key is the **example row's file path**, not a content hash. Regenerating an asset in place serves the stale cached output forever. If you replace example files, bump a `cache_version` marker or wipe `.gradio/cached_examples/<id>/`.
Hot-reload (rung 1) does **not** rebuild the cache. A `cache_version` bump needs a real commit + restart.
## Streaming and generators
`gr.Interface(fn=...)` and `.click(fn=...)` both accept generator functions. Each `yield` pushes a new value:
```python
@spaces.GPU(duration=120)
def generate(prompt):
yield gr.update(value=None, label="Starting…")
for k in range(num_steps):
yield gr.update(value=preview(k), label=f"Step {k+1}/{num_steps}")
yield gr.update(value=final, label="Done")
```
Use `gr.update(label=...)` for status narration — feels like a status line without a separate component.
**Caveat**: `gr.Progress(track_tqdm=True)` and `yield` partial outputs fight each other — pick one streaming mechanism.
## Useful UX bits
- `progress=gr.Progress(track_tqdm=True)` in the GPU function gives a free tqdm-driven progress bar.
- Pair a "Randomize seed" checkbox with the seed input, and write the actually-used seed back so users can re-run deterministically:
```python
randomize = gr.Checkbox(label="Randomize seed", value=True)
seed = gr.Number(label="Seed", value=0, precision=0)
@spaces.GPU
def gen(..., seed, randomize_seed):
if randomize_seed:
seed = random.randint(0, 2**31 - 1)
seed = int(seed)
yield ..., gr.update(value=seed) # write back into the Seed input
...
run.click(gen, inputs=[..., seed, randomize], outputs=[..., seed])
```
## Custom HTML components
Stock Gradio components cover 95% of cases. When they don't, `gr.HTML(...)` lets you build contextual custom UI without leaving the Gradio Space (no need to switch to Docker).
Guide: https://www.gradio.app/guides/custom-HTML-components
Example with a 3D camera-angle picker that makes sense in context: https://huggingface.co/spaces/multimodalart/qwen-image-multiple-angles-3d-camera
Don't reach for this if a stock component covers the need.
## Custom frontends — `gr.Server`
For fully custom frontends with their own HTML/JS, while keeping Gradio's queue + GPU scheduling:
```python
from gradio import Server
from fastapi.responses import HTMLResponse
app = Server(title="my-app")
@spaces.GPU(duration=60)
def _run_gpu(prompt): return inference(prompt)
@app.api(name="generate", concurrency_limit=1, time_limit=180)
def generate(prompt: str) -> str:
return _run_gpu(prompt) # @app.api wraps, doesn't stack with @spaces.GPU
@app.get("/", response_class=HTMLResponse)
async def homepage():
return open("index.html").read()
demo = app # HF runtime expects `demo`
if __name__ == "__main__":
demo.launch(ssr_mode=False)
```
Required for the custom `/` route to actually serve:
```bash
hf spaces variables add <ns>/<name> --env GRADIO_SSR_MODE=false
```
`launch(ssr_mode=False)` is ignored on HF — must be the env var.
Valid `@app.api` kwargs: `name`, `description`, `concurrency_limit`, `concurrency_id`, `queue`, `batch`, `max_batch_size`, `api_visibility`, `time_limit`, `stream_every`.
**Don't stack `@spaces.GPU` and `@app.api`** on the same function — silently breaks request flow. Keep them on separate functions.
**Two-ceiling coordination**: both `@spaces.GPU(duration=N)` and `@app.api(time_limit=M)` apply; the lower wins. Set `time_limit` to your max duration across modes — a too-low `time_limit` kills the request even if GPU duration would have allowed it.
Hot-reload (rung 1 in [`debugging.md`](debugging.md)) does **not** work with `gr.Server` — always `hf upload` or commit.
Reference Space: https://huggingface.co/spaces/huggingface-projects/rf-detr-realtime-webcam
## Slow-startup Gradio (big-model Spaces)
For Spaces that take 1020 min to load weights:
- Set `startup_duration_timeout: 1h` in README frontmatter (default 30 min).
- Disable SSR: `hf spaces variables add <id> --env GRADIO_SSR_MODE=false`. Otherwise the SSR health check times out before the app finishes loading.
## Don't
- `gr.Button(text="X")` — `text=` was removed; use `gr.Button("X")`.
- `gr.Button(type="button")` — drop the kwarg.
- `.click(..., _js=share_js)` — renamed to `js=`.
- `.style(height=...)` — removed in gradio 4+.
- `gr.ImageMask(brush_color=...)` — kwarg removed.
- `demo.launch(mcp_server=True)` on gradio 4.x — only valid on 5+.