batch UI updates on a per frame basis (#7564)

* 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>
This commit is contained in:
pngwn 2024-03-13 13:46:40 +00:00 committed by GitHub
parent aba44707af
commit 5d1e8dae5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1570 additions and 628 deletions

View File

@ -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

View File

@ -16,7 +16,7 @@ const base = defineConfig({
}
},
expect: { timeout: 15000 },
timeout: 15000,
timeout: 30000,
testMatch: /.*.spec.ts/,
testDir: "..",
workers: process.env.CI ? 1 : undefined
@ -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

View File

@ -42,7 +42,7 @@ type predict = (
event_data?: unknown
) => Promise<unknown>;
type client_return = {
export type client_return = {
predict: predict;
config: Config;
submit: (
@ -194,6 +194,7 @@ export function api_factory(
if (token) {
headers.Authorization = `Bearer ${token}`;
}
try {
var response = await fetch_implementation(url, {
method: "POST",

View File

@ -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";

View File

@ -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}

View File

@ -9,18 +9,32 @@ with gr.Blocks() as demo:
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()

View File

@ -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}

View File

@ -1,8 +1,10 @@
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")
@ -10,4 +12,4 @@ with gr.Blocks() as demo:
greet_btn.click(fn=greet, inputs=name, outputs=output, api_name="greet")
if __name__ == "__main__":
demo.launch()
demo.launch()

View File

@ -23,7 +23,7 @@
{...loading_status}
/>
<Accordion {label} initial_open={open}>
<Accordion {label} bind:open>
<Column>
<slot />
</Column>

View File

@ -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>

View File

@ -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;`;
});
@ -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;
@ -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);
}
}
};
@ -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 {}`;
}
}
};
}

View File

@ -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 = {
@ -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}`);
@ -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;
});
}

View File

@ -1,14 +1,12 @@
<script lang="ts">
import { load_component } from "virtual:component-loader";
import { tick } from "svelte";
import { _ } from "svelte-i18n";
import type { client } from "@gradio/client";
import { client } from "@gradio/client";
import { create_loading_status_store } from "./stores";
import type { LoadingStatusCollection } from "./stores";
import type { ComponentMeta, Dependency, LayoutNode } from "./types";
import type { UpdateTransaction } from "./init";
import { setupi18n } from "./i18n";
import { ApiDocs } from "./api_docs/";
import type { ThemeMode, Payload } from "./types";
@ -19,6 +17,7 @@
import logo from "./images/logo.svg";
import api_logo from "./api_docs/img/api-logo.svg";
import { create_components, AsyncFunction } from "./init";
setupi18n();
@ -40,38 +39,23 @@
export let version: string;
export let js: string | null;
export let fill_height = false;
export let ready: boolean;
let loading_status = create_loading_status_store();
let rootNode: ComponentMeta = {
id: layout.id,
type: "column",
props: { interactive: false, scale: fill_height ? 1 : null },
has_modes: false,
instance: null as unknown as ComponentMeta["instance"],
component: null as unknown as ComponentMeta["component"],
component_class_id: ""
};
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
dependencies.forEach((d) => {
if (d.js) {
const wrap = d.backend_fn
? d.inputs.length === 1
: d.outputs.length === 1;
try {
d.frontend_fn = new AsyncFunction(
"__fn_args",
`let result = await (${d.js})(...__fn_args);
return (${wrap} && !Array.isArray(result)) ? [result] : result;`
);
} catch (e) {
console.error("Could not parse custom js method.");
console.error(e);
}
}
const {
layout: _layout,
targets,
update_value,
get_data,
loading_status,
scheduled_updates
} = create_components(components, layout, dependencies, root, app, {
fill_height
});
$: {
ready = !!$_layout;
}
let params = new URLSearchParams(window.location.search);
let api_docs_visible = params.get("view") === "api" && show_api;
function set_api_docs_visible(visible: boolean): void {
@ -85,276 +69,25 @@
history.replaceState(null, "", "?" + params.toString());
}
function is_dep(
id: number,
type: "inputs" | "outputs",
deps: Dependency[]
): boolean {
for (const dep of deps) {
for (const dep_item of dep[type]) {
if (dep_item === id) return true;
}
}
return false;
}
let dynamic_ids: Set<number> = new Set();
function has_no_default_value(value: any): boolean {
return (
(Array.isArray(value) && value.length === 0) ||
value === "" ||
value === 0 ||
!value
);
}
let instance_map: { [id: number]: ComponentMeta };
type LoadedComponent = {
default: ComponentMeta["component"];
};
let component_set = new Set<
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
>();
let _component_map = new Map<
`${ComponentMeta["type"]}_${ComponentMeta["props"]["interactive"]}`,
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
>();
async function walk_layout(
node: LayoutNode,
type_map: Map<number, ComponentMeta["props"]["interactive"]>,
instance_map: { [id: number]: ComponentMeta },
component_map: Map<
`${ComponentMeta["type"]}_${ComponentMeta["props"]["interactive"]}`,
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
>
): Promise<void> {
ready = false;
let instance = instance_map[node.id];
const _component = (await component_map.get(
`${instance.type}_${type_map.get(node.id) || "false"}`
))!.component;
instance.component = _component.default;
if (node.children) {
instance.children = node.children.map((v) => instance_map[v.id]);
await Promise.all(
node.children.map((v) =>
walk_layout(v, type_map, instance_map, component_map)
)
);
}
}
export let ready = false;
export let render_complete = false;
$: components, layout, prepare_components();
let target_map: Record<number, Record<string, number[]>> = {};
function prepare_components(): void {
target_map = dependencies.reduce(
(acc, dep, i) => {
dep.targets.forEach(([id, trigger]) => {
if (!acc[id]) {
acc[id] = {};
}
if (acc[id]?.[trigger]) {
acc[id][trigger].push(i);
} else {
acc[id][trigger] = [i];
}
});
return acc;
},
{} as Record<number, Record<string, number[]>>
);
loading_status = create_loading_status_store();
dependencies.forEach((v, i) => {
loading_status.register(i, v.inputs, v.outputs);
});
const _dynamic_ids = new Set<number>();
for (const comp of components) {
const { id, props } = comp;
const is_input = is_dep(id, "inputs", dependencies);
if (
is_input ||
(!is_dep(id, "outputs", dependencies) &&
has_no_default_value(props?.value))
) {
_dynamic_ids.add(id);
}
}
dynamic_ids = _dynamic_ids;
const _rootNode: typeof rootNode = {
id: layout.id,
type: "column",
props: { interactive: false, scale: fill_height ? 1 : null },
has_modes: false,
instance: null as unknown as ComponentMeta["instance"],
component: null as unknown as ComponentMeta["component"],
component_class_id: ""
};
components.push(_rootNode);
const _component_set = new Set<
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
>();
const __component_map = new Map<
`${ComponentMeta["type"]}_${ComponentMeta["props"]["interactive"]}`,
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
>();
const __type_for_id = new Map<
number,
ComponentMeta["props"]["interactive"]
>();
const _instance_map = components.reduce(
(acc, next) => {
acc[next.id] = next;
return acc;
},
{} as { [id: number]: ComponentMeta }
);
components.forEach((c) => {
if ((c.props as any).interactive === false) {
(c.props as any).interactive = false;
} else if ((c.props as any).interactive === true) {
(c.props as any).interactive = true;
} else if (dynamic_ids.has(c.id)) {
(c.props as any).interactive = true;
} else {
(c.props as any).interactive = false;
}
if ((c.props as any).server_fns) {
let server: Record<string, (...args: any[]) => Promise<any>> = {};
(c.props as any).server_fns.forEach((fn: string) => {
server[fn] = async (...args: any[]) => {
if (args.length === 1) {
args = args[0];
}
const result = await app.component_server(c.id, fn, args);
return result;
};
});
(c.props as any).server = server;
}
if (target_map[c.id]) {
c.props.attached_events = Object.keys(target_map[c.id]);
}
__type_for_id.set(c.id, c.props.interactive);
if (c.type === "dataset") {
const example_component_map = new Map();
(c.props.components as string[]).forEach((name: string) => {
if (example_component_map.has(name)) {
return;
}
let _c;
const matching_component = components.find((c) => c.type === name);
if (matching_component) {
_c = load_component({
api_url: root,
name,
id: matching_component.component_class_id,
variant: "example"
});
example_component_map.set(name, _c);
}
});
c.props.component_map = example_component_map;
}
// maybe load custom
const _c = load_component({
api_url: root,
name: c.type,
id: c.component_class_id,
variant: "component"
});
_component_set.add(_c);
__component_map.set(`${c.type}_${c.props.interactive}`, _c);
});
Promise.all(Array.from(_component_set)).then(() => {
walk_layout(layout, __type_for_id, _instance_map, __component_map)
.then(async () => {
ready = true;
component_set = _component_set;
_component_map = __component_map;
instance_map = _instance_map;
rootNode = _rootNode;
})
.catch((e) => {
console.error(e);
});
});
}
function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): (...funcArgs: Parameters<T>) => void {
let lastFunc: ReturnType<typeof setTimeout>;
let lastRan: number;
let lastThis: any;
let lastArgs: IArguments | null;
return function (this: any, ...args: Parameters<T>) {
if (!lastRan) {
func.apply(this, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastThis = this;
lastArgs = arguments;
lastFunc = setTimeout(
() => {
if (Date.now() - lastRan >= limit) {
if (lastArgs) {
func.apply(lastThis, Array.prototype.slice.call(lastArgs));
}
lastRan = Date.now();
}
},
Math.max(limit - (Date.now() - lastRan), 0)
);
}
};
}
const refresh = throttle(() => {
rootNode = rootNode;
}, 50);
async function handle_update(data: any, fn_index: number): Promise<void> {
const outputs = dependencies[fn_index].outputs;
data?.forEach((value: any, i: number) => {
const output = instance_map[outputs[i]];
output.props.value_is_output = true;
const meta_updates = data?.map((value: any, i: number) => {
return {
id: outputs[i],
prop: "value_is_output",
value: true
};
});
refresh();
update_value(meta_updates);
await tick();
const updates: UpdateTransaction[] = [];
data?.forEach((value: any, i: number) => {
const output = instance_map[outputs[i]];
if (
typeof value === "object" &&
value !== null &&
@ -364,30 +97,26 @@
if (update_key === "__type__") {
continue;
} else {
output.props[update_key] = update_value;
updates.push({
id: outputs[i],
prop: update_key,
value: update_value
});
}
}
} else {
output.props.value = value;
updates.push({
id: outputs[i],
prop: "value",
value
});
}
});
refresh();
update_value(updates);
}
let submit_map: Map<number, ReturnType<typeof app.submit>> = new Map();
function set_prop<T extends ComponentMeta>(
obj: T,
prop: string,
val: any
): void {
if (!obj?.props) {
// @ts-ignore
obj.props = {};
}
obj.props[prop] = val;
refresh();
}
let handled_dependencies: number[][] = [];
let messages: (ToastMessage & { fn_index: number })[] = [];
@ -427,11 +156,26 @@
let showed_duplicate_message = false;
let showed_mobile_warning = false;
function get_data(comp: ComponentMeta): any | Promise<any> {
if (comp.instance.get_value) {
return comp.instance.get_value() as Promise<any>;
// as state updates are not synchronous, we need to ensure updates are flushed before triggering any requests
function wait_then_trigger_api_call(
dep_index: number,
trigger_id: number | null = null,
event_data: unknown = null
): void {
let _unsub = (): void => {};
function unsub(): void {
_unsub();
}
if ($scheduled_updates) {
_unsub = scheduled_updates.subscribe((updating) => {
if (!updating) {
trigger_api_call(dep_index, trigger_id, event_data);
unsub();
}
});
} else {
trigger_api_call(dep_index, trigger_id, event_data);
}
return comp.props.value;
}
async function trigger_api_call(
@ -440,6 +184,7 @@
event_data: unknown = null
): Promise<void> {
let dep = dependencies[dep_index];
const current_status = loading_status.get_status_for_fn(dep_index);
messages = messages.filter(({ fn_index }) => fn_index !== dep_index);
if (dep.cancels) {
@ -457,9 +202,7 @@
let payload: Payload = {
fn_index: dep_index,
data: await Promise.all(
dep.inputs.map((id) => get_data(instance_map[id]))
),
data: await Promise.all(dep.inputs.map((id) => get_data(id))),
event_data: dep.collects_event_data ? event_data : null,
trigger_id: trigger_id
};
@ -468,9 +211,7 @@
dep
.frontend_fn(
payload.data.concat(
await Promise.all(
dep.inputs.map((id) => get_data(instance_map[id]))
)
await Promise.all(dep.inputs.map((id) => get_data(id)))
)
)
.then((v: unknown[]) => {
@ -497,7 +238,7 @@
}
}
function make_prediction(payload: Payload): void {
async function make_prediction(payload: Payload): Promise<void> {
const submission = app
.submit(
payload.fn_index,
@ -514,7 +255,7 @@
handle_update(data, fn_index);
})
.on("status", ({ fn_index, ...status }) => {
tick().then(() => {
requestAnimationFrame(() => {
//@ts-ignore
loading_status.update({
...status,
@ -552,7 +293,7 @@
if (status.stage === "complete") {
dependencies.map(async (dep, i) => {
if (dep.trigger_after === fn_index) {
trigger_api_call(i, payload.trigger_id);
wait_then_trigger_api_call(i, payload.trigger_id);
}
});
@ -565,7 +306,11 @@
...messages
];
}, 0);
trigger_api_call(dep_index, payload.trigger_id, event_data);
wait_then_trigger_api_call(
dep_index,
payload.trigger_id,
event_data
);
user_left_page = false;
} else if (status.stage === "error") {
if (status.message) {
@ -583,7 +328,7 @@
dep.trigger_after === fn_index &&
!dep.trigger_only_on_success
) {
trigger_api_call(i, payload.trigger_id);
wait_then_trigger_api_call(i, payload.trigger_id);
}
});
@ -627,7 +372,7 @@
`let result = await (${js})();
return (!Array.isArray(result)) ? [result] : result;`
);
blocks_frontend_fn();
await blocks_frontend_fn();
}
await tick();
@ -646,11 +391,17 @@
// handle load triggers
dependencies.forEach((dep, i) => {
if (dep.targets[0][1] === "load") {
trigger_api_call(i);
wait_then_trigger_api_call(i);
}
});
if (render_complete) return;
target.addEventListener("prop_change", (e: Event) => {
if (!isCustomEvent(e)) throw new Error("not a custom event");
const { id, prop, value } = e.detail;
update_value([{ id, prop, value }]);
});
target.addEventListener("gradio", (e: Event) => {
if (!isCustomEvent(e)) throw new Error("not a custom event");
@ -662,9 +413,10 @@
} else if (event === "error" || event === "warning") {
messages = [new_message(data, -1, event), ...messages];
} else {
const deps = target_map[id]?.[event];
const deps = targets[id]?.[event];
deps?.forEach((dep_id) => {
trigger_api_call(dep_id, id, data);
wait_then_trigger_api_call(dep_id, id, data);
});
}
});
@ -681,18 +433,30 @@
$: set_status($loading_status);
function set_status(statuses: LoadingStatusCollection): void {
for (const id in statuses) {
let loading_status = statuses[id];
const updates = Object.entries(statuses).map(([id, loading_status]) => {
let dependency = dependencies[loading_status.fn_index];
loading_status.scroll_to_output = dependency.scroll_to_output;
loading_status.show_progress = dependency.show_progress;
return {
id: parseInt(id),
prop: "loading_status",
value: loading_status
};
});
set_prop(instance_map[id], "loading_status", loading_status);
}
const inputs_to_update = loading_status.get_inputs_to_update();
for (const [id, pending_status] of inputs_to_update) {
set_prop(instance_map[id], "pending", pending_status === "pending");
}
const additional_updates = Array.from(inputs_to_update).map(
([id, pending_status]) => {
return {
id,
prop: "pending",
value: pending_status === "pending"
};
}
);
update_value([...updates, ...additional_updates]);
}
function isCustomEvent(event: Event): event is CustomEvent {
@ -725,11 +489,9 @@
<div class="wrap" style:min-height={app_mode ? "100%" : "auto"}>
<div class="contain" style:flex-grow={app_mode ? "1" : "auto"}>
{#if ready}
{#if $_layout}
<MountComponents
{rootNode}
{dynamic_ids}
{instance_map}
rootNode={$_layout}
{root}
{target}
{theme_mode}
@ -768,7 +530,7 @@
{/if}
</div>
{#if api_docs_visible && ready}
{#if api_docs_visible && $_layout}
<div class="api-docs">
<!-- TODO: fix -->
<!-- svelte-ignore a11y-click-events-have-key-events-->
@ -781,10 +543,10 @@
/>
<div class="api-docs-wrap">
<ApiDocs
root_node={$_layout}
on:close={() => {
set_api_docs_visible(false);
}}
{instance_map}
{dependencies}
{root}
{app}

View File

@ -3,8 +3,6 @@
import Render from "./Render.svelte";
export let rootNode: any;
export let dynamic_ids: any;
export let instance_map: any;
export let root: any;
export let target: any;
export let theme_mode: any;
@ -17,16 +15,4 @@
});
</script>
<Render
component={rootNode.component}
id={rootNode.id}
props={rootNode.props}
children={rootNode.children}
{dynamic_ids}
{instance_map}
{root}
{target}
{theme_mode}
{version}
{autoscroll}
/>
<Render node={rootNode} {root} {target} {theme_mode} {version} {autoscroll} />

View File

@ -2,16 +2,11 @@
import { Gradio } from "./gradio_helper";
import { onMount, createEventDispatcher, setContext } from "svelte";
import type { ComponentMeta, ThemeMode } from "./types";
import RenderComponent from "./RenderComponent.svelte";
export let root: string;
export let component: ComponentMeta["component"];
export let instance_map: Record<number, ComponentMeta>;
export let id: number;
export let props: ComponentMeta["props"];
export let children: ComponentMeta["children"];
export let dynamic_ids: Set<number>;
export let node: ComponentMeta;
export let parent: string | null = null;
export let target: HTMLElement;
export let theme_mode: ThemeMode;
@ -22,14 +17,14 @@
let filtered_children: ComponentMeta[] = [];
onMount(() => {
dispatch("mount", id);
dispatch("mount", node.id);
for (const child of filtered_children) {
dispatch("mount", child.id);
}
return () => {
dispatch("destroy", id);
dispatch("destroy", node.id);
for (const child of filtered_children) {
dispatch("mount", child.id);
@ -37,10 +32,10 @@
};
});
$: children =
children &&
children.filter((v) => {
const valid_node = instance_map[v.id].type !== "statustracker";
$: node.children =
node.children &&
node.children.filter((v) => {
const valid_node = node.type !== "statustracker";
if (!valid_node) {
filtered_children.push(v);
}
@ -50,51 +45,49 @@
setContext("BLOCK_KEY", parent);
function handle_prop_change(e: { detail: Record<string, any> }): void {
for (const k in e.detail) {
instance_map[id].props[k] = e.detail[k];
}
// for (const k in e.detail) {
// instance_map[id].props[k] = e.detail[k];
// }
}
$: {
if (instance_map[id].type === "form") {
if (children?.every((c) => !c.props.visible)) {
props.visible = false;
if (node.type === "form") {
if (node.children?.every((c) => !c.props.visible)) {
node.props.visible = false;
} else {
props.visible = true;
node.props.visible = true;
}
}
}
</script>
<svelte:component
this={component}
bind:this={instance_map[id].instance}
bind:value={instance_map[id].props.value}
elem_id={("elem_id" in props && props.elem_id) || `component-${id}`}
elem_classes={("elem_classes" in props && props.elem_classes) || []}
<RenderComponent
id={node.id}
component={node.component}
bind:instance={node.instance}
bind:value={node.props.value}
elem_id={("elem_id" in node.props && node.props.elem_id) ||
`component-${node.id}`}
elem_classes={("elem_classes" in node.props && node.props.elem_classes) || []}
on:prop_change={handle_prop_change}
{target}
{...props}
{...node.props}
{theme_mode}
{root}
gradio={new Gradio(id, target, theme_mode, version, root, autoscroll)}
gradio={new Gradio(node.id, target, theme_mode, version, root, autoscroll)}
>
{#if children && children.length}
{#each children as { component, id: each_id, props, children: _children, has_modes } (each_id)}
{#if node.children && node.children.length}
{#each node.children as _node (_node.id)}
<svelte:self
{component}
node={_node}
component={_node.component}
{target}
id={each_id}
{props}
id={_node.id}
{root}
{instance_map}
children={_children}
{dynamic_ids}
{has_modes}
{theme_mode}
on:destroy
on:mount
/>
{/each}
{/if}
</svelte:component>
</RenderComponent>

View File

@ -0,0 +1,69 @@
<svelte:options immutable={true} />
<script lang="ts">
import type { Gradio } from "./gradio_helper";
import type { ComponentMeta, ThemeMode } from "./types";
import type { SvelteComponent, ComponentType } from "svelte";
// @ts-ignore
import { bind, binding_callbacks } from "svelte/internal";
export let root: string;
export let component: ComponentMeta["component"];
export let target: HTMLElement;
export let theme_mode: ThemeMode;
export let instance: ComponentMeta["instance"];
export let value: any;
export let gradio: Gradio;
export let elem_id: string;
export let elem_classes: string[];
export let id: number;
const s = (id: number, p: string, v: any): CustomEvent =>
new CustomEvent("prop_change", { detail: { id, prop: p, value: v } });
function wrap(
component: ComponentType<SvelteComponent>
): ComponentType<SvelteComponent> {
const ProxiedMyClass = new Proxy(component, {
construct(_target, args: Record<string, any>[]) {
//@ts-ignore
const instance = new _target(...args);
const props = Object.getOwnPropertyNames(instance).filter(
(s) => !s.startsWith("$")
);
function report(props: string) {
return function (propargs: any) {
const ev = s(id, props, propargs);
target.dispatchEvent(ev);
};
}
props.forEach((v) => {
binding_callbacks.push(() => bind(instance, v, report(v)));
});
return instance;
}
});
return ProxiedMyClass;
}
const _component = wrap(component);
</script>
<svelte:component
this={_component}
bind:this={instance}
bind:value
on:prop_change
{elem_id}
{elem_classes}
{target}
{...$$restProps}
{theme_mode}
{root}
{gradio}
>
<slot />
</svelte:component>

View File

@ -16,14 +16,11 @@
import python from "./img/python.svg";
import javascript from "./img/javascript.svg";
export let instance_map: {
[id: number]: ComponentMeta;
};
export let dependencies: Dependency[];
export let root: string;
export let app: Awaited<ReturnType<typeof client>>;
export let space_id: string | null;
export let root_node: ComponentMeta;
const js_docs =
"https://www.gradio.app/guides/getting-started-with-the-js-client";
const py_docs =
@ -50,9 +47,27 @@
let is_running = false;
function find_recursive(
node: ComponentMeta,
id: number
): ComponentMeta | null {
if (node.id === id) {
return node;
}
if (node.children) {
for (let child of node.children) {
let result = find_recursive(child, id);
if (result) {
return result;
}
}
}
return null;
}
let dependency_inputs = dependencies.map((dependency) =>
dependency.inputs.map((_id) => {
let default_data = instance_map[_id].documentation?.example_data;
let default_data = find_recursive(root_node, _id)?.props?.default;
if (default_data === undefined) {
default_data = "";
} else if (typeof default_data === "object") {
@ -103,10 +118,9 @@
let dependency = dependencies[index];
let attempted_component_index = 0;
try {
var inputs = dependency_inputs[index].map((input_val, i) => {
var inputs = dependency_inputs[index].map((input_val: any, i: number) => {
attempted_component_index = i;
let component = instance_map[dependency.inputs[i]];
// @ts-ignore
let component = find_recursive(root_node, dependency.inputs[i])!;
input_val = represent_value(
input_val,
component.documentation?.type?.input_payload ||
@ -130,7 +144,7 @@
if (status_code == 200) {
dependency_outputs[index] = response.data.map(
(output_val: any, i: number) => {
let component = instance_map[dependency.outputs[i]];
let component = find_recursive(root_node, dependency.outputs[i])!;
return represent_value(
output_val,

View File

@ -35,6 +35,7 @@ export class Gradio<T extends Record<string, any> = Record<string, any>> {
bubbles: true,
detail: { data, id: this.#id, event: event_name }
});
this.#el.dispatchEvent(e);
}
}

521
js/app/src/init.test.ts Normal file
View File

@ -0,0 +1,521 @@
import { describe, test, expect, vi } from "vitest";
import { spy } from "tinyspy";
import { setupServer } from "msw/node";
import { http, HttpResponse } from "msw";
import type { client_return } from "@gradio/client";
import { Dependency, TargetMap } from "./types";
import {
process_frontend_fn,
create_target_meta,
determine_interactivity,
process_server_fn,
get_component
} from "./init";
describe("process_frontend_fn", () => {
test("empty source code returns null", () => {
const source = "";
const fn = process_frontend_fn(source, false, 1, 1);
expect(fn).toBe(null);
});
test("falsey source code returns null: false", () => {
const source = false;
const fn = process_frontend_fn(source, false, 1, 1);
expect(fn).toBe(null);
});
test("falsey source code returns null: undefined", () => {
const source = undefined;
const fn = process_frontend_fn(source, false, 1, 1);
expect(fn).toBe(null);
});
test("falsey source code returns null: null", () => {
const source = null;
const fn = process_frontend_fn(source, false, 1, 1);
expect(fn).toBe(null);
});
test("source code returns a function", () => {
const source = "(arg) => arg";
const fn = process_frontend_fn(source, false, 1, 1);
expect(typeof fn).toBe("function");
});
test("arrays of values can be passed to the generated function", async () => {
const source = "(arg) => arg";
const fn = process_frontend_fn(source, false, 1, 1);
if (fn) {
await expect(fn([1])).resolves.toEqual([1]);
}
});
test("arrays of many values can be passed", async () => {
const source = "(...args) => args";
const fn = process_frontend_fn(source, false, 1, 1);
if (fn) {
await expect(fn([1, 2, 3, 4, 5, 6])).resolves.toEqual([1, 2, 3, 4, 5, 6]);
}
});
test("The generated function returns a promise", () => {
const source = "(arg) => arg";
const fn = process_frontend_fn(source, false, 1, 1);
if (fn) {
expect(fn([1])).toBeInstanceOf(Promise);
}
});
test("The generated function is callable and returns the expected value", async () => {
const source = "(arg) => arg";
const fn = process_frontend_fn(source, false, 1, 1);
if (fn) {
await expect(fn([1])).resolves.toEqual([1]);
}
});
test("The return value of the function is wrapped in an array if there is no backend function and the input length is 1", async () => {
const source = "(arg) => arg";
const fn = process_frontend_fn(source, false, 1, 1);
if (fn) {
await expect(fn([1])).resolves.toEqual([1]);
}
});
test("The return value of the function is not wrapped in an array if there is no backend function and the input length is greater than 1", async () => {
const source = "(arg) => arg";
const fn = process_frontend_fn(source, false, 2, 2);
if (fn) {
await expect(fn([1])).resolves.toEqual(1);
}
});
test("The return value of the function is wrapped in an array if there is a backend function and the input length is 1", async () => {
const source = "(arg) => arg";
const fn = process_frontend_fn(source, true, 1, 1);
if (fn) {
await expect(fn([1])).resolves.toEqual([1]);
}
});
test("The return value of the function is not wrapped in an array if there is a backend function and the input length is greater than 1", async () => {
const source = "(arg) => arg";
const fn = process_frontend_fn(source, true, 2, 2);
if (fn) {
await expect(fn([1])).resolves.toEqual(1);
}
});
});
describe("create_target_meta", () => {
test("creates a target map", () => {
const targets: Dependency["targets"] = [
[1, "change"],
[2, "input"],
[3, "load"]
];
const fn_index = 0;
const target_map = {};
const result = create_target_meta(targets, fn_index, target_map);
expect(result).toEqual({
1: { change: [0] },
2: { input: [0] },
3: { load: [0] }
});
});
test("if the target already exists, it adds the new trigger to the list", () => {
const targets: Dependency["targets"] = [
[1, "change"],
[1, "input"],
[1, "load"]
];
const fn_index = 1;
const target_map: TargetMap = {
1: { change: [0] }
};
const result = create_target_meta(targets, fn_index, target_map);
expect(result).toEqual({
1: { change: [0, 1], input: [1], load: [1] }
});
});
test("if the trigger already exists, it adds the new function to the list", () => {
const targets: Dependency["targets"] = [
[1, "change"],
[2, "change"],
[3, "change"]
];
const fn_index = 1;
const target_map: TargetMap = {
1: { change: [0] },
2: { change: [0] },
3: { change: [0] }
};
const result = create_target_meta(targets, fn_index, target_map);
expect(result).toEqual({
1: { change: [0, 1] },
2: { change: [0, 1] },
3: { change: [0, 1] }
});
});
test("if the target and trigger already exist, it adds the new function to the list", () => {
const targets: Dependency["targets"] = [[1, "change"]];
const fn_index = 1;
const target_map: TargetMap = {
1: { change: [0] }
};
const result = create_target_meta(targets, fn_index, target_map);
expect(result).toEqual({
1: { change: [0, 1] }
});
});
test("if the target, trigger and function id already exist, it does not add duplicates", () => {
const targets: Dependency["targets"] = [[1, "change"]];
const fn_index = 0;
const target_map: TargetMap = {
1: { change: [0] }
};
const result = create_target_meta(targets, fn_index, target_map);
expect(result).toEqual({
1: { change: [0] }
});
});
});
describe("determine_interactivity", () => {
test("returns true if the prop is interactive = true", () => {
const result = determine_interactivity(
0,
true,
"hi",
new Set([0]),
new Set([2])
);
expect(result).toBe(true);
});
test("returns false if the prop is interactive = false", () => {
const result = determine_interactivity(
0,
false,
"hi",
new Set([0]),
new Set([2])
);
expect(result).toBe(false);
});
test("returns true if the component is an input", () => {
const result = determine_interactivity(
0,
undefined,
"hi",
new Set([0]),
new Set([2])
);
expect(result).toBe(true);
});
test("returns true if the component is not an input or output and the component has no default value: empty string", () => {
const result = determine_interactivity(
2,
undefined,
"",
new Set([0]),
new Set([1])
);
expect(result).toBe(true);
});
test("returns true if the component is not an input or output and the component has no default value: empty array", () => {
const result = determine_interactivity(
2,
undefined,
[],
new Set([0]),
new Set([1])
);
expect(result).toBe(true);
});
test("returns true if the component is not an input or output and the component has no default value: boolean", () => {
const result = determine_interactivity(
2,
undefined,
false,
new Set([0]),
new Set([1])
);
expect(result).toBe(true);
});
test("returns true if the component is not an input or output and the component has no default value: undefined", () => {
const result = determine_interactivity(
2,
undefined,
undefined,
new Set([0]),
new Set([1])
);
expect(result).toBe(true);
});
test("returns true if the component is not an input or output and the component has no default value: null", () => {
const result = determine_interactivity(
2,
undefined,
null,
new Set([0]),
new Set([1])
);
expect(result).toBe(true);
});
test("returns true if the component is not an input or output and the component has no default value: 0", () => {
const result = determine_interactivity(
2,
undefined,
0,
new Set([0]),
new Set([1])
);
expect(result).toBe(true);
});
test("returns false if the component is not an input or output and the component has a default value", () => {
const result = determine_interactivity(
2,
undefined,
"hello",
new Set([0]),
new Set([1])
);
expect(result).toBe(false);
});
});
describe("process_server_fn", () => {
test("returns an object", () => {
const result = process_server_fn(1, ["fn1", "fn2"], {} as any);
expect(result).toBeTypeOf("object");
});
test("returns an object with the correct keys", () => {
const result = process_server_fn(1, ["fn1", "fn2"], {} as any);
expect(Object.keys(result)).toEqual(["fn1", "fn2"]);
});
test("returns an object with the correct keys and values", () => {
const app = {
component_server: async (id: number, fn: string, args: any) => {
return args;
}
} as client_return;
const result = process_server_fn(1, ["fn1", "fn2"], app);
expect(Object.keys(result)).toEqual(["fn1", "fn2"]);
expect(result.fn1).toBeInstanceOf(Function);
expect(result.fn2).toBeInstanceOf(Function);
});
test("returned server functions should resolve to a promise", async () => {
const app = {
component_server: async (id: number, fn: string, args: any) => {
return args;
}
} as client_return;
const result = process_server_fn(1, ["fn1", "fn2"], app);
const response = result.fn1("hello");
expect(response).toBeInstanceOf(Promise);
});
test("the functions call the clients component_server function with the correct arguments ", async () => {
const mock = spy(async (id: number, fn: string, args: any) => {
return args;
});
const app = {
component_server: mock as any
} as client_return;
const result = process_server_fn(1, ["fn1", "fn2"], app as client_return);
const response = await result.fn1("hello");
expect(response).toBe("hello");
expect(mock.calls).toEqual([[1, "fn1", "hello"]]);
});
test("if there are no server functions, it returns an empty object", () => {
const result = process_server_fn(1, undefined, {} as any);
expect(result).toEqual({});
});
});
describe("get_component", () => {
test("returns an object", () => {
const result = get_component("test-component-one", "class_id", "root", []);
expect(result.component).toBeTypeOf("object");
});
test("returns an object with the correct keys", () => {
const result = get_component("test-component-one", "class_id", "root", []);
expect(Object.keys(result)).toEqual([
"component",
"name",
"example_components"
]);
});
test("the component key is a promise", () => {
const result = get_component("test-component-one", "class_id", "root", []);
expect(result.component).toBeInstanceOf(Promise);
});
test("the resolved component key is an object", async () => {
const result = get_component("test-component-one", "class_id", "root", []);
const o = await result.component;
expect(o).toBeTypeOf("object");
});
test("getting the same component twice should return the same promise", () => {
const result = get_component("test-component-one", "class_id", "root", []);
const result_two = get_component(
"test-component-one",
"class_id",
"root",
[]
);
expect(result.component).toBe(result_two.component);
});
test("if example components are not provided, the example_components key is undefined", async () => {
const result = get_component("dataset", "class_id", "root", []);
expect(result.example_components).toBe(undefined);
});
test("if the type is not a dataset, the example_components key is undefined", async () => {
const result = get_component("test-component-one", "class_id", "root", []);
expect(result.example_components).toBe(undefined);
});
test("when the type is a dataset, returns an object with the correct keys and values and example components", () => {
const result = get_component(
"dataset",
"class_id",
"root",
[
{
type: "test-component-one",
component_class_id: "example_class_id",
id: 1,
props: {
value: "hi",
interactive: false
},
has_modes: false,
instance: {} as any,
component: {} as any
}
],
["test-component-one"]
);
expect(result.component).toBeTypeOf("object");
expect(result.example_components).toBeInstanceOf(Map);
});
test("when example components are returned, returns an object with the correct keys and values and example components", () => {
const result = get_component(
"dataset",
"class_id",
"root",
[
{
type: "test-component-one",
component_class_id: "example_class_id",
id: 1,
props: {
value: "hi",
interactive: false
},
has_modes: false,
instance: {} as any,
component: {} as any
}
],
["test-component-one"]
);
expect(result.example_components?.get("test-component-one")).toBeTypeOf(
"object"
);
expect(result.example_components?.get("test-component-one")).toBeInstanceOf(
Promise
);
});
test("if the component is not found then it should request the component from the server", async () => {
const api_url = "example.com";
const id = "test-random";
const variant = "component";
const handlers = [
http.get(`${api_url}/custom_component/${id}/${variant}/style.css`, () => {
return new HttpResponse('console.log("boo")', {
status: 200,
headers: {
"Content-Type": "text/css"
}
});
})
];
// vi.mock calls are always hoisted out of the test function to the top of the file
// so we need to use vi.hoisted to hoist the mock function above the vi.mock call
const { mock } = vi.hoisted(() => {
return { mock: vi.fn() };
});
vi.mock(
`example.com/custom_component/test-random/component/index.js`,
async () => {
mock();
return {
default: {
default: "HELLO"
}
};
}
);
const server = setupServer(...handlers);
server.listen();
await get_component("test-random", id, api_url, []).component;
expect(mock).toHaveBeenCalled();
server.close();
});
});

438
js/app/src/init.ts Normal file
View File

@ -0,0 +1,438 @@
import { writable, type Writable } from "svelte/store";
import type {
ComponentMeta,
Dependency,
LayoutNode,
TargetMap,
LoadingComponent
} from "./types";
import { load_component } from "virtual:component-loader";
import type { client_return } from "@gradio/client";
import { create_loading_status_store } from "./stores";
export interface UpdateTransaction {
id: number;
value: any;
prop: string;
}
let pending_updates: UpdateTransaction[][] = [];
/**
* Create a store with the layout and a map of targets
* @param components An array of component metadata
* @param layout The layout tree
* @param dependencies The events, triggers, inputs, and outputs
* @param root The root url of the app
* @param app The client instance
* @param options Options for the layout
* @returns A store with the layout and a map of targets
*/
export function create_components(
components: ComponentMeta[],
layout: LayoutNode,
dependencies: Dependency[],
root: string,
app: client_return,
options: {
fill_height: boolean;
}
): {
layout: Writable<ComponentMeta>;
targets: TargetMap;
update_value: (updates: UpdateTransaction[]) => void;
get_data: (id: number) => any | Promise<any>;
loading_status: ReturnType<typeof create_loading_status_store>;
scheduled_updates: Writable<boolean>;
} {
const _component_map = new Map();
const target_map: TargetMap = {};
const inputs = new Set<number>();
const outputs = new Set<number>();
const _rootNode: ComponentMeta = {
id: layout.id,
type: "column",
props: { interactive: false, scale: options.fill_height ? 1 : null },
has_modes: false,
instance: null as unknown as ComponentMeta["instance"],
component: null as unknown as ComponentMeta["component"],
component_class_id: ""
};
components.push(_rootNode);
const loading_status = create_loading_status_store();
dependencies.forEach((dep, fn_index) => {
loading_status.register(fn_index, dep.inputs, dep.outputs);
dep.frontend_fn = process_frontend_fn(
dep.js,
!!dep.backend_fn,
dep.inputs.length,
dep.outputs.length
);
create_target_meta(dep.targets, fn_index, target_map);
get_inputs_outputs(dep, inputs, outputs);
});
const constructor_map = preload_all_components(components, root);
let instance_map: { [id: number]: ComponentMeta } = components.reduce(
(acc, c) => {
acc[c.id] = c;
return acc;
},
{} as { [id: number]: ComponentMeta }
);
async function walk_layout(node: LayoutNode): Promise<ComponentMeta> {
const instance = instance_map[node.id];
instance.component = (await constructor_map.get(
instance.component_class_id
))!?.default;
if (instance.type === "dataset") {
instance.props.component_map = get_component(
instance.type,
instance.component_class_id,
root,
components,
instance.props.components
).example_components;
}
if (target_map[instance.id]) {
instance.props.attached_events = Object.keys(target_map[instance.id]);
}
instance.props.interactive = determine_interactivity(
instance.id,
instance.props.interactive,
instance.props.value,
inputs,
outputs
);
instance.props.server = process_server_fn(
instance.id,
instance.props.server_fns,
app
);
_component_map.set(instance.id, instance);
if (node.children) {
instance.children = await Promise.all(
node.children.map((v) => walk_layout(v))
);
}
return instance;
}
const layout_store: Writable<ComponentMeta> = writable();
walk_layout(layout).then(() => {
layout_store.set(_rootNode);
});
let update_scheduled = false;
let update_scheduled_store = writable(false);
function flush(): void {
layout_store.update((layout) => {
for (let i = 0; i < pending_updates.length; i++) {
for (let j = 0; j < pending_updates[i].length; j++) {
const update = pending_updates[i][j];
const instance = instance_map[update.id];
let new_value;
if (Array.isArray(update.value)) new_value = [...update.value];
else if (update.value === null) new_value = null;
else if (typeof update.value === "object")
new_value = { ...update.value };
else new_value = update.value;
instance.props[update.prop] = new_value;
}
}
return layout;
});
pending_updates = [];
update_scheduled = false;
update_scheduled_store.set(false);
}
function update_value(updates: UpdateTransaction[]): void {
pending_updates.push(updates);
if (!update_scheduled) {
update_scheduled = true;
update_scheduled_store.set(true);
requestAnimationFrame(flush);
}
}
function get_data(id: number): any | Promise<any> {
const comp = _component_map.get(id);
if (comp.instance.get_value) {
return comp.instance.get_value() as Promise<any>;
}
return comp.props.value;
}
return {
layout: layout_store,
targets: target_map,
update_value,
get_data,
loading_status,
scheduled_updates: update_scheduled_store
};
}
/** An async version of 'new Function' */
export const AsyncFunction: new (
...args: string[]
) => (...args: any[]) => Promise<any> = Object.getPrototypeOf(
async function () {}
).constructor;
/**
* Takes a string of source code and returns a function that can be called with arguments
* @param source the source code
* @param backend_fn if there is also a backend function
* @param input_length the number of inputs
* @param output_length the number of outputs
* @returns The function, or null if the source code is invalid or missing
*/
export function process_frontend_fn(
source: string | null | undefined | false,
backend_fn: boolean,
input_length: number,
output_length: number
): ((...args: unknown[]) => Promise<unknown[]>) | null {
if (!source) return null;
const wrap = backend_fn ? input_length === 1 : output_length === 1;
try {
return new AsyncFunction(
"__fn_args",
` let result = await (${source})(...__fn_args);
return (${wrap} && !Array.isArray(result)) ? [result] : result;`
);
} catch (e) {
console.error("Could not parse custom js method.");
console.error(e);
return null;
}
}
/**
* `Dependency.targets` is an array of `[id, trigger]` pairs with the indices as the `fn_index`.
* This function take a single list of `Dependency.targets` and add those to the target_map.
* @param targets the targets array
* @param fn_index the function index
* @param target_map the target map
* @returns the tagert map
*/
export function create_target_meta(
targets: Dependency["targets"],
fn_index: number,
target_map: TargetMap
): TargetMap {
targets.forEach(([id, trigger]) => {
if (!target_map[id]) {
target_map[id] = {};
}
if (
target_map[id]?.[trigger] &&
!target_map[id]?.[trigger].includes(fn_index)
) {
target_map[id][trigger].push(fn_index);
} else {
target_map[id][trigger] = [fn_index];
}
});
return target_map;
}
/**
* Get all component ids that are an input or output of a dependency
* @param dep the dependency
* @param inputs the set of inputs
* @param outputs the set of outputs
* @returns a tuple of the inputs and outputs
*/
export function get_inputs_outputs(
dep: Dependency,
inputs: Set<number>,
outputs: Set<number>
): [Set<number>, Set<number>] {
dep.inputs.forEach((input) => inputs.add(input));
dep.outputs.forEach((output) => outputs.add(output));
return [inputs, outputs];
}
/**
* Check if a value is not a default value
* @param value the value to check
* @returns default value boolean
*/
function has_no_default_value(value: any): boolean {
return (
(Array.isArray(value) && value.length === 0) ||
value === "" ||
value === 0 ||
!value
);
}
/**
* Determines if a component is interactive
* @param id component id
* @param interactive_prop value of the interactive prop
* @param value the main value of the component
* @param inputs set of ids that are inputs to a dependency
* @param outputs set of ids that are outputs to a dependency
* @returns if the component is interactive
*/
export function determine_interactivity(
id: number,
interactive_prop: boolean | undefined,
value: any,
inputs: Set<number>,
outputs: Set<number>
): boolean {
if (interactive_prop === false) {
return false;
} else if (interactive_prop === true) {
return true;
} else if (
inputs.has(id) ||
(!outputs.has(id) && has_no_default_value(value))
) {
return true;
}
return false;
}
type ServerFunctions = Record<string, (...args: any[]) => Promise<any>>;
/**
* Process the server function names and return a dictionary of functions
* @param id the component id
* @param server_fns the server function names
* @param app the client instance
* @returns the actual server functions
*/
export function process_server_fn(
id: number,
server_fns: string[] | undefined,
app: client_return
): ServerFunctions {
if (!server_fns) {
return {};
}
return server_fns.reduce((acc, fn: string) => {
acc[fn] = async (...args: any[]) => {
if (args.length === 1) {
args = args[0];
}
const result = await app.component_server(id, fn, args);
return result;
};
return acc;
}, {} as ServerFunctions);
}
/**
* Get a component from the backend
* @param type the type of the component
* @param class_id the class id of the component
* @param root the root url of the app
* @param components the list of component metadata
* @param example_components the list of example components
* @returns the component and its name
*/
export function get_component(
type: string,
class_id: string,
root: string,
components: ComponentMeta[],
example_components?: string[]
): {
component: LoadingComponent;
name: ComponentMeta["type"];
example_components?: Map<ComponentMeta["type"], LoadingComponent>;
} {
let example_component_map: Map<ComponentMeta["type"], LoadingComponent> =
new Map();
if (type === "dataset" && example_components) {
(example_components as string[]).forEach((name: string) => {
if (example_component_map.has(name)) {
return;
}
let _c;
const matching_component = components.find((c) => c.type === name);
if (matching_component) {
_c = load_component({
api_url: root,
name,
id: matching_component.component_class_id,
variant: "example"
});
example_component_map.set(name, _c.component);
}
});
}
const _c = load_component({
api_url: root,
name: type,
id: class_id,
variant: "component"
});
return {
component: _c.component,
name: _c.name,
example_components:
example_component_map.size > 0 ? example_component_map : undefined
};
}
/**
* Preload all components
* @param components A list of component metadata
* @param root The root url of the app
* @returns A map of component ids to their constructors
*/
export function preload_all_components(
components: ComponentMeta[],
root: string
): Map<ComponentMeta["type"], LoadingComponent> {
let constructor_map: Map<ComponentMeta["type"], LoadingComponent> = new Map();
components.forEach((c) => {
const { component, example_components } = get_component(
c.type,
c.component_class_id,
root,
components
);
constructor_map.set(c.component_class_id, component);
if (example_components) {
for (const [name, example_component] of example_components) {
constructor_map.set(name, example_component);
}
}
});
return constructor_map;
}

View File

@ -1,17 +1,25 @@
import type { ComponentType } from "svelte";
import type { SvelteComponent } from "svelte";
interface ComponentImport {
interactive: SvelteComponent;
static: SvelteComponent;
example: SvelteComponent;
/** The props that are always present on a component */
interface SharedProps {
elem_id?: string;
elem_classes?: string[];
components?: string[];
server_fns?: string[];
interactive: boolean;
[key: string]: unknown;
}
/** The metadata for a component
* The non optional fields are what are received from the backend
* The optional fields are what are added by the frontend
*/
export interface ComponentMeta {
type: string;
id: number;
has_modes: boolean;
props: Record<string, unknown> & { interactive: boolean };
props: SharedProps;
instance: SvelteComponent;
component: ComponentType<SvelteComponent>;
documentation?: Documentation;
@ -20,11 +28,13 @@ export interface ComponentMeta {
component_class_id: string;
}
/** Dictates whether a dependency is continous and/or a generator */
export interface DependencyTypes {
continuous: boolean;
generator: boolean;
}
/** An event payload that is sent with an API request */
export interface Payload {
fn_index: number;
data: unknown[];
@ -32,6 +42,7 @@ export interface Payload {
trigger_id: number | null;
}
/** A dependency as received from the backend */
export interface Dependency {
targets: [number, string][];
inputs: number[];
@ -40,7 +51,7 @@ export interface Dependency {
js: string | null;
scroll_to_output: boolean;
show_progress: "full" | "minimal" | "hidden";
frontend_fn?: (...args: unknown[]) => Promise<unknown[]>;
frontend_fn: ((...args: unknown[]) => Promise<unknown[]>) | null;
status?: string;
queue: boolean | null;
api_name: string | null;
@ -67,9 +78,24 @@ export interface Documentation {
example_data?: string;
}
/** A layout node as recived from the backend */
export interface LayoutNode {
id: number;
children: LayoutNode[];
}
/** The system theme mode */
export type ThemeMode = "system" | "light" | "dark";
/** the target map is an object mapping the target id to a series of events (another object), those events are a mapping of the event name to the function id's they trigger */
export type TargetMap = Record<number, Record<string, number[]>>;
/** A component that has been loaded via dynamic import */
export type LoadedComponent = {
default: ComponentMeta["component"];
};
/**A component that is loading */
export type LoadingComponent = Promise<{
default: ComponentMeta["component"];
}>;

View File

@ -13,8 +13,8 @@ declare module "virtual:component-loader" {
id: string;
variant: "component" | "example";
}
export function load_component(args: Args): Promise<{
export function load_component(args: Args): {
name: ComponentMeta["type"];
component: LoadedComponent;
}>;
};
}

View File

@ -42,7 +42,6 @@ test("chatinterface works with streaming functions and all buttons behave as exp
const expected_text_el_2 = page.locator(".bot p", {
hasText: "Run 3 - You typed: hello"
});
expect(textbox).toHaveValue("");
await expect(expected_text_el_2).toBeVisible();
await expect

View File

@ -32,7 +32,8 @@ import {
generate_dev_entry,
handle_ce_css,
inject_component_loader,
resolve_svelte
resolve_svelte,
mock_modules
} from "./build_plugins";
const GRADIO_VERSION = version_raw || "asd_stub_asd";
@ -41,7 +42,6 @@ const TEST_MODE = process.env.TEST_MODE || "happy-dom";
//@ts-ignore
export default defineConfig(({ mode }) => {
console.log(mode);
const targets = {
production: "../../gradio/templates/frontend",
"dev:custom": "../../gradio/templates/frontend"
@ -161,7 +161,8 @@ export default defineConfig(({ mode }) => {
inject_ejs(),
generate_cdn_entry({ version: GRADIO_VERSION, cdn_base: CDN_BASE }),
handle_ce_css(),
inject_component_loader()
inject_component_loader({ mode }),
mode === "test" && mock_modules()
],
optimizeDeps: {
exclude: ["@ffmpeg/ffmpeg", "@ffmpeg/util"]
@ -179,6 +180,7 @@ export default defineConfig(({ mode }) => {
if (log.includes("was created with unknown prop")) return false;
}
},
resolve: {
alias: {
// For the Wasm app to import the wheel file URLs.

View File

@ -4,7 +4,7 @@
</script>
<script lang="ts">
import { afterUpdate } from "svelte";
import { afterUpdate, tick } from "svelte";
import type { Gradio, SelectData } from "@gradio/utils";
import { Block } from "@gradio/atoms";
import Table from "./shared/Table.svelte";
@ -20,7 +20,7 @@
headers: ["1", "2", "3"],
metadata: null
};
let old_value: string = JSON.stringify(value);
let old_value = "";
export let value_is_output = false;
export let col_count: [number, "fixed" | "dynamic"];
export let row_count: [number, "fixed" | "dynamic"];
@ -49,21 +49,44 @@
export let loading_status: LoadingStatus;
export let interactive: boolean;
function handle_change(): void {
let _headers: Headers;
let display_value: string[][] | null;
let styling: string[][] | null;
let values: (string | number)[][];
async function handle_change(data?: {
data: Data;
headers: Headers;
metadata: Metadata;
}): Promise<void> {
let _data = data || value;
_headers = [...(_data.headers || headers)];
values = _data.data ? [..._data.data] : [];
display_value = _data?.metadata?.display_value
? [..._data?.metadata?.display_value]
: null;
styling = _data?.metadata?.styling ? [..._data?.metadata?.styling] : null;
await tick();
gradio.dispatch("change");
if (!value_is_output) {
gradio.dispatch("input");
}
}
handle_change();
afterUpdate(() => {
value_is_output = false;
});
$: {
if (JSON.stringify(value) !== old_value) {
if (old_value && JSON.stringify(value) !== old_value) {
old_value = JSON.stringify(value);
handle_change();
}
}
if (
(Array.isArray(value) && value?.[0]?.length === 0) ||
value.data?.[0]?.length === 0
@ -76,6 +99,18 @@
metadata: null
};
}
async function handle_value_change(data: {
data: Data;
headers: Headers;
metadata: Metadata;
}): Promise<void> {
if (JSON.stringify(data) !== old_value) {
value = { ...data };
old_value = JSON.stringify(value);
handle_change(data);
}
}
</script>
<Block
@ -99,9 +134,11 @@
{show_label}
{row_count}
{col_count}
{value}
{headers}
on:change={interactive ? (e) => (value = e.detail) : () => {}}
{values}
{display_value}
{styling}
headers={_headers}
on:change={(e) => handle_value_change(e.detail)}
on:select={(e) => gradio.dispatch("select", e.detail)}
{wrap}
{datatype}

View File

@ -21,8 +21,7 @@
export let label: string | null = null;
export let show_label = true;
export let headers: Headers = [];
let values: (string | number)[][];
export let value: { data: Data; headers: Headers; metadata: Metadata } | null;
export let values: (string | number)[][] = [];
export let col_count: [number, "fixed" | "dynamic"];
export let row_count: [number, "fixed" | "dynamic"];
export let latex_delimiters: {
@ -41,21 +40,10 @@
export let column_widths: string[] = [];
let selected: false | [number, number] = false;
let display_value: string[][] | null = value?.metadata?.display_value ?? null;
let styling: string[][] | null = value?.metadata?.styling ?? null;
export let display_value: string[][] | null = null;
export let styling: string[][] | null = null;
let t_rect: DOMRectReadOnly;
$: {
if (value) {
headers = value.headers;
values = value.data;
display_value = value?.metadata?.display_value ?? null;
styling = value?.metadata?.styling ?? null;
} else if (values === null) {
values = [];
}
}
const dispatch = createEventDispatcher<{
change: {
data: (string | number)[][];
@ -69,6 +57,7 @@
const get_data_at = (row: number, col: number): string | number =>
data?.[row]?.[col]?.value;
$: {
if (selected !== false) {
const [row, col] = selected;
@ -77,6 +66,7 @@
}
}
}
let els: Record<
string,
{ cell: null | HTMLTableCellElement; input: null | HTMLInputElement }
@ -87,6 +77,7 @@
function make_id(): string {
return Math.random().toString(36).substring(2, 15);
}
function make_headers(_head: Headers): HeadersWithIDs {
let _h = _head || [];
if (col_count[1] === "fixed" && _h.length < col_count[0]) {
@ -105,6 +96,7 @@
return { id: _id, value: JSON.stringify(i + 1) };
});
}
return _h.map((h, i) => {
const _id = make_id();
els[_id] = { cell: null, input: null };
@ -157,7 +149,9 @@
_headers = make_headers(headers);
old_headers = headers.slice();
trigger_change();
}
$: if (!dequal(values, old_val)) {
data = process_data(values as (string | number)[][]);
old_val = values as (string | number)[][];
@ -167,7 +161,7 @@
let old_val: undefined | (string | number)[][] = undefined;
$: _headers &&
async function trigger_change(): Promise<void> {
dispatch("change", {
data: data.map((r) => r.map(({ value }) => value)),
headers: _headers.map((h) => h.value),
@ -175,6 +169,7 @@
? null
: { display_value: display_value, styling: styling }
});
}
function get_sort_status(
name: string,
@ -425,6 +420,8 @@
selected = [index ? index + 1 : data.length - 1, 0];
}
$: data && trigger_change();
async function add_col(): Promise<void> {
parent.focus();
if (col_count[1] !== "dynamic") return;

View File

@ -7,8 +7,7 @@
export let component_map: Map<
string,
Promise<{
name: string;
component: { default: ComponentType<SvelteComponent> };
default: ComponentType<SvelteComponent>;
}>
>;
export let label = "Examples";
@ -46,6 +45,7 @@
function handle_mouseenter(i: number): void {
current_hover = i;
}
function handle_mouseleave(): void {
current_hover = -1;
}
@ -89,7 +89,7 @@
sample_row.map(async (sample_cell, j) => {
return {
value: sample_cell,
component: (await component_map.get(components[j]))?.component
component: (await component_map.get(components[j]))
?.default as ComponentType<SvelteComponent>
};
})

View File

@ -57,10 +57,10 @@
"eslint": "^8.46.0",
"eslint-plugin-svelte": "^2.32.4",
"globals": "^14.0.0",
"happy-dom": "^13.3.8",
"happy-dom": "^13.6.2",
"jsdom": "^24.0.0",
"kleur": "^4.1.5",
"msw": "^2.0.0",
"msw": "^2.2.1",
"node-html-parser": "^6.0.0",
"npm-run-all2": "^6.0.0",
"playwright": "^1.39.0",
@ -86,7 +86,7 @@
"typescript-svelte-plugin": "^0.3.34",
"vite": "^4.3.9",
"vite-plugin-turbosnap": "1.0.3",
"vitest": "^1.2.2"
"vitest": "^1.3.1"
},
"devDependencies": {
"@storybook/addon-a11y": "^7.5.1",

View File

@ -43,7 +43,7 @@ importers:
version: 9.0.0
'@testing-library/jest-dom':
specifier: ^6.0.0
version: 6.0.0(vitest@1.2.2)
version: 6.0.0(vitest@1.3.1)
'@testing-library/user-event':
specifier: ^14.0.0
version: 14.0.0(@testing-library/dom@9.0.0)
@ -81,8 +81,8 @@ importers:
specifier: ^14.0.0
version: 14.0.0
happy-dom:
specifier: ^13.3.8
version: 13.3.8
specifier: ^13.6.2
version: 13.6.2
jsdom:
specifier: ^24.0.0
version: 24.0.0
@ -90,8 +90,8 @@ importers:
specifier: ^4.1.5
version: 4.1.5
msw:
specifier: ^2.0.0
version: 2.0.0(typescript@5.0.2)
specifier: ^2.2.1
version: 2.2.1(typescript@5.0.2)
node-html-parser:
specifier: ^6.0.0
version: 6.0.0
@ -168,8 +168,8 @@ importers:
specifier: 1.0.3
version: 1.0.3
vitest:
specifier: ^1.2.2
version: 1.2.2(@types/node@20.3.2)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)
specifier: ^1.3.1
version: 1.3.1(@types/node@20.3.2)(happy-dom@13.6.2)(jsdom@24.0.0)(less@4.2.0)
devDependencies:
'@storybook/addon-a11y':
specifier: ^7.5.1
@ -2906,12 +2906,6 @@ packages:
cookie: 0.5.0
dev: false
/@bundled-es-modules/js-levenshtein@2.0.1:
resolution: {integrity: sha512-DERMS3yfbAljKsQc0U2wcqGKUWpdFjwqWuoMugEJlqBnKO180/n+4SR/J8MRDt1AN48X1ovgoD9KrdVXcaa3Rg==}
dependencies:
js-levenshtein: 1.1.6
dev: false
/@bundled-es-modules/statuses@1.0.1:
resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==}
dependencies:
@ -4297,6 +4291,39 @@ packages:
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
dev: false
/@inquirer/confirm@3.0.0:
resolution: {integrity: sha512-LHeuYP1D8NmQra1eR4UqvZMXwxEdDXyElJmmZfU44xdNLL6+GcQBS0uE16vyfZVjH8c22p9e+DStROfE/hyHrg==}
engines: {node: '>=18'}
dependencies:
'@inquirer/core': 7.0.0
'@inquirer/type': 1.2.0
dev: false
/@inquirer/core@7.0.0:
resolution: {integrity: sha512-g13W5yEt9r1sEVVriffJqQ8GWy94OnfxLCreNSOTw0HPVcszmc/If1KIf7YBmlwtX4klmvwpZHnQpl3N7VX2xA==}
engines: {node: '>=18'}
dependencies:
'@inquirer/type': 1.2.0
'@types/mute-stream': 0.0.4
'@types/node': 20.11.20
'@types/wrap-ansi': 3.0.0
ansi-escapes: 4.3.2
chalk: 4.1.2
cli-spinners: 2.9.2
cli-width: 4.1.0
figures: 3.2.0
mute-stream: 1.0.0
run-async: 3.0.0
signal-exit: 4.1.0
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
dev: false
/@inquirer/type@1.2.0:
resolution: {integrity: sha512-/vvkUkYhrjbm+RolU7V1aUFDydZVKNKqKHR5TsE+j5DXgXFwrsOPcoGUJ02K0O7q7O53CU2DOTMYCHeGZ25WHA==}
engines: {node: '>=18'}
dev: false
/@isaacs/cliui@8.0.2:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@ -4577,15 +4604,15 @@ packages:
engines: {node: '>=18'}
dev: false
/@mswjs/interceptors@0.25.11:
resolution: {integrity: sha512-27aonWAjdeoZN4j4j6QvePOSOacQUucFRUESAU8FUXsmmagDjmyOi4/NHizxzY/NHSk/HAyqF/IMhl+9puhqNw==}
/@mswjs/interceptors@0.25.16:
resolution: {integrity: sha512-8QC8JyKztvoGAdPgyZy49c9vSHHAZjHagwl4RY9E8carULk8ym3iTaiawrT1YoLF/qb449h48f71XDPgkUSOUg==}
engines: {node: '>=18'}
dependencies:
'@open-draft/deferred-promise': 2.2.0
'@open-draft/logger': 0.3.0
'@open-draft/until': 2.1.0
is-node-process: 1.2.0
outvariant: 1.4.0
outvariant: 1.4.2
strict-event-emitter: 0.5.1
dev: false
@ -4623,7 +4650,7 @@ packages:
resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==}
dependencies:
is-node-process: 1.2.0
outvariant: 1.4.0
outvariant: 1.4.2
dev: false
/@open-draft/until@2.1.0:
@ -7165,7 +7192,7 @@ packages:
lz-string: 1.5.0
pretty-format: 27.5.1
/@testing-library/jest-dom@6.0.0(vitest@1.2.2):
/@testing-library/jest-dom@6.0.0(vitest@1.3.1):
resolution: {integrity: sha512-Ye2R3+/oM27jir8CzYPmuWdavTaKwNZcu0d22L9pO/vnOYE0wmrtpw79TQJa8H6gV8/i7yd+pLaqeLlA0rTMfg==}
engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
peerDependencies:
@ -7191,7 +7218,7 @@ packages:
dom-accessibility-api: 0.5.16
lodash: 4.17.21
redent: 3.0.0
vitest: 1.2.2(@types/node@20.3.2)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)
vitest: 1.3.1(@types/node@20.3.2)(happy-dom@13.6.2)(jsdom@24.0.0)(less@4.2.0)
dev: false
/@testing-library/user-event@14.0.0(@testing-library/dom@9.0.0):
@ -7271,13 +7298,13 @@ packages:
'@types/node': 20.9.0
dev: true
/@types/cookie@0.4.1:
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
dev: false
/@types/cookie@0.5.4:
resolution: {integrity: sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==}
/@types/cookie@0.6.0:
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
dev: false
/@types/cross-spawn@6.0.5:
resolution: {integrity: sha512-wsIMP68FvGXk+RaWhraz6Xp4v7sl4qwzHAmtPaJEN2NRTXXI9LtFawUpeTsBNL/pd6QoLStdytCaAyiK7AEd/Q==}
dependencies:
@ -7407,10 +7434,6 @@ packages:
pretty-format: 29.7.0
dev: false
/@types/js-levenshtein@1.1.3:
resolution: {integrity: sha512-jd+Q+sD20Qfu9e2aEXogiO3vpOC1PYJOUdyN9gvs4Qrvkg4wF43L5OhqrPeokdv8TL0/mXoYfpkcoGZMNN2pkQ==}
dev: false
/@types/json-schema@7.0.15:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@ -7442,6 +7465,12 @@ packages:
resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
dev: false
/@types/mute-stream@0.0.4:
resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==}
dependencies:
'@types/node': 20.11.20
dev: false
/@types/node-fetch@2.6.9:
resolution: {integrity: sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==}
dependencies:
@ -7463,7 +7492,6 @@ packages:
resolution: {integrity: sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==}
dependencies:
undici-types: 5.26.5
dev: true
/@types/node@20.3.2:
resolution: {integrity: sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==}
@ -7587,6 +7615,10 @@ packages:
resolution: {integrity: sha512-ASCxdbsrwNfSMXALlC3Decif9rwDMu+80KGp5zI2RLRotfMsTv7fHL8W8VDp24wymzDyIFudhUeSCugrgRFfHQ==}
dev: false
/@types/wrap-ansi@3.0.0:
resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==}
dev: false
/@types/ws@8.5.4:
resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
dependencies:
@ -7760,38 +7792,38 @@ packages:
- supports-color
dev: false
/@vitest/expect@1.2.2:
resolution: {integrity: sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==}
/@vitest/expect@1.3.1:
resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==}
dependencies:
'@vitest/spy': 1.2.2
'@vitest/utils': 1.2.2
'@vitest/spy': 1.3.1
'@vitest/utils': 1.3.1
chai: 4.3.10
dev: false
/@vitest/runner@1.2.2:
resolution: {integrity: sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==}
/@vitest/runner@1.3.1:
resolution: {integrity: sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==}
dependencies:
'@vitest/utils': 1.2.2
'@vitest/utils': 1.3.1
p-limit: 5.0.0
pathe: 1.1.1
dev: false
/@vitest/snapshot@1.2.2:
resolution: {integrity: sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==}
/@vitest/snapshot@1.3.1:
resolution: {integrity: sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==}
dependencies:
magic-string: 0.30.5
pathe: 1.1.1
pretty-format: 29.7.0
dev: false
/@vitest/spy@1.2.2:
resolution: {integrity: sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==}
/@vitest/spy@1.3.1:
resolution: {integrity: sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==}
dependencies:
tinyspy: 2.2.0
dev: false
/@vitest/utils@1.2.2:
resolution: {integrity: sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==}
/@vitest/utils@1.3.1:
resolution: {integrity: sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==}
dependencies:
diff-sequences: 29.6.3
estree-walker: 3.0.3
@ -8395,6 +8427,7 @@ packages:
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: true
/better-opn@3.0.2:
resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==}
@ -8435,6 +8468,7 @@ packages:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.2
dev: true
/body-parser@1.20.1:
resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
@ -8548,6 +8582,7 @@ packages:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
dev: true
/bufferutil@4.0.7:
resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==}
@ -8734,10 +8769,17 @@ packages:
engines: {node: '>=8'}
dependencies:
restore-cursor: 3.1.0
dev: true
/cli-spinners@2.9.1:
resolution: {integrity: sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==}
engines: {node: '>=6'}
dev: true
/cli-spinners@2.9.2:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
dev: false
/cli-table3@0.6.3:
resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==}
@ -8748,9 +8790,9 @@ packages:
'@colors/colors': 1.5.0
dev: true
/cli-width@3.0.0:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
engines: {node: '>= 10'}
/cli-width@4.1.0:
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
engines: {node: '>= 12'}
dev: false
/cliui@6.0.0:
@ -10753,14 +10795,6 @@ packages:
combined-stream: 1.0.8
mime-types: 2.1.35
/formdata-node@4.4.1:
resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
engines: {node: '>= 12.20'}
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 4.0.0-beta.3
dev: false
/forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@ -11106,8 +11140,8 @@ packages:
uglify-js: 3.17.4
dev: true
/happy-dom@13.3.8:
resolution: {integrity: sha512-RAbq4oYfJNkVan1m1F3jfA4YEyRY0/ASoNvZsNJbuX85jIypidmsz9jQZD7Tqz0VXA2MhAGfcsh5oshwmwNYSg==}
/happy-dom@13.6.2:
resolution: {integrity: sha512-Ku+wDqcF/KwFA0dI+xIMZd9Jn020RXjuSil/Vz7gu2yhDC3FsDYZ55qqV9k+SGC4opwb4acisXqVSRxUJMlPbQ==}
engines: {node: '>=16.0.0'}
dependencies:
entities: 4.5.0
@ -11282,6 +11316,7 @@ packages:
/ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: true
/ignore@4.0.6:
resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
@ -11334,27 +11369,6 @@ packages:
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
/inquirer@8.2.6:
resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==}
engines: {node: '>=12.0.0'}
dependencies:
ansi-escapes: 4.3.2
chalk: 4.1.2
cli-cursor: 3.1.0
cli-width: 3.0.0
external-editor: 3.1.0
figures: 3.2.0
lodash: 4.17.21
mute-stream: 0.0.8
ora: 5.4.1
run-async: 2.4.1
rxjs: 7.8.1
string-width: 4.2.3
strip-ansi: 6.0.1
through: 2.3.8
wrap-ansi: 6.2.0
dev: false
/internal-slot@1.0.6:
resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==}
engines: {node: '>= 0.4'}
@ -11522,6 +11536,7 @@ packages:
/is-interactive@1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
dev: true
/is-module@1.0.0:
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
@ -11649,6 +11664,7 @@ packages:
/is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
dev: true
/is-weakref@1.0.2:
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
@ -11849,11 +11865,6 @@ packages:
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
dev: false
/js-levenshtein@1.1.6:
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
engines: {node: '>=0.10.0'}
dev: false
/js-stringify@1.0.2:
resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==}
dev: false
@ -11861,6 +11872,10 @@ packages:
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
/js-tokens@8.0.3:
resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==}
dev: false
/js-yaml@3.14.1:
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
hasBin: true
@ -12266,6 +12281,7 @@ packages:
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
dev: true
/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
@ -12567,6 +12583,7 @@ packages:
/mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
dev: true
/mimic-fn@4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
@ -12686,43 +12703,35 @@ packages:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: true
/msw@2.0.0(typescript@5.0.2):
resolution: {integrity: sha512-lw9UHuzNCWoODHaThGeLLIIuzEBUQkj3fJXQnChHifMKbB2UmF2msHd4d/lnyqjAyD0XWoibdviW9wlstFPpkA==}
/msw@2.2.1(typescript@5.0.2):
resolution: {integrity: sha512-DCsZAQwan+2onEcpD86fiEnCKW4IvYzqcwDq/2TIoeNrmBqNp/mJW4wHQyxcoYrRPwgujin7wDFflqiSO1iT/w==}
engines: {node: '>=18'}
hasBin: true
requiresBuild: true
peerDependencies:
typescript: '>= 4.7.x <= 5.2.x'
typescript: '>= 4.7.x <= 5.3.x'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@bundled-es-modules/cookie': 2.0.0
'@bundled-es-modules/js-levenshtein': 2.0.1
'@bundled-es-modules/statuses': 1.0.1
'@inquirer/confirm': 3.0.0
'@mswjs/cookies': 1.1.0
'@mswjs/interceptors': 0.25.11
'@mswjs/interceptors': 0.25.16
'@open-draft/until': 2.1.0
'@types/cookie': 0.4.1
'@types/js-levenshtein': 1.1.3
'@types/cookie': 0.6.0
'@types/statuses': 2.0.4
chalk: 4.1.2
chokidar: 3.5.3
formdata-node: 4.4.1
graphql: 16.8.1
headers-polyfill: 4.0.2
inquirer: 8.2.6
is-node-process: 1.2.0
js-levenshtein: 1.1.6
node-fetch: 2.7.0
outvariant: 1.4.0
outvariant: 1.4.2
path-to-regexp: 6.2.1
strict-event-emitter: 0.5.1
type-fest: 2.19.0
type-fest: 4.10.3
typescript: 5.0.2
yargs: 17.7.2
transitivePeerDependencies:
- encoding
dev: false
/multi-buffer-data-view@5.0.11:
@ -12733,8 +12742,9 @@ packages:
tslib: 2.6.2
dev: false
/mute-stream@0.0.8:
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
/mute-stream@1.0.0:
resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dev: false
/mz@2.7.0:
@ -12790,11 +12800,6 @@ packages:
minimatch: 3.1.2
dev: true
/node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
dev: false
/node-fetch-native@1.4.1:
resolution: {integrity: sha512-NsXBU0UgBxo2rQLOeWNZqS3fvflWePMECr8CoSWoSTqCqGbVVsvl9vZu1HfQicYN0g5piV9Gh8RTEvo/uP752w==}
dev: true
@ -12964,6 +12969,7 @@ packages:
engines: {node: '>=6'}
dependencies:
mimic-fn: 2.1.0
dev: true
/onetime@6.0.0:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
@ -13005,6 +13011,7 @@ packages:
log-symbols: 4.1.0
strip-ansi: 6.0.1
wcwidth: 1.0.1
dev: true
/os-tmpdir@1.0.2:
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
@ -13015,8 +13022,8 @@ packages:
resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==}
dev: false
/outvariant@1.4.0:
resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==}
/outvariant@1.4.2:
resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==}
dev: false
/p-filter@2.1.0:
@ -14216,6 +14223,7 @@ packages:
dependencies:
onetime: 5.1.2
signal-exit: 3.0.7
dev: true
/reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
@ -14289,8 +14297,8 @@ packages:
resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
dev: false
/run-async@2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
/run-async@3.0.0:
resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==}
engines: {node: '>=0.12.0'}
dev: false
@ -14307,12 +14315,6 @@ packages:
resolution: {integrity: sha512-ASEq9atUw7lualXB+knvgtvwkCEvGWV2gDD/8qnASzBkzEARZck9JAyxmY8OS6Nc1pCPEgDTKNcx+YqqYfzArw==}
dev: false
/rxjs@7.8.1:
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
dependencies:
tslib: 2.6.2
dev: false
/sade@1.8.1:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'}
@ -14819,10 +14821,10 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
/strip-literal@1.3.0:
resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==}
/strip-literal@2.0.0:
resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==}
dependencies:
acorn: 8.11.2
js-tokens: 8.0.3
dev: false
/style-loader@3.3.3(webpack@5.90.3):
@ -15442,10 +15444,6 @@ packages:
xtend: 4.0.2
dev: true
/through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: false
/timers-ext@0.1.7:
resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==}
dependencies:
@ -15656,6 +15654,12 @@ packages:
/type-fest@2.19.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
dev: true
/type-fest@4.10.3:
resolution: {integrity: sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==}
engines: {node: '>=16'}
dev: false
/type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
@ -16398,8 +16402,8 @@ packages:
unist-util-stringify-position: 2.0.3
dev: false
/vite-node@1.2.2(@types/node@20.3.2)(less@4.2.0):
resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==}
/vite-node@1.3.1(@types/node@20.3.2)(less@4.2.0):
resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
dependencies:
@ -16590,15 +16594,15 @@ packages:
dependencies:
vite: 4.5.2(@types/node@20.3.2)(less@4.2.0)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
/vitest@1.2.2(@types/node@20.3.2)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0):
resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==}
/vitest@1.3.1(@types/node@20.3.2)(happy-dom@13.6.2)(jsdom@24.0.0)(less@4.2.0):
resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@types/node': ^18.0.0 || >=20.0.0
'@vitest/browser': ^1.0.0
'@vitest/ui': ^1.0.0
'@vitest/browser': 1.3.1
'@vitest/ui': 1.3.1
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
@ -16616,28 +16620,27 @@ packages:
optional: true
dependencies:
'@types/node': 20.3.2
'@vitest/expect': 1.2.2
'@vitest/runner': 1.2.2
'@vitest/snapshot': 1.2.2
'@vitest/spy': 1.2.2
'@vitest/utils': 1.2.2
'@vitest/expect': 1.3.1
'@vitest/runner': 1.3.1
'@vitest/snapshot': 1.3.1
'@vitest/spy': 1.3.1
'@vitest/utils': 1.3.1
acorn-walk: 8.3.2
cac: 6.7.14
chai: 4.3.10
debug: 4.3.4
execa: 8.0.1
happy-dom: 13.3.8
happy-dom: 13.6.2
jsdom: 24.0.0
local-pkg: 0.5.0
magic-string: 0.30.5
pathe: 1.1.1
picocolors: 1.0.0
std-env: 3.7.0
strip-literal: 1.3.0
strip-literal: 2.0.0
tinybench: 2.5.1
tinypool: 0.8.2
vite: 5.1.1(@types/node@20.3.2)(less@4.2.0)
vite-node: 1.2.2(@types/node@20.3.2)(less@4.2.0)
vite-node: 1.3.1(@types/node@20.3.2)(less@4.2.0)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
@ -16688,11 +16691,6 @@ packages:
dependencies:
defaults: 1.0.4
/web-streams-polyfill@4.0.0-beta.3:
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
engines: {node: '>= 14'}
dev: false
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}