mirror of
https://github.com/gradio-app/gradio.git
synced 2025-01-24 10:54:04 +08:00
Refactoring <gradio-lite /> component making the code simpler and fixing a Playground mode bug (#8693) (#8818)
* Create <LiteIndex /> component that contains the initialization logic and manages <Index /> and <Playground /> * add changeset * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
This commit is contained in:
parent
7b049e0357
commit
2de9a97ae9
6
.changeset/gentle-wombats-thank.md
Normal file
6
.changeset/gentle-wombats-thank.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"@gradio/app": patch
|
||||
"gradio": patch
|
||||
---
|
||||
|
||||
fix:Refactoring <gradio-lite /> component making the code simpler and fixing a Playground mode bug
|
201
js/app/src/lite/LiteIndex.svelte
Normal file
201
js/app/src/lite/LiteIndex.svelte
Normal file
@ -0,0 +1,201 @@
|
||||
<script lang="ts">
|
||||
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 { onDestroy, SvelteComponent } from "svelte";
|
||||
import Index from "../Index.svelte";
|
||||
import Playground from "./Playground.svelte";
|
||||
import ErrorDisplay from "./ErrorDisplay.svelte";
|
||||
import type { ThemeMode } from "../types";
|
||||
import { WorkerProxy, type WorkerProxyOptions } from "@gradio/wasm";
|
||||
import { Client } from "@gradio/client";
|
||||
import { wasm_proxied_fetch } from "./fetch";
|
||||
import { wasm_proxied_stream_factory } from "./sse";
|
||||
import { wasm_proxied_mount_css, mount_prebuilt_css } from "./css";
|
||||
import type { mount_css } from "../css";
|
||||
|
||||
// These imports are aliased at built time with Vite. See the `resolve.alias` config in `vite.config.ts`.
|
||||
import gradioWheel from "gradio.whl";
|
||||
import gradioClientWheel from "gradio_client.whl";
|
||||
|
||||
export let info: boolean;
|
||||
export let container: boolean;
|
||||
export let is_embed: boolean;
|
||||
export let initial_height: string;
|
||||
export let eager: boolean;
|
||||
export let version: string;
|
||||
export let theme_mode: ThemeMode | null;
|
||||
export let autoscroll: boolean;
|
||||
export let control_page_title: boolean;
|
||||
export let app_mode: boolean;
|
||||
|
||||
// For Wasm mode
|
||||
export let files: WorkerProxyOptions["files"] | undefined;
|
||||
export let requirements: WorkerProxyOptions["requirements"] | undefined;
|
||||
export let code: string | undefined;
|
||||
export let entrypoint: string | undefined;
|
||||
export let sharedWorkerMode: boolean | undefined;
|
||||
|
||||
// For playground
|
||||
export let playground = false;
|
||||
export let layout: string | null;
|
||||
|
||||
const worker_proxy = new WorkerProxy({
|
||||
gradioWheelUrl: new URL(gradioWheel, import.meta.url).href,
|
||||
gradioClientWheelUrl: new URL(gradioClientWheel, import.meta.url).href,
|
||||
files: files ?? {},
|
||||
requirements: requirements ?? [],
|
||||
sharedWorkerMode: sharedWorkerMode ?? false
|
||||
});
|
||||
onDestroy(() => {
|
||||
worker_proxy.terminate();
|
||||
});
|
||||
|
||||
let error: Error | null = null;
|
||||
|
||||
const wrapFunctionWithAppLogic = <TArgs extends any[], TRet extends any>(
|
||||
func: (...args: TArgs) => Promise<TRet>
|
||||
): ((...args: TArgs) => Promise<TRet>) => {
|
||||
return async (...args: TArgs) => {
|
||||
try {
|
||||
error = null;
|
||||
const retval = await func(...args);
|
||||
refresh_index_component();
|
||||
return retval;
|
||||
} catch (err) {
|
||||
error = err as Error;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
};
|
||||
worker_proxy.runPythonCode = wrapFunctionWithAppLogic(
|
||||
worker_proxy.runPythonCode.bind(worker_proxy)
|
||||
);
|
||||
worker_proxy.runPythonFile = wrapFunctionWithAppLogic(
|
||||
worker_proxy.runPythonFile.bind(worker_proxy)
|
||||
);
|
||||
worker_proxy.writeFile = wrapFunctionWithAppLogic(
|
||||
worker_proxy.writeFile.bind(worker_proxy)
|
||||
);
|
||||
worker_proxy.renameFile = wrapFunctionWithAppLogic(
|
||||
worker_proxy.renameFile.bind(worker_proxy)
|
||||
);
|
||||
worker_proxy.unlink = wrapFunctionWithAppLogic(
|
||||
worker_proxy.unlink.bind(worker_proxy)
|
||||
);
|
||||
worker_proxy.install = wrapFunctionWithAppLogic(
|
||||
worker_proxy.install.bind(worker_proxy)
|
||||
);
|
||||
|
||||
worker_proxy.addEventListener("initialization-error", (event) => {
|
||||
error = (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 (code != null) {
|
||||
worker_proxy.runPythonCode(code);
|
||||
} else if (entrypoint != null) {
|
||||
worker_proxy.runPythonFile(entrypoint);
|
||||
} else {
|
||||
throw new Error("Either code or entrypoint must be provided.");
|
||||
}
|
||||
|
||||
mount_prebuilt_css(document.head);
|
||||
|
||||
class LiteClient extends Client {
|
||||
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
||||
return wasm_proxied_fetch(worker_proxy, input, init);
|
||||
}
|
||||
|
||||
stream(url: URL): EventSource {
|
||||
return wasm_proxied_stream_factory(worker_proxy, url);
|
||||
}
|
||||
}
|
||||
|
||||
const overridden_mount_css: typeof mount_css = async (url, target) => {
|
||||
return wasm_proxied_mount_css(worker_proxy, url, target);
|
||||
};
|
||||
|
||||
let index_component_key = 0;
|
||||
function refresh_index_component(): void {
|
||||
index_component_key += 1;
|
||||
}
|
||||
|
||||
let playground_component: SvelteComponent | null = null;
|
||||
$: playground_component?.$on("code", (event) => {
|
||||
const { code } = event.detail;
|
||||
worker_proxy.runPythonCode(code);
|
||||
});
|
||||
|
||||
export const run_code = worker_proxy.runPythonCode;
|
||||
export const run_file = worker_proxy.runPythonFile;
|
||||
export const write = worker_proxy.writeFile;
|
||||
export const rename = worker_proxy.renameFile;
|
||||
export const unlink = worker_proxy.unlink;
|
||||
export const install = worker_proxy.install;
|
||||
</script>
|
||||
|
||||
{#if playground}
|
||||
<Playground
|
||||
bind:this={playground_component}
|
||||
{worker_proxy}
|
||||
{layout}
|
||||
{code}
|
||||
{is_embed}
|
||||
>
|
||||
{#key index_component_key}
|
||||
{#if error}
|
||||
<ErrorDisplay {error} is_embed />
|
||||
{:else}
|
||||
<Index
|
||||
space={null}
|
||||
src={null}
|
||||
host={null}
|
||||
{info}
|
||||
{container}
|
||||
{is_embed}
|
||||
{initial_height}
|
||||
{eager}
|
||||
{version}
|
||||
{theme_mode}
|
||||
{autoscroll}
|
||||
{control_page_title}
|
||||
{app_mode}
|
||||
{worker_proxy}
|
||||
Client={LiteClient}
|
||||
mount_css={overridden_mount_css}
|
||||
/>
|
||||
{/if}
|
||||
{/key}
|
||||
</Playground>
|
||||
{:else}
|
||||
{#key index_component_key}
|
||||
{#if error}
|
||||
<ErrorDisplay {error} {is_embed} />
|
||||
{:else}
|
||||
<Index
|
||||
space={null}
|
||||
src={null}
|
||||
host={null}
|
||||
{info}
|
||||
{container}
|
||||
{is_embed}
|
||||
{initial_height}
|
||||
{eager}
|
||||
{version}
|
||||
{theme_mode}
|
||||
{autoscroll}
|
||||
{control_page_title}
|
||||
{app_mode}
|
||||
{worker_proxy}
|
||||
Client={LiteClient}
|
||||
mount_css={overridden_mount_css}
|
||||
/>
|
||||
{/if}
|
||||
{/key}
|
||||
{/if}
|
@ -1,35 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Index from "../Index.svelte";
|
||||
import type { ThemeMode } from "../types";
|
||||
import { mount_css as default_mount_css } from "../css";
|
||||
import type { Client as ClientType } from "@gradio/client";
|
||||
import type { WorkerProxy } from "@gradio/wasm";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { BaseCode as Code } from "@gradio/code";
|
||||
import ErrorDisplay from "./ErrorDisplay.svelte";
|
||||
import lightning from "../images/lightning.svg";
|
||||
|
||||
export let autoscroll: boolean;
|
||||
export let version: string;
|
||||
export let initial_height: string;
|
||||
export let app_mode: boolean;
|
||||
export let is_embed: boolean;
|
||||
export let theme_mode: ThemeMode | null = "system";
|
||||
export let control_page_title: boolean;
|
||||
export let container: boolean;
|
||||
export let info: boolean;
|
||||
export let eager: boolean;
|
||||
export let mount_css: typeof default_mount_css = default_mount_css;
|
||||
export let Client: typeof ClientType;
|
||||
export let worker_proxy: WorkerProxy | undefined = undefined;
|
||||
|
||||
export let space: string | null;
|
||||
export let host: string | null;
|
||||
export let src: string | null;
|
||||
|
||||
export let code: string | undefined;
|
||||
export let error_display: { is_embed: boolean; error: Error } | null;
|
||||
export let layout: string | null = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@ -172,31 +153,7 @@
|
||||
</div>
|
||||
{#if loaded}
|
||||
<div class="preview">
|
||||
{#if !error_display}
|
||||
<Index
|
||||
{autoscroll}
|
||||
{version}
|
||||
{initial_height}
|
||||
{app_mode}
|
||||
{is_embed}
|
||||
{theme_mode}
|
||||
{control_page_title}
|
||||
{container}
|
||||
{info}
|
||||
{eager}
|
||||
{mount_css}
|
||||
{Client}
|
||||
bind:worker_proxy
|
||||
{space}
|
||||
{host}
|
||||
{src}
|
||||
/>
|
||||
{:else}
|
||||
<ErrorDisplay
|
||||
is_embed={error_display.is_embed}
|
||||
error={error_display.error}
|
||||
/>
|
||||
{/if}
|
||||
<slot></slot>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -1,26 +1,11 @@
|
||||
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 { Client } from "@gradio/client";
|
||||
import { wasm_proxied_fetch } from "./fetch";
|
||||
import { wasm_proxied_stream_factory } from "./sse";
|
||||
import { wasm_proxied_mount_css, mount_prebuilt_css } from "./css";
|
||||
import type { mount_css } from "../css";
|
||||
import Index from "../Index.svelte";
|
||||
import Playground from "./Playground.svelte";
|
||||
import ErrorDisplay from "./ErrorDisplay.svelte";
|
||||
import { type WorkerProxyOptions } from "@gradio/wasm";
|
||||
import type { ThemeMode } from "../types";
|
||||
import { bootstrap_custom_element } from "./custom-element";
|
||||
|
||||
// These imports are aliased at built time with Vite. See the `resolve.alias` config in `vite.config.ts`.
|
||||
import gradioWheel from "gradio.whl";
|
||||
import gradioClientWheel from "gradio_client.whl";
|
||||
|
||||
declare let GRADIO_VERSION: string;
|
||||
|
||||
import LiteIndex from "./LiteIndex.svelte";
|
||||
|
||||
// NOTE: The following line has been copied from `main.ts`.
|
||||
// In `main.ts`, which is the normal Gradio app entry point,
|
||||
// the string literal "__ENTRY_CSS__" will be replaced with the actual CSS file path
|
||||
@ -78,217 +63,40 @@ export function create(options: Options): GradioAppController {
|
||||
|
||||
observer.observe(options.target, { childList: true });
|
||||
|
||||
const worker_proxy = new WorkerProxy({
|
||||
gradioWheelUrl: new URL(gradioWheel, import.meta.url).href,
|
||||
gradioClientWheelUrl: new URL(gradioClientWheel, import.meta.url).href,
|
||||
files: options.files ?? {},
|
||||
requirements: options.requirements ?? [],
|
||||
sharedWorkerMode: options.sharedWorkerMode ?? false
|
||||
});
|
||||
|
||||
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).catch(showError);
|
||||
} else if (options.entrypoint != null) {
|
||||
worker_proxy.runPythonFile(options.entrypoint).catch(showError);
|
||||
} else {
|
||||
throw new Error("Either code or entrypoint must be provided.");
|
||||
}
|
||||
|
||||
mount_prebuilt_css(document.head);
|
||||
|
||||
class LiteClient extends Client {
|
||||
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
||||
return wasm_proxied_fetch(worker_proxy, input, init);
|
||||
}
|
||||
|
||||
stream(url: URL): EventSource {
|
||||
return wasm_proxied_stream_factory(worker_proxy, url);
|
||||
}
|
||||
}
|
||||
|
||||
const overridden_mount_css: typeof mount_css = async (url, target) => {
|
||||
return wasm_proxied_mount_css(worker_proxy, url, target);
|
||||
};
|
||||
|
||||
let app: SvelteComponent;
|
||||
let app_props: any;
|
||||
|
||||
let loaded = false;
|
||||
|
||||
function showError(error: Error): void {
|
||||
if (app != null) {
|
||||
app.$destroy();
|
||||
}
|
||||
if (options.playground) {
|
||||
app = new Playground({
|
||||
target: options.target,
|
||||
props: {
|
||||
...app_props,
|
||||
code: options.code,
|
||||
error_display: {
|
||||
is_embed: !options.isEmbed,
|
||||
error
|
||||
},
|
||||
loaded: true
|
||||
}
|
||||
});
|
||||
app.$on("code", (event) => {
|
||||
options.code = event.detail.code;
|
||||
loaded = true;
|
||||
worker_proxy
|
||||
.runPythonCode(event.detail.code)
|
||||
.then(launchNewApp)
|
||||
.catch((e) => {
|
||||
showError(e);
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
app = new ErrorDisplay({
|
||||
target: options.target,
|
||||
props: {
|
||||
is_embed: !options.isEmbed,
|
||||
error
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function launchNewApp(): Promise<void> {
|
||||
if (app != null) {
|
||||
app.$destroy();
|
||||
}
|
||||
|
||||
app_props = {
|
||||
// embed source
|
||||
space: null,
|
||||
src: null,
|
||||
host: null,
|
||||
// embed info
|
||||
const app = new LiteIndex({
|
||||
target: options.target,
|
||||
props: {
|
||||
info: options.info,
|
||||
container: options.container,
|
||||
is_embed: options.isEmbed,
|
||||
initial_height: options.initialHeight ?? "300px", // default: 300px
|
||||
initial_height: options.initialHeight ?? "300px",
|
||||
eager: options.eager,
|
||||
// gradio meta info
|
||||
version: GRADIO_VERSION,
|
||||
theme_mode: options.themeMode,
|
||||
// misc global behaviour
|
||||
autoscroll: options.autoScroll,
|
||||
control_page_title: options.controlPageTitle,
|
||||
// for gradio docs
|
||||
// TODO: Remove -- i think this is just for autoscroll behavhiour, app vs embeds
|
||||
app_mode: options.appMode,
|
||||
// For Wasm mode
|
||||
worker_proxy,
|
||||
Client: LiteClient,
|
||||
mount_css: overridden_mount_css,
|
||||
files: options.files,
|
||||
requirements: options.requirements,
|
||||
code: options.code,
|
||||
entrypoint: options.entrypoint,
|
||||
sharedWorkerMode: options.sharedWorkerMode,
|
||||
// For playground
|
||||
playground: options.playground,
|
||||
layout: options.layout
|
||||
};
|
||||
|
||||
if (options.playground) {
|
||||
app = new Playground({
|
||||
target: options.target,
|
||||
props: {
|
||||
...app_props,
|
||||
code: options.code,
|
||||
error_display: null,
|
||||
loaded: loaded
|
||||
}
|
||||
});
|
||||
app.$on("code", (event) => {
|
||||
options.code = event.detail.code;
|
||||
loaded = true;
|
||||
worker_proxy
|
||||
.runPythonCode(event.detail.code)
|
||||
.then(launchNewApp)
|
||||
.catch((e) => {
|
||||
showError(e);
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
app = new Index({
|
||||
target: options.target,
|
||||
props: app_props
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
app.$on("loaded", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
launchNewApp();
|
||||
});
|
||||
|
||||
return {
|
||||
run_code: (code: string) => {
|
||||
return worker_proxy
|
||||
.runPythonCode(code)
|
||||
.then(launchNewApp)
|
||||
.catch((e) => {
|
||||
showError(e);
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
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)
|
||||
.then(launchNewApp)
|
||||
.catch((e) => {
|
||||
showError(e);
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
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)
|
||||
.then(launchNewApp)
|
||||
.catch((e) => {
|
||||
showError(e);
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
install: (requirements) => {
|
||||
return worker_proxy
|
||||
.install(requirements)
|
||||
.then(launchNewApp)
|
||||
.catch((e) => {
|
||||
showError(e);
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
run_code: app.run_code,
|
||||
run_file: app.run_file,
|
||||
write: app.write,
|
||||
rename: app.rename,
|
||||
unlink: app.unlink,
|
||||
install: app.install,
|
||||
unmount() {
|
||||
app.$destroy();
|
||||
worker_proxy.terminate();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user