Spaces:
Runtime error
Runtime error
| from __future__ import annotations | |
| import dataclasses | |
| import inspect | |
| import json | |
| import re | |
| import shutil | |
| import textwrap | |
| from pathlib import Path | |
| from typing import Literal | |
| from huggingface_hub import snapshot_download | |
| import gradio | |
| def _in_test_dir(): | |
| """Check if the current working directory ends with gradio/js/preview/test.""" | |
| return Path.cwd().parts[-4:] == ("gradio", "js", "preview", "test") | |
| default_demo_code = """ | |
| example = {name}().example_value() | |
| demo = gr.Interface( | |
| lambda x:x, | |
| {name}(), # interactive version of your component | |
| {name}(), # static version of your component | |
| # examples=[[example]], # uncomment this line to view the "example version" of your component | |
| ) | |
| """ | |
| static_only_demo_code = """ | |
| example = {name}().example_value() | |
| with gr.Blocks() as demo: | |
| with gr.Row(): | |
| {name}(label="Blank"), # blank component | |
| {name}(value=example, label="Populated"), # populated component | |
| """ | |
| layout_demo_code = """ | |
| with gr.Blocks() as demo: | |
| with {name}(): | |
| gr.Textbox(value="foo", interactive=True) | |
| gr.Number(value=10, interactive=True) | |
| """ | |
| fallback_code = """ | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Change the value (keep it JSON) and the front-end will update automatically.") | |
| {name}(value={{"message": "Hello from Gradio!"}}, label="Static") | |
| """ | |
| PATTERN_RE = r"gradio-template-\w+" | |
| PATTERN = "gradio-template-{template}" | |
| class ComponentFiles: | |
| template: str | |
| demo_code: str = default_demo_code | |
| python_file_name: str = "" | |
| js_dir: str = "" | |
| def __post_init__(self): | |
| self.js_dir = self.js_dir or self.template.lower() | |
| self.python_file_name = self.python_file_name or f"{self.template.lower()}.py" | |
| OVERRIDES = { | |
| "AnnotatedImage": ComponentFiles( | |
| template="AnnotatedImage", | |
| python_file_name="annotated_image.py", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "HighlightedText": ComponentFiles( | |
| template="HighlightedText", | |
| python_file_name="highlighted_text.py", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "Chatbot": ComponentFiles(template="Chatbot", demo_code=static_only_demo_code), | |
| "Gallery": ComponentFiles(template="Gallery", demo_code=static_only_demo_code), | |
| "HTML": ComponentFiles(template="HTML", demo_code=static_only_demo_code), | |
| "Label": ComponentFiles(template="Label", demo_code=static_only_demo_code), | |
| "Markdown": ComponentFiles(template="Markdown", demo_code=static_only_demo_code), | |
| "Fallback": ComponentFiles(template="Fallback", demo_code=fallback_code), | |
| "Plot": ComponentFiles(template="Plot", demo_code=static_only_demo_code), | |
| "BarPlot": ComponentFiles( | |
| template="BarPlot", | |
| python_file_name="native_plot.py", | |
| js_dir="plot", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "ClearButton": ComponentFiles( | |
| template="ClearButton", | |
| python_file_name="clear_button.py", | |
| js_dir="button", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "ColorPicker": ComponentFiles( | |
| template="ColorPicker", python_file_name="color_picker.py" | |
| ), | |
| "DuplicateButton": ComponentFiles( | |
| template="DuplicateButton", | |
| python_file_name="duplicate_button.py", | |
| js_dir="button", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "FileExplorer": ComponentFiles( | |
| template="FileExplorer", | |
| python_file_name="file_explorer.py", | |
| js_dir="fileexplorer", | |
| demo_code=textwrap.dedent( | |
| """ | |
| import os | |
| with gr.Blocks() as demo: | |
| {name}(value=os.path.dirname(__file__).split(os.sep)) | |
| """ | |
| ), | |
| ), | |
| "LinePlot": ComponentFiles( | |
| template="LinePlot", | |
| python_file_name="native_plot.py", | |
| js_dir="plot", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "LogoutButton": ComponentFiles( | |
| template="LogoutButton", | |
| python_file_name="logout_button.py", | |
| js_dir="button", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "LoginButton": ComponentFiles( | |
| template="LoginButton", | |
| python_file_name="login_button.py", | |
| js_dir="button", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "ScatterPlot": ComponentFiles( | |
| template="ScatterPlot", | |
| python_file_name="native_plot.py", | |
| js_dir="plot", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "UploadButton": ComponentFiles( | |
| template="UploadButton", | |
| python_file_name="upload_button.py", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "JSON": ComponentFiles( | |
| template="JSON", | |
| python_file_name="json_component.py", | |
| demo_code=static_only_demo_code, | |
| ), | |
| "Row": ComponentFiles( | |
| template="Row", | |
| demo_code=layout_demo_code, | |
| ), | |
| "Column": ComponentFiles( | |
| template="Column", | |
| demo_code=layout_demo_code, | |
| ), | |
| "Tabs": ComponentFiles( | |
| template="Tabs", | |
| demo_code=textwrap.dedent( | |
| """ | |
| with gr.Blocks() as demo: | |
| with {name}(): | |
| with gr.Tab("Tab 1"): | |
| gr.Textbox(value="foo", interactive=True) | |
| with gr.Tab("Tab 2"): | |
| gr.Number(value=10, interactive=True) | |
| """ | |
| ), | |
| ), | |
| "Group": ComponentFiles( | |
| template="Group", | |
| demo_code=layout_demo_code, | |
| ), | |
| "Accordion": ComponentFiles( | |
| template="Accordion", | |
| demo_code=textwrap.dedent( | |
| """ | |
| with gr.Blocks() as demo: | |
| with {name}(label="Accordion"): | |
| gr.Textbox(value="foo", interactive=True) | |
| gr.Number(value=10, interactive=True) | |
| """ | |
| ), | |
| ), | |
| "Model3D": ComponentFiles( | |
| template="Model3D", | |
| js_dir="model3D", | |
| demo_code=textwrap.dedent( | |
| """ | |
| with gr.Blocks() as demo: | |
| {name}() | |
| """ | |
| ), | |
| ), | |
| "ImageEditor": ComponentFiles( | |
| template="ImageEditor", | |
| python_file_name="image_editor.py", | |
| js_dir="imageeditor", | |
| ), | |
| "MultimodalTextbox": ComponentFiles( | |
| template="MultimodalTextbox", | |
| python_file_name="multimodal_textbox.py", | |
| js_dir="multimodaltextbox", | |
| ), | |
| "DownloadButton": ComponentFiles( | |
| template="DownloadButton", | |
| python_file_name="download_button.py", | |
| js_dir="downloadbutton", | |
| ), | |
| "Walkthrough": ComponentFiles( | |
| template="Walkthrough", | |
| js_dir="tabs", | |
| demo_code=textwrap.dedent( | |
| """ | |
| with gr.Blocks() as demo: | |
| with {name}(): | |
| with gr.Tab("Tab 1"): | |
| gr.Textbox(value="foo", interactive=True) | |
| with gr.Tab("Tab 2"): | |
| gr.Number(value=10, interactive=True) | |
| """ | |
| ), | |
| ), | |
| "Step": ComponentFiles( | |
| template="Step", | |
| js_dir="tabitem", | |
| python_file_name="walkthrough.py", | |
| demo_code=textwrap.dedent( | |
| """ | |
| with gr.Blocks() as demo: | |
| with {name}(): | |
| gr.Textbox(value="foo", interactive=True) | |
| """ | |
| ), | |
| ), | |
| } | |
| def _get_component_code(template: str | None) -> ComponentFiles: | |
| template = template or "Fallback" | |
| if template in OVERRIDES: | |
| return OVERRIDES[template] | |
| else: | |
| return ComponentFiles( | |
| python_file_name=f"{template.lower()}.py", | |
| js_dir=template.lower(), | |
| template=template, | |
| ) | |
| def _get_js_dependency_version(name: str, local_js_dir: Path) -> str: | |
| package_json = json.loads( | |
| Path(local_js_dir / name.split("/")[1] / "package.json").read_text() | |
| ) | |
| return package_json["version"] | |
| def copy_svelte_to_deps(package_json: dict): | |
| svelte_version = package_json.get("peerDependencies", {}).get("svelte", "latest") | |
| package_json["dependencies"]["svelte"] = svelte_version | |
| return package_json | |
| def _modify_js_deps( | |
| package_json: dict, | |
| key: Literal["dependencies", "devDependencies"], | |
| gradio_dir: Path, | |
| ): | |
| for dep in package_json.get(key, []): | |
| # if curent working directory is the gradio repo, use the local version of the dependency' | |
| if not _in_test_dir() and dep.startswith("@gradio/"): | |
| package_json[key][dep] = _get_js_dependency_version( | |
| dep, gradio_dir / "_frontend_code" / gradio.__version__ | |
| ) | |
| return package_json | |
| def delete_contents(directory: str | Path) -> None: | |
| """Delete all contents of a directory, but not the directory itself.""" | |
| path = Path(directory) | |
| for child in path.glob("*"): | |
| if child.is_file(): | |
| child.unlink() | |
| elif child.is_dir(): | |
| shutil.rmtree(child) | |
| def _download_from_hub(destination: Path): | |
| version = gradio.__version__ | |
| snapshot_download( | |
| repo_id="gradio/frontend", | |
| allow_patterns=f"{version}/**", | |
| local_dir=destination, | |
| repo_type="dataset", | |
| ) | |
| def _create_frontend( | |
| name: str, # noqa: ARG001 | |
| component: ComponentFiles, | |
| directory: Path, | |
| package_name: str, | |
| ): | |
| frontend = directory / "frontend" | |
| frontend.mkdir(exist_ok=True) | |
| p = Path(inspect.getfile(gradio)).parent | |
| component_source = p / "_frontend_code" | |
| if not component_source.exists(): | |
| component_source.mkdir(exist_ok=True) | |
| _download_from_hub(component_source) | |
| def ignore(_src, names): | |
| ignored = [] | |
| for n in names: | |
| if ( | |
| n.startswith("CHANGELOG") | |
| or n.startswith("README.md") | |
| or ".test." in n | |
| or ".stories." in n | |
| or ".spec." in n | |
| ): | |
| ignored.append(n) | |
| return ignored | |
| # Replace once we figure out bug with svelte-package | |
| shutil.copytree( | |
| str(p / "_frontend_code" / gradio.__version__ / component.js_dir), | |
| frontend, | |
| dirs_exist_ok=True, | |
| ignore=ignore, | |
| ) | |
| source_package_json = json.loads(Path(frontend / "package.json").read_text()) | |
| source_package_json["name"] = package_name | |
| source_package_json = _modify_js_deps(source_package_json, "dependencies", p) | |
| source_package_json = _modify_js_deps(source_package_json, "devDependencies", p) | |
| source_package_json = copy_svelte_to_deps(source_package_json) | |
| (frontend / "package.json").write_text(json.dumps(source_package_json, indent=2)) | |
| shutil.copy( | |
| str(Path(__file__).parent / "files" / "gradio.config.js"), | |
| frontend / "gradio.config.js", | |
| ) | |
| shutil.copy( | |
| str(Path(__file__).parent / "files" / "tsconfig.json"), | |
| frontend / "tsconfig.json", | |
| ) | |
| def _replace_old_class_name(old_class_name: str, new_class_name: str, content: str): | |
| pattern = rf"(?<=\b)(?<!\bimport\s)(?<!\.){re.escape(old_class_name)}(?=\b)" | |
| return re.sub(pattern, new_class_name, content) | |
| def _strip_document_lines(content: str): | |
| return "\n".join( | |
| [line for line in content.split("\n") if not line.startswith("@document(")] | |
| ) | |
| def _create_backend( | |
| name: str, component: ComponentFiles, directory: Path, package_name: str | |
| ): | |
| def find_template_in_list(template, list_to_search): | |
| for item in list_to_search: | |
| if template.lower() == item.lower(): | |
| return item | |
| return None | |
| lists_to_search = [ | |
| (gradio.components.__all__, "components"), | |
| (gradio.layouts.__all__, "layouts"), | |
| (gradio._simple_templates.__all__, "_simple_templates"), # type: ignore | |
| ] | |
| correct_cased_template = None | |
| module = None | |
| for list_, module_name in lists_to_search: | |
| correct_cased_template = find_template_in_list(component.template, list_) | |
| if correct_cased_template: | |
| module = module_name | |
| break | |
| if not correct_cased_template: | |
| raise ValueError( | |
| f"Cannot find {component.template} in gradio.components, gradio.layouts, or gradio._simple_templates. " | |
| "Please pass in a valid component name via the --template option. It must match the name of the python class." | |
| ) | |
| if not module: | |
| raise ValueError("Module not found") | |
| # These README contents are used to install the component but they are overwritten later | |
| readme_contents = textwrap.dedent( | |
| """ | |
| # {package_name} | |
| A Custom Gradio component. | |
| ## Example usage | |
| ```python | |
| import gradio as gr | |
| from {package_name} import {name} | |
| ``` | |
| """ | |
| ).format(package_name=package_name, name=name) | |
| (directory / "README.md").write_text(readme_contents) | |
| backend = directory / "backend" / package_name | |
| backend.mkdir(exist_ok=True, parents=True) | |
| gitignore = Path(__file__).parent / "files" / "gitignore" | |
| gitignore_contents = gitignore.read_text() | |
| gitignore_dest = directory / ".gitignore" | |
| gitignore_dest.write_text(gitignore_contents) | |
| pyproject = Path(__file__).parent / "files" / "pyproject_.toml" | |
| pyproject_contents = pyproject.read_text() | |
| pyproject_dest = directory / "pyproject.toml" | |
| pyproject_contents = pyproject_contents.replace("<<name>>", package_name).replace( | |
| "<<template>>", PATTERN.format(template=correct_cased_template) | |
| ) | |
| pyproject_dest.write_text(pyproject_contents) | |
| demo_dir = directory / "demo" | |
| demo_dir.mkdir(exist_ok=True, parents=True) | |
| (demo_dir / "app.py").write_text( | |
| f""" | |
| import gradio as gr | |
| from {package_name} import {name} | |
| {component.demo_code.format(name=name)} | |
| if __name__ == "__main__": | |
| demo.launch() | |
| """ | |
| ) | |
| (demo_dir / "__init__.py").touch() | |
| init = backend / "__init__.py" | |
| init.write_text( | |
| f""" | |
| from .{name.lower()} import {name} | |
| __all__ = ['{name}'] | |
| """ | |
| ) | |
| p = Path(inspect.getfile(gradio)).parent | |
| python_file = backend / f"{name.lower()}.py" | |
| shutil.copy( | |
| str(p / module / component.python_file_name), | |
| str(python_file), | |
| ) | |
| source_pyi_file = p / module / component.python_file_name.replace(".py", ".pyi") | |
| pyi_file = backend / f"{name.lower()}.pyi" | |
| if source_pyi_file.exists(): | |
| shutil.copy(str(source_pyi_file), str(pyi_file)) | |
| content = python_file.read_text() | |
| content = _replace_old_class_name(correct_cased_template, name, content) | |
| content = _strip_document_lines(content) | |
| python_file.write_text(content) | |
| if pyi_file.exists(): | |
| pyi_content = pyi_file.read_text() | |
| pyi_content = _replace_old_class_name(correct_cased_template, name, content) | |
| pyi_content = _strip_document_lines(pyi_content) | |
| pyi_file.write_text(pyi_content) | |