JS client take 2 (#3388)

* start

* change api

* integrate into gradio

* log

* try this

* format

* changes

* format

* fix css

* fix file
This commit is contained in:
pngwn 2023-03-07 16:36:25 +00:00 committed by GitHub
parent 47d6231680
commit fddf376784
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1059 additions and 1148 deletions

Binary file not shown.

View File

@ -1 +1 @@
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: stream_frames"]}, {"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", "import numpy as np\n", "\n", "def flip(im):\n", " return np.flipud(im)\n", "\n", "demo = gr.Interface(\n", " flip, \n", " gr.Image(source=\"webcam\", streaming=True), \n", " \"image\",\n", " live=True\n", ")\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: stream_frames"]}, {"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", "import numpy as np\n", "\n", "def flip(im):\n", " return np.flipud(im)\n", "\n", "demo = gr.Interface(\n", " flip, \n", " gr.Image(source=\"webcam\", streaming=True), \n", " \"image\",\n", " live=True\n", ")\n", "if __name__ == \"__main__\":\n", " demo.launch()\n", " "]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}

View File

@ -11,4 +11,5 @@ demo = gr.Interface(
live=True
)
if __name__ == "__main__":
demo.launch()
demo.launch()

View File

@ -21,6 +21,7 @@
"@gradio/button": "workspace:^0.0.1",
"@gradio/chart": "workspace:^0.0.1",
"@gradio/chatbot": "workspace:^0.0.1",
"@gradio/client": "workspace:^0.0.1",
"@gradio/file": "workspace:^0.0.1",
"@gradio/form": "workspace:^0.0.1",
"@gradio/gallery": "workspace:^0.0.1",

View File

@ -1,6 +1,7 @@
<script lang="ts">
import { tick } from "svelte";
import { _ } from "svelte-i18n";
import type { client } from "@gradio/client";
import { component_map } from "./components/directory";
import {
@ -15,7 +16,6 @@
LayoutNode,
Documentation
} from "./components/types";
import type { fn as api_fn } from "./api";
import { setupi18n } from "./i18n";
import Render from "./Render.svelte";
import { ApiDocs } from "./api_docs/";
@ -26,12 +26,10 @@
setupi18n();
export let root: string;
export let fn: ReturnType<typeof api_fn>;
export let components: Array<ComponentMeta>;
export let layout: LayoutNode;
export let dependencies: Array<Dependency>;
export let enable_queue: boolean;
export let title: string = "Gradio";
export let analytics_enabled: boolean = false;
export let target: HTMLElement;
@ -41,6 +39,7 @@
export let control_page_title = false;
export let app_mode: boolean;
export let theme: string;
export let app: Awaited<ReturnType<typeof client>>;
let loading_status = create_loading_status_store();
@ -210,6 +209,36 @@
});
});
function handle_update(data: any, fn_index: number) {
const outputs = dependencies[fn_index].outputs;
data.forEach((value: any, i: number) => {
if (
typeof value === "object" &&
value !== null &&
value.__type__ === "update"
) {
for (const [update_key, update_value] of Object.entries(value)) {
if (update_key === "__type__") {
continue;
} else {
instance_map[outputs[i]].props[update_key] = update_value;
}
}
rootNode = rootNode;
} else {
instance_map[outputs[i]].props.value = value;
}
});
}
app.on("data", ({ data, fn_index }) => {
handle_update(data, fn_index);
});
app.on("status", ({ fn_index, ...status }) => {
loading_status.update({ ...status, fn_index });
});
function set_prop<T extends ComponentMeta>(obj: T, prop: string, val: any) {
if (!obj?.props) {
obj.props = {};
@ -217,7 +246,6 @@
obj.props[prop] = val;
rootNode = rootNode;
}
let handled_dependencies: Array<number[]> = [];
async function handle_mount() {
@ -259,45 +287,36 @@
outputs.every((v) => instance_map?.[v].instance) &&
inputs.every((v) => instance_map?.[v].instance)
) {
const req = fn({
action: "predict",
backend_fn,
frontend_fn,
payload: {
fn_index: i,
data: inputs.map((id) => instance_map[id].props.value)
},
queue: queue === null ? enable_queue : queue,
queue_callback: handle_update,
loading_status: loading_status,
cancels
});
cancels &&
cancels.forEach((fn_index) => {
app.cancel("/predict", fn_index);
});
function handle_update(output: any) {
output.data.forEach((value: any, i: number) => {
if (
typeof value === "object" &&
value !== null &&
value.__type__ === "update"
) {
for (const [update_key, update_value] of Object.entries(
value
)) {
if (update_key === "__type__") {
continue;
} else {
instance_map[outputs[i]].props[update_key] = update_value;
}
}
rootNode = rootNode;
let payload = {
fn_index: i,
data: inputs.map((id) => instance_map[id].props.value)
};
if (frontend_fn) {
frontend_fn(
payload.data.concat(
outputs.map((id) => instance_map[id].props.value)
)
).then((v: []) => {
if (backend_fn) {
payload.data = v;
make_prediction();
} else {
instance_map[outputs[i]].props.value = value;
handle_update(v, i);
}
});
} else {
if (backend_fn) {
make_prediction();
}
}
if (!(queue === null ? enable_queue : queue)) {
req.then(handle_update);
function make_prediction() {
app.predict("/predict", payload);
}
handled_dependencies[i] = [-1];
@ -308,54 +327,49 @@
.forEach(([id, { instance }]: [number, ComponentMeta]) => {
if (handled_dependencies[i]?.includes(id) || !instance) return;
instance?.$on(trigger, () => {
if (loading_status.get_status_for_fn(i) === "pending") {
const current_status = loading_status.get_status_for_fn(i);
if (
current_status === "pending" ||
current_status === "generating"
) {
return;
}
// page events
const req = fn({
action: "predict",
backend_fn,
frontend_fn,
payload: {
fn_index: i,
data: inputs.map((id) => instance_map[id].props.value)
},
output_data: outputs.map((id) => instance_map[id].props.value),
queue: queue === null ? enable_queue : queue,
queue_callback: handle_update,
loading_status: loading_status,
cancels
});
if (cancels) {
cancels.forEach((fn_index) => {
app.cancel("/predict", fn_index);
});
}
if (!(queue === null ? enable_queue : queue)) {
req.then(handle_update);
let payload = {
fn_index: i,
data: inputs.map((id) => instance_map[id].props.value)
};
if (frontend_fn) {
frontend_fn(
payload.data.concat(
outputs.map((id) => instance_map[id].props.value)
)
).then((v: []) => {
if (backend_fn) {
payload.data = v;
make_prediction();
} else {
handle_update(v, i);
}
});
} else {
if (backend_fn) {
make_prediction();
}
}
function make_prediction() {
app.predict("/predict", payload);
}
});
function handle_update(output: any) {
output.data.forEach((value: any, i: number) => {
if (
typeof value === "object" &&
value !== null &&
value.__type__ === "update"
) {
for (const [update_key, update_value] of Object.entries(
value
)) {
if (update_key === "__type__") {
continue;
} else {
instance_map[outputs[i]].props[update_key] = update_value;
}
}
rootNode = rootNode;
} else {
instance_map[outputs[i]].props.value = value;
}
});
}
if (!handled_dependencies[i]) handled_dependencies[i] = [];
handled_dependencies[i].push(id);
});

View File

@ -1,6 +1,5 @@
<script context="module" lang="ts">
import { writable } from "svelte/store";
import { fn } from "./api";
import { mount_css } from "./main";
import type {
@ -19,11 +18,9 @@
dependencies: Dependency[];
dev_mode: boolean;
enable_queue: boolean;
fn: ReturnType<typeof fn>;
layout: LayoutNode;
mode: "blocks" | "interface";
root: string;
target: HTMLElement;
theme: string;
title: string;
version: string;
@ -62,6 +59,7 @@
<script lang="ts">
import { onMount } from "svelte";
import { client, SpaceStatus } from "@gradio/client";
import Embed from "./Embed.svelte";
import { Component as Loader } from "./components/StatusTracker";
@ -83,78 +81,25 @@
let _id = id++;
let status: "pending" | "error" | "success" | "login" = "pending";
let loader_status: "pending" | "error" | "complete" | "generating" =
"pending";
let app_id: string | null = null;
let wrapper: HTMLDivElement;
let ready: boolean = false;
let root: string;
let config: Config;
let loading_text: string = "Loading...";
async function handle_config(target: HTMLElement, source: string | null) {
let config;
try {
let _config = await get_config(source);
config = _config;
} catch (e) {
if (BUILD_MODE === "dev") {
console.error(e);
}
return null;
}
if (root === undefined) {
root = BACKEND_URL;
config.root = BACKEND_URL;
}
mount_css(config.root + "theme.css", document.head);
mount_custom_css(target, config.css);
window.__is_colab__ = config.is_colab;
if (config.dev_mode) {
reload_check(root);
}
return config;
}
async function get_config(source: string | null) {
if (BUILD_MODE === "dev" || location.origin === "http://localhost:9876") {
let config = await fetch(BACKEND_URL + "config");
const result = await config.json();
return result;
} else if (source) {
if (!source.endsWith("/")) {
source += "/";
}
const config = await get_source_config(source);
return config;
} else if (window.gradio_config) {
return window.gradio_config;
} else {
throw new Error("Config not found");
}
}
async function get_source_config(source: string): Promise<Config> {
let config = await (await fetch(source + "config")).json();
root = source;
config.root = source;
return config;
}
function mount_custom_css(target: HTMLElement, css_string?: string) {
function mount_custom_css(target: HTMLElement, css_string: string | null) {
if (css_string) {
let style = document.createElement("style");
style.innerHTML = css_string;
target.appendChild(style);
}
mount_css(config.root + "/theme.css", document.head);
}
async function reload_check(root: string) {
const result = await (await fetch(root + "app_id")).text();
const result = await (await fetch(root + "/app_id")).text();
if (app_id === null) {
app_id = result;
@ -215,194 +160,51 @@
return "dark";
}
export const RE_SPACE_NAME = /^[^\/]*\/[^\/]*$/;
async function check_space_status(
space_id: string,
source: string,
cb: Function = () => {}
) {
const endpoint = RE_SPACE_NAME.test(space_id) ? "" : "by-subdomain/";
let _space_id = "";
let response;
let _status;
try {
response = await fetch(
`https://huggingface.co/api/spaces/${endpoint}${space_id}`
);
_status = response.status;
let status: SpaceStatus = {
message: "",
load_status: "pending",
status: "sleeping",
detail: "SLEEPING"
};
if (_status !== 200) {
space = "";
source = "";
throw new Error();
}
response = await response.json();
_space_id = response.id;
cb(response.id);
} catch (e) {
space = "";
source = "";
status = "error";
error_detail = {
type: "space_error",
detail: {
description: "This space is experiencing an issue.",
discussions_enabled: await discussions_enabled(_space_id || space_id)
}
};
return;
}
if (!response || _status !== 200) return;
const {
runtime: { stage }
} = response;
switch (stage) {
case "STOPPED":
case "SLEEPING":
status = "pending";
loading_text = "Space is asleep. Waking it up...";
setTimeout(() => {
check_space_status(_space_id, source);
}, 1000);
break;
// poll for status
case "RUNNING":
case "RUNNING_BUILDING":
status = "success";
load_config(source);
// launch
break;
case "BUILDING":
status = "pending";
loading_text = "Space is building...";
setTimeout(() => {
check_space_status(_space_id, source);
}, 1000);
break;
case "NO_APP_FILE":
case "CONFIG_ERROR":
case "BUILD_ERROR":
case "RUNTIME_ERROR":
status = "error";
error_detail = {
type: "space_error",
detail: {
description: "This space is experiencing an issue.",
discussions_enabled: await discussions_enabled(_space_id),
stage
}
};
break;
default:
status = "error";
error_detail = {
type: "space_error",
detail: {
description: "This space is experiencing an issue.",
discussions_enabled: await discussions_enabled(_space_id)
}
};
}
let app: Awaited<ReturnType<typeof client>>;
function handle_status(_status: SpaceStatus) {
status = _status;
}
const RE_DISABLED_DISCUSSION =
/^(?=[^]*\b[dD]iscussions{0,1}\b)(?=[^]*\b[dD]isabled\b)[^]*$/;
async function discussions_enabled(space_id: string) {
try {
const r = await fetch(
`https://huggingface.co/api/spaces/${space_id}/discussions`,
{
method: "HEAD"
}
);
const error = r.headers.get("x-error-message");
if (error && RE_DISABLED_DISCUSSION.test(error)) return false;
else return true;
} catch (e) {
return false;
}
}
const session_hash = Math.random().toString(36).substring(2);
let error_detail: null | {
type: "not_found" | "space_error";
detail?: Record<string, any>;
} = null;
onMount(async () => {
if (window.__gradio_mode__ !== "website") {
theme = handle_darkmode(wrapper);
}
let source;
if (space) {
try {
const r = await fetch(
`https://huggingface.co/api/spaces/${space.trim()}/host`
);
const api_url =
BUILD_MODE === "dev"
? "http://localhost:7860"
: host || space || src || location.origin;
if (r.status !== 200) {
source = "";
space = "";
}
app = await client(api_url, handle_status);
config = app.config;
source = (await r.json()).host;
} catch (e) {
source = "";
space = "";
}
} else {
source = (host ? `https://${host}` : src)?.trim();
status = {
message: "",
load_status: "complete",
status: "running",
detail: "RUNNING"
};
mount_custom_css(wrapper, config.css);
window.__is_colab__ = config.is_colab;
if (config.dev_mode) {
reload_check(config.root);
}
if (space) {
check_space_status(space?.trim(), source);
} else if (src?.endsWith(".hf.space")) {
check_space_status(
src.replace(".hf.space", "").replace("https://", ""),
source,
(id: string) => (space = id)
);
space = " ";
}
load_config(source);
});
async function load_config(source: string) {
const _config: Config | null = await handle_config(wrapper, source);
if (_config) {
status = _config.auth_required ? "login" : "pending";
_config.fn = _config.fn = fn(
session_hash,
root + "run/",
_config.is_space,
is_embed
);
config = _config;
} else {
create_not_found_error();
}
}
function create_not_found_error() {
status = "error";
error_detail = {
type: "not_found",
detail: {
description: "This gradio app is experiencing an issue."
}
};
}
$: status = ready ? "success" : status;
$: loader_status =
!ready && status.load_status !== "error"
? "pending"
: !ready && status.load_status === "error"
? "error"
: status.load_status;
$: config && (eager || $intersecting[_id]) && load_demo();
@ -450,7 +252,7 @@
}
};
onMount(() => {
onMount(async () => {
intersecting.register(_id, wrapper);
});
</script>
@ -462,27 +264,27 @@
{version}
{initial_height}
{space}
loaded={status === "success"}
loaded={loader_status === "complete"}
bind:wrapper
>
{#if status === "pending" || status === "error"}
{#if (loader_status === "pending" || loader_status === "error") && !(config && config?.auth_required)}
<Loader
absolute={!is_embed}
{status}
status={loader_status}
timer={false}
queue_position={null}
queue_size={null}
{loading_text}
>
<div class="error" slot="error">
<p><strong>{error_detail?.detail?.description || ""}</strong></p>
{#if error_detail?.detail?.discussions_enabled}
<p><strong>{status?.message || ""}</strong></p>
{#if status.status === "space_error" && status.discussions_enabled}
<p>
Please <a
href="https://huggingface.co/spaces/{space}/discussions/new?title={discussion_message.title(
error_detail.detail.stage
status?.detail
)}&description={discussion_message.description(
error_detail.detail.stage,
status?.detail,
location.origin
)}"
>
@ -494,16 +296,17 @@
{/if}
</div>
</Loader>
{:else if status === "login" && Login}
{/if}
{#if config?.auth_required && Login}
<Login
auth_message={config.auth_message}
root={config.root}
is_space={config.is_space}
{app_mode}
/>
{/if}
{#if config && Blocks}
{:else if config && Blocks}
<Blocks
{app}
{...config}
{theme}
{control_page_title}

View File

@ -17,7 +17,7 @@
formData.append("username", username);
formData.append("password", password);
let response = await fetch(root + "login", {
let response = await fetch(root + "/login", {
method: "POST",
body: formData
});

View File

@ -1,327 +0,0 @@
import { get } from "svelte/store";
import type { LoadingStatusType } from "./stores";
type StatusResponse =
| {
status: "COMPLETE";
data: {
duration: number;
average_duration: number;
data: Array<unknown>;
};
}
| {
status: "QUEUED";
data: number;
}
| {
status: "PENDING";
data: null;
}
| {
status: "FAILED";
data: Record<string, unknown>;
};
interface Payload {
data: Array<unknown>;
fn_index: number;
session_hash?: string;
}
declare let BUILD_MODE: string;
declare let BACKEND_URL: string;
interface PostResponse {
error?: string;
[x: string]: any;
}
export interface UploadResponse {
error?: string;
files?: Array<string>;
}
const QUEUE_FULL_MSG = "This application is too busy. Keep trying!";
const BROKEN_CONNECTION_MSG = "Connection errored out.";
export async function post_data(
url: string,
body: unknown
): Promise<[PostResponse, number]> {
try {
var response = await fetch(url, {
method: "POST",
body: JSON.stringify(body),
headers: { "Content-Type": "application/json" }
});
} catch (e) {
return [{ error: BROKEN_CONNECTION_MSG }, 500];
}
const output: PostResponse = await response.json();
return [output, response.status];
}
export async function upload_files(
root: string,
files: Array<File>
): Promise<UploadResponse> {
const formData = new FormData();
files.forEach((file) => {
formData.append("files", file);
});
try {
var response = await fetch(`${root}upload`, {
method: "POST",
body: formData
});
} catch (e) {
return { error: BROKEN_CONNECTION_MSG };
}
const output: UploadResponse["files"] = await response.json();
return { files: output };
}
interface UpdateOutput {
__type__: string;
[key: string]: unknown;
}
type Output = {
data: Array<UpdateOutput | unknown>;
duration?: number;
average_duration?: number;
};
const ws_map = new Map<number, WebSocket>();
export const fn =
(
session_hash: string,
api_endpoint: string,
is_space: boolean,
is_embed: boolean
) =>
async ({
action,
payload,
queue,
backend_fn,
frontend_fn,
output_data,
queue_callback,
loading_status,
cancels
}: {
action: string;
payload: Payload;
queue: boolean;
backend_fn: boolean;
frontend_fn: Function | undefined;
output_data?: Output["data"];
queue_callback: Function;
loading_status: LoadingStatusType;
cancels: Array<number>;
}): Promise<unknown> => {
const fn_index = payload.fn_index;
payload.session_hash = session_hash;
if (frontend_fn !== undefined) {
payload.data = await frontend_fn(payload.data.concat(output_data));
}
if (backend_fn == false) {
return payload;
}
if (queue && ["predict", "interpret"].includes(action)) {
loading_status.update(
fn_index as number,
"pending",
queue,
null,
null,
null,
null
);
function send_message(fn: number, data: any) {
ws_map.get(fn)?.send(JSON.stringify(data));
}
let WS_ENDPOINT = "";
if (is_embed) {
WS_ENDPOINT = `wss://${new URL(api_endpoint).host}/queue/join`;
} else {
var ws_endpoint =
api_endpoint === "run/" ? location.href : api_endpoint;
var ws_protocol = ws_endpoint.startsWith("https") ? "wss:" : "ws:";
var ws_path = location.pathname === "/" ? "/" : location.pathname;
var ws_host =
BUILD_MODE === "dev" || location.origin === "http://localhost:9876"
? BACKEND_URL.replace("http://", "").slice(0, -1)
: location.host;
WS_ENDPOINT = `${ws_protocol}//${ws_host}${ws_path}queue/join`;
}
var websocket = new WebSocket(WS_ENDPOINT);
ws_map.set(fn_index, websocket);
websocket.onclose = (evt) => {
if (!evt.wasClean) {
loading_status.update(
fn_index,
"error",
queue,
null,
null,
null,
BROKEN_CONNECTION_MSG
);
}
};
websocket.onmessage = async function (event) {
const data = JSON.parse(event.data);
switch (data.msg) {
case "send_data":
send_message(fn_index, payload);
break;
case "send_hash":
ws_map.get(fn_index)?.send(
JSON.stringify({
session_hash: session_hash,
fn_index: fn_index
})
);
break;
case "queue_full":
loading_status.update(
fn_index,
"error",
queue,
null,
null,
null,
QUEUE_FULL_MSG
);
websocket.close();
return;
case "estimation":
loading_status.update(
fn_index,
get(loading_status)[data.fn_index]?.status || "pending",
queue,
data.queue_size,
data.rank,
data.rank_eta,
null
);
break;
case "progress":
loading_status.update(
fn_index,
"pending",
queue,
null,
null,
null,
null,
data.progress_data
);
break;
case "process_generating":
loading_status.update(
fn_index,
data.success ? "generating" : "error",
queue,
null,
null,
data.output.average_duration,
!data.success ? data.output.error : null
);
if (data.success) {
queue_callback(data.output);
}
break;
case "process_completed":
loading_status.update(
fn_index,
data.success ? "complete" : "error",
queue,
null,
null,
data.output.average_duration,
!data.success ? data.output.error : null
);
if (data.success) {
queue_callback(data.output);
}
websocket.close();
return;
case "process_starts":
loading_status.update(
fn_index,
"pending",
queue,
data.rank,
0,
null,
null
);
break;
}
};
} else {
loading_status.update(
fn_index as number,
"pending",
queue,
null,
null,
null,
null
);
var [output, status_code] = await post_data(api_endpoint + action + "/", {
...payload,
session_hash
});
if (status_code == 200) {
loading_status.update(
fn_index,
"complete",
queue,
null,
null,
output.average_duration as number,
null
);
// Cancelled jobs are set to complete
if (cancels.length > 0) {
cancels.forEach((fn_index) => {
loading_status.update(
fn_index,
"complete",
queue,
null,
null,
null,
null
);
ws_map.get(fn_index)?.close();
});
}
} else {
loading_status.update(
fn_index,
"error",
queue,
null,
null,
null,
output.error
);
throw output.error || "API Error";
}
return output;
}
};

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { onMount, createEventDispatcher } from "svelte";
import type { ComponentMeta, Dependency } from "../components/types";
import { post_data } from "../api";
import { post_data } from "@gradio/client";
import NoApi from "./NoApi.svelte";
import { represent_value } from "./utils";
@ -72,7 +72,7 @@
return;
}
let [response, status_code] = await post_data(
`${root}run/${dependency.api_name}`,
`${root}/run/${dependency.api_name}`,
{
data: inputs
}

View File

@ -18,8 +18,8 @@
const dispatch = createEventDispatcher<{ click: number }>();
let samples_dir: string = root_url
? "proxy=" + root_url + "file="
: root + "file=";
? "proxy=" + root_url + "/file="
: root + "/file=";
let page = 0;
$: gallery = components.length < 2;
let paginate = samples.length > samples_per_page;

View File

@ -5,7 +5,7 @@
import { normalise_file } from "@gradio/upload";
import { Block } from "@gradio/atoms";
import UploadText from "../UploadText.svelte";
import { upload_files } from "../../api";
import { upload_files } from "@gradio/client";
import StatusTracker from "../StatusTracker/StatusTracker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";

View File

@ -3,7 +3,7 @@ import { cleanup, render } from "@gradio/tootils";
import File from "./File.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
import { upload_files } from "../../api";
import { upload_files } from "@gradio/client";
const loading_status = {
eta: 0,
@ -22,13 +22,13 @@ describe("File", () => {
});
test("Uploads with blob", async () => {
vi.mock("../../api", async () => {
vi.mock("@gradio/client", async () => {
return {
upload_files: vi.fn((f) => new Promise((res) => res({})))
};
});
const api = await import("../../api");
const api = await import("@gradio/client");
render(File, {
loading_status,

View File

@ -3,7 +3,7 @@
import type { Styles } from "@gradio/utils";
import type { FileData } from "@gradio/upload";
import { UploadButton } from "@gradio/upload-button";
import { upload_files } from "../../api";
import { upload_files } from "@gradio/client";
import { blobToBase64 } from "@gradio/upload";
import { _ } from "svelte-i18n";

View File

@ -1,11 +1,11 @@
import { writable } from "svelte/store";
import { writable, get } from "svelte/store";
export interface LoadingStatus {
eta: number | null;
status: "pending" | "error" | "complete" | "generating";
queue: boolean;
queue_position: number | null;
queue_size: number | null;
queue_size?: number;
fn_index: number;
message?: string | null;
scroll_to_output?: boolean;
@ -32,16 +32,25 @@ export function create_loading_status_store() {
const inputs_to_update = new Map<number, string>();
const fn_status: Array<LoadingStatus["status"]> = [];
function update(
fn_index: LoadingStatus["fn_index"],
status: LoadingStatus["status"],
queue: LoadingStatus["queue"],
size: LoadingStatus["queue_size"],
position: LoadingStatus["queue_position"],
eta: LoadingStatus["eta"],
message: LoadingStatus["message"],
progress?: LoadingStatus["progress"]
) {
function update({
fn_index,
status,
queue = true,
size,
position = null,
eta = null,
message = null,
progress
}: {
fn_index: LoadingStatus["fn_index"];
status: LoadingStatus["status"];
queue?: LoadingStatus["queue"];
size?: LoadingStatus["queue_size"];
position?: LoadingStatus["queue_position"];
eta?: LoadingStatus["eta"];
message?: LoadingStatus["message"];
progress?: LoadingStatus["progress"];
}) {
const outputs = fn_outputs[fn_index];
const inputs = fn_inputs[fn_index];
const last_status = fn_status[fn_index];
@ -98,7 +107,7 @@ export function create_loading_status_store() {
}
});
store.update((outputs) => {
store.update((outputs: LoadingStatusCollection) => {
outputs_to_update.forEach(
({
id,
@ -114,7 +123,7 @@ export function create_loading_status_store() {
queue_size: queue_size,
queue_position: queue_position,
eta: eta,
message,
message: message,
progress,
status,
fn_index
@ -124,7 +133,6 @@ export function create_loading_status_store() {
return outputs;
});
fn_status[fn_index] = status;
}

View File

@ -12,7 +12,7 @@ function mock_demo(page: Page, demo: string) {
}
function mock_api(page: Page, body: Array<unknown>) {
return page.route("**/run/predict/", (route) => {
return page.route("**/run/predict", (route) => {
const id = JSON.parse(route.request().postData()!).fn_index;
return route.fulfill({
headers: {
@ -39,7 +39,7 @@ test("renders the correct elements", async ({ page }) => {
await textboxTwo.fill("dawood");
await Promise.all([
page.click('text="Submit"'),
page.waitForResponse("**/run/predict/")
page.waitForResponse("**/run/predict")
]);
await expect(await page.getByLabel("Output")).toHaveValue("hi dawood");

View File

@ -12,7 +12,7 @@ function mock_demo(page: Page, demo: string) {
}
function mock_api(page: Page, body: Array<unknown>) {
return page.route("**/run/predict/", (route) => {
return page.route("**/run/predict", (route) => {
const id = JSON.parse(route.request().postData()!).fn_index;
return route.fulfill({
headers: {
@ -32,6 +32,6 @@ test("renders the correct elements", async ({ page }) => {
await Promise.all([
page.click("button:has-text('Run')"),
page.waitForResponse("**/run/predict/")
page.waitForResponse("**/run/predict")
]);
});

View File

@ -12,7 +12,7 @@ function mock_demo(page: Page, demo: string) {
}
function mock_api(page: Page, body: Array<unknown>) {
return page.route("**/run/predict/", (route) => {
return page.route("**/run/predict", (route) => {
const id = JSON.parse(route.request().postData()!).fn_index;
return route.fulfill({
headers: {

View File

@ -12,7 +12,7 @@ function mock_demo(page: Page, demo: string) {
}
function mock_api(page: Page, body: Array<unknown>) {
return page.route("**/run/predict/", (route) => {
return page.route("**/run/predict", (route) => {
const id = JSON.parse(route.request().postData()!).fn_index;
return route.fulfill({
headers: {
@ -65,7 +65,7 @@ test("can run an api request and display the data", async ({ page }) => {
await Promise.all([
run_button.click(),
page.waitForResponse("**/run/predict/")
page.waitForResponse("**/run/predict")
]);
const json = await page.getByTestId("json").first();

View File

@ -12,7 +12,7 @@ function mock_demo(page: Page, demo: string) {
}
function mock_api(page: Page, body: Array<unknown>) {
return page.route("**/run/predict/", (route) => {
return page.route("**/run/predict", (route) => {
const id = JSON.parse(route.request().postData()!).fn_index;
return route.fulfill({
headers: {
@ -35,7 +35,7 @@ test("a component acts as both input and output", async ({ page }) => {
await textbox.fill("test");
await Promise.all([
page.click("button"),
page.waitForResponse("**/run/predict/")
page.waitForResponse("**/run/predict")
]);
await expect(await textbox).toHaveValue("tset");
});

View File

@ -13,7 +13,7 @@ function mock_demo(page: Page, demo: string) {
}
function mock_api(page: Page, body: Array<unknown>) {
return page.route("**/run/predict/", (route) => {
return page.route("**/run/predict", (route) => {
const id = JSON.parse(route.request().postData()!).fn_index;
return route.fulfill({
headers: {
@ -215,7 +215,7 @@ test("test outputs", async ({ page }) => {
await Promise.all([
submit_button.click(),
page.waitForResponse("**/run/predict/")
page.waitForResponse("**/run/predict")
]);
const textbox = await page.getByLabel("Textbox").nth(2);

View File

@ -1,383 +0,0 @@
function cheap_clone(obj: object) {
return JSON.parse(JSON.stringify(obj));
}
export const RUNNING = {
_test: true,
_id: "63d2d66613d86bc148071e75",
id: "pngwn/music-visualizer",
author: "pngwn",
sha: "4090039c565c73d5a0485d14bc3067ca6b25967c",
lastModified: "2023-01-26T19:37:53.000Z",
private: false,
cardData: {
title: "Music Visualizer",
emoji: "🐨",
colorFrom: "gray",
colorTo: "indigo",
sdk: "gradio",
sdk_version: "3.16.2",
app_file: "app.py",
pinned: false,
duplicated_from: "nateraw/music-visualizer"
},
gated: false,
disabled: false,
subdomain: "pngwn-music-visualizer",
tags: ["gradio"],
likes: 1,
datasets: ["nateraw/misc"],
sdk: "gradio",
runtime: {
stage: "RUNNING",
sdk: "gradio",
sdkVersion: "3.16.2",
hardware: {
current: "cpu-basic",
requested: "cpu-basic"
},
spaceId: "pngwn/music-visualizer"
},
siblings: [
{
rfilename: ".gitattributes"
},
{
rfilename: "README.md"
},
{
rfilename: "app.py"
},
{
rfilename: "requirements.txt"
}
]
};
export const BUILDING = {
_test: true,
_id: "621ffddf36468d709f184f2b",
id: "pngwn/test",
author: "pngwn",
sha: "5d35bea1c37f0b727798d9325532a3a47cedb8d8",
lastModified: "2022-01-10T11:02:33.000Z",
private: false,
cardData: {
title: "Test",
emoji: "🌖",
colorFrom: "purple",
colorTo: "indigo",
sdk: "gradio",
app_file: "app.py",
pinned: false
},
gated: false,
disabled: false,
subdomain: "pngwn-test",
tags: ["gradio"],
likes: 0,
sdk: "gradio",
runtime: {
stage: "BUILDING",
hardware: {
current: "cpu-basic",
requested: "cpu-basic"
},
resources: {
requests: {
cpu: 0.2,
memory: "1G",
gpu: "0",
gpuModel: "",
ephemeral: "0G"
},
limits: {
cpu: 8,
memory: "16G",
gpu: "0",
gpuModel: "",
ephemeral: "50G"
},
replicas: 1,
throttled: false,
is_custom: false,
ports: []
},
gcTimeout: null
},
siblings: [
{
rfilename: ".gitattributes"
},
{
rfilename: "README.md"
},
{
rfilename: "app.py"
}
]
};
export const RUNNING_BUILDING = {
_test: true,
_id: "621ffddf36468d709f184f2b",
id: "pngwn/test",
author: "pngwn",
sha: "5d35bea1c37f0b727798d9325532a3a47cedb8d8",
lastModified: "2022-01-10T11:02:33.000Z",
private: false,
cardData: {
title: "Test",
emoji: "🌖",
colorFrom: "purple",
colorTo: "indigo",
sdk: "gradio",
app_file: "app.py",
pinned: false
},
gated: false,
disabled: false,
subdomain: "pngwn-test",
tags: ["gradio"],
likes: 0,
sdk: "gradio",
runtime: {
stage: "RUNNING_BUILDING",
sdk: "gradio",
sdkVersion: "3.18.0",
hardware: {
current: "cpu-basic",
requested: "cpu-basic"
},
resources: {
requests: {
cpu: "0.2",
memory: "1G",
gpu: "0",
gpuModel: null,
ephemeral: "0G"
},
limits: {
cpu: "2.0",
memory: "16G",
gpu: "0",
gpuModel: null,
ephemeral: "50G"
},
replicas: 1,
throttled: false,
is_custom: false,
ports: []
},
gcTimeout: null
},
siblings: [
{
rfilename: ".gitattributes"
},
{
rfilename: "README.md"
},
{
rfilename: "app.py"
}
]
};
export const NO_APP_FILE = {
_test: true,
_id: "627a52a6f652717251c6f238",
id: "pngwn/AnimeGANv2_v3",
author: "pngwn",
sha: "977cfd1d60a3dcdb18577c37210257f8702b58dd",
lastModified: "2022-05-10T11:55:18.000Z",
private: false,
cardData: {
title: "AnimeGANv2_v3",
emoji: "🔥",
colorFrom: "green",
colorTo: "pink",
sdk: "gradio",
sdk_version: "2.9.4",
app_file: "app.py",
pinned: false
},
gated: false,
disabled: false,
subdomain: "pngwn-animeganv2-v3",
tags: ["gradio"],
likes: 0,
sdk: "gradio",
runtime: {
stage: "NO_APP_FILE",
hardware: {
current: null,
requested: "cpu-basic"
},
resources: {
replicas: 1,
ports: []
},
gcTimeout: null
},
siblings: [
{
rfilename: ".gitattributes"
},
{
rfilename: "README.md"
}
]
};
export const CONFIG_ERROR = {
_test: true,
_id: "627d3fd94918f2e0b4333354",
id: "pngwn/clear-inputs",
author: "pngwn",
sha: "d8fe24c4d35a195feacdfa9d0cbf6cfa9a7efdfc",
lastModified: "2022-05-12T17:38:44.000Z",
private: false,
cardData: {
title: "Clear Inputs",
emoji: "🐢",
colorFrom: "indigo",
colorTo: "gray",
sdk: "gradio",
sdk_version: "2.9b26",
app_file: "app.py",
pinned: false
},
gated: false,
disabled: false,
subdomain: "pngwn-clear-inputs",
tags: ["gradio"],
likes: 0,
sdk: "gradio",
runtime: {
stage: "CONFIG_ERROR",
errorMessage: "ConfigError: Gradio version does not exist",
hardware: {
current: null,
requested: "cpu-basic"
},
spaceId: "pngwn/clear-inputs"
},
siblings: [
{
rfilename: ".gitattributes"
},
{
rfilename: "README.md"
},
{
rfilename: "app.py"
}
]
};
export const RUNTIME_ERROR = {
_test: true,
_id: "634825a6c241cb6791d3472f",
id: "pngwn/altair-charts",
author: "pngwn",
sha: "f304b7c624bbd9e47a3a6dc59b03986416aa22fa",
lastModified: "2022-10-13T17:27:52.000Z",
private: false,
cardData: {
title: "Altair Charts",
emoji: "🚀",
colorFrom: "green",
colorTo: "gray",
sdk: "gradio",
sdk_version: "3.4.1",
app_file: "app.py",
pinned: false
},
gated: false,
disabled: false,
subdomain: "pngwn-altair-charts",
tags: ["gradio"],
likes: 0,
sdk: "gradio",
runtime: {
stage: "RUNTIME_ERROR",
errorMessage: "launch timed out, space was not healthy after 30 min",
hardware: {
current: null,
requested: "cpu-basic"
},
resources: {
replicas: 1,
ports: []
},
gcTimeout: null
},
siblings: [
{
rfilename: ".gitattributes"
},
{
rfilename: "README.md"
},
{
rfilename: "app.py"
},
{
rfilename: "requirements.txt"
}
]
};
export const BUILD_ERROR = {
_test: true,
_id: "63ec0a88a86bb0294d3fbe41",
id: "Gaborandi/PubMed_Downloader",
author: "Gaborandi",
sha: "3f2a988756800e4f39e96cabda6ccbf2c9c25783",
lastModified: "2023-02-15T02:26:49.000Z",
private: false,
cardData: {
title: "PubMed Downloader",
emoji: "🌖",
colorFrom: "gray",
colorTo: "indigo",
sdk: "gradio",
sdk_version: "3.18.0",
app_file: "app.py",
pinned: false
},
gated: false,
disabled: false,
subdomain: "gaborandi-pubmed-downloader",
tags: ["gradio"],
likes: 0,
sdk: "gradio",
runtime: {
stage: "BUILD_ERROR",
errorMessage: "build failed with exit code: 1, message: None",
hardware: {
current: null,
requested: "cpu-basic"
},
resources: {
replicas: 1,
ports: []
},
gcTimeout: null
},
siblings: [
{
rfilename: ".gitattributes"
},
{
rfilename: "README.md"
},
{
rfilename: "app.py"
},
{
rfilename: "requirements.txt"
}
]
};

View File

@ -24,7 +24,7 @@ function mock_theme(page: Page) {
}
function mock_api(page: Page, body: Array<unknown>) {
return page.route("**/run/predict/", (route) => {
return page.route("**/run/predict", (route) => {
const id = JSON.parse(route.request().postData()!).fn_index;
return route.fulfill({
headers: {
@ -51,7 +51,7 @@ test("matplotlib", async ({ page }) => {
await Promise.all([
page.click("text=Submit"),
page.waitForResponse("**/run/predict/")
page.waitForResponse("**/run/predict")
]);
const matplotlib_img = await page.locator("img").nth(0);
@ -80,7 +80,7 @@ test("plotly", async ({ page }) => {
await Promise.all([
page.click("text=Submit"),
page.waitForResponse("**/run/predict/")
page.waitForResponse("**/run/predict")
]);
await expect(page.locator(".js-plotly-plot")).toHaveCount(1);
});

View File

@ -32,19 +32,44 @@ async function changeSlider(
await page.mouse.up();
}
function mock_demo(page: Page, demo: string) {
return page.route("**/config", (route) => {
return route.fulfill({
headers: {
"Access-Control-Allow-Origin": "*"
},
path: `../../../demo/${demo}/config.json`
});
});
}
function mock_api(page: Page) {
return page.route("**/run/predict", (route) => {
return route.fulfill({
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify({
data: [70, null, 1]
})
});
});
}
test("slider release", async ({ page }) => {
await page.goto("http://127.0.0.1:7888/");
await mock_demo(page, "slider_release");
await mock_api(page);
await page.goto("http://localhost:9876");
const slider = page.getByLabel("Slider");
const responsePromise = page.waitForResponse(
"http://127.0.0.1:7888/run/predict/"
);
await changeSlider(page, slider, slider, 0.7);
const response = await responsePromise;
const responseData = await response.json();
expect(responseData.data[0]).toBeGreaterThan(69.5);
expect(responseData.data[0]).toBeLessThan(71.0);
expect(responseData.data[2]).toEqual(1);
const value = page.getByLabel("On release");
const events = page.getByLabel("Number of events fired");
const val = await slider.inputValue();
expect(parseInt(val)).toBeCloseTo(70);
expect(value).toHaveValue("70");
expect(events).toHaveValue("1");
});

View File

@ -14,7 +14,7 @@
const dispatch = createEventDispatcher<{ change: undefined }>();
const redirect_src_url = (src: string) =>
src.replace('src="/file', `src="${root}file`);
src.replace('src="/file', `src="${root}/file`);
$: _value = value
? value.map(([user_msg, bot_msg]) => [

View File

@ -0,0 +1,46 @@
# `@gradio/client`
A javascript client to call Gradio APIs.
**usage**
```ts
import { client } from "@gradio/client";
const app = client();
const prediction = app.predict(endpoint, payload);
// listen for predictions
prediction.on("data", (event: { data: Array<unknown>; type: "data" }) => {});
// listen for status updates
prediction.on("status", (event: { data: Status; type: "data" }) => {});
interface Status {
status: "pending" | "error" | "complete" | "generating";
size: number;
position?: number;
eta?: number;
message?: string;
progress?: Array<{
progress: number | null;
index: number | null;
length: number | null;
unit: string | null;
desc: string | null;
}>;
}
// stop listening
prediction.off("data");
// cancel a prediction if it is a generator
prediction.cancel();
// chainable
const prediction_two = app
.predict(endpoint, payload)
.on("data", data_callback)
.on("status", status_callback);
```

View File

@ -0,0 +1,10 @@
{
"name": "@gradio/client",
"version": "0.0.1",
"description": "Gradio UI packages",
"type": "module",
"main": "src/index.ts",
"author": "",
"license": "ISC",
"private": true
}

View File

@ -0,0 +1,533 @@
import {
process_endpoint,
RE_SPACE_NAME,
map_names_to_ids,
discussions_enabled
} from "./utils";
import type {
EventType,
EventListener,
ListenerMap,
Event,
Config,
Payload,
PostResponse,
UploadResponse,
Status,
SpaceStatus,
SpaceStatusCallback
} from "./types";
type event = <K extends EventType>(
eventType: K,
listener: EventListener<K>
) => ReturnType<predict>;
type predict = (endpoint: string, payload: Payload) => {};
type client_return = {
predict: predict;
config: Config;
on: event;
off: event;
cancel: (endpoint: string, fn_index?: number) => void;
};
const QUEUE_FULL_MSG = "This application is too busy. Keep trying!";
const BROKEN_CONNECTION_MSG = "Connection errored out.";
export async function post_data(
url: string,
body: unknown
): Promise<[PostResponse, number]> {
try {
var response = await fetch(url, {
method: "POST",
body: JSON.stringify(body),
headers: { "Content-Type": "application/json" }
});
} catch (e) {
return [{ error: BROKEN_CONNECTION_MSG }, 500];
}
const output: PostResponse = await response.json();
return [output, response.status];
}
export async function upload_files(
root: string,
files: Array<File>
): Promise<UploadResponse> {
const formData = new FormData();
files.forEach((file) => {
formData.append("files", file);
});
try {
var response = await fetch(`${root}/upload`, {
method: "POST",
body: formData
});
} catch (e) {
return { error: BROKEN_CONNECTION_MSG };
}
const output: UploadResponse["files"] = await response.json();
return { files: output };
}
export async function client(
app_reference: string,
space_status_callback?: SpaceStatusCallback
): Promise<client_return> {
return new Promise(async (res, rej) => {
const return_obj = {
predict,
on,
off,
cancel
};
const listener_map: ListenerMap<EventType> = {};
const { ws_protocol, http_protocol, host, space_id } =
await process_endpoint(app_reference);
const session_hash = Math.random().toString(36).substring(2);
const ws_map = new Map<number, WebSocket>();
const last_status: Record<string, Status["status"]> = {};
let config: Config;
let api_map: Record<string, number> = {};
function config_success(_config: Config) {
config = _config;
api_map = map_names_to_ids(_config?.dependencies || []);
return {
config,
...return_obj
};
}
function on<K extends EventType>(eventType: K, listener: EventListener<K>) {
const narrowed_listener_map: ListenerMap<K> = listener_map;
let listeners = narrowed_listener_map[eventType] || [];
narrowed_listener_map[eventType] = listeners;
listeners?.push(listener);
return { ...return_obj, config };
}
function off<K extends EventType>(
eventType: K,
listener: EventListener<K>
) {
const narrowed_listener_map: ListenerMap<K> = listener_map;
let listeners = narrowed_listener_map[eventType] || [];
listeners = listeners?.filter((l) => l !== listener);
narrowed_listener_map[eventType] = listeners;
return { ...return_obj, config };
}
function cancel(endpoint: string, fn_index?: number) {
const _index =
typeof fn_index === "number" ? fn_index : api_map[endpoint];
fire_event({
type: "status",
endpoint,
fn_index: _index,
status: "complete",
queue: false
});
ws_map.get(_index)?.close();
}
function fire_event<K extends EventType>(event: Event<K>) {
const narrowed_listener_map: ListenerMap<K> = listener_map;
let listeners = narrowed_listener_map[event.type] || [];
listeners?.forEach((l) => l(event));
}
async function handle_space_sucess(status: SpaceStatus) {
if (space_status_callback) space_status_callback(status);
if (status.status === "running")
try {
config = await resolve_config(`${http_protocol}//${host}`);
res(config_success(config));
} catch (e) {
if (space_status_callback) {
space_status_callback({
status: "error",
message: "Could not load this space.",
load_status: "error",
detail: "NOT_FOUND"
});
}
}
}
try {
config = await resolve_config(`${http_protocol}//${host}`);
res(config_success(config));
} catch (e) {
if (space_id) {
check_space_status(
space_id,
RE_SPACE_NAME.test(space_id) ? "space_name" : "subdomain",
handle_space_sucess
);
} else {
if (space_status_callback)
space_status_callback({
status: "error",
message: "Could not load this space.",
load_status: "error",
detail: "NOT_FOUND"
});
}
}
function make_predict(endpoint: string, payload: Payload) {
return new Promise((res, rej) => {
const trimmed_endpoint = endpoint.replace(/^\//, "");
let fn_index =
typeof payload.fn_index === "number"
? payload.fn_index
: api_map[trimmed_endpoint];
if (skip_queue(fn_index, config)) {
fire_event({
type: "status",
endpoint,
status: "pending",
queue: false,
fn_index
});
post_data(
`${http_protocol}//${host}/run${
endpoint.startsWith("/") ? endpoint : `/${endpoint}`
}`,
{
...payload,
session_hash
}
)
.then(([output, status_code]) => {
if (status_code == 200) {
fire_event({
type: "status",
endpoint,
fn_index,
status: "complete",
eta: output.average_duration,
queue: false
});
fire_event({
type: "data",
endpoint,
fn_index,
data: output.data
});
} else {
fire_event({
type: "status",
status: "error",
endpoint,
fn_index,
message: output.error,
queue: false
});
}
})
.catch((e) => {
fire_event({
type: "status",
status: "error",
message: e.message,
endpoint,
fn_index,
queue: false
});
throw new Error(e.message);
});
} else {
fire_event({
type: "status",
status: "pending",
queue: true,
endpoint,
fn_index
});
const ws_endpoint = `${ws_protocol}://${host}/queue/join`;
const websocket = new WebSocket(ws_endpoint);
ws_map.set(fn_index, websocket);
websocket.onclose = (evt) => {
if (!evt.wasClean) {
fire_event({
type: "status",
status: "error",
message: BROKEN_CONNECTION_MSG,
queue: true,
endpoint,
fn_index
});
}
};
websocket.onmessage = function (event) {
const _data = JSON.parse(event.data);
const { type, status, data } = handle_message(
_data,
last_status[fn_index]
);
if (type === "update" && status) {
// call 'status' listeners
fire_event({ type: "status", endpoint, fn_index, ...status });
if (status.status === "error") {
websocket.close();
rej(status);
}
} else if (type === "hash") {
websocket.send(JSON.stringify({ fn_index, session_hash }));
return;
} else if (type === "data") {
websocket.send(JSON.stringify({ ...payload, session_hash }));
} else if (type === "complete") {
fire_event({
type: "status",
...status,
status: status?.status!,
queue: true,
endpoint,
fn_index
});
websocket.close();
} else if (type === "generating") {
fire_event({
type: "status",
...status,
status: status?.status!,
queue: true,
endpoint,
fn_index
});
}
if (data) {
fire_event({
type: "data",
data: data.data,
endpoint,
fn_index
});
res({ data: data.data });
}
};
}
});
}
/**
* Run a prediction.
* @param endpoint - The prediction endpoint to use.
* @param status_callback - A function that is called with the current status of the prediction immediately and every time it updates.
* @return Returns the data for the prediction or an error message.
*/
function predict(endpoint: string, payload: Payload) {
return make_predict(endpoint, payload);
}
});
}
function skip_queue(id: number, config: Config) {
return (
!(config?.dependencies?.[id].queue === null
? config.enable_queue
: config?.dependencies?.[id].queue) || false
);
}
async function resolve_config(endpoint?: string): Promise<Config> {
if (window.gradio_config && location.origin !== "http://localhost:9876") {
return { ...window.gradio_config, root: endpoint };
} else if (endpoint) {
let response = await fetch(`${endpoint}/config`);
if (response.status === 200) {
const config = await response.json();
config.root = endpoint;
return config;
} else {
throw new Error("Could not get config.");
}
}
throw new Error("No config or app endpoint found");
}
async function check_space_status(
id: string,
type: "subdomain" | "space_name",
space_status_callback: SpaceStatusCallback
) {
let endpoint =
type === "subdomain"
? `https://huggingface.co/api/spaces/by-subdomain/${id}`
: `https://huggingface.co/api/spaces/${id}`;
let response;
let _status;
try {
response = await fetch(endpoint);
_status = response.status;
if (_status !== 200) {
throw new Error();
}
response = await response.json();
} catch (e) {
space_status_callback({
status: "error",
load_status: "error",
message: "Could not get space status",
detail: "NOT_FOUND"
});
return;
}
if (!response || _status !== 200) return;
const {
runtime: { stage },
id: space_name
} = response;
switch (stage) {
case "STOPPED":
case "SLEEPING":
space_status_callback({
status: "sleeping",
load_status: "pending",
message: "Space is asleep. Waking it up...",
detail: stage
});
setTimeout(() => {
check_space_status(id, type, space_status_callback);
}, 1000);
break;
// poll for status
case "RUNNING":
case "RUNNING_BUILDING":
space_status_callback({
status: "running",
load_status: "complete",
message: "",
detail: stage
});
// load_config(source);
// launch
break;
case "BUILDING":
space_status_callback({
status: "building",
load_status: "pending",
message: "Space is building...",
detail: stage
});
setTimeout(() => {
check_space_status(id, type, space_status_callback);
}, 1000);
break;
default:
space_status_callback({
status: "space_error",
load_status: "error",
message: "This space is experiencing an issue.",
detail: stage,
discussions_enabled: await discussions_enabled(space_name)
});
break;
}
}
function handle_message(
data: any,
last_status: Status["status"]
): {
type: "hash" | "data" | "update" | "complete" | "generating" | "none";
data?: any;
status?: Status;
} {
const queue = true;
switch (data.msg) {
case "send_data":
return { type: "data" };
case "send_hash":
return { type: "hash" };
case "queue_full":
return {
type: "update",
status: {
queue,
message: QUEUE_FULL_MSG,
status: "error"
}
};
case "estimation":
return {
type: "update",
status: {
queue,
status: last_status || "pending",
size: data.queue_size,
position: data.rank,
eta: data.rank_eta
}
};
case "progress":
return {
type: "update",
status: {
queue,
status: "pending",
progress: data.progress_data
}
};
case "process_generating":
return {
type: "generating",
status: {
queue,
message: !data.success ? data.output.error : null,
status: data.success ? "generating" : "error",
progress: data.progress_data,
eta: data.average_duration
},
data: data.success ? data.output : null
};
case "process_completed":
return {
type: "complete",
status: {
queue,
message: !data.success ? data.output.error : undefined,
status: data.success ? "complete" : "error",
progress: data.progress_data,
eta: data.output.average_duration
},
data: data.success ? data.output : null
};
case "process_starts":
return {
type: "update",
status: {
queue,
status: "pending",
size: data.rank,
position: 0
}
};
}
return { type: "none", status: { status: "error", queue } };
}

View File

@ -0,0 +1,2 @@
export { client, post_data, upload_files } from "./client";
export type { SpaceStatus } from "./types";

View File

@ -0,0 +1,86 @@
export interface Config {
auth_required: boolean | undefined;
auth_message: string;
components: any[];
css: string | null;
dependencies: any[];
dev_mode: boolean;
enable_queue: boolean;
layout: any;
mode: "blocks" | "interface";
root: string;
theme: string;
title: string;
version: string;
is_space: boolean;
is_colab: boolean;
show_api: boolean;
}
export interface Payload {
data: Array<unknown>;
fn_index: number;
}
export interface PostResponse {
error?: string;
[x: string]: any;
}
export interface UploadResponse {
error?: string;
files?: Array<string>;
}
export interface Status {
queue: boolean;
status: "pending" | "error" | "complete" | "generating";
size?: number;
position?: number;
eta?: number;
message?: string;
progress?: Array<{
progress: number | null;
index: number | null;
length: number | null;
unit: string | null;
desc: string | null;
}>;
}
export interface SpaceStatusNormal {
status: "sleeping" | "running" | "building" | "error" | "stopped";
detail:
| "SLEEPING"
| "RUNNING"
| "RUNNING_BUILDING"
| "BUILDING"
| "NOT_FOUND";
load_status: "pending" | "error" | "complete" | "generating";
message: string;
}
export interface SpaceStatusError {
status: "space_error";
detail: "NO_APP_FILE" | "CONFIG_ERROR" | "BUILD_ERROR" | "RUNTIME_ERROR";
load_status: "error";
message: string;
discussions_enabled: boolean;
}
export type SpaceStatus = SpaceStatusNormal | SpaceStatusError;
export type status_callback_function = (a: Status) => void;
export type SpaceStatusCallback = (a: SpaceStatus) => void;
export type EventType = "data" | "status";
export interface EventMap {
data: Record<string, any>;
status: Status;
}
export type Event<K extends EventType> = {
[P in K]: EventMap[P] & { type: P; endpoint: string; fn_index: number };
}[K];
export type EventListener<K extends EventType> = (event: Event<K>) => void;
export type ListenerMap<K extends EventType> = {
[P in K]?: EventListener<K>[];
};

View File

@ -0,0 +1,101 @@
import type { Config } from "./types";
export function determine_protocol(endpoint: string): {
ws_protocol: "ws" | "wss";
http_protocol: "http:" | "https:";
host: string;
} {
if (endpoint.startsWith("http")) {
const { protocol, host } = new URL(endpoint);
if (host.endsWith("hf.space")) {
return {
ws_protocol: "wss",
host: host,
http_protocol: protocol as "http:" | "https:"
};
} else {
return {
ws_protocol: protocol === "https:" ? "wss" : "ws",
http_protocol: protocol as "http:" | "https:",
host
};
}
}
// default to secure if no protocol is provided
return {
ws_protocol: "wss",
http_protocol: "https:",
host: endpoint
};
}
export const RE_SPACE_NAME = /^[^\/]*\/[^\/]*$/;
export const RE_SPACE_DOMAIN = /.*hf\.space\/{0,1}$/;
export async function process_endpoint(app_reference: string): Promise<{
space_id: string | false;
host: string;
ws_protocol: "ws" | "wss";
http_protocol: "http:" | "https:";
}> {
const _app_reference = app_reference.trim();
if (RE_SPACE_NAME.test(_app_reference)) {
const _host = (
await (
await fetch(`https://huggingface.co/api/spaces/${_app_reference}/host`)
).json()
).host;
return {
space_id: app_reference,
...determine_protocol(_host)
};
}
if (RE_SPACE_DOMAIN.test(_app_reference)) {
const { ws_protocol, http_protocol, host } =
determine_protocol(_app_reference);
return {
space_id: host.replace(".hf.space", ""),
ws_protocol,
http_protocol,
host
};
}
return {
space_id: false,
...determine_protocol(_app_reference)
};
}
export function map_names_to_ids(fns: Config["dependencies"]) {
let apis: Record<string, number> = {};
fns.forEach(({ api_name }, i) => {
if (api_name) apis[api_name] = i;
});
return apis;
}
const RE_DISABLED_DISCUSSION =
/^(?=[^]*\b[dD]iscussions{0,1}\b)(?=[^]*\b[dD]isabled\b)[^]*$/;
export async function discussions_enabled(space_id: string) {
try {
const r = await fetch(
`https://huggingface.co/api/spaces/${space_id}/discussions`,
{
method: "HEAD"
}
);
const error = r.headers.get("x-error-message");
if (error && RE_DISABLED_DISCUSSION.test(error)) return false;
else return true;
} catch (e) {
return false;
}
}

View File

@ -28,9 +28,9 @@ export function normalise_file(
}
} else if (file.is_file) {
if (root_url == null) {
file.data = root + "file=" + file.name;
file.data = root + "/file=" + file.name;
} else {
file.data = "proxy=" + root_url + "file=" + file.name;
file.data = "/proxy=" + root_url + "/file=" + file.name;
}
}
return file;

View File

@ -4,12 +4,5 @@ export default {
trace: "retain-on-failure"
},
globalSetup: "./playwright-setup.js",
workers: 1,
webServer: [
{
command: "GRADIO_SERVER_PORT=7888 python ../demo/slider_release/run.py",
port: 7888,
timeout: 120 * 1000
}
]
workers: 1
};

98
ui/pnpm-lock.yaml generated
View File

@ -1,4 +1,4 @@
lockfileVersion: 5.3
lockfileVersion: 5.4
importers:
@ -48,7 +48,7 @@ importers:
'@tailwindcss/forms': 0.5.0_tailwindcss@3.1.6
'@testing-library/dom': 8.11.3
'@testing-library/svelte': 3.1.0_svelte@3.49.0
'@testing-library/user-event': 13.5.0_@testing-library+dom@8.11.3
'@testing-library/user-event': 13.5.0_gzufz4q333be4gqfrvipwvqt6a
autoprefixer: 10.4.4_postcss@8.4.6
babylonjs: 5.18.0
babylonjs-loaders: 5.18.0
@ -65,15 +65,15 @@ importers:
postcss-nested: 5.0.6_postcss@8.4.6
postcss-prefix-selector: 1.16.0_postcss@8.4.6
prettier: 2.6.2
prettier-plugin-css-order: 1.3.0_postcss@8.4.6+prettier@2.6.2
prettier-plugin-svelte: 2.7.0_prettier@2.6.2+svelte@3.49.0
prettier-plugin-css-order: 1.3.0_ob5okuz2s5mlecytbeo2erc43a
prettier-plugin-svelte: 2.7.0_3cyj5wbackxvw67rnaarcmbw7y
sirv: 2.0.2
sirv-cli: 2.0.2
svelte: 3.49.0
svelte-check: 2.8.0_postcss@8.4.6+svelte@3.49.0
svelte-check: 2.8.0_mgmdnb6x5rpawk37gozc2sbtta
svelte-i18n: 3.3.13_svelte@3.49.0
svelte-preprocess: 4.10.6_62d50a01257de5eec5be08cad9d3ed66
tailwindcss: 3.1.6
svelte-preprocess: 4.10.6_mlkquajfpxs65rn6bdfntu7nmy
tailwindcss: 3.1.6_postcss@8.4.6
tinyspy: 0.3.0
typescript: 4.7.4
vite: 2.9.9
@ -98,6 +98,7 @@ importers:
'@gradio/button': workspace:^0.0.1
'@gradio/chart': workspace:^0.0.1
'@gradio/chatbot': workspace:^0.0.1
'@gradio/client': workspace:^0.0.1
'@gradio/file': workspace:^0.0.1
'@gradio/form': workspace:^0.0.1
'@gradio/gallery': workspace:^0.0.1
@ -130,6 +131,7 @@ importers:
'@gradio/button': link:../button
'@gradio/chart': link:../chart
'@gradio/chatbot': link:../chatbot
'@gradio/client': link:../client
'@gradio/file': link:../file
'@gradio/form': link:../form
'@gradio/gallery': link:../gallery
@ -219,6 +221,9 @@ importers:
'@gradio/theme': link:../theme
'@gradio/utils': link:../utils
packages/client:
specifiers: {}
packages/file:
specifiers:
'@gradio/atoms': workspace:^0.0.1
@ -334,7 +339,7 @@ importers:
'@gradio/utils': link:../utils
'@rollup/plugin-json': 5.0.2
plotly.js-dist-min: 2.11.1
svelte-vega: 1.2.0_vega-lite@5.6.0+vega@5.22.1
svelte-vega: 1.2.0_36sthfwhgi34qytpvkzggbhnle
vega: 5.22.1
vega-lite: 5.6.0_vega@5.22.1
@ -466,14 +471,14 @@ importers:
'@gradio/video': link:../video
svelte: 3.49.0
devDependencies:
'@sveltejs/adapter-auto': 1.0.0-next.91_@sveltejs+kit@1.0.0-next.318
'@sveltejs/adapter-auto': 1.0.0-next.91_b2bjiolq6much32vueqoio7eoy
'@sveltejs/kit': 1.0.0-next.318_svelte@3.49.0
autoprefixer: 10.4.2_postcss@8.4.6
postcss: 8.4.6
postcss-load-config: 3.1.1
svelte-check: 2.4.1_736abba5ed1eb6f8ecf70b1d49ead14b
svelte-preprocess: 4.10.2_d50790bb30dd88cc44babe7efa52bace
tailwindcss: 3.1.6
svelte-check: 2.4.1_onvlxjpnd23pr3hxbmout2wrjm
svelte-preprocess: 4.10.2_2udzbozq3wemyrf2xz7puuv2zy
tailwindcss: 3.1.6_postcss@8.4.6
tslib: 2.3.1
typescript: 4.5.5
@ -675,7 +680,7 @@ packages:
picomatch: 2.3.1
dev: false
/@sveltejs/adapter-auto/1.0.0-next.91_@sveltejs+kit@1.0.0-next.318:
/@sveltejs/adapter-auto/1.0.0-next.91_b2bjiolq6much32vueqoio7eoy:
resolution: {integrity: sha512-U57tQdzTfFINim8tzZSARC9ztWPzwOoHwNOpGdb2o6XrD0mEQwU9DsII7dBblvzg+xCnmd0pw7PDtXz5c5t96w==}
peerDependencies:
'@sveltejs/kit': ^1.0.0-next.587
@ -731,7 +736,7 @@ packages:
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
dependencies:
mini-svg-data-uri: 1.4.4
tailwindcss: 3.1.6
tailwindcss: 3.1.6_postcss@8.4.6
dev: false
/@testing-library/dom/7.31.2:
@ -772,7 +777,7 @@ packages:
svelte: 3.49.0
dev: false
/@testing-library/user-event/13.5.0_@testing-library+dom@8.11.3:
/@testing-library/user-event/13.5.0_gzufz4q333be4gqfrvipwvqt6a:
resolution: {integrity: sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==}
engines: {node: '>=10', npm: '>=6'}
peerDependencies:
@ -2977,25 +2982,25 @@ packages:
postcss-value-parser: 4.2.0
dev: false
/postcss-import/14.1.0_postcss@8.4.21:
/postcss-import/14.1.0_postcss@8.4.6:
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
engines: {node: '>=10.0.0'}
peerDependencies:
postcss: ^8.0.0
dependencies:
postcss: 8.4.21
postcss: 8.4.6
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.1
/postcss-js/4.0.0_postcss@8.4.21:
/postcss-js/4.0.0_postcss@8.4.6:
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
engines: {node: ^12 || ^14 || >= 16}
peerDependencies:
postcss: ^8.3.3
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.21
postcss: 8.4.6
/postcss-less/6.0.0_postcss@8.4.6:
resolution: {integrity: sha512-FPX16mQLyEjLzEuuJtxA8X3ejDLNGGEG503d2YGZR5Ask1SpDN8KmZUMpzCvyalWRywAn1n1VOA5dcqfCLo5rg==}
@ -3019,7 +3024,7 @@ packages:
yaml: 1.10.2
dev: true
/postcss-load-config/3.1.4_postcss@8.4.21:
/postcss-load-config/3.1.4_postcss@8.4.6:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'}
peerDependencies:
@ -3032,18 +3037,9 @@ packages:
optional: true
dependencies:
lilconfig: 2.0.6
postcss: 8.4.21
postcss: 8.4.6
yaml: 1.10.2
/postcss-nested/5.0.6_postcss@8.4.21:
resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
dependencies:
postcss: 8.4.21
postcss-selector-parser: 6.0.9
/postcss-nested/5.0.6_postcss@8.4.6:
resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==}
engines: {node: '>=12.0'}
@ -3052,7 +3048,6 @@ packages:
dependencies:
postcss: 8.4.6
postcss-selector-parser: 6.0.9
dev: false
/postcss-prefix-selector/1.16.0_postcss@8.4.21:
resolution: {integrity: sha512-rdVMIi7Q4B0XbXqNUEI+Z4E+pueiu/CS5E6vRCQommzdQ/sgsS4dK42U7GX8oJR+TJOtT+Qv3GkNo6iijUMp3Q==}
@ -3111,6 +3106,7 @@ packages:
nanoid: 3.3.4
picocolors: 1.0.0
source-map-js: 1.0.2
dev: false
/postcss/8.4.6:
resolution: {integrity: sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==}
@ -3120,7 +3116,7 @@ packages:
picocolors: 1.0.0
source-map-js: 1.0.2
/prettier-plugin-css-order/1.3.0_postcss@8.4.6+prettier@2.6.2:
/prettier-plugin-css-order/1.3.0_ob5okuz2s5mlecytbeo2erc43a:
resolution: {integrity: sha512-wOS4qlbUARCoiiuOG0TiB/j751soC3+gUnMMva5HVWKvHJdLNYqh+jXK3MvvixR6xkJVPxHSF7rIIhkHIuHTFg==}
engines: {node: '>=14'}
peerDependencies:
@ -3135,7 +3131,7 @@ packages:
- postcss
dev: false
/prettier-plugin-svelte/2.7.0_prettier@2.6.2+svelte@3.49.0:
/prettier-plugin-svelte/2.7.0_3cyj5wbackxvw67rnaarcmbw7y:
resolution: {integrity: sha512-fQhhZICprZot2IqEyoiUYLTRdumULGRvw0o4dzl5jt0jfzVWdGqeYW27QTWAeXhoupEZJULmNoH3ueJwUWFLIA==}
peerDependencies:
prettier: ^1.16.4 || ^2.0.0
@ -3593,7 +3589,7 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
/svelte-check/2.4.1_736abba5ed1eb6f8ecf70b1d49ead14b:
/svelte-check/2.4.1_onvlxjpnd23pr3hxbmout2wrjm:
resolution: {integrity: sha512-xhf3ShP5rnRwBokrgTBJ/0cO9QIc1DAVu1NWNRTfCDsDBNjGmkS3HgitgUadRuoMKj1+irZR/yHJ+Uqobnkbrw==}
hasBin: true
peerDependencies:
@ -3607,7 +3603,7 @@ packages:
sade: 1.8.1
source-map: 0.7.3
svelte: 3.49.0
svelte-preprocess: 4.10.2_d50790bb30dd88cc44babe7efa52bace
svelte-preprocess: 4.10.2_2udzbozq3wemyrf2xz7puuv2zy
typescript: 4.5.5
transitivePeerDependencies:
- '@babel/core'
@ -3622,7 +3618,7 @@ packages:
- sugarss
dev: true
/svelte-check/2.8.0_postcss@8.4.6+svelte@3.49.0:
/svelte-check/2.8.0_mgmdnb6x5rpawk37gozc2sbtta:
resolution: {integrity: sha512-HRL66BxffMAZusqe5I5k26mRWQ+BobGd9Rxm3onh7ZVu0nTk8YTKJ9vu3LVPjUGLU9IX7zS+jmwPVhJYdXJ8vg==}
hasBin: true
peerDependencies:
@ -3635,7 +3631,7 @@ packages:
picocolors: 1.0.0
sade: 1.8.1
svelte: 3.49.0
svelte-preprocess: 4.10.6_62d50a01257de5eec5be08cad9d3ed66
svelte-preprocess: 4.10.6_mlkquajfpxs65rn6bdfntu7nmy
typescript: 4.7.4
transitivePeerDependencies:
- '@babel/core'
@ -3673,7 +3669,7 @@ packages:
tiny-glob: 0.2.9
dev: false
/svelte-preprocess/4.10.2_d50790bb30dd88cc44babe7efa52bace:
/svelte-preprocess/4.10.2_2udzbozq3wemyrf2xz7puuv2zy:
resolution: {integrity: sha512-aPpkCreSo8EL/y8kJSa1trhiX0oyAtTjlNNM7BNjRAsMJ8Yy2LtqHt0zyd4pQPXt+D4PzbO3qTjjio3kwOxDlA==}
engines: {node: '>= 9.11.2'}
requiresBuild: true
@ -3726,7 +3722,7 @@ packages:
typescript: 4.5.5
dev: true
/svelte-preprocess/4.10.6_62d50a01257de5eec5be08cad9d3ed66:
/svelte-preprocess/4.10.6_mlkquajfpxs65rn6bdfntu7nmy:
resolution: {integrity: sha512-I2SV1w/AveMvgIQlUF/ZOO3PYVnhxfcpNyGt8pxpUVhPfyfL/CZBkkw/KPfuFix5FJ9TnnNYMhACK3DtSaYVVQ==}
engines: {node: '>= 9.11.2'}
requiresBuild: true
@ -3782,7 +3778,7 @@ packages:
resolution: {integrity: sha512-VTWHOdwDyWbndGZnI0PQJY9DO7hgQlNubtCcCL6Wlypv5dU4vEsc4A1sX9TWMuvebEe4332SgsQQHzOdZ+guhQ==}
dev: false
/svelte-vega/1.2.0_vega-lite@5.6.0+vega@5.22.1:
/svelte-vega/1.2.0_36sthfwhgi34qytpvkzggbhnle:
resolution: {integrity: sha512-MsDdO+l7o/d9d4mVkh8MBDhqZvJ45lpuprBaTj0V/ZilIG902QERHFQlam3ZFcR9C9OIKSpmPqINssWNPkDdcA==}
peerDependencies:
vega: '*'
@ -3790,7 +3786,7 @@ packages:
dependencies:
fast-deep-equal: 3.1.3
vega: 5.22.1
vega-embed: 6.21.0_vega-lite@5.6.0+vega@5.22.1
vega-embed: 6.21.0_36sthfwhgi34qytpvkzggbhnle
vega-lite: 5.6.0_vega@5.22.1
dev: false
@ -3817,10 +3813,12 @@ packages:
resolution: {integrity: sha512-hIdwt/c/e1ONnr2RJmfBxEAj/J6KQQWKdToF3Qw8ZNRsTNNteGkOe63rQy9I7J5UNlr8Yl0wkzIr9wgLY94x0Q==}
dev: false
/tailwindcss/3.1.6:
/tailwindcss/3.1.6_postcss@8.4.6:
resolution: {integrity: sha512-7skAOY56erZAFQssT1xkpk+kWt2NrO45kORlxFPXUt3CiGsVPhH1smuH5XoDH6sGPXLyBv+zgCKA2HWBsgCytg==}
engines: {node: '>=12.13.0'}
hasBin: true
peerDependencies:
postcss: ^8.0.9
dependencies:
arg: 5.0.2
chokidar: 3.5.3
@ -3835,11 +3833,11 @@ packages:
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.0.0
postcss: 8.4.21
postcss-import: 14.1.0_postcss@8.4.21
postcss-js: 4.0.0_postcss@8.4.21
postcss-load-config: 3.1.4_postcss@8.4.21
postcss-nested: 5.0.6_postcss@8.4.21
postcss: 8.4.6
postcss-import: 14.1.0_postcss@8.4.6
postcss-js: 4.0.0_postcss@8.4.6
postcss-load-config: 3.1.4_postcss@8.4.6
postcss-nested: 5.0.6_postcss@8.4.6
postcss-selector-parser: 6.0.10
postcss-value-parser: 4.2.0
quick-lru: 5.1.1
@ -4022,7 +4020,7 @@ packages:
- encoding
dev: false
/vega-embed/6.21.0_vega-lite@5.6.0+vega@5.22.1:
/vega-embed/6.21.0_36sthfwhgi34qytpvkzggbhnle:
resolution: {integrity: sha512-Tzo9VAfgNRb6XpxSFd7uphSeK2w5OxDY2wDtmpsQ+rQlPSEEI9TE6Jsb2nHRLD5J4FrmXKLrTcORqidsNQSXEg==}
peerDependencies:
vega: ^5.21.0
@ -4036,7 +4034,7 @@ packages:
vega-interpreter: 1.0.4
vega-lite: 5.6.0_vega@5.22.1
vega-schema-url-parser: 2.2.0
vega-themes: 2.12.0_vega-lite@5.6.0+vega@5.22.1
vega-themes: 2.12.0_36sthfwhgi34qytpvkzggbhnle
vega-tooltip: 0.28.0
dev: false
bundledDependencies:
@ -4255,7 +4253,7 @@ packages:
d3-array: 3.1.1
dev: false
/vega-themes/2.12.0_vega-lite@5.6.0+vega@5.22.1:
/vega-themes/2.12.0_36sthfwhgi34qytpvkzggbhnle:
resolution: {integrity: sha512-gHNYCzDgexSQDmGzQsxH57OYgFVbAOmvhIYN3MPOvVucyI+zhbUawBVIVNzG9ftucRp0MaaMVXi6ctC5HLnBsg==}
peerDependencies:
vega: '*'