Repository: gradio-app/gradio
Pull request: Add `max_file_size` parameter to `launch()` that limits the size of files that can be uploaded in the Gradio app
## Description

Closes: #7825
Closes: #6700 

`max_file_size` is a block level component set in launch.

Developers can set this parameter as an int (like `1024` to specify 1 kb or as a string like "1kb"). Validation happens before the file is uploaded both for the app and the python client. The `upload` route will return a 413 error code if any of the uploaded files are larger than max_file_size

## 🎯 PRs Should Target Issues

Before your create a PR, please check to see if there is [an existing issue](https://github.com/gradio-app/gradio/issues) for this change. If not, please create an issue before you create this PR, unless the fix is very small. 

Not adhering to this guideline will result in the PR being closed. 

## Tests

1. PRs will only be merged if tests pass on CI. To run the tests locally, please set up [your Gradio environment locally](https://github.com/gradio-app/gradio/blob/main/CONTRIBUTING.md) and run the tests: `bash scripts/run_all_tests.sh`

2. You may need to run the linters: `bash scripts/format_backend.sh` and `bash scripts/format_frontend.sh`
  


Problem statement:
Allow users to close error tag and get back to the component
- [x] I have searched to see if a similar issue already exists.

**Is your feature request related to a problem? Please describe.**  
Sometimes the same component is an input and an output. If something errors out, the component gets in a state of error and you can't fix it because it was also the input. 

Another scenario where this happens is in case there's an error in a `.upload` from a `gr.Image` component for example: maybe the cropping errored out, or you uploaded an invalid image. But now the component is broken and you can't "close" the error to try again with a different image. 

**Describe the solution you'd like**  
Allow users to "close" the `Error` state of components when they error out, revealing what was underneath



Limit file upload size
- [ ] I have searched to see if a similar issue already exists.


**Is your feature request related to a problem? Please describe.**  
Gradio app developers should be able to control the max individual file size that can be uploaded. 

**Describe the solution you'd like**  
Add a `max_file_size: int` parameter to media-based components to limit the size of files that can be uploaded. Setting it at the component level will be more flexible for different input file types instead of at the global level.

This should work both in the UI and the clients. Ideally an error should be raised prior to uploading any files so the logic should be replicated across the clients. 


**Additional context**  
Add any other context or screenshots about the feature request here.

Hints from issue discussion:
Minimal reproduction
```py
import gradio as gr

def crop_image(image):
  #Simulate error with cropping or the uploaded image
  raise gr.Error("Test")

with gr.Blocks() as demo:
    image = gr.Image(label="Name")
    image.upload(fn=crop_image, inputs=image, outputs=image)
  
if __name__ == "__main__":
    demo.launch()
```

This will render the upload component useless

https://github.com/gradio-app/gradio/assets/788417/84350a73-2008-4c19-b186-16e26b5014da

(Playground video but the same happens in 4.x) 

I think a `x` button to close the error and reveal the component behind would be enough
(btw I was on the fence between feature request and bug report because I think this is breaking for some workflows that require input/output in the same component and there's no way to restart but by reloading the webpage) 
![max_file_size_upload](https://github.com/gradio-app/gradio/assets/41651716/7547330c-a082-4901-a291-3f150a197e45)

![error_modal_calculator](https://github.com/gradio-app/gradio/assets/41651716/16cb071c-accd-45a6-9c18-0dea27d4bd98)

Commit message: Fix i18n in storybook

Patch to review:
diff --git a/.changeset/tiny-cars-spend.md b/.changeset/tiny-cars-spend.md
new file mode 100644
index 0000000000..5ae1213e0d
--- /dev/null
+++ b/.changeset/tiny-cars-spend.md
@@ -0,0 +1,68 @@
+---
+"@gradio/app": minor
+"@gradio/audio": minor
+"@gradio/chatbot": minor
+"@gradio/checkbox": minor
+"@gradio/checkboxgroup": minor
+"@gradio/client": minor
+"@gradio/code": minor
+"@gradio/colorpicker": minor
+"@gradio/dataframe": minor
+"@gradio/dropdown": minor
+"@gradio/fallback": minor
+"@gradio/file": minor
+"@gradio/fileexplorer": minor
+"@gradio/gallery": minor
+"@gradio/highlightedtext": minor
+"@gradio/html": minor
+"@gradio/image": minor
+"@gradio/imageeditor": minor
+"@gradio/json": minor
+"@gradio/label": minor
+"@gradio/markdown": minor
+"@gradio/model3d": minor
+"@gradio/multimodaltextbox": minor
+"@gradio/number": minor
+"@gradio/plot": minor
+"@gradio/radio": minor
+"@gradio/simpledropdown": minor
+"@gradio/simpleimage": minor
+"@gradio/simpletextbox": minor
+"@gradio/slider": minor
+"@gradio/statustracker": minor
+"@gradio/storybook": minor
+"@gradio/textbox": minor
+"@gradio/tootils": minor
+"@gradio/upload": minor
+"@gradio/uploadbutton": minor
+"@gradio/utils": minor
+"@gradio/video": minor
+"gradio": minor
+"gradio_client": minor
+---
+
+
+#### Setting File Upload Limits
+
+We have added a `max_file_size` size parameter to `launch()` that limits to size of files uploaded to the server. This limit applies to each individual file. This parameter can be specified as a string or an integer (corresponding to the size in bytes).
+
+The following code snippet sets a max file size of 5 megabytes.
+
+```python
+import gradio as gr
+
+demo = gr.Interface(lambda x: x, "image", "image")
+
+demo.launch(max_file_size="5mb")
+# or
+demo.launch(max_file_size=5 * gr.FileSize.MB)
+```
+
+![max_file_size_upload](https://github.com/gradio-app/gradio/assets/41651716/7547330c-a082-4901-a291-3f150a197e45)
+
+
+#### Error states can now be cleared
+
+When a component encounters an error, the error state shown in the UI can now be cleared by clicking on the `x` icon in the top right of the component. This applies to all types of errors, whether it's raised in the UI or the server.
+
+![error_modal_calculator](https://github.com/gradio-app/gradio/assets/41651716/16cb071c-accd-45a6-9c18-0dea27d4bd98)
\ No newline at end of file
diff --git a/.config/playwright-setup.js b/.config/playwright-setup.js
index 17f0dcdeea..eb980ec98c 100644
--- a/.config/playwright-setup.js
+++ b/.config/playwright-setup.js
@@ -115,7 +115,12 @@ ${demos.map((d) => `from demo.${d}.run import demo as ${d}`).join("\n")}
 
 app = FastAPI()
 ${demos
-	.map((d) => `app = gr.mount_gradio_app(app, ${d}, path="/${d}")`)
+	.map(
+		(d) =>
+			`app = gr.mount_gradio_app(app, ${d}, path="/${d}", max_file_size=${
+				d == "upload_file_limit_test" ? "'15kb'" : "None"
+			})`
+	)
 	.join("\n")}
 
 config = uvicorn.Config(app, port=${port}, log_level="info")
diff --git a/client/js/src/client.ts b/client/js/src/client.ts
index 405a0ab528..00bc2c0c39 100644
--- a/client/js/src/client.ts
+++ b/client/js/src/client.ts
@@ -250,6 +250,9 @@ export function api_factory(
 			} catch (e) {
 				return { error: BROKEN_CONNECTION_MSG };
 			}
+			if (!response.ok) {
+				return { error: await response.text() };
+			}
 			const output: UploadResponse["files"] = await response.json();
 			uploadResponses.push(...output);
 		}
diff --git a/client/js/src/types.ts b/client/js/src/types.ts
index 2c0b4375ca..e248ba3e75 100644
--- a/client/js/src/types.ts
+++ b/client/js/src/types.ts
@@ -21,6 +21,7 @@ export interface Config {
 	stylesheets: string[];
 	path: string;
 	protocol?: "sse_v2.1" | "sse_v2" | "sse_v1" | "sse" | "ws";
+	max_file_size?: number;
 }
 
 export interface Payload {
diff --git a/client/js/src/upload.ts b/client/js/src/upload.ts
index 5995024dbe..c891417dc1 100644
--- a/client/js/src/upload.ts
+++ b/client/js/src/upload.ts
@@ -13,12 +13,24 @@ export async function upload(
 	file_data: FileData[],
 	root: string,
 	upload_id?: string,
+	max_file_size?: number,
 	upload_fn: typeof upload_files = upload_files
 ): Promise<(FileData | null)[] | null> {
 	let files = (Array.isArray(file_data) ? file_data : [file_data]).map(
 		(file_data) => file_data.blob!
 	);
 
+	const oversized_files = files.filter(
+		(f) => f.size > (max_file_size ?? Infinity)
+	);
+	if (oversized_files.length) {
+		throw new Error(
+			`File size exceeds the maximum allowed size of ${max_file_size} bytes: ${oversized_files
+				.map((f) => f.name)
+				.join(", ")}`
+		);
+	}
+
 	return await Promise.all(
 		await upload_fn(root, files, undefined, upload_id).then(
 			async (response: { files?: string[]; error?: string }) => {
diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py
index 0d37f80eb7..c3bbd6151a 100644
--- a/client/python/gradio_client/client.py
+++ b/client/python/gradio_client/client.py
@@ -4,6 +4,7 @@
 import concurrent.futures
 import hashlib
 import json
+import math
 import os
 import re
 import secrets
@@ -17,6 +18,7 @@
 from concurrent.futures import Future
 from dataclasses import dataclass
 from datetime import datetime
+from functools import partial
 from pathlib import Path
 from threading import Lock
 from typing import Any, Callable, Literal
@@ -1169,7 +1171,7 @@ def process_input_files(self, *data) -> tuple:
             if self.client.upload_files and self.input_component_types[i].value_is_file:
                 d = utils.traverse(
                     d,
-                    self._upload_file,
+                    partial(self._upload_file, data_index=i),
                     lambda f: utils.is_filepath(f)
                     or utils.is_file_obj_with_meta(f)
                     or utils.is_http_url_like(f),
@@ -1217,7 +1219,7 @@ def reduce_singleton_output(self, *data) -> Any:
         else:
             return data
 
-    def _upload_file(self, f: str | dict) -> dict[str, str]:
+    def _upload_file(self, f: str | dict, data_index: int) -> dict[str, str]:
         if isinstance(f, str):
             warnings.warn(
                 f'The Client is treating: "{f}" as a file path. In future versions, this behavior will not happen automatically. '
@@ -1228,6 +1230,22 @@ def _upload_file(self, f: str | dict) -> dict[str, str]:
         else:
             file_path = f["path"]
         if not utils.is_http_url_like(file_path):
+            component_id = self.dependency["inputs"][data_index]
+            component_config = next(
+                (
+                    c
+                    for c in self.client.config["components"]
+                    if c["id"] == component_id
+                ),
+                {},
+            )
+            max_file_size = self.client.config.get("max_file_size", None)
+            max_file_size = math.inf if max_file_size is None else max_file_size
+            if os.path.getsize(file_path) > max_file_size:
+                raise ValueError(
+                    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", (Path(file_path).name, f))]
                 r = httpx.post(
diff --git a/client/python/test/conftest.py b/client/python/test/conftest.py
index 986dc48062..4613820835 100644
--- a/client/python/test/conftest.py
+++ b/client/python/test/conftest.py
@@ -446,3 +446,16 @@ def noop(x):
             butn2.click(noop, msg2, msg2)
 
     return demo
+
+
+@pytest.fixture
+def max_file_size_demo():
+    with gr.Blocks() as demo:
+        file_1b = gr.File()
+        upload_status = gr.Textbox()
+
+        file_1b.upload(
+            lambda x: "Upload successful", file_1b, upload_status, api_name="upload_1b"
+        )
+
+    return demo
diff --git a/client/python/test/files/alphabet.txt b/client/python/test/files/alphabet.txt
new file mode 100644
index 0000000000..e85d5b4528
--- /dev/null
+++ b/client/python/test/files/alphabet.txt
@@ -0,0 +1 @@
+abcdefghijklmnopqrstuvwxyz
\ No newline at end of file
diff --git a/client/python/test/files/cheetah1.jpg b/client/python/test/files/cheetah1.jpg
new file mode 100644
index 0000000000..c510ff30e0
Binary files /dev/null and b/client/python/test/files/cheetah1.jpg differ
diff --git a/client/python/test/test_client.py b/client/python/test/test_client.py
index 65c49e71c3..a5af511699 100644
--- a/client/python/test/test_client.py
+++ b/client/python/test/test_client.py
@@ -36,9 +36,12 @@
 
 @contextmanager
 def connect(
-    demo: gr.Blocks, serialize: bool = True, output_dir: str = DEFAULT_TEMP_DIR
+    demo: gr.Blocks,
+    serialize: bool = True,
+    output_dir: str = DEFAULT_TEMP_DIR,
+    max_file_size=None,
 ):
-    _, local_url, _ = demo.launch(prevent_thread_lock=True)
+    _, local_url, _ = demo.launch(prevent_thread_lock=True, max_file_size=max_file_size)
     try:
         yield Client(local_url, serialize=serialize, output_dir=output_dir)
     finally:
@@ -85,6 +88,18 @@ def test_raise_error_invalid_state(self):
         with pytest.raises(ValueError, match="invalid state"):
             Client("gradio-tests/paused-space")
 
+    def test_raise_error_max_file_size(self, max_file_size_demo):
+        with connect(max_file_size_demo, max_file_size="15kb") as client:
+            with pytest.raises(ValueError, match="exceeds the maximum file size"):
+                client.predict(
+                    file(Path(__file__).parent / "files" / "cheetah1.jpg"),
+                    api_name="/upload_1b",
+                )
+            client.predict(
+                file(Path(__file__).parent / "files" / "alphabet.txt"),
+                api_name="/upload_1b",
+            )
+
     @pytest.mark.flaky
     def test_numerical_to_label_space(self):
         client = Client("gradio-tests/titanic-survival")
diff --git a/demo/image_mod_default_image/run.ipynb b/demo/image_mod_default_image/run.ipynb
index 9e2befb5bc..1a17252385 100644
--- a/demo/image_mod_default_image/run.ipynb
+++ b/demo/image_mod_default_image/run.ipynb
@@ -1 +1 @@
-{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: image_mod_default_image"]}, {"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('images')\n", "!wget -q -O images/cheetah1.jpg https://github.com/gradio-app/gradio/raw/main/demo/image_mod_default_image/images/cheetah1.jpg\n", "!wget -q -O images/lion.jpg https://github.com/gradio-app/gradio/raw/main/demo/image_mod_default_image/images/lion.jpg\n", "!wget -q -O images/logo.png https://github.com/gradio-app/gradio/raw/main/demo/image_mod_default_image/images/logo.png"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "def image_mod(image):\n", "    return image.rotate(45)\n", "\n", "\n", "cheetah = os.path.join(os.path.abspath(''), \"images/cheetah1.jpg\")\n", "\n", "demo = gr.Interface(image_mod, gr.Image(type=\"pil\", value=cheetah), \"image\",\n", "    flagging_options=[\"blurry\", \"incorrect\", \"other\"], examples=[\n", "        os.path.join(os.path.abspath(''), \"images/lion.jpg\"),\n", "        os.path.join(os.path.abspath(''), \"images/logo.png\")\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: image_mod_default_image"]}, {"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('images')\n", "!wget -q -O images/cheetah1.jpg https://github.com/gradio-app/gradio/raw/main/demo/image_mod_default_image/images/cheetah1.jpg\n", "!wget -q -O images/lion.jpg https://github.com/gradio-app/gradio/raw/main/demo/image_mod_default_image/images/lion.jpg\n", "!wget -q -O images/logo.png https://github.com/gradio-app/gradio/raw/main/demo/image_mod_default_image/images/logo.png"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "def image_mod(image):\n", "    return image.rotate(45)\n", "\n", "\n", "cheetah = os.path.join(os.path.abspath(''), \"images/cheetah1.jpg\")\n", "\n", "demo = gr.Interface(image_mod, gr.Image(type=\"pil\", value=cheetah), \"image\",\n", "    flagging_options=[\"blurry\", \"incorrect\", \"other\"], examples=[\n", "        os.path.join(os.path.abspath(''), \"images/lion.jpg\"),\n", "        os.path.join(os.path.abspath(''), \"images/logo.png\")\n", "        ])\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch(max_file_size=\"70kb\")\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/image_mod_default_image/run.py b/demo/image_mod_default_image/run.py
index c2ad1f8be4..d05c3e299b 100644
--- a/demo/image_mod_default_image/run.py
+++ b/demo/image_mod_default_image/run.py
@@ -15,4 +15,4 @@ def image_mod(image):
         ])
 
 if __name__ == "__main__":
-    demo.launch()
+    demo.launch(max_file_size="70kb")
diff --git a/demo/upload_file_limit_test/run.ipynb b/demo/upload_file_limit_test/run.ipynb
new file mode 100644
index 0000000000..f61fe8abd5
--- /dev/null
+++ b/demo/upload_file_limit_test/run.ipynb
@@ -0,0 +1 @@
+{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: upload_file_limit_test"]}, {"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.Markdown(\"\"\"\n", "    # \u2b06\ufe0f\ud83d\udcc1 max_file_size test\n", "    The demo has a max file size of 15kb. The error modal should pop up when a file larger than that is uploaded.          \n", "    \"\"\")\n", "    with gr.Row():\n", "        with gr.Column():\n", "            gr.Image(label=\"Image\", interactive=True)\n", "            gr.Gallery(label=\"Gallery\", interactive=True)\n", "            gr.File(label=\"Single File\", interactive=True, file_count=\"single\")\n", "        with gr.Column():\n", "            gr.Model3D(label=\"Model 3D\", interactive=True,)\n", "            gr.MultimodalTextbox(label=\"Multimodal Textbox\", interactive=True)\n", "            gr.UploadButton(label=\"Upload Button\", interactive=True)\n", "        with gr.Column():\n", "            gr.Video(label=\"Video\", interactive=True)\n", "            gr.Audio(label=\"Audio\", interactive=True)\n", "            gr.File(label=\"Multiple Files\", interactive=True, file_count=\"multiple\")\n", "\n", "\n", "if __name__ == \"__main__\":\n", "    # The upload limit is set in playwright_setup.js\n", "    # since the e2e tests use mount_gradio_app\n", "    demo.launch(max_file_size=\"15kb\")"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/upload_file_limit_test/run.py b/demo/upload_file_limit_test/run.py
new file mode 100644
index 0000000000..25770e3429
--- /dev/null
+++ b/demo/upload_file_limit_test/run.py
@@ -0,0 +1,26 @@
+import gradio as gr
+
+with gr.Blocks() as demo:
+    gr.Markdown("""
+    # ⬆️📁 max_file_size test
+    The demo has a max file size of 15kb. The error modal should pop up when a file larger than that is uploaded.          
+    """)
+    with gr.Row():
+        with gr.Column():
+            gr.Image(label="Image", interactive=True)
+            gr.Gallery(label="Gallery", interactive=True)
+            gr.File(label="Single File", interactive=True, file_count="single")
+        with gr.Column():
+            gr.Model3D(label="Model 3D", interactive=True,)
+            gr.MultimodalTextbox(label="Multimodal Textbox", interactive=True)
+            gr.UploadButton(label="Upload Button", interactive=True)
+        with gr.Column():
+            gr.Video(label="Video", interactive=True)
+            gr.Audio(label="Audio", interactive=True)
+            gr.File(label="Multiple Files", interactive=True, file_count="multiple")
+
+
+if __name__ == "__main__":
+    # The upload limit is set in playwright_setup.js
+    # since the e2e tests use mount_gradio_app
+    demo.launch(max_file_size="15kb")
\ No newline at end of file
diff --git a/gradio/__init__.py b/gradio/__init__.py
index 433374a83b..23e8b6bae6 100644
--- a/gradio/__init__.py
+++ b/gradio/__init__.py
@@ -96,7 +96,7 @@
     TextArea,
 )
 from gradio.themes import Base as Theme
-from gradio.utils import NO_RELOAD, get_package_version, set_static_paths
+from gradio.utils import NO_RELOAD, FileSize, get_package_version, set_static_paths
 from gradio.wasm_utils import IS_WASM
 
 if not IS_WASM:
diff --git a/gradio/blocks.py b/gradio/blocks.py
index ea3e9c0b34..260f9529b6 100644
--- a/gradio/blocks.py
+++ b/gradio/blocks.py
@@ -1853,6 +1853,7 @@ def get_config_file(self):
             "show_error": getattr(self, "show_error", False),
             "show_api": self.show_api,
             "is_colab": utils.colab_check(),
+            "max_file_size": getattr(self, "max_file_size", None),
             "stylesheets": self.stylesheets,
             "theme": self.theme.name,
             "protocol": "sse_v3",
@@ -2031,6 +2032,7 @@ def launch(
         share_server_address: str | None = None,
         share_server_protocol: Literal["http", "https"] | None = None,
         auth_dependency: Callable[[fastapi.Request], str | None] | None = None,
+        max_file_size: str | int | None = None,
         _frontend: bool = True,
     ) -> tuple[FastAPI, str, str]:
         """
@@ -2066,6 +2068,7 @@ def launch(
             share_server_address: Use this to specify a custom FRP server and port for sharing Gradio apps (only applies if share=True). If not provided, will use the default FRP server at https://gradio.live. See https://github.com/huggingface/frp for more information.
             share_server_protocol: Use this to specify the protocol to use for the share links. Defaults to "https", unless a custom share_server_address is provided, in which case it defaults to "http". If you are using a custom share_server_address and want to use https, you must set this to "https".
             auth_dependency: A function that takes a FastAPI request and returns a string user ID or None. If the function returns None for a specific request, that user is not authorized to access the app (they will see a 401 Unauthorized response). To be used with external authentication systems like OAuth. Cannot be used with `auth`.
+            max_file_size: The maximum file size in bytes that can be uploaded. Can be a string of the form "<value><unit>", where value is any positive integer and unit is one of "b", "kb", "mb", "gb", "tb". If None, no limit is set.
         Returns:
             app: FastAPI app object that is running the demo
             local_url: Locally accessible link to the demo
@@ -2128,6 +2131,7 @@ def reverse(text):
             raise ValueError("`blocked_paths` must be a list of directories.")
 
         self.validate_queue_settings()
+        self.max_file_size = utils._parse_file_size(max_file_size)
 
         self.config = self.get_config_file()
         self.max_threads = max_threads
diff --git a/gradio/route_utils.py b/gradio/route_utils.py
index 14be277810..27ef429635 100644
--- a/gradio/route_utils.py
+++ b/gradio/route_utils.py
@@ -454,6 +454,7 @@ def __init__(
         max_fields: Union[int, float] = 1000,
         upload_id: str | None = None,
         upload_progress: FileUploadProgress | None = None,
+        max_file_size: int | float,
     ) -> None:
         self.headers = headers
         self.stream = stream
@@ -464,6 +465,7 @@ def __init__(
         self.upload_progress = upload_progress
         self._current_files = 0
         self._current_fields = 0
+        self.max_file_size = max_file_size
         self._current_partial_header_name: bytes = b""
         self._current_partial_header_value: bytes = b""
         self._current_part = MultipartPart()
@@ -594,6 +596,12 @@ async def parse(self) -> FormData:
                     assert part.file  # for type checkers  # noqa: S101
                     await part.file.write(data)
                     part.file.sha.update(data)  # type: ignore
+                    if os.stat(part.file.file.name).st_size > self.max_file_size:
+                        if self.upload_progress is not None:
+                            self.upload_progress.set_done(self.upload_id)  # type: ignore
+                        raise MultiPartException(
+                            f"File size exceeded maximum allowed size of {self.max_file_size} bytes."
+                        )
                 for part in self._file_parts_to_finish:
                     assert part.file  # for type checkers  # noqa: S101
                     await part.file.seek(0)
@@ -603,6 +611,7 @@ async def parse(self) -> FormData:
             # Close all the files if there was an error.
             for file in self._files_to_close_on_error:
                 file.close()
+                Path(file.name).unlink()
             raise exc
 
         parser.finalize()
diff --git a/gradio/routes.py b/gradio/routes.py
index 7d9b13c5be..2f6e21f388 100644
--- a/gradio/routes.py
+++ b/gradio/routes.py
@@ -5,6 +5,7 @@
 
 import asyncio
 import contextlib
+import math
 import sys
 
 if sys.version_info >= (3, 9):
@@ -1043,21 +1044,26 @@ async def upload_file(
             try:
                 if upload_id:
                     file_upload_statuses.track(upload_id)
+                max_file_size = app.get_blocks().max_file_size
+                max_file_size = max_file_size if max_file_size is not None else math.inf
                 multipart_parser = GradioMultiPartParser(
                     request.headers,
                     request.stream(),
                     max_files=1000,
                     max_fields=1000,
+                    max_file_size=max_file_size,
                     upload_id=upload_id if upload_id else None,
                     upload_progress=file_upload_statuses if upload_id else None,
                 )
                 form = await multipart_parser.parse()
             except MultiPartException as exc:
-                raise HTTPException(status_code=400, detail=exc.message) from exc
+                code = 413 if "maximum allowed size" in exc.message else 400
+                return PlainTextResponse(exc.message, status_code=code)
 
             output_files = []
             files_to_copy = []
             locations: list[str] = []
+
             for temp_file in form.getlist("files"):
                 if not isinstance(temp_file, GradioUploadFile):
                     raise TypeError("File is not an instance of GradioUploadFile")
@@ -1172,6 +1178,7 @@ def mount_gradio_app(
     blocked_paths: list[str] | None = None,
     favicon_path: str | None = None,
     show_error: bool = True,
+    max_file_size: str | int | None = None,
 ) -> fastapi.FastAPI:
     """Mount a gradio.Blocks to an existing FastAPI application.
 
@@ -1188,6 +1195,7 @@ def mount_gradio_app(
         blocked_paths: List of complete filepaths or parent directories that this gradio app is not allowed to serve (i.e. users of your app are not allowed to access). Must be absolute paths. Warning: takes precedence over `allowed_paths` and all other directories exposed by Gradio by default.
         favicon_path: If a path to a file (.png, .gif, or .ico) is provided, it will be used as the favicon for this gradio app's page.
         show_error: If True, any errors in the gradio app will be displayed in an alert modal and printed in the browser console log. Otherwise, errors will only be visible in the terminal session running the Gradio app.
+        max_file_size: The maximum file size in bytes that can be uploaded. Can be a string of the form "<value><unit>", where value is any positive integer and unit is one of "b", "kb", "mb", "gb", "tb". If None, no limit is set.
     Example:
         from fastapi import FastAPI
         import gradio as gr
@@ -1200,6 +1208,7 @@ def read_main():
         # Then run `uvicorn run:app` from the terminal and navigate to http://localhost:8000/gradio.
     """
     blocks.dev_mode = False
+    blocks.max_file_size = utils._parse_file_size(max_file_size)
     blocks.config = blocks.get_config_file()
     blocks.validate_queue_settings()
     if auth is not None and auth_dependency is not None:
diff --git a/gradio/utils.py b/gradio/utils.py
index 391b879d54..65bcb575eb 100644
--- a/gradio/utils.py
+++ b/gradio/utils.py
@@ -1280,3 +1280,27 @@ async def function_wrapper(*args, **kwargs):
         return f(*args, **kwargs)
 
     return function_wrapper
+
+
+class FileSize:
+    B = 1
+    KB = 1024 * B
+    MB = 1024 * KB
+    GB = 1024 * MB
+    TB = 1024 * GB
+
+
+def _parse_file_size(size: str | int | None) -> int | None:
+    if isinstance(size, int) or size is None:
+        return size
+
+    size = size.replace(" ", "")
+
+    last_digit_index = next(
+        (i for i, c in enumerate(size) if not c.isdigit()), len(size)
+    )
+    size_int, unit = int(size[:last_digit_index]), size[last_digit_index:].upper()
+    multiple = getattr(FileSize, unit, None)
+    if not multiple:
+        raise ValueError(f"Invalid file size unit: {unit}")
+    return multiple * size_int
diff --git a/guides/01_getting-started/03_sharing-your-app.md b/guides/01_getting-started/03_sharing-your-app.md
index 21b189f0bc..d2f6bf70df 100644
--- a/guides/01_getting-started/03_sharing-your-app.md
+++ b/guides/01_getting-started/03_sharing-your-app.md
@@ -451,4 +451,16 @@ Gradio DOES NOT ALLOW access to:
 
 - **Any other paths on the host machine**. Users should NOT be able to access other arbitrary paths on the host.
 
+Sharing your Gradio application will also allow users to upload files to your computer or server. You can set a maximum file size for uploads to prevent abuse and to preserve disk space. You can do this with the `max_file_size` parameter of `.launch`. For example, the following two code snippets limit file uploads to 5 megabytes per file.
+
+```python
+import gradio as gr
+
+demo = gr.Interface(lambda x: x, "image", "image")
+
+demo.launch(max_file_size="5mb")
+# or
+demo.launch(max_file_size=5 * gr.FileSize.MB)
+```
+
 Please make sure you are running the latest version of `gradio` for these security settings to apply.
diff --git a/js/app/src/Blocks.svelte b/js/app/src/Blocks.svelte
index d32cd218f3..f3526ad461 100644
--- a/js/app/src/Blocks.svelte
+++ b/js/app/src/Blocks.svelte
@@ -3,7 +3,7 @@
 	import { _ } from "svelte-i18n";
 	import { client } from "@gradio/client";
 
-	import type { LoadingStatusCollection } from "./stores";
+	import type { LoadingStatus, LoadingStatusCollection } from "./stores";
 
 	import type { ComponentMeta, Dependency, LayoutNode } from "./types";
 	import type { UpdateTransaction } from "./init";
@@ -18,6 +18,7 @@
 	import logo from "./images/logo.svg";
 	import api_logo from "./api_docs/img/api-logo.svg";
 	import { create_components, AsyncFunction } from "./init";
+	import type { int } from "babylonjs";
 
 	setupi18n();
 
@@ -430,6 +431,8 @@
 				trigger_share(title, description);
 			} else if (event === "error" || event === "warning") {
 				messages = [new_message(data, -1, event), ...messages];
+			} else if (event == "clear_status") {
+				update_status(id, "complete", data);
 			} else {
 				const deps = $targets[id]?.[event];
 
@@ -452,6 +455,21 @@
 
 	$: set_status($loading_status);
 
+	function update_status(
+		id: int,
+		status: "error" | "complete" | "pending",
+		data: LoadingStatus
+	): void {
+		data.status = status;
+		update_value([
+			{
+				id,
+				prop: "loading_status",
+				value: data
+			}
+		]);
+	}
+
 	function set_status(statuses: LoadingStatusCollection): void {
 		const updates = Object.entries(statuses).map(([id, loading_status]) => {
 			let dependency = dependencies[loading_status.fn_index];
@@ -518,6 +536,7 @@
 				on:destroy={({ detail }) => handle_destroy(detail)}
 				{version}
 				{autoscroll}
+				max_file_size={app.config.max_file_size}
 			/>
 		{/if}
 	</div>
diff --git a/js/app/src/MountComponents.svelte b/js/app/src/MountComponents.svelte
index d4c03ffa2b..42ce7bf288 100644
--- a/js/app/src/MountComponents.svelte
+++ b/js/app/src/MountComponents.svelte
@@ -8,6 +8,7 @@
 	export let theme_mode: any;
 	export let version: any;
 	export let autoscroll: boolean;
+	export let max_file_size: number | null = null;
 
 	const dispatch = createEventDispatcher<{ mount?: never }>();
 	onMount(() => {
@@ -15,4 +16,12 @@
 	});
 </script>
 
-<Render node={rootNode} {root} {target} {theme_mode} {version} {autoscroll} />
+<Render
+	node={rootNode}
+	{root}
+	{target}
+	{theme_mode}
+	{version}
+	{autoscroll}
+	{max_file_size}
+/>
diff --git a/js/app/src/Render.svelte b/js/app/src/Render.svelte
index 377461a060..a7f302dfd1 100644
--- a/js/app/src/Render.svelte
+++ b/js/app/src/Render.svelte
@@ -1,5 +1,5 @@
 <script lang="ts">
-	import { Gradio } from "./gradio_helper";
+	import { Gradio, formatter } from "./gradio_helper";
 	import { onMount, createEventDispatcher, setContext } from "svelte";
 	import type { ComponentMeta, ThemeMode } from "./types";
 	import RenderComponent from "./RenderComponent.svelte";
@@ -12,6 +12,7 @@
 	export let theme_mode: ThemeMode;
 	export let version: string;
 	export let autoscroll: boolean;
+	export let max_file_size: number | null;
 
 	const dispatch = createEventDispatcher<{ mount: number; destroy: number }>();
 	let filtered_children: ComponentMeta[] = [];
@@ -74,7 +75,16 @@
 	{...node.props}
 	{theme_mode}
 	{root}
-	gradio={new Gradio(node.id, target, theme_mode, version, root, autoscroll)}
+	gradio={new Gradio(
+		node.id,
+		target,
+		theme_mode,
+		version,
+		root,
+		autoscroll,
+		max_file_size,
+		formatter
+	)}
 >
 	{#if node.children && node.children.length}
 		{#each node.children as _node (_node.id)}
@@ -87,6 +97,7 @@
 				{theme_mode}
 				on:destroy
 				on:mount
+				{max_file_size}
 			/>
 		{/each}
 	{/if}
diff --git a/js/app/src/gradio_helper.ts b/js/app/src/gradio_helper.ts
index 98cb72521f..f971e8d493 100644
--- a/js/app/src/gradio_helper.ts
+++ b/js/app/src/gradio_helper.ts
@@ -1,41 +1,7 @@
 import { format } from "svelte-i18n";
 import { get } from "svelte/store";
+export { Gradio } from "@gradio/utils";
 
-const x = get(format);
+export const formatter = get(format);
 
-export type I18nFormatter = typeof x;
-export class Gradio<T extends Record<string, any> = Record<string, any>> {
-	#id: number;
-	theme: string;
-	version: string;
-	i18n: typeof x;
-	#el: HTMLElement;
-	root: string;
-	autoscroll: boolean;
-
-	constructor(
-		id: number,
-		el: HTMLElement,
-		theme: string,
-		version: string,
-		root: string,
-		autoscroll: boolean
-	) {
-		this.#id = id;
-		this.theme = theme;
-		this.version = version;
-		this.#el = el;
-		this.i18n = get(format);
-		this.root = root;
-		this.autoscroll = autoscroll;
-	}
-
-	dispatch<E extends keyof T>(event_name: E, data?: T[E]): void {
-		const e = new CustomEvent("gradio", {
-			bubbles: true,
-			detail: { data, id: this.#id, event: event_name }
-		});
-
-		this.#el.dispatchEvent(e);
-	}
-}
+export type I18nFormatter = typeof formatter;
diff --git a/js/app/test/upload_file_limit_test.spec.ts b/js/app/test/upload_file_limit_test.spec.ts
new file mode 100644
index 0000000000..17b21fc6f4
--- /dev/null
+++ b/js/app/test/upload_file_limit_test.spec.ts
@@ -0,0 +1,153 @@
+import { test, expect } from "@gradio/tootils";
+
+async function error_modal_showed(page) {
+	const toast = page.getByTestId("toast-body");
+	expect(toast).toContainText("error");
+	const close = page.getByTestId("toast-close");
+	await close.click();
+	await expect(page.getByTestId("toast-body")).toHaveCount(0);
+}
+
+async function get_file_selector(page, locator) {
+	//source https://stackoverflow.com/questions/74048369/setinputfiles-gives-error-on-antd-upload-component
+	let [file_chooser] = await Promise.all([
+		page.waitForEvent("filechooser"),
+		locator.click()
+	]);
+
+	return file_chooser;
+}
+
+test("gr.Image triggers the gr.Error model when an uploaded file exceeds max_file_size ", async ({
+	page
+}) => {
+	const file_chooser = await get_file_selector(
+		page,
+		page.getByRole("button", { name: "Drop Image Here - or - Click to Upload" })
+	);
+	await file_chooser.setFiles("./test/files/cheetah1.jpg");
+	await error_modal_showed(page);
+});
+
+test("gr.File(file_count='single') triggers the gr.Error modal when an uploaded file exceeds max_file_size ", async ({
+	page
+}) => {
+	const locator = page.getByText(
+		"Single File Drop File Here - or - Click to Upload"
+	);
+	const file_chooser = await get_file_selector(page, locator);
+	await file_chooser.setFiles("./test/files/cheetah1.jpg");
+	await error_modal_showed(page);
+});
+
+test("gr.File(file_count='multiple') triggers the gr.Error modal when an uploaded file exceeds max_file_size ", async ({
+	page
+}) => {
+	const locator = page.getByText(
+		"Multiple Files Drop File Here - or - Click to Upload"
+	);
+	const file_chooser = await get_file_selector(page, locator);
+	await file_chooser.setFiles([
+		"./test/files/cheetah1.jpg",
+		"./test/files/alphabet.txt"
+	]);
+	await error_modal_showed(page);
+});
+
+test("gr.Gallery() triggers the gr.Error modal when an uploaded file exceeds max_file_size ", async ({
+	page
+}) => {
+	const locator = page.getByText(
+		"Gallery Drop Image(s) Here - or - Click to Upload"
+	);
+	const file_chooser = await get_file_selector(page, locator);
+	await file_chooser.setFiles(["./test/files/cheetah1.jpg"]);
+	await error_modal_showed(page);
+});
+
+test("gr.Model3D() triggers the gr.Error modal when an uploaded file exceeds max_file_size ", async ({
+	page
+}) => {
+	const locator = page.getByText(
+		"Model 3D Drop File Here - or - Click to Upload"
+	);
+	const file_chooser = await get_file_selector(page, locator);
+	await file_chooser.setFiles(["./test/files/face.obj"]);
+	await error_modal_showed(page);
+});
+
+test("gr.MultimodalTextBox() triggers the gr.Error modal when an uploaded file exceeds max_file_size ", async ({
+	page
+}) => {
+	const locator = page.getByRole("button", { name: "+" });
+	const file_chooser = await get_file_selector(page, locator);
+	await file_chooser.setFiles(["./test/files/face.obj"]);
+	await error_modal_showed(page);
+});
+
+test("gr.UploadButton() triggers the gr.Error modal when an uploaded file exceeds max_file_size ", async ({
+	page
+}) => {
+	const locator = page.getByRole("button", {
+		name: "Upload Button",
+		exact: true
+	});
+	const file_chooser = await get_file_selector(page, locator);
+	await file_chooser.setFiles(["./test/files/face.obj"]);
+	await error_modal_showed(page);
+});
+
+test("gr.Video() triggers the gr.Error modal when an uploaded file exceeds max_file_size ", async ({
+	page
+}) => {
+	const locator = page.getByText(
+		"Video Drop Video Here - or - Click to Upload"
+	);
+	const file_chooser = await get_file_selector(page, locator);
+	await file_chooser.setFiles(["./test/files/world.mp4"]);
+	await error_modal_showed(page);
+});
+
+test("gr.Audio() triggers the gr.Error modal when an uploaded file exceeds max_file_size ", async ({
+	page
+}) => {
+	const locator = page.getByText(
+		"Audio Drop Audio Here - or - Click to Upload"
+	);
+	const file_chooser = await get_file_selector(page, locator);
+	await file_chooser.setFiles(["./test/files/world.mp4"]);
+	await error_modal_showed(page);
+});
+
+test("gr.File() will allow uploads below the max_file_size limit of 15kb", async ({
+	page
+}) => {
+	// test fails because the error modal does not show up
+	test.fail();
+
+	const locator = page.getByText(
+		"Single File Drop File Here - or - Click to Upload"
+	);
+	const file_chooser = await get_file_selector(page, locator);
+	await file_chooser.setFiles(["./test/files/gradio-logo.svg"]);
+	await expect(page.getByTestId("toast-body")).toHaveCount(1, {
+		timeout: 2000
+	});
+});
+
+test("gr.UploadButton() will allow uploads below the max_file_size limit of 15kb", async ({
+	page
+}) => {
+	// test fails because the error modal does not show up
+	test.fail();
+
+	const locator = page.getByRole("button", {
+		name: "Upload Button",
+		exact: true
+	});
+	const file_chooser = await get_file_selector(page, locator);
+	await file_chooser.setFiles(["./test/files/time.csv"]);
+	await expect(page.getByTestId("toast-body")).toHaveCount(1, {
+		timeout: 2000
+	});
+});
diff --git a/js/audio/Index.svelte b/js/audio/Index.svelte
index b34e4457c0..d2afb4ea33 100644
--- a/js/audio/Index.svelte
+++ b/js/audio/Index.svelte
@@ -52,6 +52,7 @@
 		upload: never;
 		clear: never;
 		share: ShareData;
+		clear_status: LoadingStatus;
 	}>;
 
 	let old_value: null | FileData = null;
@@ -151,6 +152,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 
 		<StaticAudio
@@ -187,6 +189,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 		<InteractiveAudio
 			{label}
@@ -204,6 +207,7 @@
 			{active_source}
 			{pending}
 			{streaming}
+			max_file_size={gradio.max_file_size}
 			{handle_reset_value}
 			{editable}
 			bind:dragging
diff --git a/js/audio/interactive/InteractiveAudio.svelte b/js/audio/interactive/InteractiveAudio.svelte
index 74fdc87f27..2aa81641b6 100644
--- a/js/audio/interactive/InteractiveAudio.svelte
+++ b/js/audio/interactive/InteractiveAudio.svelte
@@ -38,6 +38,7 @@
 	export let active_source: "microphone" | "upload";
 	export let handle_reset_value: () => void = () => {};
 	export let editable = true;
+	export let max_file_size: number | null = null;
 
 	// Needed for wasm support
 	const upload_fn = getContext<typeof upload_files>("upload_files");
@@ -97,9 +98,9 @@
 		let _audio_blob = new File(blobs, "audio.wav");
 		const val = await prepare_files([_audio_blob], event === "stream");
 		value = (
-			(await upload(val, root, undefined, upload_fn))?.filter(
-				Boolean
-			) as FileData[]
+			(
+				await upload(val, root, undefined, max_file_size ?? Infinity, upload_fn)
+			)?.filter(Boolean) as FileData[]
 		)[0];
 
 		dispatch(event, value);
@@ -255,6 +256,7 @@
 				bind:dragging
 				on:error={({ detail }) => dispatch("error", detail)}
 				{root}
+				{max_file_size}
 			>
 				<slot />
 			</Upload>
diff --git a/js/chatbot/Index.svelte b/js/chatbot/Index.svelte
index 63dbb91331..b67de5937e 100644
--- a/js/chatbot/Index.svelte
+++ b/js/chatbot/Index.svelte
@@ -45,6 +45,7 @@
 		share: ShareData;
 		error: string;
 		like: LikeData;
+		clear_status: LoadingStatus;
 	}>;
 	export let avatar_images: [FileData | null, FileData | null] = [null, null];
 
@@ -102,6 +103,7 @@
 			show_progress={loading_status.show_progress === "hidden"
 				? "hidden"
 				: "minimal"}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 	{/if}
 	<div class="wrapper">
diff --git a/js/checkbox/Index.svelte b/js/checkbox/Index.svelte
index 52f6e61f4d..a995fd41b6 100644
--- a/js/checkbox/Index.svelte
+++ b/js/checkbox/Index.svelte
@@ -26,6 +26,7 @@
 		change: never;
 		select: SelectData;
 		input: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let interactive: boolean;
 
@@ -48,6 +49,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 
 	{#if info}
diff --git a/js/checkboxgroup/Index.svelte b/js/checkboxgroup/Index.svelte
index a359c344db..76a1e2393c 100644
--- a/js/checkboxgroup/Index.svelte
+++ b/js/checkboxgroup/Index.svelte
@@ -10,6 +10,7 @@
 		change: never;
 		select: SelectData;
 		input: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
@@ -57,6 +58,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 	<BlockTitle {show_label} {info}>{label}</BlockTitle>
 
diff --git a/js/code/Index.svelte b/js/code/Index.svelte
index 949620f7cf..06097f5196 100644
--- a/js/code/Index.svelte
+++ b/js/code/Index.svelte
@@ -23,6 +23,7 @@
 		input: never;
 		blur: never;
 		focus: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let value = "";
 	export let value_is_output = false;
@@ -65,6 +66,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 
 	<BlockLabel Icon={CodeIcon} {show_label} {label} float={false} />
diff --git a/js/colorpicker/Index.svelte b/js/colorpicker/Index.svelte
index 786342eee2..79f343fe05 100644
--- a/js/colorpicker/Index.svelte
+++ b/js/colorpicker/Index.svelte
@@ -31,6 +31,7 @@
 		submit: never;
 		blur: never;
 		focus: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let interactive: boolean;
 </script>
@@ -40,6 +41,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 
 	<Colorpicker
diff --git a/js/dataframe/Index.svelte b/js/dataframe/Index.svelte
index 227eeed2a0..4fd30e0c7a 100644
--- a/js/dataframe/Index.svelte
+++ b/js/dataframe/Index.svelte
@@ -38,6 +38,7 @@
 		change: never;
 		select: SelectData;
 		input: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let latex_delimiters: {
 		left: string;
@@ -127,6 +128,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 	<Table
 		{root}
diff --git a/js/dropdown/Index.svelte b/js/dropdown/Index.svelte
index a8d9c0b2fc..88fdf66708 100644
--- a/js/dropdown/Index.svelte
+++ b/js/dropdown/Index.svelte
@@ -36,6 +36,7 @@
 		blur: never;
 		focus: never;
 		key_up: KeyUpData;
+		clear_status: LoadingStatus;
 	}>;
 	export let interactive: boolean;
 </script>
@@ -53,6 +54,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 
 	{#if multiselect}
diff --git a/js/fallback/Index.svelte b/js/fallback/Index.svelte
index af756468c8..129f056ae7 100644
--- a/js/fallback/Index.svelte
+++ b/js/fallback/Index.svelte
@@ -19,6 +19,7 @@
 		change: never;
 		select: SelectData;
 		input: never;
+		clear_status: LoadingStatus;
 	}>;
 </script>
 
@@ -28,6 +29,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 	{/if}
 
diff --git a/js/file/Index.svelte b/js/file/Index.svelte
index 5ecd3838b5..f54acb4c72 100644
--- a/js/file/Index.svelte
+++ b/js/file/Index.svelte
@@ -39,6 +39,7 @@
 		upload: never;
 		clear: never;
 		select: SelectData;
+		clear_status: LoadingStatus;
 	}>;
 	export let file_count: string;
 	export let file_types: string[] = ["file"];
@@ -72,6 +73,7 @@
 		status={pending_upload
 			? "generating"
 			: loading_status?.status || "complete"}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 	{#if !interactive}
 		<File
@@ -93,6 +95,7 @@
 			selectable={_selectable}
 			{root}
 			{height}
+			max_file_size={gradio.max_file_size}
 			on:change={({ detail }) => {
 				value = detail;
 			}}
@@ -100,6 +103,11 @@
 			on:clear={() => gradio.dispatch("clear")}
 			on:select={({ detail }) => gradio.dispatch("select", detail)}
 			on:upload={() => gradio.dispatch("upload")}
+			on:error={({ detail }) => {
+				loading_status = loading_status || {};
+				loading_status.status = "error";
+				gradio.dispatch("error", detail);
+			}}
 			i18n={gradio.i18n}
 		>
 			<UploadText i18n={gradio.i18n} type="file" />
diff --git a/js/file/shared/FileUpload.svelte b/js/file/shared/FileUpload.svelte
index cb650160a2..8430f31868 100644
--- a/js/file/shared/FileUpload.svelte
+++ b/js/file/shared/FileUpload.svelte
@@ -18,6 +18,7 @@
 	export let root: string;
 	export let height: number | undefined = undefined;
 	export let i18n: I18nFormatter;
+	export let max_file_size: number | null = null;
 
 	async function handle_upload({
 		detail
@@ -62,8 +63,10 @@
 		on:load={handle_upload}
 		filetype={file_types}
 		{file_count}
+		{max_file_size}
 		{root}
 		bind:dragging
+		on:error
 	>
 		<slot />
 	</Upload>
diff --git a/js/fileexplorer/Index.svelte b/js/fileexplorer/Index.svelte
index 50790fed3a..147fbb706e 100644
--- a/js/fileexplorer/Index.svelte
+++ b/js/fileexplorer/Index.svelte
@@ -31,6 +31,7 @@
 	export let min_width: number | undefined = undefined;
 	export let gradio: Gradio<{
 		change: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let server: {
 		ls: (path: string[]) => Promise<FileNode[]>;
@@ -62,6 +63,7 @@
 		{...loading_status}
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 	<BlockLabel
 		{show_label}
diff --git a/js/gallery/Index.svelte b/js/gallery/Index.svelte
index b711dd9d14..f0a1a12b60 100644
--- a/js/gallery/Index.svelte
+++ b/js/gallery/Index.svelte
@@ -41,6 +41,7 @@
 		share: ShareData;
 		error: string;
 		prop_change: Record<string, any>;
+		clear_status: LoadingStatus;
 	}>;
 
 	const dispatch = createEventDispatcher();
@@ -65,12 +66,14 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 	{#if interactive && no_value}
 		<BaseFileUpload
 			value={null}
 			{root}
 			{label}
+			max_file_size={gradio.max_file_size}
 			file_count={"multiple"}
 			file_types={["image"]}
 			i18n={gradio.i18n}
@@ -79,6 +82,11 @@
 				value = files.map((x) => ({ image: x, caption: null }));
 				gradio.dispatch("upload", value);
 			}}
+			on:error={({ detail }) => {
+				loading_status = loading_status || {};
+				loading_status.status = "error";
+				gradio.dispatch("error", detail);
+			}}
 		>
 			<UploadText i18n={gradio.i18n} type="gallery" />
 		</BaseFileUpload>
diff --git a/js/highlightedtext/HighlightedText.stories.svelte b/js/highlightedtext/HighlightedText.stories.svelte
index cfcefac34e..39d372db1b 100644
--- a/js/highlightedtext/HighlightedText.stories.svelte
+++ b/js/highlightedtext/HighlightedText.stories.svelte
@@ -19,7 +19,8 @@
 			"light",
 			"1.1.1",
 			"http://localhost:7860",
-			false
+			false,
+			null
 		)}
 		{...args}
 	/>
diff --git a/js/highlightedtext/Index.svelte b/js/highlightedtext/Index.svelte
index fc36e45852..d6386b7a16 100644
--- a/js/highlightedtext/Index.svelte
+++ b/js/highlightedtext/Index.svelte
@@ -16,6 +16,7 @@
 	export let gradio: Gradio<{
 		select: SelectData;
 		change: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
@@ -69,6 +70,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 		{#if label}
 			<BlockLabel
@@ -109,6 +111,7 @@
 			autoscroll={gradio.autoscroll}
 			{...loading_status}
 			i18n={gradio.i18n}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 		{#if label}
 			<BlockLabel
diff --git a/js/html/Index.svelte b/js/html/Index.svelte
index b2941c499b..73203ae362 100644
--- a/js/html/Index.svelte
+++ b/js/html/Index.svelte
@@ -13,6 +13,7 @@
 	export let loading_status: LoadingStatus;
 	export let gradio: Gradio<{
 		change: never;
+		clear_status: LoadingStatus;
 	}>;
 
 	$: label, gradio.dispatch("change");
@@ -24,6 +25,7 @@
 		i18n={gradio.i18n}
 		{...loading_status}
 		variant="center"
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 	<div class:pending={loading_status?.status === "pending"}>
 		<HTML
diff --git a/js/image/Index.svelte b/js/image/Index.svelte
index e7babb6ca4..b7ad5d29a9 100644
--- a/js/image/Index.svelte
+++ b/js/image/Index.svelte
@@ -60,6 +60,7 @@
 		clear: never;
 		select: SelectData;
 		share: ShareData;
+		clear_status: LoadingStatus;
 	}>;
 
 	$: {
@@ -125,6 +126,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 
 		<ImageUploader
@@ -152,6 +154,7 @@
 			{pending}
 			{streaming}
 			{mirror_webcam}
+			max_file_size={gradio.max_file_size}
 			i18n={gradio.i18n}
 		>
 			{#if active_source === "upload" || !active_source}
diff --git a/js/image/shared/ImageUploader.svelte b/js/image/shared/ImageUploader.svelte
index 3cba6add8e..28ac44a48d 100644
--- a/js/image/shared/ImageUploader.svelte
+++ b/js/image/shared/ImageUploader.svelte
@@ -25,6 +25,7 @@
 	export let selectable = false;
 	export let root: string;
 	export let i18n: I18nFormatter;
+	export let max_file_size: number | null = null;
 
 	let upload: Upload;
 	let uploading = false;
@@ -114,6 +115,7 @@
 			on:load={handle_upload}
 			on:error
 			{root}
+			{max_file_size}
 			disable_click={!sources.includes("upload")}
 		>
 			{#if value === null}
diff --git a/js/imageeditor/Index.svelte b/js/imageeditor/Index.svelte
index 8d1158e2e9..b1b251c76c 100644
--- a/js/imageeditor/Index.svelte
+++ b/js/imageeditor/Index.svelte
@@ -66,6 +66,7 @@
 		clear: never;
 		select: SelectData;
 		share: ShareData;
+		clear_status: LoadingStatus;
 	}>;
 
 	let editor_instance: InteractiveImageEditor;
@@ -136,6 +137,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 		<StaticImage
 			on:select={({ detail }) => gradio.dispatch("select", detail)}
@@ -169,6 +171,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 
 		<InteractiveImageEditor
diff --git a/js/json/Index.svelte b/js/json/Index.svelte
index 0a99c41f01..0e55b8d644 100644
--- a/js/json/Index.svelte
+++ b/js/json/Index.svelte
@@ -24,6 +24,7 @@
 	export let min_width: number | undefined = undefined;
 	export let gradio: Gradio<{
 		change: never;
+		clear_status: LoadingStatus;
 	}>;
 
 	$: {
@@ -58,6 +59,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 
 	<JSON {value} />
diff --git a/js/label/Index.svelte b/js/label/Index.svelte
index 298db49a2d..f23a67ad2c 100644
--- a/js/label/Index.svelte
+++ b/js/label/Index.svelte
@@ -13,6 +13,7 @@
 	export let gradio: Gradio<{
 		change: never;
 		select: SelectData;
+		clear_status: LoadingStatus;
 	}>;
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
@@ -48,6 +49,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 	{#if show_label}
 		<BlockLabel Icon={LabelIcon} {label} disable={container === false} />
diff --git a/js/markdown/Index.svelte b/js/markdown/Index.svelte
index 9e71dc3160..78895470b2 100644
--- a/js/markdown/Index.svelte
+++ b/js/markdown/Index.svelte
@@ -23,6 +23,7 @@
 	export let line_breaks = false;
 	export let gradio: Gradio<{
 		change: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let latex_delimiters: {
 		left: string;
@@ -46,6 +47,7 @@
 		i18n={gradio.i18n}
 		{...loading_status}
 		variant="center"
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 	<div class:pending={loading_status?.status === "pending"}>
 		<Markdown
diff --git a/js/model3D/Index.svelte b/js/model3D/Index.svelte
index 0674ec1e58..dc53cdce62 100644
--- a/js/model3D/Index.svelte
+++ b/js/model3D/Index.svelte
@@ -59,6 +59,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 
 		{#if value}
@@ -97,6 +98,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 
 		<Model3DUpload
@@ -118,7 +120,13 @@
 				value = detail;
 				gradio.dispatch("upload");
 			}}
+			on:error={({ detail }) => {
+				loading_status = loading_status || {};
+				loading_status.status = "error";
+				gradio.dispatch("error", detail);
+			}}
 			i18n={gradio.i18n}
+			max_file_size={gradio.max_file_size}
 		>
 			<UploadText i18n={gradio.i18n} type="file" />
 		</Model3DUpload>
diff --git a/js/model3D/shared/Model3DUpload.svelte b/js/model3D/shared/Model3DUpload.svelte
index 1eb2a5d331..12ab38ce8d 100644
--- a/js/model3D/shared/Model3DUpload.svelte
+++ b/js/model3D/shared/Model3DUpload.svelte
@@ -16,6 +16,7 @@
 	export let i18n: I18nFormatter;
 	export let zoom_speed = 1;
 	export let pan_speed = 1;
+	export let max_file_size: number | null = null;
 
 	// alpha, beta, radius
 	export let camera_position: [number | null, number | null, number | null] = [
@@ -87,8 +88,10 @@
 	<Upload
 		on:load={handle_upload}
 		{root}
+		{max_file_size}
 		filetype={[".stl", ".obj", ".gltf", ".glb", "model/obj", ".splat", ".ply"]}
 		bind:dragging
+		on:error
 	>
 		<slot />
 	</Upload>
diff --git a/js/multimodaltextbox/Index.svelte b/js/multimodaltextbox/Index.svelte
index 54df76e39d..893066a214 100644
--- a/js/multimodaltextbox/Index.svelte
+++ b/js/multimodaltextbox/Index.svelte
@@ -20,6 +20,8 @@
 		select: SelectData;
 		input: never;
 		focus: never;
+		error: string;
+		clear_status: LoadingStatus;
 	}>;
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
@@ -63,6 +65,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 	{/if}
 
@@ -83,12 +86,16 @@
 		{autofocus}
 		{container}
 		{autoscroll}
+		max_file_size={gradio.max_file_size}
 		on:change={() => gradio.dispatch("change", value)}
 		on:input={() => gradio.dispatch("input")}
 		on:submit={() => gradio.dispatch("submit")}
 		on:blur={() => gradio.dispatch("blur")}
 		on:select={(e) => gradio.dispatch("select", e.detail)}
 		on:focus={() => gradio.dispatch("focus")}
+		on:error={({ detail }) => {
+			gradio.dispatch("error", detail);
+		}}
 		disabled={!interactive}
 	/>
 </Block>
diff --git a/js/multimodaltextbox/shared/MultimodalTextbox.svelte b/js/multimodaltextbox/shared/MultimodalTextbox.svelte
index f50cf98237..7207bbbbe1 100644
--- a/js/multimodaltextbox/shared/MultimodalTextbox.svelte
+++ b/js/multimodaltextbox/shared/MultimodalTextbox.svelte
@@ -34,6 +34,7 @@
 	export let autoscroll = true;
 	export let root: string;
 	export let file_types: string[] | null = null;
+	export let max_file_size: number | null = null;
 
 	let upload_component: Upload;
 	let hidden_upload: HTMLInputElement;
@@ -176,6 +177,7 @@
 
 	function handle_upload_click(): void {
 		if (hidden_upload) {
+			hidden_upload.value = "";
 			hidden_upload.click();
 		}
 	}
@@ -206,11 +208,13 @@
 			on:load={handle_upload}
 			filetype={accept_file_types}
 			{root}
+			{max_file_size}
 			bind:dragging
 			bind:uploading
 			show_progress={false}
 			disable_click={true}
 			bind:hidden_upload
+			on:error
 		>
 			{#if submit_btn !== null}
 				<button class:disabled class="submit-button" on:click={handle_submit}
diff --git a/js/number/Index.svelte b/js/number/Index.svelte
index 20c15720ec..cff7bbc3c8 100644
--- a/js/number/Index.svelte
+++ b/js/number/Index.svelte
@@ -11,6 +11,7 @@
 		submit: never;
 		blur: never;
 		focus: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let label = gradio.i18n("number.number");
 	export let info: string | undefined = undefined;
@@ -66,6 +67,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 	<label class="block" class:container>
 		<BlockTitle {show_label} {info}>{label}</BlockTitle>
diff --git a/js/plot/Index.svelte b/js/plot/Index.svelte
index ee28821cfd..4548e5ca5c 100644
--- a/js/plot/Index.svelte
+++ b/js/plot/Index.svelte
@@ -30,6 +30,7 @@
 	export let bokeh_version: string | null;
 	export let gradio: Gradio<{
 		change: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let show_actions_button = false;
 </script>
@@ -53,6 +54,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 	<Plot
 		{value}
diff --git a/js/radio/Index.svelte b/js/radio/Index.svelte
index 76e09b87fb..8b0b5d49e0 100644
--- a/js/radio/Index.svelte
+++ b/js/radio/Index.svelte
@@ -15,6 +15,7 @@
 		change: never;
 		select: SelectData;
 		input: never;
+		clear_status: LoadingStatus;
 	}>;
 
 	export let label = gradio.i18n("radio.radio");
@@ -53,6 +54,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 
 	<BlockTitle {show_label} {info}>{label}</BlockTitle>
diff --git a/js/simpledropdown/Index.svelte b/js/simpledropdown/Index.svelte
index 8b31a3f18a..a5bbf7c14d 100644
--- a/js/simpledropdown/Index.svelte
+++ b/js/simpledropdown/Index.svelte
@@ -18,6 +18,7 @@
 	export let gradio: Gradio<{
 		change: string;
 		input: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let interactive: boolean;
 
@@ -56,6 +57,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 	{/if}
 
diff --git a/js/simpleimage/Index.svelte b/js/simpleimage/Index.svelte
index 580da78dd3..3d2d96f5c6 100644
--- a/js/simpleimage/Index.svelte
+++ b/js/simpleimage/Index.svelte
@@ -34,6 +34,7 @@
 		change: never;
 		upload: never;
 		clear: never;
+		clear_status: LoadingStatus;
 	}>;
 
 	$: value, gradio.dispatch("change");
@@ -58,6 +59,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 		<ImagePreview
 			{value}
@@ -84,6 +86,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 
 		<ImageUploader
diff --git a/js/simpletextbox/Index.svelte b/js/simpletextbox/Index.svelte
index 0e8bba35ef..c9fa468926 100644
--- a/js/simpletextbox/Index.svelte
+++ b/js/simpletextbox/Index.svelte
@@ -12,6 +12,7 @@
 		change: never;
 		submit: never;
 		input: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let label = "Textbox";
 	export let elem_id = "";
@@ -66,6 +67,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 	{/if}
 
diff --git a/js/slider/Index.svelte b/js/slider/Index.svelte
index d0500863cd..d5a6a22399 100644
--- a/js/slider/Index.svelte
+++ b/js/slider/Index.svelte
@@ -13,6 +13,7 @@
 		change: never;
 		input: never;
 		release: number;
+		clear_status: LoadingStatus;
 	}>;
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
@@ -79,6 +80,7 @@
 		autoscroll={gradio.autoscroll}
 		i18n={gradio.i18n}
 		{...loading_status}
+		on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 	/>
 
 	<div class="wrap">
diff --git a/js/statustracker/static/index.svelte b/js/statustracker/static/index.svelte
index a984e8c5e2..a4bb9fd57e 100644
--- a/js/statustracker/static/index.svelte
+++ b/js/statustracker/static/index.svelte
@@ -50,12 +50,18 @@
 	import Loader from "./Loader.svelte";
 	import type { LoadingStatus } from "./types";
 	import type { I18nFormatter } from "@gradio/utils";
+	import { createEventDispatcher } from "svelte";
+
+	import { IconButton } from "@gradio/atoms";
+	import { Clear } from "@gradio/icons";
+
+	const dispatch = createEventDispatcher();
 
 	export let i18n: I18nFormatter;
 	export let eta: number | null = null;
 	export let queue_position: number | null;
 	export let queue_size: number | null;
-	export let status: "complete" | "pending" | "error" | "generating";
+	export let status: "complete" | "pending" | "error" | "generating" | null;
 	export let scroll_to_output = false;
 	export let timer = true;
 	export let show_progress: "full" | "minimal" | "hidden" = "full";
@@ -271,6 +277,16 @@
 			<p class="loading">{loading_text}</p>
 		{/if}
 	{:else if status === "error"}
+		<div class="clear-status">
+			<IconButton
+				Icon={Clear}
+				label={i18n("common.clear")}
+				disabled={false}
+				on:click={(event) => {
+					dispatch("clear_status");
+				}}
+			/>
+		</div>
 		<span class="error">{i18n("common.error")}</span>
 		<slot name="error" />
 	{/if}
@@ -289,7 +305,6 @@
 		padding: 0 var(--size-6);
 		max-height: var(--size-screen-h);
 		overflow: hidden;
-		pointer-events: none;
 	}
 
 	.wrap.center {
@@ -422,4 +437,14 @@
 	.border {
 		border: 1px solid var(--border-color-primary);
 	}
+
+	.clear-status {
+		position: absolute;
+		display: flex;
+		top: var(--size-2);
+		right: var(--size-2);
+		justify-content: flex-end;
+		gap: var(--spacing-sm);
+		z-index: var(--layer-1);
+	}
 </style>
diff --git a/js/storybook/preview.ts b/js/storybook/preview.ts
index 69fde7d701..a94c372d18 100644
--- a/js/storybook/preview.ts
+++ b/js/storybook/preview.ts
@@ -1,7 +1,7 @@
 import type { Preview } from "@storybook/svelte";
 import "./theme.css";
 import { setupi18n } from "../app/src/i18n";
-import { Gradio } from "../app/src/gradio_helper";
+import { Gradio, formatter } from "../app/src/gradio_helper";
 import "../theme/src/reset.css";
 import "../theme/src/global.css";
 
@@ -19,7 +19,9 @@ const preview: Preview = {
 			"light",
 			"1.1.1",
 			"localhost:9876",
-			false
+			false,
+			null,
+			formatter
 		)
 	},
 	argTypes: {
diff --git a/js/textbox/Index.svelte b/js/textbox/Index.svelte
index 493d69e5e8..ca58d14d03 100644
--- a/js/textbox/Index.svelte
+++ b/js/textbox/Index.svelte
@@ -19,6 +19,7 @@
 		select: SelectData;
 		input: never;
 		focus: never;
+		clear_status: LoadingStatus;
 	}>;
 	export let label = "Textbox";
 	export let info: string | undefined = undefined;
@@ -58,6 +59,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 	{/if}
 
diff --git a/js/tootils/src/render.ts b/js/tootils/src/render.ts
index da722822e2..6396b0a85c 100644
--- a/js/tootils/src/render.ts
+++ b/js/tootils/src/render.ts
@@ -98,7 +98,8 @@ export async function render<
 				"light",
 				"2.0.0",
 				"http://localhost:8000",
-				false
+				false,
+				null
 			)
 		}
 	});
diff --git a/js/upload/src/Upload.svelte b/js/upload/src/Upload.svelte
index e8c3287b73..26bd8172dc 100644
--- a/js/upload/src/Upload.svelte
+++ b/js/upload/src/Upload.svelte
@@ -18,6 +18,7 @@
 	export let uploading = false;
 	export let hidden_upload: HTMLInputElement | null = null;
 	export let show_progress = true;
+	export let max_file_size: number | null = null;
 
 	let upload_id: string;
 	let file_data: FileData[];
@@ -83,10 +84,22 @@
 		await tick();
 		upload_id = Math.random().toString(36).substring(2, 15);
 		uploading = true;
-		const _file_data = await upload(file_data, root, upload_id, upload_fn);
-		dispatch("load", file_count === "single" ? _file_data?.[0] : _file_data);
-		uploading = false;
-		return _file_data || [];
+		try {
+			const _file_data = await upload(
+				file_data,
+				root,
+				upload_id,
+				max_file_size ?? Infinity,
+				upload_fn
+			);
+			dispatch("load", file_count === "single" ? _file_data?.[0] : _file_data);
+			uploading = false;
+			return _file_data || [];
+		} catch (e) {
+			dispatch("error", (e as Error).message);
+			uploading = false;
+			return [];
+		}
 	}
 
 	export async function load_files(
diff --git a/js/upload/src/UploadProgress.svelte b/js/upload/src/UploadProgress.svelte
index 0328435f04..cb7a42d65a 100644
--- a/js/upload/src/UploadProgress.svelte
+++ b/js/upload/src/UploadProgress.svelte
@@ -1,6 +1,11 @@
 <script lang="ts">
 	import { FileData } from "@gradio/client";
-	import { onMount, createEventDispatcher, getContext } from "svelte";
+	import {
+		onMount,
+		createEventDispatcher,
+		getContext,
+		onDestroy
+	} from "svelte";
 
 	type FileDataWithProgress = FileData & { progress: number };
 
@@ -56,6 +61,9 @@
 			}
 		};
 	});
+	onDestroy(() => {
+		if (event_source != null || event_source != undefined) event_source.close();
+	});
 
 	function calculateTotalProgress(files: FileData[]): number {
 		let totalProgress = 0;
diff --git a/js/uploadbutton/Index.svelte b/js/uploadbutton/Index.svelte
index dedaaefee4..3dc63da16a 100644
--- a/js/uploadbutton/Index.svelte
+++ b/js/uploadbutton/Index.svelte
@@ -24,6 +24,7 @@
 		change: never;
 		upload: never;
 		click: never;
+		error: string;
 	}>;
 	export let interactive: boolean;
 
@@ -53,9 +54,13 @@
 	{disabled}
 	{variant}
 	{label}
+	max_file_size={gradio.max_file_size}
 	on:click={() => gradio.dispatch("click")}
 	on:change={({ detail }) => handle_event(detail, "change")}
 	on:upload={({ detail }) => handle_event(detail, "upload")}
+	on:error={({ detail }) => {
+		gradio.dispatch("error", detail);
+	}}
 >
 	{label ? gradio.i18n(label) : ""}
 </UploadButton>
diff --git a/js/uploadbutton/shared/UploadButton.svelte b/js/uploadbutton/shared/UploadButton.svelte
index 08e95a7ff5..80e3f0d8de 100644
--- a/js/uploadbutton/shared/UploadButton.svelte
+++ b/js/uploadbutton/shared/UploadButton.svelte
@@ -22,6 +22,7 @@
 	export let min_width: number | undefined = undefined;
 	export let variant: "primary" | "secondary" | "stop" = "secondary";
 	export let disabled = false;
+	export let max_file_size: number | null = null;
 
 	const dispatch = createEventDispatcher();
 
@@ -50,6 +51,7 @@
 
 	async function load_files(files: FileList): Promise<void> {
 		let _files: File[] = Array.from(files);
+
 		if (!files.length) {
 			return;
 		}
@@ -59,9 +61,20 @@
 		let all_file_data = await prepare_files(_files);
 		await tick();
 
-		all_file_data = (
-			await upload(all_file_data, root, undefined, upload_fn)
-		)?.filter((x) => x !== null) as FileData[];
+		try {
+			all_file_data = (
+				await upload(
+					all_file_data,
+					root,
+					undefined,
+					max_file_size ?? Infinity,
+					upload_fn
+				)
+			)?.filter((x) => x !== null) as FileData[];
+		} catch (e) {
+			dispatch("error", (e as Error).message);
+			return;
+		}
 		value = file_count === "single" ? all_file_data?.[0] : all_file_data;
 		dispatch("change", value);
 		dispatch("upload", value);
diff --git a/js/utils/src/utils.ts b/js/utils/src/utils.ts
index 0793b552d6..f08828bfe8 100644
--- a/js/utils/src/utils.ts
+++ b/js/utils/src/utils.ts
@@ -180,6 +180,7 @@ export class Gradio<T extends Record<string, any> = Record<string, any>> {
 	#el: HTMLElement;
 	root: string;
 	autoscroll: boolean;
+	max_file_size: number | null;
 
 	constructor(
 		id: number,
@@ -187,14 +188,17 @@ export class Gradio<T extends Record<string, any> = Record<string, any>> {
 		theme: string,
 		version: string,
 		root: string,
-		autoscroll: boolean
+		autoscroll: boolean,
+		max_file_size: number | null,
+		i18n: I18nFormatter = (x: string): string => x
 	) {
 		this.#id = id;
 		this.theme = theme;
 		this.version = version;
 		this.#el = el;
+		this.max_file_size = max_file_size;
 
-		this.i18n = (x: string): string => x;
+		this.i18n = i18n;
 		this.root = root;
 		this.autoscroll = autoscroll;
 	}
diff --git a/js/video/Index.svelte b/js/video/Index.svelte
index 711dff5700..4a8062cabc 100644
--- a/js/video/Index.svelte
+++ b/js/video/Index.svelte
@@ -48,6 +48,7 @@
 		share: ShareData;
 		error: string;
 		warning: string;
+		clear_status: LoadingStatus;
 	}>;
 	export let interactive: boolean;
 	export let mirror_webcam: boolean;
@@ -137,6 +138,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 
 		<StaticVideo
@@ -175,6 +177,7 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
+			on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
 		/>
 
 		<Video
@@ -202,6 +205,7 @@
 			on:start_recording={() => gradio.dispatch("start_recording")}
 			on:stop_recording={() => gradio.dispatch("stop_recording")}
 			i18n={gradio.i18n}
+			max_file_size={gradio.max_file_size}
 		>
 			<UploadText i18n={gradio.i18n} type="video" />
 		</Video>
diff --git a/js/video/shared/InteractiveVideo.svelte b/js/video/shared/InteractiveVideo.svelte
index de23d9dfd4..d0ae4bbd15 100644
--- a/js/video/shared/InteractiveVideo.svelte
+++ b/js/video/shared/InteractiveVideo.svelte
@@ -28,6 +28,7 @@
 	export let i18n: I18nFormatter;
 	export let active_source: "webcam" | "upload" = "webcam";
 	export let handle_reset_value: () => void = () => {};
+	export let max_file_size: number | null = null;
 
 	const dispatch = createEventDispatcher<{
 		change: FileData | null;
@@ -77,6 +78,7 @@
 					bind:dragging
 					filetype="video/x-m4v,video/*"
 					on:load={handle_load}
+					{max_file_size}
 					on:error={({ detail }) => dispatch("error", detail)}
 					{root}
 				>
diff --git a/test/test_blocks.py b/test/test_blocks.py
index 01418d2caf..f1d1ea9a94 100644
--- a/test/test_blocks.py
+++ b/test/test_blocks.py
@@ -1740,7 +1740,7 @@ def test_static_files_multiple_apps(gradio_temp_dir):
 
 
 def test_time_to_live_and_delete_callback_for_state(capsys, monkeypatch):
-    monkeypatch.setenv("GRADIO_IS_E2E_TEST", 1)
+    monkeypatch.setenv("GRADIO_IS_E2E_TEST", "1")
 
     def test_fn(x):
         return x + 1, x + 1
diff --git a/test/test_routes.py b/test/test_routes.py
index 514c9f6be7..94122560ff 100644
--- a/test/test_routes.py
+++ b/test/test_routes.py
@@ -1268,3 +1268,17 @@ def test_compare_passwords_securely():
 )
 def test_starts_with_protocol(string, expected):
     assert starts_with_protocol(string) == expected
+
+
+def test_max_file_size_used_in_upload_route(connect):
+    with gr.Blocks() as demo:
+        gr.Markdown("Max file size demo")
+
+    app, _, _ = demo.launch(prevent_thread_lock=True, max_file_size="1kb")
+    test_client = TestClient(app)
+    with open("test/test_files/cheetah1.jpg", "rb") as f:
+        r = test_client.post("/upload", files={"files": f})
+        assert r.status_code == 413
+    with open("test/test_files/alphabet.txt", "rb") as f:
+        r = test_client.post("/upload", files={"files": f})
+        assert r.status_code == 200
diff --git a/test/test_utils.py b/test/test_utils.py
index 324aae34f3..2e7b4d91ed 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -13,6 +13,8 @@
 from gradio import EventData, Request
 from gradio.external_utils import format_ner_list
 from gradio.utils import (
+    FileSize,
+    _parse_file_size,
     abspath,
     append_unique_suffix,
     assert_configs_are_equivalent_besides_ids,
@@ -526,3 +528,9 @@ def test_lambda_with_args(self):
 
     def test_lambda_with_kwargs(self):
         assert get_function_params(lambda x, **kwargs: x) == [("x", False, None)]
+
+
+def test_parse_file_size():
+    assert _parse_file_size("1kb") == 1 * FileSize.KB
+    assert _parse_file_size("1mb") == 1 * FileSize.MB
+    assert _parse_file_size("505 Mb") == 505 * FileSize.MB


Code review comments:
diff_hunk: @@ -452,6 +455,21 @@
 
 	$: set_status($loading_status);
 
+	function update_status(
+		id: int,
path: js/app/src/Blocks.svelte
text: @user1:
No `int` type in javascript. Use `number`.

diff_hunk: @@ -271,6 +277,16 @@
 			<p class="loading">{loading_text}</p>
 		{/if}
 	{:else if status === "error"}
+		<div class="clear-status">
+			<IconButton
+				Icon={Clear}
+				label={i18n("common.clear")}
+				disabled={false}
+				on:click={(event) => {
path: js/statustracker/static/index.svelte
text: @user1:
we don't use `event` so we can just make this `()`

diff_hunk: @@ -18,6 +18,7 @@
 	import logo from "./images/logo.svg";
 	import api_logo from "./api_docs/img/api-logo.svg";
 	import { create_components, AsyncFunction } from "./init";
+	import type { int } from "babylonjs";
path: js/app/src/Blocks.svelte
text: @user1:
Mistaken, autoimport.

@author:
🤦