diff --git a/.changeset/shiny-news-float.md b/.changeset/shiny-news-float.md
new file mode 100644
index 000000000000..62e125ee5a37
--- /dev/null
+++ b/.changeset/shiny-news-float.md
@@ -0,0 +1,13 @@
+---
+"@gradio/app": minor
+"@gradio/audio": minor
+"@gradio/client": minor
+"@gradio/code": minor
+"@gradio/file": minor
+"@gradio/image": minor
+"@gradio/video": minor
+"@gradio/wasm": minor
+"gradio": minor
+---
+
+feat:Lite v4
diff --git a/client/js/src/client.ts b/client/js/src/client.ts
index 7ff732df7893..2135324f76b5 100644
--- a/client/js/src/client.ts
+++ b/client/js/src/client.ts
@@ -178,7 +178,7 @@ interface Client {
export function api_factory(
fetch_implementation: typeof fetch,
- WebSocket_factory: (url: URL) => WebSocket
+ EventSource_factory: (url: URL) => EventSource
): Client {
return { post_data, upload_files, client, handle_blob };
@@ -546,7 +546,7 @@ export function api_factory(
url.searchParams.set("__sign", jwt);
}
- websocket = WebSocket_factory(url);
+ websocket = new WebSocket(url);
websocket.onclose = (evt) => {
if (!evt.wasClean) {
@@ -667,7 +667,7 @@ export function api_factory(
)}/queue/join?${url_params ? url_params + "&" : ""}${params}`
);
- eventSource = new EventSource(url);
+ eventSource = EventSource_factory(url);
eventSource.onmessage = async function (event) {
const _data = JSON.parse(event.data);
@@ -1007,7 +1007,7 @@ export function api_factory(
export const { post_data, upload_files, client, handle_blob } = api_factory(
fetch,
- (...args) => new WebSocket(...args)
+ (...args) => new EventSource(...args)
);
function transform_output(
diff --git a/gradio/components/video.py b/gradio/components/video.py
index 273a32b548bc..fc66320ada10 100644
--- a/gradio/components/video.py
+++ b/gradio/components/video.py
@@ -165,16 +165,20 @@ def preprocess(self, payload: VideoData | None) -> str | None:
uploaded_format = file_name.suffix.replace(".", "")
needs_formatting = self.format is not None and uploaded_format != self.format
flip = self.sources == ["webcam"] and self.mirror_webcam
- duration = processing_utils.get_video_length(file_name)
- if self.min_length is not None and duration < self.min_length:
- raise gr.Error(
- f"Video is too short, and must be at least {self.min_length} seconds"
- )
- if self.max_length is not None and duration > self.max_length:
- raise gr.Error(
- f"Video is too long, and must be at most {self.max_length} seconds"
- )
+ if self.min_length is not None or self.max_length is not None:
+ # With this if-clause, avoid unnecessary execution of `processing_utils.get_video_length`.
+ # This is necessary for the Wasm-mode, because it uses ffprobe, which is not available in the browser.
+ duration = processing_utils.get_video_length(file_name)
+ if self.min_length is not None and duration < self.min_length:
+ raise gr.Error(
+ f"Video is too short, and must be at least {self.min_length} seconds"
+ )
+ if self.max_length is not None and duration > self.max_length:
+ raise gr.Error(
+ f"Video is too long, and must be at most {self.max_length} seconds"
+ )
+
if needs_formatting or flip:
format = f".{self.format if needs_formatting else uploaded_format}"
output_options = ["-vf", "hflip", "-c:a", "copy"] if flip else []
diff --git a/gradio/data_classes.py b/gradio/data_classes.py
index 86b89408eb88..8a87f7ac5a3c 100644
--- a/gradio/data_classes.py
+++ b/gradio/data_classes.py
@@ -11,9 +11,56 @@
from fastapi import Request
from gradio_client.utils import traverse
-from pydantic import BaseModel, RootModel, ValidationError
from typing_extensions import Literal
+from . import wasm_utils
+
+if not wasm_utils.IS_WASM:
+ from pydantic import BaseModel, RootModel, ValidationError # type: ignore
+else:
+ # XXX: Currently Pyodide V2 is not available on Pyodide,
+ # so we install V1 for the Wasm version.
+ from typing import Generic, TypeVar
+
+ from pydantic import BaseModel as BaseModelV1
+ from pydantic import ValidationError, schema_of
+
+ # Map V2 method calls to V1 implementations.
+ # Ref: https://docs.pydantic.dev/latest/migration/#changes-to-pydanticbasemodel
+ class BaseModel(BaseModelV1):
+ pass
+
+ BaseModel.model_dump = BaseModel.dict # type: ignore
+ BaseModel.model_json_schema = BaseModel.schema # type: ignore
+
+ # RootModel is not available in V1, so we create a dummy class.
+ PydanticUndefined = object()
+ RootModelRootType = TypeVar("RootModelRootType")
+
+ class RootModel(BaseModel, Generic[RootModelRootType]):
+ root: RootModelRootType
+
+ def __init__(self, root: RootModelRootType = PydanticUndefined, **data):
+ if data:
+ if root is not PydanticUndefined:
+ raise ValueError(
+ '"RootModel.__init__" accepts either a single positional argument or arbitrary keyword arguments'
+ )
+ root = data # type: ignore
+ # XXX: No runtime validation is executed.
+ super().__init__(root=root) # type: ignore
+
+ def dict(self, **kwargs):
+ return super().dict(**kwargs)["root"]
+
+ @classmethod
+ def schema(cls, **kwargs):
+ # XXX: kwargs are ignored.
+ return schema_of(cls.__fields__["root"].type_) # type: ignore
+
+ RootModel.model_dump = RootModel.dict # type: ignore
+ RootModel.model_json_schema = RootModel.schema # type: ignore
+
class PredictBody(BaseModel):
class Config:
diff --git a/gradio/processing_utils.py b/gradio/processing_utils.py
index e46318740989..35bdcd19f925 100644
--- a/gradio/processing_utils.py
+++ b/gradio/processing_utils.py
@@ -712,6 +712,10 @@ def convert_video_to_playable_mp4(video_path: str) -> str:
def get_video_length(video_path: str | Path):
+ if wasm_utils.IS_WASM:
+ raise wasm_utils.WasmUnsupportedError(
+ "Video duration is not supported in the Wasm mode."
+ )
duration = subprocess.check_output(
[
"ffprobe",
diff --git a/js/app/src/lite/index.ts b/js/app/src/lite/index.ts
index ce9f682e0d2a..163123c4fe20 100644
--- a/js/app/src/lite/index.ts
+++ b/js/app/src/lite/index.ts
@@ -1,9 +1,12 @@
-import "@gradio/theme";
+import "@gradio/theme/src/reset.css";
+import "@gradio/theme/src/global.css";
+import "@gradio/theme/src/pollen.css";
+import "@gradio/theme/src/typography.css";
import type { SvelteComponent } from "svelte";
import { WorkerProxy, type WorkerProxyOptions } from "@gradio/wasm";
import { api_factory } from "@gradio/client";
import { wasm_proxied_fetch } from "./fetch";
-import { wasm_proxied_WebSocket_factory } from "./websocket";
+import { wasm_proxied_EventSource_factory } from "./sse";
import { wasm_proxied_mount_css, mount_prebuilt_css } from "./css";
import type { mount_css } from "../css";
import Index from "../Index.svelte";
@@ -101,12 +104,12 @@ export function create(options: Options): GradioAppController {
const overridden_fetch: typeof fetch = (input, init?) => {
return wasm_proxied_fetch(worker_proxy, input, init);
};
- const WebSocket_factory = (url: URL): WebSocket => {
- return wasm_proxied_WebSocket_factory(worker_proxy, url);
+ const EventSource_factory = (url: URL): EventSource => {
+ return wasm_proxied_EventSource_factory(worker_proxy, url);
};
const { client, upload_files } = api_factory(
overridden_fetch,
- WebSocket_factory
+ EventSource_factory
);
const overridden_mount_css: typeof mount_css = async (url, target) => {
return wasm_proxied_mount_css(worker_proxy, url, target);
diff --git a/js/app/src/lite/websocket.ts b/js/app/src/lite/sse.ts
similarity index 56%
rename from js/app/src/lite/websocket.ts
rename to js/app/src/lite/sse.ts
index 0e97d5b96633..7b2e6038a689 100644
--- a/js/app/src/lite/websocket.ts
+++ b/js/app/src/lite/sse.ts
@@ -1,4 +1,4 @@
-import type { WorkerProxy } from "@gradio/wasm";
+import { type WorkerProxy, WasmWorkerEventSource } from "@gradio/wasm";
import { is_self_host } from "@gradio/wasm/network";
/**
@@ -6,14 +6,14 @@ import { is_self_host } from "@gradio/wasm/network";
* which also falls back to the original WebSocket() for external resource requests.
*/
-export function wasm_proxied_WebSocket_factory(
+export function wasm_proxied_EventSource_factory(
worker_proxy: WorkerProxy,
url: URL
-): WebSocket {
+): EventSource {
if (!is_self_host(url)) {
console.debug("Fallback to original WebSocket");
- return new WebSocket(url);
+ return new EventSource(url);
}
- return worker_proxy.openWebSocket(url.pathname) as unknown as WebSocket;
+ return new WasmWorkerEventSource(worker_proxy, url) as unknown as EventSource;
}
diff --git a/js/app/vite.config.ts b/js/app/vite.config.ts
index b60881274664..fed40d27dce7 100644
--- a/js/app/vite.config.ts
+++ b/js/app/vite.config.ts
@@ -47,6 +47,7 @@ export default defineConfig(({ mode }) => {
"dev:custom": "../../gradio/templates/frontend"
};
const production = mode === "production" || mode === "production:lite";
+ const development = mode === "development" || mode === "development:lite";
const is_lite = mode.endsWith(":lite");
return {
@@ -131,7 +132,7 @@ export default defineConfig(({ mode }) => {
}
},
plugins: [
- resolve_svelte(mode === "development"),
+ resolve_svelte(development && !is_lite),
svelte({
inspector: true,
@@ -150,7 +151,12 @@ export default defineConfig(({ mode }) => {
}
})
}),
- generate_dev_entry({ enable: mode !== "development" && mode !== "test" }),
+ generate_dev_entry({
+ enable:
+ !development &&
+ !is_lite && // At the moment of https://github.com/gradio-app/gradio/pull/6398, I skipped to make Gradio-lite work custom component. Will do it, and remove this condition.
+ mode !== "test"
+ }),
inject_ejs(),
generate_cdn_entry({ version: GRADIO_VERSION, cdn_base: CDN_BASE }),
handle_ce_css(),
diff --git a/js/audio/interactive/InteractiveAudio.svelte b/js/audio/interactive/InteractiveAudio.svelte
index 1652aa599bfa..2db20039d464 100644
--- a/js/audio/interactive/InteractiveAudio.svelte
+++ b/js/audio/interactive/InteractiveAudio.svelte
@@ -1,7 +1,12 @@
-
-