3712 js client (#3899)

This commit is contained in:
pngwn 2023-05-12 16:22:25 +01:00 committed by GitHub
parent fcf744a4a9
commit 963c2d26ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 7515 additions and 4045 deletions

View File

@ -11,6 +11,7 @@
**/.venv/**
**/.github/**
**/guides/**
**/test/**
**/.mypy_cache/**
**/*.md
**/js/_space-test/**
js/app/test/gradio_cached_examples/**

3
.gitignore vendored
View File

@ -58,4 +58,5 @@ gradio/frpc_*
# js
node_modules
public/build/
test-results
test-results
client/js/test.js

View File

@ -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": ["."],

View File

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

View File

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

View File

@ -13,6 +13,8 @@
"./package.json": "./package.json"
},
"dependencies": {
"bufferutil": "^4.0.7",
"semiver": "^1.1.0",
"ws": "^8.13.0"
},
"devDependencies": {

View 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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import { defineConfig } from "vite";
export default defineConfig({
build: {
minify: true,
// minify: true,
lib: {
entry: "src/index.ts",
formats: ["es"]

View File

@ -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.
![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/gradio-guides/whisper-screenshot.jpg)
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
View 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
View File

@ -0,0 +1,2 @@
engine-strict=true
resolution-mode=highest

View 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
View 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.

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

View 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
View 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 {};

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

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

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

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

View 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

View 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

View 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&amp;display=swap"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&amp;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>

View File

@ -0,0 +1 @@
<h2>Embeds</h2>

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

View File

@ -0,0 +1,3 @@
<h2>Client Node</h2>
<p>coming soon.</p>

View File

@ -0,0 +1,3 @@
<h2>Embeds</h2>
<p>Coming soon.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View 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;

View File

@ -0,0 +1,6 @@
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [sveltekit()]
});

View File

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

View File

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

View File

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

View File

@ -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 &lbrace; client &rbrace; from "@gradio/client";
<!--
-->
async function run() &lbrace;
{#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);
&rbrace;
run();
</pre>
</div>
{/if}
</code>
</Block>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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"
}
]
}
}
}

View 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"
}
]
}
}
}

View 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"
}
]
}
}
}

View 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": []
}
}
}

View 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"
}
]
}
}
}

View 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": {}
}

View 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": {}
}

View 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": {}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -38,7 +38,9 @@
"**/test/**/*",
"**/website/**/*",
"**/node_modules/**/*",
"**/venv/**/*"
"**/venv/**/*",
"client/js/**",
"js/_spaces-test/**"
],
"include": [
"**/*.d.ts",

View File

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

View File

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

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