Lite error handler (#6076)

* Revert "Lite: Error handling after initialization (#6005)"

This reverts commit e0ed0642ac2cb4f7ce9ee698d082607d9bab3636.

* Lift up the error handler for @gradio/lite to app/src/lite/index.ts and use a new ErrorDisplay component to show the error

* add changeset

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
This commit is contained in:
Yuichiro Tachibana (Tsuchiya) 2023-10-25 14:36:07 +09:00 committed by GitHub
parent 8241f9a7bd
commit f3f98f923c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 137 additions and 103 deletions

View File

@ -0,0 +1,7 @@
---
"@gradio/app": patch
"@gradio/wasm": patch
"gradio": patch
---
fix:Lite error handler

View File

@ -98,29 +98,6 @@
worker_proxy.addEventListener("progress-update", (event) => {
loading_text = (event as CustomEvent).detail + "...";
});
worker_proxy.addEventListener("run-start", (event) => {
status = {
message: "",
load_status: "pending",
status: "sleeping",
detail: "SLEEPING"
}
});
worker_proxy.addEventListener("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;

View File

@ -0,0 +1,51 @@
<script lang="ts">
import { StatusTracker } from "@gradio/statustracker";
export let is_embed: boolean;
export let error: Error | undefined = undefined;
</script>
<StatusTracker
absolute={!is_embed}
status="error"
timer={false}
queue_position={null}
queue_size={null}
translucent={true}
>
<div class="error" slot="error">
{#if error}
{#if error.message}
<p class="error-name">
{error.message}
</p>
{/if}
{#if error.stack}
<pre class="error-stack"><code>{error.stack}</code></pre>
{/if}
{/if}
</div>
</StatusTracker>
<style>
.error {
position: relative;
width: 100%;
padding: var(--size-4);
color: var(--body-text-color);
/* Status tracker sets `pointer-events: none`.
Override it here so the user can scroll the element with `overflow: hidden`
and copy and paste the error message */
pointer-events: all;
}
.error-name {
text-align: center;
}
.error-stack {
width: 100%;
overflow: scroll;
}
</style>

View File

@ -1,4 +1,5 @@
import "@gradio/theme";
import type { SvelteComponent } from "svelte"
import { WorkerProxy, type WorkerProxyOptions } from "@gradio/wasm";
import { api_factory } from "@gradio/client";
import { wasm_proxied_fetch } from "./fetch";
@ -6,6 +7,7 @@ import { wasm_proxied_WebSocket_factory } from "./websocket";
import { wasm_proxied_mount_css, mount_prebuilt_css } from "./css";
import type { mount_css } from "../css";
import Index from "../Index.svelte";
import ErrorDisplay from "./ErrorDisplay.svelte";
import type { ThemeMode } from "../components/types";
import { bootstrap_custom_element } from "./custom-element";
@ -76,14 +78,18 @@ export function create(options: Options): GradioAppController {
requirements: options.requirements ?? []
});
worker_proxy.addEventListener("initialization-error", (event) => {
showError((event as CustomEvent).detail);
});
// Internally, the execution of `runPythonCode()` or `runPythonFile()` is queued
// and its promise will be resolved after the Pyodide is loaded and the worker initialization is done
// (see the await in the `onmessage` callback in the webworker code)
// So we don't await this promise because we want to mount the `Index` immediately and start the app initialization asynchronously.
if (options.code != null) {
worker_proxy.runPythonCode(options.code);
worker_proxy.runPythonCode(options.code).catch(showError);
} else if (options.entrypoint != null) {
worker_proxy.runPythonFile(options.entrypoint);
worker_proxy.runPythonFile(options.entrypoint).catch(showError)
} else {
throw new Error("Either code or entrypoint must be provided.");
}
@ -104,7 +110,20 @@ export function create(options: Options): GradioAppController {
return wasm_proxied_mount_css(worker_proxy, url, target);
};
let app: Index;
let app: SvelteComponent;
function showError(error: Error): void {
if (app != null) {
app.$destroy();
}
app = new ErrorDisplay({
target: options.target,
props: {
is_embed: !options.isEmbed,
error,
}
});
}
function launchNewApp(): void {
if (app != null) {
app.$destroy();
@ -144,25 +163,53 @@ export function create(options: Options): GradioAppController {
launchNewApp();
return {
run_code: async (code: string): Promise<void> => {
await worker_proxy.runPythonCode(code);
launchNewApp();
run_code: (code: string) => {
return worker_proxy.runPythonCode(code)
.then(launchNewApp)
.catch((e) => {
showError(e);
throw e;
})
},
run_file: async (path: string): Promise<void> => {
await worker_proxy.runPythonFile(path);
launchNewApp();
run_file: (path: string) => {
return worker_proxy.runPythonFile(path)
.then(launchNewApp)
.catch((e) => {
showError(e);
throw e;
})
},
write(path, data, opts) {
return worker_proxy.writeFile(path, data, opts);
write: (path, data, opts) => {
return worker_proxy.writeFile(path, data, opts)
.then(launchNewApp)
.catch((e) => {
showError(e);
throw e;
})
},
rename(old_path: string, new_path: string): Promise<void> {
return worker_proxy.renameFile(old_path, new_path);
rename: (old_path, new_path) => {
return worker_proxy.renameFile(old_path, new_path)
.then(launchNewApp)
.catch((e) => {
showError(e);
throw e;
})
},
unlink(path) {
return worker_proxy.unlink(path);
unlink: (path) => {
return worker_proxy.unlink(path)
.then(launchNewApp)
.catch((e) => {
showError(e);
throw e;
})
},
install(requirements) {
return worker_proxy.install(requirements);
install: (requirements) => {
return worker_proxy.install(requirements)
.then(launchNewApp)
.catch((e) => {
showError(e);
throw e;
})
},
unmount() {
app.$destroy();

View File

@ -57,7 +57,7 @@ export class WorkerProxy extends EventTarget {
error
);
this.dispatchEvent(
new CustomEvent("error", {
new CustomEvent("initialization-error", {
detail: error
})
);
@ -65,43 +65,23 @@ export class WorkerProxy extends EventTarget {
}
public async runPythonCode(code: string): Promise<void> {
this.dispatchEvent(new Event("run-start"));
try {
await this.postMessageAsync({
type: "run-python-code",
data: {
code
}
});
this.firstRunPromiseDelegate.resolve();
} catch (error) {
this.dispatchEvent(
new CustomEvent("error", {
detail: error
})
);
throw error;
}
await this.postMessageAsync({
type: "run-python-code",
data: {
code
}
});
this.firstRunPromiseDelegate.resolve();
}
public async runPythonFile(path: string): Promise<void> {
this.dispatchEvent(new Event("run-start"));
try {
await this.postMessageAsync({
type: "run-python-file",
data: {
path
}
});
this.firstRunPromiseDelegate.resolve();
} catch (error) {
this.dispatchEvent(
new CustomEvent("error", {
detail: error
})
);
throw error;
}
await this.postMessageAsync({
type: "run-python-file",
data: {
path
}
});
this.firstRunPromiseDelegate.resolve();
}
// A wrapper for this.worker.postMessage(). Unlike that function, which
@ -206,13 +186,6 @@ export class WorkerProxy extends EventTarget {
data,
opts
}
}).catch((error) => {
this.dispatchEvent(
new CustomEvent("error", {
detail: error
})
);
throw error;
}) as Promise<void>;
}
@ -223,13 +196,6 @@ export class WorkerProxy extends EventTarget {
oldPath,
newPath
}
}).catch((error) => {
this.dispatchEvent(
new CustomEvent("error", {
detail: error
})
);
throw error;
}) as Promise<void>;
}
@ -239,13 +205,6 @@ export class WorkerProxy extends EventTarget {
data: {
path
}
}).catch((error) => {
this.dispatchEvent(
new CustomEvent("error", {
detail: error
})
);
throw error;
}) as Promise<void>;
}
@ -255,13 +214,6 @@ export class WorkerProxy extends EventTarget {
data: {
requirements
}
}).catch((error) => {
this.dispatchEvent(
new CustomEvent("error", {
detail: error
})
);
throw error;
}) as Promise<void>;
}