文章目录
- 一、Blocks及事件监听器
- 1.1 Blocks结构
- 1.2 事件监听器的类型
- 1.3 多数据流
- 1.4 多输入组件
- 1.5 多输出组件
- 1.6 更新组件配置
- 1.7 添加示例
- 1.8 连续运行事件
- 1.9 持续运行事件
- 1.9.1 `every`参数
- 1.9.2 `load`方法
- 1.9.3 `change`方法
- 1.10 收集事件数据
- 1.11 绑定多个触发器到同一函数
- 二、布局控制
- 2.1 行
- 2.2 列
- 2.3 组件尺寸
- 2.4 选项卡和折叠组件
- 2.5 可见性(Visibility)
- 2.6 可变数量的输出
- 2.7 定义和渲染组件
- 三、Blocks状态
- 四、动态应用程序
- 4.1 动态调整组件数
- 4.1.1 示例一:动态调整文本框
- 4.1.2 示例二:@gr.render触发机制
- 4.2 创建动态事件监听器
- 4.3 组合应用
- 4.3.1 示例一:创建待办事项列表
- 4.3.2 示例二:混音器
- 五、使用CSS和Javascript创建自定义demo
- 5.1 使用CSS进行自定义
- 5.1 使用CSS进行自定义
- 5.1.1 使用Gradio 主题
- 5.1.2 添加自定义 CSS
- 5.1.3 elem_id 和 elem_classes 参数
- 5.1 使用JavaScript 进行自定义
- 5.2.1 通过js参数添加
- 5.2.2 在事件监听器中使用
- 5.2.3 通过head参数添加
- 六、像函数一样使用Gradio Blocks
传送门:
- 《Gradio官方教程一:Gradio生态系统、主要组件及Interface class简介》
- 《Gradio 4.37.1官方教程二:Blocks》
一、Blocks及事件监听器
gradio(GitHub)、Blocks官方教程、Blocks官方文档
1.1 Blocks结构
Blocks是Gradio的一个底层API,完全使用Python编写。与Interface类相比,Blocks提供了更多的灵活性和控制,包括:
- 组件的布局
- 触发函数执行的事件
- 处理更复杂的数据流(比如输入可以触发输出,然后触发下一级的输出)
- 演示分组,如使用标签页。
Automatic1111的stable diffusion Web UI就是用Gradio的Blocks构建的,其基本用法如下:
- 创建一个Blocks对象。
- 使用它作为上下文(使用with语句)。
- 在Blocks上下文中定义布局、组件或事件。
- 最后,调用launch()方法启动演示。
通过这些步骤,你可以创建更加复杂和自定义的Web应用程序。下面是一个简单的示例:
import gradio as gr
def update(name):
return f"Welcome to Gradio, {name}!"
with gr.Blocks() as demo:
gr.Markdown("Start typing below and then click **Run** to see the output.")
with gr.Row():
inp = gr.Textbox(placeholder="What is your name?",label="Name")
out = gr.Textbox(label="Output Box")
btn = gr.Button("Run")
btn.click(fn=update, inputs=inp, outputs=out)
demo.launch()
with gr.Blocks() as demo
:使用with
语句创建了一个名为demo
的Blocks应用程序,所有组件和事件都将在这个上下文中被定义。- 创建了两个文本框组件:name和output。组件与Interface中使用的组件相同。然而,它们不是传递给某个构造函数,而是在with语句块内创建时自动添加到Blocks中。
- 定义按钮组件
btn
btn.click()
:定义按钮点击事件。click()
是事件监听器,定义了应用程序中的数据流。
在上面的示例中,当按钮被点击时,数据流被触发——调用update
函数,name
文本框的内容作为输入,函数返回的结果会显示在output
文本框中。与Interface类似,事件监听器可以接受多个输入或输出。
你也可以通过装饰器的方式定义事件监听器,这样可以跳过 fn
参数,直接分配 inputs
和 outputs
。
import gradio as gr
with gr.Blocks() as demo:
inp = gr.Textbox(placeholder="What is your name?",label="Name")
out = gr.Textbox(label="Output Box")
btn = gr.Button("Run")
@btn.click(inputs=inp, outputs=out)
def update(name):
return f"Welcome to Gradio, {name}!"
demo.launch()
在上述示例中,你可以编辑Textbox name
,但不能编辑Textbox output
。这是因为作为事件监听器的输入组件默认是交互式的,而输出组件则不是交互式的。你也可以使用interactive
关键字参数覆盖默认行为,直接配置组件的交互性。
out = gr.Textbox(label="Output Box", interactive=True)
如果一个Gradio组件既不是输入也不是输出,它的交互性有两种情况(也可以通过interactive
参数来指定):
- 如果有默认值,则直接是显示内容,默认不可交互。
- 如果没有默认值,那么可以交互。
1.2 事件监听器的类型
import gradio as gr
def welcome(name):
return f"Welcome to Gradio, {name}!"
with gr.Blocks() as demo:
gr.Markdown(
"""
# Hello World!
Start typing below to see the output.
""")
inp = gr.Textbox(placeholder="What is your name?")
out = gr.Textbox()
inp.change(welcome, inp, out)
demo.launch()
gr.Markdown()
:Markdown组件,用于显示标题和说明文字。inp.change(welcome, inp, out)
:change()
事件监听器。每当inp
文本框的内容发生变化时,welcome
函数就会被触发。也就是welcome 函数不是通过点击触发,而是通过在文本框中键入 inp 来触发。
不同的组件支持不同的事件监听器。例如,Video 组件支持 play() 事件侦听器,当用户按下播放按钮时触发。各组件的事件侦听器,详见各组件文档。
1.3 多数据流
Blocks app不像Interfaces 那样仅限于单个数据流,比如下面示例中, num1 可以充当 num2 的输入,反之亦然。
import gradio as gr
def increase(num):
return num + 1
with gr.Blocks() as demo:
a = gr.Number(label="a")
b = gr.Number(label="b")
atob = gr.Button("a > b")
btoa = gr.Button("b > a")
atob.click(increase, a, b)
btoa.click(increase, b, a)
demo.launch()
increase
:将输入数字+1并返回atob.click(increase, a, b)
:点击"a > b"
按钮时将a的数值加1并显示在b中。btoa.click(increase, b, a)
:点击"b > a"
按钮时将b的数值加1并显示在a中。
下面是一个更复杂的示例,一个模型(语音到文本模型)的输出被输入到下一个模型(情感分类器)中。
from transformers import pipeline
import gradio as gr
asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h")
classifier = pipeline("text-classification")
def speech_to_text(speech):
text = asr(speech)["text"]
return text
def text_to_sentiment(text):
return classifier(text)[0]["label"]
demo = gr.Blocks()
with demo:
audio_file = gr.Audio(type="filepath")
text = gr.Textbox()
label = gr.Label()
b1 = gr.Button("Recognize Speech")
b2 = gr.Button("Classify Sentiment")
b1.click(speech_to_text, inputs=audio_file, outputs=text)
b2.click(text_to_sentiment, inputs=text, outputs=label)
demo.launch()
1.4 多输入组件
如果希望构建包含多个输入组件的demo,有两种选择方式:
-
作为参数列表:输入以列表形式提供,函数的每个参数对应一个输入组件的值。优点是简单直观,缺点是当输入组件数量增多时,管理参数会变得复杂。
-
作为字典传递:字典的键会对应于每个输入组件,值则是该组件当前的值。因为每个组件及其值都是通过键值对明确表示的,所以当包含大量输入组件时,更易管理。
import gradio as gr
with gr.Blocks() as demo:
a = gr.Number(label="a")
b = gr.Number(label="b")
c = gr.Number(label="sum")
with gr.Row():
add_btn = gr.Button("Add")
sub_btn = gr.Button("Subtract")
# 第一种方式:参数列表传递
def add(num1, num2):
return num1 + num2
add_btn.click(add, inputs=[a, b], outputs=c)
# 第二种方式:字典传递
def sub(data):
return data[a] - data[b]
sub_btn.click(sub, inputs={a, b}, outputs=c)
demo.launch()
1.5 多输出组件
同多输入组件一样,为多个输出组件返回值也有列表和字典两种传递方式。先来看第一种方式:
with gr.Blocks() as demo:
food_box = gr.Number(value=10, label="Food Count") # 食品计数框
status_box = gr.Textbox() # 状态框
def eat(food):
if food > 0:
return food - 1, "full"
else:
return 0, "hungry"
gr.Button("EAT").click(
fn=eat,
inputs=food_box,
outputs=[food_box, status_box]
)
当按钮 "EAT"
被点击时,fn 函数 eat
会被调用,输入是 food_box
的当前值,输出是两个组件:food_box
和 status_box
,输出是按照列表顺序匹配的。当 food >0
时,返回 (food - 1, "full")
;否则返回(0, "hungry")
。
在事件监听器中也可以返回一个字典来更新多个输出组件,而不仅仅是按照顺序返回值列表。通过使用字典返回值,可以有选择地更新某些组件,而跳过其他组件,这在处理复杂条件时特别有用。
import gradio as gr
with gr.Blocks() as demo:
food_box = gr.Number(value=10, label="Food Count")
status_box = gr.Textbox()
def eat(food):
if food > 0:
return {food_box: food - 1, status_box: "full"}
else:
return {status_box: "hungry"}
gr.Button("EAT").click(
fn=eat,
inputs=food_box,
outputs=[food_box, status_box]
)
demo.launch()
当food_box > 0
时,同时更新 food_box
和 status_box
。否则只更新 status_box
,而跳过 food_box
的更新。
使用字典返回值时需要注意:尽管字典返回值允许选择性更新组件,但在事件监听器中仍需指定所有可能的输出组件。
1.6 更新组件配置
Gradio允许通过事件监听器函数来更新组件的配置,例如可见性、行数等,而不仅仅是更新组件的值,这适用于需要根据用户选择或交互来动态调整组件外观和行为的场景。
import gradio as gr
def change_textbox(choice):
if choice == "short":
return gr.Textbox(lines=2, visible=True)
elif choice == "long":
return gr.Textbox(lines=8, visible=True, value="Lorem ipsum dolor sit amet")
else:
return gr.Textbox(visible=False)
with gr.Blocks() as demo:
# 构建单选按钮radio
radio = gr.Radio(
["short", "long", "none"], label="What kind of essay would you like to write?"
)
# text是一个文本框,初始设置为2行,具有交互性并显示复制按钮。
text = gr.Textbox(lines=2, interactive=True, show_copy_button=True)
radio.change(fn=change_textbox, inputs=radio, outputs=text)
demo.launch()
上述代码中,radio
是一个单选按钮,有三个宣泄。text
是一个文本框,初始设置为2行,具有交互性并显示复制按钮。radio.change()
函数中,当 radio
组件的值改变时,会触发 change_textbox
函数,这样当用户选择不同的选项时,会返回新的文本框配置,从而更新 text
组件的属性(通过返回新的组件实例,动态更新组件的配置属性)。
1.7 添加示例
使用 gr.Examples
可以方便地为用户提供示例输入,并在需要时缓存这些示例以提高性能。gr.Examples
使用时,需要提供两个参数:
examples
: 一个嵌套列表,外层列表包含每个示例,内层列表包含每个输入组件的对应输入值。inputs
: 需要被示例填充的输入组件或组件列表。
如果设置 cache_examples=True
来缓存示例,则必须提供额外的两个参数:
outputs
:对应示例输出的组件或组件列表fn
:生成示例输出的函数
当启用
cache_examples
时,Gradio会预先计算示例输入对应的输出并将其存储。这意味着在用户点击示例时,不需要实时运行函数来生成输出,从而提高了响应速度。为此,需要知道哪个函数fn
会生成输出,以及这些输出应该填充到哪些outputs
组件中。
import gradio as gr
def calculator(num1, operation, num2):
if operation == "add":
return num1 + num2
elif operation == "subtract":
return num1 - num2
elif operation == "multiply":
return num1 * num2
elif operation == "divide":
return num1 / num2
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
num_1 = gr.Number(value=4)
operation = gr.Radio(["add", "subtract", "multiply", "divide"])
num_2 = gr.Number(value=0)
submit_btn = gr.Button(value="Calculate")
with gr.Column():
result = gr.Number()
submit_btn.click(
calculator, inputs=[num_1, operation, num_2], outputs=[result], api_name=False
)
examples = gr.Examples(
examples=[
[5, "add", 3],
[4, "divide", 2],
[-4, "multiply", 2.5],
[0, "subtract", 1.2],
],
inputs=[num_1, operation, num_2],
cache_examples=True,
outputs=result,
fn=calculator
)
if __name__ == "__main__":
demo.launch(show_api=False)
with gr.Blocks() as demo:
创建了一个Blocks对象demo
,用于构建整个应用的界面。
with gr.Row():
和with gr.Column():
进一步定义了界面布局,将界面分成两列。- 在第一列中,创建了三个输入组件:
num_1
(数字输入)、operation
(单选按钮)和num_2
(数字输入),以及一个按钮submit_btn
。 - 在第二列中,创建了一个输出组件
result
(数字输出)。
- 在第一列中,创建了三个输入组件:
submit_btn.click(...)
定义了当按钮被点击时执行的函数calculator
,并指定了输入和输出组件。gr.Examples(...)
定义了一些示例,用户点击这些示例时,会自动填充输入组件。
1.8 连续运行事件
在Gradio中,可以使用事件监听器的then
方法来连续运行事件,这将在前一个事件完成后运行下一个事件。这对于分多个步骤更新组件的情况非常有用。
例如,在下面的聊天机器人示例中,我们首先立即用用户消息更新聊天机器人,然后在模拟延迟后用计算机响应更新聊天机器人:
import gradio as gr
import random
import time
with gr.Blocks() as demo:
chatbot = gr.Chatbot() # 聊天机器人组件
msg = gr.Textbox() # 用户输入框
clear = gr.Button("Clear") # 按钮组件clear,用于清除聊天记录
def user(user_message, history):
return "", history + [[user_message, None]]
def bot(history):
bot_message = random.choice(["How are you?", "I love you", "I'm very hungry"])
time.sleep(2)
history[-1][1] = bot_message
return history
msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then(
bot, chatbot, chatbot
)
clear.click(lambda: None, None, chatbot, queue=False)
demo.queue()
demo.launch()
user
函数:用于处理用户消息。返回时清空输入框(返回空字符串)和更新后的聊天记录(在历史记录中添加用户消息,但机器回复为None)。bot
函数:用于生成机器人的回复。机器人会随机挑选一条消息进行回复,添加到聊天记录中并返回。msg.submit
:事件监听器,当用户在msg
文本框中输入消息并按下回车时,调用user
函数。其输入和输出分别是更新前后msg, chatbot
的值。queue=False
表示不将事件加入队列,立即执行。.then(bot, chatbot, chatbot)
:设置一个后续事件监听器,当user
函数执行完毕后,调用bot
函数。其输入输出分别是更新前后chatbot
的值clear.click(lambda: None, None, chatbot, queue=False)
:设置一个事件监听器,当用户点击clear按钮时,清除聊天记录(使用一个空的Lambda函数来清空chatbot组件)。demo.queue()
: 启用事件队列,使多个事件可以顺序执行(在此示例中不需要,但作为良好的实践保留)。
需要注意的是,then()
方法无论前一个事件是否出错,都会执行后续事件。如果希望只有在前一个事件成功执行后才运行后续事件,可以使用success()
方法,两者参数相同。
1.9 持续运行事件
1.9.1 every
参数
通过事件监听器的every
参数,可以在固定时间间隔内连续运行事件。这将在客户端连接打开时,每隔指定秒数运行一次事件。如果连接关闭,事件将在下一次迭代后停止运行。注意,这不考虑事件本身的运行时间。因此,一个运行时间为1秒的函数,每5秒运行一次,实际上每6秒运行一次。另外,该参数仅适用于与事件监听器关联的Python函数,而不适用于JavaScript函数。
import math
import gradio as gr
import plotly.express as px
import numpy as np
plot_end = 2 * math.pi # 初始化绘图的结束点
def get_plot(period=1):
global plot_end
x = np.arange(plot_end - 2 * math.pi, plot_end, 0.02)
y = np.sin(2*math.pi*period * x)
fig = px.line(x=x, y=y)
plot_end += 2 * math.pi
if plot_end > 1000:
plot_end = 2 * math.pi
return fig
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
gr.Markdown("Change the value of the slider to automatically update the plot")
period = gr.Slider(label="Period of plot", value=1, minimum=0, maximum=10, step=1)
plot = gr.Plot(label="Plot (updates every half second)")
dep = demo.load(get_plot, None, plot, every=1)
period.change(get_plot, period, plot, every=1, cancels=[dep])
if __name__ == "__main__":
demo.queue().launch()
- get_plot(period):根据给定的周期生成正弦波图表
- 使用
numpy
生成x轴数据。 - 使用
sin
函数生成y轴数据。 - 使用
plotly.express
绘制图表。 - 更新全局变量
plot_end
,使图表能够连续更新。
- 使用
with gr.Blocks() as demo
: 创建一个Gradio Blocks对象demo
。- 使用
gr.Row()
和gr.Column()
组织布局。 - 添加一个Markdown文本,用于说明滑块的功能。
- 创建一个滑块
period
,用于控制正弦波的周期。 - 创建一个图表组件
plot
,用于显示正弦波图。
- 使用
1.9.2 load
方法
gr.Blocks()
的load
方法是作用是在应用加载时自动执行指定函数。这个函数可以用于初始化组件或在应用启动时执行一些特定操作。其参数为:
fn
: 要执行的函数,这里是get_plot
。inputs
: 函数的输入组件列表,这里是None
,因为get_plot
函数不需要任何输入。outputs
: 函数的输出组件列表,这里是plot
,表示函数的输出将更新到plot
组件中。every
: 可选参数,表示函数执行的时间间隔(以秒为单位)。在这个例子中,every=1
表示每秒执行一次get_plot
函数。
本示例中, demo.load(...)
表示设置一个事件监听器,在加载时每秒调用一次get_plot
函数,更新图表plot
。
1.9.3 change
方法
在 Gradio 中,组件可以附加事件监听器,以便在其状态或值发生变化时执行特定的函数。示例中,period.change()
方法用于设置滑块组件 period
的变化事件监听器,当用户通过滑动滑块改变其值时,更新图表plot
,并取消之前的定时更新事件。
get_plot
: 滑块 period 的变化事件监听器中的回调函数。示例中是图表更新函数,在滑块值变化时将被调用执行。period
: 指向滑块组件本身的引用,表示监听该组件的变化事件。plot
: 输出组件,当滑块值变化时,调用get_plot
函数并将返回的图表更新到该组件。every=1
: 将每隔 1 秒执行一次get_plot
函数,动态更新图表。cancels=[dep]
: 可选参数,表示当事件发生时需要取消的先前事件。在这里,dep
是之前通过demo.load()
方法设置的定期执行的事件。
1.10 收集事件数据
通过在事件监听器函数的参数中添加事件数据类作为类型提示,可以收集有关事件的具体数据。例如,.select()
事件的数据可以通过gradio.SelectData
参数类型提示来获取。当用户选择触发组件的某个部分时,事件被触发,事件数据包括用户具体选择的信息。下面以一个简单的井字棋游戏进行演示。
import gradio as gr
with gr.Blocks() as demo:
turn = gr.Textbox("X", interactive=False, label="Turn")
board = gr.Dataframe(value=[["", "", ""]] * 3, interactive=False, type="array")
def place(board, turn, evt: gr.SelectData):
if evt.value:
return board, turn
board[evt.index[0]][evt.index[1]] = turn
turn = "O" if turn == "X" else "X"
return board, turn
board.select(place, [board, turn], [board, turn], show_progress="hidden")
demo.launch()
-
turn
: 一个文本框组件,用于显示当前玩家的回合(默认为 “X”),并且是不可交互的。 -
board
: 一个数据框组件,用于表示游戏棋盘,初始值为一个3x3的空数组,每个单元格初始化为空字符串,也是不可交互的。 -
place(board, turn, evt: gr.SelectData)
:事件函数,用于处理玩家在棋盘上放置棋子的事件,有三个参数:board
: 当前的游戏棋盘状态。turn
: 当前轮到的玩家,初始为 “X”。evt: gr.SelectData
: 这是事件数据类gr.SelectData
的实例,用于获取玩家选择的棋盘位置信息。- place函数的逻辑:
- 如果 evt.value 为真,则直接返回当前的 board 和 turn。
- 否则,将当前玩家的标记(“X” 或 “0”)放置在 board 的指定位置
evt.index[0], evt.index[1]
。 - 轮到另一个玩家,即如果当前是 “X”,则变为 “0”,反之亦然。
- 返回更新后的 board 和新的 turn。
-
board.select(...)
:设置数据框组件 board 的事件监听器。当用户在数据框中选择一个单元格时,将调用 place 函数处理这个事件,函数的输入输出组件都是board, turn
。show_progress="hidden"
用于隐藏进度条,因为这个操作是即时完成的,不需要显示进度。
1.11 绑定多个触发器到同一函数
在编程中,经常需要允许用户通过不同的方式触发同一个功能,比如点击按钮或按下回车键来提交表单。使用 gr.on 方法可以实现这一点,只需要将触发器列表传递给 trigger。
import gradio as gr
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Output Box")
greet_btn = gr.Button("Greet")
trigger = gr.Textbox(label="Trigger Box")
trigger2 = gr.Textbox(label="Trigger Box")
def greet(name, evt_data: gr.EventData):
return "Hello " + name + "!", evt_data.target.__class__.__name__
def clear_name(evt_data: gr.EventData):
return "", evt_data.target.__class__.__name__
gr.on(
triggers=[name.submit, greet_btn.click],
fn=greet,
inputs=name,
outputs=[output, trigger],
).then(clear_name, outputs=[name, trigger2])
demo.launch()
也可以使用装饰器语法:
import gradio as gr
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Output Box")
greet_btn = gr.Button("Greet")
@gr.on(triggers=[name.submit, greet_btn.click], inputs=name, outputs=output)
def greet(name):
return "Hello " + name + "!"
demo.launch()
@gr.on(triggers=[name.submit, greet_btn.click], inputs=name, outputs=output)
:表示函数 greet
将绑定到 name
文本框的提交事件和 greet_btn
按钮的点击事件上,当这些事件发生时,执行函数 greet
来更新 output
文本框的内容。
gr.on
还可以用于创建"实时"事件,方法是绑定到支持变更事件的组件上。如果不指定任何触发器,函数会自动绑定到所有具有变更事件的输入组件上。
import gradio as gr
with gr.Blocks() as demo:
with gr.Row():
num1 = gr.Slider(1, 10)
num2 = gr.Slider(1, 10)
num3 = gr.Slider(1, 10)
output = gr.Number(label="Sum")
# 装饰器确实会自动包含其下面的函数作为参数
@gr.on(inputs=[num1, num2, num3], outputs=output)
def sum(a, b, c):
return a + b + c
demo.launch()
- 创建了三个滑块组件(num1, num2, num3)和一个数字输出组件(output)
@gr.on(inputs=[num1, num2, num3], outputs=output)
:gr.on
装饰器没有指定具体的触发器(如按钮点击),所以直接绑定到三个滑块上,而滑块都有内置的变更事件。这样每当任何一个滑块的值改变时,sum函数就会自动触发,并实时更新输出。
这个例子展示了"实时"事件的本质:用户界面元素(这里是滑块)的任何变化都会立即触发相关函数,并更新输出,提供即时反馈。这种实时交互方式增强了用户体验,使界面更加动态和响应迅速。
二、布局控制
在默认情况下,Block 中的组件是垂直排列的。我们可以通过一些方法重新排列组件。在底层实现上,这种布局结构使用了网页开发的 flexbox 模型,下面一一进行介绍。
2.1 行
with gr.Row
子句中的元素将全部水平显示。例如,要并排显示两个按钮,可以这样做:
with gr.Blocks() as demo:
with gr.Row():
btn1 = gr.Button("Button 1")
btn2 = gr.Button("Button 2")
要使 Row 中的每个元素具有相同的高度,可以使用 style 方法的 equal_height
参数。
with gr.Blocks() as demo:
with gr.Row(equal_height=True):
textbox = gr.Textbox()
btn2 = gr.Button("Button 2")
如果要控制 Row 中元素的宽度,可以通过每个组件中存在的 scale
和 min_width
参数来设置。
scale
:整数,scale
决定了在 Row 布局中每个元素的扩展比例。多个元素的scale
值会一起决定每个元素占用的相对空间。min_width
:元素占用的最小宽度。如果没有足够的空间来满足所有min_width
值,Row 将会换行。
with gr.Blocks() as demo:
with gr.Row():
btn0 = gr.Button("Button 0", scale=0) # 不会扩展,占用默认大小
btn1 = gr.Button("Button 1", scale=1) # 扩展,占用1份空间
btn2 = gr.Button("Button 2", scale=2) # 扩展,占用2份空间
更多内容详见文档《Row》
2.2 列
在 Column
中的组件将会垂直排列在彼此的上方。由于垂直布局是 Blocks 应用程序的默认布局,因此 Column
通常会嵌套在 Row
中以实现更复杂的布局。同样的, scale
参数也可用于设置列的相对宽度。例如:
在一个
Row
中可以有多个Column
,每个Column
内的组件是垂直排列的。更多内容详见Column文档。
import gradio as gr
with gr.Blocks() as demo:
with gr.Row():
text1 = gr.Textbox(label="t1")
slider2 = gr.Textbox(label="s2")
drop3 = gr.Dropdown(["a", "b", "c"], label="d3")
with gr.Row():
with gr.Column(scale=1, min_width=600):
text1 = gr.Textbox(label="prompt 1")
text2 = gr.Textbox(label="prompt 2")
inbtw = gr.Button("Between")
text4 = gr.Textbox(label="prompt 1")
text5 = gr.Textbox(label="prompt 2")
with gr.Column(scale=2, min_width=600):
img1 = gr.Image("images/cheetah.jpg")
btn = gr.Button("Go")
demo.launch()
gr.Column参数 | 类型 | 说明 |
---|---|---|
scale | int | 设置列中每个组件的扩展比例,默认为1 |
variant | str | 指定列的样式变体,"default" 为默认样式。"panel" 为面板样式(灰色边框和圆角),看起来更有层次感)"compact" :紧凑型样式,组件之间的间距较小,适用于空间有限的界面 |
min_width | int /float | 设置列的最小宽度(以像素为单位),确保列不会比此宽度更窄。 |
visible | bool | 控制列的初始可见性,默认为True 。若设置为 False ,列在初始状态下被隐藏。 |
show_progress | bool | 是否在更新时显示进度条,默认为False |
elem_id | str | 为列元素指定一个唯一的HTML元素ID,用于CSS选择或JavaScript操作。 |
css | str | 为列元素添加自定义CSS类,以便应用自定义样式。 |
interactive | bool | 设置列中的组件是否可交互。 |
2.3 组件尺寸
Gradio允许使用参数来设置组件的高度和宽度,这些参数接受数值(以像素为单位)或字符串(可应用任何CSS单位)。如果参数被省略,将使用默认尺寸。
- 使用视口宽度(vw)
import gradio as gr
with gr.Blocks() as demo:
im = gr.ImageEditor(
width="50vw",
)
demo.launch()
在这个示例中,ImageEditor
组件的宽度设置为视口宽度的50%。
视口宽度(
Viewport Width
,简称vw
)是一个相对单位,用于表示相对于浏览器视口(即浏览器窗口)的宽度。组件的宽度将占据浏览器窗口宽度的50%
。表示无论浏览器窗口如何调整大小,组件的宽度都会动态地占据当前视口宽度的50%
。
- 使用百分比值定义尺寸
import gradio as gr
css = """
.container {
height: 100vh;
}
"""
with gr.Blocks(css=css) as demo:
with gr.Column(elem_classes=["container"]):
name = gr.Chatbot(value=[["1", "2"]], height="70%")
demo.launch()
在这个示例中,Column
布局组件的高度设置为视口高度的100%(100vh),而其中的Chatbot
组件占据Column
高度的70%。
为了确保界面响应式,建议在不同屏幕尺寸上进行测试,以保证一致的用户体验。有关CSS单位的详细列表,可以参考《CSS Units》。
2.4 选项卡和折叠组件
使用 with gr.Tab('tab_name'):
子句可以创建选项卡,在此上下文中创建的任何组件都将显示在该选项卡中。连续的 Tab子句将被分组在一起,这样一次只能选择一个选项卡,且只显示该选项卡上下文中的组件。
使用 gr.Accordion('label')
可以创建一个折叠组件,类似于选项卡,可以选择性地隐藏或显示内容(打开、折叠)。
更多内容详见Tab文档和 Accordions文档。
import numpy as np
import gradio as gr
def flip_text(x):
return x[::-1]
def flip_image(x):
return np.fliplr(x)
with gr.Blocks() as demo:
gr.Markdown("Flip text or image files using this demo.")
with gr.Tab("Flip Text"):
text_input = gr.Textbox()
text_output = gr.Textbox()
text_button = gr.Button("Flip")
with gr.Tab("Flip Image"):
with gr.Row():
image_input = gr.Image()
image_output = gr.Image()
image_button = gr.Button("Flip")
with gr.Accordion("Open for More!", open=False):
gr.Markdown("Look at me...")
temp_slider = gr.Slider(
minimum=0.0,
maximum=1.0,
value=0.1,
step=0.1,
interactive=True,
label="Slide me",
)
temp_slider.change(lambda x: x, [temp_slider])
text_button.click(flip_text, inputs=text_input, outputs=text_output)
image_button.click(flip_image, inputs=image_input, outputs=image_output)
demo.launch()
2.5 可见性(Visibility)
组件和布局元素都有一个 visible 参数,可以用于设置它们最初的可见性,并且可以动态更新。通过设置 gr.Column(visible=...)
,可以显示或隐藏一组组件。
import gradio as gr
with gr.Blocks() as demo:
error_box = gr.Textbox(label="Error", visible=False)
name_box = gr.Textbox(label="Name")
age_box = gr.Number(label="Age", minimum=0, maximum=100)
symptoms_box = gr.CheckboxGroup(["Cough", "Fever", "Runny Nose"])
submit_btn = gr.Button("Submit")
with gr.Column(visible=False) as output_col:
diagnosis_box = gr.Textbox(label="Diagnosis")
patient_summary_box = gr.Textbox(label="Patient Summary")
def submit(name, age, symptoms):
if len(name) == 0:
return {error_box: gr.Textbox(value="Enter name", visible=True)}
return {
output_col: gr.Column(visible=True),
diagnosis_box: "covid" if "Cough" in symptoms else "flu",
patient_summary_box: f"{name}, {age} y/o",
}
submit_btn.click(
submit,
[name_box, age_box, symptoms_box],
[error_box, diagnosis_box, patient_summary_box, output_col],
)
demo.launch()
2.6 可变数量的输出
通过动态调整组件的可见性,可以创建支持可变数量输出的演示。例如,可以用一个输入滑块控制输出文本框的数量。
import gradio as gr
max_textboxes = 10
def variable_outputs(k):
k = int(k)
return [gr.Textbox(visible=True)]*k + [gr.Textbox(visible=False)]*(max_textboxes-k)
with gr.Blocks() as demo:
s = gr.Slider(1, max_textboxes, value=max_textboxes, step=1, label="How many textboxes to show:")
textboxes = []
for i in range(max_textboxes):
t = gr.Textbox(f"Textbox {i}")
textboxes.append(t)
s.change(variable_outputs, s, textboxes)
if __name__ == "__main__":
demo.launch()
2.7 定义和渲染组件
有时候,我们需要在实际渲染组件之前定义它们。例如,可能需要在输入文本框上方显示一个示例部分(gr.Examples
),由于 gr.Examples
需要作为参数传递给输入组件对象,因此需要先定义输入组件。解决方法是:在 gr.Blocks()
范围之外定义 gr.Textbox
,然后在 UI 中需要的地方使用组件的 .render()
方法来渲染它。
input_textbox = gr.Textbox()
with gr.Blocks() as demo:
gr.Examples(["hello", "bonjour", "merhaba"], input_textbox)
input_textbox.render()
三、Blocks状态
在《Gradio官方教程一:nterface class简介》3.8章节Interface State
中我们介绍过,我们创建的的演示都是无状态的,即每次函数调用后不保存任何信息。如果你希望根据之前的交互修改演示的行为,就需要在Gradio中引入状态管理。Interface State
有两种情况:
Global State
:全局状态,是所有函数调用和所有用户都可以访问的状态变量,使用方法是在函数调用之外创建一个变量,并在函数内部访问它。Session State
:会话状态,是指数据在同一个页面会话的多次提交之间保持,但不在不同用户之间共享。
Blocks State
的概念和Interface State
相同,其Session State实现方式是:
- 创建一个gr.State()对象。如果这个状态对象有默认值,可以在构造函数中传入。
- 在事件监听器中,将 State 对象作为输入和输出
- 在事件监听器函数中,将该变量添加到输入参数中,并在返回值中返回这个变量。
更多内容详见state文档。
下面以一个猜字母游戏进行演示。
import gradio as gr
secret_word = "gradio"
with gr.Blocks() as demo:
used_letters_var = gr.State([]) # 初始化状态对象,用于存储已经猜过的字母
with gr.Row() as row:
with gr.Column():
input_letter = gr.Textbox(label="Enter letter")
btn = gr.Button("Guess Letter")
with gr.Column():
# 文本框的初始值为一串下划线,其长度等于秘密单词 secret_word 的长度,表示每个字母都被隐藏
hangman = gr.Textbox(
label="Hangman",
value="_"*len(secret_word)
)
used_letters_box = gr.Textbox(label="Used Letters")
def guess_letter(letter, used_letters):
# 将用户猜的字母添加到已使用字母列表中
used_letters.append(letter)
# 遍历 secret_word 中的每一个字母,如果字母在 used_letters 中,就将其添加到 answer 中,否则保持"_"
answer = "".join([
(letter if letter in used_letters else "_")
for letter in secret_word
])
# 返回一个字典,用于更新Gradio界面的不同部分
return {
used_letters_var: used_letters,
used_letters_box: ", ".join(used_letters),
hangman: answer
}
btn.click(
guess_letter,
[input_letter, used_letters_var],
[used_letters_var, used_letters_box, hangman]
)
demo.launch()
guess_letter
函数返回了三个参数,btn.click
的输出组件也是这三个参数:
used_letters_var
:这是一个 gr.State 对象,用于保存已猜过的字母列表。guess_letter 函数会将新的已猜过字母列表返回到这个状态对象,以确保状态在多次提交之间保持一致。used_letters_box
:将已猜过的字母列表转换为逗号分隔的字符串,并显示在这个文本框中。hangman
:更新猜词结果
四、动态应用程序
在以前的版本中,Gradio中的组件和事件监听器一旦定义并启动后,就不能再添加或删除。gr.render
装饰器大大扩展了Gradio的功能,使用它可以重新渲染组件,创建动态更新的界面。
4.1 动态调整组件数
4.1.1 示例一:动态调整文本框
在下面的示例中,我们将创建可变数量的文本框。当用户编辑输入文本时,我们会为输入中的每个字母创建一个文本框。
import gradio as gr
with gr.Blocks() as demo:
input_text = gr.Textbox(label="input")
@gr.render(inputs=input_text)
def show_split(text):
if len(text) == 0:
gr.Markdown("## No Input Provided")
else:
for letter in text:
gr.Textbox(letter)
demo.launch()
通过这个例子可以看出使用 @gr.render
装饰器的三个步骤:
- 创建一个函数并为其添加
@gr.render
装饰器 - 在
@gr.render
的inputs=
参数中添加输入组件,并在函数中为每个组件创建对应的参数,这样函数会在任何组件发生变化时自动重新运行 - 在函数内部添加所有你想基于输入来渲染的组件。
在本示例中,Gradio会监听input_text
的变化。每当用户在输入框中输入或修改内容时,show_split
函数就会被调用(通过简单的 for 循环来实现动态修改逻辑)。show_split
函数内部使用gr.Textbox()
和gr.Markdown()
来创建新的界面元素,最终Gradio会动态地在界面上添加或移除这些元素。
4.1.2 示例二:@gr.render触发机制
下面通过一个更复杂的示例,来讲解 @gr.render
装饰器的一些高级用法。
import gradio as gr
with gr.Blocks() as demo:
input_text = gr.Textbox(label="input")
mode = gr.Radio(["textbox", "button"], value="textbox")
@gr.render(inputs=[input_text, mode], triggers=[input_text.submit])
def show_split(text, mode):
if len(text) == 0:
gr.Markdown("## No Input Provided")
else:
for letter in text:
if mode == "textbox":
gr.Textbox(letter)
else:
gr.Button(letter)
demo.launch()
示例中,添加了一个单选按钮(gr.Radio
),允许用户选择文本框或按钮两种模式,根据用户选择的模式,为每个字符创建文本框或按钮。默认情况下,@gr.render
会在两种情况下重新运行:
- 应用程序加载时(
.load
监听器) - 任何输入组件发生变化时(
.change
监听器)
在本示例中,通过 triggers=[input_text.submit]
明确设置了触发器,这意味着渲染函数只有在用户提交输入文本(按回车)时才会触发,而不是每次文本变化时都触发。如果设置了自定义触发器,但仍希望在应用程序启动时自动渲染,需要将 demo.load
添加到触发器列表中。
@gr.render(inputs=[input_text, mode], triggers=[input_text.submit, demo.load])
:当用户提交输入文本或应用程序首次加载时,触发渲染函数。
4.2 创建动态事件监听器
让我们看一个示例,该示例将可变数量的 Textbox 作为输入,并将所有文本合并到一个框中。
import gradio as gr
with gr.Blocks() as demo:
text_count = gr.State(1)
add_btn = gr.Button("Add Box")
add_btn.click(lambda x: x + 1, text_count, text_count)
@gr.render(inputs=text_count)
def render_count(count):
boxes = []
for i in range(count):
box = gr.Textbox(key=i, label=f"Box {i}")
boxes.append(box)
def merge(*args):
return " ".join(args)
merge_btn.click(merge, boxes, output)
merge_btn = gr.Button("Merge")
output = gr.Textbox(label="Merged Output")
demo.launch()
- 创建动态组件:状态变量
text_count
用于跟踪需要创建的文本框数量。通过点击"Add"
按钮,我们增加text_count
的值,这会触发渲染装饰器 - 组件状态保持:在Gradio应用的动态更新过程中,使用key参数可以维护组件的状态和一致性。当界面被重新渲染(比如因为某些状态变化),具有相同key的组件将被识别为同一个组件,这样渲染前后组件的值也会保持不变,不会丢失用户已经输入或选择的数据。
- 动态事件监听:我们将创建的文本框存储在一个列表中,并将这个列表作为输入提供给合并按钮的事件监听器。注意,所有使用在渲染函数内创建的组件的事件监听器也必须定义在该渲染函数内。
- 跨渲染引用:事件监听器仍然可以引用渲染函数外部定义的组件,就像我们在这里引用
merge_btn
和output
。 - 事件监听器的生命周期:跟组件一样,每当函数重新渲染时,前一次渲染创建的事件监听器会被清除,新的监听器会被添加。
4.3 组合应用
4.3.1 示例一:创建待办事项列表
使用@gr.render
允许我们正确地管理任务列表,确保添加、完成和删除操作都能正确反映在界面上,即使在处理多个任务时也不会混淆。
import gradio as gr
with gr.Blocks() as demo:
tasks = gr.State([]) # 状态对象,存储任务列表
new_task = gr.Textbox(label="Task Name", autofocus=True) # 输入新任务
def add_task(tasks, new_task_name):
return tasks + [{"name": new_task_name, "complete": False}], "" # 将新任务添加到任务列表中
new_task.submit(add_task, [tasks, new_task], [tasks, new_task]) # 设置提交新任务的事件
@gr.render(inputs=tasks)
def render_todos(task_list):
complete = [task for task in task_list if task["complete"]]
incomplete = [task for task in task_list if not task["complete"]]
gr.Markdown(f"### Incomplete Tasks ({len(incomplete)})") # 在未完成任务框上方显示其数量
for task in incomplete:
# 为每个未完成任务创建一行,包含任务名、"完成"按钮和"删除"按钮
with gr.Row():
gr.Textbox(task['name'], show_label=False, container=False)
done_btn = gr.Button("Done", scale=0)
def mark_done(task=task):
task["complete"] = True
return task_list
done_btn.click(mark_done, None, [tasks])
delete_btn = gr.Button("Delete", scale=0, variant="stop")
def delete(task=task):
task_list.remove(task)
return task_list
delete_btn.click(delete, None, [tasks])
gr.Markdown(f"### Complete Tasks ({len(complete)})") # 在完成任务框上方显示其数量
for task in complete:
gr.Textbox(task['name'], show_label=False, container=False)
demo.launch()
这个应用的大部分逻辑都封装在一个单一的 gr.render
函数中,该函数响应 tasks
这个 gr.State
变量的变化。tasks
是一个嵌套列表,这增加了处理的复杂性。当你设计一个 gr.render
函数来响应列表或字典结构时,需要注意以下两点:
- 输出设置:任何修改状态变量并应触发重新渲染的事件监听器,必须将该状态变量设置为输出。这告诉Gradio需要检查这个变量是否在后台发生了变化,确保了界面能够适当地响应。
- 变量"冻结":在
gr.render
函数中,如果循环中的变量在事件监听器函数内部使用,应该通过在函数定义时将其设为默认参数来"冻结"这个变量,避免了由于闭包和异步操作可能导致的问题,确保每个事件监听器操作正确的任务。例如,在mark_done
和delete
函数中使用task=task
。这样做可以确保变量保持其在循环时的值,而不是在后续可能发生的变化。
4.3.2 示例二:混音器
第二个示例是创建一个混音器,用于混合多个音轨。
import gradio as gr
import numpy as np
with gr.Blocks() as demo:
track_count = gr.State(1) # 状态变量,用来记录当前音轨的数量,初始值为1
add_track_btn = gr.Button("Add Track") # 定义了一个按钮,用来增加音轨
# 为按钮添加点击事件,每次点击按钮时,track_count的值增加1。
add_track_btn.click(lambda count: count + 1, track_count, track_count)
# 使用@gr.render装饰器定义函数render_tracks,该函数根据track_count的值渲染音轨。
@gr.render(inputs=track_count)
def render_tracks(count):
audios = [] # 存储音轨
volumes = [] # 存储音量控件
with gr.Row():
for i in range(count):
with gr.Column(variant="panel", min_width=200):
# 每个音轨包含一个文本框(用于输入音轨名称)、一个音频上传控件和一个音量滑动条。
gr.Textbox(placeholder="Track Name", key=f"name-{i}", show_label=False)
track_audio = gr.Audio(label=f"Track {i}", key=f"track-{i}")
track_volume = gr.Slider(0, 100, value=100, label="Volume", key=f"volume-{i}")
audios.append(track_audio)
volumes.append(track_volume)
# 内部函数merge,用于合并音轨
def merge(data):
sr, output = None, None # 初始化变量sr(采样率)和output(输出音频数据)
for audio, volume in zip(audios, volumes):
sr, audio_val = data[audio]
volume_val = data[volume]
final_track = audio_val * (volume_val / 100) # 调整音频音量
if output is None:
# 直接将第一个音轨赋值给output
output = final_track
else:
min_shape = tuple(min(s1, s2) for s1, s2 in zip(output.shape, final_track.shape))
trimmed_output = output[:min_shape[0], ...][:, :min_shape[1], ...] if output.ndim > 1 else output[:min_shape[0]]
trimmed_final = final_track[:min_shape[0], ...][:, :min_shape[1], ...] if final_track.ndim > 1 else final_track[:min_shape[0]]
output += trimmed_output + trimmed_final
return (sr, output)
# 点击时调用merge函数,传入所有音轨和音量控件,并将合并后的音频数据输出到output_audio控件。
merge_btn.click(merge, set(audios + volumes), output_audio)
merge_btn = gr.Button("Merge Tracks") # 音轨合并按钮
output_audio = gr.Audio(label="Output", interactive=False) # 音频输出控件,用来显示合并后的音轨
demo.launch()
-
gr.Textbox(placeholder="Track Name", key=f"name-{i}", show_label=False)
:- 设置了文本框的占位符文本为"Track Name",即当文本框为空时,它会显示"Track Name"作为提示,有助于指导用户应该在此处输入什么内容。
- 为文本框设置了一个唯一的键(key),用于保持组件状态
- 默认会显示文本框标签,但这里被禁用了
-
merge_btn.click(merge, set(audios + volumes), output_audio)
:当有大量不同类型和数量不定的组件作为输入传递给事件监听器时,使用集合(set)和字典(dictionary)进行表示会比使用列表(list)更方便。audios
和volumes
是两个列表,分别包含音频组件和音量滑块组件,audios + volumes
将这两个列表合并成一个更大的列表set()
函数将这个合并后的列表转换为一个集合。这样做可以去除可能的重复项,并提供一种更有效的方式来处理大量输入。- 当
merge
函数被调用时,Gradio 会自动将这个集合转换为一个字典data
,其中:键是每个组件的唯一标识符(在这个应用中是我们设置的 key 值),值是相应组件的当前值。
-
sr, audio_val = data[audio]
:这个是Gradio 音频组件的标准返回格式。这两个元素分别代表音频的采样率(Hz,表示每秒采集的音频样本数)和音频数据(通常是一个 NumPy 数组)。在音频处理中,采样率决定了音频的质量和可表示的最高频率,在合并或处理多个音频时,确保它们有相同的采样率是很重要的。 -
merge
函数:用于合并所有音轨。它遍历所有音轨,应用音量调整,并将它们叠加在一起。为了处理可能的长度不一致问题,使用了min_shape
来截取音轨。- 直接将第一个音轨赋值给
output
- 计算当前
output
和新音轨(final_track
)的最小共同形状 - 将
both output
和final_track
裁剪到这个最小共同形状 - 将裁剪后的
output
和final_track
相加,返回最终的采样率和合并后的音频数据。
- 直接将第一个音轨赋值给
五、使用CSS和Javascript创建自定义demo
Gradio 允许您以多种方式自定义您的演示。您可以自定义演示的布局,添加自定义的 HTML,还可以添加自定义主题。本章介绍如何向您的演示中添加自定义 CSS 和 JavaScript 代码,以实现自定义样式、动画、自定义用户界面功能、分析以及更多内容。
5.1 使用CSS进行自定义
5.1 使用CSS进行自定义
5.1.1 使用Gradio 主题
自定义应用程序外观和感觉的最简方式就是使用Gradio 主题。Gradio 提供了多种预建主题(gr.themes.*
),只需要向 Blocks 构造函数传递 theme=
关键字参数,例如:
with gr.Blocks(theme=gr.themes.Glass()):
...
更多内容,详见主题指南。
5.1.2 添加自定义 CSS
为了获得额外的样式能力,用户可以通过 css=
参数向应用程序添加自定义 CSS 代码,这可以是 CSS 文件的路径或者直接的字符串代码。Gradio 应用程序的基础类是 gradio-container
,以下是一个更改 Gradio 应用程序背景颜色的示例:
with gr.Blocks(css=".gradio-container {background-color: red}") as demo:
...
如果想在 css 中引用外部文件,请将文件路径(可以是相对路径或绝对路径)传递给 file=
参数,例如:
with gr.Blocks(css=".gradio-container {background: url('file=clouds.jpg')}") as demo:
...
默认情况下,宿主机器(即运行 Gradio 应用的服务器或计算机)上的文件是用户无法访问的。这意味着,如果要在 CSS 中引用文件(比如 clouds.jpg),有两种方式:
- 确保文件是 URL 链接,这样用户可以直接通过网络访问
- 在启动 Gradio 应用时,通过 allow_list 参数允许特定的文件路径,使得用户将能够访问宿主机器上列出的文件。
更多关于安全性和文件访问的信息,详见《Security and File Access》。
另外,由于 Gradio 的 HTML DOM 可能会在不同版本之间发生变化,因此在自定义 JS 和 CSS 中 query selectors 不能保证跨版本工作,建议谨慎使用。
5.1.3 elem_id 和 elem_classes 参数
你也可以通过通过elem_id
和 elem_classes
参数,为组件添加 ID 和类,可以更方便地用 CSS 选择和样式化这些组件。
css = """
#warning {background-color: #FFCCCB}
.feedback textarea {font-size: 24px !important}
"""
with gr.Blocks(css=css) as demo:
box1 = gr.Textbox(value="Good Job", elem_classes="feedback")
box2 = gr.Textbox(value="Failure", elem_id="warning", elem_classes="feedback")
通过使用这两个参数添加的 ID 和类,可以更稳定地跨 Gradio 版本保持兼容性,因为内置的类名或 ID 可能会发生变化。但是,如果使用自定义 CSS,不能保证完全兼容未来的 Gradio 版本,因为 DOM 元素本身也可能发生变化。另外,使用类选择器时,可能需要使用 !important
选择器来覆盖 Gradio 的默认样式,否则默认样式可能优先级更高,会覆盖您的自定义样式。
5.1 使用JavaScript 进行自定义
将 JavaScript 代码添加到您的 Gradio 演示有 3 种方法。第一种是将 JavaScript 代码作为一个字符串或文件路径添加到 Blocks 或 Interface 初始化器的 js 参数中。这样,在演示首次加载时将运行该 JavaScript 代码。
5.2.1 通过js参数添加
下面是首次加载demo时显示欢迎信息动画的示例。
import gradio as gr
def welcome(name):
return f"Welcome to Gradio, {name}!"
js = """
function createGradioAnimation() {
var container = document.createElement('div');
container.id = 'gradio-animation';
container.style.fontSize = '2em';
container.style.fontWeight = 'bold';
container.style.textAlign = 'center';
container.style.marginBottom = '20px';
var text = 'Welcome to Gradio!';
for (var i = 0; i < text.length; i++) {
(function(i){
setTimeout(function(){
var letter = document.createElement('span');
letter.style.opacity = '0';
letter.style.transition = 'opacity 0.5s';
letter.innerText = text[i];
container.appendChild(letter);
setTimeout(function() {
letter.style.opacity = '1';
}, 50);
}, i * 250);
})(i);
}
var gradioContainer = document.querySelector('.gradio-container');
gradioContainer.insertBefore(container, gradioContainer.firstChild);
return 'Animation created';
}
"""
with gr.Blocks(js=js) as demo:
inp = gr.Textbox(placeholder="What is your name?")
out = gr.Textbox()
inp.change(welcome, inp, out)
demo.launch()
你也可以将 JavaScript 代码作为文件路径添加到 Blocks 或 Interface 的 js 参数中。例如,如果在 Python 脚本的同一目录中有一个名为 custom.js
的文件,可以这样添加:
with gr.Blocks(js=“custom.js”) as demo
gr.Interface(…, js=“custom.js”))
5.2.2 在事件监听器中使用
Blocks 中的事件监听器有一个 js 参数,可以接受一个 JavaScript 函数作为字符串,并将其当作 Python 事件监听器函数一样处理。你可以同时传递 JavaScript 函数和 Python 函数(在这种情况下,首先运行 JavaScript 函数)或仅传递 Javascript函数(需要将 Python fn 设置为 None )。
import gradio as gr
blocks = gr.Blocks()
with blocks as demo:
subject = gr.Textbox(placeholder="subject")
verb = gr.Radio(["ate", "loved", "hated"])
object = gr.Textbox(placeholder="object")
with gr.Row():
btn = gr.Button("Create sentence.")
reverse_btn = gr.Button("Reverse sentence.")
foo_bar_btn = gr.Button("Append foo")
reverse_then_to_the_server_btn = gr.Button(
"Reverse sentence and send to server."
)
def sentence_maker(w1, w2, w3):
return f"{w1} {w2} {w3}"
output1 = gr.Textbox(label="output 1")
output2 = gr.Textbox(label="verb")
output3 = gr.Textbox(label="verb reversed")
output4 = gr.Textbox(label="front end process and then send to backend")
btn.click(sentence_maker, [subject, verb, object], output1)
reverse_btn.click(
None, [subject, verb, object], output2, js="(s, v, o) => o + ' ' + v + ' ' + s"
)
verb.change(lambda x: x, verb, output3, js="(x) => [...x].reverse().join('')")
foo_bar_btn.click(None, [], subject, js="(x) => x + ' foo'")
reverse_then_to_the_server_btn.click(
sentence_maker,
[subject, verb, object],
output4,
js="(s, v, o) => [s, v, o].map(x => [...x].reverse().join(''))",
)
demo.launch()
5.2.3 通过head参数添加
最后,你可以将 JavaScript 代码添加到 Blocks 初始化器的 head 参数中,这将把代码添加到 HTML 文档的头部。例如,可以这样向demo中添加 Google Analytics:
head = f"""
<script async src="https://www.googletagmanager.com/gtag/js?id={google_analytics_tracking_id}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){{dataLayer.push(arguments);}}
gtag('js', new Date());
gtag('config', '{google_analytics_tracking_id}');
</script>
"""
with gr.Blocks(head=head) as demo:
...demo code...
head参数可以接受任何通常插入到HTML <head>
标签中的内容,不仅限于JavaScript,还可以包含其他HTML标签,如<meta>
标签。
请注意,添加自定义HTML可能会影响浏览器行为和兼容性(例如键盘快捷键),,建议在不同浏览器中测试界面,注意脚本如何与浏览器默认值交互。
下面的例子中,当用户不在输入文字(比如在文本框中)时,按下Shift+S
组合键可以触发界面上某个特定按钮的点击事件。这种功能可以提高用户操作效率,允许用户不使用鼠标就能快速执行某些操作。
import gradio as gr
shortcut_js = """
<script>
function shortcuts(e) {
var event = document.all ? window.event : e;
switch (e.target.tagName.toLowerCase()) {
case "input":
case "textarea":
break;
default:
if (e.key.toLowerCase() == "s" && e.shiftKey) {
document.getElementById("my_btn").click();
}
}
}
document.addEventListener('keypress', shortcuts, false);
</script>
"""
with gr.Blocks(head=shortcut_js) as demo:
action_button = gr.Button(value="Name", elem_id="my_btn")
textbox = gr.Textbox()
action_button.click(lambda : "button pressed", None, textbox)
demo.launch()
六、像函数一样使用Gradio Blocks
Gradio Blocks不仅可以用作机器学习演示,还可以通过output = demo("Hello", "friend")
这样的方式调用Gradio应用,就像调用普通函数一样。通过这种函数式调用,你可以无缝地组合Gradio应用,下面进行演示。
假设我们有一个将英语文本翻译成德语文本的演示,其代码为:
import gradio as gr
from transformers import pipeline
pipe = pipeline("translation", model="t5-base")
def translate(text):
return pipe(text)[0]["translation_text"]
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
english = gr.Textbox(label="English text")
translate_btn = gr.Button(value="Translate")
with gr.Column():
german = gr.Textbox(label="German Text")
translate_btn.click(translate, inputs=english, outputs=german, api_name="translate-to-german")
examples = gr.Examples(examples=["I went to the supermarket yesterday.", "Helen is a good swimmer."],
inputs=[english])
demo.launch()
假设我们有一个英文文本生成的应用,想额外实现德文文本生成,有两种两种解决方案:
- 找到一个现成的英语到德语翻译的代码,将这段代码复制到你的应用中
- 将英语到德语翻译功能作为一个独立的模块,在你的应用中加载这个模块,像使用普通Python函数一样使用它。
方案1虽然简单直接,但可能会使你的代码变得复杂。方案2将不同的功能(英德翻译)模块化,需要时再加载使用,而不是将所有代码都集成到你的主应用中。这样可以使你的应用更容易维护和扩展。具体做法是:使用Gradio提供的Blocks.load
方法来加载翻译功能,加载后,你可以像调用普通Python函数一样使用这个翻译功能。
import gradio as gr
from transformers import pipeline
english_translator = gr.load(name="spaces/gradio/english_translator")
english_generator = pipeline("text-generation", model="distilgpt2")
def generate_text(text):
english_text = english_generator(text)[0]["generated_text"]
german_text = english_translator(english_text)
return english_text, german_text
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
seed = gr.Text(label="Input Phrase")
with gr.Column():
english = gr.Text(label="Generated English Text")
german = gr.Text(label="Generated German Text")
btn = gr.Button("Generate")
btn.click(generate_text, inputs=[seed], outputs=[english, german])
gr.Examples(["My name is Clara and I am"], inputs=[seed])
demo.launch()
我们使用gr.load
加载了一个英德翻译功能english_translator
,它在generate_text
函数中被当作普通python函数来使用。这段代码结合了文本生成(english_generator
)和翻译(english_translator
)两个功能,通过重用现有的翻译应用,避免了在此代码中实现复杂的翻译逻辑,使代码更加简洁,易于理解和维护。
如果你加载的应用定义了多个函数,你可以使用 fn_index
或 api_name
参数来指定要使用的具体函数。在原始的英语到德语翻译demo中,可以给函数赋予一个唯一的API名称,然后使用使用这个名称来指定要使用的函数
translate_btn.click(translate, inputs=english, outputs=german, api_name="translate-to-german")
english_generator(text, api_name="translate-to-german")[0]["generated_text"]
如果应用定义了多个函数,你可以使用 fn_index
来选择特定的函数。例如,如果应用还定义了一个英语到西班牙语的翻译函数,你可以这样使用:
# 第二个函数英语翻译西班牙语的索引是1。
english_generator(text, fn_index=1)[0]["generated_text"]