Skip to content

Commit f1d83fa

Browse files
abidlabscontrastive-vaegradio-pr-bot
authored
Add ability to add custom buttons to components (#12539)
* changes * add changeset * changes * add changeset * changes * changes * changes * changes * changes * add changeset * changes * changes * changes * changes * changes * changes * changes * changes * add changeset * changes * changes * changes * changes * add changeset * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * add changeset * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * reuse frontend * changes * changes * changes * changes * format * add changeset * Refactor code for improved readability and maintainability * changes * changes * changes * changes * changes * changes * changes --------- Co-authored-by: Abubakar Abid <[email protected]> Co-authored-by: gradio-pr-bot <[email protected]>
1 parent b3a3683 commit f1d83fa

File tree

119 files changed

+911
-169
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

119 files changed

+911
-169
lines changed

.changeset/curly-cats-try.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"@gradio/annotatedimage": minor
3+
"@gradio/atoms": minor
4+
"@gradio/audio": minor
5+
"@gradio/chatbot": minor
6+
"@gradio/checkbox": minor
7+
"@gradio/checkboxgroup": minor
8+
"@gradio/code": minor
9+
"@gradio/core": minor
10+
"@gradio/datetime": minor
11+
"@gradio/dialogue": minor
12+
"@gradio/dropdown": minor
13+
"@gradio/file": minor
14+
"@gradio/fileexplorer": minor
15+
"@gradio/gallery": minor
16+
"@gradio/highlightedtext": minor
17+
"@gradio/html": minor
18+
"@gradio/image": minor
19+
"@gradio/imageslider": minor
20+
"@gradio/json": minor
21+
"@gradio/label": minor
22+
"@gradio/model3d": minor
23+
"@gradio/number": minor
24+
"@gradio/plot": minor
25+
"@gradio/radio": minor
26+
"@gradio/textbox": minor
27+
"@gradio/utils": minor
28+
"@gradio/video": minor
29+
"gradio": minor
30+
---
31+
32+
feat:Add ability to add custom buttons to components
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: textbox_custom_buttons"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "def export_data(text):\n", " print(\"Exporting data:\", text)\n", " return \"Data exported to server!\"\n", "\n", "def refresh_data():\n", " import random\n", " return f\"Refreshed content: {random.randint(1000, 9999)}\"\n", "\n", "with gr.Blocks() as demo:\n", " gr.Markdown(\"\"\"\n", " # Textbox with Custom Buttons Demo\n", " \n", " This demo showcases custom buttons in a Textbox component that can trigger either (or both):\n", " - **Python functions** \n", " - **JS functions** (with and without input parameters)\n", " \n", " You can use emojis, text, or icons for the buttons.\n", " \"\"\")\n", " \n", " gr.Markdown(\"### Textbox with Custom Buttons\")\n", " refresh_btn = gr.Button(\"Refresh\")\n", " alert_btn = gr.Button(\"\u26a0\ufe0f Alert\")\n", " clear_btn = gr.Button(\"\ud83d\uddd1\ufe0f\")\n", " \n", " textbox = gr.Textbox(\n", " value=\"Sample text content that can be exported, refreshed, or transformed.\",\n", " buttons=[\"copy\", refresh_btn, alert_btn, clear_btn],\n", " label=\"Sample Text\",\n", " lines=5\n", " )\n", " \n", " output = gr.Textbox(label=\"Output (Python Function Result)\")\n", " \n", " \n", " refresh_btn.click(refresh_data, outputs=textbox)\n", " \n", " alert_btn.click(\n", " None,\n", " inputs=textbox,\n", " outputs=[],\n", " js=\"(text) => { alert('This is a JavaScript alert!\\\\n\\\\nTextbox content: ' + text); return []; }\"\n", " )\n", " \n", " \n", " clear_btn.click(\n", " None,\n", " inputs=[],\n", " outputs=textbox,\n", " js=\"() => ''\"\n", " )\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n", "\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}

demo/textbox_custom_buttons/run.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import gradio as gr
2+
3+
def export_data(text):
4+
print("Exporting data:", text)
5+
return "Data exported to server!"
6+
7+
def refresh_data():
8+
import random
9+
return f"Refreshed content: {random.randint(1000, 9999)}"
10+
11+
with gr.Blocks() as demo:
12+
gr.Markdown("""
13+
# Textbox with Custom Buttons Demo
14+
15+
This demo showcases custom buttons in a Textbox component that can trigger either (or both):
16+
- **Python functions**
17+
- **JS functions** (with and without input parameters)
18+
19+
You can use emojis, text, or icons for the buttons.
20+
""")
21+
22+
gr.Markdown("### Textbox with Custom Buttons")
23+
refresh_btn = gr.Button("Refresh")
24+
alert_btn = gr.Button("⚠️ Alert")
25+
clear_btn = gr.Button("🗑️")
26+
27+
textbox = gr.Textbox(
28+
value="Sample text content that can be exported, refreshed, or transformed.",
29+
buttons=["copy", refresh_btn, alert_btn, clear_btn],
30+
label="Sample Text",
31+
lines=5
32+
)
33+
34+
output = gr.Textbox(label="Output (Python Function Result)")
35+
36+
37+
refresh_btn.click(refresh_data, outputs=textbox)
38+
39+
alert_btn.click(
40+
None,
41+
inputs=textbox,
42+
outputs=[],
43+
js="(text) => { alert('This is a JavaScript alert!\\n\\nTextbox content: ' + text); return []; }"
44+
)
45+
46+
47+
clear_btn.click(
48+
None,
49+
inputs=[],
50+
outputs=textbox,
51+
js="() => ''"
52+
)
53+
54+
if __name__ == "__main__":
55+
demo.launch()
56+

gradio/blocks.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,20 @@ def get_config(self, cls: type[Block] | None = None) -> dict[str, Any]:
280280
value = getattr(self, parameter.name)
281281
if dataclasses.is_dataclass(value):
282282
value = dataclasses.asdict(value) # type: ignore
283+
elif isinstance(value, Block):
284+
block_instance = value
285+
value = block_instance.get_config()
286+
value["id"] = block_instance._id
287+
elif isinstance(value, (list, tuple)):
288+
serialized_list = []
289+
for item in value:
290+
if isinstance(item, Block):
291+
item_config = item.get_config()
292+
item_config["id"] = item._id
293+
serialized_list.append(item_config)
294+
else:
295+
serialized_list.append(item)
296+
value = serialized_list
283297
config[parameter.name] = value
284298
for e in self.events:
285299
to_add = e.config_data()

gradio/components/annotated_image.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313

1414
from gradio import processing_utils, utils
1515
from gradio.components.base import Component
16+
from gradio.components.button import Button
1617
from gradio.data_classes import FileData, GradioModel
1718
from gradio.events import Events
1819
from gradio.i18n import I18nData
20+
from gradio.utils import set_default_buttons
1921

2022
if TYPE_CHECKING:
2123
from gradio.components import Timer
@@ -74,7 +76,7 @@ def __init__(
7476
render: bool = True,
7577
key: int | str | tuple[int | str, ...] | None = None,
7678
preserved_by_key: list[str] | str | None = "value",
77-
buttons: list[Literal["fullscreen"]] | None = None,
79+
buttons: list[Literal["fullscreen"] | Button] | None = None,
7880
):
7981
"""
8082
Parameters:
@@ -97,14 +99,14 @@ def __init__(
9799
render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
98100
key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
99101
preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
100-
buttons: A list of buttons to show for the component. Currently, the only valid option is "fullscreen". The "fullscreen" button allows the user to view the image in fullscreen mode. By default, all buttons are shown.
102+
buttons: A list of buttons to show for the component. Valid options in the list are "fullscreen" or a gr.Button() instance. The "fullscreen" button allows the user to view the image in fullscreen mode. Custom gr.Button() instances will appear in the toolbar with their configured icon and/or label, and clicking them will trigger any .click() events registered on the button. By default, the fullscreen button is shown, and this can hidden by providing an empty list.
101103
"""
102104
self.format = format
103105
self.show_legend = show_legend
104106
self.height = height
105107
self.width = width
106108
self.color_map = color_map
107-
self.buttons = buttons or ["fullscreen"]
109+
self.buttons = set_default_buttons(buttons, ["fullscreen"])
108110
self._value_description = "a tuple of type [image: str, annotations: list[tuple[mask: str, label: str]]] where 'image' is the path to the base image and 'annotations' is a list of tuples where each tuple has a 'mask' image filepath and a corresponding label."
109111
super().__init__(
110112
label=label,

gradio/components/audio.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818

1919
from gradio import processing_utils
2020
from gradio.components.base import Component, StreamingInput, StreamingOutput
21+
from gradio.components.button import Button
2122
from gradio.data_classes import FileData, FileDataDict, MediaStreamChunk
2223
from gradio.events import Events
2324
from gradio.i18n import I18nData
25+
from gradio.utils import set_default_buttons
2426

2527
if TYPE_CHECKING:
2628
from gradio.components import Timer
@@ -103,7 +105,7 @@ def __init__(
103105
format: Literal["wav", "mp3"] | None = None,
104106
autoplay: bool = False,
105107
editable: bool = True,
106-
buttons: list[Literal["download", "share"]] | None = None,
108+
buttons: list[Literal["download", "share"] | Button] | None = None,
107109
waveform_options: WaveformOptions | dict | None = None,
108110
loop: bool = False,
109111
recording: bool = False,
@@ -132,7 +134,7 @@ def __init__(
132134
preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
133135
format: the file extension with which to save audio files. Either 'wav' or 'mp3'. wav files are lossless but will tend to be larger files. mp3 files tend to be smaller. This parameter applies both when this component is used as an input (and `type` is "filepath") to determine which file format to convert user-provided audio to, and when this component is used as an output to determine the format of audio returned to the user. If None, no file format conversion is done and the audio is kept as is. In the case where output audio is returned from the prediction function as numpy array and no `format` is provided, it will be returned as a "wav" file.
134136
autoplay: Whether to automatically play the audio when the component is used as an output. Note: browsers will not autoplay audio files if the user has not interacted with the page yet.
135-
buttons: A list of buttons to show in the top right corner of the component. Valid options are "download" and "share". The "download" button allows the user to save the audio to their device. The "share" button allows the user to share the audio via Hugging Face Spaces Discussions. By default, all buttons are shown.
137+
buttons: A list of buttons to show in the top right corner of the component. Valid options are "download", "share", or a gr.Button() instance. The "download" button allows the user to save the audio to their device. The "share" button allows the user to share the audio via Hugging Face Spaces Discussions. Custom gr.Button() instances will appear in the toolbar with their configured icon and/or label, and clicking them will trigger any .click() events registered on the button. By default, only the "download" and "share" buttons are shown.
136138
editable: If True, allows users to manipulate the audio file if the component is interactive. Defaults to True.
137139
waveform_options: A dictionary of options for the waveform display. Options include: waveform_color (str), waveform_progress_color (str), skip_length (int), trim_region_color (str). Default is None, which uses the default values for these options. [See `gr.WaveformOptions` docs](#waveform-options).
138140
loop: If True, the audio will loop when it reaches the end and continue playing from the beginning.
@@ -175,7 +177,7 @@ def __init__(
175177
self.format = format and format.lower()
176178
self.autoplay = autoplay
177179
self.loop = loop
178-
self.buttons = buttons
180+
self.buttons = set_default_buttons(buttons, ["download", "share"])
179181
self.editable = editable
180182
if waveform_options is None:
181183
self.waveform_options = WaveformOptions()

gradio/components/chatbot.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
Component as GradioComponent,
2626
)
2727
from gradio.components.base import Component
28+
from gradio.components.button import Button
2829
from gradio.data_classes import FileData, GradioModel, GradioRootModel
2930
from gradio.events import Events
3031
from gradio.exceptions import Error
3132
from gradio.i18n import I18nData
33+
from gradio.utils import set_default_buttons
3234

3335

3436
@document()
@@ -249,7 +251,7 @@ def __init__(
249251
editable: Literal["user", "all"] | None = None,
250252
latex_delimiters: list[dict[str, str | bool]] | None = None,
251253
rtl: bool = False,
252-
buttons: list[Literal["share", "copy", "copy_all"]] | None = None,
254+
buttons: list[Literal["share", "copy", "copy_all"] | Button] | None = None,
253255
watermark: str | None = None,
254256
avatar_images: tuple[str | Path | None, str | Path | None] | None = None,
255257
sanitize_html: bool = True,
@@ -290,7 +292,7 @@ def __init__(
290292
editable: Allows user to edit messages in the chatbot. If set to "user", allows editing of user messages. If set to "all", allows editing of assistant messages as well.
291293
latex_delimiters: A list of dicts of the form {"left": open delimiter (str), "right": close delimiter (str), "display": whether to display in newline (bool)} that will be used to render LaTeX expressions. If not provided, `latex_delimiters` is set to `[{ "left": "$$", "right": "$$", "display": True }]`, so only expressions enclosed in $$ delimiters will be rendered as LaTeX, and in a new line. Pass in an empty list to disable LaTeX rendering. For more information, see the [KaTeX documentation](https://katex.org/docs/autorender.html).
292294
rtl: If True, sets the direction of the rendered text to right-to-left. Default is False, which renders text left-to-right.
293-
buttons: A list of buttons to show in the top right corner of the component. Valid options are "share", "copy", and "copy_all". The "share" button allows the user to share outputs to Hugging Face Spaces Discussions. The "copy" button makes a copy button appear next to each individual chatbot message. The "copy_all" button appears at the component level and allows the user to copy all chatbot messages. By default, "share" and "copy_all" buttons are shown.
295+
buttons: A list of buttons to show in the top right corner of the component. Valid options are "share", "copy", "copy_all", or a gr.Button() instance. The "share" button allows the user to share outputs to Hugging Face Spaces Discussions. The "copy" button makes a copy button appear next to each individual chatbot message. The "copy_all" button appears at the component level and allows the user to copy all chatbot messages. Custom gr.Button() instances will appear in the toolbar with their configured icon and/or label, and clicking them will trigger any .click() events registered on the button. By default, "share" and "copy_all" buttons are shown.
294296
watermark: If provided, this text will be appended to the end of messages copied from the chatbot, after a blank line. Useful for indicating that the message is generated by an AI model.
295297
avatar_images: Tuple of two avatar image paths or URLs for user and bot (in that order). Pass None for either the user or bot image to skip. Must be within the working directory of the Gradio app or an external URL.
296298
sanitize_html: If False, will disable HTML sanitization for chatbot messages. This is not recommended, as it can lead to security vulnerabilities.
@@ -318,7 +320,7 @@ def __init__(
318320
if latex_delimiters is None:
319321
latex_delimiters = [{"left": "$$", "right": "$$", "display": True}]
320322
self.latex_delimiters = latex_delimiters
321-
self.buttons = buttons
323+
self.buttons = set_default_buttons(buttons, ["share", "copy", "copy_all"])
322324
self.render_markdown = render_markdown
323325
self.watermark = watermark
324326
self.sanitize_html = sanitize_html
@@ -534,12 +536,12 @@ def _postprocess(
534536
self, message: MessageDict | Message | ChatMessage | NormalizedMessageDict
535537
) -> list[Message] | None:
536538
message = copy.deepcopy(message)
537-
role = message["role"] if isinstance(message, dict) else message.role
539+
role = message["role"] if isinstance(message, dict) else message.role # type: ignore[possibly-unbound-attribute]
538540
metadata = (
539-
message.get("metadata") if isinstance(message, dict) else message.metadata
541+
message.get("metadata") if isinstance(message, dict) else message.metadata # type: ignore[possibly-unbound-attribute]
540542
)
541543
options = (
542-
message.get("options") if isinstance(message, dict) else message.options
544+
message.get("options") if isinstance(message, dict) else message.options # type: ignore[possibly-unbound-attribute]
543545
)
544546
if isinstance(message, dict) and not isinstance(message["content"], list):
545547
content_ = self._postprocess_content(

gradio/components/checkbox.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from gradio_client.documentation import document
99

1010
from gradio.components.base import Component, FormComponent
11+
from gradio.components.button import Button
1112
from gradio.events import Events
1213
from gradio.i18n import I18nData
14+
from gradio.utils import set_default_buttons
1315

1416
if TYPE_CHECKING:
1517
from gradio.components import Timer
@@ -45,6 +47,7 @@ def __init__(
4547
render: bool = True,
4648
key: int | str | tuple[int | str, ...] | None = None,
4749
preserved_by_key: list[str] | str | None = "value",
50+
buttons: list[Button] | None = None,
4851
):
4952
"""
5053
Parameters:
@@ -64,6 +67,7 @@ def __init__(
6467
render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
6568
key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
6669
preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
70+
buttons: A list of gr.Button() instances to show in the top right corner of the component. Custom buttons will appear in the toolbar with their configured icon and/or label, and clicking them will trigger any .click() events registered on the button.
6771
"""
6872
super().__init__(
6973
label=label,
@@ -83,6 +87,7 @@ def __init__(
8387
preserved_by_key=preserved_by_key,
8488
value=value,
8589
)
90+
self.buttons = set_default_buttons(buttons, None)
8691

8792
def get_config(self) -> dict[str, Any]:
8893
config = super().get_config()

0 commit comments

Comments
 (0)