Skip to content

Commit

Permalink
batch UI updates on a per frame basis (gradio-app#7564)
Browse files Browse the repository at this point in the history
* changes

* process fe fn tests

* cleanup

* cleanup

* create_target_meta tests and abstraction

* add interactivity detection and tests

* more functions more tests

* add tests for component loader, fix errors

* fix everything

* add changeset

* add changeset

* ci

* cleanup

* cleanup

* test

* fix again

* tweaks

* cleanup

* add changeset

* fix loading_status

* cleanup

* ensure updates have been flushed before making API requests

* add changeset

* df fix

* fixes

* fix dataframe updates

* fix dataframe updates

* remove $open var

* add changeset

* fix tests

* extend timeout for lite

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
Co-authored-by: Hannah <hannahblair@users.noreply.github.com>
  • Loading branch information
4 people authored Mar 13, 2024
1 parent aba4470 commit 5d1e8da
Show file tree
Hide file tree
Showing 29 changed files with 1,571 additions and 629 deletions.
10 changes: 10 additions & 0 deletions .changeset/itchy-boats-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@gradio/accordion": patch
"@gradio/app": patch
"@gradio/client": patch
"@gradio/dataframe": patch
"@gradio/dataset": patch
"gradio": patch
---

fix:batch UI updates on a per frame basis
4 changes: 2 additions & 2 deletions .config/playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const base = defineConfig({
}
},
expect: { timeout: 15000 },
timeout: 15000,
timeout: 30000,
testMatch: /.*.spec.ts/,
testDir: "..",
workers: process.env.CI ? 1 : undefined
Expand All @@ -37,7 +37,7 @@ const lite = defineConfig(base, {
testMatch: [
"**/file_component_events.spec.ts",
"**/chatbot_multimodal.spec.ts",
"**/kitchen_sink.spec.ts",
// "**/kitchen_sink.spec.ts",
"**/gallery_component_events.spec.ts"
],
workers: 1
Expand Down
3 changes: 2 additions & 1 deletion client/js/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type predict = (
event_data?: unknown
) => Promise<unknown>;

type client_return = {
export type client_return = {
predict: predict;
config: Config;
submit: (
Expand Down Expand Up @@ -194,6 +194,7 @@ export function api_factory(
if (token) {
headers.Authorization = `Bearer ${token}`;
}

try {
var response = await fetch_implementation(url, {
method: "POST",
Expand Down
2 changes: 2 additions & 0 deletions client/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ export {
duplicate,
api_factory
} from "./client.js";
export type { client_return } from "./client.js";
export type { SpaceStatus } from "./types.js";

export { FileData, upload, prepare_files } from "./upload.js";
2 changes: 1 addition & 1 deletion demo/audio_debugger/run.ipynb
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: audio_debugger"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/audio_debugger/cantina.wav"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import subprocess\n", "import os\n", "\n", "audio_file = os.path.join(os.path.abspath(''), \"cantina.wav\")\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Tab(\"Audio\"):\n", " gr.Audio(audio_file)\n", " with gr.Tab(\"Interface\"):\n", " gr.Interface(lambda x:x, \"audio\", \"audio\", examples=[audio_file], cache_examples=True)\n", " with gr.Tab(\"Streaming\"):\n", " gr.Interface(lambda x:x, gr.Audio(streaming=True), \"audio\", examples=[audio_file], cache_examples=True)\n", " with gr.Tab(\"console\"):\n", " ip = gr.Textbox(label=\"User IP Address\")\n", " gr.Interface(lambda cmd:subprocess.run([cmd], capture_output=True, shell=True).stdout.decode('utf-8').strip(), \"text\", \"text\")\n", " \n", " def get_ip(request: gr.Request):\n", " return request.client.host\n", " \n", " demo.load(get_ip, None, ip)\n", " \n", "if __name__ == \"__main__\":\n", " demo.queue()\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: audio_debugger"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/audio_debugger/cantina.wav"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import subprocess\n", "import os\n", "\n", "audio_file = os.path.join(os.path.abspath(''), \"cantina.wav\")\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Tab(\"Audio\"):\n", " gr.Audio(audio_file)\n", " with gr.Tab(\"Interface\"):\n", " gr.Interface(\n", " lambda x: x, \"audio\", \"audio\", examples=[audio_file], cache_examples=True\n", " )\n", " with gr.Tab(\"Streaming\"):\n", " gr.Interface(\n", " lambda x: x,\n", " gr.Audio(streaming=True),\n", " \"audio\",\n", " examples=[audio_file],\n", " cache_examples=True,\n", " )\n", " with gr.Tab(\"console\"):\n", " ip = gr.Textbox(label=\"User IP Address\")\n", " gr.Interface(\n", " lambda cmd: subprocess.run([cmd], capture_output=True, shell=True)\n", " .stdout.decode(\"utf-8\")\n", " .strip(),\n", " \"text\",\n", " \"text\",\n", " )\n", "\n", " def get_ip(request: gr.Request):\n", " return request.client.host\n", "\n", " demo.load(get_ip, None, ip)\n", "\n", "if __name__ == \"__main__\":\n", " demo.queue()\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
26 changes: 20 additions & 6 deletions demo/audio_debugger/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,32 @@
with gr.Tab("Audio"):
gr.Audio(audio_file)
with gr.Tab("Interface"):
gr.Interface(lambda x:x, "audio", "audio", examples=[audio_file], cache_examples=True)
gr.Interface(
lambda x: x, "audio", "audio", examples=[audio_file], cache_examples=True
)
with gr.Tab("Streaming"):
gr.Interface(lambda x:x, gr.Audio(streaming=True), "audio", examples=[audio_file], cache_examples=True)
gr.Interface(
lambda x: x,
gr.Audio(streaming=True),
"audio",
examples=[audio_file],
cache_examples=True,
)
with gr.Tab("console"):
ip = gr.Textbox(label="User IP Address")
gr.Interface(lambda cmd:subprocess.run([cmd], capture_output=True, shell=True).stdout.decode('utf-8').strip(), "text", "text")

gr.Interface(
lambda cmd: subprocess.run([cmd], capture_output=True, shell=True)
.stdout.decode("utf-8")
.strip(),
"text",
"text",
)

def get_ip(request: gr.Request):
return request.client.host

demo.load(get_ip, None, ip)

if __name__ == "__main__":
demo.queue()
demo.launch()
2 changes: 1 addition & 1 deletion demo/hello_blocks/run.ipynb
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: hello_blocks"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "def greet(name):\n", " return \"Hello \" + name + \"!\"\n", "\n", "with gr.Blocks() as demo:\n", " name = gr.Textbox(label=\"Name\")\n", " output = gr.Textbox(label=\"Output Box\")\n", " greet_btn = gr.Button(\"Greet\")\n", " greet_btn.click(fn=greet, inputs=name, outputs=output, api_name=\"greet\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: hello_blocks"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "\n", "def greet(name):\n", " return \"Hello \" + name + \"!\"\n", "\n", "\n", "with gr.Blocks() as demo:\n", " name = gr.Textbox(label=\"Name\")\n", " output = gr.Textbox(label=\"Output Box\")\n", " greet_btn = gr.Button(\"Greet\")\n", " greet_btn.click(fn=greet, inputs=name, outputs=output, api_name=\"greet\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
4 changes: 3 additions & 1 deletion demo/hello_blocks/run.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import gradio as gr


def greet(name):
return "Hello " + name + "!"


with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Output Box")
greet_btn = gr.Button("Greet")
greet_btn.click(fn=greet, inputs=name, outputs=output, api_name="greet")

if __name__ == "__main__":
demo.launch()
demo.launch()
2 changes: 1 addition & 1 deletion js/accordion/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
{...loading_status}
/>

<Accordion {label} initial_open={open}>
<Accordion {label} bind:open>
<Column>
<slot />
</Column>
Expand Down
16 changes: 4 additions & 12 deletions js/accordion/shared/Accordion.svelte
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
<script lang="ts">
import { writable } from "svelte/store";
export let initial_open = true;
export let open = true;
export let label = "";
let open = writable(initial_open);
const toggle_open = (): void => {
open.update((value) => !value);
};
</script>

<button on:click={toggle_open} class="label-wrap" class:open={$open}>
<button on:click={() => (open = !open)} class="label-wrap" class:open>
<span>{label}</span>
<span style:transform={$open ? "rotate(0)" : "rotate(90deg)"} class="icon">
<span style:transform={open ? "rotate(0)" : "rotate(90deg)"} class="icon">
</span>
</button>
<div style:display={$open ? "block" : "none"}>
<div style:display={open ? "block" : "none"}>
<slot />
</div>

Expand Down
73 changes: 64 additions & 9 deletions js/app/build_plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function generate_dev_entry({ enable }: { enable: boolean }): Plugin {

const new_code = code.replace(RE_SVELTE_IMPORT, (str, $1, $2) => {
return `const ${$1.replace(
" as ",
/ as /g,
": "
)} = window.__gradio__svelte__internal;`;
});
Expand Down Expand Up @@ -255,17 +255,35 @@ function generate_component_imports(): string {
return imports;
}

function load_virtual_component_loader(): string {
function load_virtual_component_loader(mode: string): string {
const loader_path = join(__dirname, "component_loader.js");
const component_map = `
const component_map = {
${generate_component_imports()}
};
`;
let component_map = "";

if (mode === "test") {
component_map = `
const component_map = {
"test-component-one": {
component: () => import("@gradio-test/test-one"),
example: () => import("@gradio-test/test-one/example")
},
"dataset": {
component: () => import("@gradio-test/test-two"),
example: () => import("@gradio-test/test-two/example")
}
};
`;
} else {
component_map = `
const component_map = {
${generate_component_imports()}
};
`;
}

return `${component_map}\n\n${readFileSync(loader_path, "utf8")}`;
}

export function inject_component_loader(): Plugin {
export function inject_component_loader({ mode }: { mode: string }): Plugin {
const v_id = "virtual:component-loader";
const resolved_v_id = "\0" + v_id;

Expand All @@ -276,8 +294,9 @@ export function inject_component_loader(): Plugin {
if (id === v_id) return resolved_v_id;
},
load(id: string) {
this.addWatchFile(join(__dirname, "component_loader.js"));
if (id === resolved_v_id) {
return load_virtual_component_loader();
return load_virtual_component_loader(mode);
}
}
};
Expand Down Expand Up @@ -311,3 +330,39 @@ export function resolve_svelte(enable: boolean): Plugin {
}
};
}

export function mock_modules(): Plugin {
const v_id_1 = "@gradio-test/test-one";
const v_id_2 = "@gradio-test/test-two";
const v_id_1_example = "@gradio-test/test-one/example";
const v_id_2_example = "@gradio-test/test-two/example";
const resolved_v_id = "\0" + v_id_1;
const resolved_v_id_2 = "\0" + v_id_2;
const resolved_v_id_1_example = "\0" + v_id_1_example;
const resolved_v_id_2_example = "\0" + v_id_2_example;
const fallback_example = "@gradio/fallback/example";
const resolved_fallback_example = "\0" + fallback_example;

return {
name: "mock-modules",
enforce: "pre",
resolveId(id: string) {
if (id === v_id_1) return resolved_v_id;
if (id === v_id_2) return resolved_v_id_2;
if (id === v_id_1_example) return resolved_v_id_1_example;
if (id === v_id_2_example) return resolved_v_id_2_example;
if (id === fallback_example) return resolved_fallback_example;
},
load(id: string) {
if (
id === resolved_v_id ||
id === resolved_v_id_2 ||
id === resolved_v_id_1_example ||
id === resolved_v_id_2_example ||
id === resolved_fallback_example
) {
return `export default {}`;
}
}
};
}
41 changes: 32 additions & 9 deletions js/app/component_loader.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// @ts-nocheck

export async function load_component({ api_url, name, id, variant }) {
const request_map = {};

export function load_component({ api_url, name, id, variant }) {
const comps = window.__GRADIO__CC__;

const _component_map = {
Expand All @@ -9,31 +11,41 @@ export async function load_component({ api_url, name, id, variant }) {
...(!comps ? {} : comps)
};

if (request_map[`${id}-${variant}`]) {
return { component: request_map[`${id}-${variant}`], name };
}
try {
const c = await (
if (!_component_map?.[id]?.[variant] && !_component_map?.[name]?.[variant])
throw new Error();

request_map[`${id}-${variant}`] = (
_component_map?.[id]?.[variant] || // for dev mode custom components
_component_map?.[name]?.[variant]
)();

return {
name,
component: c
component: request_map[`${id}-${variant}`]
};
} catch (e) {
console.error(e);
try {
await load_css(`${api_url}/custom_component/${id}/${variant}/style.css`);
const c = await import(
/* @vite-ignore */ `${api_url}/custom_component/${id}/${variant}/index.js`
request_map[`${id}-${variant}`] = get_component_with_css(
api_url,
id,
variant
);

return {
name,
component: c
component: request_map[`${id}-${variant}`]
};
} catch (e) {
if (variant === "example") {
request_map[`${id}-${variant}`] = import("@gradio/fallback/example");

return {
name,
component: await import("@gradio/fallback/example")
component: request_map[`${id}-${variant}`]
};
}
console.error(`failed to load: ${name}`);
Expand All @@ -53,3 +65,14 @@ function load_css(url) {
link.onerror = () => reject();
});
}

function get_component_with_css(api_url, id, variant) {
return Promise.all([
load_css(`${api_url}/custom_component/${id}/${variant}/style.css`),
import(
/* @vite-ignore */ `${api_url}/custom_component/${id}/${variant}/index.js`
)
]).then(([_, module]) => {
return module;
});
}
Loading

0 comments on commit 5d1e8da

Please sign in to comment.