mirror of
https://github.com/gradio-app/gradio.git
synced 2025-03-31 12:20:26 +08:00
Custom Component Guides (#6189)
* Add code * add changeset * Undo * add changeset * Undo * Delete old * more undo * More undo * more undo * Fix link * Remove tip * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
This commit is contained in:
parent
2ba14b284f
commit
345ddd888e
5
.changeset/gentle-hounds-invite.md
Normal file
5
.changeset/gentle-hounds-invite.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"website": patch
|
||||
---
|
||||
|
||||
feat:Custom Component Guides
|
119
guides/05_custom-components/01_five-minute-guide.md
Normal file
119
guides/05_custom-components/01_five-minute-guide.md
Normal file
@ -0,0 +1,119 @@
|
||||
# Custom Components in 5 minutes
|
||||
|
||||
Gradio 4.0 introduces Custom Components -- the ability for developers to create their own custom components and use them in Gradio apps.
|
||||
You can publish your components as Python packages so that other users can use them as well.
|
||||
Users will be able to use all of Gradio's existing functions, such as `gr.Blocks`, `gr.Interface`, API usage, themes, etc. with Custom Components.
|
||||
This guide will cover how to get started making custom components.
|
||||
|
||||
## Installation
|
||||
|
||||
You will need to have:
|
||||
|
||||
* [Python 3.8+](https://www.python.org/downloads/)
|
||||
* [Node.js v16.14+](https://nodejs.dev/en/download/package-manager/)
|
||||
* [npm 9+](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
|
||||
* [gradio 4.0](https://pypi.org/project/gradio/)
|
||||
|
||||
## The Workflow
|
||||
|
||||
The Custom Components workflow consists of 4 steps: create, dev, build, and publish.
|
||||
|
||||
1. create: creates a template for you to start developing a custom component.
|
||||
2. dev: launches a development server with a sample app & hot reloading allowing you to easily develop your custom component
|
||||
3. build: builds a python package containing to your custom component's Python and JavaScript code -- this makes things official!
|
||||
4. publish: uploads your package to [PyPi](https://pypi.org/) and/or a sample app to [HuggingFace Spaces](https://hf.co/spaces).
|
||||
|
||||
Each of these steps is done via the Custom Component CLI. You can invoke it with `gradio cc` or `gradio component`
|
||||
|
||||
Tip: Run `gradio cc --help` to get a help menu of all available commands. You can also append `--help` to any command name to bring up a help page for that command, e.g. `gradio cc create --help`.
|
||||
|
||||
## 1. create
|
||||
|
||||
Bootstrap a new template by running the following in any working directory:
|
||||
|
||||
```bash
|
||||
gradio cc create MyComponent --template SimpleTextbox
|
||||
```
|
||||
|
||||
Instead of `MyComponent`, give your component any name.
|
||||
|
||||
Instead of `SimpleTextbox`, you can use any Gradio component as a template. `SimpleTextbox` is actually a special component that a stripped-down version of the `Textbox` component that makes it particularly useful when creating your first custom component.
|
||||
Some other components that are good if you are starting out: `SimpleDropdown` or `File`.
|
||||
|
||||
Tip: Run `gradio cc show` to get a list of available component templates.
|
||||
|
||||
The `create` command will:
|
||||
|
||||
1. Create a directory with your component's name in lowercase with the following structure:
|
||||
```directory
|
||||
- backend/ <- The python code for your custom component
|
||||
- frontend/ <- The javascript code for your custom component
|
||||
- demo/ <- A sample app using your custom component. Modify this to develop your component!
|
||||
- pyproject.toml <- Used to build the package and specify package metadata.
|
||||
```
|
||||
|
||||
2. Install the component in development mode
|
||||
|
||||
Each of the directories will have the code you need to get started developing!
|
||||
|
||||
## 2. dev
|
||||
|
||||
Once you have created your new component, you can start a development server by `entering the directory` and running
|
||||
|
||||
```bash
|
||||
gradio cc dev
|
||||
```
|
||||
|
||||
You'll see several lines that are printed to the console.
|
||||
The most important one is the one that says:
|
||||
|
||||
> Frontend Server (Go here): http://localhost:7861/
|
||||
|
||||
The port number might be different for you.
|
||||
Click on that link to launch the demo app in hot reload mode.
|
||||
Now, you can start making changes to the backend and frontend you'll see the results reflected live in the sample app!
|
||||
We'll go through a real example in a later guide.
|
||||
|
||||
Tip: You don't have to run dev mode from your custom component directory. The first argument to `dev` mode is the path to the directory. By default it uses the current directory.
|
||||
|
||||
## 3. build
|
||||
|
||||
Once you are satisfied with your custom component's implementation, you can `build` it to use it outside of the development server.
|
||||
|
||||
From your component directory, run:
|
||||
|
||||
```bash
|
||||
gradio cc build
|
||||
```
|
||||
|
||||
This will create a `tar.gz` and `.whl` file in a `dist/` subdirectory.
|
||||
If you or anyone installs that `.whl` file (`pip install <path-to-whl>`) they will be able to use your custom component in any gradio app!
|
||||
|
||||
## 4. publish
|
||||
|
||||
Right now, your package is only available on a `.whl` file on your computer.
|
||||
You can share that file with the world with the `publish` command!
|
||||
|
||||
Simply run the following command from your component directory:
|
||||
|
||||
```bash
|
||||
gradio cc publish
|
||||
```
|
||||
|
||||
This will guide you through the following process:
|
||||
|
||||
1. Upload your distribution files to PyPi. This is optional. If you decide to upload to PyPi, you will need a PyPI username and password. You can get one [here](https://pypi.org/account/register/).
|
||||
2. Upload a demo of your component to hugging face spaces. This is also optional.
|
||||
|
||||
|
||||
Here is an example of what publishing looks like:
|
||||
|
||||
<video autoplay muted loop>
|
||||
<source src="https://gradio-builds.s3.amazonaws.com/assets/text_with_attachments_publish.mov" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
Now that you know the high-level workflow of creating custom components, you can go in depth in the next guides!
|
||||
|
126
guides/05_custom-components/02_key-component-concepts.md
Normal file
126
guides/05_custom-components/02_key-component-concepts.md
Normal file
@ -0,0 +1,126 @@
|
||||
# Gradio Components: The Key Concepts
|
||||
|
||||
In this section, we discuss a few important concepts when it comes to components in Gradio.
|
||||
It's important to understand these concepts when developing your own component.
|
||||
Otherwise, your component may behave very different to other Gradio components!
|
||||
|
||||
Tip: You can skip this section if you are familiar with the internals of the Gradio library, such as each component's preprocess and postprocess methods.
|
||||
|
||||
## Interactive vs Static
|
||||
|
||||
Every component in Gradio comes in a `static` variant, and most come in an `interactive` version as well.
|
||||
The `static` version is used when a component is displaying a value, and the user can **NOT** change that value by interacting with it.
|
||||
The `interactive` version is used when the user is able to change the value by interacting with the Gradio UI.
|
||||
|
||||
Let's see some examples:
|
||||
|
||||
```python
|
||||
import gradio as gr
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
gr.Textbox(value="Hello", interactive=True)
|
||||
gr.Textbox(value="Hello", interactive=False)
|
||||
|
||||
demo.launch()
|
||||
|
||||
```
|
||||
This will display two textboxes.
|
||||
The only difference: you'll be able to edit the value of the Gradio component on top, and you won't be able to edit the variant on the bottom (i.e. the textbox will be disabled).
|
||||
|
||||
Perhaps a more interesting example is with the `Image` component:
|
||||
|
||||
```python
|
||||
import gradio as gr
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
gr.Image(interactive=True)
|
||||
gr.Image(interactive=False)
|
||||
|
||||
demo.launch()
|
||||
```
|
||||
|
||||
The interactive version of the component is much more complex -- you can upload and edit images, draw sketches, etc. -- while the static version does not do any of that.
|
||||
|
||||
Not every component has an interactive version.
|
||||
For example, the `gr.AnnotatedImage` only appears as a static version since there's no way to interactively change the value of the annotations or the image.
|
||||
|
||||
### What you need to remember
|
||||
|
||||
* Gradio will use the interactive version (if available) of a component if that component is used as the **input** to any event; otherwise, the static version will be used.
|
||||
|
||||
* When you design custom components, you **must** accept the boolean interactive keyword in the constructor of your Python class. In the frontend, accept the `interactive` property, a `bool` which represents whether the component is `static` or `interactive`.
|
||||
|
||||
## The value and how it is preprocessed/postprocessed
|
||||
|
||||
The most important attribute of a component is its `value`.
|
||||
Every component has a `value`.
|
||||
The value that is typically set by the user in the frontend (if the component is interactive) or displayed to the user (if it is static).
|
||||
It is also this value that is sent to the backend function when a user triggers an event, or returned by the user's function e.g. at the end of a prediction.
|
||||
|
||||
So this value is passed around quite a bit, but sometimes the format of the value needs to change between the frontend and backend.
|
||||
Take a look at this example:
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import gradio as gr
|
||||
|
||||
def sepia(input_img):
|
||||
sepia_filter = np.array([
|
||||
[0.393, 0.769, 0.189],
|
||||
[0.349, 0.686, 0.168],
|
||||
[0.272, 0.534, 0.131]
|
||||
])
|
||||
sepia_img = input_img.dot(sepia_filter.T)
|
||||
sepia_img /= sepia_img.max()
|
||||
return sepia_img
|
||||
|
||||
demo = gr.Interface(sepia, gr.Image(shape=(200, 200)), "image")
|
||||
demo.launch()
|
||||
```
|
||||
|
||||
This will create a Gradio app which has an `Image` component as the input and the output.
|
||||
In the frontend, the Image component will actually **upload** the file to the server and send the **filepath** but this is converted to a `numpy` array before it is sent to a user's function.
|
||||
Conversely, when the user returns a `numpy` array from their function, the numpy array is converted to a file so that it can be sent to the frontend and displayed by the `Image` component.
|
||||
|
||||
Tip: By default, the `Image` component sends numpy arrays to the python function because it is a common choice for machine learning engineers, though the Image component also supports other formats using the `type` parameter. Read the `Image` docs [here](https://www.gradio.app/docs/image) to learn more.
|
||||
|
||||
Each component does two conversions:
|
||||
|
||||
1. `preprocess`: Converts the `value` from the format sent by the frontend to the format expected by the python function. This usually involves going from a web-friendly **JSON** structure to a **python-native** data structure, like a `numpy` array or `PIL` image. The `Audio`, `Image` components are good examples of `preprocess` methods.
|
||||
|
||||
2. `postprocess`: Converts the value returned by the python function to the format expected by the frontend. This usually involves going from a **python-native** data-structure, like a `PIL` image to a **JSON** structure.
|
||||
|
||||
### What you need to remember
|
||||
|
||||
* Every component must implement `preprocess` and `postprocess` methods. In the rare event that no conversion needs to happen, simply return the value as-is. `Textbox` and `Number` are examples of this.
|
||||
|
||||
* As a component author, **YOU** control the format of the data displayed in the frontend as well as the format of the data someone using your component will receive. Think of an ergonomic data-structure a **python** developer will find intuitive, and control the conversion from a **Web-friendly JSON** data structure (and vice-versa) with `preprocess` and `postprocess.`
|
||||
|
||||
## The "Example Version" of a Component
|
||||
|
||||
Gradio apps support providing example inputs -- and these are very useful in helping users get started using your Gradio app.
|
||||
In `gr.Interface`, you can provide examples using the `examples` keyword, and in `Blocks`, you can provide examples using the special `gr.Examples` component.
|
||||
|
||||
At the bottom of this screenshot, we show a miniature example image of a cheetah that, when clicked, will populate the same image in the input Image component:
|
||||
|
||||

|
||||
|
||||
|
||||
To enable the example view, you must have the following two files in the top of the `frontend` directory:
|
||||
|
||||
* `Example.svelte`: this corresponds to the "example version" of your component
|
||||
* `Index.svelte`: this corresponds to the "regular version"
|
||||
|
||||
In the backend, you typically don't need to do anything unless you would like to modify the user-provided `value` of the examples to something else before it is sent to the frontend.
|
||||
You can do this in the `as_example` method of the component.
|
||||
|
||||
The `Example.svelte` and `as_example` methods will be covered in greater depth in the dedicated [frontend](./frontend) and [backend](./backend) guides.
|
||||
|
||||
### What you need to remember
|
||||
|
||||
* If you expect your component to be used as input, it is important to define an "Example" view.
|
||||
* If you don't, Gradio will use a default one but it won't be as informative as it can be!
|
||||
|
||||
## Conclusion
|
||||
|
||||
Now that you know the most important pieces to remember about Gradio components, you can start to design and build your own!
|
99
guides/05_custom-components/03_configuration.md
Normal file
99
guides/05_custom-components/03_configuration.md
Normal file
@ -0,0 +1,99 @@
|
||||
# Configuring Your Custom Component
|
||||
|
||||
The custom components workflow focuses on [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) to reduce the number of decisions you as a developer need to make when developing your custom component.
|
||||
That being said, you can still configure some aspects of the custom component package and directory.
|
||||
This guide will cover how.
|
||||
|
||||
## The Package Name
|
||||
|
||||
By default, all custom component packages are called `gradio_<component-name>` where `component-name` is the name of the component's python class in lowercase.
|
||||
|
||||
As an example, let's walkthrough changing the name of a component from `gradio_mytextbox` to `supertextbox`.
|
||||
|
||||
1. Modify the `name` in the `pyproject.toml` file.
|
||||
|
||||
```bash
|
||||
[project]
|
||||
name = "supertextbox"
|
||||
```
|
||||
|
||||
2. Change all occurrences of `gradio_<component-name>` in `pyproject.toml` to `<component-name>`
|
||||
|
||||
```bash
|
||||
[tool.hatch.build]
|
||||
artifacts = ["/backend/supertextbox/templates", "*.pyi"]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["/backend/supertextbox"]
|
||||
```
|
||||
|
||||
3. Rename the `gradio_<component-name>` directory in `backend/` to `<component-name>`
|
||||
|
||||
```bash
|
||||
mv backend/gradio_mytextbox backend/supertextbox
|
||||
```
|
||||
|
||||
|
||||
Tip: Remember to change the import statement in `demo/app.py`!
|
||||
|
||||
## Top Level Python Exports
|
||||
|
||||
By default, only the custom component python class is a top level export.
|
||||
This means that when users type `from gradio_<component-name> import ...`, the only class that will be available is the custom component class.
|
||||
To add more classes as top level exports, modify the `__all__` property in `__init__.py`
|
||||
|
||||
```python
|
||||
from .mytextbox import MyTextbox
|
||||
from .mytextbox import AdditionalClass, additional_function
|
||||
|
||||
__all__ = ['MyTextbox', 'AdditionalClass', 'additional_function']
|
||||
```
|
||||
|
||||
## Python Dependencies
|
||||
|
||||
You can add python dependencies by modifying the `dependencies` key in `pyproject.toml`
|
||||
|
||||
```bash
|
||||
dependencies = ["gradio", "numpy", "PIL"]
|
||||
```
|
||||
|
||||
Tip: Remember to run `gradio cc install` when you add dependencies!
|
||||
|
||||
## Javascript Dependencies
|
||||
|
||||
You can add JavaScript dependencies by modifying the `"dependencies"` key in `frontend/package.json`
|
||||
|
||||
```json
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "0.2.0-beta.4",
|
||||
"@gradio/statustracker": "0.3.0-beta.6",
|
||||
"@gradio/utils": "0.2.0-beta.4",
|
||||
"your-npm-package": "<version>"
|
||||
}
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
By default, the CLI will place the Python code in `backend` and the JavaScript code in `frontend`.
|
||||
It is not recommended to change this structure since it makes it easy for a potential contributor to look at your source code and know where everything is.
|
||||
However, if you did want to this is what you would have to do:
|
||||
|
||||
1. Place the Python code in the subdirectory of your choosing. Remember to modify the `[tool.hatch.build]` `[tool.hatch.build.targets.wheel]` in the `pyproject.toml` to match!
|
||||
|
||||
2. Place the JavaScript code in the subdirectory of your choosing.
|
||||
|
||||
2. Add the `FRONTEND_DIR` property on the component python class. It must be the relative path from the file where the class is defined to the location of the JavaScript directory.
|
||||
|
||||
```python
|
||||
class SuperTextbox(Component):
|
||||
FRONTEND_DIR = "../../frontend/"
|
||||
```
|
||||
|
||||
The JavaScript and Python directories must be under the same common directory!
|
||||
|
||||
## Conclusion
|
||||
|
||||
|
||||
Sticking to the defaults will make it easy for others to understand and contribute to your custom component.
|
||||
After all, the beauty of open source is that anyone can help improve your code!
|
||||
But if you ever need to deviate from the defaults, you know how!
|
226
guides/05_custom-components/04_backend.md
Normal file
226
guides/05_custom-components/04_backend.md
Normal file
@ -0,0 +1,226 @@
|
||||
# The Backend 🐍
|
||||
|
||||
This guide will cover everything you need to know to implement your custom component's backend processing.
|
||||
|
||||
## Which Class to Inherit From
|
||||
|
||||
All components inherit from one of three classes `Component`, `FormComponent`, or `BlockContext`.
|
||||
You need to inherit from one so that your component behaves like all other gradio components.
|
||||
When you start from a template with `gradio cc create --template`, you don't need to worry about which one to choose since the template uses the correct one.
|
||||
For completeness, and in the event that you need to make your own component from scratch, we explain what each class is for.
|
||||
|
||||
* `FormComponent`: Use this when you want your component to be grouped together in the same `Form` layout with other `FormComponents`. The `Slider`, `Textbox`, and `Number` components are all `FormComponents`.
|
||||
* `BlockContext`: Use this when you want to place other components "inside" your component. This enabled `with MyComponent() as component:` syntax.
|
||||
* `Component`: Use this for all other cases.
|
||||
|
||||
Tip: If your component supports streaming output, inherit from the `StreamingOutput` class.
|
||||
|
||||
Tip: If you inherit from `BlockContext`, you also need to set the metaclass to be `ComponentMeta`. See example below.
|
||||
|
||||
```python
|
||||
from gradio.blocks import BlockContext
|
||||
from gradio.component_meta import ComponentMeta
|
||||
|
||||
set_documentation_group("layout")
|
||||
|
||||
|
||||
@document()
|
||||
class Row(BlockContext, metaclass=ComponentMeta):
|
||||
pass
|
||||
```
|
||||
|
||||
## The methods you need to implement
|
||||
|
||||
When you inherit from any of these classes, the following methods must be implemented.
|
||||
Otherwise the Python interpreter will raise an error when you instantiate your component!
|
||||
|
||||
### `preprocess` and `postprocess`
|
||||
|
||||
Explained in the [Key Concepts](./key-component-concepts#the-value-and-how-it-is-preprocessed-postprocessed) guide.
|
||||
They handle the conversion from the data sent by the frontend to the format expected by the python function.
|
||||
|
||||
```python
|
||||
@abstractmethod
|
||||
def preprocess(self, x: Any) -> Any:
|
||||
"""
|
||||
Convert from the web-friendly (typically JSON) value in the frontend to the format expected by the python function.
|
||||
"""
|
||||
return x
|
||||
|
||||
@abstractmethod
|
||||
def postprocess(self, y):
|
||||
"""
|
||||
Convert from the data returned by the python function to the web-friendly (typically JSON) value expected by the frontend.
|
||||
"""
|
||||
return y
|
||||
```
|
||||
|
||||
### `as_example`
|
||||
|
||||
Takes in the original Python value and returns the modified value that should be displayed in the examples preview in the app.
|
||||
Let's look at the following example from the `Radio` component.
|
||||
|
||||
```python
|
||||
def as_example(self, input_data):
|
||||
return next((c[0] for c in self.choices if c[1] == input_data), None)
|
||||
```
|
||||
|
||||
Since `self.choices` is a list of tuples corresponding to (`display_name`, `value`), this converts the value that a user provides to the display value (or if the value is not present in `self.choices`, it is converted to `None`).
|
||||
|
||||
```python
|
||||
@abstractmethod
|
||||
def as_example(self, y):
|
||||
pass
|
||||
```
|
||||
|
||||
### `api_info`
|
||||
|
||||
A JSON-schema representation of the value that the `preprocess` expects.
|
||||
This powers api usage via the gradio clients.
|
||||
You do **not** need to implement this yourself if you components specifies a `data_model`.
|
||||
The `data_model` in the following section.
|
||||
|
||||
```python
|
||||
@abstractmethod
|
||||
def api_info(self) -> dict[str, list[str]]:
|
||||
"""
|
||||
A JSON-schema representation of the value that the `preprocess` expects and the `postprocess` returns.
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
### `example_inputs`
|
||||
|
||||
The example inputs for this component displayed in the `View API` page.
|
||||
Must be JSON-serializable.
|
||||
If your component expects a file, it is best to use a publicly accessible URL.
|
||||
|
||||
```python
|
||||
@abstractmethod
|
||||
def example_inputs(self) -> Any:
|
||||
"""
|
||||
The example inputs for this component for API usage. Must be JSON-serializable.
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
### `flag`
|
||||
|
||||
Write the component's value to a format that can be stored in the `csv` or `json` file used for flagging.
|
||||
You do **not** need to implement this yourself if you components specifies a `data_model`.
|
||||
The `data_model` in the following section.
|
||||
|
||||
```python
|
||||
@abstractmethod
|
||||
def flag(self, x: Any | GradioDataModel, flag_dir: str | Path = "") -> str:
|
||||
pass
|
||||
```
|
||||
|
||||
### `read_from_flag`
|
||||
Convert from the format stored in the `csv` or `json` file used for flagging to the component's python `value`.
|
||||
You do **not** need to implement this yourself if you components specifies a `data_model`.
|
||||
The `data_model` in the following section.
|
||||
|
||||
```python
|
||||
@abstractmethod
|
||||
def read_from_flag(
|
||||
self,
|
||||
x: Any,
|
||||
flag_dir: str | Path | None = None,
|
||||
) -> GradioDataModel | Any:
|
||||
"""
|
||||
Convert the data from the csv or jsonl file into the component state.
|
||||
"""
|
||||
return x
|
||||
```
|
||||
|
||||
## The `data_model`
|
||||
|
||||
The `data_model` is how you define the expected data format your component's value will be stored in the frontend.
|
||||
It specifies the data format your `preprocess` method expects and the format the `postprocess` method returns.
|
||||
It is not necessary to define a `data_model` for your component but it greatly simplifies the process of creating a custom component.
|
||||
If you define a custom component you only need to implement three methods - `preprocess`, `postprocess`, and `example_inputs`!
|
||||
|
||||
You define a `data_model` by defining a [pydantic model](https://docs.pydantic.dev/latest/concepts/models/#basic-model-usage) that inherits from either `GradioModel` or `GradioRootModel`.
|
||||
|
||||
This is best explained with an example. Let's look at the core `Video` component, which stores the video data as a JSON object with two keys `video` and `subtitles` which point to separate files.
|
||||
|
||||
```python
|
||||
from gradio.data_classes import FileData, GradioModel
|
||||
|
||||
class VideoData(GradioModel):
|
||||
video: FileData
|
||||
subtitles: Optional[FileData] = None
|
||||
|
||||
class Video(Component):
|
||||
data_model = VideoData
|
||||
```
|
||||
|
||||
By adding these four lines of code, your component automatically implements the methods needed for API usage, the flagging methods, and example caching methods!
|
||||
It also has the added benefit of self-documenting your code.
|
||||
Anyone who reads your component code will know exactly the data it expects.
|
||||
|
||||
Tip: If your component expects files to be uploaded from the frontend, your must use the `FileData` model! It will be explained in the following section.
|
||||
|
||||
Tip: Read the pydantic docs [here](https://docs.pydantic.dev/latest/concepts/models/#basic-model-usage).
|
||||
|
||||
The difference between a `GradioModel` and a `GradioRootModel` is that the `RootModel` will not serialize the data to a dictionary.
|
||||
For example, the `Names` model will serialize the data to `{'names': ['freddy', 'pete']}` whereas the `NamesRoot` model will serialize it to `['freddy', 'pete']`.
|
||||
|
||||
```python
|
||||
from typing import List
|
||||
|
||||
class Names(GradioModel):
|
||||
names: List[str]
|
||||
|
||||
class NamesRoot(GradioRootModel):
|
||||
root: List[str]
|
||||
```
|
||||
|
||||
Even if your component does not expect a "complex" JSON data structure it can be beneficial to define a `GradioRootModel` so that you don't have to worry about implementing the API and flagging methods.
|
||||
|
||||
Tip: Use classes from the Python typing library to type your models. e.g. `List` instead of `list`.
|
||||
|
||||
## Handling Files
|
||||
|
||||
If your component expects uploaded files as input, or returns saved files to the frontend, you **MUST** use the `FileData` to type the files in your `data_model`.
|
||||
|
||||
When you use the `FileData`:
|
||||
|
||||
* Gradio knows that it should allow serving this file to the frontend. Gradio automatically blocks requests to serve arbitrary files in the computer running the server.
|
||||
|
||||
* Gradio will automatically place the file in a cache so that duplicate copies of the file don't get saved.
|
||||
|
||||
* The client libraries will automatically know that they should upload input files prior to sending the request. They will also automatically download files.
|
||||
|
||||
If you do not use the `FileData`, your component will not work as expected!
|
||||
|
||||
|
||||
## Adding Event Triggers To Your Component
|
||||
|
||||
The events triggers for your component are defined in the `EVENTS` class attribute.
|
||||
This is a list that contains the string names of the events.
|
||||
Adding an event to this list will automatically add a method with that same name to your component!
|
||||
|
||||
You can import the `Events` enum from `gradio.events` to access commonly used events in the core gradio components.
|
||||
|
||||
For example, the following code will define `text_submit`, `file_upload` and `change` methods in the `MyComponent` class.
|
||||
|
||||
```python
|
||||
from gradio.events import Events
|
||||
from gradio.components import FormComponent
|
||||
|
||||
class MyComponent(FormComponent):
|
||||
|
||||
EVENTS = [
|
||||
"text_submit",
|
||||
"file_upload",
|
||||
Events.change
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
Tip: Don't forget to also handle these events in the JavaScript code!
|
||||
|
||||
## Conclusion
|
||||
|
274
guides/05_custom-components/05_frontend.md
Normal file
274
guides/05_custom-components/05_frontend.md
Normal file
@ -0,0 +1,274 @@
|
||||
# The Frontend 🌐⭐️
|
||||
|
||||
This guide will cover everything you need to know to implement your custom component's frontend.
|
||||
|
||||
Tip: Gradio components use Svelte. Writing Svelte is fun! If you're not familiar with it, we recommend checking out their interactive [guide](https://learn.svelte.dev/tutorial/welcome-to-svelte).
|
||||
|
||||
## The directory structure
|
||||
|
||||
The frontend code should have, at minimum, three files:
|
||||
|
||||
* `Index.svelte`: This is the main export and where your component's layout and logic should live.
|
||||
* `Example.svelte`: This is where the example view of the component is defined.
|
||||
|
||||
Feel free to add additional files and subdirectories.
|
||||
If you want to export any additional modules, remember to modify the `package.json` file
|
||||
|
||||
```json
|
||||
"exports": {
|
||||
".": "./Index.svelte",
|
||||
"./example": "./Example.svelte",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
```
|
||||
|
||||
## The Index.svelte file
|
||||
|
||||
Your component should expose the following props that will be passed down from the parent Gradio application.
|
||||
|
||||
```typescript
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
|
||||
export let gradio: Gradio<{
|
||||
event_1: never;
|
||||
event_2: never;
|
||||
}>;
|
||||
|
||||
export let elem_id = "";
|
||||
export let elem_classes: string[] = [];
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus | undefined = undefined;
|
||||
export let mode: "static" | "interactive";
|
||||
```
|
||||
|
||||
* `elem_id` and `elem_classes` allow Gradio app developers to target your component with custom CSS and JavaScript from the Python `Blocks` class.
|
||||
|
||||
* `scale` and `min_width` allow Gradio app developers to control how much space your component takes up in the UI.
|
||||
|
||||
* `loading_status` is used to display a loading status over the component when it is the output of an event.
|
||||
|
||||
* `mode` is how the parent Gradio app tells your component whether the `interactive` or `static` version should be displayed.
|
||||
|
||||
* `gradio`: The `gradio` object is created by the parent Gradio app. It stores some application-level configuration that will be useful in your component, like internationalization. You must use it to dispatch events from your component.
|
||||
|
||||
A minimal `Index.svelte` file would look like:
|
||||
|
||||
```typescript
|
||||
<script lang="ts">
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
|
||||
export let gradio: Gradio<{
|
||||
event_1: never;
|
||||
event_2: never;
|
||||
}>;
|
||||
|
||||
export let value = "";
|
||||
export let elem_id = "";
|
||||
export let elem_classes: string[] = [];
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus | undefined = undefined;
|
||||
export let mode: "static" | "interactive";
|
||||
</script>
|
||||
|
||||
<Block
|
||||
visible={true}
|
||||
{elem_id}
|
||||
{elem_classes}
|
||||
{scale}
|
||||
{min_width}
|
||||
allow_overflow={false}
|
||||
padding={true}
|
||||
>
|
||||
{#if loading_status}
|
||||
<StatusTracker
|
||||
autoscroll={gradio.autoscroll}
|
||||
i18n={gradio.i18n}
|
||||
{...loading_status}
|
||||
/>
|
||||
{/if}
|
||||
<p>{value}</p>
|
||||
</Block>
|
||||
```
|
||||
|
||||
## The Example.svelte file
|
||||
|
||||
The `Example.svelte` file should expose the following props:
|
||||
|
||||
```typescript
|
||||
export let value: string;
|
||||
export let type: "gallery" | "table";
|
||||
export let selected = false;
|
||||
export let samples_dir: string;
|
||||
export let index: number;
|
||||
```
|
||||
|
||||
* `value`: The example value that should be displayed.
|
||||
|
||||
* `type`: This is a variable that can be either `"gallery"` or `"table"` depending on how the examples are displayed. The `"gallery"` form is used when the examples correspond to a single input component, while the `"table"` form is used when a user has multiple input components, and the examples need to populate all of them.
|
||||
|
||||
* `selected`: You can also adjust how the examples are displayed if a user "selects" a particular example by using the selected variable.
|
||||
|
||||
* `samples_dir`: A URL to prepend to `value` if your example is fetching a file from the server
|
||||
|
||||
* `index`: The current index of the selected value.
|
||||
|
||||
* Any additional props your "non-example" component takes!
|
||||
|
||||
This is the `Example.svelte` file for the code `Radio` component:
|
||||
|
||||
```typescript
|
||||
<script lang="ts">
|
||||
export let value: string;
|
||||
export let type: "gallery" | "table";
|
||||
export let selected = false;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class:table={type === "table"}
|
||||
class:gallery={type === "gallery"}
|
||||
class:selected
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.gallery {
|
||||
padding: var(--size-1) var(--size-2);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## Handling Files
|
||||
|
||||
If your component deals with files, these files **should** be uploaded to the backend server.
|
||||
The `@gradio/client` npm package provides the `upload`, `prepare_files`, and `normalise_file` utility functions to help you do this.
|
||||
|
||||
The `prepare_files` function will convert the browser's `File` datatype to gradio's internal `FileData` type.
|
||||
You should use the `FileData` data in your component to keep track of uploaded files.
|
||||
|
||||
The `upload` function will upload an array of `FileData` values to the server.
|
||||
|
||||
The `normalise_file` function will generate the correct URL for your component to fetch the file from and set it to the `data` property of the `FileData.`
|
||||
|
||||
|
||||
Tip: Be sure you call `normalise_file` whenever your files are updated!
|
||||
|
||||
|
||||
Here's an example of loading files from an `<input>` element when its value changes.
|
||||
|
||||
|
||||
```typescript
|
||||
<script lang="ts">
|
||||
|
||||
import { upload, prepare_files, normalise_file, type FileData } from "@gradio/client";
|
||||
export let root;
|
||||
export let value;
|
||||
let uploaded_files;
|
||||
|
||||
$: value: normalise_file(uploaded_files, root)
|
||||
|
||||
async function handle_upload(file_data: FileData[]): Promise<void> {
|
||||
await tick();
|
||||
uploaded_files = await upload(file_data, root);
|
||||
}
|
||||
|
||||
async function loadFiles(files: FileList): Promise<void> {
|
||||
let _files: File[] = Array.from(files);
|
||||
if (!files.length) {
|
||||
return;
|
||||
}
|
||||
if (file_count === "single") {
|
||||
_files = [files[0]];
|
||||
}
|
||||
let file_data = await prepare_files(_files);
|
||||
await handle_upload(file_data);
|
||||
}
|
||||
|
||||
async function loadFilesFromUpload(e: Event): Promise<void> {
|
||||
const target = e.target;
|
||||
|
||||
if (!target.files) return;
|
||||
await loadFiles(target.files);
|
||||
}
|
||||
</script>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
on:change={loadFilesFromUpload}
|
||||
multiple={true}
|
||||
/>
|
||||
```
|
||||
|
||||
The component exposes a prop named `root`.
|
||||
This is passed down by the parent gradio app and it represents the base url that the files will be uploaded to and fetched from.
|
||||
|
||||
For WASM support, you should get the upload function from the `Context` and pass that as the third parameter of the `upload` function.
|
||||
|
||||
```typescript
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
const upload_fn = getContext<typeof upload_files>("upload_files");
|
||||
|
||||
async function handle_upload(file_data: FileData[]): Promise<void> {
|
||||
await tick();
|
||||
await upload(file_data, root, upload_fn);
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Leveraging Existing Gradio Components
|
||||
|
||||
Most of Gradio's frontend components are published on [npm](https://www.npmjs.com/), the javascript package repository.
|
||||
This means that you can use them to save yourself time while incorporating common patterns in your component, like uploading files.
|
||||
For example, the `@gradio/upload` package has `Upload` and `ModifyUpload` components for properly uploading files to the Gradio server.
|
||||
Here is how you can use them to create a user interface to upload and display PDF files.
|
||||
|
||||
```typescript
|
||||
<script>
|
||||
import { type FileData, normalise_file, Upload, ModifyUpload } from "@gradio/upload";
|
||||
import { Empty, UploadText, BlockLabel } from "@gradio/atoms";
|
||||
</script>
|
||||
|
||||
<BlockLabel Icon={File} label={label || "PDF"} />
|
||||
{#if value === null && interactive}
|
||||
<Upload
|
||||
filetype="application/pdf"
|
||||
on:load={handle_load}
|
||||
{root}
|
||||
>
|
||||
<UploadText type="file" i18n={gradio.i18n} />
|
||||
</Upload>
|
||||
{:else if value !== null}
|
||||
{#if interactive}
|
||||
<ModifyUpload i18n={gradio.i18n} on:clear={handle_clear}/>
|
||||
{/if}
|
||||
<iframe title={value.orig_name || "PDF"} src={value.data} height="{height}px" width="100%"></iframe>
|
||||
{:else}
|
||||
<Empty size="large"> <File/> </Empty>
|
||||
{/if}
|
||||
```
|
||||
|
||||
You can also combine existing Gradio components to create entirely unique experiences.
|
||||
Like rendering a gallery of chatbot conversations.
|
||||
The possibilities are endless, please read the documentation on our javascript packages [here](https://gradio.app/main/docs/js).
|
||||
We'll be adding more packages and documentation over the coming weeks!
|
||||
|
||||
## Matching Gradio Core's Design System
|
||||
|
||||
You can explore our component library via Storybook. You'll be able to interact with our components and see them in their various states.
|
||||
|
||||
For those interested in design customization, we provide the CSS variables consisting of our color palette, radii, spacing, and the icons we use - so you can easily match up your custom component with the style of our core components. This Storybook will be regularly updated with any new additions or changes.
|
||||
|
||||
[Storybook Link](https://gradio.app/main/docs/js/storybook)
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
You now how to create delightful frontends for your components!
|
||||
|
41
guides/05_custom-components/06_frequently-asked-questions.md
Normal file
41
guides/05_custom-components/06_frequently-asked-questions.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
## What do I need to install before using Custom Components?
|
||||
Before using Custom Components, make sure you have Python 3.8+, Node.js v16.14+, npm 9+, and Gradio 4.0 installed.
|
||||
|
||||
## What templates can I use to create my custom component?
|
||||
Run `gradio cc show` to see the list of built-in templates.
|
||||
You can also start off from other's custom components!
|
||||
Simply `git clone` their repository and make your modifications.
|
||||
|
||||
## What is the development server?
|
||||
When you run `gradio cc dev`, a development server will load and run a Gradio app of your choosing.
|
||||
This is like when you run `python <app-file>.py`, however the `gradio` command will hot reload so you can instantly see your changes.
|
||||
|
||||
## The development server didn't work for me
|
||||
Make sure you have your package installed along with any dependencies you have added by running `gradio cc install`.
|
||||
Make sure there aren't any syntax or import errors in the Python or JavaScript code.
|
||||
|
||||
## Do I need to host my custom component on HuggingFace Spaces?
|
||||
You can develop and build your custom component without hosting or connecting to HuggingFace.
|
||||
If you would like to share your component with the gradio community, it is recommended to publish your package to PyPi and host a demo on HuggingFace so that anyone can install it or try it out.
|
||||
|
||||
## What methods are mandatory for implementing a custom component in Gradio?
|
||||
|
||||
You must implement the `preprocess`, `postprocess`, `as_example`, `api_info`, `example_inputs`, `flag`, and `read_from_flag` methods. Read more in the [backend guide](./backend).
|
||||
|
||||
## What is the purpose of a `data_model` in Gradio custom components?
|
||||
|
||||
A `data_model` defines the expected data format for your component, simplifying the component development process and self-documenting your code. It streamlines API usage and example caching.
|
||||
|
||||
## Why is it important to use `FileData` for components dealing with file uploads?
|
||||
|
||||
Utilizing `FileData` is crucial for components that expect file uploads. It ensures secure file handling, automatic caching, and streamlined client library functionality.
|
||||
|
||||
## How can I add event triggers to my custom Gradio component?
|
||||
|
||||
You can define event triggers in the `EVENTS` class attribute by listing the desired event names, which automatically adds corresponding methods to your component.
|
||||
|
||||
## Can I implement a custom Gradio component without defining a `data_model`?
|
||||
|
||||
Yes, it is possible to create custom components without a `data_model`, but you are going to have to manually implement `api_info`, `example_inputs`, `flag`, and `read_from_flag` methods.
|
@ -85,6 +85,12 @@ for guide_folder in guide_folders:
|
||||
guide_content,
|
||||
)
|
||||
|
||||
guide_content = re.sub(
|
||||
r"(\n\nTip: )(.*?)(?=\n\n|$)",
|
||||
lambda x: f"<p class='tip'>💡 {x.group(2)}</p>",
|
||||
guide_content,
|
||||
)
|
||||
|
||||
content_no_html = guide_content
|
||||
|
||||
guide_content = "\n".join(
|
||||
@ -94,6 +100,7 @@ for guide_folder in guide_folders:
|
||||
if not any(line.startswith(label) for label in metadata_labels)
|
||||
]
|
||||
)
|
||||
|
||||
guide_content = re.sub(
|
||||
r"```([a-z]+)\n",
|
||||
lambda x: f"<div class='codeblock'><pre><code class='lang-{x.group(1)}'>",
|
||||
|
@ -193,3 +193,16 @@ code.language-bash {
|
||||
.selected-demo-window {
|
||||
@apply rounded-b-md border-2 border-gray-100 -mt-0.5;
|
||||
}
|
||||
|
||||
|
||||
.tip {
|
||||
@apply bg-gradient-to-br from-orange-50 to-white border-orange-50 border-l-2 border-l-orange-300 text-orange-700 p-4 px-6;
|
||||
}
|
||||
|
||||
.tip code {
|
||||
@apply text-orange-700
|
||||
}
|
||||
|
||||
.tip a {
|
||||
@apply text-orange-700
|
||||
}
|
@ -7,6 +7,7 @@ import "prismjs/components/prism-python";
|
||||
import "prismjs/components/prism-bash";
|
||||
import "prismjs/components/prism-json";
|
||||
import "prismjs/components/prism-typescript";
|
||||
import "prismjs/components/prism-javascript";
|
||||
import "prismjs/components/prism-csv";
|
||||
import "prismjs/components/prism-markup";
|
||||
|
||||
@ -28,6 +29,9 @@ const langs = {
|
||||
shell: "bash",
|
||||
json: "json",
|
||||
typescript: "typescript",
|
||||
ts: "typescript",
|
||||
javascript: "javascript",
|
||||
js: "javascript",
|
||||
directory: "json"
|
||||
};
|
||||
|
||||
|
@ -12,6 +12,8 @@ import "prismjs/components/prism-json";
|
||||
import "prismjs/components/prism-typescript";
|
||||
import "prismjs/components/prism-csv";
|
||||
import "prismjs/components/prism-markup";
|
||||
import "prismjs/components/prism-javascript";
|
||||
|
||||
|
||||
const langs = {
|
||||
python: "python",
|
||||
@ -21,7 +23,10 @@ const langs = {
|
||||
html: "html",
|
||||
shell: "bash",
|
||||
json: "json",
|
||||
javascript: "javascript",
|
||||
js: "javascript",
|
||||
typescript: "typescript",
|
||||
ts: "typescript",
|
||||
directory: "json"
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user