mirror of
https://github.com/gradio-app/gradio.git
synced 2025-03-19 12:00:39 +08:00
Support large files try 2 (#3191)
* changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * chagnes * changes * changes * changes * changes * changes * changes * chnages * changes * changes * merge * changes * changes * changes * changes * changes * changes --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
This commit is contained in:
parent
9b2119f297
commit
f34120c077
CHANGELOG.md
demo/zip_files
gradio
test
ui
@ -70,6 +70,7 @@ By [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3165](https://github.
|
||||
* Fixes `gr.utils.delete_none` to only remove props whose values are `None` from the config by [@abidlabs](https://github.com/abidlabs) in [PR 3188](https://github.com/gradio-app/gradio/pull/3188)
|
||||
* Fix bug where embedded demos were not loading files properly by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3177](https://github.com/gradio-app/gradio/pull/3177)
|
||||
* The `change` event is now triggered when users click the 'Clear All' button of the multiselect DropDown component by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3195](https://github.com/gradio-app/gradio/pull/3195)
|
||||
* Stops File component from freezing when a large file is uploaded by [@aliabid94](https://github.com/aliabid94) in [PR 3191](https://github.com/gradio-app/gradio/pull/3191)
|
||||
* Support Chinese pinyin in Dataframe by [@aliabid94](https://github.com/aliabid94) in [PR 3206](https://github.com/gradio-app/gradio/pull/3206)
|
||||
* The `clear` event is now triggered when images are cleared by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3218](https://github.com/gradio-app/gradio/pull/3218)
|
||||
* Fix bug where auth cookies where not sent when connecting to an app via http by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3223](https://github.com/gradio-app/gradio/pull/3223)
|
||||
|
1
demo/zip_files/.gitignore
vendored
Normal file
1
demo/zip_files/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
tmp.zip
|
@ -1 +1 @@
|
||||
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: zip_files"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/titanic.csv https://github.com/gradio-app/gradio/raw/main/demo/zip_files/files/titanic.csv\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/zip_files/tmp.zip"]}, {"cell_type": "code", "execution_count": null, "id": 44380577570523278879349135829904343037, "metadata": {}, "outputs": [], "source": ["import os\n", "from zipfile import ZipFile\n", "\n", "import gradio as gr\n", "\n", "\n", "def zip_files(files):\n", " with ZipFile(\"tmp.zip\", \"w\") as zipObj:\n", " for idx, file in enumerate(files):\n", " zipObj.write(file.name, \"file\" + str(idx))\n", " return \"tmp.zip\"\n", "\n", "demo = gr.Interface(\n", " zip_files,\n", " gr.File(file_count=\"multiple\", file_types=[\"text\", \".json\", \".csv\"]),\n", " \"file\",\n", " examples=[[[os.path.join(os.path.abspath(''),\"files/titanic.csv\"), \n", " os.path.join(os.path.abspath(''),\"files/titanic.csv\"), \n", " os.path.join(os.path.abspath(''),\"files/titanic.csv\")]]], \n", " cache_examples=True\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
||||
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: zip_files"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/titanic.csv https://github.com/gradio-app/gradio/raw/main/demo/zip_files/files/titanic.csv"]}, {"cell_type": "code", "execution_count": null, "id": 44380577570523278879349135829904343037, "metadata": {}, "outputs": [], "source": ["import os\n", "from zipfile import ZipFile\n", "\n", "import gradio as gr\n", "\n", "\n", "def zip_files(files):\n", " with ZipFile(\"tmp.zip\", \"w\") as zipObj:\n", " for idx, file in enumerate(files):\n", " zipObj.write(file.name, file.name.split(\"/\")[-1])\n", " return \"tmp.zip\"\n", "\n", "demo = gr.Interface(\n", " zip_files,\n", " gr.File(file_count=\"multiple\", file_types=[\"text\", \".json\", \".csv\"]),\n", " \"file\",\n", " examples=[[[os.path.join(os.path.abspath(''),\"files/titanic.csv\"), \n", " os.path.join(os.path.abspath(''),\"files/titanic.csv\"), \n", " os.path.join(os.path.abspath(''),\"files/titanic.csv\")]]], \n", " cache_examples=True\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
@ -7,7 +7,7 @@ import gradio as gr
|
||||
def zip_files(files):
|
||||
with ZipFile("tmp.zip", "w") as zipObj:
|
||||
for idx, file in enumerate(files):
|
||||
zipObj.write(file.name, "file" + str(idx))
|
||||
zipObj.write(file.name, file.name.split("/")[-1])
|
||||
return "tmp.zip"
|
||||
|
||||
demo = gr.Interface(
|
||||
|
Binary file not shown.
@ -15,8 +15,10 @@ from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Dict, Set, Tuple
|
||||
|
||||
import aiofiles
|
||||
import numpy as np
|
||||
import requests
|
||||
from fastapi import UploadFile
|
||||
from ffmpy import FFmpeg, FFprobe, FFRuntimeError
|
||||
from PIL import Image, ImageOps, PngImagePlugin
|
||||
|
||||
@ -403,6 +405,19 @@ class TempFileManager:
|
||||
self.temp_files.add(full_temp_file_path)
|
||||
return full_temp_file_path
|
||||
|
||||
async def save_uploaded_file(self, file: UploadFile, upload_dir: str) -> str:
|
||||
prefix, extension = self.get_prefix_and_extension(file.filename)
|
||||
output_file_obj = tempfile.NamedTemporaryFile(
|
||||
delete=False, dir=upload_dir, suffix=f"{extension}", prefix=f"{prefix}_"
|
||||
)
|
||||
async with aiofiles.open(output_file_obj.name, "wb") as output_file:
|
||||
while True:
|
||||
content = await file.read(100 * 1024 * 1024)
|
||||
if not content:
|
||||
break
|
||||
await output_file.write(content)
|
||||
return str(utils.abspath(output_file_obj.name))
|
||||
|
||||
def download_temp_copy_if_needed(self, url: str) -> str:
|
||||
"""Downloads a file and makes a temporary file path for a copy if does not already
|
||||
exist. Otherwise returns the path to the existing temp file."""
|
||||
|
@ -10,6 +10,7 @@ import mimetypes
|
||||
import os
|
||||
import posixpath
|
||||
import secrets
|
||||
import tempfile
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
@ -21,7 +22,7 @@ import httpx
|
||||
import markupsafe
|
||||
import orjson
|
||||
import pkg_resources
|
||||
from fastapi import Depends, FastAPI, HTTPException, WebSocket, status
|
||||
from fastapi import Depends, FastAPI, File, HTTPException, UploadFile, WebSocket, status
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import (
|
||||
FileResponse,
|
||||
@ -43,6 +44,7 @@ from gradio.context import Context
|
||||
from gradio.data_classes import PredictBody, ResetBody
|
||||
from gradio.documentation import document, set_documentation_group
|
||||
from gradio.exceptions import Error
|
||||
from gradio.processing_utils import TempFileManager
|
||||
from gradio.queueing import Estimation, Event
|
||||
from gradio.utils import cancel_tasks, run_coro_in_background, set_task_name
|
||||
|
||||
@ -109,6 +111,7 @@ class App(FastAPI):
|
||||
self.lock = asyncio.Lock()
|
||||
self.queue_token = secrets.token_urlsafe(32)
|
||||
self.startup_events_triggered = False
|
||||
self.uploaded_file_dir = str(utils.abspath(tempfile.mkdtemp()))
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def configure_app(self, blocks: gradio.Blocks) -> None:
|
||||
@ -297,17 +300,19 @@ class App(FastAPI):
|
||||
)
|
||||
abs_path = str(utils.abspath(path_or_url))
|
||||
in_app_dir = utils.abspath(app.cwd) in utils.abspath(path_or_url).parents
|
||||
created_by_app = str(utils.abspath(path_or_url)) in set().union(
|
||||
*blocks.temp_file_sets
|
||||
)
|
||||
created_by_app = abs_path in set().union(*blocks.temp_file_sets)
|
||||
in_file_dir = any(
|
||||
(
|
||||
utils.abspath(dir) in utils.abspath(path_or_url).parents
|
||||
for dir in blocks.file_directories
|
||||
)
|
||||
)
|
||||
was_uploaded = (
|
||||
utils.abspath(app.uploaded_file_dir)
|
||||
in utils.abspath(path_or_url).parents
|
||||
)
|
||||
|
||||
if in_app_dir or created_by_app or in_file_dir:
|
||||
if in_app_dir or created_by_app or in_file_dir or was_uploaded:
|
||||
range_val = request.headers.get("Range", "").strip()
|
||||
if range_val.startswith("bytes=") and "-" in range_val:
|
||||
range_val = range_val[6:]
|
||||
@ -514,6 +519,20 @@ class App(FastAPI):
|
||||
async def get_queue_status():
|
||||
return app.get_blocks()._queue.get_estimation()
|
||||
|
||||
@app.post("/upload", dependencies=[Depends(login_check)])
|
||||
async def upload_file(
|
||||
files: List[UploadFile] = File(...),
|
||||
):
|
||||
output_files = []
|
||||
file_manager = TempFileManager()
|
||||
for input_file in files:
|
||||
output_files.append(
|
||||
await file_manager.save_uploaded_file(
|
||||
input_file, app.uploaded_file_dir
|
||||
)
|
||||
)
|
||||
return output_files
|
||||
|
||||
@app.on_event("startup")
|
||||
@app.get("/startup-events")
|
||||
async def startup_events():
|
||||
|
1
test/test_files/alphabet.txt
Normal file
1
test/test_files/alphabet.txt
Normal file
@ -0,0 +1 @@
|
||||
abcdefghijklmnopqrstuvwxyz
|
@ -54,6 +54,17 @@ class TestRoutes:
|
||||
response = test_client.get("/config/")
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_upload_route(self, test_client):
|
||||
response = test_client.post(
|
||||
"/upload", files={"files": open("test/test_files/alphabet.txt", "r")}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
file = response.json()[0]
|
||||
assert "alphabet" in file
|
||||
assert file.endswith(".txt")
|
||||
with open(file) as saved_file:
|
||||
assert saved_file.read() == "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
def test_predict_route(self, test_client):
|
||||
response = test_client.post(
|
||||
"/api/predict/", json={"data": ["test"], "fn_index": 0}
|
||||
|
@ -36,6 +36,10 @@ interface PostResponse {
|
||||
error?: string;
|
||||
[x: string]: any;
|
||||
}
|
||||
export interface UploadResponse {
|
||||
error?: string;
|
||||
files?: Array<string>;
|
||||
}
|
||||
const QUEUE_FULL_MSG = "This application is too busy. Keep trying!";
|
||||
const BROKEN_CONNECTION_MSG = "Connection errored out.";
|
||||
|
||||
@ -55,6 +59,27 @@ export async function post_data(
|
||||
const output: PostResponse = await response.json();
|
||||
return [output, response.status];
|
||||
}
|
||||
|
||||
export async function upload_files(
|
||||
root: string,
|
||||
files: Array<File>
|
||||
): Promise<UploadResponse> {
|
||||
const formData = new FormData();
|
||||
files.forEach((file) => {
|
||||
formData.append("files", file);
|
||||
});
|
||||
try {
|
||||
var response = await fetch(`${root}upload`, {
|
||||
method: "POST",
|
||||
body: formData
|
||||
});
|
||||
} catch (e) {
|
||||
return { error: BROKEN_CONNECTION_MSG };
|
||||
}
|
||||
const output: UploadResponse["files"] = await response.json();
|
||||
return { files: output };
|
||||
}
|
||||
|
||||
interface UpdateOutput {
|
||||
__type__: string;
|
||||
[key: string]: unknown;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { FileData } from "../../File/types";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
|
||||
export let value: FileData;
|
||||
export let type: "gallery" | "table";
|
||||
|
@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { File, FileUpload } from "@gradio/file";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { File as FileComponent, FileUpload } from "@gradio/file";
|
||||
import { blobToBase64, FileData } from "@gradio/upload";
|
||||
import { normalise_file } from "@gradio/upload";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import UploadText from "../UploadText.svelte";
|
||||
import { upload_files } from "../../api";
|
||||
|
||||
import StatusTracker from "../StatusTracker/StatusTracker.svelte";
|
||||
import type { LoadingStatus } from "../StatusTracker/types";
|
||||
@ -26,20 +27,56 @@
|
||||
|
||||
export let loading_status: LoadingStatus;
|
||||
|
||||
let _value: null | FileData | Array<FileData>;
|
||||
$: _value = normalise_file(value, root, root_url);
|
||||
|
||||
let dragging = false;
|
||||
let pending_upload = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: undefined;
|
||||
error: string;
|
||||
}>();
|
||||
|
||||
$: {
|
||||
if (value !== old_value) {
|
||||
old_value = value;
|
||||
if (_value !== old_value) {
|
||||
old_value = _value;
|
||||
if (_value === null) {
|
||||
dispatch("change");
|
||||
pending_upload = false;
|
||||
} else if (mode === "dynamic") {
|
||||
let files = (Array.isArray(_value) ? _value : [_value]).map(
|
||||
(file_data) => file_data.blob!
|
||||
);
|
||||
let upload_value = _value;
|
||||
pending_upload = true;
|
||||
upload_files(root, files).then((response) => {
|
||||
if (upload_value !== _value) {
|
||||
// value has changed since upload started
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch("change");
|
||||
pending_upload = false;
|
||||
if (response.error) {
|
||||
(Array.isArray(_value) ? _value : [_value]).forEach(
|
||||
async (file_data, i) => {
|
||||
file_data.data = await blobToBase64(file_data.blob!);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
(Array.isArray(_value) ? _value : [_value]).forEach(
|
||||
(file_data, i) => {
|
||||
if (response.files) {
|
||||
file_data.orig_name = file_data.name;
|
||||
file_data.name = response.files[i];
|
||||
file_data.is_file = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
_value = normalise_file(value, root, root_url);
|
||||
}
|
||||
dispatch("change");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -51,7 +88,12 @@
|
||||
padding={false}
|
||||
{elem_id}
|
||||
>
|
||||
<StatusTracker {...loading_status} />
|
||||
<StatusTracker
|
||||
{...loading_status}
|
||||
status={pending_upload
|
||||
? "generating"
|
||||
: loading_status?.status || "complete"}
|
||||
/>
|
||||
|
||||
{#if mode === "dynamic"}
|
||||
<FileUpload
|
||||
@ -68,6 +110,6 @@
|
||||
<UploadText type="file" />
|
||||
</FileUpload>
|
||||
{:else}
|
||||
<File value={_value} {label} {show_label} />
|
||||
<FileComponent value={_value} {label} {show_label} />
|
||||
{/if}
|
||||
</Block>
|
||||
|
@ -1,6 +0,0 @@
|
||||
export interface FileData {
|
||||
name: string;
|
||||
size: number;
|
||||
data: string;
|
||||
is_example: false;
|
||||
}
|
@ -3,21 +3,34 @@
|
||||
import type { Styles } from "@gradio/utils";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { UploadButton } from "@gradio/upload-button";
|
||||
import { upload_files } from "../../api";
|
||||
import { blobToBase64 } from "@gradio/upload";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
export let style: Styles = {};
|
||||
export let elem_id: string = "";
|
||||
export let visible: boolean = true;
|
||||
export let label: string;
|
||||
export let value: null | FileData | Array<FileData>;
|
||||
export let value: null | FileData;
|
||||
export let file_count: string;
|
||||
export let file_types: Array<string> = ["file"];
|
||||
export let root: string;
|
||||
|
||||
async function handle_upload({ detail }: CustomEvent<FileData>) {
|
||||
value = detail;
|
||||
console.log(detail);
|
||||
await tick();
|
||||
dispatch("change", value);
|
||||
dispatch("upload", detail);
|
||||
upload_files(root, [detail.blob!]).then(async (response) => {
|
||||
if (response.error) {
|
||||
detail.data = await blobToBase64(detail.blob!);
|
||||
} else {
|
||||
detail.orig_name = detail.name;
|
||||
detail.name = response.files![0];
|
||||
detail.is_file = true;
|
||||
}
|
||||
dispatch("change", value);
|
||||
dispatch("upload", detail);
|
||||
});
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
|
@ -21,6 +21,6 @@
|
||||
|
||||
<style>
|
||||
.file-preview {
|
||||
overflow-x: scroll;
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
|
@ -2,11 +2,7 @@
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { Download } from "@gradio/icons";
|
||||
import { IconButton } from "@gradio/atoms";
|
||||
import {
|
||||
display_file_name,
|
||||
download_files,
|
||||
display_file_size
|
||||
} from "./utils";
|
||||
import { display_file_name, display_file_size } from "./utils";
|
||||
|
||||
export let value: FileData | FileData[];
|
||||
</script>
|
||||
@ -24,13 +20,19 @@
|
||||
</td>
|
||||
|
||||
<td class="download">
|
||||
<a
|
||||
href={download_files(file)}
|
||||
target={window.__is_colab__ ? "_blank" : null}
|
||||
download={window.__is_colab__ ? null : file.orig_name || file.name}
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
{#if file.data}
|
||||
<a
|
||||
href={file.data}
|
||||
target="_blank"
|
||||
download={window.__is_colab__
|
||||
? null
|
||||
: file.orig_name || file.name}
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
{:else}
|
||||
Uploading...
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
@ -52,7 +54,7 @@
|
||||
margin-bottom: var(--size-7);
|
||||
width: var(--size-full);
|
||||
max-height: var(--size-60);
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
color: var(--color-text-body);
|
||||
}
|
||||
.file {
|
||||
|
@ -14,7 +14,9 @@
|
||||
export let file_count: string = "single";
|
||||
export let file_types: string[] | null = null;
|
||||
|
||||
async function handle_upload({ detail }: CustomEvent<FileData>) {
|
||||
async function handle_upload({
|
||||
detail
|
||||
}: CustomEvent<FileData | Array<FileData>>) {
|
||||
value = detail;
|
||||
await tick();
|
||||
dispatch("change", value);
|
||||
@ -28,10 +30,10 @@
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: FileData | null;
|
||||
change: Array<FileData> | FileData | null;
|
||||
clear: undefined;
|
||||
drag: boolean;
|
||||
upload: FileData;
|
||||
upload: Array<FileData> | FileData;
|
||||
error: string;
|
||||
}>();
|
||||
|
||||
@ -55,16 +57,17 @@
|
||||
|
||||
<BlockLabel {show_label} Icon={File} label={label || "File"} />
|
||||
|
||||
{#if value === null}
|
||||
{#if value}
|
||||
<ModifyUpload on:clear={handle_clear} absolute />
|
||||
<FilePreview {value} />
|
||||
{:else}
|
||||
<Upload
|
||||
on:load={handle_upload}
|
||||
filetype={accept_file_types}
|
||||
parse_to_data_url={false}
|
||||
{file_count}
|
||||
bind:dragging
|
||||
>
|
||||
<slot />
|
||||
</Upload>
|
||||
{:else}
|
||||
<ModifyUpload on:clear={handle_clear} absolute />
|
||||
<FilePreview {value} />
|
||||
{/if}
|
||||
|
@ -19,10 +19,6 @@ export const display_file_name = (value: FileData): string => {
|
||||
} else return str;
|
||||
};
|
||||
|
||||
export const download_files = (value: FileData): string => {
|
||||
return value.data;
|
||||
};
|
||||
|
||||
export const display_file_size = (
|
||||
value: FileData | Array<FileData>
|
||||
): string => {
|
||||
|
@ -2,7 +2,6 @@
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { BlockLabel, IconButton } from "@gradio/atoms";
|
||||
import { File, Download } from "@gradio/icons";
|
||||
import { download_files } from "./utils";
|
||||
|
||||
export let value: FileData | null;
|
||||
export let clearColor: Array<number> = [0, 0, 0, 0];
|
||||
@ -81,7 +80,7 @@
|
||||
<div class="model3D">
|
||||
<div class="download">
|
||||
<a
|
||||
href={download_files(value)}
|
||||
href={value.data}
|
||||
target={window.__is_colab__ ? "_blank" : null}
|
||||
download={window.__is_colab__ ? null : value.orig_name || value.name}
|
||||
>
|
||||
|
@ -19,10 +19,6 @@ export const display_file_name = (value: FileData): string => {
|
||||
} else return str;
|
||||
};
|
||||
|
||||
export const download_files = (value: FileData): string => {
|
||||
return value.data;
|
||||
};
|
||||
|
||||
export const display_file_size = (
|
||||
value: FileData | Array<FileData>
|
||||
): string => {
|
||||
|
@ -9,6 +9,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@gradio/button": "workspace:^0.0.1",
|
||||
"@gradio/utils": "workspace:^0.0.1"
|
||||
"@gradio/utils": "workspace:^0.0.1",
|
||||
"@gradio/upload": "workspace:^0.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import type { Styles } from "@gradio/utils";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { FileData } from "./types";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { type } from "@testing-library/user-event/dist/type";
|
||||
|
||||
export let style: Styles = {};
|
||||
@ -35,33 +35,30 @@
|
||||
|
||||
const loadFiles = (files: FileList) => {
|
||||
let _files: Array<File> = Array.from(files);
|
||||
if (!files.length || !window.FileReader) {
|
||||
if (!files.length) {
|
||||
return;
|
||||
}
|
||||
if (file_count === "single") {
|
||||
_files = [files[0]];
|
||||
}
|
||||
var all_file_data: Array<FileData | string> = [];
|
||||
var all_file_data: Array<FileData | File> = [];
|
||||
_files.forEach((f, i) => {
|
||||
let ReaderObj = new FileReader();
|
||||
ReaderObj.readAsDataURL(f);
|
||||
ReaderObj.onloadend = function () {
|
||||
all_file_data[i] = include_file_metadata
|
||||
? {
|
||||
name: f.name,
|
||||
size: f.size,
|
||||
data: this.result as string
|
||||
}
|
||||
: (this.result as string);
|
||||
if (
|
||||
all_file_data.filter((x) => x !== undefined).length === files.length
|
||||
) {
|
||||
dispatch(
|
||||
"load",
|
||||
file_count == "single" ? all_file_data[0] : all_file_data
|
||||
);
|
||||
}
|
||||
};
|
||||
all_file_data[i] = include_file_metadata
|
||||
? {
|
||||
name: f.name,
|
||||
size: f.size,
|
||||
data: "",
|
||||
blob: f
|
||||
}
|
||||
: f;
|
||||
if (
|
||||
all_file_data.filter((x) => x !== undefined).length === files.length
|
||||
) {
|
||||
dispatch(
|
||||
"load",
|
||||
file_count == "single" ? all_file_data[0] : all_file_data
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
export interface FileData {
|
||||
name: string;
|
||||
orig_name?: string;
|
||||
size?: number;
|
||||
data: string;
|
||||
is_file?: boolean;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { FileData } from "./types";
|
||||
import { blobToBase64 } from "./utils";
|
||||
|
||||
export let filetype: string | null = null;
|
||||
export let include_file_metadata = true;
|
||||
@ -10,6 +11,7 @@
|
||||
export let flex: boolean = true;
|
||||
export let file_count: string = "single";
|
||||
export let disable_click = false;
|
||||
export let parse_to_data_url = true;
|
||||
|
||||
let hidden_upload: HTMLInputElement;
|
||||
|
||||
@ -25,7 +27,7 @@
|
||||
hidden_upload.click();
|
||||
};
|
||||
|
||||
const loadFiles = (files: FileList) => {
|
||||
const loadFiles = async (files: FileList) => {
|
||||
let _files: Array<File> = Array.from(files);
|
||||
if (!files.length || !window.FileReader) {
|
||||
return;
|
||||
@ -33,41 +35,55 @@
|
||||
if (file_count === "single") {
|
||||
_files = [files[0]];
|
||||
}
|
||||
var all_file_data: Array<FileData | string> = [];
|
||||
_files.forEach((f, i) => {
|
||||
let ReaderObj = new FileReader();
|
||||
ReaderObj.readAsDataURL(f);
|
||||
ReaderObj.onloadend = function () {
|
||||
all_file_data[i] = include_file_metadata
|
||||
? {
|
||||
name: f.name,
|
||||
size: f.size,
|
||||
data: this.result as string
|
||||
}
|
||||
: (this.result as string);
|
||||
if (
|
||||
all_file_data.filter((x) => x !== undefined).length === files.length
|
||||
) {
|
||||
dispatch(
|
||||
"load",
|
||||
file_count == "single" ? all_file_data[0] : all_file_data
|
||||
);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (include_file_metadata) {
|
||||
var file_metadata: Array<{ name: string; size: number }> = _files.map(
|
||||
(f) => ({
|
||||
name: f.name,
|
||||
size: f.size
|
||||
})
|
||||
);
|
||||
}
|
||||
var load_file_data = [];
|
||||
var file_data: Array<string> | Array<File> = [];
|
||||
if (parse_to_data_url) {
|
||||
file_data = await Promise.all(_files.map((f) => blobToBase64(f)));
|
||||
} else {
|
||||
file_data = _files;
|
||||
}
|
||||
if (include_file_metadata) {
|
||||
if (parse_to_data_url) {
|
||||
load_file_data = file_data.map((data, i) => ({
|
||||
data,
|
||||
...file_metadata[i]
|
||||
}));
|
||||
} else {
|
||||
load_file_data = file_data.map((data, i) => ({
|
||||
data: "",
|
||||
blob: data,
|
||||
...file_metadata[i]
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
load_file_data = file_data;
|
||||
}
|
||||
dispatch(
|
||||
"load",
|
||||
file_count === "single" ? load_file_data[0] : load_file_data
|
||||
);
|
||||
};
|
||||
|
||||
const loadFilesFromUpload = (e: Event) => {
|
||||
const loadFilesFromUpload = async (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
||||
if (!target.files) return;
|
||||
loadFiles(target.files);
|
||||
await loadFiles(target.files);
|
||||
};
|
||||
|
||||
const loadFilesFromDrop = (e: DragEvent) => {
|
||||
const loadFilesFromDrop = async (e: DragEvent) => {
|
||||
dragging = false;
|
||||
if (!e.dataTransfer?.files) return;
|
||||
loadFiles(e.dataTransfer.files);
|
||||
await loadFiles(e.dataTransfer.files);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -3,5 +3,6 @@ export interface FileData {
|
||||
orig_name?: string;
|
||||
size?: number;
|
||||
data: string;
|
||||
blob?: File;
|
||||
is_file?: boolean;
|
||||
}
|
||||
|
@ -35,3 +35,13 @@ export function normalise_file(
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
export const blobToBase64 = (blob: File): Promise<string> => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(blob);
|
||||
return new Promise((resolve) => {
|
||||
reader.onloadend = () => {
|
||||
resolve(reader.result as string);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
107
ui/pnpm-lock.yaml
generated
107
ui/pnpm-lock.yaml
generated
@ -1,4 +1,4 @@
|
||||
lockfileVersion: 5.4
|
||||
lockfileVersion: 5.3
|
||||
|
||||
importers:
|
||||
|
||||
@ -48,7 +48,7 @@ importers:
|
||||
'@tailwindcss/forms': 0.5.0_tailwindcss@3.1.6
|
||||
'@testing-library/dom': 8.11.3
|
||||
'@testing-library/svelte': 3.1.0_svelte@3.49.0
|
||||
'@testing-library/user-event': 13.5.0_gzufz4q333be4gqfrvipwvqt6a
|
||||
'@testing-library/user-event': 13.5.0_@testing-library+dom@8.11.3
|
||||
autoprefixer: 10.4.4_postcss@8.4.6
|
||||
babylonjs: 5.18.0
|
||||
babylonjs-loaders: 5.18.0
|
||||
@ -65,15 +65,15 @@ importers:
|
||||
postcss-nested: 5.0.6_postcss@8.4.6
|
||||
postcss-prefix-selector: 1.16.0_postcss@8.4.6
|
||||
prettier: 2.6.2
|
||||
prettier-plugin-css-order: 1.3.0_ob5okuz2s5mlecytbeo2erc43a
|
||||
prettier-plugin-svelte: 2.7.0_3cyj5wbackxvw67rnaarcmbw7y
|
||||
prettier-plugin-css-order: 1.3.0_postcss@8.4.6+prettier@2.6.2
|
||||
prettier-plugin-svelte: 2.7.0_prettier@2.6.2+svelte@3.49.0
|
||||
sirv: 2.0.2
|
||||
sirv-cli: 2.0.2
|
||||
svelte: 3.49.0
|
||||
svelte-check: 2.8.0_mgmdnb6x5rpawk37gozc2sbtta
|
||||
svelte-check: 2.8.0_postcss@8.4.6+svelte@3.49.0
|
||||
svelte-i18n: 3.3.13_svelte@3.49.0
|
||||
svelte-preprocess: 4.10.6_mlkquajfpxs65rn6bdfntu7nmy
|
||||
tailwindcss: 3.1.6_postcss@8.4.6
|
||||
svelte-preprocess: 4.10.6_62d50a01257de5eec5be08cad9d3ed66
|
||||
tailwindcss: 3.1.6
|
||||
tinyspy: 0.3.0
|
||||
typescript: 4.7.4
|
||||
vite: 2.9.9
|
||||
@ -373,9 +373,11 @@ importers:
|
||||
packages/upload-button:
|
||||
specifiers:
|
||||
'@gradio/button': workspace:^0.0.1
|
||||
'@gradio/upload': workspace:^0.0.1
|
||||
'@gradio/utils': workspace:^0.0.1
|
||||
dependencies:
|
||||
'@gradio/button': link:../button
|
||||
'@gradio/upload': link:../upload
|
||||
'@gradio/utils': link:../utils
|
||||
|
||||
packages/utils:
|
||||
@ -725,7 +727,7 @@ packages:
|
||||
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
|
||||
dependencies:
|
||||
mini-svg-data-uri: 1.4.4
|
||||
tailwindcss: 3.1.6_postcss@8.4.6
|
||||
tailwindcss: 3.1.6
|
||||
dev: false
|
||||
|
||||
/@testing-library/dom/7.31.2:
|
||||
@ -766,7 +768,7 @@ packages:
|
||||
svelte: 3.49.0
|
||||
dev: false
|
||||
|
||||
/@testing-library/user-event/13.5.0_gzufz4q333be4gqfrvipwvqt6a:
|
||||
/@testing-library/user-event/13.5.0_@testing-library+dom@8.11.3:
|
||||
resolution: {integrity: sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==}
|
||||
engines: {node: '>=10', npm: '>=6'}
|
||||
peerDependencies:
|
||||
@ -2981,19 +2983,6 @@ packages:
|
||||
postcss-value-parser: 4.2.0
|
||||
read-cache: 1.0.0
|
||||
resolve: 1.22.1
|
||||
dev: true
|
||||
|
||||
/postcss-import/14.1.0_postcss@8.4.6:
|
||||
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
postcss: ^8.0.0
|
||||
dependencies:
|
||||
postcss: 8.4.6
|
||||
postcss-value-parser: 4.2.0
|
||||
read-cache: 1.0.0
|
||||
resolve: 1.22.1
|
||||
dev: false
|
||||
|
||||
/postcss-js/4.0.0_postcss@8.4.21:
|
||||
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
|
||||
@ -3003,17 +2992,6 @@ packages:
|
||||
dependencies:
|
||||
camelcase-css: 2.0.1
|
||||
postcss: 8.4.21
|
||||
dev: true
|
||||
|
||||
/postcss-js/4.0.0_postcss@8.4.6:
|
||||
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
|
||||
engines: {node: ^12 || ^14 || >= 16}
|
||||
peerDependencies:
|
||||
postcss: ^8.3.3
|
||||
dependencies:
|
||||
camelcase-css: 2.0.1
|
||||
postcss: 8.4.6
|
||||
dev: false
|
||||
|
||||
/postcss-less/6.0.0_postcss@8.4.6:
|
||||
resolution: {integrity: sha512-FPX16mQLyEjLzEuuJtxA8X3ejDLNGGEG503d2YGZR5Ask1SpDN8KmZUMpzCvyalWRywAn1n1VOA5dcqfCLo5rg==}
|
||||
@ -3052,24 +3030,6 @@ packages:
|
||||
lilconfig: 2.0.6
|
||||
postcss: 8.4.21
|
||||
yaml: 1.10.2
|
||||
dev: true
|
||||
|
||||
/postcss-load-config/3.1.4_postcss@8.4.6:
|
||||
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
|
||||
engines: {node: '>= 10'}
|
||||
peerDependencies:
|
||||
postcss: '>=8.0.9'
|
||||
ts-node: '>=9.0.0'
|
||||
peerDependenciesMeta:
|
||||
postcss:
|
||||
optional: true
|
||||
ts-node:
|
||||
optional: true
|
||||
dependencies:
|
||||
lilconfig: 2.0.6
|
||||
postcss: 8.4.6
|
||||
yaml: 1.10.2
|
||||
dev: false
|
||||
|
||||
/postcss-nested/5.0.6_postcss@8.4.21:
|
||||
resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==}
|
||||
@ -3079,7 +3039,6 @@ packages:
|
||||
dependencies:
|
||||
postcss: 8.4.21
|
||||
postcss-selector-parser: 6.0.9
|
||||
dev: true
|
||||
|
||||
/postcss-nested/5.0.6_postcss@8.4.6:
|
||||
resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==}
|
||||
@ -3157,7 +3116,7 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
|
||||
/prettier-plugin-css-order/1.3.0_ob5okuz2s5mlecytbeo2erc43a:
|
||||
/prettier-plugin-css-order/1.3.0_postcss@8.4.6+prettier@2.6.2:
|
||||
resolution: {integrity: sha512-wOS4qlbUARCoiiuOG0TiB/j751soC3+gUnMMva5HVWKvHJdLNYqh+jXK3MvvixR6xkJVPxHSF7rIIhkHIuHTFg==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
@ -3172,7 +3131,7 @@ packages:
|
||||
- postcss
|
||||
dev: false
|
||||
|
||||
/prettier-plugin-svelte/2.7.0_3cyj5wbackxvw67rnaarcmbw7y:
|
||||
/prettier-plugin-svelte/2.7.0_prettier@2.6.2+svelte@3.49.0:
|
||||
resolution: {integrity: sha512-fQhhZICprZot2IqEyoiUYLTRdumULGRvw0o4dzl5jt0jfzVWdGqeYW27QTWAeXhoupEZJULmNoH3ueJwUWFLIA==}
|
||||
peerDependencies:
|
||||
prettier: ^1.16.4 || ^2.0.0
|
||||
@ -3659,7 +3618,7 @@ packages:
|
||||
- sugarss
|
||||
dev: true
|
||||
|
||||
/svelte-check/2.8.0_mgmdnb6x5rpawk37gozc2sbtta:
|
||||
/svelte-check/2.8.0_postcss@8.4.6+svelte@3.49.0:
|
||||
resolution: {integrity: sha512-HRL66BxffMAZusqe5I5k26mRWQ+BobGd9Rxm3onh7ZVu0nTk8YTKJ9vu3LVPjUGLU9IX7zS+jmwPVhJYdXJ8vg==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@ -3672,7 +3631,7 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
sade: 1.8.1
|
||||
svelte: 3.49.0
|
||||
svelte-preprocess: 4.10.6_mlkquajfpxs65rn6bdfntu7nmy
|
||||
svelte-preprocess: 4.10.6_62d50a01257de5eec5be08cad9d3ed66
|
||||
typescript: 4.7.4
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@ -3763,7 +3722,7 @@ packages:
|
||||
typescript: 4.5.5
|
||||
dev: true
|
||||
|
||||
/svelte-preprocess/4.10.6_mlkquajfpxs65rn6bdfntu7nmy:
|
||||
/svelte-preprocess/4.10.6_62d50a01257de5eec5be08cad9d3ed66:
|
||||
resolution: {integrity: sha512-I2SV1w/AveMvgIQlUF/ZOO3PYVnhxfcpNyGt8pxpUVhPfyfL/CZBkkw/KPfuFix5FJ9TnnNYMhACK3DtSaYVVQ==}
|
||||
engines: {node: '>= 9.11.2'}
|
||||
requiresBuild: true
|
||||
@ -3883,40 +3842,6 @@ packages:
|
||||
resolve: 1.22.1
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/tailwindcss/3.1.6_postcss@8.4.6:
|
||||
resolution: {integrity: sha512-7skAOY56erZAFQssT1xkpk+kWt2NrO45kORlxFPXUt3CiGsVPhH1smuH5XoDH6sGPXLyBv+zgCKA2HWBsgCytg==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
postcss: ^8.0.9
|
||||
dependencies:
|
||||
arg: 5.0.2
|
||||
chokidar: 3.5.3
|
||||
color-name: 1.1.4
|
||||
detective: 5.2.1
|
||||
didyoumean: 1.2.2
|
||||
dlv: 1.1.3
|
||||
fast-glob: 3.2.11
|
||||
glob-parent: 6.0.2
|
||||
is-glob: 4.0.3
|
||||
lilconfig: 2.0.6
|
||||
normalize-path: 3.0.0
|
||||
object-hash: 3.0.0
|
||||
picocolors: 1.0.0
|
||||
postcss: 8.4.6
|
||||
postcss-import: 14.1.0_postcss@8.4.6
|
||||
postcss-js: 4.0.0_postcss@8.4.6
|
||||
postcss-load-config: 3.1.4_postcss@8.4.6
|
||||
postcss-nested: 5.0.6_postcss@8.4.6
|
||||
postcss-selector-parser: 6.0.10
|
||||
postcss-value-parser: 4.2.0
|
||||
quick-lru: 5.1.1
|
||||
resolve: 1.22.1
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
/then-request/6.0.2:
|
||||
resolution: {integrity: sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==}
|
||||
|
Loading…
x
Reference in New Issue
Block a user