From b68aeea412c79efb89c4c16a356839b3b0327b68 Mon Sep 17 00:00:00 2001 From: aliabid94 Date: Thu, 20 Jul 2023 14:44:52 +0300 Subject: [PATCH] Improve `gr.ChatInterface` UI, add autofocus to textbox (#4978) * changes * changes * lint * fix tests * fix --------- Co-authored-by: Abubakar Abid --- CHANGELOG.md | 2 + gradio/chat_interface.py | 95 ++++++++++---------- gradio/components/textbox.py | 6 ++ gradio/test_data/blocks_configs.py | 3 + js/app/src/components/Textbox/Textbox.svelte | 2 + js/form/src/Textbox.svelte | 5 ++ test/test_blocks.py | 2 + test/test_components.py | 1 + 8 files changed, 71 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3451318d..141a1938be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ No changes to highlight. ## New Features: - Add `show_download_button` param to allow the download button in static Image components to be hidden by [@hannahblair](https://github.com/hannahblair) in [PR 4959](https://github.com/gradio-app/gradio/pull/4959) +- Added autofocus argument to Textbox by [@aliabid94](https://github.com/aliabid94) in [PR 4978](https://github.com/gradio-app/gradio/pull/4978) - Use `gr.State` in `gr.ChatInterface` to reduce latency by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4976](https://github.com/gradio-app/gradio/pull/4976) ## Bug Fixes: @@ -18,6 +19,7 @@ No changes to highlight. ## Other Changes: - Apply pyright to the `components` directory by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4948](https://github.com/gradio-app/gradio/pull/4948) +- Improved look of ChatInterface by [@aliabid94](https://github.com/aliabid94) in [PR 4978](https://github.com/gradio-app/gradio/pull/4978) # Version 3.37 diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 22113542e5..8d1c577c27 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -20,7 +20,7 @@ from gradio.components import ( Textbox, ) from gradio.helpers import create_examples as Examples # noqa: N812 -from gradio.layouts import Group, Row +from gradio.layouts import Column, Group, Row from gradio.themes import ThemeClass as Theme set_documentation_group("chatinterface") @@ -110,57 +110,62 @@ class ChatInterface(Blocks): if description: Markdown(description) - with Group(): + with Column(variant="panel"): if chatbot: self.chatbot = chatbot.render() else: self.chatbot = Chatbot(label="Chatbot") + + with Group(): + with Row(): + if textbox: + self.textbox = textbox.render() + else: + self.textbox = Textbox( + container=False, + show_label=False, + placeholder="Type a message...", + scale=10, + autofocus=True, + ) + if submit_btn: + if isinstance(submit_btn, Button): + submit_btn.render() + elif isinstance(submit_btn, str): + submit_btn = Button( + submit_btn, variant="primary", scale=1, min_width=0 + ) + else: + raise ValueError( + f"The submit_btn parameter must be a gr.Button, string, or None, not {type(submit_btn)}" + ) + self.buttons.append(submit_btn) + with Row(): - if textbox: - self.textbox = textbox.render() - else: - self.textbox = Textbox( - container=False, - show_label=False, - placeholder="Type a message...", - scale=10, - ) - if submit_btn: - if isinstance(submit_btn, Button): - submit_btn.render() - elif isinstance(submit_btn, str): - submit_btn = Button( - submit_btn, variant="primary", scale=1, min_width=0 - ) - else: - raise ValueError( - f"The submit_btn parameter must be a gr.Button, string, or None, not {type(submit_btn)}" - ) - self.buttons.append(submit_btn) + self.stop_btn = Button("Stop", variant="stop", visible=False) - with Row(): - self.stop_btn = Button("Stop", variant="stop", visible=False) + for btn in [retry_btn, undo_btn, clear_btn]: + if btn: + if isinstance(btn, Button): + btn.render() + elif isinstance(btn, str): + btn = Button(btn, variant="secondary", size="sm") + else: + raise ValueError( + f"All the _btn parameters must be a gr.Button, string, or None, not {type(btn)}" + ) + self.buttons.append(btn) - for btn in [retry_btn, undo_btn, clear_btn]: - if btn: - if isinstance(btn, Button): - btn.render() - elif isinstance(btn, str): - btn = Button(btn, variant="secondary") - else: - raise ValueError( - f"All the _btn parameters must be a gr.Button, string, or None, not {type(btn)}" - ) - self.buttons.append(btn) - - self.fake_api_btn = Button("Fake API", visible=False) - self.fake_response_textbox = Textbox(label="Response", visible=False) - ( - self.submit_btn, - self.retry_btn, - self.undo_btn, - self.clear_btn, - ) = self.buttons + self.fake_api_btn = Button("Fake API", visible=False) + self.fake_response_textbox = Textbox( + label="Response", visible=False + ) + ( + self.submit_btn, + self.retry_btn, + self.undo_btn, + self.clear_btn, + ) = self.buttons if examples: if inspect.isgeneratorfunction(self.fn): diff --git a/gradio/components/textbox.py b/gradio/components/textbox.py index 5b7699ffdf..156a6d3561 100644 --- a/gradio/components/textbox.py +++ b/gradio/components/textbox.py @@ -66,6 +66,7 @@ class Textbox( interactive: bool | None = None, visible: bool = True, elem_id: str | None = None, + autofocus: bool = False, elem_classes: list[str] | str | None = None, type: Literal["text", "password", "email"] = "text", text_align: Literal["left", "right"] | None = None, @@ -88,6 +89,7 @@ class Textbox( min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first. interactive: if True, will be rendered as an editable textbox; 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. + autofocus: If True, will focus on the textbox when the page loads. 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. elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. type: The type of textbox. One of: 'text', 'password', 'email', Default is 'text'. @@ -105,6 +107,7 @@ class Textbox( self.max_lines = 1 self.placeholder = placeholder self.show_copy_button = show_copy_button + self.autofocus = autofocus self.select: EventListenerMethod """ Event listener for when the user selects text in the Textbox. @@ -139,6 +142,7 @@ class Textbox( "placeholder": self.placeholder, "value": self.value, "type": self.type, + "autofocus": self.autofocus, "show_copy_button": self.show_copy_button, "container": self.container, "text_align": self.text_align, @@ -164,6 +168,7 @@ class Textbox( text_align: Literal["left", "right"] | None = None, rtl: bool | None = None, show_copy_button: bool | None = None, + autofocus: bool | None = None, ): return { "lines": lines, @@ -180,6 +185,7 @@ class Textbox( "type": type, "interactive": interactive, "show_copy_button": show_copy_button, + "autofocus": autofocus, "text_align": text_align, "rtl": rtl, "__type__": "update", diff --git a/gradio/test_data/blocks_configs.py b/gradio/test_data/blocks_configs.py index a2796aaf91..fb16480c68 100644 --- a/gradio/test_data/blocks_configs.py +++ b/gradio/test_data/blocks_configs.py @@ -188,6 +188,7 @@ XRAY_CONFIG = { "id": 14, "type": "textbox", "props": { + "autofocus": False, "lines": 1, "max_lines": 20, "value": "", @@ -508,6 +509,7 @@ XRAY_CONFIG_DIFF_IDS = { "id": 19, "type": "textbox", "props": { + "autofocus": False, "lines": 1, "max_lines": 20, "value": "", @@ -784,6 +786,7 @@ XRAY_CONFIG_WITH_MISTAKE = { "show_copy_button": False, "type": "text", "rtl": False, + "autofocus": False, }, }, ], diff --git a/js/app/src/components/Textbox/Textbox.svelte b/js/app/src/components/Textbox/Textbox.svelte index afa8dc84c3..ada5a139a7 100644 --- a/js/app/src/components/Textbox/Textbox.svelte +++ b/js/app/src/components/Textbox/Textbox.svelte @@ -26,6 +26,7 @@ export let value_is_output: boolean = false; export let rtl = false; export let text_align: "left" | "right" | undefined = undefined; + export let autofocus: boolean = false; @@ -46,6 +47,7 @@ max_lines={!max_lines && mode === "static" ? lines + 1 : max_lines} {placeholder} {show_copy_button} + {autofocus} {container} on:change on:input diff --git a/js/form/src/Textbox.svelte b/js/form/src/Textbox.svelte index b47766e94d..0d768726ce 100644 --- a/js/form/src/Textbox.svelte +++ b/js/form/src/Textbox.svelte @@ -18,6 +18,7 @@ export let type: "text" | "password" | "email" = "text"; export let show_copy_button: boolean = false; export let rtl = false; + export let autofocus: boolean = false; export let text_align: "left" | "right" | undefined = undefined; let el: HTMLTextAreaElement | HTMLInputElement; @@ -150,6 +151,7 @@ bind:this={el} {placeholder} {disabled} + {autofocus} on:keypress={handle_keypress} on:blur={handle_blur} on:select={handle_select} @@ -164,6 +166,7 @@ bind:this={el} {placeholder} {disabled} + {autofocus} on:keypress={handle_keypress} on:blur={handle_blur} on:select={handle_select} @@ -178,6 +181,7 @@ bind:this={el} {placeholder} {disabled} + {autofocus} on:keypress={handle_keypress} on:blur={handle_blur} on:select={handle_select} @@ -202,6 +206,7 @@ {placeholder} rows={lines} {disabled} + {autofocus} on:keypress={handle_keypress} on:blur={handle_blur} on:select={handle_select} diff --git a/test/test_blocks.py b/test/test_blocks.py index 59ba9f1c0f..04c9f44675 100644 --- a/test/test_blocks.py +++ b/test/test_blocks.py @@ -1065,6 +1065,7 @@ class TestSpecificUpdate: "lines": 4, "info": None, "max_lines": None, + "autofocus": None, "placeholder": None, "label": None, "show_label": None, @@ -1093,6 +1094,7 @@ class TestSpecificUpdate: "show_label": None, "container": None, "scale": None, + "autofocus": None, "min_width": None, "visible": None, "value": gr.components._Keywords.NO_VALUE, diff --git a/test/test_components.py b/test/test_components.py index 0711c08564..5f62c5f5d2 100644 --- a/test/test_components.py +++ b/test/test_components.py @@ -105,6 +105,7 @@ class TestTextbox: "root_url": None, "rtl": False, "text_align": None, + "autofocus": False, } @pytest.mark.asyncio