File size: 8,346 Bytes
a03b3ba |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
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_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";
import ErrorDisplay from "./ErrorDisplay.svelte";
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;
// 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
// by the Vite plugin `handle_ce_css` in `build_plugins.ts`,
// and the CSS file will be dynamically loaded at runtime
// as the file path (the `ENTRY_CSS` variable) will be passed to `mount_css()`.
// This mechanism has been introduced in https://github.com/gradio-app/gradio/pull/1444
// to make Gradio work as a Web Component library
// with which users can use Gradio by loading only one JS file,
// without a link tag referring to the CSS file.
// However, we don't rely on this mechanism here to make things simpler by leaving the Vite plugins as is,
// because it will be refactored in the near future.
// As a result, the users of the Wasm app will have to load the CSS file manually.
// const ENTRY_CSS = "__ENTRY_CSS__";
interface GradioAppController {
run_code: (code: string) => Promise<void>;
run_file: (path: string) => Promise<void>;
write: (
path: string,
data: string | ArrayBufferView,
opts: any
) => Promise<void>;
rename: (old_path: string, new_path: string) => Promise<void>;
unlink: (path: string) => Promise<void>;
install: (requirements: string[]) => Promise<void>;
unmount: () => void;
}
export interface Options {
target: HTMLElement;
files?: WorkerProxyOptions["files"];
requirements?: WorkerProxyOptions["requirements"];
code?: string;
entrypoint?: string;
sharedWorkerMode?: boolean;
info: boolean;
container: boolean;
isEmbed: boolean;
initialHeight?: string;
eager: boolean;
themeMode: ThemeMode | null;
autoScroll: boolean;
controlPageTitle: boolean;
appMode: boolean;
}
export function create(options: Options): GradioAppController {
// TODO: Runtime type validation for options.
const observer = new MutationObserver(() => {
document.body.style.padding = "0";
});
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);
const overridden_fetch: typeof fetch = (input, init?) => {
return wasm_proxied_fetch(worker_proxy, input, init);
};
const EventSource_factory = (url: URL): EventSource => {
return wasm_proxied_EventSource_factory(worker_proxy, url);
};
const { client, upload_files } = api_factory(
overridden_fetch,
EventSource_factory
);
const overridden_mount_css: typeof mount_css = async (url, target) => {
return wasm_proxied_mount_css(worker_proxy, url, target);
};
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();
}
app = new Index({
target: options.target,
props: {
// embed source
space: null,
src: null,
host: null,
// embed info
info: options.info,
container: options.container,
is_embed: options.isEmbed,
initial_height: options.initialHeight ?? "300px", // default: 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,
upload_files,
mount_css: overridden_mount_css,
fetch_implementation: overridden_fetch,
EventSource_factory
}
});
}
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;
});
},
unmount() {
app.$destroy();
worker_proxy.terminate();
}
};
}
/**
* I'm not sure if this is a correct way to export functions from a bundle created with Vite.
* However, at least, the library mode (https://vitejs.dev/guide/build.html#library-mode)
* with an exported function (`export async function create()`) didn't work for our case.
* In library mode with the `build.lib.entry = (this file)` config,
* Vite creates a bundle exporting the functions from this file, which looks nice,
* however, it inevitably enables inlining of all the static file assets,
* while we need to disable inlining for the wheel files to pass their URLs to `micropip.install()`.
*
* > If you specify build.lib, build.assetsInlineLimit will be ignored and assets will always be inlined, regardless of file size or being a Git LFS placeholder.
* > https://vitejs.dev/config/build-options.html#build-assetsinlinelimit
*
* There is an open issue about this: https://github.com/vitejs/vite/issues/4454
*
* FYI, stlite (https://github.com/whitphx/stlite) uses Webpack,
* which supports bundling libraries that export entities to the global scope and disabling assets inlining
* (https://webpack.js.org/guides/author-libraries/).
*/
// @ts-ignore
globalThis.createGradioApp = create;
bootstrap_custom_element();
declare let BUILD_MODE: string;
if (BUILD_MODE === "dev") {
(async function () {
const DevApp = (await import("./dev/App.svelte")).default;
const app = new DevApp({
target: document.getElementById("dev-app")!
});
})();
}
|