From eafb3d16bd871ae5012f451491e27d08bb2eccb2 Mon Sep 17 00:00:00 2001 From: Ali Abid Date: Wed, 29 May 2024 12:54:01 -0700 Subject: [PATCH 1/9] changes --- client/python/gradio_client/documentation.py | 2 +- demo/render_merge/run.ipynb | 2 +- demo/render_merge/run.py | 6 ++- demo/render_merge_simple/run.ipynb | 1 + demo/render_merge_simple/run.py | 25 ++++++++++ demo/render_split_simple/run.ipynb | 1 + demo/render_split_simple/run.py | 15 ++++++ gradio/renderable.py | 40 ++++++++++++++-- .../04_dynamic-apps-with-render-decorator.md | 46 +++++++++++++++++++ ...-CSS-and-JS.md => 05_custom-CSS-and-JS.md} | 0 ...s.md => 06_using-blocks-like-functions.md} | 0 11 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 demo/render_merge_simple/run.ipynb create mode 100644 demo/render_merge_simple/run.py create mode 100644 demo/render_split_simple/run.ipynb create mode 100644 demo/render_split_simple/run.py create mode 100644 guides/04_building-with-blocks/04_dynamic-apps-with-render-decorator.md rename guides/04_building-with-blocks/{04_custom-CSS-and-JS.md => 05_custom-CSS-and-JS.md} (100%) rename guides/04_building-with-blocks/{05_using-blocks-like-functions.md => 06_using-blocks-like-functions.md} (100%) diff --git a/client/python/gradio_client/documentation.py b/client/python/gradio_client/documentation.py index 2074f14923915..ac4547de4579b 100644 --- a/client/python/gradio_client/documentation.py +++ b/client/python/gradio_client/documentation.py @@ -143,7 +143,7 @@ def document_fn(fn: Callable, cls) -> tuple[str, list[dict], dict, str | None]: print(line) if not (line.startswith(" ") or line.strip() == ""): raise SyntaxError( - f"Documentation format for {fn.__name__} has format error in line: {line}" + f"Documentation format for {fn.__name__} has format error in line: {line} in mode {mode}" ) line = line[4:] if mode == "parameter": diff --git a/demo/render_merge/run.ipynb b/demo/render_merge/run.ipynb index 9e782eb20484d..eeac7ac49b007 100644 --- a/demo/render_merge/run.ipynb +++ b/demo/render_merge/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: render_merge"]}, {"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", "with gr.Blocks() as demo:\n", " text_count = gr.Slider(1, 5, step=1, label=\"Textbox Count\")\n", "\n", " @gr.render(inputs=text_count)\n", " def render_count(count):\n", " boxes = []\n", " for i in range(count):\n", " box = gr.Textbox(key=i, label=f\"Box {i}\")\n", " boxes.append(box)\n", "\n", " def merge(*args):\n", " return \" \".join(args)\n", " \n", " merge_btn.click(merge, boxes, output)\n", "\n", " def clear():\n", " return [\"\"] * count\n", " \n", " clear_btn.click(clear, None, boxes)\n", "\n", " def countup():\n", " return [i for i in range(count)]\n", " \n", " count_btn.click(countup, None, boxes, queue=False)\n", "\n", " with gr.Row():\n", " merge_btn = gr.Button(\"Merge\")\n", " clear_btn = gr.Button(\"Clear\")\n", " count_btn = gr.Button(\"Count\")\n", " \n", " output = gr.Textbox()\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: render_merge"]}, {"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", "with gr.Blocks() as demo:\n", " text_count = gr.Slider(1, 5, step=1, label=\"Textbox Count\")\n", "\n", " @gr.render(inputs=text_count)\n", " def render_count(count):\n", " boxes = []\n", " for i in range(count):\n", " with gr.Row():\n", " box = gr.Textbox(key=i, label=f\"Box {i}\")\n", " repeat_btn = gr.Button(\"Repeat\")\n", " repeat_btn.click(lambda x: x * 2, box, box)\n", " \n", " boxes.append(box)\n", "\n", " def merge(*args):\n", " return \" \".join(args)\n", " \n", " merge_btn.click(merge, boxes, output)\n", "\n", " def clear():\n", " return [\"\"] * count\n", " \n", " clear_btn.click(clear, None, boxes)\n", "\n", " def countup():\n", " return [i for i in range(count)]\n", " \n", " count_btn.click(countup, None, boxes, queue=False)\n", "\n", " with gr.Row():\n", " merge_btn = gr.Button(\"Merge\")\n", " clear_btn = gr.Button(\"Clear\")\n", " count_btn = gr.Button(\"Count\")\n", " \n", " output = gr.Textbox()\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/render_merge/run.py b/demo/render_merge/run.py index 6aec8317dd691..ae39669ea1e97 100644 --- a/demo/render_merge/run.py +++ b/demo/render_merge/run.py @@ -7,7 +7,11 @@ def render_count(count): boxes = [] for i in range(count): - box = gr.Textbox(key=i, label=f"Box {i}") + with gr.Row(): + box = gr.Textbox(key=i, label=f"Box {i}") + repeat_btn = gr.Button("Repeat") + repeat_btn.click(lambda x: x * 2, box, box) + boxes.append(box) def merge(*args): diff --git a/demo/render_merge_simple/run.ipynb b/demo/render_merge_simple/run.ipynb new file mode 100644 index 0000000000000..86202d9833bd5 --- /dev/null +++ b/demo/render_merge_simple/run.ipynb @@ -0,0 +1 @@ +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: render_merge_simple"]}, {"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", "with gr.Blocks() as demo:\n", " text_count = gr.State(1)\n", " add_btn = gr.Button(\"Add Box\")\n", " add_btn.click(lambda x: x + 1, text_count, text_count)\n", "\n", " @gr.render(inputs=text_count)\n", " def render_count(count):\n", " boxes = []\n", " for i in range(count):\n", " box = gr.Textbox(key=i, label=f\"Box {i}\")\n", " boxes.append(box)\n", "\n", " def merge(*args):\n", " return \" \".join(args)\n", " \n", " merge_btn.click(merge, boxes, output)\n", "\n", "\n", " merge_btn = gr.Button(\"Merge\")\n", " output = gr.Textbox(label=\"Merged Output\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/render_merge_simple/run.py b/demo/render_merge_simple/run.py new file mode 100644 index 0000000000000..e912c9b41e764 --- /dev/null +++ b/demo/render_merge_simple/run.py @@ -0,0 +1,25 @@ +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") + +if __name__ == "__main__": + demo.launch() \ No newline at end of file diff --git a/demo/render_split_simple/run.ipynb b/demo/render_split_simple/run.ipynb new file mode 100644 index 0000000000000..c1bbe633fc334 --- /dev/null +++ b/demo/render_split_simple/run.ipynb @@ -0,0 +1 @@ +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: render_split_simple"]}, {"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", "with gr.Blocks() as demo:\n", " input_text = gr.Textbox(label=\"input\")\n", "\n", " @gr.render(inputs=input_text)\n", " def show_split(text):\n", " if len(text) == 0:\n", " gr.Markdown(\"## No Input Provided\")\n", " else:\n", " for letter in text:\n", " gr.Textbox(letter)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/render_split_simple/run.py b/demo/render_split_simple/run.py new file mode 100644 index 0000000000000..6c018f26341d3 --- /dev/null +++ b/demo/render_split_simple/run.py @@ -0,0 +1,15 @@ +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) + +if __name__ == "__main__": + demo.launch() \ No newline at end of file diff --git a/gradio/renderable.py b/gradio/renderable.py index 7f6756bcbc0b7..b4c731a959af0 100644 --- a/gradio/renderable.py +++ b/gradio/renderable.py @@ -8,6 +8,7 @@ from gradio.events import EventListener, EventListenerMethod from gradio.layouts import Column, Row +from gradio_client.documentation import document class Renderable: def __init__( @@ -71,15 +72,48 @@ def apply(self, *args, **kwargs): finally: LocalContext.renderable.set(None) - +@document() def render( inputs: list[Component] | None = None, triggers: list[EventListener] | EventListener | None = None, + *, + queue: bool = True, + trigger_mode: Literal["once", "multiple", "always_last"] | None = "always_last", concurrency_limit: int | None | Literal["default"] = None, concurrency_id: str | None = None, - trigger_mode: Literal["once", "multiple", "always_last"] | None = "always_last", - queue: bool = True, ): + """ + The render decorator allows Gradio Blocks apps to have dynamic layouts, so that the components and event listeners in your app can change depending on custom logic. + Attaching a @gr.render decorator to a function will cause the function to be re-run whenever the inputs are changed (or specified triggers are activated). The function contains the components and event listeners that will update based on the inputs. + + The basic usage of @gr.render is as follows: + 1) Create a function and attach the @gr.render decorator to it. + 2) Add the input components to the `inputs=` argument of @gr.render, and create a corresponding argument in your function for each component. + 3) Add all components inside the function that you want to update based on the inputs. Any event listeners that use these components should also be inside this function. + Parameters: + inputs: List of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list. + triggers: List of triggers to listen to, e.g. [btn.click, number.change]. If None, will listen to changes to any inputs. + queue: If True, will place the request on the queue, if the queue has been enabled. If False, will not put this event on the queue, even if the queue has been enabled. If None, will use the queue setting of the gradio app. + trigger_mode: If "once" (default for all events except `.change()`) would not allow any submissions while an event is pending. If set to "multiple", unlimited submissions are allowed while pending, and "always_last" (default for `.change()` and `.key_up()` events) would allow a second submission after the pending event is complete. + concurrency_limit: If set, this is the maximum number of this event that can be running simultaneously. Can be set to None to mean no concurrency_limit (any number of this event can be running simultaneously). Set to "default" to use the default concurrency limit (defined by the `default_concurrency_limit` parameter in `Blocks.queue()`, which itself is 1 by default). + concurrency_id: If set, this is the id of the concurrency group. Events with the same concurrency_id will be limited by the lowest set concurrency_limit. + Example: + import gradio as gr + + with gr.Blocks() as demo: + input_text = gr.Textbox() + + @gr.render(inputs=input_text) + def show_split(text): + if len(text) == 0: + gr.Markdown("## No Input Provided") + else: + for letter in text: + with gr.Row(): + text = gr.Textbox(letter) + btn = gr.Button("Clear") + btn.click(lambda: gr.Textbox(value=""), None, text) + """ if Context.root_block is None: raise ValueError("Reactive render must be inside a Blocks context.") diff --git a/guides/04_building-with-blocks/04_dynamic-apps-with-render-decorator.md b/guides/04_building-with-blocks/04_dynamic-apps-with-render-decorator.md new file mode 100644 index 0000000000000..edd06db30ed8c --- /dev/null +++ b/guides/04_building-with-blocks/04_dynamic-apps-with-render-decorator.md @@ -0,0 +1,46 @@ +# Dynamic Apps with the Render Decorator + +The components and event listeners you define in a Blocks so far have been fixed - once the demo was launched, new components and listeners could not be added, and existing one could not be removed. + +The `@gr.render` decorator introduces the ability to dynamically change this. Let's take a look. + +## Dynamic Number of Components + +In the example below, we will create a variable number of Textboxes. When the user edits the input Textbox, we create a Textbox for each letter in the input. Try it out below: + +$code_render_split_simple +$demo_render_split_simple + +See how we can now create a variable number of Textboxes using our custom logic - in this case, a simple `for` loop. The `@gr.render` decorator enables this with the following steps: + +1) Create a function and attach the @gr.render decorator to it. +2) Add the input components to the `inputs=` argument of @gr.render, and create a corresponding argument in your function for each component. This function will automatically re-run on any change to a component. +3) Add all components inside the function that you want to render based on the inputs. + +Now whenever the inputs change, the funciton re-runs, and replaces the components created from the previous funciton run with the latest run. Pretty straightforward! Let's add a little more complexity to this app: + +$code_render_split +$demo_render_split + +By default, `@gr.render` re-runs are triggered by the `.load` listener to the app and the `.change` listener to any input component provided. We can override this by explicitly setting the triggers in the decorator, as we have in this app to only trigger on `input_text.submit` instead. +If you are setting custom triggers, and you also want an automatic render at the start of the app, make sure to add `demo.load` to your list of triggers. + +## Dynamic Event Listeners + +If you're creating components, you probably want to attach event listeners to them as well. Let's take a look at an example that takes in a variable number of Textbox as input, and merges all the text into a single box. + +$code_render_merge_simple +$demo_render_merge_simple + +Let's take a look at what's happening here: + +1) The state variable `text_count` is keeping track of the number of Textboxes to create. By increasing it via the Add button, we trigger re-renders as `text_count` is an input to the render decorator. +2) Note that in every single Textbox we create in the render function, we explicitly set a `key=` argument. This key allows us to preserve the value of this Component between re-renders. If you type in a value in a textbox, and then click the Add button, all the Textboxes re-render, but their values aren't cleared because the `key=` maintains the the value of a Component across a render. +3) We've stored the Textboxes created in a list, and provide this list as input to the merge button event listener. Note that **all event listeners that use Components created inside a render function must also be defined inside that render function**. The event listener can still reference Components outside the render function, as we do here by referencing `merge_btn` and `output` which are both defined outside the render function. + +Just as with Components, whenever a function re-renders, the event listeners created from the previous render are cleared and the new event listeners from the latest run are attached. + +This allows us to create highly customizable and complex interactions! Take a look at the example below, which spices up the previous example with a lot more event listeners: + +$code_render_merge +$demo_render_merge diff --git a/guides/04_building-with-blocks/04_custom-CSS-and-JS.md b/guides/04_building-with-blocks/05_custom-CSS-and-JS.md similarity index 100% rename from guides/04_building-with-blocks/04_custom-CSS-and-JS.md rename to guides/04_building-with-blocks/05_custom-CSS-and-JS.md diff --git a/guides/04_building-with-blocks/05_using-blocks-like-functions.md b/guides/04_building-with-blocks/06_using-blocks-like-functions.md similarity index 100% rename from guides/04_building-with-blocks/05_using-blocks-like-functions.md rename to guides/04_building-with-blocks/06_using-blocks-like-functions.md From 91d1092e8ef6a4574336f2ccd022be908a21b0df Mon Sep 17 00:00:00 2001 From: Ali Abid Date: Wed, 29 May 2024 13:00:07 -0700 Subject: [PATCH 2/9] changes --- client/python/gradio_client/documentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/python/gradio_client/documentation.py b/client/python/gradio_client/documentation.py index ac4547de4579b..2074f14923915 100644 --- a/client/python/gradio_client/documentation.py +++ b/client/python/gradio_client/documentation.py @@ -143,7 +143,7 @@ def document_fn(fn: Callable, cls) -> tuple[str, list[dict], dict, str | None]: print(line) if not (line.startswith(" ") or line.strip() == ""): raise SyntaxError( - f"Documentation format for {fn.__name__} has format error in line: {line} in mode {mode}" + f"Documentation format for {fn.__name__} has format error in line: {line}" ) line = line[4:] if mode == "parameter": From 9bf48e83639ea8b1be5cb9918ebf457728a00d7e Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Wed, 29 May 2024 20:00:25 +0000 Subject: [PATCH 3/9] add changeset --- .changeset/rare-places-help.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rare-places-help.md diff --git a/.changeset/rare-places-help.md b/.changeset/rare-places-help.md new file mode 100644 index 0000000000000..b0ca536e1df31 --- /dev/null +++ b/.changeset/rare-places-help.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Render decorator documentation From 95a41efb87cf9747f442d1d55793e01c4714b146 Mon Sep 17 00:00:00 2001 From: aliabd Date: Wed, 29 May 2024 16:20:44 -0400 Subject: [PATCH 4/9] fix dependency loop and documentation group --- client/python/gradio_client/documentation.py | 1 + js/statustracker/package.json | 1 - pnpm-lock.yaml | 3 --- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/client/python/gradio_client/documentation.py b/client/python/gradio_client/documentation.py index 2074f14923915..700e8c62cb019 100644 --- a/client/python/gradio_client/documentation.py +++ b/client/python/gradio_client/documentation.py @@ -58,6 +58,7 @@ def extract_instance_attr_doc(cls, attr): ("gradio.theme", "themes"), ("gradio_client.", "py-client"), ("gradio.utils", "helpers"), + ("gradio.renderable", "renderable") ] diff --git a/js/statustracker/package.json b/js/statustracker/package.json index 60a13108892fb..627ea49b70c16 100644 --- a/js/statustracker/package.json +++ b/js/statustracker/package.json @@ -16,7 +16,6 @@ }, "dependencies": { "@gradio/atoms": "workspace:^", - "@gradio/column": "workspace:^", "@gradio/icons": "workspace:^", "@gradio/utils": "workspace:^" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85faeafadff3b..859b49aa600b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1626,9 +1626,6 @@ importers: '@gradio/atoms': specifier: workspace:^ version: link:../atoms - '@gradio/column': - specifier: workspace:^ - version: link:../column '@gradio/icons': specifier: workspace:^ version: link:../icons From dc59442f5809001a1c6a916473acdc6173c6dc07 Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Wed, 29 May 2024 20:21:44 +0000 Subject: [PATCH 5/9] add changeset --- .changeset/rare-places-help.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/rare-places-help.md b/.changeset/rare-places-help.md index b0ca536e1df31..a4db04f038ece 100644 --- a/.changeset/rare-places-help.md +++ b/.changeset/rare-places-help.md @@ -1,5 +1,7 @@ --- +"@gradio/statustracker": minor "gradio": minor +"gradio_client": minor --- feat:Render decorator documentation From dad77ffc574df66e09cfa1256bdcaed1e95b5fab Mon Sep 17 00:00:00 2001 From: Ali Abid Date: Wed, 29 May 2024 13:29:24 -0700 Subject: [PATCH 6/9] changes --- gradio/renderable.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gradio/renderable.py b/gradio/renderable.py index b4c731a959af0..ab431143231b1 100644 --- a/gradio/renderable.py +++ b/gradio/renderable.py @@ -2,13 +2,14 @@ from typing import Callable, Literal +from gradio_client.documentation import document + from gradio.blocks import Block from gradio.components import Component from gradio.context import Context, LocalContext from gradio.events import EventListener, EventListenerMethod from gradio.layouts import Column, Row -from gradio_client.documentation import document class Renderable: def __init__( @@ -72,6 +73,7 @@ def apply(self, *args, **kwargs): finally: LocalContext.renderable.set(None) + @document() def render( inputs: list[Component] | None = None, @@ -86,7 +88,7 @@ def render( The render decorator allows Gradio Blocks apps to have dynamic layouts, so that the components and event listeners in your app can change depending on custom logic. Attaching a @gr.render decorator to a function will cause the function to be re-run whenever the inputs are changed (or specified triggers are activated). The function contains the components and event listeners that will update based on the inputs. - The basic usage of @gr.render is as follows: + The basic usage of @gr.render is as follows: 1) Create a function and attach the @gr.render decorator to it. 2) Add the input components to the `inputs=` argument of @gr.render, and create a corresponding argument in your function for each component. 3) Add all components inside the function that you want to update based on the inputs. Any event listeners that use these components should also be inside this function. @@ -113,7 +115,7 @@ def show_split(text): text = gr.Textbox(letter) btn = gr.Button("Clear") btn.click(lambda: gr.Textbox(value=""), None, text) - """ + """ if Context.root_block is None: raise ValueError("Reactive render must be inside a Blocks context.") From e8e9983ec9144e5b98d8157cc43d45a79fde09d6 Mon Sep 17 00:00:00 2001 From: aliabd Date: Wed, 29 May 2024 16:30:44 -0400 Subject: [PATCH 7/9] fix numbered list --- .../04_dynamic-apps-with-render-decorator.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guides/04_building-with-blocks/04_dynamic-apps-with-render-decorator.md b/guides/04_building-with-blocks/04_dynamic-apps-with-render-decorator.md index edd06db30ed8c..a2eb53ffef123 100644 --- a/guides/04_building-with-blocks/04_dynamic-apps-with-render-decorator.md +++ b/guides/04_building-with-blocks/04_dynamic-apps-with-render-decorator.md @@ -13,9 +13,9 @@ $demo_render_split_simple See how we can now create a variable number of Textboxes using our custom logic - in this case, a simple `for` loop. The `@gr.render` decorator enables this with the following steps: -1) Create a function and attach the @gr.render decorator to it. -2) Add the input components to the `inputs=` argument of @gr.render, and create a corresponding argument in your function for each component. This function will automatically re-run on any change to a component. -3) Add all components inside the function that you want to render based on the inputs. +1. Create a function and attach the @gr.render decorator to it. +2. Add the input components to the `inputs=` argument of @gr.render, and create a corresponding argument in your function for each component. This function will automatically re-run on any change to a component. +3. Add all components inside the function that you want to render based on the inputs. Now whenever the inputs change, the funciton re-runs, and replaces the components created from the previous funciton run with the latest run. Pretty straightforward! Let's add a little more complexity to this app: From d6db975b345f6f092e4bddc5d0c0c9d108f2e3f8 Mon Sep 17 00:00:00 2001 From: Ali Abid Date: Wed, 29 May 2024 13:41:27 -0700 Subject: [PATCH 8/9] changes --- client/python/gradio_client/documentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/python/gradio_client/documentation.py b/client/python/gradio_client/documentation.py index 700e8c62cb019..03f08ceae0e0b 100644 --- a/client/python/gradio_client/documentation.py +++ b/client/python/gradio_client/documentation.py @@ -58,7 +58,7 @@ def extract_instance_attr_doc(cls, attr): ("gradio.theme", "themes"), ("gradio_client.", "py-client"), ("gradio.utils", "helpers"), - ("gradio.renderable", "renderable") + ("gradio.renderable", "renderable"), ] From 4997e3efb337fbfba24ccec53a890e984138f7c1 Mon Sep 17 00:00:00 2001 From: Ali Abid Date: Wed, 29 May 2024 13:42:50 -0700 Subject: [PATCH 9/9] changes --- demo/audio_mixer/run.ipynb | 1 + demo/audio_mixer/run.py | 18 ++++++++++++++++++ demo/render_merge/run.ipynb | 2 +- demo/render_merge/run.py | 6 +----- 4 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 demo/audio_mixer/run.ipynb create mode 100644 demo/audio_mixer/run.py diff --git a/demo/audio_mixer/run.ipynb b/demo/audio_mixer/run.ipynb new file mode 100644 index 0000000000000..898d044e5a380 --- /dev/null +++ b/demo/audio_mixer/run.ipynb @@ -0,0 +1 @@ +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: audio_mixer"]}, {"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", "with gr.Blocks() as demo:\n", " track_count = gr.State(1)\n", " add_track_btn = gr.Button(\"Add Track\")\n", "\n", " @gr.render(inputs=track_count)\n", " def render_tracks(count):\n", " tracks = []\n", " with gr.Row():\n", " for i in range(count):\n", " with gr.Column(variant=\"panel\", scale=0):\n", " track_name = gr.Textbox(placeholder=\"Track Name\", key=f\"name-{i}\", show_label=False)\n", " track_audio = gr.Audio(label=f\"Track {i}\", key=f\"track-{i}\")\n", " track_volume = gr.Slider(0, 1, step=0.01, label=\"Volume\", key=f\"volume-{i}\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/audio_mixer/run.py b/demo/audio_mixer/run.py new file mode 100644 index 0000000000000..6a19f2f6bedf1 --- /dev/null +++ b/demo/audio_mixer/run.py @@ -0,0 +1,18 @@ +import gradio as gr + +with gr.Blocks() as demo: + track_count = gr.State(1) + add_track_btn = gr.Button("Add Track") + + @gr.render(inputs=track_count) + def render_tracks(count): + tracks = [] + with gr.Row(): + for i in range(count): + with gr.Column(variant="panel", scale=0): + track_name = 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, 1, step=0.01, label="Volume", key=f"volume-{i}") + +if __name__ == "__main__": + demo.launch() \ No newline at end of file diff --git a/demo/render_merge/run.ipynb b/demo/render_merge/run.ipynb index eeac7ac49b007..2572128f0de15 100644 --- a/demo/render_merge/run.ipynb +++ b/demo/render_merge/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: render_merge"]}, {"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", "with gr.Blocks() as demo:\n", " text_count = gr.Slider(1, 5, step=1, label=\"Textbox Count\")\n", "\n", " @gr.render(inputs=text_count)\n", " def render_count(count):\n", " boxes = []\n", " for i in range(count):\n", " with gr.Row():\n", " box = gr.Textbox(key=i, label=f\"Box {i}\")\n", " repeat_btn = gr.Button(\"Repeat\")\n", " repeat_btn.click(lambda x: x * 2, box, box)\n", " \n", " boxes.append(box)\n", "\n", " def merge(*args):\n", " return \" \".join(args)\n", " \n", " merge_btn.click(merge, boxes, output)\n", "\n", " def clear():\n", " return [\"\"] * count\n", " \n", " clear_btn.click(clear, None, boxes)\n", "\n", " def countup():\n", " return [i for i in range(count)]\n", " \n", " count_btn.click(countup, None, boxes, queue=False)\n", "\n", " with gr.Row():\n", " merge_btn = gr.Button(\"Merge\")\n", " clear_btn = gr.Button(\"Clear\")\n", " count_btn = gr.Button(\"Count\")\n", " \n", " output = gr.Textbox()\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: render_merge"]}, {"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", "with gr.Blocks() as demo:\n", " text_count = gr.Slider(1, 5, step=1, label=\"Textbox Count\")\n", "\n", " @gr.render(inputs=text_count)\n", " def render_count(count):\n", " boxes = []\n", " for i in range(count):\n", " box = gr.Textbox(key=i, label=f\"Box {i}\") \n", " boxes.append(box)\n", "\n", " def merge(*args):\n", " return \" \".join(args)\n", " \n", " merge_btn.click(merge, boxes, output)\n", "\n", " def clear():\n", " return [\"\"] * count\n", " \n", " clear_btn.click(clear, None, boxes)\n", "\n", " def countup():\n", " return [i for i in range(count)]\n", " \n", " count_btn.click(countup, None, boxes, queue=False)\n", "\n", " with gr.Row():\n", " merge_btn = gr.Button(\"Merge\")\n", " clear_btn = gr.Button(\"Clear\")\n", " count_btn = gr.Button(\"Count\")\n", " \n", " output = gr.Textbox()\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/render_merge/run.py b/demo/render_merge/run.py index ae39669ea1e97..6fe922b37d429 100644 --- a/demo/render_merge/run.py +++ b/demo/render_merge/run.py @@ -7,11 +7,7 @@ def render_count(count): boxes = [] for i in range(count): - with gr.Row(): - box = gr.Textbox(key=i, label=f"Box {i}") - repeat_btn = gr.Button("Repeat") - repeat_btn.click(lambda x: x * 2, box, box) - + box = gr.Textbox(key=i, label=f"Box {i}") boxes.append(box) def merge(*args):