diff --git a/.changeset/full-flies-join.md b/.changeset/full-flies-join.md
new file mode 100644
index 0000000000000..78163117b8a5d
--- /dev/null
+++ b/.changeset/full-flies-join.md
@@ -0,0 +1,8 @@
+---
+"@gradio/file": minor
+"@gradio/tootils": minor
+"@gradio/upload": minor
+"gradio": minor
+---
+
+feat:add delete event to `File` component
diff --git a/demo/file_component_events/run.ipynb b/demo/file_component_events/run.ipynb
index 90a46080e1753..6aa1a62cebbc3 100644
--- a/demo/file_component_events/run.ipynb
+++ b/demo/file_component_events/run.ipynb
@@ -1 +1 @@
-{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: file_component_events"]}, {"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", " \n", " with gr.Row():\n", " with gr.Column():\n", " file_component = gr.File(label=\"Upload Single File\", file_count=\"single\")\n", " with gr.Column():\n", " output_file_1 = gr.File(label=\"Upload Single File Output\", file_count=\"single\")\n", " num_load_btn_1 = gr.Number(label=\"# Load Upload Single File\", value=0)\n", " file_component.upload(lambda s,n: (s, n + 1), [file_component, num_load_btn_1], [output_file_1, num_load_btn_1])\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_multiple = gr.File(label=\"Upload Multiple Files\", file_count=\"multiple\")\n", " with gr.Column():\n", " output_file_2 = gr.File(label=\"Upload Multiple Files Output\", file_count=\"multiple\")\n", " num_load_btn_2 = gr.Number(label=\"# Load Upload Multiple Files\", value=0)\n", " file_component_multiple.upload(lambda s,n: (s, n + 1), [file_component_multiple, num_load_btn_2], [output_file_2, num_load_btn_2])\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_specific = gr.File(label=\"Upload Multiple Files Image/Video\", file_count=\"multiple\", file_types=[\"image\", \"video\"])\n", " with gr.Column():\n", " output_file_3 = gr.File(label=\"Upload Multiple Files Output Image/Video\", file_count=\"multiple\")\n", " num_load_btn_3 = gr.Number(label=\"# Load Upload Multiple Files Image/Video\", value=0)\n", " file_component_specific.upload(lambda s,n: (s, n + 1), [file_component_specific, num_load_btn_3], [output_file_3, num_load_btn_3])\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_pdf = gr.File(label=\"Upload PDF File\", file_types=[\"pdf\"])\n", " with gr.Column():\n", " output_file_4 = gr.File(label=\"Upload PDF File Output\")\n", " num_load_btn_4 = gr.Number(label=\"# Load Upload PDF File\", value=0)\n", " file_component_pdf.upload(lambda s,n: (s, n + 1), [file_component_pdf, num_load_btn_4], [output_file_4, num_load_btn_4])\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_invalid = gr.File(label=\"Upload File with Invalid file_types\", file_types=[\"invalid file_type\"])\n", " with gr.Column():\n", " output_file_5 = gr.File(label=\"Upload File with Invalid file_types Output\")\n", " num_load_btn_5 = gr.Number(label=\"# Load Upload File with Invalid file_types\", value=0)\n", " file_component_invalid.upload(lambda s,n: (s, n + 1), [file_component_invalid, num_load_btn_5], [output_file_5, num_load_btn_5])\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: file_component_events"]}, {"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", "\n", "def delete_file(n: int, file: gr.DeletedFileData):\n", " return [file.file.path, n + 1]\n", "\n", "\n", "with gr.Blocks() as demo:\n", "\n", " with gr.Row():\n", " with gr.Column():\n", " file_component = gr.File(label=\"Upload Single File\", file_count=\"single\")\n", " with gr.Column():\n", " output_file_1 = gr.File(\n", " label=\"Upload Single File Output\", file_count=\"single\"\n", " )\n", " num_load_btn_1 = gr.Number(label=\"# Load Upload Single File\", value=0)\n", " file_component.upload(\n", " lambda s, n: (s, n + 1),\n", " [file_component, num_load_btn_1],\n", " [output_file_1, num_load_btn_1],\n", " )\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_multiple = gr.File(\n", " label=\"Upload Multiple Files\", file_count=\"multiple\"\n", " )\n", " with gr.Column():\n", " output_file_2 = gr.File(\n", " label=\"Upload Multiple Files Output\", file_count=\"multiple\"\n", " )\n", " num_load_btn_2 = gr.Number(label=\"# Load Upload Multiple Files\", value=0)\n", " file_component_multiple.upload(\n", " lambda s, n: (s, n + 1),\n", " [file_component_multiple, num_load_btn_2],\n", " [output_file_2, num_load_btn_2],\n", " )\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_specific = gr.File(\n", " label=\"Upload Multiple Files Image/Video\",\n", " file_count=\"multiple\",\n", " file_types=[\"image\", \"video\"],\n", " )\n", " with gr.Column():\n", " output_file_3 = gr.File(\n", " label=\"Upload Multiple Files Output Image/Video\", file_count=\"multiple\"\n", " )\n", " num_load_btn_3 = gr.Number(\n", " label=\"# Load Upload Multiple Files Image/Video\", value=0\n", " )\n", " file_component_specific.upload(\n", " lambda s, n: (s, n + 1),\n", " [file_component_specific, num_load_btn_3],\n", " [output_file_3, num_load_btn_3],\n", " )\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_pdf = gr.File(label=\"Upload PDF File\", file_types=[\"pdf\"])\n", " with gr.Column():\n", " output_file_4 = gr.File(label=\"Upload PDF File Output\")\n", " num_load_btn_4 = gr.Number(label=\"# Load Upload PDF File\", value=0)\n", " file_component_pdf.upload(\n", " lambda s, n: (s, n + 1),\n", " [file_component_pdf, num_load_btn_4],\n", " [output_file_4, num_load_btn_4],\n", " )\n", " with gr.Row():\n", " with gr.Column():\n", " file_component_invalid = gr.File(\n", " label=\"Upload File with Invalid file_types\",\n", " file_types=[\"invalid file_type\"],\n", " )\n", " with gr.Column():\n", " output_file_5 = gr.File(label=\"Upload File with Invalid file_types Output\")\n", " num_load_btn_5 = gr.Number(\n", " label=\"# Load Upload File with Invalid file_types\", value=0\n", " )\n", " file_component_invalid.upload(\n", " lambda s, n: (s, n + 1),\n", " [file_component_invalid, num_load_btn_5],\n", " [output_file_5, num_load_btn_5],\n", " )\n", " with gr.Row():\n", " with gr.Column():\n", " del_file_input = gr.File(label=\"Delete File\", file_count=\"multiple\")\n", " with gr.Column():\n", " del_file_data = gr.Textbox(label=\"Delete file data\")\n", " num_load_btn_6 = gr.Number(label=\"# Deleted File\", value=0)\n", " del_file_input.delete(\n", " delete_file,\n", " [num_load_btn_6],\n", " [del_file_data, num_load_btn_6],\n", " )\n", " # f = gr.File(label=\"Upload many File\", file_count=\"multiple\")\n", " # # f.delete(delete_file)\n", " # f.delete(delete_file, inputs=None, outputs=None)\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/file_component_events/run.py b/demo/file_component_events/run.py
index dc0096102601b..3965eabe14f32 100644
--- a/demo/file_component_events/run.py
+++ b/demo/file_component_events/run.py
@@ -1,42 +1,100 @@
import gradio as gr
+
+def delete_file(n: int, file: gr.DeletedFileData):
+ return [file.file.path, n + 1]
+
+
with gr.Blocks() as demo:
-
+
with gr.Row():
with gr.Column():
file_component = gr.File(label="Upload Single File", file_count="single")
with gr.Column():
- output_file_1 = gr.File(label="Upload Single File Output", file_count="single")
+ output_file_1 = gr.File(
+ label="Upload Single File Output", file_count="single"
+ )
num_load_btn_1 = gr.Number(label="# Load Upload Single File", value=0)
- file_component.upload(lambda s,n: (s, n + 1), [file_component, num_load_btn_1], [output_file_1, num_load_btn_1])
+ file_component.upload(
+ lambda s, n: (s, n + 1),
+ [file_component, num_load_btn_1],
+ [output_file_1, num_load_btn_1],
+ )
with gr.Row():
with gr.Column():
- file_component_multiple = gr.File(label="Upload Multiple Files", file_count="multiple")
+ file_component_multiple = gr.File(
+ label="Upload Multiple Files", file_count="multiple"
+ )
with gr.Column():
- output_file_2 = gr.File(label="Upload Multiple Files Output", file_count="multiple")
+ output_file_2 = gr.File(
+ label="Upload Multiple Files Output", file_count="multiple"
+ )
num_load_btn_2 = gr.Number(label="# Load Upload Multiple Files", value=0)
- file_component_multiple.upload(lambda s,n: (s, n + 1), [file_component_multiple, num_load_btn_2], [output_file_2, num_load_btn_2])
+ file_component_multiple.upload(
+ lambda s, n: (s, n + 1),
+ [file_component_multiple, num_load_btn_2],
+ [output_file_2, num_load_btn_2],
+ )
with gr.Row():
with gr.Column():
- file_component_specific = gr.File(label="Upload Multiple Files Image/Video", file_count="multiple", file_types=["image", "video"])
+ file_component_specific = gr.File(
+ label="Upload Multiple Files Image/Video",
+ file_count="multiple",
+ file_types=["image", "video"],
+ )
with gr.Column():
- output_file_3 = gr.File(label="Upload Multiple Files Output Image/Video", file_count="multiple")
- num_load_btn_3 = gr.Number(label="# Load Upload Multiple Files Image/Video", value=0)
- file_component_specific.upload(lambda s,n: (s, n + 1), [file_component_specific, num_load_btn_3], [output_file_3, num_load_btn_3])
+ output_file_3 = gr.File(
+ label="Upload Multiple Files Output Image/Video", file_count="multiple"
+ )
+ num_load_btn_3 = gr.Number(
+ label="# Load Upload Multiple Files Image/Video", value=0
+ )
+ file_component_specific.upload(
+ lambda s, n: (s, n + 1),
+ [file_component_specific, num_load_btn_3],
+ [output_file_3, num_load_btn_3],
+ )
with gr.Row():
with gr.Column():
file_component_pdf = gr.File(label="Upload PDF File", file_types=["pdf"])
with gr.Column():
output_file_4 = gr.File(label="Upload PDF File Output")
num_load_btn_4 = gr.Number(label="# Load Upload PDF File", value=0)
- file_component_pdf.upload(lambda s,n: (s, n + 1), [file_component_pdf, num_load_btn_4], [output_file_4, num_load_btn_4])
+ file_component_pdf.upload(
+ lambda s, n: (s, n + 1),
+ [file_component_pdf, num_load_btn_4],
+ [output_file_4, num_load_btn_4],
+ )
with gr.Row():
with gr.Column():
- file_component_invalid = gr.File(label="Upload File with Invalid file_types", file_types=["invalid file_type"])
+ file_component_invalid = gr.File(
+ label="Upload File with Invalid file_types",
+ file_types=["invalid file_type"],
+ )
with gr.Column():
output_file_5 = gr.File(label="Upload File with Invalid file_types Output")
- num_load_btn_5 = gr.Number(label="# Load Upload File with Invalid file_types", value=0)
- file_component_invalid.upload(lambda s,n: (s, n + 1), [file_component_invalid, num_load_btn_5], [output_file_5, num_load_btn_5])
+ num_load_btn_5 = gr.Number(
+ label="# Load Upload File with Invalid file_types", value=0
+ )
+ file_component_invalid.upload(
+ lambda s, n: (s, n + 1),
+ [file_component_invalid, num_load_btn_5],
+ [output_file_5, num_load_btn_5],
+ )
+ with gr.Row():
+ with gr.Column():
+ del_file_input = gr.File(label="Delete File", file_count="multiple")
+ with gr.Column():
+ del_file_data = gr.Textbox(label="Delete file data")
+ num_load_btn_6 = gr.Number(label="# Deleted File", value=0)
+ del_file_input.delete(
+ delete_file,
+ [num_load_btn_6],
+ [del_file_data, num_load_btn_6],
+ )
+ # f = gr.File(label="Upload many File", file_count="multiple")
+ # # f.delete(delete_file)
+ # f.delete(delete_file, inputs=None, outputs=None)
if __name__ == "__main__":
demo.launch()
diff --git a/gradio/__init__.py b/gradio/__init__.py
index 5143223a89d0f..1352c33112f52 100644
--- a/gradio/__init__.py
+++ b/gradio/__init__.py
@@ -60,7 +60,14 @@
from gradio.components.audio import WaveformOptions
from gradio.components.image_editor import Brush, Eraser
from gradio.data_classes import FileData
-from gradio.events import EventData, KeyUpData, LikeData, SelectData, on
+from gradio.events import (
+ DeletedFileData,
+ EventData,
+ KeyUpData,
+ LikeData,
+ SelectData,
+ on,
+)
from gradio.exceptions import Error
from gradio.external import load
from gradio.flagging import (
diff --git a/gradio/components/file.py b/gradio/components/file.py
index 86cb20e0ce531..3d88ff1c6ffd0 100644
--- a/gradio/components/file.py
+++ b/gradio/components/file.py
@@ -26,7 +26,7 @@ class File(Component):
Demo: zip_files, zip_to_json
"""
- EVENTS = [Events.change, Events.select, Events.clear, Events.upload]
+ EVENTS = [Events.change, Events.select, Events.clear, Events.upload, Events.delete]
def __init__(
self,
diff --git a/gradio/data_classes.py b/gradio/data_classes.py
index 398dd5f2314e6..f43e15073068d 100644
--- a/gradio/data_classes.py
+++ b/gradio/data_classes.py
@@ -8,7 +8,7 @@
import shutil
from abc import ABC, abstractmethod
from enum import Enum, auto
-from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union
+from typing import TYPE_CHECKING, Any, List, Optional, Tuple, TypedDict, Union
from fastapi import Request
from gradio_client.utils import traverse
@@ -189,6 +189,16 @@ def from_json(cls, x) -> GradioRootModel:
GradioDataModel = Union[GradioModel, GradioRootModel]
+class FileDataDict(TypedDict):
+ path: str # server filepath
+ url: Optional[str] # normalised server url
+ size: Optional[int] # size in bytes
+ orig_name: Optional[str] # original filename
+ mime_type: Optional[str]
+ is_stream: bool
+ meta: dict
+
+
class FileData(GradioModel):
path: str # server filepath
url: Optional[str] = None # normalised server url
diff --git a/gradio/events.py b/gradio/events.py
index 5ec9e444ea78c..63f8f9341bf5c 100644
--- a/gradio/events.py
+++ b/gradio/events.py
@@ -20,6 +20,8 @@
from gradio_client.documentation import document
from jinja2 import Template
+from gradio.data_classes import FileData, FileDataDict
+
if TYPE_CHECKING:
from gradio.blocks import Block, Component
@@ -149,6 +151,15 @@ def __init__(self, target: Block | None, data: Any):
"""
+class DeletedFileData(EventData):
+ def __init__(self, target: Block | None, data: FileDataDict):
+ super().__init__(target, data)
+ self.file: FileData = FileData(**data)
+ """
+ The file that was deleted.
+ """
+
+
@dataclasses.dataclass
class EventListenerMethod:
block: Block | None
@@ -585,6 +596,10 @@ class Events:
"apply",
doc="This listener is triggered when the user applies changes to the {{ component }} through an integrated UI action.",
)
+ delete = EventListener(
+ "delete",
+ doc="This listener is triggered when the user deletes and item from the {{ component }}. Uses event data gradio.DeletedFileData to carry `value` referring to the file that was deleted as an instance of FileData. See EventData documentation on how to use this event data",
+ )
class LikeData(EventData):
diff --git a/js/app/test/file_component_events.spec.ts b/js/app/test/file_component_events.spec.ts
index 039049c6fb9a3..0fccf1422d201 100644
--- a/js/app/test/file_component_events.spec.ts
+++ b/js/app/test/file_component_events.spec.ts
@@ -1,5 +1,13 @@
import { test, expect, drag_and_drop_file } 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);
+}
+
test("File component properly dispatches load event for the single file case.", async ({
page
}) => {
@@ -63,11 +71,33 @@ test("File component properly handles drag and drop of pdf file.", async ({
test("File component properly handles invalid file_types.", async ({
page
}) => {
- const uploader = await page.locator("input[type=file]").last();
- await uploader.setInputFiles(["./test/files/cheetah1.jpg"]);
+ const locator = page.locator("input[type=file]").nth(4);
+ await drag_and_drop_file(
+ page,
+ locator,
+ "./test/files/cheetah1.jpg",
+ "cheetah1.jpg",
+ "image/jpeg"
+ );
- // Check that the pdf file was uploaded
- await expect(
- page.getByLabel("# Load Upload File with Invalid file_types")
- ).toHaveValue("1");
+ await error_modal_showed(page);
+});
+
+test("Delete event is fired correctly", async ({ page }) => {
+ const locator = page.locator("input[type=file]").nth(5);
+ await drag_and_drop_file(
+ page,
+ locator,
+ "./test/files/cheetah1.jpg",
+ "cheetah1.jpg",
+ "image/jpeg",
+ 2
+ );
+
+ await page.getByLabel("Remove this file").first().click();
+
+ await expect(page.getByLabel("# Deleted File")).toHaveValue("1");
+ expect(
+ (await page.getByLabel("Delete file data").inputValue()).length
+ ).toBeGreaterThan(5);
});
diff --git a/js/file/Index.svelte b/js/file/Index.svelte
index 805e8454f757c..f8e72a56aa8eb 100644
--- a/js/file/Index.svelte
+++ b/js/file/Index.svelte
@@ -40,6 +40,7 @@
clear: never;
select: SelectData;
clear_status: LoadingStatus;
+ delete: FileData;
}>;
export let file_count: string;
export let file_types: string[] = ["file"];
@@ -110,6 +111,9 @@
loading_status.status = "error";
gradio.dispatch("error", detail);
}}
+ on:delete={({ detail }) => {
+ gradio.dispatch("delete", detail);
+ }}
i18n={gradio.i18n}
>
diff --git a/js/file/shared/FilePreview.svelte b/js/file/shared/FilePreview.svelte
index 0c63685fae779..fbc9c0bcb41de 100644
--- a/js/file/shared/FilePreview.svelte
+++ b/js/file/shared/FilePreview.svelte
@@ -8,6 +8,7 @@
const dispatch = createEventDispatcher<{
select: SelectData;
change: FileData[] | FileData;
+ delete: FileData;
}>();
export let value: FileData | FileData[];
export let selectable = false;
@@ -46,9 +47,10 @@
}
function remove_file(index: number): void {
- normalized_files.splice(index, 1);
+ const removed = normalized_files.splice(index, 1);
normalized_files = [...normalized_files];
value = normalized_files;
+ dispatch("delete", removed[0]);
dispatch("change", normalized_files);
}
diff --git a/js/file/shared/FileUpload.svelte b/js/file/shared/FileUpload.svelte
index 7e2ab3d8eea2f..a578631e9025a 100644
--- a/js/file/shared/FileUpload.svelte
+++ b/js/file/shared/FileUpload.svelte
@@ -50,16 +50,19 @@
$: dispatch("drag", dragging);
-
+
{#if value && (Array.isArray(value) ? value.length > 0 : true)}
-
+
{:else}
=> {
const buffer = (await fsPromises.readFile(filePath)).toString("base64");
const dataTransfer = await page.evaluateHandle(
- async ({ bufferData, localFileName, localFileType }) => {
+ async ({ bufferData, localFileName, localFileType, count }) => {
const dt = new DataTransfer();
const blobData = await fetch(bufferData).then((res) => res.blob());
- const file = new File([blobData], localFileName, { type: localFileType });
- dt.items.add(file);
+ const file = new File([blobData], localFileName, {
+ type: localFileType
+ });
+
+ for (let i = 0; i < count; i++) {
+ dt.items.add(file);
+ }
return dt;
},
{
bufferData: `data:application/octet-stream;base64,${buffer}`,
localFileName: fileName,
- localFileType: fileType
+ localFileType: fileType,
+ count
}
);
- await page.dispatchEvent(selector, "drop", { dataTransfer });
+ if (typeof selector === "string") {
+ await page.dispatchEvent(selector, "drop", { dataTransfer });
+ } else {
+ await selector.dispatchEvent("drop", { dataTransfer });
+ }
};
diff --git a/js/upload/src/Upload.svelte b/js/upload/src/Upload.svelte
index 0ce9ce930681d..5870b40f5056b 100644
--- a/js/upload/src/Upload.svelte
+++ b/js/upload/src/Upload.svelte
@@ -150,6 +150,7 @@
} else {
return false;
}
+
return (
acceptArray.includes(uploaded_file_extension) ||
acceptArray.some((type) => {