A newer version of the Gradio SDK is available:
5.9.1
如何创建一个新组件
简介
本指南旨在说明如何添加一个新组件,你可以在 Gradio 应用程序中使用该组件。该指南将通过代码片段逐步展示如何添加ColorPicker组件。
先决条件
确保您已经按照CONTRIBUTING.md指南设置了本地开发环境(包括客户端和服务器端)。
以下是在 Gradio 上创建新组件的步骤:
1. 创建一个新的 Python 类并导入它
首先要做的是在components.py文件中创建一个新的类。这个 Python 类应该继承自一系列的基本组件,并且应该根据要添加的组件的类型(例如输入、输出或静态组件)将其放置在文件中的正确部分。 一般来说,建议参考现有的组件(例如TextBox),将其代码复制为骨架,然后根据实际情况进行修改。
让我们来看一下添加到components.py文件中的 ColorPicker 组件的类:
@document()
class ColorPicker(Changeable, Submittable, IOComponent):
"""
创建一个颜色选择器,用户可以选择颜色作为字符串输入。
预处理:将选择的颜色值作为{str}传递给函数。
后处理:期望从函数中返回一个{str},并将颜色选择器的值设置为它。
示例格式:表示颜色的十六进制{str},例如红色的"#ff0000"。
演示:color_picker,color_generator
"""
def __init__(
self,
value: str = None,
*,
label: Optional[str] = None,
show_label: bool = True,
interactive: Optional[bool] = None,
visible: bool = True,
elem_id: Optional[str] = None,
**kwargs,
):
"""
Parameters:
"""
Parameters:
value: default text to provide in color picker.
label: component name in interface.
show_label: if True, will display label.
interactive: if True, will be rendered as an editable color picker; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
visible: If False, component will be hidden.
elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
"""
self.value = self.postprocess(value)
self.cleared_value = "#000000"
self.test_input = value
IOComponent.__init__(
self,
label=label,
show_label=show_label,
interactive=interactive,
visible=visible,
elem_id=elem_id,
**kwargs,
)
def get_config(self):
return {
"value": self.value,
**IOComponent.get_config(self),
}
@staticmethod
def update(
value: Optional[Any] = None,
label: Optional[str] = None,
show_label: Optional[bool] = None,
visible: Optional[bool] = None,
interactive: Optional[bool] = None,
):
return {
"value": value,
"label": label,
"show_label": show_label,
"visible": visible,
"interactive": interactive,
"__type__": "update",
}
# 输入功能
def preprocess(self, x: str | None) -> Any:
"""
Any preprocessing needed to be performed on function input.
Parameters:
x (str): text
Returns:
(str): text
"""
if x is None:
return None
else:
return str(x)
def preprocess_example(self, x: str | None) -> Any:
"""
在传递给主函数之前,对示例进行任何预处理。
"""
if x is None:
return None
else:
return str(x)
# 输出功能
def postprocess(self, y: str | None):
"""
Any postprocessing needed to be performed on function output.
Parameters:
y (str | None): text
Returns:
(str | None): text
"""
if y is None:
return None
else:
return str(y)
def deserialize(self, x):
"""
将从调用接口的序列化输出(例如base64表示)转换为输出的人类可读版本(图像的路径等)
"""
return x
一旦定义完,就需要在__init__模块类中导入新类,以使其可见。
from gradio.components import (
...
ColorPicker,
...
)
1.1 为 Python 类编写单元测试
在开发新组件时,还应为其编写一套单元测试。这些测试应该放在gradio/test/test_components.py文件中。同样,如上所述,参考其他组件的测试(例如Textbox)并添加尽可能多的单元测试,以测试新组件的所有不同方面和功能。例如,为 ColorPicker 组件添加了以下测试:
class TestColorPicker(unittest.TestCase):
def test_component_functions(self):
"""
Preprocess, postprocess, serialize, save_flagged, restore_flagged, tokenize, get_config
"""
color_picker_input = gr.ColorPicker()
self.assertEqual(color_picker_input.preprocess("#000000"), "#000000")
self.assertEqual(color_picker_input.preprocess_example("#000000"), "#000000")
self.assertEqual(color_picker_input.postprocess(None), None)
self.assertEqual(color_picker_input.postprocess("#FFFFFF"), "#FFFFFF")
self.assertEqual(color_picker_input.serialize("#000000", True), "#000000")
color_picker_input.interpretation_replacement = "unknown"
self.assertEqual(
color_picker_input.get_config(),
{
"value": None,
"show_label": True,
"label": None,
"style": {},
"elem_id": None,
"visible": True,
"interactive": None,
"name": "colorpicker",
},
)
def test_in_interface_as_input(self):
"""
接口、处理、解释
"""
iface = gr.Interface(lambda x: x, "colorpicker", "colorpicker")
self.assertEqual(iface.process(["#000000"]), ["#000000"])
def test_in_interface_as_output(self):
"""
接口、处理
"""
iface = gr.Interface(lambda x: x, "colorpicker", gr.ColorPicker())
self.assertEqual(iface.process(["#000000"]), ["#000000"])
def test_static(self):
"""
后处理
"""
component = gr.ColorPicker("#000000")
self.assertEqual(component.get_config().get("value"), "#000000")
2. 创建一个新的 Svelte 组件
让我们来看看创建新组件的前端并将其与其 Python 代码映射起来的步骤:
- 在 js 文件夹 中创建一个新的 UI-side Svelte 组件,并确定要放置在什么地方。选项包括:创建新组件的包(如果与现有组件完全不同),或将新组件添加到现有包中,例如 form 包。例如,ColorPicker 组件被包含在 form 包中,因为它与已存在的组件相似。
- 在您将 Svelte 组件放置的包的 src 文件夹中创建一个带有适当名称的文件,注意:名称必须以大写字母开头。这是“核心”组件,是没有 Gradio 特定功能了解的通用组件。最初,将任何文本 /HTML 添加到此文件,以便组件呈现任何内容。ColorPicker 的 Svelte 应用程序代码如下所示:
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { get_styles } from "@gradio/utils";
import { BlockTitle } from "@gradio/atoms";
import type { Styles } from "@gradio/utils";
export let value: string = "#000000";
export let style: Styles = {};
export let label: string;
export let disabled = false;
export let show_label: boolean = true;
$: value;
$: handle_change(value);
const dispatch = createEventDispatcher<{
change: string;
submit: undefined;
}>();
function handle_change(val: string) {
dispatch("change", val);
}
$: ({ styles } = get_styles(style, ["rounded", "border"]));
</script>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block">
<BlockTitle {show_label}>{label}</BlockTitle>
<input
type="color"
class="gr-box-unrounded {classes}"
bind:value
{disabled}
/>
</label>
- 通过执行
export { default as FileName } from "./FileName.svelte"
,在您将 Svelte 组件放置的包的 index.ts 文件中导出此文件。例如,在 index.ts 文件中导出了 ColorPicker 文件,并通过export { default as ColorPicker } from "./ColorPicker.svelte";
执行导出。 - 创建 js/app/src/components 中的 Gradio 特定组件。这是一个 Gradio 包装器,处理库的特定逻辑,将必要的数据传递给核心组件,并附加任何必要的事件监听器。复制另一个组件的文件夹,重新命名并编辑其中的代码,保持结构不变。
在这里,您将拥有三个文件,第一个文件用于 Svelte 应用程序,具体如下所示:
<svelte:options accessors={true} />
<script lang="ts">
import { ColorPicker } from "@gradio/form";
import { Block } from "@gradio/atoms";
import StatusTracker from "../StatusTracker/StatusTracker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
import type { Styles } from "@gradio/utils";
export let label: string = "ColorPicker";
export let elem_id: string = "";
export let visible: boolean = true;
export let value: string;
export let form_position: "first" | "last" | "mid" | "single" = "single";
export let show_label: boolean;
export let style: Styles = {};
export let loading_status: LoadingStatus;
export let interactive: boolean;
</script>
<Block
{visible}
{form_position}
{elem_id}
disable={typeof style.container === "boolean" && !style.container}
>
<StatusTracker {...loading_status} />
<ColorPicker
{style}
bind:value
{label}
{show_label}
on:change
on:submit
disabled={!interactive}
/>
</Block>
第二个文件包含了前端的测试,例如 ColorPicker 组件的测试:
import { test, describe, assert, afterEach } from "vitest";
import { cleanup, render } from "@self/tootils";
import ColorPicker from "./ColorPicker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
const loading_status = {
eta: 0,
queue_position: 1,
status: "complete" as LoadingStatus["status"],
scroll_to_output: false,
visible: true,
fn_index: 0
};
describe("ColorPicker", () => {
afterEach(() => cleanup());
test("renders provided value", () => {
const { getByDisplayValue } = render(ColorPicker, {
loading_status,
show_label: true,
interactive: true,
value: "#000000",
label: "ColorPicker"
});
const item: HTMLInputElement = getByDisplayValue("#000000");
assert.equal(item.value, "#000000");
});
test("changing the color should update the value", async () => {
const { component, getByDisplayValue } = render(ColorPicker, {
loading_status,
show_label: true,
interactive: true,
value: "#000000",
label: "ColorPicker"
});
const item: HTMLInputElement = getByDisplayValue("#000000");
assert.equal(item.value, "#000000");
await component.$set({
value: "#FFFFFF"
});
assert.equal(component.value, "#FFFFFF");
});
});
The third one is the index.ts file:
export { default as Component } from "./ColorPicker.svelte";
export const modes = ["static", "dynamic"];
directory.ts
文件中添加组件的映射。复制并粘贴任何组件的映射行,并编辑其文本。键名必须是 Python 库中实际组件名称的小写版本。例如,对于 ColorPicker 组件,映射如下所示:
export const component_map = {
...
colorpicker: () => import("./ColorPicker"),
...
}
2.1 为 Svelte 组件编写单元测试
在开发新组件时,您还应该为其编写一套单元测试。测试应该放置在新组件的文件夹中,文件名为 MyAwesomeComponent.test.ts。同样,像上面那样参考其他组件的测试(例如Textbox.test.ts),并添加尽可能多的单元测试,以测试新组件的不同方面和功能。
3. 创建新的演示
最后一步是在gradio/demo 文件夹中创建一个使用新添加的组件的演示。同样,建议参考现有演示。在一个名为 run.py 的文件中编写演示的代码,添加必要的要求和显示应用程序界面的图像。最后添加一个显示其用法的 gif。 您可以查看为 ColorPicker 创建的demo,其中以新组件选择的图标和颜色作为输入,并以选择的颜色着色的相同图标作为输出。
要测试应用程序:
- 在终端上运行
python path/demo/run.py
,它会在地址 http://localhost:7860 启动后端; - 在另一个终端上,运行
pnpm dev
以在 http://localhost:9876 上启动具有热重新加载功能的前端。
结论
在本指南中,我们展示了将新组件添加到 Gradio 是多么简单,逐步介绍了如何添加 ColorPicker 组件。要了解更多细节,可以参考 PR:#1695.