Files
wehub-resource-sync adf0d17497
publish / version_or_publish (push) Waiting to run
storybook-build / changes (push) Waiting to run
storybook-build / :storybook-build (push) Blocked by required conditions
Sync Gradio Skills to Hugging Face / sync-skills (push) Waiting to run
functional / changes (push) Waiting to run
functional / build-frontend (push) Blocked by required conditions
functional / functional-test-SSR=false (push) Blocked by required conditions
functional / functional-reload (push) Blocked by required conditions
functional / functional-test-SSR=true (push) Blocked by required conditions
hygiene / hygiene-test (push) Waiting to run
python / changes (push) Waiting to run
python / build (push) Blocked by required conditions
python / test-ubuntu-latest-flaky (push) Blocked by required conditions
python / test-ubuntu-latest-not-flaky (push) Blocked by required conditions
python / test-windows-latest-flaky (push) Blocked by required conditions
python / test-windows-latest-not-flaky (push) Blocked by required conditions
js / changes (push) Waiting to run
js / js-test (push) Blocked by required conditions
docs-build / changes (push) Waiting to run
docs-build / docs-build (push) Blocked by required conditions
docs-build / website-build (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 13:17:32 +08:00

443 lines
14 KiB
Python

"""Generates .agents/skills/gradio/ from Gradio's docstrings, guides, and demos.
Usage:
python scripts/generate_skill.py # regenerate
python scripts/generate_skill.py --check # CI: fail if output would change
"""
import argparse
import os
import shutil
import sys
import tempfile
DIR = os.path.dirname(__file__)
REPO_ROOT = os.path.abspath(os.path.join(DIR, ".."))
sys.path.insert(0, os.path.join(REPO_ROOT, "client", "python"))
sys.path.insert(0, REPO_ROOT)
from gradio_client.documentation import generate_documentation # noqa: E402
import gradio # noqa: E402, F401
DEMOS_DIR = os.path.join(REPO_ROOT, "demo")
SKILL_DIR = os.path.join(REPO_ROOT, ".agents", "skills", "gradio")
CURATED_GUIDES = [
"quickstart",
"the-interface-class",
"blocks-and-event-listeners",
"controlling-layout",
"more-blocks-features",
"custom-CSS-and-JS",
"streaming-outputs",
"streaming-inputs",
"sharing-your-app",
"custom-HTML-components",
"getting-started-with-the-python-client",
"getting-started-with-the-js-client",
]
IMPORTANT_DEMOS = [
"blocks_essay_simple",
"blocks_flipper",
"blocks_form",
"blocks_hello",
"blocks_layout",
"calculator",
"chatbot_simple",
"chatbot_streaming",
"chatinterface_multimodal",
"custom_css",
"fake_diffusion",
"hello_world",
"image_editor",
"on_listener_decorator",
"render_merge",
"reverse_audio_2",
"sepia_filter",
"sort_records",
"streaming_simple",
"tabbed_interface_lite",
"tax_calculator",
"timer_simple",
"variable_outputs",
"video_identity",
]
KEY_COMPONENTS = [
"Textbox",
"Number",
"Slider",
"Checkbox",
"Dropdown",
"Radio",
"Image",
"Audio",
"Video",
"File",
"Chatbot",
"DataFrame",
"Button",
"Markdown",
"HTML",
]
def load_all_demo_code():
demos = {}
for demo_folder in os.listdir(DEMOS_DIR):
runfile = os.path.join(DEMOS_DIR, demo_folder, "run.py")
if not os.path.exists(runfile):
continue
with open(runfile) as f:
code = f.read()
code = code.replace("# type: ignore", "").replace(
'if __name__ == "__main__":\n demo.launch()', "demo.launch()"
)
demos[demo_folder] = code
return demos
def build_signature(entry):
params = ", ".join(
p["name"]
+ ": "
+ str(p["annotation"])
+ (" = " + str(p["default"]) if "default" in p else "")
for p in entry["parameters"]
)
return f"{entry['name']}({params})"
def organize_docs(raw_docs):
organized = {
"building": {},
"components": {},
"helpers": {},
"modals": {},
"routes": {},
"chatinterface": {},
}
for mode in raw_docs:
for c in raw_docs[mode]:
for p in c.get("parameters", []):
p["annotation"] = str(p["annotation"])
if "default" in p:
p["default"] = str(p["default"])
for fn in c["fns"]:
for p in fn.get("parameters", []):
p["annotation"] = str(p["annotation"])
if "default" in p:
p["default"] = str(p["default"])
if mode == "component":
organized["components"][c["name"]] = c
elif mode == "py-client":
continue
elif mode in organized:
organized[mode][c["name"]] = c
else:
organized["building"][c["name"]] = c
return organized
def build_events_matrix(organized):
component_events = {}
for name, comp in organized["components"].items():
if hasattr(comp.get("class"), "EVENTS"):
events = [fn["name"] for fn in comp["fns"] if fn["name"] in [str(e) for e in comp["class"].EVENTS]]
if events:
component_events[name] = events
return component_events
def generate_examples(all_demos):
lines = ["# Gradio End-to-End Examples\n"]
lines.append("Complete working Gradio apps for reference.\n")
for demo_name in IMPORTANT_DEMOS:
if demo_name not in all_demos:
continue
pretty = demo_name.replace("_", " ").title()
code = all_demos[demo_name].strip()
lines.append(f"## {pretty}\n")
lines.append(f"```python\n{code}\n```\n")
return "\n".join(lines)
def generate_api_signatures(organized):
"""Generate API signatures for commonly used components."""
lines = ["# Component API Signatures\n"]
lines.append("Quick reference for common Gradio component constructors.\n")
for name in KEY_COMPONENTS:
if name not in organized["components"]:
continue
entry = organized["components"][name]
sig = build_signature(entry)
desc = entry.get("description", "").replace("<br>", " ").strip()
lines.append(f"## `{name}`\n")
lines.append(f"```python\n{sig}\n```\n")
if desc:
lines.append(f"{desc}\n")
return "\n".join(lines)
def generate_event_listeners(organized):
"""Generate event listener documentation."""
events_matrix = build_events_matrix(organized)
lines = ["# Event Listeners\n"]
lines.append("Events supported by each component.\n\n")
lines.append("## Event Listener Signature\n")
lines.append("```python")
lines.append("""component.event_name(
fn: Callable | None | Literal["decorator"] = "decorator",
inputs: Component | Sequence[Component] | set[Component] | None = None,
outputs: Component | Sequence[Component] | set[Component] | None = None,
api_name: str | None = None,
api_description: str | None | Literal[False] = None,
scroll_to_output: bool = False,
show_progress: Literal["full", "minimal", "hidden"] = "full",
show_progress_on: Component | Sequence[Component] | None = None,
queue: bool = True,
batch: bool = False,
max_batch_size: int = 4,
preprocess: bool = True,
postprocess: bool = True,
cancels: dict[str, Any] | list[dict[str, Any]] | None = None,
trigger_mode: Literal["once", "multiple", "always_last"] | None = None,
js: str | Literal[True] | None = None,
concurrency_limit: int | None | Literal["default"] = "default",
concurrency_id: str | None = None,
api_visibility: Literal["public", "private", "undocumented"] = "public",
time_limit: int | None = None,
stream_every: float = 0.5,
key: int | str | tuple[int | str, ...] | None = None,
validator: Callable | None = None,
) -> Dependency""")
lines.append("```\n")
lines.append("## Supported Events by Component\n")
for comp, events in sorted(events_matrix.items()):
lines.append(f"- **{comp}**: {', '.join(events)}\n")
return "\n".join(lines)
def generate_skill_md(organized, guide_links):
# Build list of key component names for main doc
key_component_list = []
for name in KEY_COMPONENTS:
if name in organized["components"]:
key_component_list.append(f"- `{name}`")
guide_list = "\n".join(
f"- [{title}](https://www.gradio.app/guides/{slug})"
for title, slug in guide_links
)
skill_md = f"""---
name: gradio
description: Build Gradio web UIs and demos in Python. Use when creating, modifying, debugging, or answering questions about Gradio and its capabilties, components, event listeners, or layouts.
---
# Gradio
Gradio is a Python library for building interactive web UIs and ML demos. This skill covers the core API, patterns, and examples.
## References
- `references/examples.md` - Illustrative examples showcasing the core Gradio API.
- `references/api-signatures.md` - API signatures of commonly used components.
- `references/event-listeners.md` - API signatures of events supported by each component.
## Guides
Detailed guides on specific topics (read these when relevant):
{guide_list}
## Core Patterns
**Interface** (high-level): wraps a function with input/output components.
```python
import gradio as gr
def greet(name):
return f"Hello {{name}}!"
gr.Interface(fn=greet, inputs="text", outputs="text").launch()
```
**Blocks** (low-level): flexible layout with explicit event wiring.
```python
import gradio as gr
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Greeting")
btn = gr.Button("Greet")
btn.click(fn=lambda n: f"Hello {{n}}!", inputs=name, outputs=output)
demo.launch()
```
**ChatInterface**: high-level wrapper for chatbot UIs.
```python
import gradio as gr
def respond(message, history):
return f"You said: {{message}}"
gr.ChatInterface(fn=respond).launch()
```
## Custom HTML Components
If a task requires significant customization of an existing component or a component that doesn't exist in Gradio, you can create one with `gr.HTML`. It supports `html_template` (with `${{}}` JS expressions and `{{{{}}}}` Handlebars syntax), `css_template` for scoped styles, and `js_on_load` for interactivity — where `props.value` updates the component value and `trigger('event_name')` fires Gradio events. For reuse, subclass `gr.HTML` and define `api_info()` for API/MCP support.
See the [full guide](https://www.gradio.app/guides/custom-HTML-components) as well as example in `references/examples.md`
## Server Mode
Use `gr.Server` instead of gr.Blocks when the users requests any of the following:
- Completely custom UI (your own HTML, React, Svelte, etc.) powered by Gradio's backend.
- Full control of FastAPI server (custom GET/POST routes, middleware, dependency injection) alongside Gradio API endpoints
See the [full guide](https://www.gradio.app/guides/server-mode) and example in `references/examples.md`.
If the user's use case can be handled by Gradio's built-in components or customizable HTML components, prefer not to use `gr.Server`.
"""
return skill_md.strip() + "\n"
GUIDE_TITLES = {
"quickstart": "Quickstart",
"the-interface-class": "The Interface Class",
"blocks-and-event-listeners": "Blocks and Event Listeners",
"controlling-layout": "Controlling Layout",
"more-blocks-features": "More Blocks Features",
"custom-CSS-and-JS": "Custom CSS and JS",
"streaming-outputs": "Streaming Outputs",
"streaming-inputs": "Streaming Inputs",
"sharing-your-app": "Sharing Your App",
"custom-HTML-components": "Custom HTML Components",
"getting-started-with-the-python-client": "Getting Started with the Python Client",
"getting-started-with-the-js-client": "Getting Started with the JS Client",
}
def generate_to(output_dir):
raw_docs = generate_documentation()
organized = organize_docs(raw_docs)
all_demos = load_all_demo_code()
os.makedirs(output_dir, exist_ok=True)
guide_links = []
for guide_name in CURATED_GUIDES:
title = GUIDE_TITLES.get(guide_name, guide_name.replace("-", " ").title())
guide_links.append((title, guide_name))
# Create references subdirectory
references_dir = os.path.join(output_dir, "references")
os.makedirs(references_dir, exist_ok=True)
# Generate main skill file
skill_md = generate_skill_md(organized, guide_links)
with open(os.path.join(output_dir, "SKILL.md"), "w") as f:
f.write(skill_md)
# Generate reference files
examples = generate_examples(all_demos)
with open(os.path.join(references_dir, "examples.md"), "w") as f:
f.write(examples)
api_signatures = generate_api_signatures(organized)
with open(os.path.join(references_dir, "api-signatures.md"), "w") as f:
f.write(api_signatures)
event_listeners = generate_event_listeners(organized)
with open(os.path.join(references_dir, "event-listeners.md"), "w") as f:
f.write(event_listeners)
return skill_md
def files_equal(path1, path2):
if not os.path.exists(path1) or not os.path.exists(path2):
return False
with open(path1) as f1, open(path2) as f2:
return f1.read() == f2.read()
def check(output_dir):
tmp = tempfile.mkdtemp()
try:
tmp_skill = os.path.join(tmp, "gradio")
generate_to(tmp_skill)
generated_files = ["SKILL.md", "references/examples.md", "references/api-signatures.md", "references/event-listeners.md"]
stale = []
for fname in generated_files:
existing = os.path.join(output_dir, fname)
fresh = os.path.join(tmp_skill, fname)
if not files_equal(existing, fresh):
stale.append(fname)
if stale:
print("ERROR: Skill files are out of date. Stale files:")
for f in stale:
print(f" {f}")
print("\nRun `python scripts/generate_skill.py` to update.")
sys.exit(1)
print("OK: Skill files are up to date.")
finally:
shutil.rmtree(tmp)
def main():
parser = argparse.ArgumentParser(description="Generate Gradio skill files.")
parser.add_argument(
"--check",
action="store_true",
help="Check if files are up to date (for CI). Exits with code 1 if stale.",
)
args = parser.parse_args()
if args.check:
check(SKILL_DIR)
return
print("Generating Gradio skill...")
if os.path.exists(SKILL_DIR):
shutil.rmtree(SKILL_DIR)
skill_md = generate_to(SKILL_DIR)
skill_lines = len(skill_md.splitlines())
for name in os.listdir(SKILL_DIR):
path = os.path.join(SKILL_DIR, name)
if os.path.isdir(path) and not os.path.islink(path):
count = len(os.listdir(path))
print(f" {name}/: {count} files")
elif os.path.isfile(path):
with open(path) as f:
lines = len(f.readlines())
print(f" {name}: {lines} lines")
print(f"\nDone! Output in {SKILL_DIR}")
if skill_lines > 500:
print(f" WARNING: SKILL.md is {skill_lines} lines (recommended <500)")
if __name__ == "__main__":
main()