Repository: gradio-app/gradio
Pull request: Adds a `gr.DownloadButton` component
After https://github.com/gradio-app/gradio/pull/7528, I wanted to go through the process of creating a new component with the new approach of working with the files, i.e. setting the URLs in the backend using `serve_static_file()` to mimic the work that a custom component developer would have to do.

I decided to add the long-requested `gr.DownloadButton` component (fixes: https://github.com/gradio-app/gradio/issues/5426)

Problem statement:
a `gr.DownloadButton` that lets you download a file with a button click
- [x] I have searched to see if a similar issue already exists.


**Is your feature request related to a problem? Please describe.**  
Is possible to directly download file with a button click, rather using the `gr.File` component. Directly download will be more intuitive, and using the gr. File requires two operations.

**Describe the solution you'd like**  
I don't want to add the `gr.File` component on my app's UI. I'd like to download a file (eg: image) from a http url or local path with a button click.

Hints from issue discussion:
Hi @hyer we don't have a `gr.DownloadButton` component -- we could create a component like this in the future. The other possibility would be to use the `link` attribute of the `gr.Button()` to point to the filepath. Something like this:

```py
import gradio as gr

with gr.Blocks() as demo:
    gr.Button(link="/file=cheetah.jpg")
    
demo.launch()
```
@abidlabs could you do this code: 

On interface too? import gradio as gr

with gr.Blocks() as demo:
    gr.Button(link="/file=cheetah.jpg")
    
demo.launch() thanks for your help on this.
Not sure I understand the question @cowanAI -- can you explain what you mean?
Let’s say I make a gradio interface not blocks, and then add a microphone
component to it, can we use the link button to download the audio in that
way?

On Wed, Sep 6, 2023 at 6:48 PM Abubakar Abid ***@***.***>
wrote:

> Not sure I understand the question @cowanAI <https://github.com/cowanAI>
> -- can you explain what you mean?
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/gradio-app/gradio/issues/5426#issuecomment-1708755591>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/A6RIPFGDZJ7EG3JPYKI3LMLXZCSNBANCNFSM6AAAAAA4MTLNUU>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>

Ah yeah it should be possible I think. Let's say your filepath is `filepath` You can return a `gr.Button.update(link="file/=" + filepath)` from your function
> 

Thanks @abidlabs . However, this is viewable only in the browser, not the usual way of saving to local disk. Look forward to seeing the `gr.DownloadButton` component soon
@abidlabs when I try to do this with the link argument I get the error: `{"detail":"File not allowed: material/test.txt."}`

Are you still working on the gr.DownloadButton?

```
with gr.Blocks() as demo:
    filepath = "material/test.txt"
    btn = gr.Button("Here", link="file/material/test.txt")
```
Hi @pcschreiber1 -- which version of gradio are you using?
Thank you for the quick reply @abidlabs , I'm using gradio 4.7.1
In Gradio 4.x, files your local system are not exposed by default, so you'll need to explicitly allow the specific file or directory. E.g. 

```py
import gradio as gr

with gr.Blocks() as demo:
...
    
demo.launch(allowed_paths=["file/material/test.txt"])
```

or you could do a containing folder as well, like:

```py
import gradio as gr

with gr.Blocks() as demo:
...
    
demo.launch(allowed_paths=["file/"])
```

Commit message: notebooks

Patch to review:
diff --git a/.changeset/weak-ducks-clean.md b/.changeset/weak-ducks-clean.md
new file mode 100644
index 0000000000..7d88fba8a4
--- /dev/null
+++ b/.changeset/weak-ducks-clean.md
@@ -0,0 +1,8 @@
+---
+"@gradio/app": minor
+"@gradio/downloadbutton": minor
+"@gradio/uploadbutton": minor
+"gradio": minor
+---
+
+feat:Adds a `gr.DownloadButton` component
diff --git a/demo/downloadbutton_component/run.ipynb b/demo/downloadbutton_component/run.ipynb
new file mode 100644
index 0000000000..0aa25c093f
--- /dev/null
+++ b/demo/downloadbutton_component/run.ipynb
@@ -0,0 +1 @@
+{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: downloadbutton_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", "    gr.DownloadButton(\"\ud83d\udcc2 Click to download file\", value=\"http://www.marketingtool.online/en/face-generator/img/faces/avatar-1151ce9f4b2043de0d2e3b7826127998.jpg\")\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/downloadbutton_component/run.py b/demo/downloadbutton_component/run.py
new file mode 100644
index 0000000000..583eb7b7b5
--- /dev/null
+++ b/demo/downloadbutton_component/run.py
@@ -0,0 +1,7 @@
+import gradio as gr
+
+with gr.Blocks() as demo:
+    gr.DownloadButton("📂 Click to download file", value="http://www.marketingtool.online/en/face-generator/img/faces/avatar-1151ce9f4b2043de0d2e3b7826127998.jpg")
+
+if __name__ == "__main__":
+    demo.launch()
\ No newline at end of file
diff --git a/demo/fake_gan/files/cheetah1.jpg b/demo/fake_gan/files/cheetah1.jpg
deleted file mode 100644
index c510ff30e0..0000000000
Binary files a/demo/fake_gan/files/cheetah1.jpg and /dev/null differ
diff --git a/demo/fake_gan/run.ipynb b/demo/fake_gan/run.ipynb
index 77783d1e3e..f03cfa9dbc 100644
--- a/demo/fake_gan/run.ipynb
+++ b/demo/fake_gan/run.ipynb
@@ -1 +1 @@
-{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: fake_gan\n", "### This is a fake GAN that shows how to create a text-to-image interface for image generation. Check out the Stable Diffusion demo for more: https://hf.co/spaces/stabilityai/stable-diffusion/\n", "        "]}, {"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": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/cheetah1.jpg https://github.com/gradio-app/gradio/raw/main/demo/fake_gan/files/cheetah1.jpg"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["# This demo needs to be run from the repo folder.\n", "# python demo/fake_gan/run.py\n", "import random\n", "\n", "import gradio as gr\n", "\n", "\n", "def fake_gan():\n", "    images = [\n", "        (random.choice(\n", "            [\n", "                \"http://www.marketingtool.online/en/face-generator/img/faces/avatar-1151ce9f4b2043de0d2e3b7826127998.jpg\",\n", "                \"http://www.marketingtool.online/en/face-generator/img/faces/avatar-116b5e92936b766b7fdfc242649337f7.jpg\",\n", "                \"http://www.marketingtool.online/en/face-generator/img/faces/avatar-1163530ca19b5cebe1b002b8ec67b6fc.jpg\",\n", "                \"http://www.marketingtool.online/en/face-generator/img/faces/avatar-1116395d6e6a6581eef8b8038f4c8e55.jpg\",\n", "                \"http://www.marketingtool.online/en/face-generator/img/faces/avatar-11319be65db395d0e8e6855d18ddcef0.jpg\",\n", "            ]\n", "        ), f\"label {i}\")\n", "        for i in range(3)\n", "    ]\n", "    return images\n", "\n", "\n", "with gr.Blocks() as demo:\n", "    gallery = gr.Gallery(\n", "        label=\"Generated images\", show_label=False, elem_id=\"gallery\"\n", "    , columns=[3], rows=[1], object_fit=\"contain\", height=\"auto\")\n", "    btn = gr.Button(\"Generate images\", scale=0)\n", "\n", "    btn.click(fake_gan, None, gallery)\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: fake_gan\n", "### This is a fake GAN that shows how to create a text-to-image interface for image generation. Check out the Stable Diffusion demo for more: https://hf.co/spaces/stabilityai/stable-diffusion/\n", "        "]}, {"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": ["# This demo needs to be run from the repo folder.\n", "# python demo/fake_gan/run.py\n", "import random\n", "\n", "import gradio as gr\n", "\n", "\n", "def fake_gan():\n", "    images = [\n", "        (random.choice(\n", "            [\n", "                \"http://www.marketingtool.online/en/face-generator/img/faces/avatar-1151ce9f4b2043de0d2e3b7826127998.jpg\",\n", "                \"http://www.marketingtool.online/en/face-generator/img/faces/avatar-116b5e92936b766b7fdfc242649337f7.jpg\",\n", "                \"http://www.marketingtool.online/en/face-generator/img/faces/avatar-1163530ca19b5cebe1b002b8ec67b6fc.jpg\",\n", "                \"http://www.marketingtool.online/en/face-generator/img/faces/avatar-1116395d6e6a6581eef8b8038f4c8e55.jpg\",\n", "                \"http://www.marketingtool.online/en/face-generator/img/faces/avatar-11319be65db395d0e8e6855d18ddcef0.jpg\",\n", "            ]\n", "        ), f\"label {i}\")\n", "        for i in range(3)\n", "    ]\n", "    return images\n", "\n", "\n", "with gr.Blocks() as demo:\n", "    gallery = gr.Gallery(\n", "        label=\"Generated images\", show_label=False, elem_id=\"gallery\"\n", "    , columns=[3], rows=[1], object_fit=\"contain\", height=\"auto\")\n", "    btn = gr.Button(\"Generate images\", scale=0)\n", "\n", "    btn.click(fake_gan, None, gallery)\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/upload_and_download/run.ipynb b/demo/upload_and_download/run.ipynb
new file mode 100644
index 0000000000..8a7962c821
--- /dev/null
+++ b/demo/upload_and_download/run.ipynb
@@ -0,0 +1 @@
+{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: upload_and_download"]}, {"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": ["from pathlib import Path\n", "import gradio as gr\n", "\n", "def upload_file(filepath):\n", "    name = Path(filepath).name\n", "    return [gr.UploadButton(visible=False), gr.DownloadButton(label=f\"Download {name}\", value=filepath, visible=True)]\n", "\n", "def download_file():\n", "    return [gr.UploadButton(visible=True), gr.DownloadButton(visible=False)]\n", "\n", "with gr.Blocks() as demo:\n", "    gr.Markdown(\"First upload a file and and then you'll be able download it (but only once!)\")\n", "    with gr.Row():\n", "        u = gr.UploadButton(\"Upload a file\", file_count=\"single\")\n", "        d = gr.DownloadButton(\"Download a file\", visible=False)\n", "\n", "    u.upload(upload_file, u, [u, d])\n", "    d.click(download_file, None, [u, d])\n", "\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/upload_and_download/run.py b/demo/upload_and_download/run.py
new file mode 100644
index 0000000000..00678008fa
--- /dev/null
+++ b/demo/upload_and_download/run.py
@@ -0,0 +1,22 @@
+from pathlib import Path
+import gradio as gr
+
+def upload_file(filepath):
+    name = Path(filepath).name
+    return [gr.UploadButton(visible=False), gr.DownloadButton(label=f"Download {name}", value=filepath, visible=True)]
+
+def download_file():
+    return [gr.UploadButton(visible=True), gr.DownloadButton(visible=False)]
+
+with gr.Blocks() as demo:
+    gr.Markdown("First upload a file and and then you'll be able download it (but only once!)")
+    with gr.Row():
+        u = gr.UploadButton("Upload a file", file_count="single")
+        d = gr.DownloadButton("Download a file", visible=False)
+
+    u.upload(upload_file, u, [u, d])
+    d.click(download_file, None, [u, d])
+
+
+if __name__ == "__main__":
+    demo.launch()
\ No newline at end of file
diff --git a/gradio/__init__.py b/gradio/__init__.py
index 87bb32c21d..6f2cad95e8 100644
--- a/gradio/__init__.py
+++ b/gradio/__init__.py
@@ -26,6 +26,7 @@
     DataFrame,
     Dataframe,
     Dataset,
+    DownloadButton,
     Dropdown,
     DuplicateButton,
     File,
diff --git a/gradio/components/__init__.py b/gradio/components/__init__.py
index 14cf404a7b..c3c23da253 100644
--- a/gradio/components/__init__.py
+++ b/gradio/components/__init__.py
@@ -19,6 +19,7 @@
 from gradio.components.color_picker import ColorPicker
 from gradio.components.dataframe import Dataframe
 from gradio.components.dataset import Dataset
+from gradio.components.download_button import DownloadButton
 from gradio.components.dropdown import Dropdown
 from gradio.components.duplicate_button import DuplicateButton
 from gradio.components.fallback import Fallback
@@ -73,6 +74,7 @@
     "Dataframe",
     "DataFrame",
     "Dataset",
+    "DownloadButton",
     "DuplicateButton",
     "Fallback",
     "Form",
diff --git a/gradio/components/download_button.py b/gradio/components/download_button.py
new file mode 100644
index 0000000000..8e8161b516
--- /dev/null
+++ b/gradio/components/download_button.py
@@ -0,0 +1,105 @@
+"""gr.UploadButton() component."""
+
+from __future__ import annotations
+
+import tempfile
+from typing import Callable, Literal
+
+from gradio_client.documentation import document
+
+from gradio.components.base import Component
+from gradio.data_classes import FileData
+from gradio.events import Events
+
+
+@document()
+class DownloadButton(Component):
+    """
+    Creates a button, that when clicked, allows a user to download a single file of arbitrary type.
+
+    Demos: upload_and_download
+    """
+
+    EVENTS = [Events.click]
+
+    def __init__(
+        self,
+        label: str = "Download",
+        value: str | list[str] | Callable | None = None,
+        *,
+        every: float | None = None,
+        variant: Literal["primary", "secondary", "stop"] = "secondary",
+        visible: bool = True,
+        size: Literal["sm", "lg"] | None = None,
+        icon: str | None = None,
+        scale: int | None = None,
+        min_width: int | None = None,
+        interactive: bool = True,
+        elem_id: str | None = None,
+        elem_classes: list[str] | str | None = None,
+        render: bool = True,
+    ):
+        """
+        Parameters:
+            label: Text to display on the button. Defaults to "Upload a File".
+            value: File or list of files to upload by default.
+            every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
+            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".
+            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.
+            interactive: If False, the UploadButton will be in a disabled state.
+            elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+            elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
+            render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+        """
+        self.data_model = FileData
+        self.size = size
+        self.label = label
+        self.variant = variant
+        super().__init__(
+            label=label,
+            every=every,
+            visible=visible,
+            elem_id=elem_id,
+            elem_classes=elem_classes,
+            render=render,
+            value=value,
+            scale=scale,
+            min_width=min_width,
+            interactive=interactive,
+        )
+        self.icon = self.serve_static_file(icon)
+
+    def preprocess(self, payload: FileData | None) -> str | None:
+        """
+        Parameters:
+            payload: File information as a FileData object,
+        Returns:
+            (Rarely used) passes the file as a `str` into the function.
+        """
+        if payload is None:
+            return None
+        file_name = payload.path
+        file = tempfile.NamedTemporaryFile(delete=False, dir=self.GRADIO_CACHE)
+        file.name = file_name
+        return file_name
+
+    def postprocess(self, value: str | None) -> FileData | None:
+        """
+        Parameters:
+            value: Expects a `str` filepath
+        Returns:
+            File information as a FileData object
+        """
+        if value is None:
+            return None
+        return FileData(path=value)
+
+    def example_inputs(self) -> str:
+        return "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf"
+
+    @property
+    def skip_api(self):
+        return False
diff --git a/gradio/components/upload_button.py b/gradio/components/upload_button.py
index 18c03fe3b9..e8c3ebd8b3 100644
--- a/gradio/components/upload_button.py
+++ b/gradio/components/upload_button.py
@@ -20,7 +20,7 @@ class UploadButton(Component):
     """
     Used to create an upload button, when clicked allows a user to upload files that satisfy the specified file type or generic files (if file_type not set).
 
-    Demos: upload_button
+    Demos: upload_and_download, upload_button
     """
 
     EVENTS = [Events.click, Events.upload]
diff --git a/js/app/package.json b/js/app/package.json
index ee93d12e24..935fc183b0 100644
--- a/js/app/package.json
+++ b/js/app/package.json
@@ -38,6 +38,7 @@
 		"@gradio/column": "workspace:^",
 		"@gradio/dataframe": "workspace:^",
 		"@gradio/dataset": "workspace:^",
+		"@gradio/downloadbutton": "workspace:^",
 		"@gradio/dropdown": "workspace:^",
 		"@gradio/fallback": "workspace:^",
 		"@gradio/file": "workspace:^",
diff --git a/js/downloadbutton/Index.svelte b/js/downloadbutton/Index.svelte
new file mode 100644
index 0000000000..8b75072703
--- /dev/null
+++ b/js/downloadbutton/Index.svelte
@@ -0,0 +1,41 @@
+<script context="module" lang="ts">
+	export { default as BaseButton } from "./shared/DownloadButton.svelte";
+</script>
+
+<script lang="ts">
+	import type { Gradio } from "@gradio/utils";
+	import { type FileData } from "@gradio/client";
+
+	import DownloadButton from "./shared/DownloadButton.svelte";
+
+	export let elem_id = "";
+	export let elem_classes: string[] = [];
+	export let visible = true;
+	export let value: null | FileData;
+	export let variant: "primary" | "secondary" | "stop" = "secondary";
+	export let interactive: boolean;
+	export let size: "sm" | "lg" = "lg";
+	export let scale: number | null = null;
+	export let icon: null | FileData = null;
+	export let min_width: number | undefined = undefined;
+	export let label: string | null = null;
+	export let gradio: Gradio<{
+		click: never;
+	}>;
+</script>
+
+<DownloadButton
+	{value}
+	{variant}
+	{elem_id}
+	{elem_classes}
+	{size}
+	{scale}
+	{icon}
+	{min_width}
+	{visible}
+	disabled={!interactive}
+	on:click={() => gradio.dispatch("click")}
+>
+	{label ? gradio.i18n(label) : ""}
+</DownloadButton>
diff --git a/js/downloadbutton/package.json b/js/downloadbutton/package.json
new file mode 100644
index 0000000000..288239ff22
--- /dev/null
+++ b/js/downloadbutton/package.json
@@ -0,0 +1,20 @@
+{
+	"name": "@gradio/downloadbutton",
+	"version": "0.0.1",
+	"description": "Gradio UI packages",
+	"type": "module",
+	"author": "",
+	"license": "ISC",
+	"private": false,
+	"dependencies": {
+		"@gradio/button": "workspace:^",
+		"@gradio/client": "workspace:^",
+		"@gradio/utils": "workspace:^"
+	},
+	"main": "./Index.svelte",
+	"main_changeset": true,
+	"exports": {
+		".": "./Index.svelte",
+		"./package.json": "./package.json"
+	}
+}
diff --git a/js/downloadbutton/shared/DownloadButton.svelte b/js/downloadbutton/shared/DownloadButton.svelte
new file mode 100644
index 0000000000..86c62fbe2d
--- /dev/null
+++ b/js/downloadbutton/shared/DownloadButton.svelte
@@ -0,0 +1,60 @@
+<script lang="ts">
+	import { type FileData } from "@gradio/client";
+	import { BaseButton } from "@gradio/button";
+
+	export let elem_id = "";
+	export let elem_classes: string[] = [];
+	export let visible = true;
+	export let variant: "primary" | "secondary" | "stop" = "secondary";
+	export let size: "sm" | "lg" = "lg";
+	export let value: null | FileData;
+	export let icon: null | FileData;
+	export let disabled = false;
+	export let scale: number | null = null;
+	export let min_width: number | undefined = undefined;
+
+	function download_file(): void {
+		if (!value?.url) {
+			return;
+		}
+		let file_name;
+		if (!value.orig_name && value.url) {
+			const parts = value.url.split("/");
+			file_name = parts[parts.length - 1];
+			file_name = file_name.split("?")[0].split("#")[0];
+		} else {
+			file_name = value.orig_name;
+		}
+		const a = document.createElement("a");
+		a.href = value.url;
+		a.download = file_name || "file";
+		document.body.appendChild(a);
+		a.click();
+		document.body.removeChild(a);
+	}
+</script>
+
+<BaseButton
+	{size}
+	{variant}
+	{elem_id}
+	{elem_classes}
+	{visible}
+	on:click={download_file}
+	{scale}
+	{min_width}
+	{disabled}
+>
+	{#if icon}
+		<img class="button-icon" src={icon.url} alt={`${value} icon`} />
+	{/if}
+	<slot />
+</BaseButton>
+
+<style>
+	.button-icon {
+		width: var(--text-xl);
+		height: var(--text-xl);
+		margin-right: var(--spacing-xl);
+	}
+</style>
diff --git a/js/uploadbutton/shared/UploadButton.svelte b/js/uploadbutton/shared/UploadButton.svelte
index de664d03a4..08e95a7ff5 100644
--- a/js/uploadbutton/shared/UploadButton.svelte
+++ b/js/uploadbutton/shared/UploadButton.svelte
@@ -43,12 +43,12 @@
 		accept_file_types = file_types.join(", ");
 	}
 
-	function openFileUpload(): void {
+	function open_file_upload(): void {
 		dispatch("click");
 		hidden_upload.click();
 	}
 
-	async function loadFiles(files: FileList): Promise<void> {
+	async function load_files(files: FileList): Promise<void> {
 		let _files: File[] = Array.from(files);
 		if (!files.length) {
 			return;
@@ -67,14 +67,14 @@
 		dispatch("upload", value);
 	}
 
-	async function loadFilesFromUpload(e: Event): Promise<void> {
+	async function load_files_from_upload(e: Event): Promise<void> {
 		const target = e.target as HTMLInputElement;
 
 		if (!target.files) return;
-		await loadFiles(target.files);
+		await load_files(target.files);
 	}
 
-	function clearInputValue(e: Event): void {
+	function clear_input_value(e: Event): void {
 		const target = e.target as HTMLInputElement;
 		if (target.value) target.value = "";
 	}
@@ -85,8 +85,8 @@
 	accept={accept_file_types}
 	type="file"
 	bind:this={hidden_upload}
-	on:change={loadFilesFromUpload}
-	on:click={clearInputValue}
+	on:change={load_files_from_upload}
+	on:click={clear_input_value}
 	multiple={file_count === "multiple" || undefined}
 	webkitdirectory={file_count === "directory" || undefined}
 	mozdirectory={file_count === "directory" || undefined}
@@ -99,7 +99,7 @@
 	{elem_id}
 	{elem_classes}
 	{visible}
-	on:click={openFileUpload}
+	on:click={open_file_upload}
 	{scale}
 	{min_width}
 	{disabled}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3f75d04aaa..6fb99ca64d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -411,6 +411,9 @@ importers:
       '@gradio/dataset':
         specifier: workspace:^
         version: link:../dataset
+      '@gradio/downloadbutton':
+        specifier: workspace:^
+        version: link:../downloadbutton
       '@gradio/dropdown':
         specifier: workspace:^
         version: link:../dropdown
@@ -834,6 +837,18 @@ importers:
         specifier: workspace:^
         version: link:../utils
 
+  js/downloadbutton:
+    dependencies:
+      '@gradio/button':
+        specifier: workspace:^
+        version: link:../button
+      '@gradio/client':
+        specifier: workspace:^
+        version: link:../../client/js
+      '@gradio/utils':
+        specifier: workspace:^
+        version: link:../utils
+
   js/dropdown:
     dependencies:
       '@gradio/atoms':


Code review comments:
path: demo/fake_gan/files/cheetah1.jpg
text: @author:
Unused file