mirror of
https://github.com/gradio-app/gradio.git
synced 2025-04-06 12:30:29 +08:00
Refactor JS Client (#7646)
* initial setup * fix hf token prefix * warnings + error handling * implement stream, predict and submit * update blocks with new client * add changeset * temp comment * add changeset * add sse_v3 chages * imrpove typing, reimplement websocket logic * refactor zerogpu logic * integrate fetch_implemenation * wasm changes * tweaks * tscheck fixes * type fixes * test * test import tweaks * test * package.json * improve typing + config * config tweak * remove fetch-mock * pnpm lock * config * remove module * tweak * tweak * fix config * test * test * lockfile * fix types * more config tweaks * type fixes * formatting * fix typing * formatting * type fixes * test * lockfile * revert comment * package.json * tweak * formatting * test * revert ts removal * remove comments + logic fixes * type fix * formatting * test * stream logic changes * fix test * revert comments * fix streaming test * stream tweak * data typing * fix wasm fetch * client typing tweaks * more fixes + typing improvements * move functions * event_source tweaks * cleanup * add hardware types and cleanup jwt * improve api info typing * add await client() backwards compatiblity * rename create to connect and move duplicate logic * Update `Client` usage examples (#8003) * update client examples * remove test data * fix types * remove types changes * client -> Client.create * Update client/js/README.md Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * Update client/js/README.md Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * update duplicate docs * attempt to update cn docs * format --------- Co-authored-by: Abubakar Abid <abubakar@huggingface.co> * type improvements and config error handling * fix image editor issue * Merge branch 'main' into refactor-client * improve typing --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
This commit is contained in:
parent
0fb058ec23
commit
450b8cc898
8
.changeset/metal-radios-cheat.md
Normal file
8
.changeset/metal-radios-cheat.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
"@gradio/app": minor
|
||||
"@gradio/client": minor
|
||||
"@gradio/spaces-test": minor
|
||||
"gradio": minor
|
||||
---
|
||||
|
||||
feat:Refactor JS Client
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -71,6 +71,7 @@ gradio/frpc_*
|
||||
node_modules
|
||||
public/build/
|
||||
test-results
|
||||
client/js/dist/*
|
||||
client/js/test.js
|
||||
.config/test.py
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
## JavaScript Client Library
|
||||
|
||||
A javascript (and typescript) client to call Gradio APIs.
|
||||
A JavaScript (and TypeScript) Client to call Gradio APIs.
|
||||
|
||||
## Installation
|
||||
|
||||
The Gradio JavaScript client is available on npm as `@gradio/client`. You can install it as below:
|
||||
The Gradio JavaScript Client is available on npm as `@gradio/client`. You can install it as below:
|
||||
|
||||
```sh
|
||||
npm i @gradio/client
|
||||
@ -12,18 +12,18 @@ npm i @gradio/client
|
||||
|
||||
## Usage
|
||||
|
||||
The JavaScript Gradio Client exposes two named imports, `client` and `duplicate`.
|
||||
The JavaScript Gradio Client exposes the Client class, `Client`, along with various other utility functions. `Client` is used to initialise and establish a connection to, or duplicate, a Gradio app.
|
||||
|
||||
### `client`
|
||||
### `Client`
|
||||
|
||||
The client function connects to the API of a hosted Gradio space and returns an object that allows you to make calls to that API.
|
||||
The Client function connects to the API of a hosted Gradio space and returns an object that allows you to make calls to that API.
|
||||
|
||||
The simplest example looks like this:
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
const result = await app.predict("/predict");
|
||||
```
|
||||
|
||||
@ -34,7 +34,7 @@ This function accepts two arguments: `source` and `options`:
|
||||
This is the url or name of the gradio app whose API you wish to connect to. This parameter is required and should always be a string. For example:
|
||||
|
||||
```ts
|
||||
client("user/space-name");
|
||||
Client.connect("user/space-name");
|
||||
```
|
||||
|
||||
#### `options`
|
||||
@ -48,23 +48,23 @@ This should be a Hugging Face personal access token and is required if you wish
|
||||
Example:
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name", { hf_token: "hf_..." });
|
||||
const app = await Client.connect("user/space-name", { hf_token: "hf_..." });
|
||||
```
|
||||
|
||||
##### `status_callback`
|
||||
|
||||
This should be a function which will notify your of the status of a space if it is not running. If the gradio API you are connecting to is awake and running or is not hosted on Hugging Face space then this function will do nothing.
|
||||
This should be a function which will notify you of the status of a space if it is not running. If the gradio API you are connecting to is not awake and running or is not hosted on Hugging Face space then this function will do nothing.
|
||||
|
||||
**Additional context**
|
||||
|
||||
Applications hosted on Hugging Face spaces can be in a number of different states. As spaces are a GitOps tool and will rebuild when new changes are pushed to the repository, they have various building, running and error states. If a space is not 'running' then the function passed as the `status_callback` will notify you of the current state of the space and the status of the space as it changes. Spaces that are building or sleeping can take longer than usual to respond, so you can use this information to give users feedback about the progress of their action.
|
||||
|
||||
```ts
|
||||
import { client, type SpaceStatus } from "@gradio/client";
|
||||
import { Client, type SpaceStatus } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name", {
|
||||
const app = await Client.connect("user/space-name", {
|
||||
// The space_status parameter does not need to be manually annotated, this is just for illustration.
|
||||
space_status: (space_status: SpaceStatus) => console.log(space_status)
|
||||
});
|
||||
@ -100,9 +100,9 @@ The gradio client returns an object with a number of methods and properties:
|
||||
The `predict` method allows you to call an api endpoint and get a prediction result:
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
const result = await app.predict("/predict");
|
||||
```
|
||||
|
||||
@ -113,20 +113,20 @@ const result = await app.predict("/predict");
|
||||
This is the endpoint for an api request and is required. The default endpoint for a `gradio.Interface` is `"/predict"`. Explicitly named endpoints have a custom name. The endpoint names can be found on the "View API" page of a space.
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
const result = await app.predict("/predict");
|
||||
```
|
||||
|
||||
##### `payload`
|
||||
|
||||
The `payload` argument is generally optional but this depends on the API itself. If the API endpoint depends on values being passed in then it is required for the API request to succeed. The data that should be passed in is detailed on the "View API" page of a space, or accessible via the `view_api()` method of the client.
|
||||
The `payload` argument is generally required but this depends on the API itself. If the API endpoint depends on values being passed in then the argument is required for the API request to succeed. The data that should be passed in is detailed on the "View API" page of a space, or accessible via the `view_api()` method of the client.
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
const result = await app.predict("/predict", [1, "Hello", "friends"]);
|
||||
```
|
||||
|
||||
@ -135,9 +135,9 @@ const result = await app.predict("/predict", [1, "Hello", "friends"]);
|
||||
The `submit` method provides a more flexible way to call an API endpoint, providing you with status updates about the current progress of the prediction as well as supporting more complex endpoint types.
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
const submission = app.submit("/predict", payload);
|
||||
```
|
||||
|
||||
@ -179,9 +179,9 @@ interface Status {
|
||||
Usage of these subscribe callback looks like this:
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
const submission = app
|
||||
.submit("/predict", payload)
|
||||
.on("data", (data) => console.log(data))
|
||||
@ -193,9 +193,9 @@ const submission = app
|
||||
The `off` method unsubscribes from a specific event of the submitted job and works similarly to `document.removeEventListener`; both the event name and the original callback must be passed in to successfully unsubscribe:
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
const handle_data = (data) => console.log(data);
|
||||
|
||||
const submission = app.submit("/predict", payload).on("data", handle_data);
|
||||
@ -209,9 +209,9 @@ submission.off("/predict", handle_data);
|
||||
The `destroy` method will remove all subscriptions to a job, regardless of whether or not they are `"data"` or `"status"` events. This is a convenience method for when you do not want to unsubscribe use the `off` method.
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
const handle_data = (data) => console.log(data);
|
||||
|
||||
const submission = app.submit("/predict", payload).on("data", handle_data);
|
||||
@ -225,9 +225,9 @@ submission.destroy();
|
||||
Certain types of gradio function can run repeatedly and in some cases indefinitely. the `cancel` method will stop such an endpoints and prevent the API from issuing additional updates.
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
const submission = app
|
||||
.submit("/predict", payload)
|
||||
.on("data", (data) => console.log(data));
|
||||
@ -242,9 +242,9 @@ submission.cancel();
|
||||
The `view_api` method provides details about the API you are connected to. It returns a JavaScript object of all named endpoints, unnamed endpoints and what values they accept and return. This method does not accept arguments.
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
const api_info = await app.view_api();
|
||||
|
||||
console.log(api_info);
|
||||
@ -255,9 +255,9 @@ console.log(api_info);
|
||||
The `config` property contains the configuration for the gradio application you are connected to. This object may contain useful meta information about the application.
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const app = await Client.connect("user/space-name");
|
||||
console.log(app.config);
|
||||
```
|
||||
|
||||
@ -268,9 +268,9 @@ The duplicate function will attempt to duplicate the space that is referenced an
|
||||
`duplicate` accepts the same arguments as `client` with the addition of a `private` options property dictating whether the duplicated space should be private or public. A huggingface token is required for duplication to work.
|
||||
|
||||
```ts
|
||||
import { duplicate } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await duplicate("user/space-name", {
|
||||
const app = await Client.duplicate("user/space-name", {
|
||||
hf_token: "hf_..."
|
||||
});
|
||||
```
|
||||
@ -292,9 +292,9 @@ Accepts all options that `client` accepts, except `hf_token` is required. [See `
|
||||
This is an optional property specific to `duplicate`'s options object and will determine whether the space should be public or private. Spaces duplicated via the `duplicate` method are public by default.
|
||||
|
||||
```ts
|
||||
import { duplicate } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await duplicate("user/space-name", {
|
||||
const app = await Client.duplicate("user/space-name", {
|
||||
hf_token: "hf_...",
|
||||
private: true
|
||||
});
|
||||
@ -305,9 +305,9 @@ const app = await duplicate("user/space-name", {
|
||||
This is an optional property specific to `duplicate`'s options object and will set the timeout in minutes before the duplicated space will go to sleep.
|
||||
|
||||
```ts
|
||||
import { duplicate } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await duplicate("user/space-name", {
|
||||
const app = await Client.duplicate("user/space-name", {
|
||||
hf_token: "hf_...",
|
||||
private: true,
|
||||
timeout: 5
|
||||
@ -322,16 +322,22 @@ Possible hardware options are:
|
||||
|
||||
- `"cpu-basic"`
|
||||
- `"cpu-upgrade"`
|
||||
- `"cpu-xl"`
|
||||
- `"t4-small"`
|
||||
- `"t4-medium"`
|
||||
- `"a10g-small"`
|
||||
- `"a10g-large"`
|
||||
- `"a10g-largex2"`
|
||||
- `"a10g-largex4"`
|
||||
- `"a100-large"`
|
||||
- `"zero-a10g"`
|
||||
- `"h100"`
|
||||
- `"h100x8"`
|
||||
|
||||
```ts
|
||||
import { duplicate } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await duplicate("user/space-name", {
|
||||
const app = await Client.duplicate("user/space-name", {
|
||||
hf_token: "hf_...",
|
||||
private: true,
|
||||
hardware: "a10g-small"
|
||||
|
@ -15,10 +15,11 @@
|
||||
"dependencies": {
|
||||
"bufferutil": "^4.0.7",
|
||||
"semiver": "^1.1.0",
|
||||
"typescript": "^5.0.0",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.5.4",
|
||||
"@types/ws": "^8.5.10",
|
||||
"esbuild": "^0.20.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
File diff suppressed because it is too large
Load Diff
20
client/js/src/constants.ts
Normal file
20
client/js/src/constants.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// endpoints
|
||||
export const HOST_URL = "host";
|
||||
export const API_URL = "api/predict/";
|
||||
export const SSE_URL_V0 = "queue/join";
|
||||
export const SSE_DATA_URL_V0 = "queue/data";
|
||||
export const SSE_URL = "queue/data";
|
||||
export const SSE_DATA_URL = "queue/join";
|
||||
export const UPLOAD_URL = "upload";
|
||||
export const LOGIN_URL = "login";
|
||||
export const CONFIG_URL = "config";
|
||||
export const API_INFO_URL = "info";
|
||||
export const RAW_API_INFO_URL = "info?serialize=False";
|
||||
export const SPACE_FETCHER_URL =
|
||||
"https://gradio-space-api-fetcher-v2.hf.space/api";
|
||||
export const RESET_URL = "reset";
|
||||
export const SPACE_URL = "https://hf.space/{}";
|
||||
|
||||
// messages
|
||||
export const QUEUE_FULL_MSG = "This application is too busy. Keep trying!";
|
||||
export const BROKEN_CONNECTION_MSG = "Connection errored out.";
|
23
client/js/src/globals.d.ts
vendored
23
client/js/src/globals.d.ts
vendored
@ -1,3 +1,5 @@
|
||||
import { Config } from "./types";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__gradio_mode__: "app" | "website";
|
||||
@ -6,24 +8,3 @@ declare global {
|
||||
__gradio_space__: string | null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
space_id: string | null;
|
||||
is_colab: boolean;
|
||||
show_api: boolean;
|
||||
stylesheets: string[];
|
||||
path: string;
|
||||
}
|
||||
|
300
client/js/src/helpers/api_info.ts
Normal file
300
client/js/src/helpers/api_info.ts
Normal file
@ -0,0 +1,300 @@
|
||||
import type { Status } from "../types";
|
||||
import { QUEUE_FULL_MSG } from "../constants";
|
||||
import type { ApiData, ApiInfo, Config, JsApiData } from "../types";
|
||||
import { determine_protocol } from "./init_helpers";
|
||||
|
||||
export const RE_SPACE_NAME = /^[^\/]*\/[^\/]*$/;
|
||||
export const RE_SPACE_DOMAIN = /.*hf\.space\/{0,1}$/;
|
||||
|
||||
export async function process_endpoint(
|
||||
app_reference: string,
|
||||
hf_token?: `hf_${string}`
|
||||
): Promise<{
|
||||
space_id: string | false;
|
||||
host: string;
|
||||
ws_protocol: "ws" | "wss";
|
||||
http_protocol: "http:" | "https:";
|
||||
}> {
|
||||
const headers: { Authorization?: string } = {};
|
||||
if (hf_token) {
|
||||
headers.Authorization = `Bearer ${hf_token}`;
|
||||
}
|
||||
|
||||
const _app_reference = app_reference.trim();
|
||||
|
||||
if (RE_SPACE_NAME.test(_app_reference)) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://huggingface.co/api/spaces/${_app_reference}/host`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (res.status !== 200)
|
||||
throw new Error("Space metadata could not be loaded.");
|
||||
const _host = (await res.json()).host;
|
||||
|
||||
return {
|
||||
space_id: app_reference,
|
||||
...determine_protocol(_host)
|
||||
};
|
||||
} catch (e: any) {
|
||||
throw new Error("Space metadata could not be loaded." + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
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 transform_api_info(
|
||||
api_info: ApiInfo<ApiData>,
|
||||
config: Config,
|
||||
api_map: Record<string, number>
|
||||
): ApiInfo<JsApiData> {
|
||||
const transformed_info: ApiInfo<JsApiData> = {
|
||||
named_endpoints: {},
|
||||
unnamed_endpoints: {}
|
||||
};
|
||||
|
||||
Object.keys(api_info).forEach((category) => {
|
||||
if (category === "named_endpoints" || category === "unnamed_endpoints") {
|
||||
transformed_info[category] = {};
|
||||
|
||||
Object.entries(api_info[category]).forEach(
|
||||
([endpoint, { parameters, returns }]) => {
|
||||
const dependencyIndex =
|
||||
config.dependencies.findIndex((dep) => dep.api_name === endpoint) ||
|
||||
api_map[endpoint.replace("/", "")] ||
|
||||
-1;
|
||||
|
||||
const dependencyTypes =
|
||||
dependencyIndex !== -1
|
||||
? config.dependencies[dependencyIndex].types
|
||||
: { continuous: false, generator: false };
|
||||
|
||||
const transform_type = (
|
||||
data: ApiData,
|
||||
component: string,
|
||||
serializer: string,
|
||||
signature_type: "return" | "parameter"
|
||||
): JsApiData => ({
|
||||
...data,
|
||||
description: get_description(data.type, serializer),
|
||||
type:
|
||||
get_type(data.type, component, serializer, signature_type) || ""
|
||||
});
|
||||
|
||||
transformed_info[category][endpoint] = {
|
||||
parameters: parameters.map((p: ApiData) =>
|
||||
transform_type(p, p.component, p.serializer, "parameter")
|
||||
),
|
||||
returns: returns.map((r: ApiData) =>
|
||||
transform_type(r, r.component, r.serializer, "return")
|
||||
),
|
||||
type: dependencyTypes
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return transformed_info;
|
||||
}
|
||||
|
||||
export function get_type(
|
||||
type: { type: any; description: string },
|
||||
component: string,
|
||||
serializer: string,
|
||||
signature_type: "return" | "parameter"
|
||||
): string | undefined {
|
||||
switch (type.type) {
|
||||
case "string":
|
||||
return "string";
|
||||
case "boolean":
|
||||
return "boolean";
|
||||
case "number":
|
||||
return "number";
|
||||
}
|
||||
|
||||
if (
|
||||
serializer === "JSONSerializable" ||
|
||||
serializer === "StringSerializable"
|
||||
) {
|
||||
return "any";
|
||||
} else if (serializer === "ListStringSerializable") {
|
||||
return "string[]";
|
||||
} else if (component === "Image") {
|
||||
return signature_type === "parameter" ? "Blob | File | Buffer" : "string";
|
||||
} else if (serializer === "FileSerializable") {
|
||||
if (type?.type === "array") {
|
||||
return signature_type === "parameter"
|
||||
? "(Blob | File | Buffer)[]"
|
||||
: `{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}[]`;
|
||||
}
|
||||
return signature_type === "parameter"
|
||||
? "Blob | File | Buffer"
|
||||
: `{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}`;
|
||||
} else if (serializer === "GallerySerializable") {
|
||||
return signature_type === "parameter"
|
||||
? "[(Blob | File | Buffer), (string | null)][]"
|
||||
: `[{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}, (string | null))][]`;
|
||||
}
|
||||
}
|
||||
|
||||
export function get_description(
|
||||
type: { type: any; description: string },
|
||||
serializer: string
|
||||
): string {
|
||||
if (serializer === "GallerySerializable") {
|
||||
return "array of [file, label] tuples";
|
||||
} else if (serializer === "ListStringSerializable") {
|
||||
return "array of strings";
|
||||
} else if (serializer === "FileSerializable") {
|
||||
return "array of files or single file";
|
||||
}
|
||||
return type.description;
|
||||
}
|
||||
|
||||
export function handle_message(
|
||||
data: any,
|
||||
last_status: Status["stage"]
|
||||
): {
|
||||
type:
|
||||
| "hash"
|
||||
| "data"
|
||||
| "update"
|
||||
| "complete"
|
||||
| "generating"
|
||||
| "log"
|
||||
| "none"
|
||||
| "heartbeat"
|
||||
| "unexpected_error";
|
||||
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,
|
||||
stage: "error",
|
||||
code: data.code,
|
||||
success: data.success
|
||||
}
|
||||
};
|
||||
case "heartbeat":
|
||||
return {
|
||||
type: "heartbeat"
|
||||
};
|
||||
case "unexpected_error":
|
||||
return {
|
||||
type: "unexpected_error",
|
||||
status: {
|
||||
queue,
|
||||
message: data.message,
|
||||
stage: "error",
|
||||
success: false
|
||||
}
|
||||
};
|
||||
case "estimation":
|
||||
return {
|
||||
type: "update",
|
||||
status: {
|
||||
queue,
|
||||
stage: last_status || "pending",
|
||||
code: data.code,
|
||||
size: data.queue_size,
|
||||
position: data.rank,
|
||||
eta: data.rank_eta,
|
||||
success: data.success
|
||||
}
|
||||
};
|
||||
case "progress":
|
||||
return {
|
||||
type: "update",
|
||||
status: {
|
||||
queue,
|
||||
stage: "pending",
|
||||
code: data.code,
|
||||
progress_data: data.progress_data,
|
||||
success: data.success
|
||||
}
|
||||
};
|
||||
case "log":
|
||||
return { type: "log", data: data };
|
||||
case "process_generating":
|
||||
return {
|
||||
type: "generating",
|
||||
status: {
|
||||
queue,
|
||||
message: !data.success ? data.output.error : null,
|
||||
stage: data.success ? "generating" : "error",
|
||||
code: data.code,
|
||||
progress_data: data.progress_data,
|
||||
eta: data.average_duration
|
||||
},
|
||||
data: data.success ? data.output : null
|
||||
};
|
||||
case "process_completed":
|
||||
if ("error" in data.output) {
|
||||
return {
|
||||
type: "update",
|
||||
status: {
|
||||
queue,
|
||||
message: data.output.error as string,
|
||||
stage: "error",
|
||||
code: data.code,
|
||||
success: data.success
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: "complete",
|
||||
status: {
|
||||
queue,
|
||||
message: !data.success ? data.output.error : undefined,
|
||||
stage: data.success ? "complete" : "error",
|
||||
code: data.code,
|
||||
progress_data: data.progress_data
|
||||
},
|
||||
data: data.success ? data.output : null
|
||||
};
|
||||
|
||||
case "process_starts":
|
||||
return {
|
||||
type: "update",
|
||||
status: {
|
||||
queue,
|
||||
stage: "pending",
|
||||
code: data.code,
|
||||
size: data.rank,
|
||||
position: 0,
|
||||
success: data.success,
|
||||
eta: data.eta
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return { type: "none", status: { stage: "error", queue } };
|
||||
}
|
115
client/js/src/helpers/data.ts
Normal file
115
client/js/src/helpers/data.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { NodeBlob } from "../client";
|
||||
import type {
|
||||
ApiData,
|
||||
BlobRef,
|
||||
Config,
|
||||
EndpointInfo,
|
||||
JsApiData,
|
||||
ParamType
|
||||
} from "../types";
|
||||
|
||||
export function update_object(
|
||||
object: { [x: string]: any },
|
||||
newValue: any,
|
||||
stack: (string | number)[]
|
||||
): void {
|
||||
while (stack.length > 1) {
|
||||
const key = stack.shift();
|
||||
if (typeof key === "string" || typeof key === "number") {
|
||||
object = object[key];
|
||||
} else {
|
||||
throw new Error("Invalid key type");
|
||||
}
|
||||
}
|
||||
|
||||
const key = stack.shift();
|
||||
if (typeof key === "string" || typeof key === "number") {
|
||||
object[key] = newValue;
|
||||
} else {
|
||||
throw new Error("Invalid key type");
|
||||
}
|
||||
}
|
||||
|
||||
export async function walk_and_store_blobs(
|
||||
param: ParamType,
|
||||
type: string | undefined = undefined,
|
||||
path: string[] = [],
|
||||
root = false,
|
||||
endpoint_info: EndpointInfo<ApiData | JsApiData> | undefined = undefined
|
||||
): Promise<BlobRef[]> {
|
||||
if (Array.isArray(param)) {
|
||||
let blob_refs: BlobRef[] = [];
|
||||
|
||||
await Promise.all(
|
||||
param.map(async (item) => {
|
||||
let new_path = path.slice();
|
||||
new_path.push(item);
|
||||
|
||||
const array_refs = await walk_and_store_blobs(
|
||||
param[item],
|
||||
root ? endpoint_info?.parameters[item]?.component || undefined : type,
|
||||
new_path,
|
||||
false,
|
||||
endpoint_info
|
||||
);
|
||||
|
||||
blob_refs = blob_refs.concat(array_refs);
|
||||
})
|
||||
);
|
||||
|
||||
return blob_refs;
|
||||
} else if (globalThis.Buffer && param instanceof globalThis.Buffer) {
|
||||
const is_image = type === "Image";
|
||||
return [
|
||||
{
|
||||
path: path,
|
||||
blob: is_image ? false : new NodeBlob([param]),
|
||||
type
|
||||
}
|
||||
];
|
||||
} else if (typeof param === "object") {
|
||||
let blob_refs: BlobRef[] = [];
|
||||
for (let key in param) {
|
||||
if (param.hasOwnProperty(key)) {
|
||||
let new_path = path.slice();
|
||||
new_path.push(key);
|
||||
blob_refs = blob_refs.concat(
|
||||
await walk_and_store_blobs(
|
||||
// @ts-ignore
|
||||
param[key],
|
||||
undefined,
|
||||
new_path,
|
||||
false,
|
||||
endpoint_info
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return blob_refs;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function skip_queue(id: number, config: Config): boolean {
|
||||
return (
|
||||
!(config?.dependencies?.[id]?.queue === null
|
||||
? config.enable_queue
|
||||
: config?.dependencies?.[id]?.queue) || false
|
||||
);
|
||||
}
|
||||
|
||||
// todo: add jsdoc for this function
|
||||
|
||||
export function post_message<Res = any>(
|
||||
message: any,
|
||||
origin: string
|
||||
): Promise<Res> {
|
||||
return new Promise((res, _rej) => {
|
||||
const channel = new MessageChannel();
|
||||
channel.port1.onmessage = (({ data }) => {
|
||||
channel.port1.close();
|
||||
res(data);
|
||||
}) as (ev: MessageEvent<Res>) => void;
|
||||
window.parent.postMessage(message, origin, [channel.port2]);
|
||||
});
|
||||
}
|
134
client/js/src/helpers/init_helpers.ts
Normal file
134
client/js/src/helpers/init_helpers.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import type { Config } from "../types";
|
||||
import { CONFIG_URL } from "../constants";
|
||||
import { Client } from "..";
|
||||
|
||||
/**
|
||||
* This function is used to resolve the URL for making requests when the app has a root path.
|
||||
* The root path could be a path suffix like "/app" which is appended to the end of the base URL. Or
|
||||
* it could be a full URL like "https://abidlabs-test-client-replica--gqf2x.hf.space" which is used when hosting
|
||||
* Gradio apps on Hugging Face Spaces.
|
||||
* @param {string} base_url The base URL at which the Gradio server is hosted
|
||||
* @param {string} root_path The root path, which could be a path suffix (e.g. mounted in FastAPI app) or a full URL (e.g. hosted on Hugging Face Spaces)
|
||||
* @param {boolean} prioritize_base Whether to prioritize the base URL over the root path. This is used when both the base path and root paths are full URLs. For example, for fetching files the root path should be prioritized, but for making requests, the base URL should be prioritized.
|
||||
* @returns {string} the resolved URL
|
||||
*/
|
||||
export function resolve_root(
|
||||
base_url: string,
|
||||
root_path: string,
|
||||
prioritize_base: boolean
|
||||
): string {
|
||||
if (root_path.startsWith("http://") || root_path.startsWith("https://")) {
|
||||
return prioritize_base ? base_url : root_path;
|
||||
}
|
||||
return base_url + root_path;
|
||||
}
|
||||
|
||||
export async function get_jwt(
|
||||
space: string,
|
||||
token: `hf_${string}`
|
||||
): Promise<string | false> {
|
||||
try {
|
||||
const r = await fetch(`https://huggingface.co/api/spaces/${space}/jwt`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
const jwt = (await r.json()).token;
|
||||
|
||||
return jwt || false;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function map_names_to_ids(
|
||||
fns: Config["dependencies"]
|
||||
): Record<string, number> {
|
||||
let apis: Record<string, number> = {};
|
||||
|
||||
fns.forEach(({ api_name }, i: number) => {
|
||||
if (api_name) apis[api_name] = i;
|
||||
});
|
||||
return apis;
|
||||
}
|
||||
|
||||
export async function resolve_config(
|
||||
this: Client,
|
||||
endpoint: string
|
||||
): Promise<Config | undefined> {
|
||||
const headers: Record<string, string> = this.options.hf_token
|
||||
? { Authorization: `Bearer ${this.options.hf_token}` }
|
||||
: {};
|
||||
|
||||
headers["Content-Type"] = "application/json";
|
||||
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
window.gradio_config &&
|
||||
location.origin !== "http://localhost:9876" &&
|
||||
!window.gradio_config.dev_mode
|
||||
) {
|
||||
const path = window.gradio_config.root;
|
||||
const config = window.gradio_config;
|
||||
let config_root = resolve_root(endpoint, config.root, false);
|
||||
config.root = config_root;
|
||||
return { ...config, path };
|
||||
} else if (endpoint) {
|
||||
const response = await this.fetch_implementation(
|
||||
`${endpoint}/${CONFIG_URL}`,
|
||||
{
|
||||
headers
|
||||
}
|
||||
);
|
||||
|
||||
if (response?.status === 200) {
|
||||
let config = await response.json();
|
||||
config.path = config.path ?? "";
|
||||
config.root = endpoint;
|
||||
return config;
|
||||
}
|
||||
throw new Error("Could not get config.");
|
||||
}
|
||||
|
||||
throw new Error("No config or app endpoint found");
|
||||
}
|
||||
|
||||
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:"
|
||||
};
|
||||
}
|
||||
return {
|
||||
ws_protocol: protocol === "https:" ? "wss" : "ws",
|
||||
http_protocol: protocol as "http:" | "https:",
|
||||
host
|
||||
};
|
||||
} else if (endpoint.startsWith("file:")) {
|
||||
// This case is only expected to be used for the Wasm mode (Gradio-lite),
|
||||
// where users can create a local HTML file using it and open the page in a browser directly via the `file:` protocol.
|
||||
return {
|
||||
ws_protocol: "ws",
|
||||
http_protocol: "http:",
|
||||
host: "lite.local" // Special fake hostname only used for this case. This matches the hostname allowed in `is_self_host()` in `js/wasm/network/host.ts`.
|
||||
};
|
||||
}
|
||||
|
||||
// default to secure if no protocol is provided
|
||||
return {
|
||||
ws_protocol: "wss",
|
||||
http_protocol: "https:",
|
||||
host: endpoint
|
||||
};
|
||||
}
|
192
client/js/src/helpers/spaces.ts
Normal file
192
client/js/src/helpers/spaces.ts
Normal file
@ -0,0 +1,192 @@
|
||||
import type { SpaceStatusCallback } from "../types";
|
||||
|
||||
export async function check_space_status(
|
||||
id: string,
|
||||
type: "subdomain" | "space_name",
|
||||
status_callback: SpaceStatusCallback
|
||||
): Promise<void> {
|
||||
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) {
|
||||
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":
|
||||
status_callback({
|
||||
status: "sleeping",
|
||||
load_status: "pending",
|
||||
message: "Space is asleep. Waking it up...",
|
||||
detail: stage
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
check_space_status(id, type, status_callback);
|
||||
}, 1000); // poll for status
|
||||
break;
|
||||
case "PAUSED":
|
||||
status_callback({
|
||||
status: "paused",
|
||||
load_status: "error",
|
||||
message:
|
||||
"This space has been paused by the author. If you would like to try this demo, consider duplicating the space.",
|
||||
detail: stage,
|
||||
discussions_enabled: await discussions_enabled(space_name)
|
||||
});
|
||||
break;
|
||||
case "RUNNING":
|
||||
case "RUNNING_BUILDING":
|
||||
status_callback({
|
||||
status: "running",
|
||||
load_status: "complete",
|
||||
message: "",
|
||||
detail: stage
|
||||
});
|
||||
break;
|
||||
case "BUILDING":
|
||||
status_callback({
|
||||
status: "building",
|
||||
load_status: "pending",
|
||||
message: "Space is building...",
|
||||
detail: stage
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
check_space_status(id, type, status_callback);
|
||||
}, 1000);
|
||||
break;
|
||||
default:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
const RE_DISABLED_DISCUSSION =
|
||||
/^(?=[^]*\b[dD]iscussions{0,1}\b)(?=[^]*\b[dD]isabled\b)[^]*$/;
|
||||
export async function discussions_enabled(space_id: string): Promise<boolean> {
|
||||
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;
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function get_space_hardware(
|
||||
space_id: string,
|
||||
hf_token?: `hf_${string}` | undefined
|
||||
): Promise<(typeof hardware_types)[number]> {
|
||||
const headers: { Authorization?: string } = {};
|
||||
if (hf_token) {
|
||||
headers.Authorization = `Bearer ${hf_token}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://huggingface.co/api/spaces/${space_id}/runtime`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (res.status !== 200)
|
||||
throw new Error("Space hardware could not be obtained.");
|
||||
|
||||
const { hardware } = await res.json();
|
||||
|
||||
return hardware.current;
|
||||
} catch (e: any) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function set_space_timeout(
|
||||
space_id: string,
|
||||
timeout: number,
|
||||
hf_token?: `hf_${string}`
|
||||
): Promise<any> {
|
||||
const headers: { Authorization?: string } = {};
|
||||
if (hf_token) {
|
||||
headers.Authorization = `Bearer ${hf_token}`;
|
||||
}
|
||||
|
||||
const body: {
|
||||
seconds?: number;
|
||||
} = {
|
||||
seconds: timeout
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://huggingface.co/api/spaces/${space_id}/sleeptime`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...headers },
|
||||
body: JSON.stringify(body)
|
||||
}
|
||||
);
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(
|
||||
"Could not set sleep timeout on duplicated Space. Please visit *ADD HF LINK TO SETTINGS* to set a timeout manually to reduce billing charges."
|
||||
);
|
||||
}
|
||||
|
||||
const response = await res.json();
|
||||
return response;
|
||||
} catch (e: any) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export const hardware_types = [
|
||||
"cpu-basic",
|
||||
"cpu-upgrade",
|
||||
"cpu-xl",
|
||||
"t4-small",
|
||||
"t4-medium",
|
||||
"a10g-small",
|
||||
"a10g-large",
|
||||
"a10g-largex2",
|
||||
"a10g-largex4",
|
||||
"a100-large",
|
||||
"zero-a10g",
|
||||
"h100",
|
||||
"h100x8"
|
||||
] as const;
|
@ -1,11 +1,17 @@
|
||||
export {
|
||||
client,
|
||||
post_data,
|
||||
upload_files,
|
||||
duplicate,
|
||||
api_factory
|
||||
} from "./client.js";
|
||||
export type { client_return } from "./client.js";
|
||||
export type { SpaceStatus } from "./types.js";
|
||||
export { Client } from "./client";
|
||||
|
||||
export { FileData, upload, prepare_files } from "./upload.js";
|
||||
export { predict } from "./utils/predict";
|
||||
export { submit } from "./utils/submit";
|
||||
export { upload_files } from "./utils/upload_files";
|
||||
export { FileData, upload, prepare_files } from "./upload";
|
||||
|
||||
export type {
|
||||
SpaceStatus,
|
||||
Status,
|
||||
client_return,
|
||||
UploadResponse
|
||||
} from "./types";
|
||||
|
||||
// todo: remove in @gradio/client v1.0
|
||||
export { client } from "./client";
|
||||
export { duplicate } from "./utils/duplicate";
|
||||
|
@ -1,13 +1,130 @@
|
||||
// API Data Types
|
||||
|
||||
import { hardware_types } from "./helpers/spaces";
|
||||
|
||||
export interface ApiData {
|
||||
label: string;
|
||||
type: {
|
||||
type: any;
|
||||
description: string;
|
||||
};
|
||||
component: string;
|
||||
example_input?: any;
|
||||
python_type: { type: string; description: string };
|
||||
serializer: string;
|
||||
}
|
||||
|
||||
export interface JsApiData {
|
||||
label: string;
|
||||
type: string;
|
||||
description: string;
|
||||
component: string;
|
||||
example_input?: any;
|
||||
serializer: string;
|
||||
python_type: { type: string; description: string };
|
||||
}
|
||||
|
||||
export interface EndpointInfo<T extends ApiData | JsApiData> {
|
||||
parameters: T[];
|
||||
returns: T[];
|
||||
type?: DependencyTypes;
|
||||
}
|
||||
|
||||
export interface ApiInfo<T extends ApiData | JsApiData> {
|
||||
named_endpoints: Record<string, EndpointInfo<T>>;
|
||||
unnamed_endpoints: Record<string, EndpointInfo<T>>;
|
||||
}
|
||||
|
||||
export interface BlobRef {
|
||||
path: string[];
|
||||
type: string | undefined;
|
||||
blob: Blob | false;
|
||||
}
|
||||
|
||||
export type ParamType = string | Buffer | Record<string, any> | any[];
|
||||
|
||||
// Event and Submission Types
|
||||
|
||||
type event = <K extends EventType>(
|
||||
eventType: K,
|
||||
listener: EventListener<K>
|
||||
) => SubmitReturn;
|
||||
|
||||
type predict = (
|
||||
endpoint: string | number,
|
||||
data?: unknown[],
|
||||
event_data?: unknown
|
||||
) => Promise<unknown>;
|
||||
|
||||
export type client_return = {
|
||||
config: Config | undefined;
|
||||
predict: predict;
|
||||
submit: (
|
||||
endpoint: string | number,
|
||||
data: unknown[],
|
||||
event_data?: unknown,
|
||||
trigger_id?: number | null
|
||||
) => SubmitReturn;
|
||||
component_server: (
|
||||
component_id: number,
|
||||
fn_name: string,
|
||||
data: unknown[]
|
||||
) => any;
|
||||
view_api: (fetch_implementation: typeof fetch) => Promise<ApiInfo<JsApiData>>;
|
||||
};
|
||||
|
||||
export type SubmitReturn = {
|
||||
on: event;
|
||||
off: event;
|
||||
cancel: () => Promise<void>;
|
||||
destroy: () => void;
|
||||
};
|
||||
|
||||
// Space Status Types
|
||||
|
||||
export type SpaceStatus = SpaceStatusNormal | SpaceStatusError;
|
||||
|
||||
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" | "paused";
|
||||
detail:
|
||||
| "NO_APP_FILE"
|
||||
| "CONFIG_ERROR"
|
||||
| "BUILD_ERROR"
|
||||
| "RUNTIME_ERROR"
|
||||
| "PAUSED";
|
||||
load_status: "error";
|
||||
message: string;
|
||||
discussions_enabled: boolean;
|
||||
}
|
||||
|
||||
export type SpaceStatusCallback = (a: SpaceStatus) => void;
|
||||
|
||||
// Configuration and Response Types
|
||||
// --------------------------------
|
||||
export interface Config {
|
||||
auth_required: boolean | undefined;
|
||||
auth_required: boolean;
|
||||
analytics_enabled: boolean;
|
||||
auth_message: string;
|
||||
components: any[];
|
||||
css: string | null;
|
||||
js: string | null;
|
||||
head: string | null;
|
||||
dependencies: any[];
|
||||
dependencies: Dependency[];
|
||||
dev_mode: boolean;
|
||||
enable_queue: boolean;
|
||||
show_error: boolean;
|
||||
layout: any;
|
||||
mode: "blocks" | "interface";
|
||||
root: string;
|
||||
@ -16,30 +133,112 @@ export interface Config {
|
||||
title: string;
|
||||
version: string;
|
||||
space_id: string | null;
|
||||
is_space: boolean;
|
||||
is_colab: boolean;
|
||||
show_api: boolean;
|
||||
stylesheets: string[];
|
||||
path: string;
|
||||
protocol?: "sse_v2.1" | "sse_v2" | "sse_v1" | "sse" | "ws";
|
||||
protocol: "sse_v3" | "sse_v2.1" | "sse_v2" | "sse_v1" | "sse" | "ws";
|
||||
max_file_size?: number;
|
||||
}
|
||||
|
||||
export interface Dependency {
|
||||
targets: [number, string][];
|
||||
inputs: number[];
|
||||
outputs: number[];
|
||||
backend_fn: boolean;
|
||||
js: string | null;
|
||||
scroll_to_output: boolean;
|
||||
trigger: "click" | "load" | string;
|
||||
max_batch_size: number;
|
||||
show_progress: "full" | "minimal" | "hidden";
|
||||
frontend_fn: ((...args: unknown[]) => Promise<unknown[]>) | null;
|
||||
status?: string;
|
||||
queue: boolean | null;
|
||||
every: number | null;
|
||||
batch: boolean;
|
||||
api_name: string | null;
|
||||
cancels: number[];
|
||||
types: DependencyTypes;
|
||||
collects_event_data: boolean;
|
||||
pending_request?: boolean;
|
||||
trigger_after?: number;
|
||||
trigger_only_on_success?: boolean;
|
||||
trigger_mode: "once" | "multiple" | "always_last";
|
||||
final_event: Payload | null;
|
||||
show_api: boolean;
|
||||
zerogpu?: boolean;
|
||||
}
|
||||
|
||||
export interface DependencyTypes {
|
||||
continuous: boolean;
|
||||
generator: boolean;
|
||||
}
|
||||
|
||||
export interface Payload {
|
||||
fn_index: number;
|
||||
data: unknown[];
|
||||
fn_index?: number;
|
||||
event_data?: unknown;
|
||||
time?: Date;
|
||||
event_data?: unknown;
|
||||
trigger_id?: number | null;
|
||||
}
|
||||
|
||||
export interface PostResponse {
|
||||
error?: string;
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
export interface UploadResponse {
|
||||
error?: string;
|
||||
files?: string[];
|
||||
}
|
||||
|
||||
// Client and File Handling Types
|
||||
|
||||
export interface DuplicateOptions extends ClientOptions {
|
||||
private?: boolean;
|
||||
hardware?: (typeof hardware_types)[number];
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface ClientOptions {
|
||||
hf_token?: `hf_${string}`;
|
||||
status_callback?: SpaceStatusCallback | null;
|
||||
}
|
||||
|
||||
export interface FileData {
|
||||
name: string;
|
||||
orig_name?: string;
|
||||
size?: number;
|
||||
data: string;
|
||||
blob?: File;
|
||||
is_file?: boolean;
|
||||
mime_type?: string;
|
||||
alt_text?: string;
|
||||
}
|
||||
|
||||
// Event and Listener Types
|
||||
|
||||
export type EventType = "data" | "status" | "log";
|
||||
|
||||
export interface EventMap {
|
||||
data: Payload;
|
||||
status: Status;
|
||||
log: LogMessage;
|
||||
}
|
||||
|
||||
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>[];
|
||||
};
|
||||
export interface LogMessage {
|
||||
log: string;
|
||||
level: "warning" | "info";
|
||||
}
|
||||
|
||||
export interface Status {
|
||||
queue: boolean;
|
||||
code?: string;
|
||||
@ -59,62 +258,3 @@ export interface Status {
|
||||
}[];
|
||||
time?: Date;
|
||||
}
|
||||
|
||||
export interface LogMessage {
|
||||
log: string;
|
||||
level: "warning" | "info";
|
||||
}
|
||||
|
||||
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" | "paused";
|
||||
detail:
|
||||
| "NO_APP_FILE"
|
||||
| "CONFIG_ERROR"
|
||||
| "BUILD_ERROR"
|
||||
| "RUNTIME_ERROR"
|
||||
| "PAUSED";
|
||||
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" | "log";
|
||||
|
||||
export interface EventMap {
|
||||
data: Payload;
|
||||
status: Status;
|
||||
log: LogMessage;
|
||||
}
|
||||
|
||||
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>[];
|
||||
};
|
||||
export interface FileData {
|
||||
name: string;
|
||||
orig_name?: string;
|
||||
size?: number;
|
||||
data: string;
|
||||
blob?: File;
|
||||
is_file?: boolean;
|
||||
mime_type?: string;
|
||||
alt_text?: string;
|
||||
}
|
||||
|
@ -1,20 +1,16 @@
|
||||
import { upload_files } from "./client";
|
||||
|
||||
function is_url(str: string): boolean {
|
||||
try {
|
||||
const url = new URL(str);
|
||||
return url.protocol === "http:" || url.protocol === "https:";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
import type { UploadResponse } from "./types";
|
||||
import { upload_files } from ".";
|
||||
|
||||
export async function upload(
|
||||
file_data: FileData[],
|
||||
root: string,
|
||||
root_url: string,
|
||||
upload_id?: string,
|
||||
max_file_size?: number,
|
||||
upload_fn: typeof upload_files = upload_files
|
||||
upload_fn: (
|
||||
root_url: string,
|
||||
files: (Blob | File)[],
|
||||
upload_id?: string
|
||||
) => Promise<UploadResponse> = upload_files
|
||||
): Promise<(FileData | null)[] | null> {
|
||||
let files = (Array.isArray(file_data) ? file_data : [file_data]).map(
|
||||
(file_data) => file_data.blob!
|
||||
@ -32,7 +28,7 @@ export async function upload(
|
||||
}
|
||||
|
||||
return await Promise.all(
|
||||
await upload_fn(root, files, undefined, upload_id).then(
|
||||
await upload_fn(root_url, files, upload_id).then(
|
||||
async (response: { files?: string[]; error?: string }) => {
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
@ -42,7 +38,7 @@ export async function upload(
|
||||
const file = new FileData({
|
||||
...file_data[i],
|
||||
path: f,
|
||||
url: root + "/file=" + f
|
||||
url: root_url + "/file=" + f
|
||||
});
|
||||
return file;
|
||||
});
|
||||
@ -60,7 +56,7 @@ export async function prepare_files(
|
||||
is_stream?: boolean
|
||||
): Promise<FileData[]> {
|
||||
return files.map(
|
||||
(f, i) =>
|
||||
(f) =>
|
||||
new FileData({
|
||||
path: f.name,
|
||||
orig_name: f.name,
|
||||
|
@ -1,314 +0,0 @@
|
||||
import type { Config } from "./types.js";
|
||||
|
||||
/**
|
||||
* This function is used to resolve the URL for making requests when the app has a root path.
|
||||
* The root path could be a path suffix like "/app" which is appended to the end of the base URL. Or
|
||||
* it could be a full URL like "https://abidlabs-test-client-replica--gqf2x.hf.space" which is used when hosting
|
||||
* Gradio apps on Hugging Face Spaces.
|
||||
* @param {string} base_url The base URL at which the Gradio server is hosted
|
||||
* @param {string} root_path The root path, which could be a path suffix (e.g. mounted in FastAPI app) or a full URL (e.g. hosted on Hugging Face Spaces)
|
||||
* @param {boolean} prioritize_base Whether to prioritize the base URL over the root path. This is used when both the base path and root paths are full URLs. For example, for fetching files the root path should be prioritized, but for making requests, the base URL should be prioritized.
|
||||
* @returns {string} the resolved URL
|
||||
*/
|
||||
export function resolve_root(
|
||||
base_url: string,
|
||||
root_path: string,
|
||||
prioritize_base: boolean
|
||||
): string {
|
||||
if (root_path.startsWith("http://") || root_path.startsWith("https://")) {
|
||||
return prioritize_base ? base_url : root_path;
|
||||
}
|
||||
return base_url + root_path;
|
||||
}
|
||||
|
||||
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:"
|
||||
};
|
||||
}
|
||||
return {
|
||||
ws_protocol: protocol === "https:" ? "wss" : "ws",
|
||||
http_protocol: protocol as "http:" | "https:",
|
||||
host
|
||||
};
|
||||
} else if (endpoint.startsWith("file:")) {
|
||||
// This case is only expected to be used for the Wasm mode (Gradio-lite),
|
||||
// where users can create a local HTML file using it and open the page in a browser directly via the `file:` protocol.
|
||||
return {
|
||||
ws_protocol: "ws",
|
||||
http_protocol: "http:",
|
||||
host: "lite.local" // Special fake hostname only used for this case. This matches the hostname allowed in `is_self_host()` in `js/wasm/network/host.ts`.
|
||||
};
|
||||
}
|
||||
|
||||
// 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,
|
||||
token?: `hf_${string}`
|
||||
): Promise<{
|
||||
space_id: string | false;
|
||||
host: string;
|
||||
ws_protocol: "ws" | "wss";
|
||||
http_protocol: "http:" | "https:";
|
||||
}> {
|
||||
const headers: { Authorization?: string } = {};
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const _app_reference = app_reference.trim();
|
||||
|
||||
if (RE_SPACE_NAME.test(_app_reference)) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://huggingface.co/api/spaces/${_app_reference}/host`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (res.status !== 200)
|
||||
throw new Error("Space metadata could not be loaded.");
|
||||
const _host = (await res.json()).host;
|
||||
|
||||
return {
|
||||
space_id: app_reference,
|
||||
...determine_protocol(_host)
|
||||
};
|
||||
} catch (e: any) {
|
||||
throw new Error("Space metadata could not be loaded." + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
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"]
|
||||
): Record<string, number> {
|
||||
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): Promise<boolean> {
|
||||
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;
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function get_space_hardware(
|
||||
space_id: string,
|
||||
token: `hf_${string}`
|
||||
): Promise<(typeof hardware_types)[number]> {
|
||||
const headers: { Authorization?: string } = {};
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://huggingface.co/api/spaces/${space_id}/runtime`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (res.status !== 200)
|
||||
throw new Error("Space hardware could not be obtained.");
|
||||
|
||||
const { hardware } = await res.json();
|
||||
|
||||
return hardware;
|
||||
} catch (e: any) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function set_space_hardware(
|
||||
space_id: string,
|
||||
new_hardware: (typeof hardware_types)[number],
|
||||
token: `hf_${string}`
|
||||
): Promise<(typeof hardware_types)[number]> {
|
||||
const headers: { Authorization?: string } = {};
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://huggingface.co/api/spaces/${space_id}/hardware`,
|
||||
{ headers, body: JSON.stringify(new_hardware) }
|
||||
);
|
||||
|
||||
if (res.status !== 200)
|
||||
throw new Error(
|
||||
"Space hardware could not be set. Please ensure the space hardware provided is valid and that a Hugging Face token is passed in."
|
||||
);
|
||||
|
||||
const { hardware } = await res.json();
|
||||
|
||||
return hardware;
|
||||
} catch (e: any) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function set_space_timeout(
|
||||
space_id: string,
|
||||
timeout: number,
|
||||
token: `hf_${string}`
|
||||
): Promise<number> {
|
||||
const headers: { Authorization?: string } = {};
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://huggingface.co/api/spaces/${space_id}/hardware`,
|
||||
{ headers, body: JSON.stringify({ seconds: timeout }) }
|
||||
);
|
||||
|
||||
if (res.status !== 200)
|
||||
throw new Error(
|
||||
"Space hardware could not be set. Please ensure the space hardware provided is valid and that a Hugging Face token is passed in."
|
||||
);
|
||||
|
||||
const { hardware } = await res.json();
|
||||
|
||||
return hardware;
|
||||
} catch (e: any) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export const hardware_types = [
|
||||
"cpu-basic",
|
||||
"cpu-upgrade",
|
||||
"t4-small",
|
||||
"t4-medium",
|
||||
"a10g-small",
|
||||
"a10g-large",
|
||||
"a100-large"
|
||||
] as const;
|
||||
|
||||
function apply_edit(
|
||||
target: any,
|
||||
path: (number | string)[],
|
||||
action: string,
|
||||
value: any
|
||||
): any {
|
||||
if (path.length === 0) {
|
||||
if (action === "replace") {
|
||||
return value;
|
||||
} else if (action === "append") {
|
||||
return target + value;
|
||||
}
|
||||
throw new Error(`Unsupported action: ${action}`);
|
||||
}
|
||||
|
||||
let current = target;
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
current = current[path[i]];
|
||||
}
|
||||
|
||||
const last_path = path[path.length - 1];
|
||||
switch (action) {
|
||||
case "replace":
|
||||
current[last_path] = value;
|
||||
break;
|
||||
case "append":
|
||||
current[last_path] += value;
|
||||
break;
|
||||
case "add":
|
||||
if (Array.isArray(current)) {
|
||||
current.splice(Number(last_path), 0, value);
|
||||
} else {
|
||||
current[last_path] = value;
|
||||
}
|
||||
break;
|
||||
case "delete":
|
||||
if (Array.isArray(current)) {
|
||||
current.splice(Number(last_path), 1);
|
||||
} else {
|
||||
delete current[last_path];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown action: ${action}`);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
export function apply_diff(
|
||||
obj: any,
|
||||
diff: [string, (number | string)[], any][]
|
||||
): any {
|
||||
diff.forEach(([action, path, value]) => {
|
||||
obj = apply_edit(obj, path, action, value);
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function post_message<Res = any>(
|
||||
message: any,
|
||||
origin: string
|
||||
): Promise<Res> {
|
||||
return new Promise((res, _rej) => {
|
||||
const channel = new MessageChannel();
|
||||
channel.port1.onmessage = (({ data }) => {
|
||||
channel.port1.close();
|
||||
res(data);
|
||||
}) as (ev: MessageEvent<Res>) => void;
|
||||
window.parent.postMessage(message, origin, [channel.port2]);
|
||||
});
|
||||
}
|
@ -16,12 +16,13 @@ const image_path = join(
|
||||
"lion.jpg"
|
||||
);
|
||||
|
||||
import { walk_and_store_blobs, client, handle_blob } from "./client";
|
||||
import { walk_and_store_blobs } from "../helpers/data";
|
||||
import { Client } from "..";
|
||||
|
||||
describe.skip("extract blob parts", () => {
|
||||
test("convert Buffer to Blob", async () => {
|
||||
const image = readFileSync(image_path);
|
||||
await client("gradio/hello_world_main");
|
||||
await Client.connect("gradio/hello_world_main");
|
||||
const parts = walk_and_store_blobs({
|
||||
data: {
|
||||
image
|
||||
@ -34,7 +35,7 @@ describe.skip("extract blob parts", () => {
|
||||
test("leave node Blob as Blob", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
await client("gradio/hello_world_main");
|
||||
await Client.connect("gradio/hello_world_main");
|
||||
const parts = walk_and_store_blobs({
|
||||
data: {
|
||||
image
|
||||
@ -47,7 +48,7 @@ describe.skip("extract blob parts", () => {
|
||||
test("handle deep structures", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
await client("gradio/hello_world_main");
|
||||
await Client.connect("gradio/hello_world_main");
|
||||
const parts = walk_and_store_blobs({
|
||||
a: {
|
||||
b: {
|
||||
@ -64,7 +65,7 @@ describe.skip("extract blob parts", () => {
|
||||
test("handle deep structures with arrays", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
await client("gradio/hello_world_main");
|
||||
await Client.connect("gradio/hello_world_main");
|
||||
const parts = walk_and_store_blobs({
|
||||
a: [
|
||||
{
|
||||
@ -87,7 +88,7 @@ describe.skip("extract blob parts", () => {
|
||||
test("handle deep structures with arrays 2", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
await client("gradio/hello_world_main");
|
||||
await Client.connect("gradio/hello_world_main");
|
||||
const obj = {
|
||||
a: [
|
||||
{
|
||||
@ -121,7 +122,7 @@ describe("handle_blob", () => {
|
||||
test("handle blobs", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
const app = await client("gradio/hello_world_main");
|
||||
const app = await Client.connect("gradio/hello_world_main");
|
||||
const obj = [
|
||||
{
|
||||
a: [
|
||||
@ -136,7 +137,7 @@ describe("handle_blob", () => {
|
||||
}
|
||||
];
|
||||
|
||||
const parts = await handle_blob(app.config.root, obj, undefined);
|
||||
const parts = await app.handle_blob(app.config.root, obj, undefined);
|
||||
//@ts-ignore
|
||||
// assert.isString(parts.data[0].a[0].b[0].data[0][0]);
|
||||
});
|
||||
@ -146,7 +147,7 @@ describe.skip("private space", () => {
|
||||
test("can access a private space", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
const app = await client("pngwn/hello_world", {
|
||||
const app = await Client.connect("pngwn/hello_world", {
|
||||
hf_token: "hf_"
|
||||
});
|
||||
|
||||
@ -165,7 +166,7 @@ describe.skip("private space", () => {
|
||||
}
|
||||
];
|
||||
|
||||
const parts = await handle_blob(app.config.root, obj, "hf_");
|
||||
const parts = await app.handle_blob(app.config.root, obj, undefined);
|
||||
//@ts-ignore
|
||||
assert.isString(parts.data[0].a[0].b[0].data[0][0]);
|
||||
});
|
87
client/js/src/utils/duplicate.ts
Normal file
87
client/js/src/utils/duplicate.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
get_space_hardware,
|
||||
hardware_types,
|
||||
set_space_timeout
|
||||
} from "../helpers/spaces";
|
||||
import type { DuplicateOptions } from "../types";
|
||||
import { Client } from "../client";
|
||||
|
||||
export async function duplicate(
|
||||
app_reference: string,
|
||||
options: DuplicateOptions
|
||||
): Promise<Client> {
|
||||
const { hf_token, private: _private, hardware, timeout } = options;
|
||||
|
||||
if (hardware && !hardware_types.includes(hardware)) {
|
||||
throw new Error(
|
||||
`Invalid hardware type provided. Valid types are: ${hardware_types
|
||||
.map((v) => `"${v}"`)
|
||||
.join(",")}.`
|
||||
);
|
||||
}
|
||||
const headers = {
|
||||
Authorization: `Bearer ${hf_token}`,
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
|
||||
const user = (
|
||||
await (
|
||||
await fetch(`https://huggingface.co/api/whoami-v2`, {
|
||||
headers
|
||||
})
|
||||
).json()
|
||||
).name;
|
||||
|
||||
const space_name = app_reference.split("/")[1];
|
||||
const body: {
|
||||
repository: string;
|
||||
private?: boolean;
|
||||
hardware?: string;
|
||||
} = {
|
||||
repository: `${user}/${space_name}`
|
||||
};
|
||||
|
||||
if (_private) {
|
||||
body.private = true;
|
||||
}
|
||||
|
||||
let original_hardware;
|
||||
|
||||
if (!hardware) {
|
||||
original_hardware = await get_space_hardware(app_reference, hf_token);
|
||||
}
|
||||
|
||||
const requested_hardware = hardware || original_hardware || "cpu-basic";
|
||||
|
||||
body.hardware = requested_hardware;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://huggingface.co/api/spaces/${app_reference}/duplicate`,
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(body)
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status === 409) {
|
||||
try {
|
||||
const client = await Client.connect(`${user}/${space_name}`, options);
|
||||
return client;
|
||||
} catch (error) {
|
||||
console.error("Failed to connect Client instance:", error);
|
||||
throw error;
|
||||
}
|
||||
} else if (response.status !== 200) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
const duplicated_space = await response.json();
|
||||
|
||||
await set_space_timeout(`${user}/${space_name}`, timeout || 300, hf_token);
|
||||
return await Client.connect(duplicated_space.url, options);
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
47
client/js/src/utils/handle_blob.ts
Normal file
47
client/js/src/utils/handle_blob.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { update_object, walk_and_store_blobs } from "../helpers/data";
|
||||
import type { ApiData, EndpointInfo, JsApiData } from "../types";
|
||||
import { FileData } from "../upload";
|
||||
import type { Client } from "..";
|
||||
|
||||
export async function handle_blob(
|
||||
this: Client,
|
||||
endpoint: string,
|
||||
data: unknown[],
|
||||
api_info: EndpointInfo<JsApiData | ApiData>
|
||||
): Promise<unknown[]> {
|
||||
const self = this;
|
||||
|
||||
const blobRefs = await walk_and_store_blobs(
|
||||
data,
|
||||
undefined,
|
||||
[],
|
||||
true,
|
||||
api_info
|
||||
);
|
||||
|
||||
const results = await Promise.all(
|
||||
blobRefs.map(async ({ path, blob, type }) => {
|
||||
if (!blob) return { path, type };
|
||||
|
||||
const response = await self.upload_files(endpoint, [blob]);
|
||||
const file_url = response.files && response.files[0];
|
||||
return {
|
||||
path,
|
||||
file_url,
|
||||
type,
|
||||
name: blob?.name
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
results.forEach(({ path, file_url, type, name }) => {
|
||||
if (type === "Gallery") {
|
||||
update_object(data, file_url, path);
|
||||
} else if (file_url) {
|
||||
const file = new FileData({ path: file_url, orig_name: name });
|
||||
update_object(data, file, path);
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
37
client/js/src/utils/post_data.ts
Normal file
37
client/js/src/utils/post_data.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { BROKEN_CONNECTION_MSG } from "../constants";
|
||||
import type { PostResponse } from "../types";
|
||||
import { Client } from "..";
|
||||
|
||||
export async function post_data(
|
||||
this: Client,
|
||||
url: string,
|
||||
body: unknown,
|
||||
additional_headers?: any
|
||||
): Promise<[PostResponse, number]> {
|
||||
const headers: {
|
||||
Authorization?: string;
|
||||
"Content-Type": "application/json";
|
||||
} = { "Content-Type": "application/json" };
|
||||
if (this.options.hf_token) {
|
||||
headers.Authorization = `Bearer ${this.options.hf_token}`;
|
||||
}
|
||||
try {
|
||||
var response = await this.fetch_implementation(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
headers: { ...headers, ...additional_headers }
|
||||
});
|
||||
} catch (e) {
|
||||
return [{ error: BROKEN_CONNECTION_MSG }, 500];
|
||||
}
|
||||
let output: PostResponse;
|
||||
let status: number;
|
||||
try {
|
||||
output = await response.json();
|
||||
status = response.status;
|
||||
} catch (e) {
|
||||
output = { error: `Could not parse server response: ${e}` };
|
||||
status = 500;
|
||||
}
|
||||
return [output, status];
|
||||
}
|
56
client/js/src/utils/predict.ts
Normal file
56
client/js/src/utils/predict.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { Client } from "../client";
|
||||
import type { Dependency, SubmitReturn } from "../types";
|
||||
|
||||
export async function predict(
|
||||
this: Client,
|
||||
endpoint: string | number,
|
||||
data?: unknown[]
|
||||
): Promise<SubmitReturn> {
|
||||
let data_returned = false;
|
||||
let status_complete = false;
|
||||
let dependency: Dependency;
|
||||
|
||||
if (!this.config) {
|
||||
throw new Error("Could not resolve app config");
|
||||
}
|
||||
|
||||
if (typeof endpoint === "number") {
|
||||
dependency = this.config.dependencies[endpoint];
|
||||
} else {
|
||||
const trimmed_endpoint = endpoint.replace(/^\//, "");
|
||||
dependency = this.config.dependencies[this.api_map[trimmed_endpoint]];
|
||||
}
|
||||
|
||||
if (dependency?.types.continuous) {
|
||||
throw new Error(
|
||||
"Cannot call predict on this function as it may run forever. Use submit instead"
|
||||
);
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const app = this.submit(endpoint, data || []);
|
||||
let result: unknown;
|
||||
|
||||
app
|
||||
.on("data", (d: unknown) => {
|
||||
// if complete message comes before data, resolve here
|
||||
if (status_complete) {
|
||||
app.destroy();
|
||||
resolve(d as SubmitReturn);
|
||||
}
|
||||
data_returned = true;
|
||||
result = d;
|
||||
})
|
||||
.on("status", (status) => {
|
||||
if (status.stage === "error") reject(status);
|
||||
if (status.stage === "complete") {
|
||||
status_complete = true;
|
||||
// if complete message comes after data, resolve here
|
||||
if (data_returned) {
|
||||
app.destroy();
|
||||
resolve(result as SubmitReturn);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
166
client/js/src/utils/stream.ts
Normal file
166
client/js/src/utils/stream.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import { BROKEN_CONNECTION_MSG } from "../constants";
|
||||
import type { Client } from "../client";
|
||||
|
||||
export function open_stream(this: Client): void {
|
||||
let {
|
||||
event_callbacks,
|
||||
unclosed_events,
|
||||
pending_stream_messages,
|
||||
stream_status,
|
||||
config
|
||||
} = this;
|
||||
|
||||
if (!config) {
|
||||
throw new Error("Could not resolve app config");
|
||||
}
|
||||
|
||||
stream_status.open = true;
|
||||
|
||||
let event_source: EventSource | null = null;
|
||||
let params = new URLSearchParams({
|
||||
session_hash: this.session_hash
|
||||
}).toString();
|
||||
|
||||
let url = new URL(`${config.root}/queue/data?${params}`);
|
||||
event_source = this.eventSource_factory(url);
|
||||
|
||||
if (!event_source) {
|
||||
throw new Error("Cannot connect to sse endpoint: " + url.toString());
|
||||
}
|
||||
|
||||
event_source.onmessage = async function (event) {
|
||||
let _data = JSON.parse(event.data);
|
||||
if (_data.msg === "close_stream") {
|
||||
close_stream(stream_status, event_source);
|
||||
return;
|
||||
}
|
||||
const event_id = _data.event_id;
|
||||
if (!event_id) {
|
||||
await Promise.all(
|
||||
Object.keys(event_callbacks).map(
|
||||
(event_id) =>
|
||||
// @ts-ignore
|
||||
event_callbacks[event_id](_data) // todo: check event_callbacks
|
||||
)
|
||||
);
|
||||
} else if (event_callbacks[event_id] && config) {
|
||||
if (
|
||||
_data.msg === "process_completed" &&
|
||||
["sse", "sse_v1", "sse_v2", "sse_v2.1"].includes(config.protocol)
|
||||
) {
|
||||
unclosed_events.delete(event_id);
|
||||
if (unclosed_events.size === 0) {
|
||||
close_stream(stream_status, event_source);
|
||||
}
|
||||
}
|
||||
let fn = event_callbacks[event_id];
|
||||
window.setTimeout(fn, 0, _data); // need to do this to put the event on the end of the event loop, so the browser can refresh between callbacks and not freeze in case of quick generations. See https://github.com/gradio-app/gradio/pull/7055
|
||||
} else {
|
||||
if (!pending_stream_messages[event_id]) {
|
||||
pending_stream_messages[event_id] = [];
|
||||
}
|
||||
pending_stream_messages[event_id].push(_data);
|
||||
}
|
||||
};
|
||||
event_source.onerror = async function () {
|
||||
await Promise.all(
|
||||
Object.keys(event_callbacks).map((event_id) =>
|
||||
// @ts-ignore
|
||||
event_callbacks[event_id]({
|
||||
msg: "unexpected_error",
|
||||
message: BROKEN_CONNECTION_MSG
|
||||
})
|
||||
)
|
||||
);
|
||||
close_stream(stream_status, event_source);
|
||||
};
|
||||
}
|
||||
|
||||
export function close_stream(
|
||||
stream_status: { open: boolean },
|
||||
event_source: EventSource | null
|
||||
): void {
|
||||
if (stream_status && event_source) {
|
||||
stream_status.open = false;
|
||||
event_source?.close();
|
||||
}
|
||||
}
|
||||
|
||||
export function apply_diff_stream(
|
||||
pending_diff_streams: Record<string, any[][]>,
|
||||
event_id: string,
|
||||
data: any
|
||||
): void {
|
||||
let is_first_generation = !pending_diff_streams[event_id];
|
||||
if (is_first_generation) {
|
||||
pending_diff_streams[event_id] = [];
|
||||
data.data.forEach((value: any, i: number) => {
|
||||
pending_diff_streams[event_id][i] = value;
|
||||
});
|
||||
} else {
|
||||
data.data.forEach((value: any, i: number) => {
|
||||
let new_data = apply_diff(pending_diff_streams[event_id][i], value);
|
||||
pending_diff_streams[event_id][i] = new_data;
|
||||
data.data[i] = new_data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function apply_diff(
|
||||
obj: any,
|
||||
diff: [string, (number | string)[], any][]
|
||||
): any {
|
||||
diff.forEach(([action, path, value]) => {
|
||||
obj = apply_edit(obj, path, action, value);
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function apply_edit(
|
||||
target: any,
|
||||
path: (number | string)[],
|
||||
action: string,
|
||||
value: any
|
||||
): any {
|
||||
if (path.length === 0) {
|
||||
if (action === "replace") {
|
||||
return value;
|
||||
} else if (action === "append") {
|
||||
return target + value;
|
||||
}
|
||||
throw new Error(`Unsupported action: ${action}`);
|
||||
}
|
||||
|
||||
let current = target;
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
current = current[path[i]];
|
||||
}
|
||||
|
||||
const last_path = path[path.length - 1];
|
||||
switch (action) {
|
||||
case "replace":
|
||||
current[last_path] = value;
|
||||
break;
|
||||
case "append":
|
||||
current[last_path] += value;
|
||||
break;
|
||||
case "add":
|
||||
if (Array.isArray(current)) {
|
||||
current.splice(Number(last_path), 0, value);
|
||||
} else {
|
||||
current[last_path] = value;
|
||||
}
|
||||
break;
|
||||
case "delete":
|
||||
if (Array.isArray(current)) {
|
||||
current.splice(Number(last_path), 1);
|
||||
} else {
|
||||
delete current[last_path];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown action: ${action}`);
|
||||
}
|
||||
return target;
|
||||
}
|
689
client/js/src/utils/submit.ts
Normal file
689
client/js/src/utils/submit.ts
Normal file
@ -0,0 +1,689 @@
|
||||
/* eslint-disable complexity */
|
||||
import type {
|
||||
Status,
|
||||
Payload,
|
||||
EventType,
|
||||
ListenerMap,
|
||||
SubmitReturn,
|
||||
EventListener,
|
||||
Event,
|
||||
JsApiData,
|
||||
EndpointInfo,
|
||||
ApiInfo,
|
||||
Config,
|
||||
Dependency
|
||||
} from "../types";
|
||||
|
||||
import { skip_queue, post_message } from "../helpers/data";
|
||||
import { resolve_root } from "../helpers/init_helpers";
|
||||
import { handle_message, process_endpoint } from "../helpers/api_info";
|
||||
import { BROKEN_CONNECTION_MSG, QUEUE_FULL_MSG } from "../constants";
|
||||
import { apply_diff_stream, close_stream } from "./stream";
|
||||
import { Client } from "../client";
|
||||
|
||||
export function submit(
|
||||
this: Client,
|
||||
endpoint: string | number,
|
||||
data: unknown[],
|
||||
event_data?: unknown,
|
||||
trigger_id?: number | null
|
||||
): SubmitReturn {
|
||||
try {
|
||||
const { hf_token } = this.options;
|
||||
const {
|
||||
fetch_implementation,
|
||||
app_reference,
|
||||
config,
|
||||
session_hash,
|
||||
api_info,
|
||||
api_map,
|
||||
stream_status,
|
||||
pending_stream_messages,
|
||||
pending_diff_streams,
|
||||
event_callbacks,
|
||||
unclosed_events,
|
||||
post_data
|
||||
} = this;
|
||||
|
||||
if (!api_info) throw new Error("No API found");
|
||||
if (!config) throw new Error("Could not resolve app config");
|
||||
|
||||
let { fn_index, endpoint_info, dependency } = get_endpoint_info(
|
||||
api_info,
|
||||
endpoint,
|
||||
api_map,
|
||||
config
|
||||
);
|
||||
|
||||
let websocket: WebSocket;
|
||||
let event_source: EventSource | null;
|
||||
let protocol = config.protocol ?? "ws";
|
||||
|
||||
const _endpoint = typeof endpoint === "number" ? "/predict" : endpoint;
|
||||
let payload: Payload;
|
||||
let event_id: string | null = null;
|
||||
let complete: Status | undefined | false = false;
|
||||
const listener_map: ListenerMap<EventType> = {};
|
||||
let last_status: Record<string, Status["stage"]> = {};
|
||||
let url_params =
|
||||
typeof window !== "undefined"
|
||||
? new URLSearchParams(window.location.search).toString()
|
||||
: "";
|
||||
|
||||
// event subscription methods
|
||||
function fire_event<K extends EventType>(event: Event<K>): void {
|
||||
const narrowed_listener_map: ListenerMap<K> = listener_map;
|
||||
const listeners = narrowed_listener_map[event.type] || [];
|
||||
listeners?.forEach((l) => l(event));
|
||||
}
|
||||
|
||||
function on<K extends EventType>(
|
||||
eventType: K,
|
||||
listener: EventListener<K>
|
||||
): SubmitReturn {
|
||||
const narrowed_listener_map: ListenerMap<K> = listener_map;
|
||||
const listeners = narrowed_listener_map[eventType] || [];
|
||||
narrowed_listener_map[eventType] = listeners;
|
||||
listeners?.push(listener);
|
||||
|
||||
return { on, off, cancel, destroy };
|
||||
}
|
||||
|
||||
function off<K extends EventType>(
|
||||
eventType: K,
|
||||
listener: EventListener<K>
|
||||
): SubmitReturn {
|
||||
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 { on, off, cancel, destroy };
|
||||
}
|
||||
|
||||
async function cancel(): Promise<void> {
|
||||
const _status: Status = {
|
||||
stage: "complete",
|
||||
queue: false,
|
||||
time: new Date()
|
||||
};
|
||||
complete = _status;
|
||||
fire_event({
|
||||
..._status,
|
||||
type: "status",
|
||||
endpoint: _endpoint,
|
||||
fn_index: fn_index
|
||||
});
|
||||
|
||||
let cancel_request = {};
|
||||
if (protocol === "ws") {
|
||||
if (websocket && websocket.readyState === 0) {
|
||||
websocket.addEventListener("open", () => {
|
||||
websocket.close();
|
||||
});
|
||||
} else {
|
||||
websocket.close();
|
||||
}
|
||||
cancel_request = { fn_index, session_hash };
|
||||
} else {
|
||||
event_source?.close();
|
||||
cancel_request = { event_id };
|
||||
}
|
||||
|
||||
try {
|
||||
if (!config) {
|
||||
throw new Error("Could not resolve app config");
|
||||
}
|
||||
|
||||
await fetch_implementation(`${config.root}/reset`, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
body: JSON.stringify(cancel_request)
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
"The `/reset` endpoint could not be called. Subsequent endpoint results may be unreliable."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function destroy(): void {
|
||||
for (const event_type in listener_map) {
|
||||
listener_map &&
|
||||
listener_map[event_type as "data" | "status"]?.forEach((fn) => {
|
||||
off(event_type as "data" | "status", fn);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.handle_blob(`${config.root}`, data, endpoint_info).then(
|
||||
async (_payload) => {
|
||||
payload = {
|
||||
data: _payload || [],
|
||||
event_data,
|
||||
fn_index,
|
||||
trigger_id
|
||||
};
|
||||
if (skip_queue(fn_index, config)) {
|
||||
fire_event({
|
||||
type: "status",
|
||||
endpoint: _endpoint,
|
||||
stage: "pending",
|
||||
queue: false,
|
||||
fn_index,
|
||||
time: new Date()
|
||||
});
|
||||
|
||||
post_data(
|
||||
`${config.root}/run${
|
||||
_endpoint.startsWith("/") ? _endpoint : `/${_endpoint}`
|
||||
}${url_params ? "?" + url_params : ""}`,
|
||||
{
|
||||
...payload,
|
||||
session_hash
|
||||
}
|
||||
)
|
||||
.then(([output, status_code]: any) => {
|
||||
const data = output.data;
|
||||
if (status_code == 200) {
|
||||
fire_event({
|
||||
type: "data",
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
data: data,
|
||||
time: new Date(),
|
||||
event_data,
|
||||
trigger_id
|
||||
});
|
||||
|
||||
fire_event({
|
||||
type: "status",
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
stage: "complete",
|
||||
eta: output.average_duration,
|
||||
queue: false,
|
||||
time: new Date()
|
||||
});
|
||||
} else {
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "error",
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
message: output.error,
|
||||
queue: false,
|
||||
time: new Date()
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "error",
|
||||
message: e.message,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
queue: false,
|
||||
time: new Date()
|
||||
});
|
||||
});
|
||||
} else if (protocol == "ws") {
|
||||
const { ws_protocol, host } = await process_endpoint(
|
||||
app_reference,
|
||||
hf_token
|
||||
);
|
||||
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "pending",
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date()
|
||||
});
|
||||
|
||||
let url = new URL(
|
||||
`${ws_protocol}://${resolve_root(
|
||||
host,
|
||||
config.path as string,
|
||||
true
|
||||
)}/queue/join${url_params ? "?" + url_params : ""}`
|
||||
);
|
||||
|
||||
if (this.jwt) {
|
||||
url.searchParams.set("__sign", this.jwt);
|
||||
}
|
||||
|
||||
websocket = new WebSocket(url);
|
||||
|
||||
websocket.onclose = (evt) => {
|
||||
if (!evt.wasClean) {
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "error",
|
||||
broken: true,
|
||||
message: BROKEN_CONNECTION_MSG,
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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 && !complete) {
|
||||
// call 'status' listeners
|
||||
fire_event({
|
||||
type: "status",
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date(),
|
||||
...status
|
||||
});
|
||||
if (status.stage === "error") {
|
||||
websocket.close();
|
||||
}
|
||||
} 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") {
|
||||
complete = status;
|
||||
} else if (type === "log") {
|
||||
fire_event({
|
||||
type: "log",
|
||||
log: data.log,
|
||||
level: data.level,
|
||||
endpoint: _endpoint,
|
||||
fn_index
|
||||
});
|
||||
} else if (type === "generating") {
|
||||
fire_event({
|
||||
type: "status",
|
||||
time: new Date(),
|
||||
...status,
|
||||
stage: status?.stage!,
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index
|
||||
});
|
||||
}
|
||||
if (data) {
|
||||
fire_event({
|
||||
type: "data",
|
||||
time: new Date(),
|
||||
data: data.data,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
event_data,
|
||||
trigger_id
|
||||
});
|
||||
|
||||
if (complete) {
|
||||
fire_event({
|
||||
type: "status",
|
||||
time: new Date(),
|
||||
...complete,
|
||||
stage: status?.stage!,
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index
|
||||
});
|
||||
websocket.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// different ws contract for gradio versions older than 3.6.0
|
||||
//@ts-ignore
|
||||
if (semiver(config.version || "2.0.0", "3.6") < 0) {
|
||||
addEventListener("open", () =>
|
||||
websocket.send(JSON.stringify({ hash: session_hash }))
|
||||
);
|
||||
}
|
||||
} else if (protocol == "sse") {
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "pending",
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date()
|
||||
});
|
||||
var params = new URLSearchParams({
|
||||
fn_index: fn_index.toString(),
|
||||
session_hash: session_hash
|
||||
}).toString();
|
||||
let url = new URL(
|
||||
`${config.root}/queue/join?${
|
||||
url_params ? url_params + "&" : ""
|
||||
}${params}`
|
||||
);
|
||||
|
||||
event_source = this.eventSource_factory(url);
|
||||
|
||||
if (!event_source) {
|
||||
throw new Error(
|
||||
"Cannot connect to sse endpoint: " + url.toString()
|
||||
);
|
||||
}
|
||||
|
||||
event_source.onmessage = async function (event) {
|
||||
const _data = JSON.parse(event.data);
|
||||
const { type, status, data } = handle_message(
|
||||
_data,
|
||||
last_status[fn_index]
|
||||
);
|
||||
|
||||
if (type === "update" && status && !complete) {
|
||||
// call 'status' listeners
|
||||
fire_event({
|
||||
type: "status",
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date(),
|
||||
...status
|
||||
});
|
||||
if (status.stage === "error") {
|
||||
event_source?.close();
|
||||
}
|
||||
} else if (type === "data") {
|
||||
event_id = _data.event_id as string;
|
||||
let [_, status] = await post_data(`${config.root}/queue/data`, {
|
||||
...payload,
|
||||
session_hash,
|
||||
event_id
|
||||
});
|
||||
if (status !== 200) {
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "error",
|
||||
message: BROKEN_CONNECTION_MSG,
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date()
|
||||
});
|
||||
event_source?.close();
|
||||
}
|
||||
} else if (type === "complete") {
|
||||
complete = status;
|
||||
} else if (type === "log") {
|
||||
fire_event({
|
||||
type: "log",
|
||||
log: data.log,
|
||||
level: data.level,
|
||||
endpoint: _endpoint,
|
||||
fn_index
|
||||
});
|
||||
} else if (type === "generating") {
|
||||
fire_event({
|
||||
type: "status",
|
||||
time: new Date(),
|
||||
...status,
|
||||
stage: status?.stage!,
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index
|
||||
});
|
||||
}
|
||||
if (data) {
|
||||
fire_event({
|
||||
type: "data",
|
||||
time: new Date(),
|
||||
data: data.data,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
event_data,
|
||||
trigger_id
|
||||
});
|
||||
|
||||
if (complete) {
|
||||
fire_event({
|
||||
type: "status",
|
||||
time: new Date(),
|
||||
...complete,
|
||||
stage: status?.stage!,
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index
|
||||
});
|
||||
event_source?.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
} else if (
|
||||
protocol == "sse_v1" ||
|
||||
protocol == "sse_v2" ||
|
||||
protocol == "sse_v2.1" ||
|
||||
protocol == "sse_v3"
|
||||
) {
|
||||
// latest API format. v2 introduces sending diffs for intermediate outputs in generative functions, which makes payloads lighter.
|
||||
// v3 only closes the stream when the backend sends the close stream message.
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "pending",
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date()
|
||||
});
|
||||
let hostname = window.location.hostname;
|
||||
let hfhubdev = "dev.spaces.huggingface.tech";
|
||||
const origin = hostname.includes(".dev.")
|
||||
? `https://moon-${hostname.split(".")[1]}.${hfhubdev}`
|
||||
: `https://huggingface.co`;
|
||||
const zerogpu_auth_promise =
|
||||
dependency.zerogpu && window.parent != window && config.space_id
|
||||
? post_message<Headers>("zerogpu-headers", origin)
|
||||
: Promise.resolve(null);
|
||||
const post_data_promise = zerogpu_auth_promise.then((headers) => {
|
||||
return post_data(
|
||||
`${config.root}/queue/join?${url_params}`,
|
||||
{
|
||||
...payload,
|
||||
session_hash
|
||||
},
|
||||
headers
|
||||
);
|
||||
});
|
||||
post_data_promise.then(([response, status]: any) => {
|
||||
if (status === 503) {
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "error",
|
||||
message: QUEUE_FULL_MSG,
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date()
|
||||
});
|
||||
} else if (status !== 200) {
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "error",
|
||||
message: BROKEN_CONNECTION_MSG,
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date()
|
||||
});
|
||||
} else {
|
||||
event_id = response.event_id as string;
|
||||
let callback = async function (_data: object): Promise<void> {
|
||||
try {
|
||||
const { type, status, data } = handle_message(
|
||||
_data,
|
||||
last_status[fn_index]
|
||||
);
|
||||
|
||||
if (type == "heartbeat") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "update" && status && !complete) {
|
||||
// call 'status' listeners
|
||||
fire_event({
|
||||
type: "status",
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date(),
|
||||
...status
|
||||
});
|
||||
} else if (type === "complete") {
|
||||
complete = status;
|
||||
} else if (type == "unexpected_error") {
|
||||
console.error("Unexpected error", status?.message);
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "error",
|
||||
message:
|
||||
status?.message || "An Unexpected Error Occurred!",
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date()
|
||||
});
|
||||
} else if (type === "log") {
|
||||
fire_event({
|
||||
type: "log",
|
||||
log: data.log,
|
||||
level: data.level,
|
||||
endpoint: _endpoint,
|
||||
fn_index
|
||||
});
|
||||
return;
|
||||
} else if (type === "generating") {
|
||||
fire_event({
|
||||
type: "status",
|
||||
time: new Date(),
|
||||
...status,
|
||||
stage: status?.stage!,
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index
|
||||
});
|
||||
if (
|
||||
data &&
|
||||
["sse_v2", "sse_v2.1", "sse_v3"].includes(protocol)
|
||||
) {
|
||||
apply_diff_stream(pending_diff_streams, event_id!, data);
|
||||
}
|
||||
}
|
||||
if (data) {
|
||||
fire_event({
|
||||
type: "data",
|
||||
time: new Date(),
|
||||
data: data.data,
|
||||
endpoint: _endpoint,
|
||||
fn_index
|
||||
});
|
||||
|
||||
if (complete) {
|
||||
fire_event({
|
||||
type: "status",
|
||||
time: new Date(),
|
||||
...complete,
|
||||
stage: status?.stage!,
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
status?.stage === "complete" ||
|
||||
status?.stage === "error"
|
||||
) {
|
||||
if (event_callbacks[event_id!]) {
|
||||
delete event_callbacks[event_id!];
|
||||
}
|
||||
if (event_id! in pending_diff_streams) {
|
||||
delete pending_diff_streams[event_id!];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Unexpected client exception", e);
|
||||
fire_event({
|
||||
type: "status",
|
||||
stage: "error",
|
||||
message: "An Unexpected Error Occurred!",
|
||||
queue: true,
|
||||
endpoint: _endpoint,
|
||||
fn_index,
|
||||
time: new Date()
|
||||
});
|
||||
if (["sse_v2", "sse_v2.1"].includes(protocol)) {
|
||||
close_stream(stream_status, event_source);
|
||||
stream_status.open = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (event_id in pending_stream_messages) {
|
||||
pending_stream_messages[event_id].forEach((msg) =>
|
||||
callback(msg)
|
||||
);
|
||||
delete pending_stream_messages[event_id];
|
||||
}
|
||||
// @ts-ignore
|
||||
event_callbacks[event_id] = callback;
|
||||
unclosed_events.add(event_id);
|
||||
if (!stream_status.open) {
|
||||
this.open_stream();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return { on, off, cancel, destroy };
|
||||
} catch (error) {
|
||||
console.error("Submit function encountered an error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function get_endpoint_info(
|
||||
api_info: ApiInfo<JsApiData>,
|
||||
endpoint: string | number,
|
||||
api_map: Record<string, number>,
|
||||
config: Config
|
||||
): {
|
||||
fn_index: number;
|
||||
endpoint_info: EndpointInfo<JsApiData>;
|
||||
dependency: Dependency;
|
||||
} {
|
||||
let fn_index: number;
|
||||
let endpoint_info: EndpointInfo<JsApiData>;
|
||||
let dependency: Dependency;
|
||||
|
||||
if (typeof endpoint === "number") {
|
||||
fn_index = endpoint;
|
||||
endpoint_info = api_info.unnamed_endpoints[fn_index];
|
||||
dependency = config.dependencies[endpoint];
|
||||
} else {
|
||||
const trimmed_endpoint = endpoint.replace(/^\//, "");
|
||||
|
||||
fn_index = api_map[trimmed_endpoint];
|
||||
endpoint_info = api_info.named_endpoints[endpoint.trim()];
|
||||
dependency = config.dependencies[api_map[trimmed_endpoint]];
|
||||
}
|
||||
|
||||
if (typeof fn_index !== "number") {
|
||||
throw new Error(
|
||||
"There is no endpoint matching that name of fn_index matching that number."
|
||||
);
|
||||
}
|
||||
return { fn_index, endpoint_info, dependency };
|
||||
}
|
46
client/js/src/utils/upload_files.ts
Normal file
46
client/js/src/utils/upload_files.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import type { Client } from "..";
|
||||
import { BROKEN_CONNECTION_MSG } from "../constants";
|
||||
import type { UploadResponse } from "../types";
|
||||
|
||||
export async function upload_files(
|
||||
this: Client,
|
||||
root_url: string,
|
||||
files: (Blob | File)[],
|
||||
upload_id?: string
|
||||
): Promise<UploadResponse> {
|
||||
const headers: {
|
||||
Authorization?: string;
|
||||
} = {};
|
||||
if (this.options.hf_token) {
|
||||
headers.Authorization = `Bearer ${this.options.hf_token}`;
|
||||
}
|
||||
const chunkSize = 1000;
|
||||
const uploadResponses = [];
|
||||
for (let i = 0; i < files.length; i += chunkSize) {
|
||||
const chunk = files.slice(i, i + chunkSize);
|
||||
const formData = new FormData();
|
||||
chunk.forEach((file) => {
|
||||
formData.append("files", file);
|
||||
});
|
||||
try {
|
||||
const upload_url = upload_id
|
||||
? `${root_url}/upload?upload_id=${upload_id}`
|
||||
: `${root_url}/upload`;
|
||||
var response = await this.fetch_implementation(upload_url, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers
|
||||
});
|
||||
} catch (e) {
|
||||
return { error: BROKEN_CONNECTION_MSG };
|
||||
}
|
||||
if (!response.ok) {
|
||||
return { error: await response.text() };
|
||||
}
|
||||
const output: UploadResponse["files"] = await response.json();
|
||||
if (output) {
|
||||
uploadResponses.push(...output);
|
||||
}
|
||||
}
|
||||
return { files: uploadResponses };
|
||||
}
|
70
client/js/src/utils/view_api.ts
Normal file
70
client/js/src/utils/view_api.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import type { ApiInfo, ApiData } from "../types";
|
||||
import semiver from "semiver";
|
||||
import { API_INFO_URL, BROKEN_CONNECTION_MSG } from "../constants";
|
||||
import { Client } from "../client";
|
||||
import { SPACE_FETCHER_URL } from "../constants";
|
||||
import { transform_api_info } from "../helpers/api_info";
|
||||
|
||||
export async function view_api(this: Client): Promise<any> {
|
||||
if (this.api_info) return this.api_info;
|
||||
|
||||
const { hf_token } = this.options;
|
||||
const { config } = this;
|
||||
|
||||
const headers: {
|
||||
Authorization?: string;
|
||||
"Content-Type": "application/json";
|
||||
} = { "Content-Type": "application/json" };
|
||||
|
||||
if (hf_token) {
|
||||
headers.Authorization = `Bearer ${hf_token}`;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let response: Response;
|
||||
|
||||
if (semiver(config?.version || "2.0.0", "3.30") < 0) {
|
||||
response = await this.fetch_implementation(SPACE_FETCHER_URL, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
serialize: false,
|
||||
config: JSON.stringify(config)
|
||||
}),
|
||||
headers
|
||||
});
|
||||
} else {
|
||||
response = await this.fetch_implementation(
|
||||
`${config?.root}/${API_INFO_URL}`,
|
||||
{
|
||||
headers
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(BROKEN_CONNECTION_MSG);
|
||||
}
|
||||
|
||||
let api_info = (await response.json()) as
|
||||
| ApiInfo<ApiData>
|
||||
| { api: ApiInfo<ApiData> };
|
||||
if ("api" in api_info) {
|
||||
api_info = api_info.api;
|
||||
}
|
||||
|
||||
if (
|
||||
api_info.named_endpoints["/predict"] &&
|
||||
!api_info.unnamed_endpoints["0"]
|
||||
) {
|
||||
api_info.unnamed_endpoints[0] = api_info.named_endpoints["/predict"];
|
||||
}
|
||||
|
||||
return transform_api_info(api_info, config, this.api_map);
|
||||
} catch (e) {
|
||||
"Could not get API info. " + (e as Error).message;
|
||||
}
|
||||
}
|
1
client/js/src/vite-env.d.ts
vendored
Normal file
1
client/js/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
@ -7,8 +7,21 @@
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "dist",
|
||||
"declarationMap": true,
|
||||
"module": "es2020",
|
||||
"module": "ESNext",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler */
|
||||
"moduleResolution": "bundler",
|
||||
"skipDefaultLibCheck": true
|
||||
"skipDefaultLibCheck": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: "src/index.ts",
|
||||
formats: ["es"]
|
||||
formats: ["es"],
|
||||
fileName: (format) => `index.${format}.js`
|
||||
},
|
||||
rollupOptions: {
|
||||
input: "src/index.ts",
|
||||
@ -17,18 +15,7 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
svelte()
|
||||
// {
|
||||
// name: "resolve-gradio-client",
|
||||
// enforce: "pre",
|
||||
// resolveId(id) {
|
||||
// if (id === "@gradio/client") {
|
||||
// return path.join(__dirname, "src", "index.ts");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
],
|
||||
plugins: [svelte()],
|
||||
|
||||
ssr: {
|
||||
target: "node",
|
||||
|
8
globals.d.ts
vendored
8
globals.d.ts
vendored
@ -14,7 +14,7 @@ declare global {
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
auth_required: boolean | undefined;
|
||||
auth_required: boolean;
|
||||
auth_message: string;
|
||||
components: any[];
|
||||
css: string | null;
|
||||
@ -32,4 +32,10 @@ export interface Config {
|
||||
show_api: boolean;
|
||||
stylesheets: string[];
|
||||
path: string;
|
||||
js: string | null;
|
||||
head: string | null;
|
||||
analytics_enabled: boolean;
|
||||
show_error: boolean;
|
||||
is_space: boolean;
|
||||
protocol: "ws" | "sse" | "sse_v1" | "sse_v2" | "sse_v2.1" | "sse_v3";
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Getting Started with the Gradio JavaScript client
|
||||
# Getting Started with the Gradio JavaScript Client
|
||||
|
||||
Tags: CLIENT, API, SPACES
|
||||
|
||||
The Gradio JavaScript client makes it very easy to use any Gradio app as an API. As an example, consider this [Hugging Face Space that transcribes audio files](https://huggingface.co/spaces/abidlabs/whisper) that are recorded from the microphone.
|
||||
The Gradio JavaScript Client makes it very easy to use any Gradio app as an API. As an example, consider this [Hugging Face Space that transcribes audio files](https://huggingface.co/spaces/abidlabs/whisper) that are recorded from the microphone.
|
||||
|
||||

|
||||
|
||||
@ -11,21 +11,21 @@ Using the `@gradio/client` library, we can easily use the Gradio as an API to tr
|
||||
Here's the entire code to do it:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const response = await fetch(
|
||||
"https://github.com/audio-samples/audio-samples.github.io/raw/master/samples/wav/ted_speakers/SalmanKhan/sample-1.wav"
|
||||
);
|
||||
const audio_file = await response.blob();
|
||||
|
||||
const app = await client("abidlabs/whisper");
|
||||
const app = await Client.connect("abidlabs/whisper");
|
||||
const transcription = await app.predict("/predict", [audio_file]);
|
||||
|
||||
console.log(transcription.data);
|
||||
// [ "I said the same phrase 30 times." ]
|
||||
```
|
||||
|
||||
The Gradio client works with any hosted Gradio app, whether it be an image generator, a text summarizer, a stateful chatbot, a tax calculator, or anything else! The Gradio Client is mostly used with apps hosted on [Hugging Face Spaces](https://hf.space), but your app can be hosted anywhere, such as your own server.
|
||||
The Gradio Client works with any hosted Gradio app, whether it be an image generator, a text summarizer, a stateful chatbot, a tax calculator, or anything else! The Gradio Client is mostly used with apps hosted on [Hugging Face Spaces](https://hf.space), but your app can be hosted anywhere, such as your own server.
|
||||
|
||||
**Prequisites**: To use the Gradio client, you do _not_ need to know the `gradio` library in great detail. However, it is helpful to have general familiarity with Gradio's concepts of input and output components.
|
||||
|
||||
@ -44,47 +44,45 @@ Start by connecting instantiating a `client` instance and connecting it to a Gra
|
||||
## Connecting to a Hugging Face Space
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = client("abidlabs/en2fr"); // a Space that translates from English to French
|
||||
const app = Client.connect("abidlabs/en2fr"); // a Space that translates from English to French
|
||||
```
|
||||
|
||||
You can also connect to private Spaces by passing in your HF token with the `hf_token` property of the options parameter. You can get your HF token here: https://huggingface.co/settings/tokens
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = client("abidlabs/my-private-space", { hf_token="hf_..." })
|
||||
const app = Client.connect("abidlabs/my-private-space", { hf_token="hf_..." })
|
||||
```
|
||||
|
||||
## Duplicating a Space for private use
|
||||
|
||||
While you can use any public Space as an API, you may get rate limited by Hugging Face if you make too many requests. For unlimited usage of a Space, simply duplicate the Space to create a private Space, and then use it to make as many requests as you'd like!
|
||||
While you can use any public Space as an API, you may get rate limited by Hugging Face if you make too many requests. For unlimited usage of a Space, simply duplicate the Space to create a private Space, and then use it to make as many requests as you'd like! You'll need to pass in your [Hugging Face token](https://huggingface.co/settings/tokens)).
|
||||
|
||||
The `@gradio/client` exports another function, `duplicate`, to make this process simple (you'll need to pass in your [Hugging Face token](https://huggingface.co/settings/tokens)).
|
||||
|
||||
`duplicate` is almost identical to `client`, the only difference is under the hood:
|
||||
`Client.duplicate` is almost identical to `Client.connect`, the only difference is under the hood:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const response = await fetch(
|
||||
"https://audio-samples.github.io/samples/mp3/blizzard_unconditional/sample-0.mp3"
|
||||
);
|
||||
const audio_file = await response.blob();
|
||||
|
||||
const app = await duplicate("abidlabs/whisper", { hf_token: "hf_..." });
|
||||
const app = await Client.duplicate("abidlabs/whisper", { hf_token: "hf_..." });
|
||||
const transcription = app.predict("/predict", [audio_file]);
|
||||
```
|
||||
|
||||
If you have previously duplicated a Space, re-running `duplicate` will _not_ create a new Space. Instead, the client will attach to the previously-created Space. So it is safe to re-run the `duplicate` method multiple times with the same space.
|
||||
If you have previously duplicated a Space, re-running `Client.duplicate` will _not_ create a new Space. Instead, the client will attach to the previously-created Space. So it is safe to re-run the `Client.duplicate` method multiple times with the same space.
|
||||
|
||||
**Note:** if the original Space uses GPUs, your private Space will as well, and your Hugging Face account will get billed based on the price of the GPU. To minimize charges, your Space will automatically go to sleep after 5 minutes of inactivity. You can also set the hardware using the `hardware` and `timeout` properties of `duplicate`'s options object like this:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await duplicate("abidlabs/whisper", {
|
||||
const app = await Client.duplicate("abidlabs/whisper", {
|
||||
hf_token: "hf_...",
|
||||
timeout: 60,
|
||||
hardware: "a10g-small"
|
||||
@ -96,21 +94,21 @@ const app = await duplicate("abidlabs/whisper", {
|
||||
If your app is running somewhere else, just provide the full URL instead, including the "http://" or "https://". Here's an example of making predictions to a Gradio app that is running on a share URL:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = client("https://bec81a83-5b5c-471e.gradio.live");
|
||||
const app = Client.connect("https://bec81a83-5b5c-471e.gradio.live");
|
||||
```
|
||||
|
||||
## Inspecting the API endpoints
|
||||
|
||||
Once you have connected to a Gradio app, you can view the APIs that are available to you by calling the `client`'s `view_api` method.
|
||||
Once you have connected to a Gradio app, you can view the APIs that are available to you by calling the `Client`'s `view_api` method.
|
||||
|
||||
For the Whisper Space, we can do this:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("abidlabs/whisper");
|
||||
const app = await Client.connect("abidlabs/whisper");
|
||||
|
||||
const app_info = await app.view_api();
|
||||
|
||||
@ -161,32 +159,32 @@ The View API page also includes an "API Recorder" that lets you interact with th
|
||||
The simplest way to make a prediction is simply to call the `.predict()` method with the appropriate arguments:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const app = await Client.connect("abidlabs/en2fr");
|
||||
const result = await app.predict("/predict", ["Hello"]);
|
||||
```
|
||||
|
||||
If there are multiple parameters, then you should pass them as an array to `.predict()`, like this:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("gradio/calculator");
|
||||
const app = await Client.connect("gradio/calculator");
|
||||
const result = await app.predict("/predict", [4, "add", 5]);
|
||||
```
|
||||
|
||||
For certain inputs, such as images, you should pass in a `Buffer`, `Blob` or `File` depending on what is most convenient. In node, this would be a `Buffer` or `Blob`; in a browser environment, this would be a `Blob` or `File`.
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const response = await fetch(
|
||||
"https://audio-samples.github.io/samples/mp3/blizzard_unconditional/sample-0.mp3"
|
||||
);
|
||||
const audio_file = await response.blob();
|
||||
|
||||
const app = await client("abidlabs/whisper");
|
||||
const app = await Client.connect("abidlabs/whisper");
|
||||
const result = await app.predict("/predict", [audio_file]);
|
||||
```
|
||||
|
||||
@ -195,7 +193,7 @@ const result = await app.predict("/predict", [audio_file]);
|
||||
If the API you are working with can return results over time, or you wish to access information about the status of a job, you can use the event interface for more flexibility. This is especially useful for iterative endpoints or generator endpoints that will produce a series of values over time as discreet responses.
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
function log_result(payload) {
|
||||
const {
|
||||
@ -205,7 +203,7 @@ function log_result(payload) {
|
||||
console.log(`The translated result is: ${translation}`);
|
||||
}
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const app = await Client.connect("abidlabs/en2fr");
|
||||
const job = app.submit("/predict", ["Hello"]);
|
||||
|
||||
job.on("data", log_result);
|
||||
@ -216,7 +214,7 @@ job.on("data", log_result);
|
||||
The event interface also allows you to get the status of the running job by listening to the `"status"` event. This returns an object with the following attributes: `status` (a human readbale status of the current job, `"pending" | "generating" | "complete" | "error"`), `code` (the detailed gradio code for the job), `position` (the current position of this job in the queue), `queue_size` (the total queue size), `eta` (estimated time this job will complete), `success` (a boolean representing whether the job completed successfully), and `time` ( as `Date` object detailing the time that the status was generated).
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
function log_status(status) {
|
||||
console.log(
|
||||
@ -224,7 +222,7 @@ function log_status(status) {
|
||||
);
|
||||
}
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const app = await Client.connect("abidlabs/en2fr");
|
||||
const job = app.submit("/predict", ["Hello"]);
|
||||
|
||||
job.on("status", log_status);
|
||||
@ -235,9 +233,9 @@ job.on("status", log_status);
|
||||
The job instance also has a `.cancel()` method that cancels jobs that have been queued but not started. For example, if you run:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const app = await Client.connect("abidlabs/en2fr");
|
||||
const job_one = app.submit("/predict", ["Hello"]);
|
||||
const job_two = app.submit("/predict", ["Friends"]);
|
||||
|
||||
@ -252,9 +250,9 @@ If the first job has started processing, then it will not be canceled but the cl
|
||||
Some Gradio API endpoints do not return a single value, rather they return a series of values. You can listen for these values in real time using the event interface:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("gradio/count_generator");
|
||||
const app = await Client.connect("gradio/count_generator");
|
||||
const job = app.submit(0, [9]);
|
||||
|
||||
job.on("data", (data) => console.log(data));
|
||||
@ -265,9 +263,9 @@ This will log out the values as they are generated by the endpoint.
|
||||
You can also cancel jobs that that have iterative outputs, in which case the job will finish immediately.
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("gradio/count_generator");
|
||||
const app = await Client.connect("gradio/count_generator");
|
||||
const job = app.submit(0, [9]);
|
||||
|
||||
job.on("data", (data) => console.log(data));
|
||||
|
@ -11,14 +11,14 @@ Gradio JavaScript客户端使得使用任何Gradio应用作为API非常简单。
|
||||
以下是完成此操作的完整代码:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const response = await fetch(
|
||||
"https://github.com/audio-samples/audio-samples.github.io/raw/master/samples/wav/ted_speakers/SalmanKhan/sample-1.wav"
|
||||
);
|
||||
const audio_file = await response.blob();
|
||||
|
||||
const app = await client("abidlabs/whisper");
|
||||
const app = await Client.connect("abidlabs/whisper");
|
||||
const transcription = await app.predict("/predict", [audio_file]);
|
||||
|
||||
console.log(transcription.data);
|
||||
@ -39,22 +39,22 @@ npm i @gradio/client
|
||||
|
||||
## 连接到正在运行的Gradio应用
|
||||
|
||||
首先,通过实例化`client`对象并将其连接到在Hugging Face Spaces或任何其他位置运行的Gradio应用来建立连接。
|
||||
首先,通过实例化`Client`对象并将其连接到在Hugging Face Spaces或任何其他位置运行的Gradio应用来建立连接。
|
||||
|
||||
## 连接到Hugging Face Space
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = client("abidlabs/en2fr"); // 一个从英语翻译为法语的 Space
|
||||
const app = Client.connect("abidlabs/en2fr"); // 一个从英语翻译为法语的 Space
|
||||
```
|
||||
|
||||
您还可以通过在options参数的`hf_token`属性中传入您的HF token来连接到私有Spaces。您可以在此处获取您的HF token:https://huggingface.co/settings/tokens
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = client("abidlabs/my-private-space", { hf_token="hf_..." })
|
||||
const app = Client.connect("abidlabs/my-private-space", { hf_token="hf_..." })
|
||||
```
|
||||
|
||||
## 为私人使用复制一个Space
|
||||
@ -63,17 +63,17 @@ const app = client("abidlabs/my-private-space", { hf_token="hf_..." })
|
||||
|
||||
`@gradio/client`还导出了另一个函数`duplicate`,以使此过程变得简单(您将需要传入您的[Hugging Face token](https://huggingface.co/settings/tokens))。
|
||||
|
||||
`duplicate`与`client`几乎相同,唯一的区别在于底层实现:
|
||||
`duplicate`与`Client`几乎相同,唯一的区别在于底层实现:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const response = await fetch(
|
||||
"https://audio-samples.github.io/samples/mp3/blizzard_unconditional/sample-0.mp3"
|
||||
);
|
||||
const audio_file = await response.blob();
|
||||
|
||||
const app = await duplicate("abidlabs/whisper", { hf_token: "hf_..." });
|
||||
const app = await Client.duplicate("abidlabs/whisper", { hf_token: "hf_..." });
|
||||
const transcription = app.predict("/predict", [audio_file]);
|
||||
```
|
||||
|
||||
@ -82,9 +82,9 @@ const transcription = app.predict("/predict", [audio_file]);
|
||||
**注意:**如果原始Space使用了GPU,您的私有Space也将使用GPU,并且将根据GPU的价格向您的Hugging Face账户计费。为了最大程度地减少费用,在5分钟不活动后,您的Space将自动进入休眠状态。您还可以使用`duplicate`的options对象的`hardware`和`timeout`属性来设置硬件,例如:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await duplicate("abidlabs/whisper", {
|
||||
const app = await Client.duplicate("abidlabs/whisper", {
|
||||
hf_token: "hf_...",
|
||||
timeout: 60,
|
||||
hardware: "a10g-small"
|
||||
@ -96,21 +96,21 @@ const app = await duplicate("abidlabs/whisper", {
|
||||
如果您的应用程序在其他地方运行,只需提供完整的URL,包括"http://"或"https://"。以下是向运行在共享URL上的Gradio应用进行预测的示例:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = client("https://bec81a83-5b5c-471e.gradio.live");
|
||||
const app = Client.connect("https://bec81a83-5b5c-471e.gradio.live");
|
||||
```
|
||||
|
||||
## 检查API端点
|
||||
|
||||
一旦连接到Gradio应用程序,可以通过调用`client`的`view_api`方法来查看可用的API端点。
|
||||
一旦连接到Gradio应用程序,可以通过调用`Client`的`view_api`方法来查看可用的API端点。
|
||||
|
||||
对于Whisper Space,我们可以这样做:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("abidlabs/whisper");
|
||||
const app = await Client.connect("abidlabs/whisper");
|
||||
|
||||
const app_info = await app.view_info();
|
||||
|
||||
@ -152,33 +152,33 @@ console.log(app_info);
|
||||
进行预测的最简单方法就是使用适当的参数调用`.predict()`方法:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const app = await Client.connect("abidlabs/en2fr");
|
||||
const result = await app.predict("/predict", ["Hello"]);
|
||||
```
|
||||
|
||||
如果有多个参数,您应该将它们作为一个数组传递给`.predict()`,像这样:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("gradio/calculator");
|
||||
const app = await Client.connect("gradio/calculator");
|
||||
const result = await app.predict("/predict", [4, "add", 5]);
|
||||
```
|
||||
|
||||
对于某些输入,例如图像,您应该根据所需要的方便程度传入`Buffer`、`Blob`或`File`。在Node.js中,可以使用`Buffer`或`Blob`;在浏览器环境中,可以使用`Blob`或`File`。
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const response = await fetch(
|
||||
"https://audio-samples.github.io/samples/mp3/blizzard_unconditional/sample-0.mp3"
|
||||
);
|
||||
const audio_file = await response.blob();
|
||||
|
||||
const app = await client("abidlabs/whisper");
|
||||
const result = await client.predict("/predict", [audio_file]);
|
||||
const app = await Client.connect("abidlabs/whisper");
|
||||
const result = await Client.connect.predict("/predict", [audio_file]);
|
||||
```
|
||||
|
||||
## 使用事件
|
||||
@ -186,7 +186,7 @@ const result = await client.predict("/predict", [audio_file]);
|
||||
如果您使用的API可以随时间返回结果,或者您希望访问有关作业状态的信息,您可以使用事件接口获取更大的灵活性。这对于迭代的或生成器的端点特别有用,因为它们会生成一系列离散的响应值。
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
function log_result(payload) {
|
||||
const {
|
||||
@ -196,7 +196,7 @@ function log_result(payload) {
|
||||
console.log(`翻译结果为:${translation}`);
|
||||
}
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const app = await Client.connect("abidlabs/en2fr");
|
||||
const job = app.submit("/predict", ["Hello"]);
|
||||
|
||||
job.on("data", log_result);
|
||||
@ -207,13 +207,13 @@ job.on("data", log_result);
|
||||
事件接口还可以通过监听`"status"`事件来获取运行作业的状态。这将返回一个对象,其中包含以下属性:`status`(当前作业的人类可读状态,`"pending" | "generating" | "complete" | "error"`),`code`(作业的详细gradio code),`position`(此作业在队列中的当前位置),`queue_size`(总队列大小),`eta`(作业完成的预计时间),`success`(表示作业是否成功完成的布尔值)和`time`(作业状态生成的时间,是一个`Date`对象)。
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
function log_status(status) {
|
||||
console.log(`此作业的当前状态为:${JSON.stringify(status, null, 2)}`);
|
||||
}
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const app = await Client.connect("abidlabs/en2fr");
|
||||
const job = app.submit("/predict", ["Hello"]);
|
||||
|
||||
job.on("status", log_status);
|
||||
@ -224,9 +224,9 @@ job.on("status", log_status);
|
||||
作业实例还具有`.cancel()`方法,用于取消已排队但尚未启动的作业。例如,如果您运行以下命令:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const app = await Client.connect("abidlabs/en2fr");
|
||||
const job_one = app.submit("/predict", ["Hello"]);
|
||||
const job_two = app.submit("/predict", ["Friends"]);
|
||||
|
||||
@ -241,9 +241,9 @@ job_two.cancel();
|
||||
某些Gradio API端点不返回单个值,而是返回一系列值。您可以使用事件接口实时侦听这些值:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("gradio/count_generator");
|
||||
const app = await Client.connect("gradio/count_generator");
|
||||
const job = app.submit(0, [9]);
|
||||
|
||||
job.on("data", (data) => console.log(data));
|
||||
@ -254,9 +254,9 @@ job.on("data", (data) => console.log(data));
|
||||
您还可以取消具有迭代输出的作业,在这种情况下,作业将立即完成。
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client("gradio/count_generator");
|
||||
const app = await Client.connect("gradio/count_generator");
|
||||
const job = app.submit(0, [9]);
|
||||
|
||||
job.on("data", (data) => console.log(data));
|
||||
|
@ -1,13 +1,16 @@
|
||||
<script>
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
import EndpointInputs from "../../lib/EndpointInputs.svelte";
|
||||
import ResponsePreview from "../../lib/ResponsePreview.svelte";
|
||||
|
||||
let api = "gradio/cancel_events";
|
||||
let hf_token = "";
|
||||
|
||||
/**
|
||||
* @type Awaited<ReturnType<typeof client>>
|
||||
* @type {`hf_${string}`}
|
||||
*/
|
||||
let hf_token = "hf_";
|
||||
/**
|
||||
* @type Client
|
||||
*/
|
||||
let app;
|
||||
/**
|
||||
@ -40,11 +43,10 @@
|
||||
app_info = undefined;
|
||||
active_endpoint = "";
|
||||
response_data = { data: [], fn_index: 0, endpoint: "" };
|
||||
if (!api || (hf_token && !hf_token.startsWith("_hf"))) return;
|
||||
if (!api || (hf_token && !hf_token.startsWith("hf_"))) return;
|
||||
|
||||
app = await client(api, {
|
||||
//@ts-ignore
|
||||
hf_token: hf_token
|
||||
app = await Client.connect(api, {
|
||||
hf_token
|
||||
});
|
||||
|
||||
const { named_endpoints, unnamed_endpoints } = await app.view_api();
|
||||
@ -69,7 +71,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* @type ReturnType<Awaited<ReturnType<typeof client>>["submit"]>
|
||||
* @type ReturnType<Client['submit']>
|
||||
*/
|
||||
let job;
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { tick } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { client } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
import { setContext } from "svelte";
|
||||
|
||||
import type { LoadingStatus, LoadingStatusCollection } from "./stores";
|
||||
|
||||
@ -34,7 +35,7 @@
|
||||
export let control_page_title = false;
|
||||
export let app_mode: boolean;
|
||||
export let theme_mode: ThemeMode;
|
||||
export let app: Awaited<ReturnType<typeof client>>;
|
||||
export let app: Awaited<ReturnType<typeof Client.connect>>;
|
||||
export let space_id: string | null;
|
||||
export let version: string;
|
||||
export let js: string | null;
|
||||
@ -498,6 +499,8 @@
|
||||
function isCustomEvent(event: Event): event is CustomEvent {
|
||||
return "detail" in event;
|
||||
}
|
||||
|
||||
setContext("upload_files", app.upload_files);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@ -525,7 +528,7 @@
|
||||
|
||||
<div class="wrap" style:min-height={app_mode ? "100%" : "auto"}>
|
||||
<div class="contain" style:flex-grow={app_mode ? "1" : "auto"}>
|
||||
{#if $_layout}
|
||||
{#if $_layout && app.config}
|
||||
<MountComponents
|
||||
rootNode={$_layout}
|
||||
{root}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script context="module" lang="ts">
|
||||
import { writable } from "svelte/store";
|
||||
import { mount_css as default_mount_css, prefix_css } from "./css";
|
||||
import type { Client as ClientType } from "@gradio/client";
|
||||
|
||||
import type { ComponentMeta, Dependency, LayoutNode } from "./types";
|
||||
|
||||
@ -63,7 +64,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, setContext, createEventDispatcher } from "svelte";
|
||||
import type { api_factory, SpaceStatus } from "@gradio/client";
|
||||
import type { SpaceStatus } from "@gradio/client";
|
||||
import Embed from "./Embed.svelte";
|
||||
import type { ThemeMode } from "./types";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -90,8 +91,7 @@
|
||||
|
||||
// These utilities are exported to be injectable for the Wasm version.
|
||||
export let mount_css: typeof default_mount_css = default_mount_css;
|
||||
export let client: ReturnType<typeof api_factory>["client"];
|
||||
export let upload_files: ReturnType<typeof api_factory>["upload_files"];
|
||||
export let Client: typeof ClientType;
|
||||
export let worker_proxy: WorkerProxy | undefined = undefined;
|
||||
if (worker_proxy) {
|
||||
setWorkerProxyContext(worker_proxy);
|
||||
@ -257,7 +257,7 @@
|
||||
detail: "SLEEPING"
|
||||
};
|
||||
|
||||
let app: Awaited<ReturnType<typeof client>>;
|
||||
let app: ClientType;
|
||||
let css_ready = false;
|
||||
function handle_status(_status: SpaceStatus): void {
|
||||
status = _status;
|
||||
@ -277,9 +277,14 @@
|
||||
}`
|
||||
: host || space || src || location.origin;
|
||||
|
||||
app = await client(api_url, {
|
||||
app = await Client.connect(api_url, {
|
||||
status_callback: handle_status
|
||||
});
|
||||
|
||||
if (!app.config) {
|
||||
throw new Error("Could not resolve app config");
|
||||
}
|
||||
|
||||
config = app.config;
|
||||
window.__gradio_space__ = config.space_id;
|
||||
|
||||
@ -304,10 +309,14 @@
|
||||
eventSource = new EventSource(url);
|
||||
eventSource.onmessage = async function (event) {
|
||||
if (event.data === "CHANGE") {
|
||||
app = await client(api_url, {
|
||||
app = await Client.connect(api_url, {
|
||||
status_callback: handle_status
|
||||
});
|
||||
|
||||
if (!app.config) {
|
||||
throw new Error("Could not resolve app config");
|
||||
}
|
||||
|
||||
config = app.config;
|
||||
window.__gradio_space__ = config.space_id;
|
||||
await mount_custom_css(config.css);
|
||||
@ -317,8 +326,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
setContext("upload_files", upload_files);
|
||||
|
||||
$: loader_status =
|
||||
!ready && status.load_status !== "error"
|
||||
? "pending"
|
||||
|
@ -2,11 +2,9 @@
|
||||
/* eslint-disable */
|
||||
import { onMount, createEventDispatcher } from "svelte";
|
||||
import type { ComponentMeta, Dependency } from "../types";
|
||||
import { post_data } from "@gradio/client";
|
||||
import NoApi from "./NoApi.svelte";
|
||||
import type { client } from "@gradio/client";
|
||||
import type { Client } from "@gradio/client";
|
||||
import type { Payload } from "../types";
|
||||
import { represent_value } from "./utils";
|
||||
|
||||
import ApiBanner from "./ApiBanner.svelte";
|
||||
import Button from "../../../button/shared/Button.svelte";
|
||||
@ -21,7 +19,7 @@
|
||||
|
||||
export let dependencies: Dependency[];
|
||||
export let root: string;
|
||||
export let app: Awaited<ReturnType<typeof client>>;
|
||||
export let app: Awaited<ReturnType<typeof Client.connect>>;
|
||||
export let space_id: string | null;
|
||||
export let root_node: ComponentMeta;
|
||||
const js_docs =
|
||||
@ -51,44 +49,6 @@
|
||||
|
||||
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 = find_recursive(root_node, _id)?.props?.default;
|
||||
if (default_data === undefined) {
|
||||
default_data = "";
|
||||
} else if (typeof default_data === "object") {
|
||||
default_data = JSON.stringify(default_data);
|
||||
}
|
||||
return default_data;
|
||||
})
|
||||
);
|
||||
|
||||
let dependency_outputs: any[][] = dependencies.map(
|
||||
(dependency) => new Array(dependency.outputs.length)
|
||||
);
|
||||
|
||||
let dependency_failures: boolean[][] = dependencies.map((dependency) =>
|
||||
new Array(dependency.inputs.length).fill(false)
|
||||
);
|
||||
|
||||
async function get_info(): Promise<{
|
||||
named_endpoints: any;
|
||||
unnamed_endpoints: any;
|
||||
@ -117,54 +77,6 @@
|
||||
js_info = js_api_info;
|
||||
});
|
||||
|
||||
async function run(index: number): Promise<void> {
|
||||
is_running = true;
|
||||
let dependency = dependencies[index];
|
||||
let attempted_component_index = 0;
|
||||
try {
|
||||
var inputs = dependency_inputs[index].map((input_val: any, i: number) => {
|
||||
attempted_component_index = i;
|
||||
let component = find_recursive(root_node, dependency.inputs[i])!;
|
||||
input_val = represent_value(
|
||||
input_val,
|
||||
component.documentation?.type?.input_payload ||
|
||||
component.documentation?.type?.payload
|
||||
);
|
||||
dependency_failures[index][attempted_component_index] = false;
|
||||
return input_val;
|
||||
});
|
||||
} catch (err) {
|
||||
dependency_failures[index][attempted_component_index] = true;
|
||||
is_running = false;
|
||||
return;
|
||||
}
|
||||
let [response, status_code] = await post_data(
|
||||
`${root}run/${dependency.api_name}`,
|
||||
{
|
||||
data: inputs
|
||||
}
|
||||
);
|
||||
is_running = false;
|
||||
if (status_code == 200) {
|
||||
dependency_outputs[index] = response.data.map(
|
||||
(output_val: any, i: number) => {
|
||||
let component = find_recursive(root_node, dependency.outputs[i])!;
|
||||
|
||||
return represent_value(
|
||||
output_val,
|
||||
component.documentation?.type?.response_object ||
|
||||
component.documentation?.type?.payload,
|
||||
"js"
|
||||
);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
dependency_failures[index] = new Array(
|
||||
dependency_failures[index].length
|
||||
).fill(true);
|
||||
}
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
onMount(() => {
|
||||
|
@ -78,14 +78,14 @@ result = client.<span class="highlight">predict</span
|
||||
<CopyButton code={js_code?.innerText} />
|
||||
</div>
|
||||
<div bind:this={js_code}>
|
||||
<pre>import { client } from "@gradio/client";
|
||||
<pre>import { Client } from "@gradio/client";
|
||||
{#each blob_examples as { label, type, python_type, component, example_input, serializer }, i}<!--
|
||||
-->
|
||||
const response_{i} = await fetch("{example_input.url}");
|
||||
const example{component} = await response_{i}.blob();
|
||||
{/each}<!--
|
||||
-->
|
||||
const app = await client(<span class="token string">"{root}"</span>);
|
||||
const app = await Client.connect(<span class="token string">"{root}"</span>);
|
||||
const result = await app.predict({#if named}<span class="api-name"
|
||||
>"/{dependency.api_name}"</span
|
||||
>{:else}{dependency_index}{/if}, [<!--
|
||||
|
@ -63,9 +63,9 @@ client.<span class="highlight"
|
||||
<CopyButton code={js_code?.innerText} />
|
||||
</div>
|
||||
<div bind:this={js_code}>
|
||||
<pre>import { client } from "@gradio/client";
|
||||
<pre>import { Client } from "@gradio/client";
|
||||
|
||||
const app = await client(<span class="token string">"{root}"</span>);
|
||||
const app = await Client.connect(<span class="token string">"{root}"</span>);
|
||||
{#each api_calls as call}<!--
|
||||
-->
|
||||
{#if dependencies[call.fn_index].backend_fn}client.predict(<span
|
||||
|
@ -2,7 +2,7 @@
|
||||
import Index from "../Index.svelte";
|
||||
import type { ThemeMode } from "../types";
|
||||
import { mount_css as default_mount_css } from "../css";
|
||||
import type { api_factory } from "@gradio/client";
|
||||
import type { Client as ClientType } from "@gradio/client";
|
||||
import type { WorkerProxy } from "@gradio/wasm";
|
||||
import { SvelteComponent, createEventDispatcher, onMount } from "svelte";
|
||||
import Code from "@gradio/code";
|
||||
@ -21,8 +21,7 @@
|
||||
export let info: boolean;
|
||||
export let eager: boolean;
|
||||
export let mount_css: typeof default_mount_css = default_mount_css;
|
||||
export let client: ReturnType<typeof api_factory>["client"];
|
||||
export let upload_files: ReturnType<typeof api_factory>["upload_files"];
|
||||
export let Client: typeof ClientType;
|
||||
export let worker_proxy: WorkerProxy | undefined = undefined;
|
||||
export let fetch_implementation: typeof fetch = fetch;
|
||||
export let EventSource_factory: (url: URL) => EventSource = (url) =>
|
||||
@ -216,8 +215,7 @@
|
||||
{info}
|
||||
{eager}
|
||||
{mount_css}
|
||||
{client}
|
||||
{upload_files}
|
||||
{Client}
|
||||
bind:worker_proxy
|
||||
{fetch_implementation}
|
||||
{EventSource_factory}
|
||||
|
@ -4,7 +4,8 @@ import "@gradio/theme/src/pollen.css";
|
||||
import "@gradio/theme/src/typography.css";
|
||||
import type { SvelteComponent } from "svelte";
|
||||
import { WorkerProxy, type WorkerProxyOptions } from "@gradio/wasm";
|
||||
import { api_factory } from "@gradio/client";
|
||||
import { Client, upload_files } from "@gradio/client";
|
||||
import type { UploadResponse } from "@gradio/client";
|
||||
import { wasm_proxied_fetch } from "./fetch";
|
||||
import { wasm_proxied_EventSource_factory } from "./sse";
|
||||
import { wasm_proxied_mount_css, mount_prebuilt_css } from "./css";
|
||||
@ -132,10 +133,20 @@ export function create(options: Options): GradioAppController {
|
||||
const EventSource_factory = (url: URL): EventSource => {
|
||||
return wasm_proxied_EventSource_factory(worker_proxy, url);
|
||||
};
|
||||
const { client, upload_files } = api_factory(
|
||||
overridden_fetch,
|
||||
EventSource_factory
|
||||
);
|
||||
|
||||
class LiteClient extends Client {
|
||||
fetch_implementation(
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit
|
||||
): Promise<Response> {
|
||||
return wasm_proxied_fetch(worker_proxy, input, init);
|
||||
}
|
||||
|
||||
eventSource_factory(url: URL): EventSource {
|
||||
return wasm_proxied_EventSource_factory(worker_proxy, url);
|
||||
}
|
||||
}
|
||||
|
||||
const overridden_mount_css: typeof mount_css = async (url, target) => {
|
||||
return wasm_proxied_mount_css(worker_proxy, url, target);
|
||||
};
|
||||
@ -210,8 +221,7 @@ export function create(options: Options): GradioAppController {
|
||||
app_mode: options.appMode,
|
||||
// For Wasm mode
|
||||
worker_proxy,
|
||||
client,
|
||||
upload_files,
|
||||
Client: LiteClient,
|
||||
mount_css: overridden_mount_css,
|
||||
fetch_implementation: overridden_fetch,
|
||||
EventSource_factory,
|
||||
|
@ -2,7 +2,7 @@ import "@gradio/theme/src/reset.css";
|
||||
import "@gradio/theme/src/global.css";
|
||||
import "@gradio/theme/src/pollen.css";
|
||||
import "@gradio/theme/src/typography.css";
|
||||
import { client, upload_files } from "@gradio/client";
|
||||
import { Client } from "@gradio/client";
|
||||
import { mount_css } from "./css";
|
||||
import type Index from "./Index.svelte";
|
||||
|
||||
@ -126,8 +126,7 @@ function create_custom_element(): void {
|
||||
autoscroll: this.autoscroll === "true" ? true : false,
|
||||
control_page_title: this.control_page_title === "true" ? true : false,
|
||||
// injectables
|
||||
client,
|
||||
upload_files,
|
||||
Client,
|
||||
// for gradio docs
|
||||
// TODO: Remove -- i think this is just for autoscroll behavhiour, app vs embeds
|
||||
app_mode: window.__gradio_mode__ === "app"
|
||||
@ -195,8 +194,7 @@ function create_custom_element(): void {
|
||||
control_page_title:
|
||||
this.control_page_title === "true" ? true : false,
|
||||
// injectables
|
||||
client,
|
||||
upload_files,
|
||||
Client,
|
||||
// for gradio docs
|
||||
// TODO: Remove -- i think this is just for autoscroll behavhiour, app vs embeds
|
||||
app_mode: window.__gradio_mode__ === "app"
|
||||
|
@ -38,8 +38,8 @@ export interface DependencyTypes {
|
||||
export interface Payload {
|
||||
fn_index: number;
|
||||
data: unknown[];
|
||||
event_data: unknown | null;
|
||||
trigger_id: number | null;
|
||||
event_data?: unknown | null;
|
||||
trigger_id?: number | null;
|
||||
}
|
||||
|
||||
/** A dependency as received from the backend */
|
||||
|
228
pnpm-lock.yaml
generated
228
pnpm-lock.yaml
generated
@ -218,17 +218,20 @@ importers:
|
||||
dependencies:
|
||||
bufferutil:
|
||||
specifier: ^4.0.7
|
||||
version: 4.0.7
|
||||
version: 4.0.8
|
||||
semiver:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
typescript:
|
||||
specifier: ^5.0.0
|
||||
version: 5.4.2
|
||||
ws:
|
||||
specifier: ^8.13.0
|
||||
version: 8.13.0(bufferutil@4.0.7)
|
||||
version: 8.16.0(bufferutil@4.0.8)
|
||||
devDependencies:
|
||||
'@types/ws':
|
||||
specifier: ^8.5.4
|
||||
version: 8.5.4
|
||||
specifier: ^8.5.10
|
||||
version: 8.5.10
|
||||
esbuild:
|
||||
specifier: ^0.20.0
|
||||
version: 0.20.0
|
||||
@ -256,7 +259,7 @@ importers:
|
||||
version: 2.0.0(@sveltejs/kit@1.26.0)
|
||||
'@sveltejs/kit':
|
||||
specifier: ^1.5.0
|
||||
version: 1.26.0(svelte@4.2.12)(vite@4.5.3)
|
||||
version: 1.26.0(svelte@4.2.12)(vite@4.5.2)
|
||||
prettier:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@ -299,7 +302,7 @@ importers:
|
||||
version: 2.0.2(@sveltejs/kit@1.27.6)
|
||||
'@sveltejs/kit':
|
||||
specifier: ^1.27.6
|
||||
version: 1.27.6(svelte@4.2.12)(vite@4.5.3)
|
||||
version: 1.27.6(svelte@4.2.12)(vite@4.5.2)
|
||||
'@tailwindcss/forms':
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0(tailwindcss@3.1.6)
|
||||
@ -811,7 +814,7 @@ importers:
|
||||
version: 3.0.3
|
||||
katex:
|
||||
specifier: ^0.16.7
|
||||
version: 0.16.10
|
||||
version: 0.16.7
|
||||
marked:
|
||||
specifier: ^12.0.0
|
||||
version: 12.0.0
|
||||
@ -1143,7 +1146,7 @@ importers:
|
||||
version: 2.0.0
|
||||
katex:
|
||||
specifier: ^0.16.7
|
||||
version: 0.16.10
|
||||
version: 0.16.7
|
||||
marked:
|
||||
specifier: ^12.0.0
|
||||
version: 12.0.0
|
||||
@ -1294,7 +1297,7 @@ importers:
|
||||
version: 5.0.1(rollup@3.28.0)
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^2.5.2
|
||||
version: 2.5.2(svelte@4.2.12)(vite@4.5.3)
|
||||
version: 2.5.2(svelte@4.2.12)(vite@4.5.2)
|
||||
'@types/which':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@ -1327,7 +1330,7 @@ importers:
|
||||
version: 3.34.0
|
||||
sugarss:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1(postcss@8.4.38)
|
||||
version: 4.0.1(postcss@8.4.35)
|
||||
which:
|
||||
specifier: 4.0.0
|
||||
version: 4.0.0
|
||||
@ -1346,7 +1349,7 @@ importers:
|
||||
version: 15.1.0(rollup@3.28.0)
|
||||
'@rollup/plugin-typescript':
|
||||
specifier: ^11.1.2
|
||||
version: 11.1.2(rollup@3.28.0)(typescript@5.4.3)
|
||||
version: 11.1.2(rollup@3.28.0)(typescript@5.4.2)
|
||||
rollup:
|
||||
specifier: ^3.28.0
|
||||
version: 3.28.0
|
||||
@ -1637,13 +1640,6 @@ packages:
|
||||
'@jridgewell/gen-mapping': 0.3.3
|
||||
'@jridgewell/trace-mapping': 0.3.20
|
||||
|
||||
/@ampproject/remapping@2.3.0:
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
/@aw-web-design/x-default-browser@1.4.126:
|
||||
resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==}
|
||||
hasBin: true
|
||||
@ -1735,7 +1731,7 @@ packages:
|
||||
resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.3
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-builder-binary-assignment-operator-visitor@7.22.15:
|
||||
@ -1819,8 +1815,8 @@ packages:
|
||||
resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/template': 7.22.15
|
||||
'@babel/types': 7.23.3
|
||||
'@babel/template': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
|
||||
/@babel/helper-hoist-variables@7.22.5:
|
||||
resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
|
||||
@ -1872,7 +1868,7 @@ packages:
|
||||
resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.3
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-plugin-utils@7.22.5:
|
||||
@ -1921,7 +1917,7 @@ packages:
|
||||
resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.3
|
||||
'@babel/types': 7.24.0
|
||||
|
||||
/@babel/helper-string-parser@7.22.5:
|
||||
resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==}
|
||||
@ -1930,7 +1926,6 @@ packages:
|
||||
/@babel/helper-string-parser@7.24.1:
|
||||
resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/helper-validator-identifier@7.22.20:
|
||||
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
|
||||
@ -1997,7 +1992,6 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.3):
|
||||
resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==}
|
||||
@ -2963,7 +2957,6 @@ packages:
|
||||
'@babel/code-frame': 7.24.1
|
||||
'@babel/parser': 7.24.1
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/traverse@7.23.3:
|
||||
resolution: {integrity: sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==}
|
||||
@ -3015,7 +3008,6 @@ packages:
|
||||
'@babel/helper-string-parser': 7.24.1
|
||||
'@babel/helper-validator-identifier': 7.22.20
|
||||
to-fast-properties: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@bundled-es-modules/cookie@2.0.0:
|
||||
resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==}
|
||||
@ -4451,6 +4443,7 @@ packages:
|
||||
'@jridgewell/set-array': 1.2.1
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
dev: true
|
||||
|
||||
/@jridgewell/resolve-uri@3.1.1:
|
||||
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
|
||||
@ -4467,6 +4460,7 @@ packages:
|
||||
/@jridgewell/set-array@1.2.1:
|
||||
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/sourcemap-codec@1.4.15:
|
||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||
@ -5057,7 +5051,7 @@ packages:
|
||||
dependencies:
|
||||
playwright: 1.39.0
|
||||
playwright-core: 1.39.0
|
||||
vite: 4.5.0(@types/node@20.3.2)
|
||||
vite: 4.5.2(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
@ -5190,7 +5184,7 @@ packages:
|
||||
sucrase: 3.34.0
|
||||
dev: false
|
||||
|
||||
/@rollup/plugin-typescript@11.1.2(rollup@3.28.0)(typescript@5.4.3):
|
||||
/@rollup/plugin-typescript@11.1.2(rollup@3.28.0)(typescript@5.4.2):
|
||||
resolution: {integrity: sha512-0ghSOCMcA7fl1JM+0gYRf+Q/HWyg+zg7/gDSc+fRLmlJWcW5K1I+CLRzaRhXf4Y3DRyPnnDo4M2ktw+a6JcDEg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
@ -5206,7 +5200,7 @@ packages:
|
||||
'@rollup/pluginutils': 5.0.5(rollup@3.28.0)
|
||||
resolve: 1.22.8
|
||||
rollup: 3.28.0
|
||||
typescript: 5.4.3
|
||||
typescript: 5.4.2
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@4.2.1:
|
||||
@ -5822,7 +5816,7 @@ packages:
|
||||
util: 0.12.5
|
||||
util-deprecate: 1.0.2
|
||||
watchpack: 2.4.0
|
||||
ws: 8.16.0
|
||||
ws: 8.16.0(bufferutil@4.0.8)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- encoding
|
||||
@ -6106,7 +6100,7 @@ packages:
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^1.0.0
|
||||
dependencies:
|
||||
'@sveltejs/kit': 1.26.0(svelte@4.2.12)(vite@4.5.3)
|
||||
'@sveltejs/kit': 1.26.0(svelte@4.2.12)(vite@4.5.2)
|
||||
import-meta-resolve: 2.2.2
|
||||
dev: true
|
||||
|
||||
@ -6115,7 +6109,7 @@ packages:
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^1.0.0
|
||||
dependencies:
|
||||
'@sveltejs/kit': 1.27.6(svelte@4.2.12)(vite@4.5.3)
|
||||
'@sveltejs/kit': 1.27.6(svelte@4.2.12)(vite@4.5.2)
|
||||
import-meta-resolve: 2.2.2
|
||||
dev: true
|
||||
|
||||
@ -6124,7 +6118,7 @@ packages:
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^1.5.0
|
||||
dependencies:
|
||||
'@sveltejs/kit': 1.27.6(svelte@4.2.12)(vite@4.5.3)
|
||||
'@sveltejs/kit': 1.27.6(svelte@4.2.12)(vite@4.5.2)
|
||||
dev: true
|
||||
|
||||
/@sveltejs/adapter-vercel@3.0.3(@sveltejs/kit@1.27.6):
|
||||
@ -6132,7 +6126,7 @@ packages:
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^1.5.0
|
||||
dependencies:
|
||||
'@sveltejs/kit': 1.27.6(svelte@4.2.12)(vite@4.5.3)
|
||||
'@sveltejs/kit': 1.27.6(svelte@4.2.12)(vite@4.5.2)
|
||||
'@vercel/nft': 0.23.1
|
||||
esbuild: 0.18.20
|
||||
transitivePeerDependencies:
|
||||
@ -6140,7 +6134,7 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@sveltejs/kit@1.26.0(svelte@4.2.12)(vite@4.5.3):
|
||||
/@sveltejs/kit@1.26.0(svelte@4.2.12)(vite@4.5.2):
|
||||
resolution: {integrity: sha512-CV/AlTziC05yrz7UjVqEd0pH6+2dnrbmcnHGr2d3jXtmOgzNnlDkXtX8g3BfJ6nntsPD+0jtS2PzhvRHblRz4A==}
|
||||
engines: {node: ^16.14 || >=18}
|
||||
hasBin: true
|
||||
@ -6149,7 +6143,7 @@ packages:
|
||||
svelte: ^3.54.0 || ^4.0.0-next.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 2.5.2(svelte@4.2.12)(vite@4.5.3)
|
||||
'@sveltejs/vite-plugin-svelte': 2.5.2(svelte@4.2.12)(vite@4.5.2)
|
||||
'@types/cookie': 0.5.4
|
||||
cookie: 0.5.0
|
||||
devalue: 4.3.2
|
||||
@ -6163,12 +6157,12 @@ packages:
|
||||
svelte: 4.2.12
|
||||
tiny-glob: 0.2.9
|
||||
undici: 5.26.5
|
||||
vite: 4.5.3(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
vite: 4.5.2(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@sveltejs/kit@1.27.6(svelte@4.2.12)(vite@4.5.3):
|
||||
/@sveltejs/kit@1.27.6(svelte@4.2.12)(vite@4.5.2):
|
||||
resolution: {integrity: sha512-GsjTkMbKzXdbeRg0tk8S7HNShQ4879ftRr0ZHaZfjbig1xQwG57Bvcm9U9/mpLJtCapLbLWUnygKrgcLISLC8A==}
|
||||
engines: {node: ^16.14 || >=18}
|
||||
hasBin: true
|
||||
@ -6177,7 +6171,7 @@ packages:
|
||||
svelte: ^3.54.0 || ^4.0.0-next.0 || ^5.0.0-next.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 2.5.2(svelte@4.2.12)(vite@4.5.3)
|
||||
'@sveltejs/vite-plugin-svelte': 2.5.2(svelte@4.2.12)(vite@4.5.2)
|
||||
'@types/cookie': 0.5.4
|
||||
cookie: 0.5.0
|
||||
devalue: 4.3.2
|
||||
@ -6191,11 +6185,11 @@ packages:
|
||||
svelte: 4.2.12
|
||||
tiny-glob: 0.2.9
|
||||
undici: 5.26.5
|
||||
vite: 4.5.3(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
vite: 4.5.2(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.5.2)(svelte@4.2.12)(vite@4.5.3):
|
||||
/@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.5.2)(svelte@4.2.12)(vite@4.5.2):
|
||||
resolution: {integrity: sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==}
|
||||
engines: {node: ^14.18.0 || >= 16}
|
||||
peerDependencies:
|
||||
@ -6203,10 +6197,10 @@ packages:
|
||||
svelte: ^3.54.0 || ^4.0.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 2.5.2(svelte@4.2.12)(vite@4.5.3)
|
||||
'@sveltejs/vite-plugin-svelte': 2.5.2(svelte@4.2.12)(vite@4.5.2)
|
||||
debug: 4.3.4
|
||||
svelte: 4.2.12
|
||||
vite: 4.5.3(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
vite: 4.5.2(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -6225,22 +6219,22 @@ packages:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/@sveltejs/vite-plugin-svelte@2.5.2(svelte@4.2.12)(vite@4.5.3):
|
||||
/@sveltejs/vite-plugin-svelte@2.5.2(svelte@4.2.12)(vite@4.5.2):
|
||||
resolution: {integrity: sha512-Dfy0Rbl+IctOVfJvWGxrX/3m6vxPLH8o0x+8FA5QEyMUQMo4kGOVIojjryU7YomBAexOTAuYf1RT7809yDziaA==}
|
||||
engines: {node: ^14.18.0 || >= 16}
|
||||
peerDependencies:
|
||||
svelte: ^3.54.0 || ^4.0.0 || ^5.0.0-next.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 1.0.4(@sveltejs/vite-plugin-svelte@2.5.2)(svelte@4.2.12)(vite@4.5.3)
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 1.0.4(@sveltejs/vite-plugin-svelte@2.5.2)(svelte@4.2.12)(vite@4.5.2)
|
||||
debug: 4.3.4
|
||||
deepmerge: 4.3.1
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.5
|
||||
svelte: 4.2.12
|
||||
svelte-hmr: 0.15.3(svelte@4.2.12)
|
||||
vite: 4.5.3(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
vitefu: 0.2.5(vite@4.5.3)
|
||||
vite: 4.5.2(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
vitefu: 0.2.5(vite@4.5.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -6590,7 +6584,6 @@ packages:
|
||||
resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
dev: false
|
||||
|
||||
/@types/node@20.3.2:
|
||||
resolution: {integrity: sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==}
|
||||
@ -6722,10 +6715,10 @@ packages:
|
||||
resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==}
|
||||
dev: false
|
||||
|
||||
/@types/ws@8.5.4:
|
||||
resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
|
||||
/@types/ws@8.5.10:
|
||||
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
|
||||
dependencies:
|
||||
'@types/node': 20.9.0
|
||||
'@types/node': 20.11.30
|
||||
dev: true
|
||||
|
||||
/@types/yargs-parser@21.0.3:
|
||||
@ -7533,13 +7526,12 @@ packages:
|
||||
ieee754: 1.2.1
|
||||
dev: true
|
||||
|
||||
/bufferutil@4.0.7:
|
||||
resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==}
|
||||
/bufferutil@4.0.8:
|
||||
resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
|
||||
engines: {node: '>=6.14.2'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
node-gyp-build: 4.6.1
|
||||
dev: false
|
||||
|
||||
/builtin-modules@3.3.0:
|
||||
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
||||
@ -10734,7 +10726,7 @@ packages:
|
||||
whatwg-encoding: 3.1.1
|
||||
whatwg-mimetype: 4.0.0
|
||||
whatwg-url: 14.0.0
|
||||
ws: 8.16.0
|
||||
ws: 8.16.0(bufferutil@4.0.8)
|
||||
xml-name-validator: 5.0.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
@ -10801,8 +10793,8 @@ packages:
|
||||
promise: 7.3.1
|
||||
dev: false
|
||||
|
||||
/katex@0.16.10:
|
||||
resolution: {integrity: sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==}
|
||||
/katex@0.16.7:
|
||||
resolution: {integrity: sha512-Xk9C6oGKRwJTfqfIbtr0Kes9OSv6IFsuhFGc7tW4urlpMJtuh+7YhzU6YEG9n8gmWKcMAFzkp7nr+r69kV0zrA==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
commander: 8.3.0
|
||||
@ -11096,8 +11088,8 @@ packages:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
/magic-string@0.30.8:
|
||||
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
|
||||
/magic-string@0.30.9:
|
||||
resolution: {integrity: sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
@ -11533,7 +11525,6 @@ packages:
|
||||
/node-gyp-build@4.6.1:
|
||||
resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/node-html-parser@6.0.0:
|
||||
resolution: {integrity: sha512-o4vS5Jm7ZdV5WN4/jHmCEVJOpm4exLCeXOcZnNzXi0BGv0AS8FsGwyQ4k0Ujmui1NMQs6qsTy+amjjpr9rmz4Q==}
|
||||
@ -12157,14 +12148,6 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
|
||||
/postcss@8.4.38:
|
||||
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
nanoid: 3.3.7
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.2.0
|
||||
|
||||
/preferred-pm@3.1.2:
|
||||
resolution: {integrity: sha512-nk7dKrcW8hfCZ4H6klWcdRknBOXWzNQByJ0oJyX97BOupsYD+FzLS4hflgEu/uPUEHZCuRfMxzCBsuWd7OzT8Q==}
|
||||
engines: {node: '>=10'}
|
||||
@ -12427,7 +12410,7 @@ packages:
|
||||
resolution: {integrity: sha512-RagtX3TfV2M0QAfThG2SMvwE31ikqAFDUXc5/4xhppEoVf4VbL7L0kbKOIdSNg7MbVsHELVxftk66WvT926GpA==}
|
||||
dependencies:
|
||||
base-64: 1.0.0
|
||||
ws: 8.16.0
|
||||
ws: 8.16.0(bufferutil@4.0.8)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
@ -13109,10 +13092,6 @@ packages:
|
||||
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
/source-map-js@1.2.0:
|
||||
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
/source-map-support@0.5.21:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
dependencies:
|
||||
@ -13360,13 +13339,13 @@ packages:
|
||||
ts-interface-checker: 0.1.13
|
||||
dev: false
|
||||
|
||||
/sugarss@4.0.1(postcss@8.4.38):
|
||||
/sugarss@4.0.1(postcss@8.4.35):
|
||||
resolution: {integrity: sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==}
|
||||
engines: {node: '>=12.0'}
|
||||
peerDependencies:
|
||||
postcss: ^8.3.3
|
||||
dependencies:
|
||||
postcss: 8.4.38
|
||||
postcss: 8.4.35
|
||||
|
||||
/supports-color@5.5.0:
|
||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||
@ -13397,8 +13376,8 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
sade: 1.8.1
|
||||
svelte: 4.2.12
|
||||
svelte-preprocess: 5.0.4(@babel/core@7.23.3)(postcss@8.4.31)(svelte@4.2.12)(typescript@5.2.2)
|
||||
typescript: 5.2.2
|
||||
svelte-preprocess: 5.0.4(@babel/core@7.23.3)(postcss@8.4.31)(svelte@4.2.12)(typescript@5.4.2)
|
||||
typescript: 5.4.2
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- coffeescript
|
||||
@ -13424,8 +13403,8 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
sade: 1.8.1
|
||||
svelte: 4.2.2
|
||||
svelte-preprocess: 5.1.3(@babel/core@7.23.3)(postcss@8.4.31)(svelte@4.2.2)(typescript@5.2.2)
|
||||
typescript: 5.2.2
|
||||
svelte-preprocess: 5.1.3(@babel/core@7.23.3)(postcss@8.4.31)(svelte@4.2.2)(typescript@5.4.2)
|
||||
typescript: 5.4.2
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- coffeescript
|
||||
@ -13513,7 +13492,7 @@ packages:
|
||||
tiny-glob: 0.2.9
|
||||
dev: false
|
||||
|
||||
/svelte-preprocess@5.0.4(@babel/core@7.23.3)(postcss@8.4.31)(svelte@4.2.12)(typescript@5.2.2):
|
||||
/svelte-preprocess@5.0.4(@babel/core@7.23.3)(postcss@8.4.31)(svelte@4.2.12)(typescript@5.4.2):
|
||||
resolution: {integrity: sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==}
|
||||
engines: {node: '>= 14.10.0'}
|
||||
requiresBuild: true
|
||||
@ -13559,7 +13538,7 @@ packages:
|
||||
sorcery: 0.11.0
|
||||
strip-indent: 3.0.0
|
||||
svelte: 4.2.12
|
||||
typescript: 5.2.2
|
||||
typescript: 5.4.2
|
||||
dev: true
|
||||
|
||||
/svelte-preprocess@5.0.4(@babel/core@7.23.3)(postcss@8.4.31)(svelte@4.2.2)(typescript@5.0.2):
|
||||
@ -13660,7 +13639,7 @@ packages:
|
||||
typescript: 5.0.2
|
||||
dev: true
|
||||
|
||||
/svelte-preprocess@5.1.3(@babel/core@7.23.3)(postcss@8.4.31)(svelte@4.2.2)(typescript@5.2.2):
|
||||
/svelte-preprocess@5.1.3(@babel/core@7.23.3)(postcss@8.4.31)(svelte@4.2.2)(typescript@5.4.2):
|
||||
resolution: {integrity: sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==}
|
||||
engines: {node: '>= 16.0.0', pnpm: ^8.0.0}
|
||||
requiresBuild: true
|
||||
@ -13706,7 +13685,7 @@ packages:
|
||||
sorcery: 0.11.0
|
||||
strip-indent: 3.0.0
|
||||
svelte: 4.2.2
|
||||
typescript: 5.2.2
|
||||
typescript: 5.4.2
|
||||
dev: false
|
||||
|
||||
/svelte-range-slider-pips@2.0.1:
|
||||
@ -13748,7 +13727,7 @@ packages:
|
||||
resolution: {integrity: sha512-d8+wsh5TfPwqVzbm4/HCXC783/KPHV60NvwitJnyTA5lWn1elhXMNWhXGCJ7PwPa8qFUnyJNIyuIRt2mT0WMug==}
|
||||
engines: {node: '>=16'}
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@ampproject/remapping': 2.2.1
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
'@types/estree': 1.0.5
|
||||
@ -13760,7 +13739,7 @@ packages:
|
||||
estree-walker: 3.0.3
|
||||
is-reference: 3.0.2
|
||||
locate-character: 3.0.0
|
||||
magic-string: 0.30.8
|
||||
magic-string: 0.30.9
|
||||
periscopic: 3.1.0
|
||||
|
||||
/svelte@4.2.2:
|
||||
@ -14195,17 +14174,11 @@ packages:
|
||||
engines: {node: '>=12.20'}
|
||||
hasBin: true
|
||||
|
||||
/typescript@5.2.2:
|
||||
resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==}
|
||||
/typescript@5.4.2:
|
||||
resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
/typescript@5.4.3:
|
||||
resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/ufo@1.3.1:
|
||||
resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==}
|
||||
|
||||
@ -14878,44 +14851,8 @@ packages:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
/vite@4.5.0(@types/node@20.3.2):
|
||||
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': '>= 14'
|
||||
less: '*'
|
||||
lightningcss: ^1.21.0
|
||||
sass: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.4.0
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 20.3.2
|
||||
esbuild: 0.18.20
|
||||
postcss: 8.4.31
|
||||
rollup: 3.29.4
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: false
|
||||
|
||||
/vite@4.5.3(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1):
|
||||
resolution: {integrity: sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==}
|
||||
/vite@4.5.2(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1):
|
||||
resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@ -14945,11 +14882,11 @@ packages:
|
||||
'@types/node': 20.3.2
|
||||
esbuild: 0.18.20
|
||||
lightningcss: 1.21.7
|
||||
postcss: 8.4.38
|
||||
postcss: 8.4.31
|
||||
rollup: 3.29.4
|
||||
sass: 1.66.1
|
||||
stylus: 0.63.0
|
||||
sugarss: 4.0.1(postcss@8.4.38)
|
||||
sugarss: 4.0.1(postcss@8.4.35)
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
@ -14998,7 +14935,7 @@ packages:
|
||||
dependencies:
|
||||
vite: 4.3.9(@types/node@20.3.2)
|
||||
|
||||
/vitefu@0.2.5(vite@4.5.3):
|
||||
/vitefu@0.2.5(vite@4.5.2):
|
||||
resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==}
|
||||
peerDependencies:
|
||||
vite: ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||
@ -15006,7 +14943,7 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 4.5.3(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
vite: 4.5.2(@types/node@20.3.2)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
|
||||
/vitest@1.4.0(@types/node@20.3.2)(happy-dom@14.0.0)(jsdom@24.0.0):
|
||||
resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==}
|
||||
@ -15281,22 +15218,7 @@ packages:
|
||||
signal-exit: 3.0.7
|
||||
dev: true
|
||||
|
||||
/ws@8.13.0(bufferutil@4.0.7):
|
||||
resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dependencies:
|
||||
bufferutil: 4.0.7
|
||||
dev: false
|
||||
|
||||
/ws@8.16.0:
|
||||
/ws@8.16.0(bufferutil@4.0.8):
|
||||
resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
@ -15307,6 +15229,8 @@ packages:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dependencies:
|
||||
bufferutil: 4.0.8
|
||||
|
||||
/xml-name-validator@5.0.0:
|
||||
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
|
||||
|
Loading…
x
Reference in New Issue
Block a user