From b6bca34a1092a024985428ad703270c592ae1359 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Thu, 12 Dec 2024 16:39:22 -0800 Subject: [PATCH 01/69] save history prototype --- gradio/chat_interface.py | 237 ++++++++++++++++++++++++--------------- 1 file changed, 148 insertions(+), 89 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 84eef5e5f99a4..c5a3bcd72fffc 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -15,9 +15,10 @@ import anyio from gradio_client.documentation import document -from gradio import utils +from gradio import render, utils from gradio.blocks import Blocks from gradio.components import ( + HTML, Button, Chatbot, Component, @@ -42,6 +43,39 @@ from gradio.routes import Request from gradio.themes import ThemeClass as Theme +save_history_css = """ + +
+Local chat history +
+""" @document() class ChatInterface(Blocks): @@ -99,6 +133,7 @@ def __init__( fill_height: bool = True, fill_width: bool = False, api_name: str | Literal[False] = "chat", + save_history: bool = False, ): """ Parameters: @@ -215,100 +250,124 @@ def __init__( ) if description: Markdown(description) - if chatbot: - if self.type: - if self.type != chatbot.type: - warnings.warn( - "The type of the gr.Chatbot does not match the type of the gr.ChatInterface." - f"The type of the gr.ChatInterface, '{self.type}', will be used." + with Row(): + if save_history: + with Column(scale=1, min_width=100): + HTML(save_history_css, container=True, padding=False) + history = [ + "Hello how are you?", + "Explain the concept of AI to a 5 year old", + "If a woodchuck could chuck wood how much wood would a woodchuck chuck?", + ] + state = State(history) + with Group(): + @render(inputs=state) + def create_history(history): + for message in history: + h = HTML(message, padding=False, elem_classes=["_gradio-save-history"]) + h.click(lambda x:x, h, self.textbox) + + with Column(scale=6): + if chatbot: + if self.type: + if self.type != chatbot.type: + warnings.warn( + "The type of the gr.Chatbot does not match the type of the gr.ChatInterface." + f"The type of the gr.ChatInterface, '{self.type}', will be used." + ) + chatbot.type = self.type + chatbot._setup_data_model() + else: + warnings.warn( + f"The gr.ChatInterface was not provided with a type, so the type of the gr.Chatbot, '{chatbot.type}', will be used." + ) + self.type = chatbot.type + self.chatbot = cast( + Chatbot, get_component_instance(chatbot, render=True) ) - chatbot.type = self.type - chatbot._setup_data_model() - else: - warnings.warn( - f"The gr.ChatInterface was not provided with a type, so the type of the gr.Chatbot, '{chatbot.type}', will be used." - ) - self.type = chatbot.type - self.chatbot = cast( - Chatbot, get_component_instance(chatbot, render=True) - ) - if self.chatbot.examples and self.examples_messages: - warnings.warn( - "The ChatInterface already has examples set. The examples provided in the chatbot will be ignored." - ) - self.chatbot.examples = ( - self.examples_messages - if not self._additional_inputs_in_examples - else None - ) - self.chatbot._setup_examples() - else: - self.type = self.type or "tuples" - self.chatbot = Chatbot( - label="Chatbot", - scale=1, - height=200 if fill_height else None, - type=self.type, - autoscroll=autoscroll, - examples=self.examples_messages - if not self._additional_inputs_in_examples - else None, - ) - with Group(): - with Row(): - if textbox: - textbox.show_label = False - textbox_ = get_component_instance(textbox, render=True) - if not isinstance(textbox_, (Textbox, MultimodalTextbox)): - raise TypeError( - f"Expected a gr.Textbox or gr.MultimodalTextbox component, but got {builtins.type(textbox_)}" + if self.chatbot.examples and self.examples_messages: + warnings.warn( + "The ChatInterface already has examples set. The examples provided in the chatbot will be ignored." ) - self.textbox = textbox_ + self.chatbot.examples = ( + self.examples_messages + if not self._additional_inputs_in_examples + else None + ) + self.chatbot._setup_examples() else: - textbox_component = ( - MultimodalTextbox if self.multimodal else Textbox + self.type = self.type or "tuples" + self.chatbot = Chatbot( + label="Chatbot", + scale=1, + height=400 if fill_height else None, + type=self.type, + autoscroll=autoscroll, + examples=self.examples_messages + if not self._additional_inputs_in_examples + else None, ) - self.textbox = textbox_component( - show_label=False, - label="Message", - placeholder="Type a message...", - scale=7, - autofocus=autofocus, - submit_btn=submit_btn, - stop_btn=stop_btn, + with Group(): + with Row(): + if textbox: + textbox.show_label = False + textbox_ = get_component_instance( + textbox, render=True + ) + if not isinstance( + textbox_, (Textbox, MultimodalTextbox) + ): + raise TypeError( + f"Expected a gr.Textbox or gr.MultimodalTextbox component, but got {builtins.type(textbox_)}" + ) + self.textbox = textbox_ + else: + textbox_component = ( + MultimodalTextbox + if self.multimodal + else Textbox + ) + self.textbox = textbox_component( + show_label=False, + label="Message", + placeholder="Type a message...", + scale=7, + autofocus=autofocus, + submit_btn=submit_btn, + stop_btn=stop_btn, + ) + + # Hide the stop button at the beginning, and show it with the given value during the generator execution. + self.original_stop_btn = self.textbox.stop_btn + self.textbox.stop_btn = False + + self.fake_api_btn = Button("Fake API", visible=False) + self.fake_response_textbox = Textbox( + label="Response", visible=False + ) # Used to store the response from the API call + + if self.examples: + self.examples_handler = Examples( + examples=self.examples, + inputs=[self.textbox] + self.additional_inputs, + outputs=self.chatbot, + fn=self._examples_stream_fn + if self.is_generator + else self._examples_fn, + cache_examples=self.cache_examples, + cache_mode=self.cache_mode, + visible=self._additional_inputs_in_examples, + preprocess=self._additional_inputs_in_examples, ) - # Hide the stop button at the beginning, and show it with the given value during the generator execution. - self.original_stop_btn = self.textbox.stop_btn - self.textbox.stop_btn = False - - self.fake_api_btn = Button("Fake API", visible=False) - self.fake_response_textbox = Textbox( - label="Response", visible=False - ) # Used to store the response from the API call - - if self.examples: - self.examples_handler = Examples( - examples=self.examples, - inputs=[self.textbox] + self.additional_inputs, - outputs=self.chatbot, - fn=self._examples_stream_fn - if self.is_generator - else self._examples_fn, - cache_examples=self.cache_examples, - cache_mode=self.cache_mode, - visible=self._additional_inputs_in_examples, - preprocess=self._additional_inputs_in_examples, - ) - - any_unrendered_inputs = any( - not inp.is_rendered for inp in self.additional_inputs - ) - if self.additional_inputs and any_unrendered_inputs: - with Accordion(**self.additional_inputs_accordion_params): # type: ignore - for input_component in self.additional_inputs: - if not input_component.is_rendered: - input_component.render() + any_unrendered_inputs = any( + not inp.is_rendered for inp in self.additional_inputs + ) + if self.additional_inputs and any_unrendered_inputs: + with Accordion(**self.additional_inputs_accordion_params): # type: ignore + for input_component in self.additional_inputs: + if not input_component.is_rendered: + input_component.render() self.saved_input = State() # Stores the most recent user message self.chatbot_state = ( From a846b386326e9d7bda1fb30d4df7e0aa638cb491 Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Fri, 13 Dec 2024 00:41:15 +0000 Subject: [PATCH 02/69] add changeset --- .changeset/tiny-areas-train.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tiny-areas-train.md diff --git a/.changeset/tiny-areas-train.md b/.changeset/tiny-areas-train.md new file mode 100644 index 0000000000000..18c433d9b81c4 --- /dev/null +++ b/.changeset/tiny-areas-train.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Save chat history WIP From 4572a74d5008ec0caab24bf4ec0a77a98b0a115c Mon Sep 17 00:00:00 2001 From: Dmitry Ustalov Date: Mon, 23 Dec 2024 23:33:22 +0100 Subject: [PATCH 03/69] Declare exports in __all__ for type checking (#10238) * Declare exports * add changeset * type fixes * more type fixes * add changeset * notebooks * changes --------- Co-authored-by: gradio-pr-bot Co-authored-by: Freddy Boulton Co-authored-by: Abubakar Abid --- .changeset/young-geckos-brake.md | 6 + client/python/gradio_client/client.py | 4 +- demo/agent_chatbot/run.ipynb | 2 +- demo/agent_chatbot/run.py | 4 +- gradio/__init__.py | 118 ++++++++++++++++++ gradio/blocks.py | 5 +- gradio/cli/commands/components/_docs_utils.py | 2 +- gradio/components/native_plot.py | 8 +- gradio/components/video.py | 2 +- gradio/external.py | 7 +- gradio/themes/base.py | 3 +- gradio/utils.py | 2 +- test/components/test_gallery.py | 2 +- test/test_utils.py | 2 +- 14 files changed, 144 insertions(+), 23 deletions(-) create mode 100644 .changeset/young-geckos-brake.md diff --git a/.changeset/young-geckos-brake.md b/.changeset/young-geckos-brake.md new file mode 100644 index 0000000000000..a350d60010dac --- /dev/null +++ b/.changeset/young-geckos-brake.md @@ -0,0 +1,6 @@ +--- +"gradio": patch +"gradio_client": patch +--- + +fix:Declare exports in __all__ for type checking diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index 36e17a1a8cef0..9f8ca4597b844 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -1356,8 +1356,8 @@ def _upload_file(self, f: dict, data_index: int) -> dict[str, str]: f"File {file_path} exceeds the maximum file size of {max_file_size} bytes " f"set in {component_config.get('label', '') + ''} component." ) - with open(file_path, "rb") as f: - files = [("files", (orig_name.name, f))] + with open(file_path, "rb") as f_: + files = [("files", (orig_name.name, f_))] r = httpx.post( self.client.upload_url, headers=self.client.headers, diff --git a/demo/agent_chatbot/run.ipynb b/demo/agent_chatbot/run.ipynb index ccf9c2fe27edc..aec9695588921 100644 --- a/demo/agent_chatbot/run.ipynb +++ b/demo/agent_chatbot/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: agent_chatbot"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio transformers>=4.47.0"]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from dataclasses import asdict\n", "from transformers import Tool, ReactCodeAgent # type: ignore\n", "from transformers.agents import stream_to_gradio, HfApiEngine # type: ignore\n", "\n", "# Import tool from Hub\n", "image_generation_tool = Tool.from_space(\n", " space_id=\"black-forest-labs/FLUX.1-schnell\",\n", " name=\"image_generator\",\n", " description=\"Generates an image following your prompt. Returns a PIL Image.\",\n", " api_name=\"/infer\",\n", ")\n", "\n", "llm_engine = HfApiEngine(\"Qwen/Qwen2.5-Coder-32B-Instruct\")\n", "# Initialize the agent with both tools and engine\n", "agent = ReactCodeAgent(tools=[image_generation_tool], llm_engine=llm_engine)\n", "\n", "\n", "def interact_with_agent(prompt, history):\n", " messages = []\n", " yield messages\n", " for msg in stream_to_gradio(agent, prompt):\n", " messages.append(asdict(msg))\n", " yield messages\n", " yield messages\n", "\n", "\n", "demo = gr.ChatInterface(\n", " interact_with_agent,\n", " chatbot= gr.Chatbot(\n", " label=\"Agent\",\n", " type=\"messages\",\n", " avatar_images=(\n", " None,\n", " \"https://em-content.zobj.net/source/twitter/53/robot-face_1f916.png\",\n", " ),\n", " ),\n", " examples=[\n", " [\"Generate an image of an astronaut riding an alligator\"],\n", " [\"I am writing a children's book for my daughter. Can you help me with some illustrations?\"],\n", " ],\n", " type=\"messages\",\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: agent_chatbot"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio transformers>=4.47.0"]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from dataclasses import asdict\n", "from transformers import Tool, ReactCodeAgent # type: ignore\n", "from transformers.agents import stream_to_gradio, HfApiEngine # type: ignore\n", "\n", "# Import tool from Hub\n", "image_generation_tool = Tool.from_space( # type: ignore\n", " space_id=\"black-forest-labs/FLUX.1-schnell\",\n", " name=\"image_generator\",\n", " description=\"Generates an image following your prompt. Returns a PIL Image.\",\n", " api_name=\"/infer\",\n", ")\n", "\n", "llm_engine = HfApiEngine(\"Qwen/Qwen2.5-Coder-32B-Instruct\")\n", "# Initialize the agent with both tools and engine\n", "agent = ReactCodeAgent(tools=[image_generation_tool], llm_engine=llm_engine)\n", "\n", "\n", "def interact_with_agent(prompt, history):\n", " messages = []\n", " yield messages\n", " for msg in stream_to_gradio(agent, prompt):\n", " messages.append(asdict(msg)) # type: ignore\n", " yield messages\n", " yield messages\n", "\n", "\n", "demo = gr.ChatInterface(\n", " interact_with_agent,\n", " chatbot= gr.Chatbot(\n", " label=\"Agent\",\n", " type=\"messages\",\n", " avatar_images=(\n", " None,\n", " \"https://em-content.zobj.net/source/twitter/53/robot-face_1f916.png\",\n", " ),\n", " ),\n", " examples=[\n", " [\"Generate an image of an astronaut riding an alligator\"],\n", " [\"I am writing a children's book for my daughter. Can you help me with some illustrations?\"],\n", " ],\n", " type=\"messages\",\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/agent_chatbot/run.py b/demo/agent_chatbot/run.py index 4e6cf36214536..7467971e88245 100644 --- a/demo/agent_chatbot/run.py +++ b/demo/agent_chatbot/run.py @@ -4,7 +4,7 @@ from transformers.agents import stream_to_gradio, HfApiEngine # type: ignore # Import tool from Hub -image_generation_tool = Tool.from_space( +image_generation_tool = Tool.from_space( # type: ignore space_id="black-forest-labs/FLUX.1-schnell", name="image_generator", description="Generates an image following your prompt. Returns a PIL Image.", @@ -20,7 +20,7 @@ def interact_with_agent(prompt, history): messages = [] yield messages for msg in stream_to_gradio(agent, prompt): - messages.append(asdict(msg)) + messages.append(asdict(msg)) # type: ignore yield messages yield messages diff --git a/gradio/__init__.py b/gradio/__init__.py index 9375c4c512e4e..e5c0ae975313b 100644 --- a/gradio/__init__.py +++ b/gradio/__init__.py @@ -119,3 +119,121 @@ from gradio.ipython_ext import load_ipython_extension __version__ = get_package_version() + +__all__ = [ + "Accordion", + "AnnotatedImage", + "Annotatedimage", + "Audio", + "BarPlot", + "Blocks", + "BrowserState", + "Brush", + "Button", + "CSVLogger", + "ChatInterface", + "ChatMessage", + "Chatbot", + "Checkbox", + "CheckboxGroup", + "Checkboxgroup", + "ClearButton", + "Code", + "ColorPicker", + "Column", + "CopyData", + "DataFrame", + "Dataframe", + "Dataset", + "DateTime", + "DeletedFileData", + "DownloadButton", + "DownloadData", + "Dropdown", + "DuplicateButton", + "EditData", + "Eraser", + "Error", + "EventData", + "Examples", + "File", + "FileData", + "FileExplorer", + "FileSize", + "Files", + "FlaggingCallback", + "Gallery", + "Group", + "HTML", + "Highlight", + "HighlightedText", + "Highlightedtext", + "IS_WASM", + "Image", + "ImageEditor", + "ImageMask", + "Info", + "Interface", + "JSON", + "Json", + "KeyUpData", + "Label", + "LikeData", + "LinePlot", + "List", + "LoginButton", + "Markdown", + "Matrix", + "MessageDict", + "Mic", + "Microphone", + "Model3D", + "MultimodalTextbox", + "NO_RELOAD", + "Number", + "Numpy", + "OAuthProfile", + "OAuthToken", + "Paint", + "ParamViewer", + "PlayableVideo", + "Plot", + "Progress", + "Radio", + "Request", + "RetryData", + "Row", + "ScatterPlot", + "SelectData", + "SimpleCSVLogger", + "Sketchpad", + "Slider", + "State", + "Tab", + "TabItem", + "TabbedInterface", + "Tabs", + "Text", + "TextArea", + "Textbox", + "Theme", + "Timer", + "UndoData", + "UploadButton", + "Video", + "Warning", + "WaveformOptions", + "__version__", + "close_all", + "deploy", + "get_package_version", + "load", + "load_chat", + "load_ipython_extension", + "mount_gradio_app", + "on", + "render", + "set_static_paths", + "skip", + "update", +] diff --git a/gradio/blocks.py b/gradio/blocks.py index 8aabdbfb418e1..b4f227822d894 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -1475,10 +1475,7 @@ def is_callable(self, fn_index: int = 0) -> bool: return False if any(block.stateful for block in dependency.inputs): return False - if any(block.stateful for block in dependency.outputs): - return False - - return True + return not any(block.stateful for block in dependency.outputs) def __call__(self, *inputs, fn_index: int = 0, api_name: str | None = None): """ diff --git a/gradio/cli/commands/components/_docs_utils.py b/gradio/cli/commands/components/_docs_utils.py index 9b61b76889149..9cf879e491df5 100644 --- a/gradio/cli/commands/components/_docs_utils.py +++ b/gradio/cli/commands/components/_docs_utils.py @@ -71,7 +71,7 @@ def get_param_name(param): def format_none(value): """Formats None and NonType values.""" - if value is None or value is type(None) or value == "None" or value == "NoneType": + if value is None or value is type(None) or value in ("None", "NoneType"): return "None" return value diff --git a/gradio/components/native_plot.py b/gradio/components/native_plot.py index 9016febdb17e8..dfe9a675d7a5e 100644 --- a/gradio/components/native_plot.py +++ b/gradio/components/native_plot.py @@ -147,10 +147,10 @@ def __init__( every=every, inputs=inputs, ) - for key, val in kwargs.items(): - if key == "color_legend_title": + for key_, val in kwargs.items(): + if key_ == "color_legend_title": self.color_title = val - if key in [ + if key_ in [ "stroke_dash", "overlay_point", "x_label_angle", @@ -161,7 +161,7 @@ def __init__( "width", ]: warnings.warn( - f"Argument '{key}' has been deprecated.", DeprecationWarning + f"Argument '{key_}' has been deprecated.", DeprecationWarning ) def get_block_name(self) -> str: diff --git a/gradio/components/video.py b/gradio/components/video.py index 83a0078c07da2..f84471e65c77c 100644 --- a/gradio/components/video.py +++ b/gradio/components/video.py @@ -276,7 +276,7 @@ def postprocess( """ if self.streaming: return value # type: ignore - if value is None or value == [None, None] or value == (None, None): + if value is None or value in ([None, None], (None, None)): return None if isinstance(value, (str, Path)): processed_files = (self._format_video(value), None) diff --git a/gradio/external.py b/gradio/external.py index d410dcdce304b..2e2d0ccf76860 100644 --- a/gradio/external.py +++ b/gradio/external.py @@ -615,7 +615,7 @@ def load_chat( [{"role": "system", "content": system_message}] if system_message else [] ) - def open_api(message: str, history: list | None) -> str: + def open_api(message: str, history: list | None) -> str | None: history = history or start_message if len(history) > 0 and isinstance(history[0], (list, tuple)): history = ChatInterface._tuples_to_messages(history) @@ -641,7 +641,8 @@ def open_api_stream( ) response = "" for chunk in stream: - response += chunk.choices[0].delta.content - yield response + if chunk.choices[0].delta.content is not None: + response += chunk.choices[0].delta.content + yield response return ChatInterface(open_api_stream if streaming else open_api, type="messages") diff --git a/gradio/themes/base.py b/gradio/themes/base.py index 51230be69623d..31d571c49d959 100644 --- a/gradio/themes/base.py +++ b/gradio/themes/base.py @@ -126,8 +126,7 @@ def to_dict(self): if ( not prop.startswith("_") or prop.startswith("_font") - or prop == "_stylesheets" - or prop == "name" + or prop in ("_stylesheets", "name") ) and isinstance(getattr(self, prop), (list, str)): schema["theme"][prop] = getattr(self, prop) return schema diff --git a/gradio/utils.py b/gradio/utils.py index 7ad4baa84da78..c6d5ba5a6aace 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -1244,7 +1244,7 @@ def compare_objects(obj1, obj2, path=None): if obj1 == obj2: return edits - if type(obj1) != type(obj2): + if type(obj1) is not type(obj2): edits.append(("replace", path, obj2)) return edits diff --git a/test/components/test_gallery.py b/test/components/test_gallery.py index 9b9ca67022461..eac9ce763a596 100644 --- a/test/components/test_gallery.py +++ b/test/components/test_gallery.py @@ -126,5 +126,5 @@ def test_gallery_format(self): output = gallery.postprocess( [np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)] ) - if type(output.root[0]) == GalleryImage: + if isinstance(output.root[0], GalleryImage): assert output.root[0].image.path.endswith(".jpeg") diff --git a/test/test_utils.py b/test/test_utils.py index 7b28c101a2a8a..7f60554bc28c6 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -304,7 +304,7 @@ class GenericObject: for x in test_objs: hints = get_type_hints(x) assert len(hints) == 1 - assert hints["s"] == str + assert hints["s"] is str assert len(get_type_hints(GenericObject())) == 0 From bcd383e850e4df7bf6dde901ba86bd11d2b94f57 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 23 Dec 2024 18:19:46 -0500 Subject: [PATCH 04/69] Add `gr.BrowserState` change event (#10245) * changes * changes * add changeset * format * changes --------- Co-authored-by: gradio-pr-bot --- .changeset/solid-moments-mate.md | 6 ++++++ demo/browserstate/run.ipynb | 2 +- demo/browserstate/run.py | 11 ++++++++++- gradio/components/browser_state.py | 2 ++ js/browserstate/Index.svelte | 12 +++++++++++- js/browserstate/package.json | 3 ++- pnpm-lock.yaml | 3 +++ 7 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .changeset/solid-moments-mate.md diff --git a/.changeset/solid-moments-mate.md b/.changeset/solid-moments-mate.md new file mode 100644 index 0000000000000..19474197b2391 --- /dev/null +++ b/.changeset/solid-moments-mate.md @@ -0,0 +1,6 @@ +--- +"@gradio/browserstate": minor +"gradio": minor +--- + +feat:Add `gr.BrowserState` change event diff --git a/demo/browserstate/run.ipynb b/demo/browserstate/run.ipynb index b3fc96115affb..0f5b3fa80b1ea 100644 --- a/demo/browserstate/run.ipynb +++ b/demo/browserstate/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: browserstate"]}, {"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 random\n", "import string\n", "import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " gr.Markdown(\"Your Username and Password will get saved in the browser's local storage. \"\n", " \"If you refresh the page, the values will be retained.\")\n", " username = gr.Textbox(label=\"Username\")\n", " password = gr.Textbox(label=\"Password\", type=\"password\")\n", " btn = gr.Button(\"Generate Randomly\")\n", " local_storage = gr.BrowserState([\"\", \"\"])\n", "\n", " @btn.click(outputs=[username, password])\n", " def generate_randomly():\n", " u = \"\".join(random.choices(string.ascii_letters + string.digits, k=10))\n", " p = \"\".join(random.choices(string.ascii_letters + string.digits, k=10))\n", " return u, p\n", "\n", " @demo.load(inputs=[local_storage], outputs=[username, password])\n", " def load_from_local_storage(saved_values):\n", " print(\"loading from local storage\", saved_values)\n", " return saved_values[0], saved_values[1]\n", "\n", " @gr.on([username.change, password.change], inputs=[username, password], outputs=[local_storage])\n", " def save_to_local_storage(username, password):\n", " return [username, password]\n", "\n", "demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: browserstate"]}, {"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 random\n", "import string\n", "import gradio as gr\n", "import time\n", "with gr.Blocks() as demo:\n", " gr.Markdown(\"Your Username and Password will get saved in the browser's local storage. \"\n", " \"If you refresh the page, the values will be retained.\")\n", " username = gr.Textbox(label=\"Username\")\n", " password = gr.Textbox(label=\"Password\", type=\"password\")\n", " btn = gr.Button(\"Generate Randomly\")\n", " local_storage = gr.BrowserState([\"\", \"\"])\n", " saved_message = gr.Markdown(\"\u2705 Saved to local storage\", visible=False)\n", "\n", " @btn.click(outputs=[username, password])\n", " def generate_randomly():\n", " u = \"\".join(random.choices(string.ascii_letters + string.digits, k=10))\n", " p = \"\".join(random.choices(string.ascii_letters + string.digits, k=10))\n", " return u, p\n", "\n", " @demo.load(inputs=[local_storage], outputs=[username, password])\n", " def load_from_local_storage(saved_values):\n", " print(\"loading from local storage\", saved_values)\n", " return saved_values[0], saved_values[1]\n", "\n", " @gr.on([username.change, password.change], inputs=[username, password], outputs=[local_storage])\n", " def save_to_local_storage(username, password):\n", " return [username, password]\n", "\n", " @gr.on(local_storage.change, outputs=[saved_message])\n", " def show_saved_message():\n", " timestamp = time.strftime(\"%I:%M:%S %p\")\n", " return gr.Markdown(\n", " f\"\u2705 Saved to local storage at {timestamp}\",\n", " visible=True\n", " )\n", "\n", "demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/browserstate/run.py b/demo/browserstate/run.py index 294b56fd58f64..a150825146457 100644 --- a/demo/browserstate/run.py +++ b/demo/browserstate/run.py @@ -1,7 +1,7 @@ import random import string import gradio as gr - +import time with gr.Blocks() as demo: gr.Markdown("Your Username and Password will get saved in the browser's local storage. " "If you refresh the page, the values will be retained.") @@ -9,6 +9,7 @@ password = gr.Textbox(label="Password", type="password") btn = gr.Button("Generate Randomly") local_storage = gr.BrowserState(["", ""]) + saved_message = gr.Markdown("✅ Saved to local storage", visible=False) @btn.click(outputs=[username, password]) def generate_randomly(): @@ -25,4 +26,12 @@ def load_from_local_storage(saved_values): def save_to_local_storage(username, password): return [username, password] + @gr.on(local_storage.change, outputs=[saved_message]) + def show_saved_message(): + timestamp = time.strftime("%I:%M:%S %p") + return gr.Markdown( + f"✅ Saved to local storage at {timestamp}", + visible=True + ) + demo.launch() diff --git a/gradio/components/browser_state.py b/gradio/components/browser_state.py index 178893d9d53d6..2fc41cf2b1214 100644 --- a/gradio/components/browser_state.py +++ b/gradio/components/browser_state.py @@ -9,10 +9,12 @@ from gradio_client.documentation import document from gradio.components.base import Component +from gradio.events import Events @document() class BrowserState(Component): + EVENTS = [Events.change] """ Special component that stores state in the browser's localStorage in an encrypted format. """ diff --git a/js/browserstate/Index.svelte b/js/browserstate/Index.svelte index caba0c450d544..eedf9c86c5ad2 100644 --- a/js/browserstate/Index.svelte +++ b/js/browserstate/Index.svelte @@ -4,6 +4,7 @@ import { beforeUpdate } from "svelte"; import { encrypt, decrypt } from "./crypto"; import { dequal } from "dequal/lite"; + import type { Gradio } from "@gradio/utils"; export let storage_key: string; export let secret: string; @@ -11,6 +12,9 @@ export let value = default_value; let initialized = false; let old_value = value; + export let gradio: Gradio<{ + change: never; + }>; function load_value(): void { const stored = localStorage.getItem(storage_key); @@ -40,7 +44,13 @@ } } - $: value && !dequal(value, old_value) && save_value(); + $: value && + (() => { + if (!dequal(value, old_value)) { + save_value(); + gradio.dispatch("change"); + } + })(); beforeUpdate(() => { if (!initialized) { diff --git a/js/browserstate/package.json b/js/browserstate/package.json index db249f087ed75..9ef5384793759 100644 --- a/js/browserstate/package.json +++ b/js/browserstate/package.json @@ -17,7 +17,8 @@ }, "dependencies": { "dequal": "^2.0.2", - "crypto-js": "^4.1.1" + "crypto-js": "^4.1.1", + "@gradio/utils": "workspace:^" }, "peerDependencies": { "svelte": "^4.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da308be259b85..3c8e825748e42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -710,6 +710,9 @@ importers: js/browserstate: dependencies: + '@gradio/utils': + specifier: workspace:^ + version: link:../utils crypto-js: specifier: ^4.1.1 version: 4.2.0 From a1e0bd1222bb645804095b048659555ee01c7731 Mon Sep 17 00:00:00 2001 From: Dawood Date: Thu, 26 Dec 2024 16:29:38 -0800 Subject: [PATCH 05/69] history --- demo/chatinterface_save_history/run.ipynb | 1 + demo/chatinterface_save_history/run.py | 20 +++++++++++ gradio/chat_interface.py | 43 +++++++++++++++-------- 3 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 demo/chatinterface_save_history/run.ipynb create mode 100644 demo/chatinterface_save_history/run.py diff --git a/demo/chatinterface_save_history/run.ipynb b/demo/chatinterface_save_history/run.ipynb new file mode 100644 index 0000000000000..26a807f96127b --- /dev/null +++ b/demo/chatinterface_save_history/run.ipynb @@ -0,0 +1 @@ +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_save_history"]}, {"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 echo_multimodal(message, history):\n", " response = []\n", " response.append(\"You wrote: '\" + message[\"text\"] + \"' and uploaded:\")\n", " if message.get(\"files\"):\n", " for file in message[\"files\"]:\n", " response.append(gr.File(value=file))\n", " return response\n", "\n", "demo = gr.ChatInterface(\n", " echo_multimodal,\n", " type=\"messages\",\n", " multimodal=True,\n", " textbox=gr.MultimodalTextbox(file_count=\"multiple\"),\n", " save_history=True,\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/chatinterface_save_history/run.py b/demo/chatinterface_save_history/run.py new file mode 100644 index 0000000000000..5d2c846fefcb2 --- /dev/null +++ b/demo/chatinterface_save_history/run.py @@ -0,0 +1,20 @@ +import gradio as gr + +def echo_multimodal(message, history): + response = [] + response.append("You wrote: '" + message["text"] + "' and uploaded:") + if message.get("files"): + for file in message["files"]: + response.append(gr.File(value=file)) + return response + +demo = gr.ChatInterface( + echo_multimodal, + type="messages", + multimodal=True, + textbox=gr.MultimodalTextbox(file_count="multiple"), + save_history=True, +) + +if __name__ == "__main__": + demo.launch() diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 336c31c72fb95..d4be6c805cd29 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -15,11 +15,12 @@ import anyio from gradio_client.documentation import document -from gradio import render, utils +from gradio import utils from gradio.blocks import Blocks from gradio.components import ( HTML, JSON, + BrowserState, Button, Chatbot, Component, @@ -41,6 +42,7 @@ from gradio.helpers import create_examples as Examples # noqa: N812 from gradio.helpers import special_args, update from gradio.layouts import Accordion, Column, Group, Row +from gradio.renderable import render from gradio.routes import Request from gradio.themes import ThemeClass as Theme @@ -173,6 +175,7 @@ def __init__( fill_height: if True, the chat interface will expand to the height of window. fill_width: Whether to horizontally expand to fill container fully. If False, centers and constrains app to a maximum width. api_name: the name of the API endpoint to use for the chat interface. Defaults to "chat". Set to False to disable the API endpoint. + save_history: if True, will save the chat history to the browser's local storage. Defaults to False. """ super().__init__( analytics_enabled=analytics_enabled, @@ -211,6 +214,7 @@ def __init__( self.cache_examples = cache_examples self.cache_mode = cache_mode self.editable = editable + self.save_history = save_history self.additional_inputs = [ get_component_instance(i) for i in utils.none_or_singleton_to_list(additional_inputs) @@ -261,18 +265,14 @@ def __init__( if save_history: with Column(scale=1, min_width=100): HTML(save_history_css, container=True, padding=False) - history = [ - "Hello how are you?", - "Explain the concept of AI to a 5 year old", - "If a woodchuck could chuck wood how much wood would a woodchuck chuck?", - ] - state = State(history) + if not hasattr(self, 'saved_history'): + self.saved_history = BrowserState([]) with Group(): - @render(inputs=state) - def create_history(history): - for message in history: - h = HTML(message, padding=False, elem_classes=["_gradio-save-history"]) - h.click(lambda x:x, h, self.textbox) + @render(inputs=self.saved_history) + def create_history(conversations): + for chat_conversation in conversations: + h = HTML(chat_conversation[0]["content"], padding=False, elem_classes=["_gradio-save-history"]) + h.click(lambda _:chat_conversation, h, self.chatbot) with Column(scale=6): if chatbot: @@ -412,6 +412,8 @@ def create_history(history): self.chatbot_state = ( State(self.chatbot.value) if self.chatbot.value else State([]) ) + if self.chatbot.value: + self.saved_history = BrowserState(self.chatbot.value) self.show_progress = show_progress self._setup_events() @@ -458,10 +460,21 @@ def _setup_events(self) -> None: if hasattr(self.fn, "zerogpu"): submit_fn.__func__.zerogpu = self.fn.zerogpu # type: ignore + def test(chatbot, chatbot_state, saved_history): + if self.save_history: + if isinstance(chatbot_state, list) and len(chatbot_state) == 0: + return chatbot, saved_history + [chatbot] + else: + # replace the most recent element in saved history with chatbot_state + saved_history[-1] = chatbot + return chatbot, saved_history + else: + return chatbot + synchronize_chat_state_kwargs = { - "fn": lambda x: x, - "inputs": [self.chatbot], - "outputs": [self.chatbot_state], + "fn": test, + "inputs": [self.chatbot, self.chatbot_state] + ([self.saved_history] if hasattr(self, 'saved_history') else []), + "outputs": [self.chatbot_state] + ([self.saved_history] if hasattr(self, 'saved_history') else []), "show_api": False, "queue": False, } From caccb0b8b6a3925adef0fd9945ea31d9e4707cf8 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 10:27:41 -0500 Subject: [PATCH 06/69] changes --- gradio/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradio/__init__.py b/gradio/__init__.py index f621285f0039f..f0feeac8b727e 100644 --- a/gradio/__init__.py +++ b/gradio/__init__.py @@ -167,7 +167,6 @@ "ImageEditor", "ImageMask", "Info", - "Success", "Interface", "JSON", "Json", @@ -204,6 +203,7 @@ "Sketchpad", "Slider", "State", + "Success", "Tab", "TabItem", "TabbedInterface", From 9251dea908dac11d0c4850329f8f5810c0130bd3 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 11:26:35 -0500 Subject: [PATCH 07/69] changes --- gradio/chat_interface.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index d4be6c805cd29..87087c0ed3f9a 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -215,6 +215,7 @@ def __init__( self.cache_mode = cache_mode self.editable = editable self.save_history = save_history + self.saved_messages = BrowserState([], storage_key="saved_messages") self.additional_inputs = [ get_component_instance(i) for i in utils.none_or_singleton_to_list(additional_inputs) @@ -265,10 +266,8 @@ def __init__( if save_history: with Column(scale=1, min_width=100): HTML(save_history_css, container=True, padding=False) - if not hasattr(self, 'saved_history'): - self.saved_history = BrowserState([]) with Group(): - @render(inputs=self.saved_history) + @render(inputs=self.saved_messages) def create_history(conversations): for chat_conversation in conversations: h = HTML(chat_conversation[0]["content"], padding=False, elem_classes=["_gradio-save-history"]) @@ -412,8 +411,6 @@ def create_history(conversations): self.chatbot_state = ( State(self.chatbot.value) if self.chatbot.value else State([]) ) - if self.chatbot.value: - self.saved_history = BrowserState(self.chatbot.value) self.show_progress = show_progress self._setup_events() From 08e6615f3273fe77db1cbfd85a910458a1e8e719 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 11:28:03 -0500 Subject: [PATCH 08/69] changes --- gradio/chat_interface.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 87087c0ed3f9a..cf5dfe3e9bfdb 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -457,21 +457,10 @@ def _setup_events(self) -> None: if hasattr(self.fn, "zerogpu"): submit_fn.__func__.zerogpu = self.fn.zerogpu # type: ignore - def test(chatbot, chatbot_state, saved_history): - if self.save_history: - if isinstance(chatbot_state, list) and len(chatbot_state) == 0: - return chatbot, saved_history + [chatbot] - else: - # replace the most recent element in saved history with chatbot_state - saved_history[-1] = chatbot - return chatbot, saved_history - else: - return chatbot - synchronize_chat_state_kwargs = { - "fn": test, - "inputs": [self.chatbot, self.chatbot_state] + ([self.saved_history] if hasattr(self, 'saved_history') else []), - "outputs": [self.chatbot_state] + ([self.saved_history] if hasattr(self, 'saved_history') else []), + "fn": lambda x: x, + "inputs": [self.chatbot], + "outputs": [self.chatbot_state], "show_api": False, "queue": False, } @@ -487,7 +476,6 @@ def test(chatbot, chatbot_state, saved_history): Literal["full", "minimal", "hidden"], self.show_progress ), } - submit_event = ( self.textbox.submit( self._clear_and_save_textbox, From 3cdb67b16f82959579686be833135614aa67194e Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 11:49:41 -0500 Subject: [PATCH 09/69] history --- gradio/chat_interface.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index cf5dfe3e9bfdb..110770719dc67 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -80,6 +80,7 @@ """ + @document() class ChatInterface(Blocks): """ @@ -215,7 +216,6 @@ def __init__( self.cache_mode = cache_mode self.editable = editable self.save_history = save_history - self.saved_messages = BrowserState([], storage_key="saved_messages") self.additional_inputs = [ get_component_instance(i) for i in utils.none_or_singleton_to_list(additional_inputs) @@ -255,6 +255,14 @@ def __init__( break with self: + self.saved_messages = BrowserState( + [ + [{"role": "user", "content": "Hello!"}], + [{"role": "user", "content": "Hi!"}], + ], + storage_key="saved_messages", + ) + with Column(): if title: Markdown( @@ -267,11 +275,31 @@ def __init__( with Column(scale=1, min_width=100): HTML(save_history_css, container=True, padding=False) with Group(): + @render(inputs=self.saved_messages) def create_history(conversations): for chat_conversation in conversations: - h = HTML(chat_conversation[0]["content"], padding=False, elem_classes=["_gradio-save-history"]) - h.click(lambda _:chat_conversation, h, self.chatbot) + h = HTML( + chat_conversation[0]["content"], + padding=False, + elem_classes=["_gradio-save-history"], + ) + + # Using a closure to capture current chat_conversation value instead of a lambda directly + def create_click_handler(conversation): + return lambda _: conversation + + h.click( + create_click_handler(chat_conversation), + h, + self.chatbot, + ).then( + lambda x: x, + [self.chatbot], + [self.chatbot_state], + show_api=False, + queue=False, + ) with Column(scale=6): if chatbot: From c20cedaa0c0c5ff25b1dc547ad8f8b32b4aa9a52 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 13:45:58 -0500 Subject: [PATCH 10/69] changes --- gradio/chat_interface.py | 86 +++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 110770719dc67..b68f6dd9ac433 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -255,13 +255,14 @@ def __init__( break with self: - self.saved_messages = BrowserState( + self.saved_conversations = BrowserState( [ [{"role": "user", "content": "Hello!"}], [{"role": "user", "content": "Hi!"}], ], - storage_key="saved_messages", + storage_key="saved_conversations", ) + self.conversation_id = State(None) with Column(): if title: @@ -276,30 +277,31 @@ def __init__( HTML(save_history_css, container=True, padding=False) with Group(): - @render(inputs=self.saved_messages) + @render(inputs=self.saved_conversations) def create_history(conversations): - for chat_conversation in conversations: - h = HTML( - chat_conversation[0]["content"], - padding=False, - elem_classes=["_gradio-save-history"], - ) - - # Using a closure to capture current chat_conversation value instead of a lambda directly - def create_click_handler(conversation): - return lambda _: conversation - - h.click( - create_click_handler(chat_conversation), - h, - self.chatbot, - ).then( - lambda x: x, - [self.chatbot], - [self.chatbot_state], - show_api=False, - queue=False, - ) + for index, conversation in enumerate(conversations): + if conversation: + html = HTML( + conversation[0]["content"], + padding=False, + elem_classes=["_gradio-save-history"], + ) + + # Using a closure to capture current chat_conversation value instead of a lambda directly + def create_click_handler(index, conversation): + return lambda _: (index, conversation) + + html.click( + create_click_handler(index, conversation), + html, + [self.conversation_id, self.chatbot], + ).then( + lambda x: x, + [self.chatbot], + [self.chatbot_state], + show_api=False, + queue=False, + ) with Column(scale=6): if chatbot: @@ -479,6 +481,15 @@ def _setup_example_messages( examples_messages.append(example_message) return examples_messages + def _save_conversation(self, index: int | None, conversation: list[MessageDict] | TupleFormat, saved_conversations: list[list[MessageDict] | TupleFormat]): + if self.save_history: + if index is not None: + saved_conversations[index] = conversation + else: + saved_conversations.append(conversation) + index = len(saved_conversations) - 1 + return saved_conversations, index + def _setup_events(self) -> None: submit_triggers = [self.textbox.submit, self.chatbot.retry] submit_fn = self._stream_fn if self.is_generator else self._submit_fn @@ -504,6 +515,14 @@ def _setup_events(self) -> None: Literal["full", "minimal", "hidden"], self.show_progress ), } + save_fn_kwargs = { + "fn": self._save_conversation, + "inputs": [self.conversation_id, self.chatbot_state, self.saved_conversations], + "outputs": [self.saved_conversations, self.conversation_id], + "show_api": False, + "queue": False, + } + submit_event = ( self.textbox.submit( self._clear_and_save_textbox, @@ -526,7 +545,10 @@ def _setup_events(self) -> None: None, self.textbox, show_api=False, + ).then( + **save_fn_kwargs ) + # Creates the "/chat" API endpoint self.fake_api_btn.click( submit_fn, @@ -588,6 +610,8 @@ def _setup_events(self) -> None: lambda: update(interactive=True), outputs=[self.textbox], show_api=False, + ).then( + **save_fn_kwargs ) self._setup_stop_events(submit_triggers, [submit_event, retry_event]) @@ -598,14 +622,18 @@ def _setup_events(self) -> None: [self.chatbot, self.textbox], show_api=False, queue=False, - ).then(**synchronize_chat_state_kwargs) + ).then(**synchronize_chat_state_kwargs).then( + **save_fn_kwargs + ) self.chatbot.option_select( self.option_clicked, [self.chatbot], [self.chatbot, self.saved_input], show_api=False, - ).then(**submit_fn_kwargs).then(**synchronize_chat_state_kwargs) + ).then(**submit_fn_kwargs).then(**synchronize_chat_state_kwargs).then( + **save_fn_kwargs + ) self.chatbot.clear(**synchronize_chat_state_kwargs) @@ -615,7 +643,9 @@ def _setup_events(self) -> None: [self.chatbot], [self.chatbot, self.chatbot_state, self.saved_input], show_api=False, - ).success(**submit_fn_kwargs).success(**synchronize_chat_state_kwargs) + ).success(**submit_fn_kwargs).success(**synchronize_chat_state_kwargs).then( + **save_fn_kwargs + ) def _setup_stop_events( self, event_triggers: list[Callable], events_to_cancel: list[Dependency] From 843876495c094284a3c28c03189d00ff6bcc4a4d Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 13:46:16 -0500 Subject: [PATCH 11/69] changes --- gradio/chat_interface.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index b68f6dd9ac433..e57af4fd3cc63 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -288,11 +288,15 @@ def create_history(conversations): ) # Using a closure to capture current chat_conversation value instead of a lambda directly - def create_click_handler(index, conversation): + def create_click_handler( + index, conversation + ): return lambda _: (index, conversation) html.click( - create_click_handler(index, conversation), + create_click_handler( + index, conversation + ), html, [self.conversation_id, self.chatbot], ).then( @@ -481,7 +485,12 @@ def _setup_example_messages( examples_messages.append(example_message) return examples_messages - def _save_conversation(self, index: int | None, conversation: list[MessageDict] | TupleFormat, saved_conversations: list[list[MessageDict] | TupleFormat]): + def _save_conversation( + self, + index: int | None, + conversation: list[MessageDict] | TupleFormat, + saved_conversations: list[list[MessageDict] | TupleFormat], + ): if self.save_history: if index is not None: saved_conversations[index] = conversation @@ -517,7 +526,11 @@ def _setup_events(self) -> None: } save_fn_kwargs = { "fn": self._save_conversation, - "inputs": [self.conversation_id, self.chatbot_state, self.saved_conversations], + "inputs": [ + self.conversation_id, + self.chatbot_state, + self.saved_conversations, + ], "outputs": [self.saved_conversations, self.conversation_id], "show_api": False, "queue": False, @@ -545,9 +558,7 @@ def _setup_events(self) -> None: None, self.textbox, show_api=False, - ).then( - **save_fn_kwargs - ) + ).then(**save_fn_kwargs) # Creates the "/chat" API endpoint self.fake_api_btn.click( @@ -610,9 +621,7 @@ def _setup_events(self) -> None: lambda: update(interactive=True), outputs=[self.textbox], show_api=False, - ).then( - **save_fn_kwargs - ) + ).then(**save_fn_kwargs) self._setup_stop_events(submit_triggers, [submit_event, retry_event]) @@ -622,9 +631,7 @@ def _setup_events(self) -> None: [self.chatbot, self.textbox], show_api=False, queue=False, - ).then(**synchronize_chat_state_kwargs).then( - **save_fn_kwargs - ) + ).then(**synchronize_chat_state_kwargs).then(**save_fn_kwargs) self.chatbot.option_select( self.option_clicked, @@ -644,8 +651,8 @@ def _setup_events(self) -> None: [self.chatbot, self.chatbot_state, self.saved_input], show_api=False, ).success(**submit_fn_kwargs).success(**synchronize_chat_state_kwargs).then( - **save_fn_kwargs - ) + **save_fn_kwargs + ) def _setup_stop_events( self, event_triggers: list[Callable], events_to_cancel: list[Dependency] From 269ff00bbf3c6398526bfe6998e1bbf8d666e577 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 13:57:43 -0500 Subject: [PATCH 12/69] changes --- gradio/chat_interface.py | 88 +++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index e57af4fd3cc63..c0ce94c64b939 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -46,7 +46,7 @@ from gradio.routes import Request from gradio.themes import ThemeClass as Theme -save_history_css = """ +new_chat_html = """ -
-Local chat history +
+✙   New Chat
""" - @document() class ChatInterface(Blocks): """ @@ -255,13 +254,7 @@ def __init__( break with self: - self.saved_conversations = BrowserState( - [ - [{"role": "user", "content": "Hello!"}], - [{"role": "user", "content": "Hi!"}], - ], - storage_key="saved_conversations", - ) + self.saved_conversations = BrowserState([], storage_key="saved_conversations") self.conversation_id = State(None) with Column(): @@ -274,38 +267,49 @@ def __init__( with Row(): if save_history: with Column(scale=1, min_width=100): - HTML(save_history_css, container=True, padding=False) - with Group(): - - @render(inputs=self.saved_conversations) - def create_history(conversations): - for index, conversation in enumerate(conversations): - if conversation: - html = HTML( - conversation[0]["content"], - padding=False, - elem_classes=["_gradio-save-history"], - ) - - # Using a closure to capture current chat_conversation value instead of a lambda directly - def create_click_handler( - index, conversation - ): - return lambda _: (index, conversation) - - html.click( - create_click_handler( + @render(inputs=self.saved_conversations) + def create_history(conversations): + html_new = HTML(new_chat_html, container=True, padding=False) + html_new.click( + lambda : (None, []), + None, + [self.conversation_id, self.chatbot] + ).then( + lambda x: x, + [self.chatbot], + [self.chatbot_state], + show_api=False, + queue=False, + ) + if conversations: + with Group(): + for index, conversation in enumerate(conversations): + if conversation: + html = HTML( + conversation[0]["content"], + padding=False, + elem_classes=["_gradio-save-history"], + ) + + # Using a closure to capture current chat_conversation value instead of a lambda directly + def create_click_handler( index, conversation - ), - html, - [self.conversation_id, self.chatbot], - ).then( - lambda x: x, - [self.chatbot], - [self.chatbot_state], - show_api=False, - queue=False, - ) + ): + return lambda _: (index, conversation) + + html.click( + create_click_handler( + index, conversation + ), + html, + [self.conversation_id, self.chatbot], + ).then( + lambda x: x, + [self.chatbot], + [self.chatbot_state], + show_api=False, + queue=False, + ) with Column(scale=6): if chatbot: From 5477552cc423b10e5f750063d37e302388f5c0ae Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 13:59:51 -0500 Subject: [PATCH 13/69] format --- gradio/chat_interface.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index c0ce94c64b939..1af999bf54fc3 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -80,6 +80,7 @@
""" + @document() class ChatInterface(Blocks): """ @@ -254,7 +255,9 @@ def __init__( break with self: - self.saved_conversations = BrowserState([], storage_key="saved_conversations") + self.saved_conversations = BrowserState( + [], storage_key="saved_conversations" + ) self.conversation_id = State(None) with Column(): @@ -267,13 +270,16 @@ def __init__( with Row(): if save_history: with Column(scale=1, min_width=100): + @render(inputs=self.saved_conversations) def create_history(conversations): - html_new = HTML(new_chat_html, container=True, padding=False) + html_new = HTML( + new_chat_html, container=True, padding=False + ) html_new.click( - lambda : (None, []), + lambda: (None, []), None, - [self.conversation_id, self.chatbot] + [self.conversation_id, self.chatbot], ).then( lambda x: x, [self.chatbot], @@ -283,26 +289,36 @@ def create_history(conversations): ) if conversations: with Group(): - for index, conversation in enumerate(conversations): + for index, conversation in enumerate( + conversations + ): if conversation: html = HTML( conversation[0]["content"], padding=False, - elem_classes=["_gradio-save-history"], + elem_classes=[ + "_gradio-save-history" + ], ) # Using a closure to capture current chat_conversation value instead of a lambda directly def create_click_handler( index, conversation ): - return lambda _: (index, conversation) + return lambda _: ( + index, + conversation, + ) html.click( create_click_handler( index, conversation ), html, - [self.conversation_id, self.chatbot], + [ + self.conversation_id, + self.chatbot, + ], ).then( lambda x: x, [self.chatbot], From a51cbef62c9a58a68660c39db9a4b7cb7a63981b Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Mon, 30 Dec 2024 19:02:28 +0000 Subject: [PATCH 14/69] add changeset --- .changeset/tiny-areas-train.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tiny-areas-train.md b/.changeset/tiny-areas-train.md index 18c433d9b81c4..09d95d497166a 100644 --- a/.changeset/tiny-areas-train.md +++ b/.changeset/tiny-areas-train.md @@ -2,4 +2,4 @@ "gradio": minor --- -feat:Save chat history WIP +feat:Support saving chat history in `gr.ChatInterface` From 59baf96a783d497e67d2d41521616d992c187108 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 18:01:57 -0500 Subject: [PATCH 15/69] changes --- gradio/chat_interface.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 1af999bf54fc3..c3f526bd1530f 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -73,10 +73,17 @@ font-size: 0.8rem; text-align: center; padding: 10px 0px; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; }
-✙   New Chat + + + +New Chat
""" @@ -280,6 +287,8 @@ def create_history(conversations): lambda: (None, []), None, [self.conversation_id, self.chatbot], + show_api=False, + queue=False, ).then( lambda x: x, [self.chatbot], @@ -517,7 +526,16 @@ def _save_conversation( else: saved_conversations.append(conversation) index = len(saved_conversations) - 1 - return saved_conversations, index + return index, saved_conversations + + def _delete_conversation( + self, + index: int | None, + saved_conversations: list[list[MessageDict] | TupleFormat] + ): + if index is not None: + saved_conversations.pop(index) + return None, saved_conversations def _setup_events(self) -> None: submit_triggers = [self.textbox.submit, self.chatbot.retry] @@ -551,7 +569,7 @@ def _setup_events(self) -> None: self.chatbot_state, self.saved_conversations, ], - "outputs": [self.saved_conversations, self.conversation_id], + "outputs": [self.conversation_id, self.saved_conversations], "show_api": False, "queue": False, } @@ -662,7 +680,13 @@ def _setup_events(self) -> None: **save_fn_kwargs ) - self.chatbot.clear(**synchronize_chat_state_kwargs) + self.chatbot.clear(**synchronize_chat_state_kwargs).then( + self._delete_conversation, + [self.conversation_id, self.saved_conversations], + [self.conversation_id, self.saved_conversations], + show_api=False, + queue=False, + ) if self.editable: self.chatbot.edit( From 25558a00cade88fa58374bf78eb71d73dc6c54bc Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 18:03:04 -0500 Subject: [PATCH 16/69] changes --- gradio/chat_interface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index c3f526bd1530f..189f94c7e6114 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -529,10 +529,10 @@ def _save_conversation( return index, saved_conversations def _delete_conversation( - self, - index: int | None, - saved_conversations: list[list[MessageDict] | TupleFormat] - ): + self, + index: int | None, + saved_conversations: list[list[MessageDict] | TupleFormat], + ): if index is not None: saved_conversations.pop(index) return None, saved_conversations From ea42b9ebc07aa67718a079c84b2c6cc10a978ce1 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 18:31:28 -0500 Subject: [PATCH 17/69] more changes --- demo/chatinterface_save_history/run.ipynb | 2 +- demo/chatinterface_save_history/run.py | 6 +----- gradio/chat_interface.py | 22 +++++++++++++++++----- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/demo/chatinterface_save_history/run.ipynb b/demo/chatinterface_save_history/run.ipynb index 26a807f96127b..8880af469b52c 100644 --- a/demo/chatinterface_save_history/run.ipynb +++ b/demo/chatinterface_save_history/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_save_history"]}, {"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 echo_multimodal(message, history):\n", " response = []\n", " response.append(\"You wrote: '\" + message[\"text\"] + \"' and uploaded:\")\n", " if message.get(\"files\"):\n", " for file in message[\"files\"]:\n", " response.append(gr.File(value=file))\n", " return response\n", "\n", "demo = gr.ChatInterface(\n", " echo_multimodal,\n", " type=\"messages\",\n", " multimodal=True,\n", " textbox=gr.MultimodalTextbox(file_count=\"multiple\"),\n", " save_history=True,\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_save_history"]}, {"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 echo_multimodal(message, history):\n", " response = \"You wrote: '\" + message[\"text\"] + \"' and uploaded: \" + str(len(message[\"files\"])) + \" files\"\n", " return response\n", "\n", "demo = gr.ChatInterface(\n", " echo_multimodal,\n", " type=\"messages\",\n", " multimodal=True,\n", " textbox=gr.MultimodalTextbox(file_count=\"multiple\"),\n", " save_history=True,\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/chatinterface_save_history/run.py b/demo/chatinterface_save_history/run.py index 5d2c846fefcb2..0870f35e35e34 100644 --- a/demo/chatinterface_save_history/run.py +++ b/demo/chatinterface_save_history/run.py @@ -1,11 +1,7 @@ import gradio as gr def echo_multimodal(message, history): - response = [] - response.append("You wrote: '" + message["text"] + "' and uploaded:") - if message.get("files"): - for file in message["files"]: - response.append(gr.File(value=file)) + response = "You wrote: '" + message["text"] + "' and uploaded: " + str(len(message["files"])) + " files" return response demo = gr.ChatInterface( diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 189f94c7e6114..e4177f15789f2 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -149,7 +149,7 @@ def __init__( ): """ Parameters: - fn: the function to wrap the chat interface around. In the default case (assuming `type` is set to "messages"), the function should accept two parameters: a `str` input message and `list` of openai-style dictionary {"role": "user" | "assistant", "content": `str` | {"path": `str`} | `gr.Component`} representing the chat history, and the function should return/yield a `str` (if a simple message), a supported Gradio component (to return a file), a `dict` (for a complete openai-style message response), or a `list` of such messages. + fn: the function to wrap the chat interface around. Normally (assuming `type` is set to "messages"), the function should accept two parameters: a `str` representing the input message and `list` of openai-style dictionaries: {"role": "user" | "assistant", "content": `str` | {"path": `str`} | `gr.Component`} representing the chat history. The function should return/yield a `str` (for a simple message), a supported Gradio component (e.g. gr.Image to return an image), a `dict` (for a complete openai-style message response), or a `list` of such messages. multimodal: if True, the chat interface will use a `gr.MultimodalTextbox` component for the input, which allows for the uploading of multimedia files. If False, the chat interface will use a gr.Textbox component for the input. If this is True, the first argument of `fn` should accept not a `str` message but a `dict` message with keys "text" and "files" type: The format of the messages passed into the chat history parameter of `fn`. If "messages", passes the history as a list of dictionaries with openai-style "role" and "content" keys. The "content" key's value should be one of the following - (1) strings in valid Markdown (2) a dictionary with a "path" key and value corresponding to the file to display or (3) an instance of a Gradio component: at the moment gr.Image, gr.Plot, gr.Video, gr.Gallery, gr.Audio, and gr.HTML are supported. The "role" key should be one of 'user' or 'assistant'. Any other roles will not be displayed in the output. If this parameter is 'tuples' (deprecated), passes the chat history as a `list[list[str | None | tuple]]`, i.e. a list of lists. The inner list should have 2 elements: the user message and the response message. chatbot: an instance of the gr.Chatbot component to use for the chat interface, if you would like to customize the chatbot properties. If not provided, a default gr.Chatbot component will be created. @@ -222,6 +222,8 @@ def __init__( self.cache_examples = cache_examples self.cache_mode = cache_mode self.editable = editable + if save_history and not type == "messages": + raise ValueError("save_history is only supported for type='messages'") self.save_history = save_history self.additional_inputs = [ get_component_instance(i) @@ -303,7 +305,7 @@ def create_history(conversations): ): if conversation: html = HTML( - conversation[0]["content"], + self._generate_chat_title(conversation), padding=False, elem_classes=[ "_gradio-save-history" @@ -514,11 +516,21 @@ def _setup_example_messages( examples_messages.append(example_message) return examples_messages + def _generate_chat_title(self, conversation: list[MessageDict]) -> str: + title = "" + for message in conversation: + if message["role"] == "user": + if isinstance(message["content"], str): + return title + message["content"] + else: + title += "📎 " + return "New Chat" + def _save_conversation( self, index: int | None, - conversation: list[MessageDict] | TupleFormat, - saved_conversations: list[list[MessageDict] | TupleFormat], + conversation: list[MessageDict], + saved_conversations: list[list[MessageDict]], ): if self.save_history: if index is not None: @@ -531,7 +543,7 @@ def _save_conversation( def _delete_conversation( self, index: int | None, - saved_conversations: list[list[MessageDict] | TupleFormat], + saved_conversations: list[list[MessageDict]], ): if index is not None: saved_conversations.pop(index) From 95b49fb8dff968ac43535e1019fe9e6666585046 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 18:39:22 -0500 Subject: [PATCH 18/69] changes --- gradio/chat_interface.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index e4177f15789f2..690ff05d6e3db 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -305,7 +305,9 @@ def create_history(conversations): ): if conversation: html = HTML( - self._generate_chat_title(conversation), + self._generate_chat_title( + conversation + ), padding=False, elem_classes=[ "_gradio-save-history" From 99bb7dfbbf6eb1e1fdef4ef3c1cb5bc562f54054 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 30 Dec 2024 19:03:15 -0500 Subject: [PATCH 19/69] dataset changes --- gradio/components/dataset.py | 3 +++ js/dataset/Index.svelte | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gradio/components/dataset.py b/gradio/components/dataset.py index 731a63eada5e1..df1d9ea29db9e 100644 --- a/gradio/components/dataset.py +++ b/gradio/components/dataset.py @@ -34,6 +34,7 @@ def __init__( samples: list[list[Any]] | None = None, headers: list[str] | None = None, type: Literal["values", "index", "tuple"] = "values", + layout: Literal["gallery", "table"] | None = None, samples_per_page: int = 10, visible: bool = True, elem_id: str | None = None, @@ -53,6 +54,7 @@ def __init__( samples: a nested list of samples. Each sublist within the outer list represents a data sample, and each element within the sublist represents an value for each component headers: Column headers in the Dataset widget, should be the same len as components. If not provided, inferred from component labels type: "values" if clicking on a sample should pass the value of the sample, "index" if it should pass the index of the sample, or "tuple" if it should pass both the index and the value of the sample. + layout: "gallery" if the dataset should be displayed as a gallery with each sample in a clickable card, or "table" if it should be displayed as a table with each sample in a row. By default, "gallery" is used if there is a single component, and "table" is used if there are more than one component. If there are more than one component, the layout can only be "table". samples_per_page: how many examples to show per page. visible: If False, component will be hidden. elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles. @@ -75,6 +77,7 @@ def __init__( self.container = container self.scale = scale self.min_width = min_width + self.layout = layout self._components = [get_component_instance(c) for c in components or []] if component_props is None: self.component_props = [ diff --git a/js/dataset/Index.svelte b/js/dataset/Index.svelte index 9329a709ecfc0..27f276d330df0 100644 --- a/js/dataset/Index.svelte +++ b/js/dataset/Index.svelte @@ -29,6 +29,7 @@ click: number; select: SelectData; }>; + export let layout: "gallery" | "table" | null = null; // Although the `samples_dir` prop is not used in any of the core Gradio component, it is kept for backward compatibility // with any custom components created with gradio<=4.20.0 @@ -37,7 +38,7 @@ : `${root}/file=`; let page = 0; - $: gallery = components.length < 2 || sample_labels !== null; + $: gallery = layout === null ? components.length < 2 || sample_labels !== null : layout === "gallery"; let paginate = samples ? samples.length > samples_per_page : false; let selected_samples: any[][]; From 7a6a8b7a4510e8812c3db2cf8674af53b6f85e58 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 07:26:42 -0500 Subject: [PATCH 20/69] changes --- js/dataset/Index.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/dataset/Index.svelte b/js/dataset/Index.svelte index 27f276d330df0..992be5723b476 100644 --- a/js/dataset/Index.svelte +++ b/js/dataset/Index.svelte @@ -38,7 +38,7 @@ : `${root}/file=`; let page = 0; - $: gallery = layout === null ? components.length < 2 || sample_labels !== null : layout === "gallery"; + $: gallery = (components.length < 2 || sample_labels !== null) && layout !== "table"; let paginate = samples ? samples.length > samples_per_page : false; let selected_samples: any[][]; From 25805138d1aa3045823284def934f9428b399dcd Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Tue, 31 Dec 2024 12:27:33 +0000 Subject: [PATCH 21/69] add changeset --- .changeset/tiny-areas-train.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/tiny-areas-train.md b/.changeset/tiny-areas-train.md index 09d95d497166a..1a50b47bbf551 100644 --- a/.changeset/tiny-areas-train.md +++ b/.changeset/tiny-areas-train.md @@ -1,4 +1,5 @@ --- +"@gradio/dataset": minor "gradio": minor --- From 9ce023f5ffc24ed17eafd3806248ced6a47046e3 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 07:36:55 -0500 Subject: [PATCH 22/69] add md variant for button --- gradio/chat_interface.py | 15 +++++---------- gradio/components/button.py | 2 +- gradio/components/dataset.py | 2 ++ gradio/themes/base.py | 20 ++++++++++++++++++++ js/button/shared/Button.svelte | 9 ++++++++- js/dataset/Index.svelte | 13 ++++++++----- 6 files changed, 44 insertions(+), 17 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 690ff05d6e3db..e18f4ed39e51b 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -79,12 +79,6 @@ gap: 4px; } -
- - - -New Chat -
""" @@ -282,10 +276,11 @@ def __init__( @render(inputs=self.saved_conversations) def create_history(conversations): - html_new = HTML( - new_chat_html, container=True, padding=False - ) - html_new.click( + new_chat_button = Button(variant="secondary", size="md") + # html_new = HTML( + # new_chat_html, container=True, padding=False + # ) + new_chat_button.click( lambda: (None, []), None, [self.conversation_id, self.chatbot], diff --git a/gradio/components/button.py b/gradio/components/button.py index 8f420df8f7950..7775f1b83b882 100644 --- a/gradio/components/button.py +++ b/gradio/components/button.py @@ -29,7 +29,7 @@ def __init__( every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop", "huggingface"] = "secondary", - size: Literal["sm", "lg"] | None = None, + size: Literal["sm", "md", "lg"] | None = None, icon: str | None = None, link: str | None = None, visible: bool = True, diff --git a/gradio/components/dataset.py b/gradio/components/dataset.py index df1d9ea29db9e..130821ed9261b 100644 --- a/gradio/components/dataset.py +++ b/gradio/components/dataset.py @@ -29,6 +29,7 @@ def __init__( self, *, label: str | None = None, + show_label: bool = True, components: Sequence[Component] | list[str] | None = None, component_props: list[dict[str, Any]] | None = None, samples: list[list[Any]] | None = None, @@ -50,6 +51,7 @@ def __init__( """ Parameters: label: the label for this component, appears above the component. + show_label: If True, the label will be shown above the component. components: Which component types to show in this dataset widget, can be passed in as a list of string names or Components instances. The following components are supported in a Dataset: Audio, Checkbox, CheckboxGroup, ColorPicker, Dataframe, Dropdown, File, HTML, Image, Markdown, Model3D, Number, Radio, Slider, Textbox, TimeSeries, Video samples: a nested list of samples. Each sublist within the outer list represents a data sample, and each element within the sublist represents an value for each component headers: Column headers in the Dataset widget, should be the same len as components. If not provided, inferred from component labels diff --git a/gradio/themes/base.py b/gradio/themes/base.py index 31d571c49d959..279a45f31b2a4 100644 --- a/gradio/themes/base.py +++ b/gradio/themes/base.py @@ -708,6 +708,10 @@ def set( button_small_radius=None, button_small_text_size=None, button_small_text_weight=None, + button_medium_padding=None, + button_medium_radius=None, + button_medium_text_size=None, + button_medium_text_weight=None, button_primary_background_fill=None, button_primary_background_fill_dark=None, button_primary_background_fill_hover=None, @@ -1006,6 +1010,10 @@ def set( button_small_radius: The corner radius of a button set to "small" size. button_small_text_size: The text size of a button set to "small" size. button_small_text_weight: The text weight of a button set to "small" size. + button_medium_padding: The padding of a button set to "medium" size. + button_medium_radius: The corner radius of a button set to "medium" size. + button_medium_text_size: The text size of a button set to "medium" size. + button_medium_text_weight: The text weight of a button set to "medium" size. button_transition: The transition animation duration of a button between regular, hover, and focused states. button_transform_hover: The transform animation of a button on hover. button_transform_active: The transform animation of a button when pressed. @@ -1956,5 +1964,17 @@ def set( self.button_small_text_weight = button_small_text_weight or getattr( self, "button_small_text_weight", "400" ) + self.button_medium_padding = button_medium_padding or getattr( + self, "button_medium_padding", "*spacing_md calc(2 * *spacing_md)" + ) + self.button_medium_radius = button_medium_radius or getattr( + self, "button_medium_radius", "*radius_md" + ) + self.button_medium_text_size = button_medium_text_size or getattr( + self, "button_medium_text_size", "*text_md" + ) + self.button_medium_text_weight = button_medium_text_weight or getattr( + self, "button_medium_text_weight", "600" + ) return self diff --git a/js/button/shared/Button.svelte b/js/button/shared/Button.svelte index 42c3a5f85ccbd..cfcc0b48fa34c 100644 --- a/js/button/shared/Button.svelte +++ b/js/button/shared/Button.svelte @@ -6,7 +6,7 @@ export let visible = true; export let variant: "primary" | "secondary" | "stop" | "huggingface" = "secondary"; - export let size: "sm" | "lg" = "lg"; + export let size: "sm" | "md" | "lg" = "lg"; export let value: string | null = null; export let link: string | null = null; export let icon: FileData | null = null; @@ -174,6 +174,13 @@ font-size: var(--button-small-text-size); } + .md { + border-radius: var(--button-medium-radius); + padding: var(--button-medium-padding); + font-weight: var(--button-medium-text-weight); + font-size: var(--button-medium-text-size); + } + .lg { border-radius: var(--button-large-radius); padding: var(--button-large-padding); diff --git a/js/dataset/Index.svelte b/js/dataset/Index.svelte index 992be5723b476..f78bc11df9cae 100644 --- a/js/dataset/Index.svelte +++ b/js/dataset/Index.svelte @@ -12,6 +12,7 @@ }> >; export let label = "Examples"; + export let show_label: boolean = true; export let headers: string[]; export let samples: any[][] | null = null; let old_samples: any[][] | null = null; @@ -127,9 +128,10 @@ allow_overflow={false} container={false} > -
- + - {label} -
+ {label} + + {/if} {#if gallery} {:else} -
- - - - {#each headers as header} - - {/each} - - - - {#each component_meta as sample_row, i} - { - value = i + page * samples_per_page; - gradio.dispatch("click", value); - }} - on:mouseenter={() => handle_mouseenter(i)} - on:mouseleave={() => handle_mouseleave()} - > - {#each sample_row as { value, component }, j} - {@const component_name = components[j]} - {#if component_name !== undefined && component_map.get(component_name) !== undefined} - - {/if} + {#if selected_samples.length > 0} +
+
- {header} -
- -
+ + + {#each headers as header} + {/each} - {/each} - -
+ {header} +
-
+ + + {#each component_meta as sample_row, i} + { + value = i + page * samples_per_page; + gradio.dispatch("click", value); + }} + on:mouseenter={() => handle_mouseenter(i)} + on:mouseleave={() => handle_mouseleave()} + > + {#each sample_row as { value, component }, j} + {@const component_name = components[j]} + {#if component_name !== undefined && component_map.get(component_name) !== undefined} + + + + {/if} + {/each} + + {/each} + + + + {/if} {/if} {#if paginate}
From 7987774526864fbdc380ef75bded3fecd0253f11 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 12:53:59 -0500 Subject: [PATCH 32/69] changes --- gradio/chat_interface.py | 46 ++++++++------------------------------- js/textbox/Example.svelte | 2 -- 2 files changed, 9 insertions(+), 39 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 597af6c9d90b6..04738b56e614b 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -45,41 +45,6 @@ from gradio.routes import Request from gradio.themes import ThemeClass as Theme -new_chat_html = """ - -""" - @document() class ChatInterface(Blocks): @@ -464,14 +429,21 @@ def _setup_example_messages( return examples_messages def _generate_chat_title(self, conversation: list[MessageDict]) -> str: + """ + Generate a title for a conversation by taking the first user message that is a string + and truncating it to 40 characters. If files are present, add a 📎 to the title. + """ title = "" for message in conversation: if message["role"] == "user": if isinstance(message["content"], str): - return title + message["content"] + title += message["content"] + break else: title += "📎 " - return "New Chat" + if len(title) > 40: + title = title[:40] + "..." + return title or "Conversation" def _save_conversation( self, diff --git a/js/textbox/Example.svelte b/js/textbox/Example.svelte index be119a239cfc6..b5a59a1698ef9 100644 --- a/js/textbox/Example.svelte +++ b/js/textbox/Example.svelte @@ -38,8 +38,6 @@ div { overflow: hidden; - min-width: var(--local-text-width); - white-space: nowrap; } From 89e5c715e8fac21b9dc7c0e6bd63cb4a08ef551b Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 12:54:31 -0500 Subject: [PATCH 33/69] changes --- gradio/utils.py | 3 +- js/dataset/Index.svelte | 98 ++++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/gradio/utils.py b/gradio/utils.py index fca853114f54e..c6d5d3dfbee5a 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -1098,7 +1098,7 @@ def is_in_or_equal(path_1: str | Path, path_2: str | Path) -> bool: @document() -def set_static_paths(paths: str | Path |list[str | Path]) -> list[str | Path]: +def set_static_paths(paths: str | Path | list[str | Path]) -> list[str | Path]: """ Set the static paths to be served by the gradio app. @@ -1129,6 +1129,7 @@ def set_static_paths(paths: str | Path |list[str | Path]) -> list[str | Path]: demo.launch() """ from gradio.data_classes import _StaticFiles + if isinstance(paths, (str, Path)): paths = [paths] _StaticFiles.all_paths.extend([Path(p).resolve() for p in paths]) diff --git a/js/dataset/Index.svelte b/js/dataset/Index.svelte index 28a933387fca6..e6f94aec5ca17 100644 --- a/js/dataset/Index.svelte +++ b/js/dataset/Index.svelte @@ -185,58 +185,56 @@ {/if} {/each}
- {:else} - {#if selected_samples.length > 0} -
- - - - {#each headers as header} - + {:else if selected_samples.length > 0} +
+
- {header} -
+ + + {#each headers as header} + + {/each} + + + + {#each component_meta as sample_row, i} + { + value = i + page * samples_per_page; + gradio.dispatch("click", value); + }} + on:mouseenter={() => handle_mouseenter(i)} + on:mouseleave={() => handle_mouseleave()} + > + {#each sample_row as { value, component }, j} + {@const component_name = components[j]} + {#if component_name !== undefined && component_map.get(component_name) !== undefined} + + {/if} {/each} - - - {#each component_meta as sample_row, i} - { - value = i + page * samples_per_page; - gradio.dispatch("click", value); - }} - on:mouseenter={() => handle_mouseenter(i)} - on:mouseleave={() => handle_mouseleave()} - > - {#each sample_row as { value, component }, j} - {@const component_name = components[j]} - {#if component_name !== undefined && component_map.get(component_name) !== undefined} - - {/if} - {/each} - - {/each} - -
+ {header} +
+ +
- -
-
- {/if} + {/each} + + + {/if} {#if paginate}
From 51e1daad4cd827d7e010accea0636d4911837bf1 Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Tue, 31 Dec 2024 17:54:48 +0000 Subject: [PATCH 34/69] add changeset --- .changeset/tiny-areas-train.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/tiny-areas-train.md b/.changeset/tiny-areas-train.md index f07259cbaa71a..0e0f8864986ae 100644 --- a/.changeset/tiny-areas-train.md +++ b/.changeset/tiny-areas-train.md @@ -2,6 +2,7 @@ "@gradio/button": minor "@gradio/dataset": minor "@gradio/icons": minor +"@gradio/textbox": minor "gradio": minor --- From d95022b2cd5223f0cbbc064f49f6b688394b7cba Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 12:55:50 -0500 Subject: [PATCH 35/69] changes --- gradio/chat_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 04738b56e614b..bc1c1d6718467 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -141,7 +141,7 @@ def __init__( fill_height: if True, the chat interface will expand to the height of window. fill_width: Whether to horizontally expand to fill container fully. If False, centers and constrains app to a maximum width. api_name: the name of the API endpoint to use for the chat interface. Defaults to "chat". Set to False to disable the API endpoint. - save_history: if True, will save the chat history to the browser's local storage. Defaults to False. + save_history: if True, will save the chat history to the browser's local storage and display previous conversations in a side panel. """ super().__init__( analytics_enabled=analytics_enabled, From 86f44321d9483d0d4590ea58350913b91f048ccd Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 12:56:19 -0500 Subject: [PATCH 36/69] docs --- gradio/components/button.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradio/components/button.py b/gradio/components/button.py index 655d33b4dd51b..44c2da3df89d8 100644 --- a/gradio/components/button.py +++ b/gradio/components/button.py @@ -49,7 +49,7 @@ def __init__( inputs: components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change. variant: sets the background and text color of the button. Use 'primary' for main call-to-action buttons, 'secondary' for a more subdued style, 'stop' for a stop button, 'huggingface' for a black background with white text, consistent with Hugging Face's button styles. size: size of the button. Can be "sm" or "lg". - icon: URL or path to the icon file to display within the button. Or a base64-encoded image. If None, no icon will be displayed. + icon: URL or path to the icon file to display within the button. If None, no icon will be displayed. link: URL to open when the button is clicked. If None, no link will be used. visible: if False, component will be hidden. interactive: if False, the Button will be in a disabled state. From d18879b40132fce8715c2f583348711cb08cf0c3 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 13:02:13 -0500 Subject: [PATCH 37/69] changes --- gradio/components/clear_button.py | 2 +- gradio/components/download_button.py | 2 +- gradio/components/duplicate_button.py | 2 +- gradio/components/login_button.py | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gradio/components/clear_button.py b/gradio/components/clear_button.py index f8ba9c404872d..fa20b26f4ca07 100644 --- a/gradio/components/clear_button.py +++ b/gradio/components/clear_button.py @@ -36,7 +36,7 @@ def __init__( every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", - size: Literal["sm", "lg"] | None = None, + size: Literal["sm", "md", "lg"] | None = None, icon: str | None = None, link: str | None = None, visible: bool = True, diff --git a/gradio/components/download_button.py b/gradio/components/download_button.py index 6c34718c09745..e12ec00849da6 100644 --- a/gradio/components/download_button.py +++ b/gradio/components/download_button.py @@ -37,7 +37,7 @@ def __init__( inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", visible: bool = True, - size: Literal["sm", "lg"] | None = None, + size: Literal["sm", "md", "lg"] | None = None, icon: str | None = None, scale: int | None = None, min_width: int | None = None, diff --git a/gradio/components/duplicate_button.py b/gradio/components/duplicate_button.py index 30433d2d6462d..2517add43d5d9 100644 --- a/gradio/components/duplicate_button.py +++ b/gradio/components/duplicate_button.py @@ -30,7 +30,7 @@ def __init__( every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop", "huggingface"] = "huggingface", - size: Literal["sm", "lg"] | None = "sm", + size: Literal["sm", "md", "lg"] | None = "sm", icon: str | None = None, link: str | None = None, visible: bool = True, diff --git a/gradio/components/login_button.py b/gradio/components/login_button.py index ca34d2ec34a57..64b2f86c56a8f 100644 --- a/gradio/components/login_button.py +++ b/gradio/components/login_button.py @@ -10,6 +10,7 @@ from gradio_client.documentation import document +from gradio import utils from gradio.components import Button, Component from gradio.context import get_blocks_context from gradio.routes import Request @@ -34,9 +35,9 @@ def __init__( every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop", "huggingface"] = "huggingface", - size: Literal["sm", "lg"] | None = None, + size: Literal["sm", "md", "lg"] | None = None, icon: str - | None = "https://huggingface.co/front/assets/huggingface_logo-noborder.svg", + | None = utils.get_icon_path("Huggingface-Logo.svg"), link: str | None = None, visible: bool = True, interactive: bool = True, From 7c4a8e3944b267bcffb2002a5d09229e5811f6b3 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 13:02:29 -0500 Subject: [PATCH 38/69] changes --- gradio/components/login_button.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gradio/components/login_button.py b/gradio/components/login_button.py index 64b2f86c56a8f..e2a5b8497ee75 100644 --- a/gradio/components/login_button.py +++ b/gradio/components/login_button.py @@ -36,8 +36,7 @@ def __init__( inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop", "huggingface"] = "huggingface", size: Literal["sm", "md", "lg"] | None = None, - icon: str - | None = utils.get_icon_path("Huggingface-Logo.svg"), + icon: str | None = utils.get_icon_path("Huggingface-Logo.svg"), link: str | None = None, visible: bool = True, interactive: bool = True, From af6523e30cce2c49194b0b3ad41d571b4b6338fc Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 13:02:47 -0500 Subject: [PATCH 39/69] changes --- gradio/components/login_button.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradio/components/login_button.py b/gradio/components/login_button.py index e2a5b8497ee75..5d0827aaf85d0 100644 --- a/gradio/components/login_button.py +++ b/gradio/components/login_button.py @@ -6,6 +6,7 @@ import time import warnings from collections.abc import Sequence +from pathlib import Path from typing import TYPE_CHECKING, Literal from gradio_client.documentation import document @@ -36,7 +37,7 @@ def __init__( inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop", "huggingface"] = "huggingface", size: Literal["sm", "md", "lg"] | None = None, - icon: str | None = utils.get_icon_path("Huggingface-Logo.svg"), + icon: str | Path | None = utils.get_icon_path("Huggingface-Logo.svg"), link: str | None = None, visible: bool = True, interactive: bool = True, From 037b1e9202bed036886f45b0e9c39311a0b5aa0a Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 13:04:02 -0500 Subject: [PATCH 40/69] changes --- gradio/components/clear_button.py | 3 ++- gradio/components/duplicate_button.py | 3 ++- js/icons/src/Huggingface-Logo.svg | 37 +++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 js/icons/src/Huggingface-Logo.svg diff --git a/gradio/components/clear_button.py b/gradio/components/clear_button.py index fa20b26f4ca07..bce4383d86026 100644 --- a/gradio/components/clear_button.py +++ b/gradio/components/clear_button.py @@ -5,6 +5,7 @@ import copy import json from collections.abc import Sequence +from pathlib import Path from typing import TYPE_CHECKING, Any, Literal from gradio_client.documentation import document @@ -37,7 +38,7 @@ def __init__( inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", size: Literal["sm", "md", "lg"] | None = None, - icon: str | None = None, + icon: str | Path | None = None, link: str | None = None, visible: bool = True, interactive: bool = True, diff --git a/gradio/components/duplicate_button.py b/gradio/components/duplicate_button.py index 2517add43d5d9..db9e8b0d9591b 100644 --- a/gradio/components/duplicate_button.py +++ b/gradio/components/duplicate_button.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Sequence +from pathlib import Path from typing import TYPE_CHECKING, Literal from gradio_client.documentation import document @@ -31,7 +32,7 @@ def __init__( inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop", "huggingface"] = "huggingface", size: Literal["sm", "md", "lg"] | None = "sm", - icon: str | None = None, + icon: str | Path | None = None, link: str | None = None, visible: bool = True, interactive: bool = True, diff --git a/js/icons/src/Huggingface-Logo.svg b/js/icons/src/Huggingface-Logo.svg new file mode 100644 index 0000000000000..43c5d3c0c97a9 --- /dev/null +++ b/js/icons/src/Huggingface-Logo.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + From ded0b3d8ed584fa1c9f3ed60325c702686995d48 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 13:10:07 -0500 Subject: [PATCH 41/69] fix --- js/button/shared/Button.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/js/button/shared/Button.svelte b/js/button/shared/Button.svelte index cc7b34577a90d..1cd4ca7e84405 100644 --- a/js/button/shared/Button.svelte +++ b/js/button/shared/Button.svelte @@ -13,7 +13,6 @@ export let disabled = false; export let scale: number | null = null; export let min_width: number | undefined = undefined; - console.log("icon", icon); {#if link && link.length > 0} From 7cbc66f338cec972e6afea3d86c423b10a769e16 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 13:28:19 -0500 Subject: [PATCH 42/69] fix tests --- gradio/chat_interface.py | 2 ++ js/spa/test/test_chatinterface_streaming_echo.spec.ts | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index bc1c1d6718467..c0a1e25a47ed6 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -650,6 +650,8 @@ def _setup_events(self) -> None: [self.load, self.saved_conversations.change], inputs=[self.saved_conversations], outputs=[self.chat_history_dataset], + show_api=False, + queue=False, ) def load_chat_history(conversations): return Dataset( diff --git a/js/spa/test/test_chatinterface_streaming_echo.spec.ts b/js/spa/test/test_chatinterface_streaming_echo.spec.ts index b847c2a594640..6cca9de5f4fec 100644 --- a/js/spa/test/test_chatinterface_streaming_echo.spec.ts +++ b/js/spa/test/test_chatinterface_streaming_echo.spec.ts @@ -82,9 +82,8 @@ for (const test_case of cases) { ); const api_recorder = await page.locator("#api-recorder"); await api_recorder.click(); - const n_calls = test_case.includes("non_stream") ? 5 : 7; await expect(page.locator("#num-recorded-api-calls")).toContainText( - `🪄 Recorded API Calls [${n_calls}]` + `🪄 Recorded API Calls` ); }); } From 37f4a84ce4db0234f99b8a652b63c2d9439c5a8f Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 15:50:13 -0500 Subject: [PATCH 43/69] change --- gradio/utils.py | 20 ++++++++--------- js/icons/src/Huggingface-Logo.svg | 37 ------------------------------- js/icons/src/Plus.svg | 4 ---- 3 files changed, 10 insertions(+), 51 deletions(-) delete mode 100644 js/icons/src/Huggingface-Logo.svg delete mode 100644 js/icons/src/Plus.svg diff --git a/gradio/utils.py b/gradio/utils.py index c6d5d3dfbee5a..917f98c40857c 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -8,12 +8,12 @@ import functools import hashlib import importlib +import importlib.resources import importlib.util import inspect import json import json.decoder import os -import pkgutil import posixpath import re import shutil @@ -1098,7 +1098,7 @@ def is_in_or_equal(path_1: str | Path, path_2: str | Path) -> bool: @document() -def set_static_paths(paths: str | Path | list[str | Path]) -> list[str | Path]: +def set_static_paths(paths: str | Path | list[str | Path]) -> None: """ Set the static paths to be served by the gradio app. @@ -1131,9 +1131,8 @@ def set_static_paths(paths: str | Path | list[str | Path]) -> list[str | Path]: from gradio.data_classes import _StaticFiles if isinstance(paths, (str, Path)): - paths = [paths] + paths = [Path(paths)] _StaticFiles.all_paths.extend([Path(p).resolve() for p in paths]) - return paths def is_static_file(file_path: Any): @@ -1593,16 +1592,17 @@ def none_or_singleton_to_list(value: Any) -> list: def get_icon_path(icon_name: str) -> str: - """Get the path to an icon file in the js/icons/src directory - and return it as a static file path so that it can be used in the backend. + """Get the path to an icon file in the gradio/templates/frontend/static/img/ + directory and return it as a static file path so that it can be used by components. Parameters: icon_name: Name of the icon file (e.g. "Plus.svg") Returns: str: Full path to the icon file served as a static file """ - package_directory = Path(__file__).parent.parent - icon_path = package_directory / "js" / "icons" / "src" / icon_name - if not icon_path.exists(): + try: + icon_path = importlib.resources.files("gradio").joinpath("templates/frontend/assets/img", icon_name) + set_static_paths(str(icon_path)) + return str(icon_path) + except FileNotFoundError: raise ValueError(f"Icon file not found: {icon_name}") - return str(set_static_paths(icon_path)[0]) diff --git a/js/icons/src/Huggingface-Logo.svg b/js/icons/src/Huggingface-Logo.svg deleted file mode 100644 index 43c5d3c0c97a9..0000000000000 --- a/js/icons/src/Huggingface-Logo.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - diff --git a/js/icons/src/Plus.svg b/js/icons/src/Plus.svg deleted file mode 100644 index 57194b58a4b14..0000000000000 --- a/js/icons/src/Plus.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From 6e90ca3db8809ce5c214b41f1b8a511d242b1841 Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Tue, 31 Dec 2024 20:51:06 +0000 Subject: [PATCH 44/69] add changeset --- .changeset/tiny-areas-train.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/tiny-areas-train.md b/.changeset/tiny-areas-train.md index 0e0f8864986ae..69cc289d6519d 100644 --- a/.changeset/tiny-areas-train.md +++ b/.changeset/tiny-areas-train.md @@ -1,7 +1,6 @@ --- "@gradio/button": minor "@gradio/dataset": minor -"@gradio/icons": minor "@gradio/textbox": minor "gradio": minor --- From bff664e08c41e101fe5937234d71a3cdd8fef391 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 15:52:47 -0500 Subject: [PATCH 45/69] fix logo issue --- gradio/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradio/utils.py b/gradio/utils.py index 917f98c40857c..d41ab2e48ff16 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -1601,7 +1601,7 @@ def get_icon_path(icon_name: str) -> str: str: Full path to the icon file served as a static file """ try: - icon_path = importlib.resources.files("gradio").joinpath("templates/frontend/assets/img", icon_name) + icon_path = importlib.resources.files("gradio").joinpath("templates/frontend/static/img", icon_name) set_static_paths(str(icon_path)) return str(icon_path) except FileNotFoundError: From 6842fa5f0daad71a6695bfc345f7899cc6dff311 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 15:55:25 -0500 Subject: [PATCH 46/69] changes --- gradio/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gradio/utils.py b/gradio/utils.py index d41ab2e48ff16..1ce37d00e22aa 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -49,6 +49,7 @@ Literal, Optional, TypeVar, + cast, ) import anyio @@ -1591,7 +1592,7 @@ def none_or_singleton_to_list(value: Any) -> list: return [value] -def get_icon_path(icon_name: str) -> str: +def get_icon_path(icon_name: str) -> DeveloperPath: """Get the path to an icon file in the gradio/templates/frontend/static/img/ directory and return it as a static file path so that it can be used by components. @@ -1603,6 +1604,6 @@ def get_icon_path(icon_name: str) -> str: try: icon_path = importlib.resources.files("gradio").joinpath("templates/frontend/static/img", icon_name) set_static_paths(str(icon_path)) - return str(icon_path) + return cast(DeveloperPath, icon_path) except FileNotFoundError: raise ValueError(f"Icon file not found: {icon_name}") From 5052e5d2168ba5c13c78ecf95f63ee37540c65d7 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 15:59:10 -0500 Subject: [PATCH 47/69] version --- client/python/gradio_client/utils.py | 11 ++++------- gradio/utils.py | 22 ++++++++++------------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/client/python/gradio_client/utils.py b/client/python/gradio_client/utils.py index 9d020359c9668..e9059f5dc8227 100644 --- a/client/python/gradio_client/utils.py +++ b/client/python/gradio_client/utils.py @@ -4,10 +4,10 @@ import base64 import concurrent.futures import copy +import importlib.resources import json import mimetypes import os -import pkgutil import secrets import shutil import tempfile @@ -72,12 +72,9 @@ class Message(TypedDict, total=False): def get_package_version() -> str: try: - package_json_data = ( - pkgutil.get_data(__name__, "package.json").decode("utf-8").strip() # type: ignore - ) - package_data = json.loads(package_json_data) - version = package_data.get("version", "") - return version + package_json = importlib.resources.files(__name__).joinpath("package.json") + package_data = json.loads(package_json.read_text()) + return package_data.get("version", "") except Exception: return "" diff --git a/gradio/utils.py b/gradio/utils.py index 1ce37d00e22aa..007584a751599 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -49,7 +49,6 @@ Literal, Optional, TypeVar, - cast, ) import anyio @@ -81,12 +80,9 @@ def get_package_version() -> str: try: - package_json_data = ( - pkgutil.get_data(__name__, "package.json").decode("utf-8").strip() # type: ignore - ) - package_data = json.loads(package_json_data) - version = package_data.get("version", "") - return version + package_json = importlib.resources.files(__name__).joinpath("package.json") + package_data = json.loads(package_json.read_text()) + return package_data.get("version", "") except Exception: return "" @@ -1592,7 +1588,7 @@ def none_or_singleton_to_list(value: Any) -> list: return [value] -def get_icon_path(icon_name: str) -> DeveloperPath: +def get_icon_path(icon_name: str) -> str: """Get the path to an icon file in the gradio/templates/frontend/static/img/ directory and return it as a static file path so that it can be used by components. @@ -1602,8 +1598,10 @@ def get_icon_path(icon_name: str) -> DeveloperPath: str: Full path to the icon file served as a static file """ try: - icon_path = importlib.resources.files("gradio").joinpath("templates/frontend/static/img", icon_name) + icon_path = importlib.resources.files("gradio").joinpath( + "templates/frontend/static/img", icon_name + ) set_static_paths(str(icon_path)) - return cast(DeveloperPath, icon_path) - except FileNotFoundError: - raise ValueError(f"Icon file not found: {icon_name}") + return str(icon_path) + except FileNotFoundError as e: + raise ValueError(f"Icon file not found: {icon_name}") from e From 791deca03aff21caf5440f32dc5c00be96bad208 Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Tue, 31 Dec 2024 20:59:59 +0000 Subject: [PATCH 48/69] add changeset --- .changeset/tiny-areas-train.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/tiny-areas-train.md b/.changeset/tiny-areas-train.md index 69cc289d6519d..6a5103ef274f9 100644 --- a/.changeset/tiny-areas-train.md +++ b/.changeset/tiny-areas-train.md @@ -3,6 +3,7 @@ "@gradio/dataset": minor "@gradio/textbox": minor "gradio": minor +"gradio_client": minor --- feat:Support saving chat history in `gr.ChatInterface` From 59bcf92142751720b2f2b51cb0bc64f3b47703ba Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 17:17:30 -0500 Subject: [PATCH 49/69] fix typecheck --- gradio/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradio/utils.py b/gradio/utils.py index 007584a751599..76d8353026b7a 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -1599,7 +1599,7 @@ def get_icon_path(icon_name: str) -> str: """ try: icon_path = importlib.resources.files("gradio").joinpath( - "templates/frontend/static/img", icon_name + str(Path("templates") / "frontend" / "static" / "img" / icon_name) ) set_static_paths(str(icon_path)) return str(icon_path) From ddbe6a8d982eb04d1e35282cce6e2355c77221aa Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 17:40:02 -0500 Subject: [PATCH 50/69] remove redundant --- gradio/chat_interface.py | 39 ++++----------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index c0a1e25a47ed6..4ff39f8680ee3 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -235,7 +235,7 @@ def __init__( if description: Markdown(description) with Row(): - if save_history: + if self.save_history: with Column(scale=1, min_width=100): self.new_chat_button = Button( "New chat", @@ -320,40 +320,9 @@ def __init__( stop_btn=stop_btn, ) - # Hide the stop button at the beginning, and show it with the given value during the generator execution. - self.original_stop_btn = self.textbox.stop_btn - self.textbox.stop_btn = False - - self.fake_api_btn = Button("Fake API", visible=False) - self.fake_response_textbox = Textbox( - label="Response", visible=False - ) # Used to store the response from the API call - - if self.examples: - self.examples_handler = Examples( - examples=self.examples, - inputs=[self.textbox] + self.additional_inputs, - outputs=self.chatbot, - fn=self._examples_stream_fn - if self.is_generator - else self._examples_fn, - cache_examples=self.cache_examples, - cache_mode=self.cache_mode, - visible=self._additional_inputs_in_examples, - preprocess=self._additional_inputs_in_examples, - ) - - any_unrendered_inputs = any( - not inp.is_rendered for inp in self.additional_inputs - ) - if self.additional_inputs and any_unrendered_inputs: - with Accordion(**self.additional_inputs_accordion_params): # type: ignore - for input_component in self.additional_inputs: - if not input_component.is_rendered: - input_component.render() - # Hide the stop button at the beginning, and show it with the given value during the generator execution. - self.original_stop_btn = self.textbox.stop_btn - self.textbox.stop_btn = False + # Hide the stop button at the beginning, and show it with the given value during the generator execution. + self.original_stop_btn = self.textbox.stop_btn + self.textbox.stop_btn = False self.fake_api_btn = Button("Fake API", visible=False) self.api_response = JSON( From 92e905e505b9227f515bc275d70da9870c4528a3 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 17:49:14 -0500 Subject: [PATCH 51/69] pkg version --- client/python/gradio_client/utils.py | 11 +++++++---- gradio/utils.py | 10 +++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/client/python/gradio_client/utils.py b/client/python/gradio_client/utils.py index e9059f5dc8227..9d020359c9668 100644 --- a/client/python/gradio_client/utils.py +++ b/client/python/gradio_client/utils.py @@ -4,10 +4,10 @@ import base64 import concurrent.futures import copy -import importlib.resources import json import mimetypes import os +import pkgutil import secrets import shutil import tempfile @@ -72,9 +72,12 @@ class Message(TypedDict, total=False): def get_package_version() -> str: try: - package_json = importlib.resources.files(__name__).joinpath("package.json") - package_data = json.loads(package_json.read_text()) - return package_data.get("version", "") + package_json_data = ( + pkgutil.get_data(__name__, "package.json").decode("utf-8").strip() # type: ignore + ) + package_data = json.loads(package_json_data) + version = package_data.get("version", "") + return version except Exception: return "" diff --git a/gradio/utils.py b/gradio/utils.py index 76d8353026b7a..2ffe4655d24c0 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -14,6 +14,7 @@ import json import json.decoder import os +import pkgutil import posixpath import re import shutil @@ -80,9 +81,12 @@ def get_package_version() -> str: try: - package_json = importlib.resources.files(__name__).joinpath("package.json") - package_data = json.loads(package_json.read_text()) - return package_data.get("version", "") + package_json_data = ( + pkgutil.get_data(__name__, "package.json").decode("utf-8").strip() # type: ignore + ) + package_data = json.loads(package_json_data) + version = package_data.get("version", "") + return version except Exception: return "" From 9075df8660c5f82586e1efb7b929ea108af9eb26 Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Tue, 31 Dec 2024 22:50:09 +0000 Subject: [PATCH 52/69] add changeset --- .changeset/tiny-areas-train.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/tiny-areas-train.md b/.changeset/tiny-areas-train.md index 6a5103ef274f9..69cc289d6519d 100644 --- a/.changeset/tiny-areas-train.md +++ b/.changeset/tiny-areas-train.md @@ -3,7 +3,6 @@ "@gradio/dataset": minor "@gradio/textbox": minor "gradio": minor -"gradio_client": minor --- feat:Support saving chat history in `gr.ChatInterface` From 13bfe8c485d049f7d8c6f1e5c13e2bc04ab71dd5 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 17:59:02 -0500 Subject: [PATCH 53/69] changes --- scripts/copy_demos.py | 6 ++++++ testing-guidelines/ci.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/copy_demos.py b/scripts/copy_demos.py index 3a38fa8490305..a48618d421158 100644 --- a/scripts/copy_demos.py +++ b/scripts/copy_demos.py @@ -1,3 +1,9 @@ +""" +We pull in a select number of spaces and build them into a single FastAPI application to preview them on HF Spaces. +The script that is run in CI is located at: https://github.com/gradio-app/github/blob/main/packages/copy-demos/index.ts +This is the Python version of that script for local use. +""" + import argparse import os import pathlib diff --git a/testing-guidelines/ci.md b/testing-guidelines/ci.md index 569c5eb32792b..f21a8b69861b3 100644 --- a/testing-guidelines/ci.md +++ b/testing-guidelines/ci.md @@ -236,7 +236,7 @@ The `spaces` preview is a little more involved as it is a custom process and req The process is relatively straightforward, and follows [the steps mentioned above](#spaces) but there a few details to be aware of. -- We use [a custom script](https://github.com/gradio-app/gradio/blob/main/scripts/copy_demos.py) to pull in a select number of spaces and build them into a single FastAPI application. We serve each demo on its own subpath. This is the demo app that gets deployed to spaces. +- We use [a custom script](https://github.com/gradio-app/github/blob/main/packages/copy-demos/index.ts) to pull in a select number of spaces and build them into a single FastAPI application. We serve each demo on its own subpath. This is the demo app that gets deployed to spaces. - We build a new wheel from the pull requests source code and upload it to s3, we then add the url for this wheel to the requirements.txt of the space we are deploying. - The wheel name (and subsequently the url) include the commit SHA, every build is unique even for the same pull request - It is important the 'version' of the wheel is the same as the latest version of Gradio. This is because spaces _first_ installs the requirements from the `requirements.txt` and _then_ installs whatever it needs to based on the `sdk` field of the spaces `README.md`. Since the `sdk` is set to Gradio in this case, it will attempt to install the latest version of Gradio and see that the version requirement is already satisfied. If we didn't have matching versions then our custom wheel would be overwritten. From 32bed0d5d0dd5c404cae6a29a81aa1cfeaedae60 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 18:00:53 -0500 Subject: [PATCH 54/69] Revert "changes" This reverts commit 13bfe8c485d049f7d8c6f1e5c13e2bc04ab71dd5. --- scripts/copy_demos.py | 6 ------ testing-guidelines/ci.md | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/scripts/copy_demos.py b/scripts/copy_demos.py index a48618d421158..3a38fa8490305 100644 --- a/scripts/copy_demos.py +++ b/scripts/copy_demos.py @@ -1,9 +1,3 @@ -""" -We pull in a select number of spaces and build them into a single FastAPI application to preview them on HF Spaces. -The script that is run in CI is located at: https://github.com/gradio-app/github/blob/main/packages/copy-demos/index.ts -This is the Python version of that script for local use. -""" - import argparse import os import pathlib diff --git a/testing-guidelines/ci.md b/testing-guidelines/ci.md index f21a8b69861b3..569c5eb32792b 100644 --- a/testing-guidelines/ci.md +++ b/testing-guidelines/ci.md @@ -236,7 +236,7 @@ The `spaces` preview is a little more involved as it is a custom process and req The process is relatively straightforward, and follows [the steps mentioned above](#spaces) but there a few details to be aware of. -- We use [a custom script](https://github.com/gradio-app/github/blob/main/packages/copy-demos/index.ts) to pull in a select number of spaces and build them into a single FastAPI application. We serve each demo on its own subpath. This is the demo app that gets deployed to spaces. +- We use [a custom script](https://github.com/gradio-app/gradio/blob/main/scripts/copy_demos.py) to pull in a select number of spaces and build them into a single FastAPI application. We serve each demo on its own subpath. This is the demo app that gets deployed to spaces. - We build a new wheel from the pull requests source code and upload it to s3, we then add the url for this wheel to the requirements.txt of the space we are deploying. - The wheel name (and subsequently the url) include the commit SHA, every build is unique even for the same pull request - It is important the 'version' of the wheel is the same as the latest version of Gradio. This is because spaces _first_ installs the requirements from the `requirements.txt` and _then_ installs whatever it needs to based on the `sdk` field of the spaces `README.md`. Since the `sdk` is set to Gradio in this case, it will attempt to install the latest version of Gradio and see that the version requirement is already satisfied. If we didn't have matching versions then our custom wheel would be overwritten. From 48093c1622a6f1b5e25086a42d1f876fd480605c Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 18:41:55 -0500 Subject: [PATCH 55/69] reorganize code --- gradio/chat_interface.py | 267 ++++++++++++++++++++------------------- 1 file changed, 140 insertions(+), 127 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 4ff39f8680ee3..c453ead12e243 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -180,6 +180,12 @@ def __init__( self.cache_examples = cache_examples self.cache_mode = cache_mode self.editable = editable + self.fill_height = fill_height + self.autoscroll = autoscroll + self.autofocus = autofocus + self.title = title + self.description = description + self.show_progress = show_progress if save_history and not type == "messages": raise ValueError("save_history is only supported for type='messages'") self.save_history = save_history @@ -223,142 +229,149 @@ def __init__( with self: self.saved_conversations = BrowserState( - [], storage_key="saved_conversations" + [], storage_key="_saved_conversations" ) self.conversation_id = State(None) + self.saved_input = State() # Stores the most recent user message + self.null_component = State() # Used to discard unneeded values + self.chatbot_state = State(self.chatbot.value if self.chatbot.value else []) with Column(): - if title: - Markdown( - f"

{self.title}

" - ) - if description: - Markdown(description) + self._render_header() with Row(): - if self.save_history: - with Column(scale=1, min_width=100): - self.new_chat_button = Button( - "New chat", - variant="secondary", - size="md", - icon=utils.get_icon_path("Plus.svg"), - ) - self.chat_history_dataset = Dataset( - components=[Textbox(visible=False)], - show_label=False, - layout="table", - type="index", - ) - + self._render_history_area() with Column(scale=6): - if chatbot: - if self.type: - if self.type != chatbot.type: - warnings.warn( - "The type of the gr.Chatbot does not match the type of the gr.ChatInterface." - f"The type of the gr.ChatInterface, '{self.type}', will be used." - ) - chatbot.type = self.type - chatbot._setup_data_model() - else: - warnings.warn( - f"The gr.ChatInterface was not provided with a type, so the type of the gr.Chatbot, '{chatbot.type}', will be used." - ) - self.type = chatbot.type - self.chatbot = cast( - Chatbot, get_component_instance(chatbot, render=True) - ) - if self.chatbot.examples and self.examples_messages: - warnings.warn( - "The ChatInterface already has examples set. The examples provided in the chatbot will be ignored." - ) - self.chatbot.examples = ( - self.examples_messages - if not self._additional_inputs_in_examples - else None - ) - self.chatbot._setup_examples() - else: - self.type = self.type or "tuples" - self.chatbot = Chatbot( - label="Chatbot", - scale=1, - height=400 if fill_height else None, - type=self.type, - autoscroll=autoscroll, - examples=self.examples_messages - if not self._additional_inputs_in_examples - else None, - ) - with Group(): - with Row(): - if textbox: - textbox.show_label = False - textbox_ = get_component_instance( - textbox, render=True - ) - if not isinstance( - textbox_, (Textbox, MultimodalTextbox) - ): - raise TypeError( - f"Expected a gr.Textbox or gr.MultimodalTextbox component, but got {builtins.type(textbox_)}" - ) - self.textbox = textbox_ - else: - textbox_component = ( - MultimodalTextbox - if self.multimodal - else Textbox - ) - self.textbox = textbox_component( - show_label=False, - label="Message", - placeholder="Type a message...", - scale=7, - autofocus=autofocus, - submit_btn=submit_btn, - stop_btn=stop_btn, - ) - - # Hide the stop button at the beginning, and show it with the given value during the generator execution. - self.original_stop_btn = self.textbox.stop_btn - self.textbox.stop_btn = False - - self.fake_api_btn = Button("Fake API", visible=False) - self.api_response = JSON( - label="Response", visible=False - ) # Used to store the response from the API call - - if self.examples: - self.examples_handler = Examples( - examples=self.examples, - inputs=[self.textbox] + self.additional_inputs, - outputs=self.chatbot, - fn=self._examples_stream_fn - if self.is_generator - else self._examples_fn, - cache_examples=self.cache_examples, - cache_mode=self.cache_mode, - visible=self._additional_inputs_in_examples, - preprocess=self._additional_inputs_in_examples, - ) + self._render_chatbot_area(chatbot, textbox, submit_btn, stop_btn) + self._render_footer() + self._setup_events() + + def _render_header(self): + if self.title: + Markdown( + f"

{self.title}

" + ) + if self.description: + Markdown(self.description) - any_unrendered_inputs = any( - not inp.is_rendered for inp in self.additional_inputs + def _render_history_area(self): + if self.save_history: + with Column(scale=1, min_width=100): + self.new_chat_button = Button( + "New chat", + variant="secondary", + size="md", + icon=utils.get_icon_path("Plus.svg"), ) - if self.additional_inputs and any_unrendered_inputs: - with Accordion(**self.additional_inputs_accordion_params): # type: ignore - for input_component in self.additional_inputs: - if not input_component.is_rendered: - input_component.render() - - self.saved_input = State() # Stores the most recent user message - self.null_component = State() # Used to discard unneeded values - self.chatbot_state = ( - State(self.chatbot.value) if self.chatbot.value else State([]) + self.chat_history_dataset = Dataset( + components=[Textbox(visible=False)], + show_label=False, + layout="table", + type="index", ) - self.show_progress = show_progress - self._setup_events() + + def _render_chatbot_area(self, chatbot: Chatbot | None, textbox: Textbox | MultimodalTextbox | None, submit_btn: str | bool | None, stop_btn: str | bool | None): + if chatbot: + if self.type: + if self.type != chatbot.type: + warnings.warn( + "The type of the gr.Chatbot does not match the type of the gr.ChatInterface." + f"The type of the gr.ChatInterface, '{self.type}', will be used." + ) + chatbot.type = cast(Literal["messages", "tuples"], self.type) + chatbot._setup_data_model() + else: + warnings.warn( + f"The gr.ChatInterface was not provided with a type, so the type of the gr.Chatbot, '{chatbot.type}', will be used." + ) + self.type = chatbot.type + self.chatbot = cast( + Chatbot, get_component_instance(chatbot, render=True) + ) + if self.chatbot.examples and self.examples_messages: + warnings.warn( + "The ChatInterface already has examples set. The examples provided in the chatbot will be ignored." + ) + self.chatbot.examples = ( + self.examples_messages + if not self._additional_inputs_in_examples + else None + ) + self.chatbot._setup_examples() + else: + self.type = self.type or "tuples" + self.chatbot = Chatbot( + label="Chatbot", + scale=1, + height=400 if self.fill_height else None, + type=cast(Literal["messages", "tuples"], self.type), + autoscroll=self.autoscroll, + examples=self.examples_messages + if not self._additional_inputs_in_examples + else None, + ) + with Group(): + with Row(): + if textbox: + textbox.show_label = False + textbox_ = get_component_instance( + textbox, render=True + ) + if not isinstance( + textbox_, (Textbox, MultimodalTextbox) + ): + raise TypeError( + f"Expected a gr.Textbox or gr.MultimodalTextbox component, but got {builtins.type(textbox_)}" + ) + self.textbox = textbox_ + else: + textbox_component = ( + MultimodalTextbox + if self.multimodal + else Textbox + ) + self.textbox = textbox_component( + show_label=False, + label="Message", + placeholder="Type a message...", + scale=7, + autofocus=self.autofocus, + submit_btn=submit_btn, + stop_btn=stop_btn, + ) + + # Hide the stop button at the beginning, and show it with the given value during the generator execution. + self.original_stop_btn = self.textbox.stop_btn + self.textbox.stop_btn = False + + self.fake_api_btn = Button("Fake API", visible=False) + self.api_response = JSON( + label="Response", visible=False + ) # Used to store the response from the API call + + def _render_footer(self): + if self.examples: + self.examples_handler = Examples( + examples=self.examples, + inputs=[self.textbox] + self.additional_inputs, + outputs=self.chatbot, + fn=self._examples_stream_fn + if self.is_generator + else self._examples_fn, + cache_examples=self.cache_examples, + cache_mode=cast(Literal["eager", "lazy"], self.cache_mode), + visible=self._additional_inputs_in_examples, + preprocess=self._additional_inputs_in_examples, + ) + + any_unrendered_inputs = any( + not inp.is_rendered for inp in self.additional_inputs + ) + if self.additional_inputs and any_unrendered_inputs: + with Accordion(**self.additional_inputs_accordion_params): # type: ignore + for input_component in self.additional_inputs: + if not input_component.is_rendered: + input_component.render() + def _setup_example_messages( self, From edeb03517ee196816554b3726e72b958f9debe7e Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 18:44:56 -0500 Subject: [PATCH 56/69] format --- gradio/chat_interface.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index c453ead12e243..5eef65543f1f9 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -241,7 +241,9 @@ def __init__( with Row(): self._render_history_area() with Column(scale=6): - self._render_chatbot_area(chatbot, textbox, submit_btn, stop_btn) + self._render_chatbot_area( + chatbot, textbox, submit_btn, stop_btn + ) self._render_footer() self._setup_events() @@ -269,7 +271,13 @@ def _render_history_area(self): type="index", ) - def _render_chatbot_area(self, chatbot: Chatbot | None, textbox: Textbox | MultimodalTextbox | None, submit_btn: str | bool | None, stop_btn: str | bool | None): + def _render_chatbot_area( + self, + chatbot: Chatbot | None, + textbox: Textbox | MultimodalTextbox | None, + submit_btn: str | bool | None, + stop_btn: str | bool | None, + ): if chatbot: if self.type: if self.type != chatbot.type: @@ -284,9 +292,7 @@ def _render_chatbot_area(self, chatbot: Chatbot | None, textbox: Textbox | Multi f"The gr.ChatInterface was not provided with a type, so the type of the gr.Chatbot, '{chatbot.type}', will be used." ) self.type = chatbot.type - self.chatbot = cast( - Chatbot, get_component_instance(chatbot, render=True) - ) + self.chatbot = cast(Chatbot, get_component_instance(chatbot, render=True)) if self.chatbot.examples and self.examples_messages: warnings.warn( "The ChatInterface already has examples set. The examples provided in the chatbot will be ignored." @@ -313,21 +319,15 @@ def _render_chatbot_area(self, chatbot: Chatbot | None, textbox: Textbox | Multi with Row(): if textbox: textbox.show_label = False - textbox_ = get_component_instance( - textbox, render=True - ) - if not isinstance( - textbox_, (Textbox, MultimodalTextbox) - ): + textbox_ = get_component_instance(textbox, render=True) + if not isinstance(textbox_, (Textbox, MultimodalTextbox)): raise TypeError( f"Expected a gr.Textbox or gr.MultimodalTextbox component, but got {builtins.type(textbox_)}" ) self.textbox = textbox_ else: textbox_component = ( - MultimodalTextbox - if self.multimodal - else Textbox + MultimodalTextbox if self.multimodal else Textbox ) self.textbox = textbox_component( show_label=False, @@ -354,9 +354,7 @@ def _render_footer(self): examples=self.examples, inputs=[self.textbox] + self.additional_inputs, outputs=self.chatbot, - fn=self._examples_stream_fn - if self.is_generator - else self._examples_fn, + fn=self._examples_stream_fn if self.is_generator else self._examples_fn, cache_examples=self.cache_examples, cache_mode=cast(Literal["eager", "lazy"], self.cache_mode), visible=self._additional_inputs_in_examples, @@ -372,7 +370,6 @@ def _render_footer(self): if not input_component.is_rendered: input_component.render() - def _setup_example_messages( self, examples: list[str] | list[MultimodalValue] | list[list] | None, From abb8a8ea7405c25ee8f3c337cba8c11d6a5bd167 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 31 Dec 2024 18:45:51 -0500 Subject: [PATCH 57/69] changes --- gradio/chat_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 5eef65543f1f9..213a1f1b6fb70 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -234,7 +234,6 @@ def __init__( self.conversation_id = State(None) self.saved_input = State() # Stores the most recent user message self.null_component = State() # Used to discard unneeded values - self.chatbot_state = State(self.chatbot.value if self.chatbot.value else []) with Column(): self._render_header() @@ -343,6 +342,7 @@ def _render_chatbot_area( self.original_stop_btn = self.textbox.stop_btn self.textbox.stop_btn = False + self.chatbot_state = State(self.chatbot.value if self.chatbot.value else []) self.fake_api_btn = Button("Fake API", visible=False) self.api_response = JSON( label="Response", visible=False From df7c38845cc158296242c9540609200cbecbd2fa Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Thu, 2 Jan 2025 08:10:55 -0500 Subject: [PATCH 58/69] add to deployed demos --- demo/chatinterface_streaming_echo/run.ipynb | 2 +- demo/chatinterface_streaming_echo/run.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/chatinterface_streaming_echo/run.ipynb b/demo/chatinterface_streaming_echo/run.ipynb index d077e6dcccf98..891ce4bbd1a79 100644 --- a/demo/chatinterface_streaming_echo/run.ipynb +++ b/demo/chatinterface_streaming_echo/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_streaming_echo"]}, {"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 time\n", "import gradio as gr\n", "\n", "def slow_echo(message, history):\n", " for i in range(len(message)):\n", " time.sleep(0.05)\n", " yield \"You typed: \" + message[: i + 1]\n", "\n", "demo = gr.ChatInterface(slow_echo, type=\"messages\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_streaming_echo"]}, {"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 time\n", "import gradio as gr\n", "\n", "def slow_echo(message, history):\n", " for i in range(len(message)):\n", " time.sleep(0.05)\n", " yield \"You typed: \" + message[: i + 1]\n", "\n", "demo = gr.ChatInterface(slow_echo, type=\"messages\", save_history=True)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/chatinterface_streaming_echo/run.py b/demo/chatinterface_streaming_echo/run.py index 6e87b7dcc0759..2499cf56691b0 100644 --- a/demo/chatinterface_streaming_echo/run.py +++ b/demo/chatinterface_streaming_echo/run.py @@ -6,7 +6,7 @@ def slow_echo(message, history): time.sleep(0.05) yield "You typed: " + message[: i + 1] -demo = gr.ChatInterface(slow_echo, type="messages") +demo = gr.ChatInterface(slow_echo, type="messages", save_history=True) if __name__ == "__main__": demo.launch() From 9e79e86fc255406f6dd121cf79c42b9e03681424 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Thu, 2 Jan 2025 10:08:36 -0500 Subject: [PATCH 59/69] fix icons --- gradio/chat_interface.py | 2 +- gradio/components/login_button.py | 2 +- gradio/icons/README.md | 2 ++ gradio/icons/huggingface-logo.svg | 37 +++++++++++++++++++++++++++++++ gradio/icons/plus.svg | 17 ++++++++++++++ gradio/utils.py | 4 ++-- 6 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 gradio/icons/README.md create mode 100644 gradio/icons/huggingface-logo.svg create mode 100644 gradio/icons/plus.svg diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py index 213a1f1b6fb70..cc5aae57bce35 100644 --- a/gradio/chat_interface.py +++ b/gradio/chat_interface.py @@ -261,7 +261,7 @@ def _render_history_area(self): "New chat", variant="secondary", size="md", - icon=utils.get_icon_path("Plus.svg"), + icon=utils.get_icon_path("plus.svg"), ) self.chat_history_dataset = Dataset( components=[Textbox(visible=False)], diff --git a/gradio/components/login_button.py b/gradio/components/login_button.py index 5d0827aaf85d0..f8eca3c58ecec 100644 --- a/gradio/components/login_button.py +++ b/gradio/components/login_button.py @@ -37,7 +37,7 @@ def __init__( inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop", "huggingface"] = "huggingface", size: Literal["sm", "md", "lg"] | None = None, - icon: str | Path | None = utils.get_icon_path("Huggingface-Logo.svg"), + icon: str | Path | None = utils.get_icon_path("huggingface-logo.svg"), link: str | None = None, visible: bool = True, interactive: bool = True, diff --git a/gradio/icons/README.md b/gradio/icons/README.md new file mode 100644 index 0000000000000..f38df88f11fe1 --- /dev/null +++ b/gradio/icons/README.md @@ -0,0 +1,2 @@ +The icons in this directory are loaded via `gradio.utils.get_icon_path` and +can be used directly in backend code (e.g. to populate icons in components). \ No newline at end of file diff --git a/gradio/icons/huggingface-logo.svg b/gradio/icons/huggingface-logo.svg new file mode 100644 index 0000000000000..43c5d3c0c97a9 --- /dev/null +++ b/gradio/icons/huggingface-logo.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/gradio/icons/plus.svg b/gradio/icons/plus.svg new file mode 100644 index 0000000000000..dc38228265078 --- /dev/null +++ b/gradio/icons/plus.svg @@ -0,0 +1,17 @@ + + + diff --git a/gradio/utils.py b/gradio/utils.py index 2ffe4655d24c0..b7c54b4a55c43 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -1593,7 +1593,7 @@ def none_or_singleton_to_list(value: Any) -> list: def get_icon_path(icon_name: str) -> str: - """Get the path to an icon file in the gradio/templates/frontend/static/img/ + """Get the path to an icon file in the gradio/icons/ directory and return it as a static file path so that it can be used by components. Parameters: @@ -1603,7 +1603,7 @@ def get_icon_path(icon_name: str) -> str: """ try: icon_path = importlib.resources.files("gradio").joinpath( - str(Path("templates") / "frontend" / "static" / "img" / icon_name) + str(Path("icons") / icon_name) ) set_static_paths(str(icon_path)) return str(icon_path) From 4fe0eaf0147d0343f6c08d39ab499900b0dfe4f0 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Thu, 2 Jan 2025 10:12:23 -0500 Subject: [PATCH 60/69] fix icon --- gradio/utils.py | 21 ++++++++++----------- test/test_utils.py | 6 ++++++ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/gradio/utils.py b/gradio/utils.py index b7c54b4a55c43..6ba2a922b73bc 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -1593,19 +1593,18 @@ def none_or_singleton_to_list(value: Any) -> list: def get_icon_path(icon_name: str) -> str: - """Get the path to an icon file in the gradio/icons/ - directory and return it as a static file path so that it can be used by components. + """Get the path to an icon file in the gradio/icons/ directory + and return it as a static file path so that it can be used by components. Parameters: - icon_name: Name of the icon file (e.g. "Plus.svg") + icon_name: Name of the icon file (e.g. "plus.svg") Returns: str: Full path to the icon file served as a static file """ - try: - icon_path = importlib.resources.files("gradio").joinpath( - str(Path("icons") / icon_name) - ) - set_static_paths(str(icon_path)) - return str(icon_path) - except FileNotFoundError as e: - raise ValueError(f"Icon file not found: {icon_name}") from e + icon_path = str( + importlib.resources.files("gradio").joinpath(str(Path("icons") / icon_name)) + ) + if Path(icon_path).exists(): + set_static_paths(icon_path) + return icon_path + raise ValueError(f"Icon file not found: {icon_name}") diff --git a/test/test_utils.py b/test/test_utils.py index 7f60554bc28c6..c5e4dd21ddd71 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -30,6 +30,7 @@ download_if_url, get_extension_from_file_path_or_url, get_function_params, + get_icon_path, get_type_hints, ipython_check, is_allowed_file, @@ -716,3 +717,8 @@ def __deepcopy__(self, memo): result = safe_deepcopy(original) assert result is not original assert type(result) is type(original) + + +def test_get_icon_path(): + assert get_icon_path("plus.svg").endswith("plus.svg") + assert get_icon_path("huggingface-logo.svg").endswith("huggingface-logo.svg") From b63a9bb8443b15e30784c67ad2d595dcabed8f20 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Thu, 2 Jan 2025 10:17:36 -0500 Subject: [PATCH 61/69] lint --- gradio/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradio/utils.py b/gradio/utils.py index 6ba2a922b73bc..c961b34a77050 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -1593,7 +1593,7 @@ def none_or_singleton_to_list(value: Any) -> list: def get_icon_path(icon_name: str) -> str: - """Get the path to an icon file in the gradio/icons/ directory + """Get the path to an icon file in the gradio/icons/ directory and return it as a static file path so that it can be used by components. Parameters: From 49be8b07a3f93675a324ea6ae8d42f169a4c8118 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Thu, 2 Jan 2025 10:18:11 -0500 Subject: [PATCH 62/69] changes --- gradio/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradio/utils.py b/gradio/utils.py index c961b34a77050..080a3d1181a4e 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -1593,7 +1593,7 @@ def none_or_singleton_to_list(value: Any) -> list: def get_icon_path(icon_name: str) -> str: - """Get the path to an icon file in the gradio/icons/ directory + """Get the path to an icon file in the "gradio/icons/" directory and return it as a static file path so that it can be used by components. Parameters: From fc00a7d192b061892843685c0541c19faecc9680 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Fri, 3 Jan 2025 09:10:50 -0500 Subject: [PATCH 63/69] example --- demo/chatinterface_streaming_echo/run.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/demo/chatinterface_streaming_echo/run.py b/demo/chatinterface_streaming_echo/run.py index 2c1c403dbe997..089c6711dc5eb 100644 --- a/demo/chatinterface_streaming_echo/run.py +++ b/demo/chatinterface_streaming_echo/run.py @@ -6,7 +6,13 @@ def slow_echo(message, history): time.sleep(0.05) yield "You typed: " + message[: i + 1] -demo = gr.ChatInterface(slow_echo, type="messages", flagging_mode="manual", flagging_options=["Like", "Spam", "Inappropriate", "Other"], save_history=True) +demo = gr.ChatInterface( + slow_echo, + type="messages", + flagging_mode="manual", + flagging_options=["Like", "Spam", "Inappropriate", "Other"], + save_history=True, +) if __name__ == "__main__": demo.launch() From 14f48997cd245868611801119217990460e9ffce Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Fri, 3 Jan 2025 09:21:49 -0500 Subject: [PATCH 64/69] changes --- demo/chatinterface_streaming_echo/run.ipynb | 2 +- guides/05_chatbots/01_creating-a-chatbot-fast.md | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/demo/chatinterface_streaming_echo/run.ipynb b/demo/chatinterface_streaming_echo/run.ipynb index 82d9947f04a55..9b82faac936e2 100644 --- a/demo/chatinterface_streaming_echo/run.ipynb +++ b/demo/chatinterface_streaming_echo/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_streaming_echo"]}, {"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 time\n", "import gradio as gr\n", "\n", "def slow_echo(message, history):\n", " for i in range(len(message)):\n", " time.sleep(0.05)\n", " yield \"You typed: \" + message[: i + 1]\n", "\n", "demo = gr.ChatInterface(slow_echo, type=\"messages\", save_history=True)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_streaming_echo"]}, {"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 time\n", "import gradio as gr\n", "\n", "def slow_echo(message, history):\n", " for i in range(len(message)):\n", " time.sleep(0.05)\n", " yield \"You typed: \" + message[: i + 1]\n", "\n", "demo = gr.ChatInterface(\n", " slow_echo,\n", " type=\"messages\",\n", " flagging_mode=\"manual\",\n", " flagging_options=[\"Like\", \"Spam\", \"Inappropriate\", \"Other\"], \n", " save_history=True,\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/guides/05_chatbots/01_creating-a-chatbot-fast.md b/guides/05_chatbots/01_creating-a-chatbot-fast.md index 36ab09ff1597e..27ef1b7692003 100644 --- a/guides/05_chatbots/01_creating-a-chatbot-fast.md +++ b/guides/05_chatbots/01_creating-a-chatbot-fast.md @@ -341,14 +341,22 @@ To use the endpoint, you should use either the [Gradio Python Client](/guides/ge * Slack bot [[tutorial]](../guides/creating-a-slack-bot-from-a-gradio-app) * Website widget [[tutorial]](../guides/creating-a-website-widget-from-a-gradio-chatbot) -## Collecting Feedback +## Chat History -To gather feedback on your generations, set `gr.ChatInterface(flagging_mode="manual")` and users can thumbs-up and down assistant responses. Each flagged response, along with the entire chat history, will get saved in a CSV file in the app folder (or wherever `flagging_dir` specifies). +You can enable persistent chat history for your ChatInterface, allowing users to maintain multiple conversations and easily switch between them. When enabled, conversations are stored locally and privately in the user's browser using local storage. So if you deploy a ChatInterface e.g. on [Hugging Face Spaces](https://hf.space), each user will have their own separate chat history that won't interfere with other users' conversations. This means multiple users can interact with the same ChatInterface simultaneously while maintaining their own private conversation histories. -You can also specify more feedback options via `flagging_options`, which will appear under a dedicated flag button. Here's an example that shows several flagging options. Because the case-sensitive string "Like" is one of the flagging options, the user will see a "thumbs up" icon next to each assistant message. The three other flagging options will appear under a dedicated "flag" icon. +To enable this feature, simply set `gr.ChatInterface(save_history=True)` (as shown in the example in the next section). Users will then see their previous conversations in a side panel and can continue any previous chat or start a new one. + +## Collecting User Feedback + +To gather feedback on your chat model, set `gr.ChatInterface(flagging_mode="manual")` and users will be able to thumbs-up or thumbs-down assistant responses. Each flagged response, along with the entire chat history, will get saved in a CSV file in the app working directory (this can be configured via the `flagging_dir` parameter). + +You can also change the feedback options via `flagging_options` parameter. The default options are "Like" and "Dislike", which appear as the thumbs-up and thumbs-down icons. Any other options appear under a dedicated flag icon. This example shows a ChatInterface that has both chat history (mentioned in the previous section) and user feedback enabled: $code_chatinterface_streaming_echo +Note that in this example, we set several flagging options: "Like", "Spam", "Inappropriate", "Other". Because the case-sensitive string "Like" is one of the flagging options, the user will see a thumbs-up icon next to each assistant message. The three other flagging options will appear in a dropdown under the flag icon. + ## What's Next? Now that you've learned about the `gr.ChatInterface` class and how it can be used to create chatbot UIs quickly, we recommend reading one of the following: From 25b590cfc2234bec6c0dc8e71eb5023aa5aff4d7 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Fri, 3 Jan 2025 09:39:24 -0500 Subject: [PATCH 65/69] fix buttons --- gradio/components/button.py | 4 ++-- gradio/components/clear_button.py | 2 +- gradio/components/download_button.py | 6 +++--- gradio/components/duplicate_button.py | 6 +++--- gradio/components/login_button.py | 2 +- gradio/components/logout_button.py | 2 +- gradio/components/upload_button.py | 4 ++-- js/downloadbutton/shared/DownloadButton.svelte | 2 +- js/uploadbutton/shared/UploadButton.svelte | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/gradio/components/button.py b/gradio/components/button.py index 44c2da3df89d8..6bce6e75fe31c 100644 --- a/gradio/components/button.py +++ b/gradio/components/button.py @@ -30,7 +30,7 @@ def __init__( every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop", "huggingface"] = "secondary", - size: Literal["sm", "md", "lg"] | None = None, + size: Literal["sm", "md", "lg"] = "lg", icon: str | Path | None = None, link: str | None = None, visible: bool = True, @@ -48,7 +48,7 @@ def __init__( every: continuously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer. inputs: components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change. variant: sets the background and text color of the button. Use 'primary' for main call-to-action buttons, 'secondary' for a more subdued style, 'stop' for a stop button, 'huggingface' for a black background with white text, consistent with Hugging Face's button styles. - size: size of the button. Can be "sm" or "lg". + size: size of the button. Can be "sm", "md", or "lg". icon: URL or path to the icon file to display within the button. If None, no icon will be displayed. link: URL to open when the button is clicked. If None, no link will be used. visible: if False, component will be hidden. diff --git a/gradio/components/clear_button.py b/gradio/components/clear_button.py index bce4383d86026..6056990e87ba0 100644 --- a/gradio/components/clear_button.py +++ b/gradio/components/clear_button.py @@ -37,7 +37,7 @@ def __init__( every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", - size: Literal["sm", "md", "lg"] | None = None, + size: Literal["sm", "md", "lg"] = "lg", icon: str | Path | None = None, link: str | None = None, visible: bool = True, diff --git a/gradio/components/download_button.py b/gradio/components/download_button.py index e12ec00849da6..ac7bdbf5491bc 100644 --- a/gradio/components/download_button.py +++ b/gradio/components/download_button.py @@ -1,4 +1,4 @@ -"""gr.UploadButton() component.""" +"""gr.DownloadButton() component.""" from __future__ import annotations @@ -37,7 +37,7 @@ def __init__( inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", visible: bool = True, - size: Literal["sm", "md", "lg"] | None = None, + size: Literal["sm", "md", "lg"] = "lg", icon: str | None = None, scale: int | None = None, min_width: int | None = None, @@ -55,7 +55,7 @@ def __init__( inputs: Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change. variant: 'primary' for main call-to-action, 'secondary' for a more subdued style, 'stop' for a stop button. visible: If False, component will be hidden. - size: Size of the button. Can be "sm" or "lg". + size: size of the button. Can be "sm", "md", or "lg". icon: URL or path to the icon file to display within the button. If None, no icon will be displayed. scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True. 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. diff --git a/gradio/components/duplicate_button.py b/gradio/components/duplicate_button.py index db9e8b0d9591b..f7a9a6a1f13f7 100644 --- a/gradio/components/duplicate_button.py +++ b/gradio/components/duplicate_button.py @@ -1,4 +1,4 @@ -"""Predefined buttons with bound events that can be included in a gr.Blocks for convenience.""" +"""gr.DuplicateButton() component""" from __future__ import annotations @@ -31,7 +31,7 @@ def __init__( every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop", "huggingface"] = "huggingface", - size: Literal["sm", "md", "lg"] | None = "sm", + size: Literal["sm", "md", "lg"] = "sm", icon: str | Path | None = None, link: str | None = None, visible: bool = True, @@ -51,7 +51,7 @@ def __init__( every: continuously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer. inputs: components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change. variant: sets the background and text color of the button. Use 'primary' for main call-to-action buttons, 'secondary' for a more subdued style, 'stop' for a stop button, 'huggingface' for a black background with white text, consistent with Hugging Face's button styles. - size: size of the button. Can be "sm" or "lg". + size: size of the button. Can be "sm", "md", or "lg". icon: URL or path to the icon file to display within the button. If None, no icon will be displayed. link: URL to open when the button is clicked. If None, no link will be used. visible: if False, component will be hidden. diff --git a/gradio/components/login_button.py b/gradio/components/login_button.py index f8eca3c58ecec..8190011a44988 100644 --- a/gradio/components/login_button.py +++ b/gradio/components/login_button.py @@ -36,7 +36,7 @@ def __init__( every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop", "huggingface"] = "huggingface", - size: Literal["sm", "md", "lg"] | None = None, + size: Literal["sm", "md", "lg"] = "lg", icon: str | Path | None = utils.get_icon_path("huggingface-logo.svg"), link: str | None = None, visible: bool = True, diff --git a/gradio/components/logout_button.py b/gradio/components/logout_button.py index 2a1e44412ee6d..3393f19ba33e0 100644 --- a/gradio/components/logout_button.py +++ b/gradio/components/logout_button.py @@ -32,7 +32,7 @@ def __init__( every: Timer | float | None = None, inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", - size: Literal["sm", "lg"] | None = None, + size: Literal["sm", "lg"] = "lg", icon: str | None = "https://huggingface.co/front/assets/huggingface_logo-noborder.svg", # Link to logout page (which will delete the session cookie and redirect to landing page). diff --git a/gradio/components/upload_button.py b/gradio/components/upload_button.py index 5a1fad11c2289..fcfae1c7bdde7 100644 --- a/gradio/components/upload_button.py +++ b/gradio/components/upload_button.py @@ -42,7 +42,7 @@ def __init__( inputs: Component | Sequence[Component] | set[Component] | None = None, variant: Literal["primary", "secondary", "stop"] = "secondary", visible: bool = True, - size: Literal["sm", "lg"] | None = None, + size: Literal["sm", "md", "lg"] = "lg", icon: str | None = None, scale: int | None = None, min_width: int | None = None, @@ -63,7 +63,7 @@ def __init__( inputs: Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change. variant: 'primary' for main call-to-action, 'secondary' for a more subdued style, 'stop' for a stop button. visible: If False, component will be hidden. - size: Size of the button. Can be "sm" or "lg". + size: size of the button. Can be "sm", "md", or "lg". icon: URL or path to the icon file to display within the button. If None, no icon will be displayed. scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True. 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. diff --git a/js/downloadbutton/shared/DownloadButton.svelte b/js/downloadbutton/shared/DownloadButton.svelte index 77d2c96d6b432..ccb5a8e168be1 100644 --- a/js/downloadbutton/shared/DownloadButton.svelte +++ b/js/downloadbutton/shared/DownloadButton.svelte @@ -7,7 +7,7 @@ export let elem_classes: string[] = []; export let visible = true; export let variant: "primary" | "secondary" | "stop" = "secondary"; - export let size: "sm" | "lg" = "lg"; + export let size: "sm" | "md" | "lg" = "lg"; export let value: null | FileData; export let icon: null | FileData; export let disabled = false; diff --git a/js/uploadbutton/shared/UploadButton.svelte b/js/uploadbutton/shared/UploadButton.svelte index bd7cd476bd858..9c39e0433bb02 100644 --- a/js/uploadbutton/shared/UploadButton.svelte +++ b/js/uploadbutton/shared/UploadButton.svelte @@ -11,7 +11,7 @@ export let file_count: string; export let file_types: string[] = []; export let root: string; - export let size: "sm" | "lg" = "lg"; + export let size: "sm" | "md" | "lg" = "lg"; export let icon: FileData | null = null; export let scale: number | null = null; export let min_width: number | undefined = undefined; From 01363e27f36331f15452ae3dc47dd21527f8384b Mon Sep 17 00:00:00 2001 From: gradio-pr-bot Date: Fri, 3 Jan 2025 14:40:25 +0000 Subject: [PATCH 66/69] add changeset --- .changeset/tiny-areas-train.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/tiny-areas-train.md b/.changeset/tiny-areas-train.md index 69cc289d6519d..7f22a838d4d7b 100644 --- a/.changeset/tiny-areas-train.md +++ b/.changeset/tiny-areas-train.md @@ -1,7 +1,9 @@ --- "@gradio/button": minor "@gradio/dataset": minor +"@gradio/downloadbutton": minor "@gradio/textbox": minor +"@gradio/uploadbutton": minor "gradio": minor --- From 1d9fb134c5f9a559dbed2c8ccbee394cd5180491 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Sat, 4 Jan 2025 09:17:12 -0800 Subject: [PATCH 67/69] format --- js/chatbot/shared/FlagActive.svelte | 7 +++++++ js/chatbot/shared/LikeDislike.svelte | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 js/chatbot/shared/FlagActive.svelte diff --git a/js/chatbot/shared/FlagActive.svelte b/js/chatbot/shared/FlagActive.svelte new file mode 100644 index 0000000000000..804dce5979a7d --- /dev/null +++ b/js/chatbot/shared/FlagActive.svelte @@ -0,0 +1,7 @@ + diff --git a/js/chatbot/shared/LikeDislike.svelte b/js/chatbot/shared/LikeDislike.svelte index 1589f7afe7c5c..b4ac9c70f9563 100644 --- a/js/chatbot/shared/LikeDislike.svelte +++ b/js/chatbot/shared/LikeDislike.svelte @@ -5,6 +5,7 @@ import ThumbUpActive from "./ThumbUpActive.svelte"; import ThumbUpDefault from "./ThumbUpDefault.svelte"; import Flag from "./Flag.svelte"; + import FlagActive from "./FlagActive.svelte"; export let handle_action: (selected: string | null) => void; export let feedback_options: string[]; @@ -46,7 +47,13 @@ {#if extra_feedback.length > 0}
- +
{#each extra_feedback as option}