mirror of
https://github.com/gradio-app/gradio.git
synced 2025-03-19 12:00:39 +08:00
3712 js client (#3899)
This commit is contained in:
parent
fcf744a4a9
commit
963c2d26ee
@ -11,6 +11,7 @@
|
||||
**/.venv/**
|
||||
**/.github/**
|
||||
**/guides/**
|
||||
**/test/**
|
||||
**/.mypy_cache/**
|
||||
**/*.md
|
||||
**/js/_space-test/**
|
||||
js/app/test/gradio_cached_examples/**
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -58,4 +58,5 @@ gradio/frpc_*
|
||||
# js
|
||||
node_modules
|
||||
public/build/
|
||||
test-results
|
||||
test-results
|
||||
client/js/test.js
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -11,6 +11,9 @@
|
||||
// Add support for autocomplete in other file types
|
||||
"cssvar.extensions": ["js", "css", "html", "jsx", "tsx", "svelte"],
|
||||
"python.analysis.extraPaths": ["./gradio/themes/utils"],
|
||||
"prettier.useTabs": true,
|
||||
"editor.formatOnSave": true,
|
||||
"svelte.plugin.svelte.format.enable": true,
|
||||
"prettier.configPath": ".config/.prettierrc.json",
|
||||
"prettier.ignorePath": ".config/.prettierignore",
|
||||
"python.testing.pytestArgs": ["."],
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
## New Features:
|
||||
|
||||
- Add JS client code snippets to use via api page by [@aliabd](https://github.com/aliabd) in [PR 3927](https://github.com/gradio-app/gradio/pull/3927).
|
||||
No changes to highlight.
|
||||
|
||||
## Bug Fixes:
|
||||
@ -10,7 +11,7 @@ No changes to highlight.
|
||||
|
||||
## Other Changes:
|
||||
|
||||
No changes to highlight.
|
||||
- Update the js client by [@pngwn](https://github.com/pngwn) in [PR 3899](https://github.com/gradio-app/gradio/pull/3899)
|
||||
|
||||
## Breaking Changes:
|
||||
|
||||
|
@ -1,52 +1,339 @@
|
||||
# `@gradio/client`
|
||||
## JavaScript Client Library
|
||||
|
||||
A javascript client to call Gradio APIs.
|
||||
A javascript (and typescript) client to call Gradio APIs.
|
||||
|
||||
**install it**
|
||||
## Installation
|
||||
|
||||
The Gradio JavaScript client is available on npm as `@gradio/client`. You can install it as below:
|
||||
|
||||
```sh
|
||||
pnpm add @gradio/client
|
||||
npm i -D @gradio/client
|
||||
```
|
||||
|
||||
**usage**
|
||||
## Usage
|
||||
|
||||
The JavaScript Gradio Client exposes two named imports, `client` and `duplicate`.
|
||||
|
||||
### `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 simplest example looks like this:
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
|
||||
const app = client();
|
||||
const app = await client("user/space-name");
|
||||
const result = await app.predict("/predict");
|
||||
```
|
||||
|
||||
const prediction = app.predict(endpoint, payload);
|
||||
This function accepts two arguments: `source` and `options`:
|
||||
|
||||
// listen for predictions
|
||||
prediction.on("data", (event: { data: Array<unknown>; type: "data" }) => {});
|
||||
#### `source`
|
||||
|
||||
// listen for status updates
|
||||
prediction.on("status", (event: { data: Status; type: "data" }) => {});
|
||||
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:
|
||||
|
||||
interface Status {
|
||||
status: "pending" | "error" | "complete" | "generating";
|
||||
size: number;
|
||||
position?: number;
|
||||
eta?: number;
|
||||
message?: string;
|
||||
progress?: Array<{
|
||||
progress: number | null;
|
||||
index: number | null;
|
||||
length: number | null;
|
||||
unit: string | null;
|
||||
desc: string | null;
|
||||
}>;
|
||||
```ts
|
||||
client("user/space-name");
|
||||
```
|
||||
|
||||
#### `options`
|
||||
|
||||
The options object can optionally be passed a second parameter. This object has two properties, `hf_token` and `status_callback`.
|
||||
|
||||
##### `hf_token`
|
||||
|
||||
This should be a Hugging Face personal access token and is required if you wish to make calls to a private gradio api. This option is optional and should be a string starting with `"hf_"`.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("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.
|
||||
|
||||
**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";
|
||||
|
||||
const app = await client("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),
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
interface SpaceStatusNormal {
|
||||
status: "sleeping" | "running" | "building" | "error" | "stopped";
|
||||
detail:
|
||||
| "SLEEPING"
|
||||
| "RUNNING"
|
||||
| "RUNNING_BUILDING"
|
||||
| "BUILDING"
|
||||
| "NOT_FOUND";
|
||||
load_status: "pending" | "error" | "complete" | "generating";
|
||||
message: string;
|
||||
}
|
||||
|
||||
// stop listening
|
||||
prediction.off("data");
|
||||
interface SpaceStatusError {
|
||||
status: "space_error";
|
||||
detail: "NO_APP_FILE" | "CONFIG_ERROR" | "BUILD_ERROR" | "RUNTIME_ERROR";
|
||||
load_status: "error";
|
||||
message: string;
|
||||
discussions_enabled: boolean;
|
||||
|
||||
// cancel a prediction if it is a generator
|
||||
prediction.cancel();
|
||||
|
||||
// chainable
|
||||
const prediction_two = app
|
||||
.predict(endpoint, payload)
|
||||
.on("data", data_callback)
|
||||
.on("status", status_callback);
|
||||
type SpaceStatus = SpaceStatusNormal | SpaceStatusError;
|
||||
```
|
||||
|
||||
The gradio client returns an object with a number of methods and properties:
|
||||
|
||||
#### `predict`
|
||||
|
||||
The `predict` method allows you to call an api endpoint and get a prediction result:
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const result = await app.predict("/predict");
|
||||
```
|
||||
|
||||
`predict` accepts two parameters, `endpoint` and `payload`. It returns a promise that resolves to the prediction result.
|
||||
|
||||
##### `endpoint`
|
||||
|
||||
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";
|
||||
|
||||
const app = await client("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.
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const result = await app.predict("/predict", [1, "Hello", "friends"]);
|
||||
```
|
||||
|
||||
#### `submit`
|
||||
|
||||
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";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const submission = app.submit("/predict", payload);
|
||||
```
|
||||
|
||||
The `submit` method accepts the same [`endpoint`](#endpoint) and [`payload`](#payload) arguments as `predict`.
|
||||
|
||||
The `submit` method does not return a promise and should not be awaited, instead it returns an object with a `on`, `off`, and `cancel` methods.
|
||||
|
||||
##### `on`
|
||||
|
||||
The `on` method allows you to subscribe to events related to the submitted API request. There are two types of event that can be subscribed to: `"data"` updates and `"status"` updates.
|
||||
|
||||
`"data"` updates are issued when the API computes a value, the callback provided as the second argument will be called when such a value is sent to the client. The shape of the data depends on the way the API itself is constructed. This event may fire more than once if that endpoint supports emmitting new values over time.
|
||||
|
||||
`"status` updates are issued when the status of a request changes. This information allows you to offer feedback to users when the queue position of the request changes, or when the request changes from queued to processing.
|
||||
|
||||
The status payload look like this:
|
||||
|
||||
```ts
|
||||
interface Status {
|
||||
queue: boolean;
|
||||
code?: string;
|
||||
success?: boolean;
|
||||
stage: "pending" | "error" | "complete" | "generating";
|
||||
size?: number;
|
||||
position?: number;
|
||||
eta?: number;
|
||||
message?: string;
|
||||
progress_data?: Array<{
|
||||
progress: number | null;
|
||||
index: number | null;
|
||||
length: number | null;
|
||||
unit: string | null;
|
||||
desc: string | null;
|
||||
}>;
|
||||
time?: Date;
|
||||
}
|
||||
```
|
||||
|
||||
Usage of these subscribe callback looks like this:
|
||||
|
||||
```ts
|
||||
import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const submission = app
|
||||
.submit("/predict", payload)
|
||||
.on("data", (data) => console.log(data))
|
||||
.on("status", (status: Status) => console.log(status));
|
||||
```
|
||||
|
||||
##### `off`
|
||||
|
||||
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";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const handle_data = (data) => console.log(data);
|
||||
|
||||
const submission = app.submit("/predict", payload).on("data", handle_data);
|
||||
|
||||
// later
|
||||
submission.off("/predict", handle_data);
|
||||
```
|
||||
|
||||
##### `destroy`
|
||||
|
||||
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 wnat to unsubscribe use the `off` method.
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const handle_data = (data) => console.log(data);
|
||||
|
||||
const submission = app.submit("/predict", payload).on("data", handle_data);
|
||||
|
||||
// later
|
||||
submission.destroy();
|
||||
```
|
||||
|
||||
##### `cancel`
|
||||
|
||||
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";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const submission = app
|
||||
.submit("/predict", payload)
|
||||
.on("data", (data) => console.log(data));
|
||||
|
||||
// later
|
||||
|
||||
submission.cancel();
|
||||
```
|
||||
|
||||
#### `view_api`
|
||||
|
||||
The `view_api` method provides details about the API you are connected too. 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";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const api_info = await app.view_api();
|
||||
|
||||
console.log(api_info);
|
||||
```
|
||||
|
||||
#### `config`
|
||||
|
||||
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";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
console.log(app.config);
|
||||
```
|
||||
|
||||
### `duplicate`
|
||||
|
||||
The duplicate function will attempt to duplicate the space that is referenced and return an instance of `client` connected to that space. If the space has already been duplicated then it will not create a new duplicate and will instead connect to the existing duplicated space. The huggingface token that is passed in will dictate the user under which the space is created.
|
||||
|
||||
`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";
|
||||
|
||||
const app = await duplicate("user/space-name", {
|
||||
hf_token: "hf_...",
|
||||
});
|
||||
```
|
||||
|
||||
This function accepts two arguments: `source` and `options`:
|
||||
|
||||
#### `source`
|
||||
|
||||
The space to duplicate and connect to. [See `client`'s `source` parameter](#source).
|
||||
|
||||
#### `options`
|
||||
|
||||
Accepts all options that `client` accepts, except `hf_token` is required. [See `client`'s `options` parameter](#source).
|
||||
|
||||
`duplicate` also accepts one additional `options` property.
|
||||
|
||||
##### `private`
|
||||
|
||||
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";
|
||||
|
||||
const app = await duplicate("user/space-name", {
|
||||
hf_token: "hf_...",
|
||||
private: true,
|
||||
});
|
||||
```
|
||||
|
||||
##### `timeout`
|
||||
|
||||
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";
|
||||
|
||||
const app = await duplicate("user/space-name", {
|
||||
hf_token: "hf_...",
|
||||
private: true,
|
||||
timeout: 5,
|
||||
});
|
||||
```
|
||||
|
||||
##### `hardware`
|
||||
|
||||
This is an optional property specific to `duplicate`'s options object and will set the hardware for the duplicated space. By default the hardware used will match that of the original space. If this cannot be obtained it will default to `"cpu-basic"`. For hardware upgrades (beyond the basic CPU tier), you may be required to provide [billing information on Hugging Face](https://huggingface.co/settings/billing).
|
||||
|
||||
Possible hardware options are:
|
||||
|
||||
- `"cpu-basic"`
|
||||
- `"cpu-upgrade"`
|
||||
- `"t4-small"`
|
||||
- `"t4-medium"`
|
||||
- `"a10g-small"`
|
||||
- `"a10g-large"`
|
||||
- `"a100-large"`
|
||||
|
||||
```ts
|
||||
import { duplicate } from "@gradio/client";
|
||||
|
||||
const app = await duplicate("user/space-name", {
|
||||
hf_token: "hf_...",
|
||||
private: true,
|
||||
hardware: "a10g-small",
|
||||
});
|
||||
```
|
||||
|
@ -13,6 +13,8 @@
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"bufferutil": "^4.0.7",
|
||||
"semiver": "^1.1.0",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
172
client/js/src/client.node-test.ts
Normal file
172
client/js/src/client.node-test.ts
Normal file
@ -0,0 +1,172 @@
|
||||
import { test, describe, assert } from "vitest";
|
||||
import { readFileSync } from "fs";
|
||||
import { join, dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { Blob } from "node:buffer";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const image_path = join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"demo",
|
||||
"kitchen_sink",
|
||||
"files",
|
||||
"lion.jpg"
|
||||
);
|
||||
|
||||
import { walk_and_store_blobs, client, handle_blob } from "./client";
|
||||
|
||||
describe.skip("extract blob parts", () => {
|
||||
test("convert Buffer to Blob", async () => {
|
||||
const image = readFileSync(image_path);
|
||||
await client("gradio/hello_world_main");
|
||||
const parts = walk_and_store_blobs({
|
||||
data: {
|
||||
image
|
||||
}
|
||||
});
|
||||
|
||||
assert.isTrue(parts[0].blob instanceof Blob);
|
||||
});
|
||||
|
||||
test("leave node Blob as Blob", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
await client("gradio/hello_world_main");
|
||||
const parts = walk_and_store_blobs({
|
||||
data: {
|
||||
image
|
||||
}
|
||||
});
|
||||
|
||||
assert.isTrue(parts[0].blob instanceof Blob);
|
||||
});
|
||||
|
||||
test("handle deep structures", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
await client("gradio/hello_world_main");
|
||||
const parts = walk_and_store_blobs({
|
||||
a: {
|
||||
b: {
|
||||
data: {
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assert.isTrue(parts[0].blob instanceof Blob);
|
||||
});
|
||||
|
||||
test("handle deep structures with arrays", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
await client("gradio/hello_world_main");
|
||||
const parts = walk_and_store_blobs({
|
||||
a: [
|
||||
{
|
||||
b: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
image
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
assert.isTrue(parts[0].blob instanceof Blob);
|
||||
});
|
||||
|
||||
test("handle deep structures with arrays 2", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
await client("gradio/hello_world_main");
|
||||
const obj = {
|
||||
a: [
|
||||
{
|
||||
b: [
|
||||
{
|
||||
data: [[image], image, [image, [image]]]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const parts = walk_and_store_blobs(obj);
|
||||
|
||||
function map_path(
|
||||
obj: Record<string, any>,
|
||||
parts: { path: string[]; blob: any }[]
|
||||
) {
|
||||
const { path, blob } = parts[parts.length - 1];
|
||||
let ref = obj;
|
||||
path.forEach((p) => (ref = ref[p]));
|
||||
|
||||
return ref === blob;
|
||||
}
|
||||
|
||||
assert.isTrue(parts[0].blob instanceof Blob);
|
||||
// assert.isTrue(map_path(obj, parts));
|
||||
});
|
||||
});
|
||||
|
||||
describe("handle_blob", () => {
|
||||
test("handle blobs", async () => {
|
||||
const image = new Blob([readFileSync(image_path)]);
|
||||
|
||||
const app = await client("gradio/hello_world_main");
|
||||
const obj = [
|
||||
{
|
||||
a: [
|
||||
{
|
||||
b: [
|
||||
{
|
||||
data: [[image], image, [image, [image]]]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const parts = await handle_blob(app.config.root, obj, undefined);
|
||||
//@ts-ignore
|
||||
// assert.isString(parts.data[0].a[0].b[0].data[0][0]);
|
||||
});
|
||||
});
|
||||
|
||||
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", {
|
||||
hf_token: "hf_"
|
||||
});
|
||||
|
||||
console.log(app);
|
||||
const obj = [
|
||||
{
|
||||
a: [
|
||||
{
|
||||
b: [
|
||||
{
|
||||
data: [[image], image, [image, [image]]]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const parts = await handle_blob(app.config.root, obj, "hf_");
|
||||
//@ts-ignore
|
||||
assert.isString(parts.data[0].a[0].b[0].data[0][0]);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -1,2 +1,2 @@
|
||||
export { client, post_data, upload_files } from "./client";
|
||||
export type { SpaceStatus } from "./types";
|
||||
export { client, post_data, upload_files, duplicate } from "./client.js";
|
||||
export type { SpaceStatus } from "./types.js";
|
||||
|
@ -22,6 +22,8 @@ export interface Config {
|
||||
export interface Payload {
|
||||
data: Array<unknown>;
|
||||
fn_index?: number;
|
||||
event_data?: unknown;
|
||||
time?: Date;
|
||||
}
|
||||
|
||||
export interface PostResponse {
|
||||
@ -35,18 +37,21 @@ export interface UploadResponse {
|
||||
|
||||
export interface Status {
|
||||
queue: boolean;
|
||||
status: "pending" | "error" | "complete" | "generating";
|
||||
code?: string;
|
||||
success?: boolean;
|
||||
stage: "pending" | "error" | "complete" | "generating";
|
||||
size?: number;
|
||||
position?: number;
|
||||
eta?: number;
|
||||
message?: string;
|
||||
progress?: Array<{
|
||||
progress_data?: Array<{
|
||||
progress: number | null;
|
||||
index: number | null;
|
||||
length: number | null;
|
||||
unit: string | null;
|
||||
desc: string | null;
|
||||
}>;
|
||||
time?: Date;
|
||||
}
|
||||
|
||||
export interface SpaceStatusNormal {
|
||||
@ -75,7 +80,7 @@ export type SpaceStatusCallback = (a: SpaceStatus) => void;
|
||||
export type EventType = "data" | "status";
|
||||
|
||||
export interface EventMap {
|
||||
data: Record<string, any>;
|
||||
data: Payload;
|
||||
status: Status;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { Config } from "./types";
|
||||
import type { Config } from "./types.js";
|
||||
|
||||
export function determine_protocol(endpoint: string): {
|
||||
ws_protocol: "ws" | "wss";
|
||||
@ -33,24 +33,40 @@ export function determine_protocol(endpoint: string): {
|
||||
|
||||
export const RE_SPACE_NAME = /^[^\/]*\/[^\/]*$/;
|
||||
export const RE_SPACE_DOMAIN = /.*hf\.space\/{0,1}$/;
|
||||
export async function process_endpoint(app_reference: string): Promise<{
|
||||
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)) {
|
||||
const _host = (
|
||||
await (
|
||||
await fetch(`https://huggingface.co/api/spaces/${_app_reference}/host`)
|
||||
).json()
|
||||
).host;
|
||||
return {
|
||||
space_id: app_reference,
|
||||
...determine_protocol(_host)
|
||||
};
|
||||
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) {
|
||||
throw new Error("Space metadata could not be loaded." + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (RE_SPACE_DOMAIN.test(_app_reference)) {
|
||||
@ -99,3 +115,97 @@ export async function discussions_enabled(space_id: string) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function get_space_hardware(
|
||||
space_id: string,
|
||||
token: `hf_${string}`
|
||||
) {
|
||||
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) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function set_space_hardware(
|
||||
space_id: string,
|
||||
new_hardware: typeof hardware_types[number],
|
||||
token: `hf_${string}`
|
||||
) {
|
||||
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) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function set_space_timeout(
|
||||
space_id: string,
|
||||
timeout: number,
|
||||
token: `hf_${string}`
|
||||
) {
|
||||
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) {
|
||||
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;
|
||||
|
@ -1,10 +1,14 @@
|
||||
{
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts", "src/**/*.node-test.ts"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "dist",
|
||||
"declarationMap": true
|
||||
"declarationMap": true,
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node16",
|
||||
"skipDefaultLibCheck": true
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
minify: true,
|
||||
// minify: true,
|
||||
lib: {
|
||||
entry: "src/index.ts",
|
||||
formats: ["es"]
|
||||
|
@ -0,0 +1,269 @@
|
||||
# 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.
|
||||
|
||||

|
||||
|
||||
Using the `@gradio/client` library, we can easily use the Gradio as an API to transcribe audio files programmatically.
|
||||
|
||||
Here's the entire code to do it:
|
||||
|
||||
```js
|
||||
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 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.
|
||||
|
||||
**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.
|
||||
|
||||
## Installation
|
||||
|
||||
The lightweight `@gradio/client` package can be installed from the npm registry with a package manager of your choice and support node version 18 and above:
|
||||
|
||||
```bash
|
||||
npm i -D @gradio/client
|
||||
```
|
||||
|
||||
## Connecting to a running Gradio App
|
||||
|
||||
Start by connecting instantiating a `client` instance and connecting it to a Gradio app that is running on Hugging Face Spaces or generally anywhere on the web.
|
||||
|
||||
## Connecting to a Hugging Face Space
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
|
||||
const app = client("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";
|
||||
|
||||
const app = client("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!
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
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 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.
|
||||
|
||||
**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";
|
||||
|
||||
const app = await duplicate("abidlabs/whisper", {
|
||||
hf_token: "hf_...",
|
||||
timeout: 60,
|
||||
hardware: "a10g-small",
|
||||
});
|
||||
```
|
||||
|
||||
## Connecting a general Gradio app
|
||||
|
||||
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";
|
||||
|
||||
const app = client("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.
|
||||
|
||||
For the Whisper Space, we can do this:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("abidlabs/whisper");
|
||||
|
||||
const app_info = await app.view_info();
|
||||
|
||||
console.log(app_info);
|
||||
```
|
||||
|
||||
And we will see the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"named_endpoints": {
|
||||
"/predict": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "text",
|
||||
"component": "Textbox",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "output",
|
||||
"component": "Textbox",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"unnamed_endpoints": {}
|
||||
}
|
||||
```
|
||||
|
||||
This shows us that we have 1 API endpoint in this space, and shows us how to use the API endpoint to make a prediction: we should call the `.predict()` method (which we will explore below), providing a parameter `input_audio` of type `string`, which is a url to a file.
|
||||
|
||||
We should also provide the `api_name='/predict'` argument to the `predict()` method. Although this isn't necessary if a Gradio app has only 1 named endpoint, it does allow us to call different endpoints in a single app if they are available. If an app has unnamed API endpoints, these can also be displayed by running `.view_api(all_endpoints=True)`.
|
||||
|
||||
## Making a prediction
|
||||
|
||||
The simplest way to make a prediction is simply to call the `.predict()` method with the appropriate arguments:
|
||||
|
||||
```js
|
||||
import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("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";
|
||||
|
||||
const app = await client("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";
|
||||
|
||||
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]);
|
||||
```
|
||||
|
||||
## Using events
|
||||
|
||||
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";
|
||||
|
||||
function log_result(payload) {
|
||||
const {
|
||||
data: [translation],
|
||||
} = payload;
|
||||
|
||||
console.log(`The translated result is: ${translation}`);
|
||||
}
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const job = app.submit("/predict", ["Hello"]);
|
||||
|
||||
job.on("data", log_result);
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
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";
|
||||
|
||||
function log_status(status) {
|
||||
console.log(
|
||||
`The current status for this job is: ${JSON.stringify(status, null, 2)}.`
|
||||
);
|
||||
}
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const job = app.submit("/predict", ["Hello"]);
|
||||
|
||||
job.on("status", log_status);
|
||||
```
|
||||
|
||||
## Cancelling Jobs
|
||||
|
||||
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";
|
||||
|
||||
const app = await client("abidlabs/en2fr");
|
||||
const job_one = app.submit("/predict", ["Hello"]);
|
||||
const job_two = app.submit("/predict", ["Friends"]);
|
||||
|
||||
job_one.cancel();
|
||||
job_two.cancel();
|
||||
```
|
||||
|
||||
If the first job has started processing, then it will not be canceled but the client will no longer listen for updates (throwing away the job). If the second job has not yet started, it will be successfully canceled and removed from the queue.
|
||||
|
||||
## Generator Endpoints
|
||||
|
||||
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";
|
||||
|
||||
const app = await client("gradio/count_generator");
|
||||
const job = app.submit(0, [9]);
|
||||
|
||||
job.on("data", (data) => console.log(data));
|
||||
```
|
||||
|
||||
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";
|
||||
|
||||
const app = await client("gradio/count_generator");
|
||||
const job = app.submit(0, [9]);
|
||||
|
||||
job.on("data", (data) => console.log(data));
|
||||
|
||||
setTimeout(() => {
|
||||
job.cancel();
|
||||
}, 3000);
|
||||
```
|
10
js/_spaces-test/.gitignore
vendored
Normal file
10
js/_spaces-test/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
2
js/_spaces-test/.npmrc
Normal file
2
js/_spaces-test/.npmrc
Normal file
@ -0,0 +1,2 @@
|
||||
engine-strict=true
|
||||
resolution-mode=highest
|
13
js/_spaces-test/.prettierignore
Normal file
13
js/_spaces-test/.prettierignore
Normal file
@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
38
js/_spaces-test/README.md
Normal file
38
js/_spaces-test/README.md
Normal file
@ -0,0 +1,38 @@
|
||||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
17
js/_spaces-test/jsconfig.json
Normal file
17
js/_spaces-test/jsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
30
js/_spaces-test/package.json
Normal file
30
js/_spaces-test/package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "spaces-test",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
||||
"lint": "prettier --plugin-search-dir . --check .",
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.5.0",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.8.1",
|
||||
"svelte": "^3.54.0",
|
||||
"svelte-check": "^3.0.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.3.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@gradio/client": "workspace:^0.0.1",
|
||||
"@gradio/form": "workspace:^0.0.1",
|
||||
"@gradio/theme": "workspace:^0.0.1"
|
||||
}
|
||||
}
|
12
js/_spaces-test/src/app.d.ts
vendored
Normal file
12
js/_spaces-test/src/app.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
12
js/_spaces-test/src/app.html
Normal file
12
js/_spaces-test/src/app.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
89
js/_spaces-test/src/lib/EndpointInputs.svelte
Normal file
89
js/_spaces-test/src/lib/EndpointInputs.svelte
Normal file
@ -0,0 +1,89 @@
|
||||
<script>
|
||||
/**
|
||||
* @type {{type: string; label: string; component:string}[]}
|
||||
*/
|
||||
export let app_info;
|
||||
|
||||
$: console.log(app_info);
|
||||
|
||||
/**
|
||||
* @type any[]
|
||||
*/
|
||||
export let request_data = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param files {FileList|null}
|
||||
* @param i {number}
|
||||
*/
|
||||
function handle_file(files, i) {
|
||||
if (!files) return;
|
||||
const _files = Array.from(files);
|
||||
request_data[i] = files.length === 1 ? _files[0] : _files;
|
||||
console.log(request_data);
|
||||
}
|
||||
</script>
|
||||
|
||||
<h3>Request Inputs</h3>
|
||||
|
||||
{#each app_info as { type, label, component }, i}
|
||||
{#if type === "string"}
|
||||
<label for="">
|
||||
<span>{label} <code>{type}</code></span>
|
||||
<input type="text" bind:value={request_data[i]} />
|
||||
</label>
|
||||
{:else if type === "number"}
|
||||
<label for="">
|
||||
<span>{label} <code>{type}</code></span>
|
||||
<input type="number" bind:value={request_data[i]} />
|
||||
</label>
|
||||
{:else if type === "boolean"}
|
||||
<label for="">
|
||||
<span>{label} <code>{type}</code></span>
|
||||
<input type="checkbox" bind:value={request_data[i]} />
|
||||
</label>
|
||||
{:else if type === "number"}
|
||||
<label for="">
|
||||
<span>{label} <code>{type}</code></span>
|
||||
<input type="number" bind:value={request_data[i]} />
|
||||
</label>
|
||||
{:else if type === "string[]"}
|
||||
<label for="">
|
||||
<span>{label} <code>{type} - comma separated list</code></span>
|
||||
<input
|
||||
type="text"
|
||||
value={request_data[i]}
|
||||
on:input={(e) =>
|
||||
(request_data[i] = e.currentTarget.value
|
||||
.split(",")
|
||||
.map((v) => v.trim()))}
|
||||
/>
|
||||
</label>
|
||||
{:else if ["Image", "Audio", "Video"].includes(component)}
|
||||
<label for="">
|
||||
<span>{label} <code>File</code></span>
|
||||
<input
|
||||
type="file"
|
||||
on:input={(e) => handle_file(e.currentTarget.files, i)}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
outline: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
</style>
|
90
js/_spaces-test/src/lib/ResponsePreview.svelte
Normal file
90
js/_spaces-test/src/lib/ResponsePreview.svelte
Normal file
@ -0,0 +1,90 @@
|
||||
<script>
|
||||
import Spinner from "./Spinner.svelte";
|
||||
import Warning from "./Warning.svelte";
|
||||
import Success from "./Success.svelte";
|
||||
/**
|
||||
* @type {{data: any[]}}
|
||||
*/
|
||||
export let response_data = { data: [] };
|
||||
/**
|
||||
* @type {{type: string; label: string; component:string}[]}
|
||||
*/
|
||||
export let app_info;
|
||||
|
||||
/**
|
||||
* @type {"pending" | "error" | "complete" | "generating" | 'idle'}
|
||||
*/
|
||||
export let status = "idle";
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="heading-wrap">
|
||||
<h3>Response Outputs</h3>
|
||||
{#if status === "pending" || status === "generating"}
|
||||
<Spinner />
|
||||
{:else if status === "error"}
|
||||
<Warning />
|
||||
{:else if status === "complete"}
|
||||
<Success />
|
||||
{/if}
|
||||
</div>
|
||||
{#each app_info as { type, label, component }, i}
|
||||
{#if type === "string"}
|
||||
<label for="">
|
||||
<span>{label} <code>{type}</code></span>
|
||||
<input type="text" disabled value={response_data.data[i] || ""} />
|
||||
</label>
|
||||
{:else if type === "number"}
|
||||
<label for="">
|
||||
<span>{label} <code>{type}</code></span>
|
||||
<input type="number" disabled value={response_data.data[i] || ""} />
|
||||
</label>
|
||||
{:else if type === "boolean"}
|
||||
<label for="">
|
||||
<span>{label} <code>{type}</code></span>
|
||||
<input type="checkbox" disabled value={response_data.data[i] || ""} />
|
||||
</label>
|
||||
{:else if type === "number"}
|
||||
<label for="">
|
||||
<span>{label} <code>{type}</code></span>
|
||||
<input type="number" disabled value={response_data.data[i] || ""} />
|
||||
</label>
|
||||
{:else if type === "string[]"}
|
||||
<label for="">
|
||||
<span>{label} <code>{type} - comma separated list</code></span>
|
||||
<input type="text" disabled value={response_data.data[i] || ""} />
|
||||
</label>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<h4>JSON</h4>
|
||||
<pre><code
|
||||
>{JSON.stringify(
|
||||
response_data.data.length ? response_data : {},
|
||||
null,
|
||||
2
|
||||
)}</code
|
||||
></pre>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
outline: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.heading-wrap {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
35
js/_spaces-test/src/lib/Spinner.svelte
Normal file
35
js/_spaces-test/src/lib/Spinner.svelte
Normal file
@ -0,0 +1,35 @@
|
||||
<span class="loader" />
|
||||
|
||||
<style>
|
||||
.loader {
|
||||
display: block;
|
||||
position: relative;
|
||||
animation: shadowPulse 2s linear infinite;
|
||||
box-sizing: border-box;
|
||||
margin: var(--spacing-xxl) 0 var(--spacing-lg) var(--spacing-xxl) !important;
|
||||
box-shadow: -24px 0 #fff, 24px 0 #fff;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
@keyframes shadowPulse {
|
||||
33% {
|
||||
box-shadow: -24px 0 #ff3d00, 24px 0 #fff;
|
||||
background: #fff;
|
||||
}
|
||||
66% {
|
||||
box-shadow: -24px 0 #fff, 24px 0 #fff;
|
||||
background: #ff3d00;
|
||||
}
|
||||
100% {
|
||||
box-shadow: -24px 0 #fff, 24px 0 #ff3d00;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
transform: scale(0.4) translateY(7px);
|
||||
}
|
||||
</style>
|
27
js/_spaces-test/src/lib/Success.svelte
Normal file
27
js/_spaces-test/src/lib/Success.svelte
Normal file
@ -0,0 +1,27 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 32 32"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2Zm-2 19.59l-5-5L10.59 15L14 18.41L21.41 11l1.596 1.586Z"
|
||||
/><path
|
||||
fill="none"
|
||||
d="m14 21.591l-5-5L10.591 15L14 18.409L21.41 11l1.595 1.585L14 21.591z"
|
||||
/></svg
|
||||
>
|
||||
|
||||
<style>
|
||||
svg {
|
||||
margin: var(--spacing-xxl) 0 var(--spacing-lg) var(--spacing-lg) !important;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--color-green-500);
|
||||
}
|
||||
|
||||
svg path {
|
||||
transform: translateY(2px);
|
||||
color: var(--color-green-500);
|
||||
}
|
||||
</style>
|
After Width: | Height: | Size: 581 B |
30
js/_spaces-test/src/lib/Warning.svelte
Normal file
30
js/_spaces-test/src/lib/Warning.svelte
Normal file
@ -0,0 +1,30 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 32 32"
|
||||
><path
|
||||
fill="none"
|
||||
d="M16 26a1.5 1.5 0 1 1 1.5-1.5A1.5 1.5 0 0 1 16 26Zm-1.125-5h2.25v-9h-2.25Z"
|
||||
/><path
|
||||
fill="currentColor"
|
||||
d="M16.002 6.171h-.004L4.648 27.997l.003.003h22.698l.002-.003ZM14.875 12h2.25v9h-2.25ZM16 26a1.5 1.5 0 1 1 1.5-1.5A1.5 1.5 0 0 1 16 26Z"
|
||||
/><path
|
||||
fill="currentColor"
|
||||
d="M29 30H3a1 1 0 0 1-.887-1.461l13-25a1 1 0 0 1 1.774 0l13 25A1 1 0 0 1 29 30ZM4.65 28h22.7l.001-.003L16.002 6.17h-.004L4.648 27.997Z"
|
||||
/></svg
|
||||
>
|
||||
|
||||
<style>
|
||||
svg {
|
||||
margin: var(--spacing-xxl) 0 var(--spacing-lg) var(--spacing-lg) !important;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--color-red-500);
|
||||
}
|
||||
|
||||
svg path {
|
||||
transform: translateY(2px);
|
||||
color: var(--color-red-500);
|
||||
}
|
||||
</style>
|
After Width: | Height: | Size: 789 B |
71
js/_spaces-test/src/routes/+layout.svelte
Normal file
71
js/_spaces-test/src/routes/+layout.svelte
Normal file
@ -0,0 +1,71 @@
|
||||
<script>
|
||||
import "@gradio/theme";
|
||||
import "../../../app/test/mocks/theme.css";
|
||||
import { page } from "$app/stores";
|
||||
import { afterNavigate } from "$app/navigation";
|
||||
|
||||
$: console.log($page);
|
||||
|
||||
const links = [
|
||||
["/embeds", "Embeds"],
|
||||
["/client-browser", "Client-Browser"],
|
||||
["/client-node", "Client-Node"]
|
||||
];
|
||||
|
||||
$: afterNavigate(() => (location.hash = $page.url.pathname.replace("/", "")));
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link
|
||||
rel="preconnect"
|
||||
href="https://fonts.gstatic.com"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600&display=swap"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&display=swap"
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<div class="prose">
|
||||
<h1><span style="color: var(--color-accent);">Gradio</span> Test Space</h1>
|
||||
<ul>
|
||||
{#each links as [url, name]}
|
||||
<li><a href={url} class:active={url === $page.route.id}>{name}</a></li>
|
||||
{/each}
|
||||
</ul>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
gap: var(--spacing-xxl);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.active {
|
||||
color: var(--color-accent);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.prose {
|
||||
margin-top: var(--size-5);
|
||||
}
|
||||
</style>
|
1
js/_spaces-test/src/routes/+page.svelte
Normal file
1
js/_spaces-test/src/routes/+page.svelte
Normal file
@ -0,0 +1 @@
|
||||
<h2>Embeds</h2>
|
309
js/_spaces-test/src/routes/client-browser/+page.svelte
Normal file
309
js/_spaces-test/src/routes/client-browser/+page.svelte
Normal file
@ -0,0 +1,309 @@
|
||||
<script>
|
||||
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>>
|
||||
*/
|
||||
let app;
|
||||
/**
|
||||
* @type any
|
||||
*/
|
||||
let app_info;
|
||||
/**
|
||||
* @type string[]
|
||||
*/
|
||||
let named = [];
|
||||
/**
|
||||
* @type string[]
|
||||
*/
|
||||
let unnamed = [];
|
||||
/**
|
||||
* @type string | number
|
||||
*/
|
||||
let active_endpoint = "";
|
||||
/**
|
||||
* @type {{data: any[]; fn_index: number; endpoint: string|number}}
|
||||
*/
|
||||
let response_data = { data: [], fn_index: 0, endpoint: "" };
|
||||
/**
|
||||
* @type any[]
|
||||
*/
|
||||
let request_data = [];
|
||||
async function connect() {
|
||||
named = [];
|
||||
unnamed = [];
|
||||
app_info = undefined;
|
||||
active_endpoint = "";
|
||||
response_data = { data: [], fn_index: 0, endpoint: "" };
|
||||
if (!api || (hf_token && !hf_token.startsWith("_hf"))) return;
|
||||
|
||||
app = await client(api, {
|
||||
//@ts-ignore
|
||||
hf_token: hf_token
|
||||
});
|
||||
|
||||
console.log(app.config);
|
||||
|
||||
const { named_endpoints, unnamed_endpoints } = await app.view_api();
|
||||
|
||||
named = Object.keys(named_endpoints);
|
||||
unnamed = Object.keys(unnamed_endpoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type {"named" | "unnamed"}
|
||||
* @param _endpoint {string}
|
||||
*/
|
||||
async function select_endpoint(type, _endpoint) {
|
||||
response_data = { data: [], fn_index: 0, endpoint: "" };
|
||||
const _endpoint_info = (await app.view_api())?.[`${type}_endpoints`]?.[
|
||||
type === "unnamed" ? parseInt(_endpoint) : _endpoint
|
||||
];
|
||||
if (!_endpoint_info) return;
|
||||
|
||||
console.log(_endpoint_info);
|
||||
|
||||
app_info = _endpoint_info;
|
||||
active_endpoint = type === "unnamed" ? parseInt(_endpoint) : _endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type ReturnType<Awaited<ReturnType<typeof client>>["submit"]>
|
||||
*/
|
||||
let job;
|
||||
|
||||
/**
|
||||
* @type {"pending" | "error" | "complete" | "generating" | 'idle'}
|
||||
*/
|
||||
let status = "idle";
|
||||
|
||||
async function submit() {
|
||||
response_data = { data: [], fn_index: 0, endpoint: "" };
|
||||
|
||||
job = app
|
||||
.submit(active_endpoint, request_data)
|
||||
.on("data", (data) => {
|
||||
response_data = data;
|
||||
})
|
||||
.on("status", (_status) => {
|
||||
status = _status.stage;
|
||||
});
|
||||
// console.log(res);
|
||||
// response_data = res;
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
job.cancel();
|
||||
}
|
||||
|
||||
let endpoint_type_text = "";
|
||||
$: {
|
||||
if (!app_info) {
|
||||
endpoint_type_text = "";
|
||||
} else if (!app_info.type.continuos && app_info.type.generator) {
|
||||
endpoint_type_text =
|
||||
"This endpoint generates values over time and can be cancelled.";
|
||||
} else if (app_info.type.continuos && app_info.type.generator) {
|
||||
endpoint_type_text =
|
||||
"This endpoint generates values over time and will continue to yield values until cancelled.";
|
||||
} else {
|
||||
endpoint_type_text = "This endpoint runs once and cannot be cancelled.";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<h2>Client Browser</h2>
|
||||
|
||||
<p>
|
||||
Enter a space <code>user-space/name</code> to test the client in a browser environment
|
||||
with any space.
|
||||
</p>
|
||||
<p>
|
||||
You may optionally provide a <code>hf_token</code> to test a private space
|
||||
</p>
|
||||
|
||||
<div class="input-wrap">
|
||||
<label for="">
|
||||
<span>Space Name</span>
|
||||
<input type="text" placeholder="user/space-name" bind:value={api} />
|
||||
</label>
|
||||
|
||||
<label for="">
|
||||
<span>Hugging Face Token <i>(optional)</i></span>
|
||||
<input type="text" placeholder="hf_..." bind:value={hf_token} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button on:click={connect}>Connect</button>
|
||||
|
||||
{#if named.length || unnamed.length}
|
||||
<div class="endpoints">
|
||||
<div class="endpoint">
|
||||
<h3>Named endpoints</h3>
|
||||
{#if named.length}
|
||||
{#each named as endpoint}
|
||||
<button
|
||||
class="endpoint-button"
|
||||
class:selected={endpoint === active_endpoint}
|
||||
on:click={() => select_endpoint("named", endpoint)}
|
||||
>{endpoint}</button
|
||||
>
|
||||
{/each}
|
||||
{:else}
|
||||
<p>There are no named endpoints</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="endpoint">
|
||||
<h3>Unnamed endpoints</h3>
|
||||
|
||||
{#if unnamed.length}
|
||||
{#each unnamed as endpoint}
|
||||
<button
|
||||
class="endpoint-button"
|
||||
class:selected={parseInt(endpoint) === active_endpoint}
|
||||
on:click={() => select_endpoint("unnamed", endpoint)}
|
||||
>{endpoint}</button
|
||||
>
|
||||
{/each}
|
||||
{:else}
|
||||
<p>There are no unnamed endpoints</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if app_info}
|
||||
<hr />
|
||||
<p>
|
||||
This endpoint accepts {app_info.parameters.length
|
||||
? app_info.parameters.length
|
||||
: "no"} piece{app_info.parameters.length < 1 ||
|
||||
app_info.parameters.length > 1
|
||||
? "s"
|
||||
: ""} of data and returns {app_info.returns.length
|
||||
? app_info.returns.length
|
||||
: "no"} piece{app_info.returns.length < 1 || app_info.returns.length > 1
|
||||
? "s"
|
||||
: ""} of data. {endpoint_type_text}
|
||||
</p>
|
||||
<hr />
|
||||
<div class="app_info">
|
||||
<div>
|
||||
<EndpointInputs app_info={app_info.parameters} bind:request_data />
|
||||
<button class="submit" on:click={submit}>Submit Request</button>
|
||||
{#if app_info.type.generator || app_info.type.continuous}
|
||||
<button
|
||||
class="cancel"
|
||||
on:click={cancel}
|
||||
disabled={status !== "generating" && status !== "pending"}
|
||||
>Cancel Request</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<ResponsePreview {status} app_info={app_info.returns} {response_data} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label:first-child input {
|
||||
margin-right: -1px;
|
||||
border-top-left-radius: 2px;
|
||||
}
|
||||
|
||||
label:last-child input {
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
|
||||
input {
|
||||
outline: none;
|
||||
border-bottom: none;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
z-index: 1;
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.input-wrap {
|
||||
display: flex;
|
||||
margin-top: var(--size-6);
|
||||
}
|
||||
|
||||
button {
|
||||
border-bottom-right-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
background: var(--color-accent);
|
||||
padding: var(--size-2) var(--size-2-5);
|
||||
width: 100%;
|
||||
color: var(--background-fill-primary);
|
||||
font-weight: bold;
|
||||
font-size: var(--scale-0);
|
||||
/* margin-top: var(--size-3); */
|
||||
}
|
||||
|
||||
.endpoints button {
|
||||
background: var(--color-orange-200);
|
||||
width: auto;
|
||||
color: var(--body-text-color);
|
||||
}
|
||||
button.selected {
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.endpoints {
|
||||
display: flex;
|
||||
gap: var(--size-10);
|
||||
margin-top: var(--size-10);
|
||||
}
|
||||
|
||||
.endpoint {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app_info {
|
||||
display: flex;
|
||||
gap: var(--size-10);
|
||||
margin-top: var(--size-10);
|
||||
}
|
||||
|
||||
.app_info > div {
|
||||
width: 100%;
|
||||
}
|
||||
.submit {
|
||||
margin-top: var(--size-5);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: var(--size-6) 0;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
margin-top: var(--size-2);
|
||||
background-color: var(--color-red-600);
|
||||
}
|
||||
.endpoint-button {
|
||||
margin-right: var(--size-1);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
button[disabled] {
|
||||
opacity: 0.2;
|
||||
}
|
||||
</style>
|
3
js/_spaces-test/src/routes/client-node/+page.svelte
Normal file
3
js/_spaces-test/src/routes/client-node/+page.svelte
Normal file
@ -0,0 +1,3 @@
|
||||
<h2>Client Node</h2>
|
||||
|
||||
<p>coming soon.</p>
|
3
js/_spaces-test/src/routes/embeds/+page.svelte
Normal file
3
js/_spaces-test/src/routes/embeds/+page.svelte
Normal file
@ -0,0 +1,3 @@
|
||||
<h2>Embeds</h2>
|
||||
|
||||
<p>Coming soon.</p>
|
BIN
js/_spaces-test/static/favicon.png
Normal file
BIN
js/_spaces-test/static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
13
js/_spaces-test/svelte.config.js
Normal file
13
js/_spaces-test/svelte.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
import adapter from "@sveltejs/adapter-auto";
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
6
js/_spaces-test/vite.config.js
Normal file
6
js/_spaces-test/vite.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
@ -232,30 +232,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
app.on("data", ({ data, fn_index }) => {
|
||||
handle_update(data, fn_index);
|
||||
let status = loading_status.get_status_for_fn(fn_index);
|
||||
if (status === "complete") {
|
||||
// handle .success and successful .then here, after data has updated
|
||||
dependencies.forEach((dep, i) => {
|
||||
if (dep.trigger_after === fn_index) {
|
||||
trigger_api_call(i, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.on("status", ({ fn_index, ...status }) => {
|
||||
loading_status.update({ ...status, fn_index });
|
||||
if (status.status === "error") {
|
||||
// handle failed .then here, since "data" listener won't trigger
|
||||
dependencies.forEach((dep, i) => {
|
||||
if (dep.trigger_after === fn_index && !dep.trigger_only_on_success) {
|
||||
trigger_api_call(i, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
let submit_map: Map<number, ReturnType<typeof app.submit>> = new Map();
|
||||
|
||||
function set_prop<T extends ComponentMeta>(obj: T, prop: string, val: any) {
|
||||
if (!obj?.props) {
|
||||
@ -266,7 +243,7 @@
|
||||
}
|
||||
let handled_dependencies: Array<number[]> = [];
|
||||
|
||||
const trigger_api_call = (dep_index: number, event_data: unknown) => {
|
||||
const trigger_api_call = async (dep_index: number, event_data: unknown) => {
|
||||
let dep = dependencies[dep_index];
|
||||
const current_status = loading_status.get_status_for_fn(dep_index);
|
||||
if (current_status === "pending" || current_status === "generating") {
|
||||
@ -274,9 +251,11 @@
|
||||
}
|
||||
|
||||
if (dep.cancels) {
|
||||
dep.cancels.forEach((fn_index) => {
|
||||
app.cancel("/predict", fn_index);
|
||||
});
|
||||
await Promise.all(
|
||||
dep.cancels.map((fn_index) => {
|
||||
submit_map.get(fn_index)?.cancel();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let payload = {
|
||||
@ -307,7 +286,42 @@
|
||||
}
|
||||
|
||||
function make_prediction() {
|
||||
app.predict("/predict", payload);
|
||||
const submission = app
|
||||
.submit(payload.fn_index, payload.data as unknown[], payload.event_data)
|
||||
.on("data", ({ data, fn_index }) => {
|
||||
handle_update(data, fn_index);
|
||||
let status = loading_status.get_status_for_fn(fn_index);
|
||||
|
||||
if (status === "complete") {
|
||||
dependencies.forEach((dep, i) => {
|
||||
if (dep.trigger_after === fn_index) {
|
||||
trigger_api_call(i, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.on("status", ({ fn_index, ...status }) => {
|
||||
loading_status.update({
|
||||
...status,
|
||||
status: status.stage,
|
||||
progress: status.progress_data,
|
||||
fn_index
|
||||
});
|
||||
|
||||
if (status.stage === "error") {
|
||||
// handle failed .then here, since "data" listener won't trigger
|
||||
dependencies.forEach((dep, i) => {
|
||||
if (
|
||||
dep.trigger_after === fn_index &&
|
||||
!dep.trigger_only_on_success
|
||||
) {
|
||||
trigger_api_call(i, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
submit_map.set(dep_index, submission);
|
||||
}
|
||||
};
|
||||
|
||||
@ -466,6 +480,7 @@
|
||||
{instance_map}
|
||||
{dependencies}
|
||||
{root}
|
||||
{app}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -194,7 +194,7 @@
|
||||
? "http://localhost:7860"
|
||||
: host || space || src || location.origin;
|
||||
|
||||
app = await client(api_url, handle_status);
|
||||
app = await client(api_url, { status_callback: handle_status });
|
||||
config = app.config;
|
||||
|
||||
status = {
|
||||
|
@ -3,6 +3,7 @@
|
||||
import type { ComponentMeta, Dependency } from "../components/types";
|
||||
import { post_data } from "@gradio/client";
|
||||
import NoApi from "./NoApi.svelte";
|
||||
import type { client } from "@gradio/client";
|
||||
|
||||
import { represent_value } from "./utils";
|
||||
|
||||
@ -20,6 +21,7 @@
|
||||
};
|
||||
export let dependencies: Array<Dependency>;
|
||||
export let root: string;
|
||||
export let app: Awaited<ReturnType<typeof client>>;
|
||||
|
||||
if (root === "") {
|
||||
root = location.protocol + "//" + location.host + location.pathname;
|
||||
@ -31,8 +33,8 @@
|
||||
let current_language: "python" | "javascript" = "python";
|
||||
|
||||
const langs = [
|
||||
["python", python]
|
||||
// ["javascript", javascript]
|
||||
["python", python],
|
||||
["javascript", javascript]
|
||||
] as const;
|
||||
|
||||
let is_running = false;
|
||||
@ -62,16 +64,24 @@
|
||||
let data = await response.json();
|
||||
return data;
|
||||
}
|
||||
async function get_js_info() {
|
||||
let js_api_info = await app.view_api();
|
||||
return js_api_info;
|
||||
}
|
||||
|
||||
let info: {
|
||||
named_endpoints: any;
|
||||
unnamed_endpoints: any;
|
||||
};
|
||||
|
||||
let js_info: Record<string, any>;
|
||||
|
||||
get_info()
|
||||
.then((data) => (info = data))
|
||||
.catch((err) => console.log(err));
|
||||
|
||||
get_js_info().then((js_api_info) => (js_info = js_api_info));
|
||||
|
||||
const run = async (index: number) => {
|
||||
is_running = true;
|
||||
let dependency = dependencies[index];
|
||||
@ -142,9 +152,13 @@
|
||||
<div class="client-doc">
|
||||
<h2>
|
||||
Use the <a
|
||||
href="https://pypi.org/project/gradio-client/"
|
||||
href="https://gradio.app/docs/#python-client"
|
||||
target="_blank"><code class="library">gradio_client</code></a
|
||||
> Python library to query the demo via API.
|
||||
>
|
||||
Python library or the
|
||||
<a href="https://gradio.app/docs/#javascript-client" target="_blank"
|
||||
><code class="library">@gradio/client</code></a
|
||||
> Javascript package to query the demo via API.
|
||||
</h2>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
@ -174,6 +188,9 @@
|
||||
endpoint_parameters={info.named_endpoints[
|
||||
"/" + dependency.api_name
|
||||
].parameters}
|
||||
js_parameters={js_info.named_endpoints[
|
||||
"/" + dependency.api_name
|
||||
].parameters}
|
||||
{instance_map}
|
||||
{dependency}
|
||||
{dependency_index}
|
||||
@ -195,12 +212,15 @@
|
||||
endpoint_returns={info.named_endpoints[
|
||||
"/" + dependency.api_name
|
||||
].returns}
|
||||
js_returns={js_info.named_endpoints["/" + dependency.api_name]
|
||||
.returns}
|
||||
{instance_map}
|
||||
{dependency}
|
||||
{dependency_index}
|
||||
{is_running}
|
||||
{dependency_outputs}
|
||||
{root}
|
||||
{current_language}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@ -217,6 +237,8 @@
|
||||
named={false}
|
||||
endpoint_parameters={info.unnamed_endpoints[dependency_index]
|
||||
.parameters}
|
||||
js_parameters={js_info.unnamed_endpoints[dependency_index]
|
||||
.parameters}
|
||||
{instance_map}
|
||||
{dependency}
|
||||
{dependency_index}
|
||||
@ -237,11 +259,13 @@
|
||||
named={false}
|
||||
endpoint_returns={info.unnamed_endpoints[dependency_index]
|
||||
.returns}
|
||||
js_returns={js_info.unnamed_endpoints[dependency_index].returns}
|
||||
{instance_map}
|
||||
{dependency}
|
||||
{dependency_index}
|
||||
{is_running}
|
||||
{dependency_outputs}
|
||||
{current_language}
|
||||
{root}
|
||||
/>
|
||||
</div>
|
||||
|
@ -15,12 +15,28 @@
|
||||
export let dependency_inputs: string[][];
|
||||
export let dependency_failures: boolean[][];
|
||||
export let endpoint_parameters: any;
|
||||
export let js_parameters: any;
|
||||
export let named: boolean;
|
||||
|
||||
export let current_language: "python" | "javascript";
|
||||
|
||||
let python_code: HTMLElement;
|
||||
let js_code: HTMLElement;
|
||||
|
||||
let blob_components = ["Audio", "File", "Image", "Video"];
|
||||
let blob_examples: any[] = endpoint_parameters.filter(
|
||||
(param: {
|
||||
label: string;
|
||||
type: string;
|
||||
python_type: {
|
||||
type: string;
|
||||
description: string;
|
||||
};
|
||||
component: string;
|
||||
example_input: string;
|
||||
serializer: string;
|
||||
}) => blob_components.includes(param.component)
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
@ -53,8 +69,8 @@ result = client.predict(<!--
|
||||
-->{/if}<!--
|
||||
--><span class="desc"
|
||||
><!--
|
||||
--> # {python_type.type} <!--
|
||||
-->representing input in '{label}' <!--
|
||||
--> # {python_type.type} {#if python_type.description}({python_type.description}){/if}<!--
|
||||
--> in '{label}' <!--
|
||||
-->{component} component<!--
|
||||
--></span
|
||||
><!--
|
||||
@ -68,6 +84,65 @@ result = client.predict(<!--
|
||||
)
|
||||
print(result)</pre>
|
||||
</div>
|
||||
{:else if current_language === "javascript"}
|
||||
<div class="copy">
|
||||
<CopyButton code={js_code?.innerText} />
|
||||
</div>
|
||||
<div bind:this={js_code}>
|
||||
<pre>import { client } from "@gradio/client";
|
||||
<!--
|
||||
-->
|
||||
|
||||
async function run() {
|
||||
{#each blob_examples as { label, type, python_type, component, example_input, serializer }, i}<!--
|
||||
-->
|
||||
const response_{i} = await fetch("{example_input}");
|
||||
const example{component} = await response_{i}.blob();
|
||||
{/each}<!--
|
||||
-->
|
||||
const app = await client(<span class="token string">"{root}"</span>);
|
||||
const result = await app.predict({#if named}"/{dependency.api_name}"{:else}{dependency_index}{/if}, [<!--
|
||||
-->{#each endpoint_parameters as { label, type, python_type, component, example_input, serializer }, i}<!--
|
||||
-->{#if blob_components.includes(component)}<!--
|
||||
-->
|
||||
<span
|
||||
class="example-inputs">example{component}</span
|
||||
>, <!--
|
||||
--><span class="desc"
|
||||
><!--
|
||||
--> // blob <!--
|
||||
-->in '{label}' <!--
|
||||
-->{component} component<!--
|
||||
--></span
|
||||
><!--
|
||||
-->{:else}<!--
|
||||
-->
|
||||
<span class="example-inputs"
|
||||
>{represent_value(
|
||||
example_input,
|
||||
python_type.type,
|
||||
"js"
|
||||
)}</span
|
||||
>, <!--
|
||||
--><span class="desc"
|
||||
><!--
|
||||
-->// {js_parameters[i]
|
||||
.type} {#if js_parameters[i].description}({js_parameters[i]
|
||||
.description}){/if}<!--
|
||||
--> in '{label}' <!--
|
||||
-->{component} component<!--
|
||||
--></span
|
||||
><!--
|
||||
-->{/if}
|
||||
{/each}
|
||||
]);
|
||||
|
||||
console.log(result?.data);
|
||||
}
|
||||
|
||||
run();
|
||||
</pre>
|
||||
</div>
|
||||
{/if}
|
||||
</code>
|
||||
</Block>
|
||||
|
@ -5,7 +5,7 @@
|
||||
export let current_language: "python" | "javascript";
|
||||
|
||||
let py_install: string = "pip install gradio_client";
|
||||
let js_install: string = "pnpm add @gradio/client";
|
||||
let js_install: string = "npm i -D @gradio/client";
|
||||
</script>
|
||||
|
||||
<Block>
|
||||
|
@ -15,7 +15,10 @@
|
||||
|
||||
export let root: string;
|
||||
export let endpoint_returns: any;
|
||||
export let js_returns: any;
|
||||
|
||||
export let named: boolean;
|
||||
export let current_language: "python" | "javascript";
|
||||
|
||||
const format_url = (desc: string | undefined, data: string | undefined) =>
|
||||
desc
|
||||
@ -33,11 +36,13 @@
|
||||
<div class="response-wrap">
|
||||
<div class:hide={is_running}>
|
||||
{#if endpoint_returns.length > 1}({/if}
|
||||
{#each endpoint_returns as { label, type, python_type, component, serializer }}
|
||||
{#each endpoint_returns as { label, type, python_type, component, serializer }, i}
|
||||
<div class:second-level={endpoint_returns.length > 1}>
|
||||
<span class="desc"
|
||||
><!--
|
||||
--> # {python_type.type}
|
||||
--> # {#if current_language === "python"}{python_type.type}{:else}{js_returns[
|
||||
i
|
||||
].type}{/if}
|
||||
<!--
|
||||
-->representing output in '{label}' <!--
|
||||
-->{component}
|
||||
|
@ -10,14 +10,18 @@ export function represent_value(
|
||||
return lang === null ? value : '"' + value + '"';
|
||||
} else if (type === "number") {
|
||||
return lang === null ? parseFloat(value) : value;
|
||||
} else if (type === "boolean") {
|
||||
} else if (type === "boolean" || type == "bool") {
|
||||
if (lang === "py") {
|
||||
value = String(value);
|
||||
return value === "true" ? "True" : "False";
|
||||
} else if (lang === "js") {
|
||||
return value;
|
||||
} else {
|
||||
return value === "true";
|
||||
}
|
||||
} else if (type === "List[str]") {
|
||||
value = JSON.stringify(value);
|
||||
return value;
|
||||
} else {
|
||||
// assume object type
|
||||
if (lang === null) {
|
||||
|
@ -1,30 +1,5 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
import { mock_theme, wait_for_page } from "./utils";
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `../../demo/${demo}/config.json`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mock_api(page: Page, body: Array<unknown>) {
|
||||
return page.route("**/run/predict", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: body[id]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { mock_theme, wait_for_page, mock_api, mock_demo } from "./utils";
|
||||
|
||||
test("renders the correct elements", async ({ page }) => {
|
||||
await mock_demo(page, "blocks_inputs");
|
||||
|
@ -1,30 +1,5 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
import { mock_theme, wait_for_page } from "./utils";
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `../../demo/${demo}/config.json`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mock_api(page: Page, body: Array<unknown>) {
|
||||
return page.route("**/run/predict", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: body[id]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
import { mock_theme, wait_for_page, mock_api, mock_demo } from "./utils";
|
||||
|
||||
test("renders the correct elements", async ({ page }) => {
|
||||
await mock_demo(page, "blocks_kinematics");
|
||||
|
@ -1,30 +1,5 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
import { mock_theme, wait_for_page } from "./utils";
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `../../demo/${demo}/config.json`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mock_api(page: Page, body: Array<unknown>) {
|
||||
return page.route("**/run/predict", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: body[id]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
import { mock_theme, wait_for_page, mock_api, mock_demo } from "./utils";
|
||||
|
||||
test("renders the correct elements", async ({ page }) => {
|
||||
await mock_demo(page, "blocks_page_load");
|
||||
|
@ -1,30 +1,5 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
import { mock_theme, wait_for_page } from "./utils";
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `../../demo/${demo}/config.json`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mock_api(page: Page, body: Array<unknown>) {
|
||||
return page.route("**/run/predict", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: body[id]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
import { mock_theme, wait_for_page, mock_api, mock_demo } from "./utils";
|
||||
|
||||
test("renders the correct elements", async ({ page }) => {
|
||||
await mock_demo(page, "blocks_xray");
|
||||
|
@ -1,30 +1,5 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
import { mock_theme, wait_for_page } from "./utils";
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `../../demo/${demo}/config.json`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mock_api(page: Page, body: Array<unknown>) {
|
||||
return page.route("**/run/predict", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: body[id]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
import { mock_theme, wait_for_page, mock_api, mock_demo } from "./utils";
|
||||
|
||||
test("a component acts as both input and output", async ({ page }) => {
|
||||
await mock_demo(page, "input_output");
|
||||
|
@ -1,31 +1,6 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
import { BASE64_IMAGE, BASE64_AUDIO } from "./media_data";
|
||||
import { mock_theme, wait_for_page } from "./utils";
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `../../demo/${demo}/config.json`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mock_api(page: Page, body: Array<unknown>) {
|
||||
return page.route("**/run/predict", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: body[id]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
import { mock_theme, wait_for_page, mock_api, mock_demo } from "./utils";
|
||||
|
||||
test("test inputs", async ({ page }) => {
|
||||
await mock_demo(page, "kitchen_sink");
|
||||
@ -93,7 +68,8 @@ test("test outputs", async ({ page }) => {
|
||||
{
|
||||
name: "worldt30a4ike.mp4",
|
||||
data: ""
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: null,
|
||||
data: null
|
||||
}
|
||||
|
142
js/app/test/mocks/info-blocks_inputs.json
Normal file
142
js/app/test/mocks/info-blocks_inputs.json
Normal file
@ -0,0 +1,142 @@
|
||||
{
|
||||
"named_endpoints": {},
|
||||
"unnamed_endpoints": {
|
||||
"0": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "Input",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"example_input": "Howdy!",
|
||||
"serializer": "StringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Input 2",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"example_input": "Howdy!",
|
||||
"serializer": "StringSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "Output",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"serializer": "StringSerializable"
|
||||
}
|
||||
]
|
||||
},
|
||||
"1": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "parameter_6",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"example_input": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
|
||||
"serializer": "ImgSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "value_7",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"serializer": "ImgSerializable"
|
||||
}
|
||||
]
|
||||
},
|
||||
"2": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "parameter_10",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Dataset",
|
||||
"example_input": "Howdy!",
|
||||
"serializer": "StringSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "Input",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"serializer": "StringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Input 2",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"serializer": "StringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Output",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"serializer": "StringSerializable"
|
||||
}
|
||||
]
|
||||
},
|
||||
"3": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "parameter_12",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Dataset",
|
||||
"example_input": "Howdy!",
|
||||
"serializer": "StringSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "value_6",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"serializer": "ImgSerializable"
|
||||
},
|
||||
{
|
||||
"label": "value_7",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"serializer": "ImgSerializable"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
49
js/app/test/mocks/info-blocks_kinematics.json
Normal file
49
js/app/test/mocks/info-blocks_kinematics.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"named_endpoints": {},
|
||||
"unnamed_endpoints": {
|
||||
"0": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "Speed",
|
||||
"type": {
|
||||
"type": "number",
|
||||
"description": "numeric value between 1 and 30"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "int | float",
|
||||
"description": "numeric value between 1 and 30"
|
||||
},
|
||||
"component": "Slider",
|
||||
"example_input": 1,
|
||||
"serializer": "NumberSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Angle",
|
||||
"type": {
|
||||
"type": "number",
|
||||
"description": "numeric value between 0 and 90"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "int | float",
|
||||
"description": "numeric value between 0 and 90"
|
||||
},
|
||||
"component": "Slider",
|
||||
"example_input": 0,
|
||||
"serializer": "NumberSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "value_6",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Plot",
|
||||
"serializer": "JSONSerializable"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
26
js/app/test/mocks/info-blocks_page_load.json
Normal file
26
js/app/test/mocks/info-blocks_page_load.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"named_endpoints": {},
|
||||
"unnamed_endpoints": {
|
||||
"0": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "Name",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"example_input": "Howdy!",
|
||||
"serializer": "StringSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "Output",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"serializer": "StringSerializable"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
109
js/app/test/mocks/info-blocks_xray.json
Normal file
109
js/app/test/mocks/info-blocks_xray.json
Normal file
@ -0,0 +1,109 @@
|
||||
{
|
||||
"named_endpoints": {
|
||||
"/xray_model": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "Disease to Scan For",
|
||||
"type": { "type": "array", "items": { "type": "string" } },
|
||||
"python_type": { "type": "List[str]", "description": "" },
|
||||
"component": "Checkboxgroup",
|
||||
"example_input": "Covid",
|
||||
"serializer": "ListStringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "parameter_6",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"example_input": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
|
||||
"serializer": "ImgSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "value_7",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Json",
|
||||
"serializer": "JSONSerializable"
|
||||
}
|
||||
]
|
||||
},
|
||||
"/ct_model": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "Disease to Scan For",
|
||||
"type": { "type": "array", "items": { "type": "string" } },
|
||||
"python_type": { "type": "List[str]", "description": "" },
|
||||
"component": "Checkboxgroup",
|
||||
"example_input": "Covid",
|
||||
"serializer": "ListStringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "parameter_11",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"example_input": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
|
||||
"serializer": "ImgSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "value_12",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Json",
|
||||
"serializer": "JSONSerializable"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"unnamed_endpoints": {
|
||||
"2": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "parameter_12",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Json",
|
||||
"example_input": null,
|
||||
"serializer": "JSONSerializable"
|
||||
},
|
||||
{
|
||||
"label": "parameter_7",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Json",
|
||||
"example_input": null,
|
||||
"serializer": "JSONSerializable"
|
||||
}
|
||||
],
|
||||
"returns": []
|
||||
}
|
||||
}
|
||||
}
|
26
js/app/test/mocks/info-input_output.json
Normal file
26
js/app/test/mocks/info-input_output.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"named_endpoints": {},
|
||||
"unnamed_endpoints": {
|
||||
"0": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "Input-Output",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"example_input": "Howdy!",
|
||||
"serializer": "StringSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "Input-Output",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"serializer": "StringSerializable"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
628
js/app/test/mocks/info-kitchen_sink.json
Normal file
628
js/app/test/mocks/info-kitchen_sink.json
Normal file
@ -0,0 +1,628 @@
|
||||
{
|
||||
"named_endpoints": {
|
||||
"/predict": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "Textbox",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"example_input": "Howdy!",
|
||||
"serializer": "StringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Textbox 2",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"example_input": "Howdy!",
|
||||
"serializer": "StringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Number",
|
||||
"type": { "type": "number" },
|
||||
"python_type": { "type": "int | float", "description": "" },
|
||||
"component": "Number",
|
||||
"example_input": 5,
|
||||
"serializer": "NumberSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Slider: 10 - 20",
|
||||
"type": {
|
||||
"type": "number",
|
||||
"description": "numeric value between 10 and 20"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "int | float",
|
||||
"description": "numeric value between 10 and 20"
|
||||
},
|
||||
"component": "Slider",
|
||||
"example_input": 10,
|
||||
"serializer": "NumberSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Slider: step @ 0.04",
|
||||
"type": {
|
||||
"type": "number",
|
||||
"description": "numeric value between 0 and 20"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "int | float",
|
||||
"description": "numeric value between 0 and 20"
|
||||
},
|
||||
"component": "Slider",
|
||||
"example_input": 0,
|
||||
"serializer": "NumberSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Checkbox",
|
||||
"type": { "type": "boolean" },
|
||||
"python_type": { "type": "bool", "description": "" },
|
||||
"component": "Checkbox",
|
||||
"example_input": true,
|
||||
"serializer": "BooleanSerializable"
|
||||
},
|
||||
{
|
||||
"label": "CheckboxGroup",
|
||||
"type": { "type": "array", "items": { "type": "string" } },
|
||||
"python_type": { "type": "List[str]", "description": "" },
|
||||
"component": "Checkboxgroup",
|
||||
"example_input": "foo",
|
||||
"serializer": "ListStringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Radio",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Radio",
|
||||
"example_input": "foo",
|
||||
"serializer": "StringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Dropdown",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Option from: ['foo', 'bar', 'baz']"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "Option from: ['foo', 'bar', 'baz']"
|
||||
},
|
||||
"component": "Dropdown",
|
||||
"example_input": "foo",
|
||||
"serializer": "SimpleSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Multiselect Dropdown (Max choice: 2)",
|
||||
"type": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "List of options from: ['foo', 'bar', 'baz']"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "List[str]",
|
||||
"description": "List of options from: ['foo', 'bar', 'baz']"
|
||||
},
|
||||
"component": "Dropdown",
|
||||
"example_input": ["foo"],
|
||||
"serializer": "SimpleSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Image",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"example_input": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
|
||||
"serializer": "ImgSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Image w/ Cropper",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"example_input": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
|
||||
"serializer": "ImgSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Sketchpad",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"example_input": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
|
||||
"serializer": "ImgSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Webcam",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"example_input": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
|
||||
"serializer": "ImgSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Video",
|
||||
"type": {
|
||||
"oneOf": [
|
||||
{ "type": "string", "description": "filepath or URL to file" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "filepath or URL to file"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "name of file"
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to file"
|
||||
},
|
||||
"component": "Video",
|
||||
"example_input": "https://github.com/gradio-app/gradio/raw/main/test/test_files/video_sample.mp4",
|
||||
"serializer": "FileSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Audio",
|
||||
"type": {
|
||||
"oneOf": [
|
||||
{ "type": "string", "description": "filepath or URL to file" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to file"
|
||||
},
|
||||
"component": "Audio",
|
||||
"example_input": "https://github.com/gradio-app/gradio/raw/main/test/test_files/audio_sample.wav",
|
||||
"serializer": "FileSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Microphone",
|
||||
"type": {
|
||||
"oneOf": [
|
||||
{ "type": "string", "description": "filepath or URL to file" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to file"
|
||||
},
|
||||
"component": "Audio",
|
||||
"example_input": "https://github.com/gradio-app/gradio/raw/main/test/test_files/audio_sample.wav",
|
||||
"serializer": "FileSerializable"
|
||||
},
|
||||
{
|
||||
"label": "File",
|
||||
"type": {
|
||||
"oneOf": [
|
||||
{ "type": "string", "description": "filepath or URL to file" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to file"
|
||||
},
|
||||
"component": "File",
|
||||
"example_input": "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf",
|
||||
"serializer": "FileSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Dataframe",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Dataframe",
|
||||
"example_input": null,
|
||||
"serializer": "JSONSerializable"
|
||||
},
|
||||
{
|
||||
"label": "df2",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Timeseries",
|
||||
"example_input": null,
|
||||
"serializer": "JSONSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "Textbox",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Textbox",
|
||||
"serializer": "StringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Label",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Label",
|
||||
"serializer": "JSONSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Audio",
|
||||
"type": {
|
||||
"oneOf": [
|
||||
{ "type": "string", "description": "filepath or URL to file" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to file"
|
||||
},
|
||||
"component": "Audio",
|
||||
"serializer": "FileSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Image",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to image"
|
||||
},
|
||||
"component": "Image",
|
||||
"serializer": "ImgSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Video",
|
||||
"type": {
|
||||
"oneOf": [
|
||||
{ "type": "string", "description": "filepath or URL to file" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "filepath or URL to file"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "name of file"
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to file"
|
||||
},
|
||||
"component": "Video",
|
||||
"serializer": "FileSerializable"
|
||||
},
|
||||
{
|
||||
"label": "HighlightedText",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Highlightedtext",
|
||||
"serializer": "JSONSerializable"
|
||||
},
|
||||
{
|
||||
"label": "HighlightedText",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Highlightedtext",
|
||||
"serializer": "JSONSerializable"
|
||||
},
|
||||
{
|
||||
"label": "JSON",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Json",
|
||||
"serializer": "JSONSerializable"
|
||||
},
|
||||
{
|
||||
"label": "HTML",
|
||||
"type": { "type": "string" },
|
||||
"python_type": { "type": "str", "description": "" },
|
||||
"component": "Html",
|
||||
"serializer": "StringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "File",
|
||||
"type": {
|
||||
"oneOf": [
|
||||
{ "type": "string", "description": "filepath or URL to file" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath or URL to file"
|
||||
},
|
||||
"component": "File",
|
||||
"serializer": "FileSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Dataframe",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Dataframe",
|
||||
"serializer": "JSONSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Numpy",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Dataframe",
|
||||
"serializer": "JSONSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Timeseries",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Timeseries",
|
||||
"serializer": "JSONSerializable"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"unnamed_endpoints": {}
|
||||
}
|
79
js/app/test/mocks/info-outbreak_forecast.json
Normal file
79
js/app/test/mocks/info-outbreak_forecast.json
Normal file
@ -0,0 +1,79 @@
|
||||
{
|
||||
"named_endpoints": {
|
||||
"/predict": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "Plot Type",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Option from: ['Matplotlib', 'Plotly', 'Altair']"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "Option from: ['Matplotlib', 'Plotly', 'Altair']"
|
||||
},
|
||||
"component": "Dropdown",
|
||||
"example_input": "Matplotlib",
|
||||
"serializer": "SimpleSerializable"
|
||||
},
|
||||
{
|
||||
"label": "R",
|
||||
"type": {
|
||||
"type": "number",
|
||||
"description": "numeric value between 1 and 4"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "int | float",
|
||||
"description": "numeric value between 1 and 4"
|
||||
},
|
||||
"component": "Slider",
|
||||
"example_input": 1,
|
||||
"serializer": "NumberSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Month",
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Option from: ['January', 'February', 'March', 'April', 'May']"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "Option from: ['January', 'February', 'March', 'April', 'May']"
|
||||
},
|
||||
"component": "Dropdown",
|
||||
"example_input": "January",
|
||||
"serializer": "SimpleSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Countries",
|
||||
"type": { "type": "array", "items": { "type": "string" } },
|
||||
"python_type": { "type": "List[str]", "description": "" },
|
||||
"component": "Checkboxgroup",
|
||||
"example_input": "USA",
|
||||
"serializer": "ListStringSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Social Distancing?",
|
||||
"type": { "type": "boolean" },
|
||||
"python_type": { "type": "bool", "description": "" },
|
||||
"component": "Checkbox",
|
||||
"example_input": true,
|
||||
"serializer": "BooleanSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "output",
|
||||
"type": { "type": {}, "description": "any valid json" },
|
||||
"python_type": {
|
||||
"type": "str",
|
||||
"description": "filepath to JSON file"
|
||||
},
|
||||
"component": "Plot",
|
||||
"serializer": "JSONSerializable"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"unnamed_endpoints": {}
|
||||
}
|
39
js/app/test/mocks/info-slider_release.json
Normal file
39
js/app/test/mocks/info-slider_release.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"named_endpoints": {
|
||||
"/predict": {
|
||||
"parameters": [
|
||||
{
|
||||
"label": "parameter_1",
|
||||
"type": {
|
||||
"type": "number",
|
||||
"description": "numeric value between 0 and 100"
|
||||
},
|
||||
"python_type": {
|
||||
"type": "int | float",
|
||||
"description": "numeric value between 0 and 100"
|
||||
},
|
||||
"component": "Slider",
|
||||
"example_input": 0,
|
||||
"serializer": "NumberSerializable"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
{
|
||||
"label": "On release",
|
||||
"type": { "type": "number" },
|
||||
"python_type": { "type": "int | float", "description": "" },
|
||||
"component": "Number",
|
||||
"serializer": "NumberSerializable"
|
||||
},
|
||||
{
|
||||
"label": "Number of events fired",
|
||||
"type": { "type": "number" },
|
||||
"python_type": { "type": "int | float", "description": "" },
|
||||
"component": "Number",
|
||||
"serializer": "NumberSerializable"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"unnamed_endpoints": {}
|
||||
}
|
@ -1,31 +1,6 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
import { BASE64_PLOT_IMG } from "./media_data";
|
||||
import { mock_theme, wait_for_page } from "./utils";
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `../../demo/${demo}/config.json`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mock_api(page: Page, body: Array<unknown>) {
|
||||
return page.route("**/run/predict", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: body[id]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
import { mock_theme, wait_for_page, mock_api, mock_demo } from "./utils";
|
||||
|
||||
test("matplotlib", async ({ page }) => {
|
||||
await mock_demo(page, "outbreak_forecast");
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { test, expect, Page, Locator } from "@playwright/test";
|
||||
import { mock_theme, wait_for_page } from "./utils";
|
||||
import { mock_theme, wait_for_page, mock_api, mock_demo } from "./utils";
|
||||
|
||||
//taken from: https://github.com/microsoft/playwright/issues/20032
|
||||
async function changeSlider(
|
||||
@ -33,33 +33,9 @@ async function changeSlider(
|
||||
await page.mouse.up();
|
||||
}
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `../../demo/${demo}/config.json`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mock_api(page: Page) {
|
||||
return page.route("**/run/predict", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: [70, null, 1]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test("slider release", async ({ page }) => {
|
||||
await mock_demo(page, "slider_release");
|
||||
await mock_api(page);
|
||||
await mock_api(page, [[70, null, 1]]);
|
||||
await mock_theme(page);
|
||||
await wait_for_page(page);
|
||||
const slider = page.getByLabel("Slider");
|
||||
|
@ -15,3 +15,38 @@ export async function wait_for_page(page: Page) {
|
||||
await page.goto("http://localhost:9876");
|
||||
await page.waitForResponse("**/theme.css");
|
||||
}
|
||||
|
||||
export function mock_demo(page: Page, demo: string) {
|
||||
return Promise.all([
|
||||
page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `../../demo/${demo}/config.json`
|
||||
});
|
||||
}),
|
||||
page.route("**/info", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
path: `./test/mocks/info-${demo}.json`
|
||||
});
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
export function mock_api(page: Page, body?: Array<unknown>) {
|
||||
return page.route("**/run/predict", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: body === undefined ? [] : body[id]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ const TEST_CDN = !!process.env.TEST_CDN;
|
||||
const CDN = TEST_CDN
|
||||
? "http://localhost:4321/"
|
||||
: `https://gradio.s3-us-west-2.amazonaws.com/${GRADIO_VERSION}/`;
|
||||
const TEST_MODE = process.env.TEST_MODE || "happy-dom";
|
||||
|
||||
//@ts-ignore
|
||||
export default defineConfig(({ mode }) => {
|
||||
@ -116,8 +117,11 @@ export default defineConfig(({ mode }) => {
|
||||
handle_ce_css()
|
||||
],
|
||||
test: {
|
||||
environment: "happy-dom",
|
||||
include: ["**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
||||
environment: TEST_MODE,
|
||||
include:
|
||||
TEST_MODE === "node"
|
||||
? ["**/*.node-test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"]
|
||||
: ["**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
||||
globals: true
|
||||
}
|
||||
};
|
||||
|
@ -10,7 +10,7 @@
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
line-height: 1.5;
|
||||
font-family: -var(--font-sans);
|
||||
font-family: var(--font-sans);
|
||||
-moz-tab-size: 4;
|
||||
tab-size: 2;
|
||||
}
|
||||
|
14
package.json
14
package.json
@ -13,11 +13,12 @@
|
||||
"preview:cdn-server": "sirv ../gradio/templates/cdn --single --port=4321 --cors",
|
||||
"preview:cdn-app": "pnpm --filter @gradio/cdn-test dev",
|
||||
"preview:cdn-local": "run-p preview:cdn-server preview:cdn-app",
|
||||
"format:check": "prettier --config .config/.prettierrc.json --ignore-path .config/.prettierignore --check --plugin-search-dir=. .",
|
||||
"format:write": "prettier --config .config/.prettierrc.json --ignore-path .config/.prettierignore --write --plugin-search-dir=. .",
|
||||
"format:check": "prettier --ignore-path .config/.prettierignore --check --plugin-search-dir=. .",
|
||||
"format:write": "prettier --ignore-path .config/.prettierignore --write --plugin-search-dir=. .",
|
||||
"ts:check": "svelte-check --tsconfig tsconfig.json",
|
||||
"test": "pnpm --filter @gradio/client build && vitest dev --config .config/vitest.config.ts",
|
||||
"test:run": "pnpm --filter @gradio/client build && vitest run --config .config/vitest.config.ts",
|
||||
"test:node": "TEST_MODE=node pnpm vitest run --config .config/vitest.config.ts",
|
||||
"test:browser": "pnpm --filter @gradio/app test:browser:full",
|
||||
"test:browser:full": "run-s build test:browser",
|
||||
"test:browser:debug": "pnpm --filter @gradio/app test:browser:debug",
|
||||
@ -70,5 +71,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/three": "^0.138.0"
|
||||
},
|
||||
"prettier": {
|
||||
"useTabs": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 80,
|
||||
"pluginSearchDirs": [
|
||||
".."
|
||||
]
|
||||
}
|
||||
}
|
||||
|
3540
pnpm-lock.yaml
generated
3540
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,142 +1,99 @@
|
||||
{
|
||||
"asset": {
|
||||
"generator": "COLLADA2GLTF",
|
||||
"version": "2.0"
|
||||
},
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"nodes": [
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"children": [
|
||||
1
|
||||
],
|
||||
"matrix": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"mesh": 0
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 1,
|
||||
"POSITION": 2
|
||||
},
|
||||
"indices": 0,
|
||||
"mode": 4,
|
||||
"material": 0
|
||||
}
|
||||
],
|
||||
"name": "Mesh"
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5123,
|
||||
"count": 36,
|
||||
"max": [
|
||||
23
|
||||
],
|
||||
"min": [
|
||||
0
|
||||
],
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
-1.0,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 288,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [
|
||||
0.5,
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"min": [
|
||||
-0.5,
|
||||
-0.5,
|
||||
-0.5
|
||||
],
|
||||
"type": "VEC3"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.800000011920929,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.0
|
||||
},
|
||||
"name": "Red"
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 576,
|
||||
"byteLength": 72,
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 576,
|
||||
"byteStride": 12,
|
||||
"target": 34962
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 648,
|
||||
"uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
|
||||
}
|
||||
]
|
||||
"asset": {
|
||||
"generator": "COLLADA2GLTF",
|
||||
"version": "2.0"
|
||||
},
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"nodes": [0]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"children": [1],
|
||||
"matrix": [
|
||||
1.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"mesh": 0
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 1,
|
||||
"POSITION": 2
|
||||
},
|
||||
"indices": 0,
|
||||
"mode": 4,
|
||||
"material": 0
|
||||
}
|
||||
],
|
||||
"name": "Mesh"
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5123,
|
||||
"count": 36,
|
||||
"max": [23],
|
||||
"min": [0],
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [1.0, 1.0, 1.0],
|
||||
"min": [-1.0, -1.0, -1.0],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 288,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [0.5, 0.5, 0.5],
|
||||
"min": [-0.5, -0.5, -0.5],
|
||||
"type": "VEC3"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [0.800000011920929, 0.0, 0.0, 1.0],
|
||||
"metallicFactor": 0.0
|
||||
},
|
||||
"name": "Red"
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 576,
|
||||
"byteLength": 72,
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 576,
|
||||
"byteStride": 12,
|
||||
"target": 34962
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 648,
|
||||
"uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +1,10 @@
|
||||
{"label": "web site", "confidences": [{"label": "web site", "confidence": 0.6625185608863831}, {"label": "envelope", "confidence": 0.02277352288365364}, {"label": "menu", "confidence": 0.012591145932674408}, {"label": "monitor", "confidence": 0.012062293477356434}, {"label": "washer", "confidence": 0.008587697520852089}]}
|
||||
{
|
||||
"label": "web site",
|
||||
"confidences": [
|
||||
{ "label": "web site", "confidence": 0.6625185608863831 },
|
||||
{ "label": "envelope", "confidence": 0.02277352288365364 },
|
||||
{ "label": "menu", "confidence": 0.012591145932674408 },
|
||||
{ "label": "monitor", "confidence": 0.012062293477356434 },
|
||||
{ "label": "washer", "confidence": 0.008587697520852089 }
|
||||
]
|
||||
}
|
||||
|
@ -38,7 +38,9 @@
|
||||
"**/test/**/*",
|
||||
"**/website/**/*",
|
||||
"**/node_modules/**/*",
|
||||
"**/venv/**/*"
|
||||
"**/venv/**/*",
|
||||
"client/js/**",
|
||||
"js/_spaces-test/**"
|
||||
],
|
||||
"include": [
|
||||
"**/*.d.ts",
|
||||
|
@ -1,14 +1,19 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from gradio_client.documentation import document_cls, generate_documentation
|
||||
from gradio.events import EventListener
|
||||
import markdown2
|
||||
|
||||
|
||||
from ..guides import guides
|
||||
|
||||
DIR = os.path.dirname(__file__)
|
||||
GRADIO_DIR = "../../"
|
||||
TEMPLATE_FILE = os.path.join(DIR, "template.html")
|
||||
TEMP_TEMPLATE = os.path.join(DIR, "temporary_template.html")
|
||||
DEMOS_DIR = os.path.join(GRADIO_DIR, "demo")
|
||||
JS_CLIENT_README = os.path.join(GRADIO_DIR, "client", "js", "README.md")
|
||||
|
||||
docs = generate_documentation()
|
||||
docs["component"].sort(key=lambda x: x["name"])
|
||||
@ -141,9 +146,34 @@ def find_cls(target_cls):
|
||||
raise ValueError("Class not found")
|
||||
|
||||
|
||||
def build_js_client():
|
||||
with open(JS_CLIENT_README, "r") as f:
|
||||
js_docs = f.read()
|
||||
js_docs = re.sub(
|
||||
r"```([a-z]+)\n",
|
||||
lambda x: f"<div class='codeblock'><pre><code class='lang-{x.group(1)}'>",
|
||||
js_docs,
|
||||
)
|
||||
js_docs = re.sub(r"```", "</code></pre></div>", js_docs)
|
||||
with open(TEMP_TEMPLATE, "w") as temp_html:
|
||||
temp_html.write(
|
||||
markdown2.markdown(
|
||||
js_docs,
|
||||
extras=[
|
||||
"target-blank-links",
|
||||
"header-ids",
|
||||
"tables",
|
||||
"fenced-code-blocks",
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
def build(output_dir, jinja_env, gradio_wheel_url, gradio_version):
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
template = jinja_env.get_template("docs/template.html")
|
||||
build_js_client()
|
||||
output = template.render(
|
||||
docs=docs,
|
||||
ordered_events=ordered_events,
|
||||
@ -168,6 +198,7 @@ def build(output_dir, jinja_env, gradio_wheel_url, gradio_version):
|
||||
|
||||
|
||||
def build_pip_template(version, jinja_env):
|
||||
build_js_client()
|
||||
template = jinja_env.get_template("docs/template.html")
|
||||
output = template.render(
|
||||
docs=docs, find_cls=find_cls, version="pip", gradio_version=version, canonical_suffix="", ordered_events=ordered_events
|
||||
|
@ -163,6 +163,8 @@
|
||||
<a class="thinner-link px-4 pl-8 block" href="#{{'gradio_client-' + component['name'].lower() }}">{{ component['name'] }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a class="thin-link px-4 block" href="#javascript-client">Javascript</a>
|
||||
|
||||
</div>
|
||||
<div class="flex flex-col w-full min-w-full lg:w-10/12 lg:min-w-0">
|
||||
<div>
|
||||
@ -408,6 +410,8 @@
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</section>
|
||||
<section id="javascript-client" class="pt-2 flex flex-col gap-10 mb-8">
|
||||
{% include "docs/temporary_template.html" %}
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@ -568,4 +572,39 @@
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<style>
|
||||
#javascript-client-library {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem; /* 36px */
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5rem; /* 24px */
|
||||
line-height: 2rem; /* 32px */
|
||||
font-weight: 300;
|
||||
padding-top: 0.5rem; /* 16px */
|
||||
padding-bottom: 0.5rem; /* 16px */
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.25rem; /* 20px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.125rem; /* 18px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</html>
|
||||
|
135
website/homepage/src/docs/temporary_template.html
Normal file
135
website/homepage/src/docs/temporary_template.html
Normal file
@ -0,0 +1,135 @@
|
||||
<h2 id="javascript-client-library">JavaScript Client Library</h2>
|
||||
|
||||
<p>A javascript (and typescript) client to call Gradio APIs.</p>
|
||||
|
||||
<h2 id="installation">Installation</h2>
|
||||
|
||||
<p>The Gradio JavaScript client is available on npm as <code>@gradio/client</code>. You can install it as below:</p>
|
||||
|
||||
<div class='codeblock'><pre><code class='lang-sh'>pnpm add @gradio/client
|
||||
# or npm i @gradio/client
|
||||
</code></pre></div>
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<p>The JavaScript Gradio Client exposes 2 named imports, <code>client</code> and <code>duplicate</code>.</p>
|
||||
|
||||
<h3 id="client"><code>client</code></h3>
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
<p>The simplest example looks like this:</p>
|
||||
|
||||
<div class='codeblock'><pre><code class='lang-ts'>import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const result = await app.predict(payload);
|
||||
</code></pre></div>
|
||||
|
||||
<p>This function accaepts two parameters: <code>source</code> and <code>options</code>:</p>
|
||||
|
||||
<h4 id="source"><code>source</code></h4>
|
||||
|
||||
<p>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:</p>
|
||||
|
||||
<div class='codeblock'><pre><code class='lang-ts'>client("user/space-name");
|
||||
</code></pre></div>
|
||||
|
||||
<h4 id="options"><code>options</code></h4>
|
||||
|
||||
<p>The options object can optionally be passed a second parameter. This object has two properties, <code>hf_token</code> and <code>status_callback</code>.</p>
|
||||
|
||||
<h5 id="hf_token"><code>hf_token</code></h5>
|
||||
|
||||
<p>This should be a hugging face personal access token and is required if you wish to make calls to a private gradio api. This option is optional and should be a string starting with <code>"hf_"</code>.</p>
|
||||
|
||||
<p>Example:</p>
|
||||
|
||||
<div class='codeblock'><pre><code class='lang-ts'>import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name", { hf_token: "hf_..." });
|
||||
</code></pre></div>
|
||||
|
||||
<h5 id="status_callback"><code>status_callback</code></h5>
|
||||
|
||||
<p>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 hugginface space then this function will do nothing.</p>
|
||||
|
||||
<p><strong>Additional context</strong></p>
|
||||
|
||||
<p>Applications hosted on Hugginface spaces can be in a number of different states, as this is 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 <code>status_callback</code> 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.</p>
|
||||
|
||||
<div class='codeblock'><pre><code class='lang-ts'>import { client, type SpaceStatus } from "@gradio/client";
|
||||
|
||||
const app = await client("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)
|
||||
});
|
||||
</code></pre></div>
|
||||
|
||||
<div class='codeblock'><pre><code class='lang-ts'>interface SpaceStatusNormal {
|
||||
status: "sleeping" | "running" | "building" | "error" | "stopped";
|
||||
detail:
|
||||
| "SLEEPING"
|
||||
| "RUNNING"
|
||||
| "RUNNING_BUILDING"
|
||||
| "BUILDING"
|
||||
| "NOT_FOUND";
|
||||
load_status: "pending" | "error" | "complete" | "generating";
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface SpaceStatusError {
|
||||
status: "space_error";
|
||||
detail: "NO_APP_FILE" | "CONFIG_ERROR" | "BUILD_ERROR" | "RUNTIME_ERROR";
|
||||
load_status: "error";
|
||||
message: string;
|
||||
discussions_enabled: boolean;
|
||||
}
|
||||
|
||||
type SpaceStatus = SpaceStatusNormal | SpaceStatusError;
|
||||
</code></pre></div>
|
||||
|
||||
<p>The gradio client returns an object with a number of utility methods and properties:</p>
|
||||
|
||||
<h4 id="predict"><code>predict</code></h4>
|
||||
|
||||
<p>The <code>predict</code> method allows you to call an api endpoint and get a prediction:</p>
|
||||
|
||||
<div class='codeblock'><pre><code class='lang-ts'>import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const result = await app.predict(payload);
|
||||
</code></pre></div>
|
||||
|
||||
<h4 id="submit"><code>submit</code></h4>
|
||||
|
||||
<p>The submit method provides a more flexible way to call a gradio enpoint, providing you with status updates about the current progress of the prediction as well as supporting more complex endpoints types.</p>
|
||||
|
||||
<div class='codeblock'><pre><code class='lang-ts'>import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const result = app
|
||||
.submit(payload)
|
||||
.on("data", (data) => console.log(data))
|
||||
.on("status", (status) => console.log(status));
|
||||
</code></pre></div>
|
||||
|
||||
<h4 id="info"><code>info</code></h4>
|
||||
|
||||
<p>The <code>info</code> method provides details about the api you are connected too. It returns a javascript object of all named enpoints, unnamed endpoints and what values they accept and return.</p>
|
||||
|
||||
<div class='codeblock'><pre><code class='lang-ts'>import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
const api_info = await app.info();
|
||||
</code></pre></div>
|
||||
|
||||
<h4 id="config"><code>config</code></h4>
|
||||
|
||||
<p>The <code>config</code> property contain the configuration for the gradio app you are connected to. This object may contain useful meta information about the application</p>
|
||||
|
||||
<div class='codeblock'><pre><code class='lang-ts'>import { client } from "@gradio/client";
|
||||
|
||||
const app = await client("user/space-name");
|
||||
console.log(app.config);
|
||||
</code></pre></div>
|
Loading…
x
Reference in New Issue
Block a user