mirror of
https://github.com/gradio-app/gradio.git
synced 2025-04-12 12:40:29 +08:00
Lite: Show initialization progress messages (#5983)
This commit is contained in:
parent
d8a6491a18
commit
a32aabaf50
7
.changeset/eight-hoops-juggle.md
Normal file
7
.changeset/eight-hoops-juggle.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
"@gradio/app": minor
|
||||
"@gradio/wasm": minor
|
||||
"gradio": minor
|
||||
---
|
||||
|
||||
feat:Lite: Show initialization progress messages
|
@ -94,6 +94,25 @@
|
||||
export let worker_proxy: WorkerProxy | undefined = undefined;
|
||||
if (worker_proxy) {
|
||||
setWorkerProxyContext(worker_proxy);
|
||||
|
||||
worker_proxy.addEventListener("progress-update", (event) => {
|
||||
loading_text = (event as CustomEvent).detail + "...";
|
||||
});
|
||||
worker_proxy.addEventListener("initialization-error", (event) => {
|
||||
const error: Error = (event as CustomEvent).detail;
|
||||
|
||||
// XXX: Although `status` is expected to store Space status info,
|
||||
// we are using it to store the error thrown from the Wasm runtime here
|
||||
// as a workaround to display the error message in the UI
|
||||
// without breaking the existing code.
|
||||
status = {
|
||||
status: "space_error",
|
||||
message: error.message,
|
||||
detail: "RUNTIME_ERROR",
|
||||
load_status: "error",
|
||||
discussions_enabled: false
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export let space: string | null;
|
||||
|
@ -113,3 +113,15 @@ export interface ReplyMessageError {
|
||||
}
|
||||
|
||||
export type ReplyMessage = ReplyMessageSuccess | ReplyMessageError;
|
||||
|
||||
export interface OutMessageBase {
|
||||
type: string;
|
||||
data: unknown;
|
||||
}
|
||||
export interface OutMessageProgressUpdate extends OutMessageBase {
|
||||
type: "progress-update";
|
||||
data: {
|
||||
log: string;
|
||||
};
|
||||
}
|
||||
export type OutMessage = OutMessageProgressUpdate;
|
||||
|
@ -5,6 +5,7 @@ import type { PyodideInterface } from "pyodide";
|
||||
import type {
|
||||
InMessage,
|
||||
InMessageInit,
|
||||
OutMessage,
|
||||
ReplyMessageError,
|
||||
ReplyMessageSuccess
|
||||
} from "../message-types";
|
||||
@ -29,10 +30,21 @@ let call_asgi_app_from_js: (
|
||||
let run_script: (path: string) => Promise<void>;
|
||||
let unload_local_modules: (target_dir_path?: string) => void;
|
||||
|
||||
function updateProgress(log: string): void {
|
||||
const message: OutMessage = {
|
||||
type: "progress-update",
|
||||
data: {
|
||||
log
|
||||
}
|
||||
};
|
||||
self.postMessage(message);
|
||||
}
|
||||
|
||||
async function loadPyodideAndPackages(
|
||||
options: InMessageInit["data"]
|
||||
): Promise<void> {
|
||||
console.debug("Loading Pyodide.");
|
||||
updateProgress("Loading Pyodide");
|
||||
pyodide = await loadPyodide({
|
||||
stdout: console.debug,
|
||||
stderr: console.error
|
||||
@ -40,6 +52,7 @@ async function loadPyodideAndPackages(
|
||||
console.debug("Pyodide is loaded.");
|
||||
|
||||
console.debug("Mounting files.", options.files);
|
||||
updateProgress("Mounting files");
|
||||
await Promise.all(
|
||||
Object.keys(options.files).map(async (path) => {
|
||||
const file = options.files[path];
|
||||
@ -62,6 +75,7 @@ async function loadPyodideAndPackages(
|
||||
console.debug("Files are mounted.");
|
||||
|
||||
console.debug("Loading micropip");
|
||||
updateProgress("Loading micropip");
|
||||
await pyodide.loadPackage("micropip");
|
||||
const micropip = pyodide.pyimport("micropip");
|
||||
console.debug("micropip is loaded.");
|
||||
@ -71,6 +85,7 @@ async function loadPyodideAndPackages(
|
||||
options.gradioClientWheelUrl
|
||||
];
|
||||
console.debug("Loading Gradio wheels.", gradioWheelUrls);
|
||||
updateProgress("Loading Gradio wheels");
|
||||
await micropip.add_mock_package("ffmpy", "0.3.0");
|
||||
await micropip.add_mock_package("aiohttp", "3.8.4");
|
||||
await pyodide.loadPackage(["ssl", "distutils", "setuptools"]);
|
||||
@ -82,11 +97,13 @@ async function loadPyodideAndPackages(
|
||||
});
|
||||
console.debug("Gradio wheels are loaded.");
|
||||
|
||||
console.debug("Install packages.", options.requirements);
|
||||
console.debug("Installing packages.", options.requirements);
|
||||
updateProgress("Installing packages");
|
||||
await micropip.install.callKwargs(options.requirements, { keep_going: true });
|
||||
console.debug("Packages are installed.");
|
||||
|
||||
console.debug("Mock os module methods.");
|
||||
console.debug("Mocking os module methods.");
|
||||
updateProgress("Mock os module methods");
|
||||
// `os.link` is used in `aiofiles` (https://github.com/Tinche/aiofiles/blob/v23.1.0/src/aiofiles/os.py#L31),
|
||||
// which is imported from `gradio.ranged_response` (https://github.com/gradio-app/gradio/blob/v3.32.0/gradio/ranged_response.py#L12).
|
||||
// However, it's not available on Wasm.
|
||||
@ -97,13 +114,15 @@ os.link = lambda src, dst: None
|
||||
`);
|
||||
console.debug("os module methods are mocked.");
|
||||
|
||||
console.debug("Import gradio package.");
|
||||
console.debug("Importing gradio package.");
|
||||
updateProgress("Importing gradio package");
|
||||
// Importing the gradio package takes a long time, so we do it separately.
|
||||
// This is necessary for accurate performance profiling.
|
||||
await pyodide.runPythonAsync(`import gradio`);
|
||||
console.debug("gradio package is imported.");
|
||||
|
||||
console.debug("Define a ASGI wrapper function.");
|
||||
console.debug("Defining a ASGI wrapper function.");
|
||||
updateProgress("Defining a ASGI wrapper function");
|
||||
// TODO: Unlike Streamlit, user's code is executed in the global scope,
|
||||
// so we should not define this function in the global scope.
|
||||
await pyodide.runPythonAsync(`
|
||||
@ -143,7 +162,8 @@ async def _call_asgi_app_from_js(scope, receive, send):
|
||||
call_asgi_app_from_js = pyodide.globals.get("_call_asgi_app_from_js");
|
||||
console.debug("The ASGI wrapper function is defined.");
|
||||
|
||||
console.debug("Mock async libraries.");
|
||||
console.debug("Mocking async libraries.");
|
||||
updateProgress("Mocking async libraries");
|
||||
// FastAPI uses `anyio.to_thread.run_sync` internally which, however, doesn't work in Wasm environments where the `threading` module is not supported.
|
||||
// So we mock `anyio.to_thread.run_sync` here not to use threads.
|
||||
await pyodide.runPythonAsync(`
|
||||
@ -155,7 +175,8 @@ anyio.to_thread.run_sync = mocked_anyio_to_thread_run_sync
|
||||
`);
|
||||
console.debug("Async libraries are mocked.");
|
||||
|
||||
console.debug("Set matplotlib backend.");
|
||||
console.debug("Setting matplotlib backend.");
|
||||
updateProgress("Setting matplotlib backend");
|
||||
// Ref: https://github.com/streamlit/streamlit/blob/1.22.0/lib/streamlit/web/bootstrap.py#L111
|
||||
// This backend setting is required to use matplotlib in Wasm environment.
|
||||
await pyodide.runPythonAsync(`
|
||||
@ -164,7 +185,8 @@ matplotlib.use("agg")
|
||||
`);
|
||||
console.debug("matplotlib backend is set.");
|
||||
|
||||
console.debug("Set up Python utility functions.");
|
||||
console.debug("Setting up Python utility functions.");
|
||||
updateProgress("Setting up Python utility functions");
|
||||
await pyodide.runPythonAsync(scriptRunnerPySource);
|
||||
run_script = pyodide.globals.get("_run_script");
|
||||
await pyodide.runPythonAsync(unloadModulesPySource);
|
||||
@ -182,11 +204,21 @@ self.onmessage = async (event: MessageEvent<InMessage>): Promise<void> => {
|
||||
if (msg.type === "init") {
|
||||
pyodideReadyPromise = loadPyodideAndPackages(msg.data);
|
||||
|
||||
const replyMessage: ReplyMessageSuccess = {
|
||||
type: "reply:success",
|
||||
data: null
|
||||
};
|
||||
messagePort.postMessage(replyMessage);
|
||||
pyodideReadyPromise
|
||||
.then(() => {
|
||||
const replyMessage: ReplyMessageSuccess = {
|
||||
type: "reply:success",
|
||||
data: null
|
||||
};
|
||||
messagePort.postMessage(replyMessage);
|
||||
})
|
||||
.catch((error) => {
|
||||
const replyMessage: ReplyMessageError = {
|
||||
type: "reply:error",
|
||||
error
|
||||
};
|
||||
messagePort.postMessage(replyMessage);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import type {
|
||||
HttpResponse,
|
||||
InMessage,
|
||||
InMessageWebSocket,
|
||||
OutMessage,
|
||||
ReplyMessage
|
||||
} from "./message-types";
|
||||
import { MessagePortWebSocket } from "./messageportwebsocket";
|
||||
@ -18,12 +19,14 @@ export interface WorkerProxyOptions {
|
||||
requirements: string[];
|
||||
}
|
||||
|
||||
export class WorkerProxy {
|
||||
export class WorkerProxy extends EventTarget {
|
||||
private worker: globalThis.Worker;
|
||||
|
||||
private firstRunPromiseDelegate = new PromiseDelegate<void>();
|
||||
|
||||
constructor(options: WorkerProxyOptions) {
|
||||
super();
|
||||
|
||||
console.debug("WorkerProxy.constructor(): Create a new worker.");
|
||||
// Loading a worker here relies on Vite's support for WebWorkers (https://vitejs.dev/guide/features.html#web-workers),
|
||||
// assuming that this module is imported from the Gradio frontend (`@gradio/app`), which is bundled with Vite.
|
||||
@ -32,6 +35,10 @@ export class WorkerProxy {
|
||||
const workerMaker = new Worker(new URL("./webworker.js", import.meta.url));
|
||||
this.worker = workerMaker.worker;
|
||||
|
||||
this.worker.onmessage = (e) => {
|
||||
this._processWorkerMessage(e.data);
|
||||
};
|
||||
|
||||
this.postMessageAsync({
|
||||
type: "init",
|
||||
data: {
|
||||
@ -40,9 +47,21 @@ export class WorkerProxy {
|
||||
files: options.files,
|
||||
requirements: options.requirements
|
||||
}
|
||||
}).then(() => {
|
||||
console.debug("WorkerProxy.constructor(): Initialization is done.");
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
console.debug("WorkerProxy.constructor(): Initialization is done.");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"WorkerProxy.constructor(): Initialization failed.",
|
||||
error
|
||||
);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("initialization-error", {
|
||||
detail: error
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public async runPythonCode(code: string): Promise<void> {
|
||||
@ -88,6 +107,19 @@ export class WorkerProxy {
|
||||
});
|
||||
}
|
||||
|
||||
private _processWorkerMessage(msg: OutMessage): void {
|
||||
switch (msg.type) {
|
||||
case "progress-update": {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("progress-update", {
|
||||
detail: msg.data.log
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async httpRequest(request: HttpRequest): Promise<HttpResponse> {
|
||||
// Wait for the first run to be done
|
||||
// to avoid the "Gradio app has not been launched." error
|
||||
|
Loading…
x
Reference in New Issue
Block a user