@gradio/client 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +49 -43
  3. package/dist/client.d.ts +63 -70
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/constants.d.ts +23 -0
  6. package/dist/constants.d.ts.map +1 -0
  7. package/dist/helpers/api_info.d.ts +25 -0
  8. package/dist/helpers/api_info.d.ts.map +1 -0
  9. package/dist/helpers/data.d.ts +8 -0
  10. package/dist/helpers/data.d.ts.map +1 -0
  11. package/dist/{utils.d.ts → helpers/init_helpers.d.ts} +6 -18
  12. package/dist/helpers/init_helpers.d.ts.map +1 -0
  13. package/dist/helpers/spaces.d.ts +7 -0
  14. package/dist/helpers/spaces.d.ts.map +1 -0
  15. package/dist/index.d.ts +8 -4
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1628 -1398
  18. package/dist/test/handlers.d.ts +3 -0
  19. package/dist/test/handlers.d.ts.map +1 -0
  20. package/dist/test/mock_eventsource.d.ts +2 -0
  21. package/dist/test/mock_eventsource.d.ts.map +1 -0
  22. package/dist/test/server.d.ts +2 -0
  23. package/dist/test/server.d.ts.map +1 -0
  24. package/dist/test/test_data.d.ts +76 -0
  25. package/dist/test/test_data.d.ts.map +1 -0
  26. package/dist/types.d.ts +153 -49
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/upload.d.ts +2 -2
  29. package/dist/upload.d.ts.map +1 -1
  30. package/dist/utils/duplicate.d.ts +4 -0
  31. package/dist/utils/duplicate.d.ts.map +1 -0
  32. package/dist/utils/handle_blob.d.ts +4 -0
  33. package/dist/utils/handle_blob.d.ts.map +1 -0
  34. package/dist/utils/post_data.d.ts +4 -0
  35. package/dist/utils/post_data.d.ts.map +1 -0
  36. package/dist/utils/predict.d.ts +4 -0
  37. package/dist/utils/predict.d.ts.map +1 -0
  38. package/dist/utils/stream.d.ts +8 -0
  39. package/dist/utils/stream.d.ts.map +1 -0
  40. package/dist/utils/submit.d.ts +4 -0
  41. package/dist/utils/submit.d.ts.map +1 -0
  42. package/dist/utils/upload_files.d.ts +4 -0
  43. package/dist/utils/upload_files.d.ts.map +1 -0
  44. package/dist/utils/view_api.d.ts +3 -0
  45. package/dist/utils/view_api.d.ts.map +1 -0
  46. package/dist/{wrapper-6f348d45.js → wrapper-CviSselG.js} +259 -17
  47. package/package.json +10 -3
  48. package/src/client.ts +314 -1691
  49. package/src/constants.ts +27 -0
  50. package/src/globals.d.ts +2 -21
  51. package/src/helpers/api_info.ts +300 -0
  52. package/src/helpers/data.ts +133 -0
  53. package/src/helpers/init_helpers.ts +130 -0
  54. package/src/helpers/spaces.ts +197 -0
  55. package/src/index.ts +16 -10
  56. package/src/test/api_info.test.ts +456 -0
  57. package/src/test/data.test.ts +281 -0
  58. package/src/test/handlers.ts +438 -0
  59. package/src/test/init.test.ts +139 -0
  60. package/src/test/init_helpers.test.ts +94 -0
  61. package/src/test/mock_eventsource.ts +11 -0
  62. package/src/test/post_data.test.ts +45 -0
  63. package/src/test/server.ts +6 -0
  64. package/src/test/spaces.test.ts +145 -0
  65. package/src/test/stream.test.ts +67 -0
  66. package/src/test/test_data.ts +557 -0
  67. package/src/test/upload_files.test.ts +42 -0
  68. package/src/test/view_api.test.ts +53 -0
  69. package/src/types.ts +201 -59
  70. package/src/upload.ts +19 -15
  71. package/src/utils/duplicate.ts +104 -0
  72. package/src/utils/handle_blob.ts +47 -0
  73. package/src/utils/post_data.ts +37 -0
  74. package/src/utils/predict.ts +56 -0
  75. package/src/utils/stream.ts +175 -0
  76. package/src/utils/submit.ts +697 -0
  77. package/src/utils/upload_files.ts +51 -0
  78. package/src/utils/view_api.ts +67 -0
  79. package/src/vite-env.d.ts +1 -0
  80. package/tsconfig.json +15 -2
  81. package/vite.config.js +11 -17
  82. package/dist/utils.d.ts.map +0 -1
  83. package/src/client.node-test.ts +0 -172
  84. package/src/utils.ts +0 -314
package/src/client.ts CHANGED
@@ -1,1769 +1,392 @@
1
- //@ts-nocheck
2
-
3
- import semiver from "semiver";
4
-
5
- import {
6
- process_endpoint,
7
- RE_SPACE_NAME,
8
- map_names_to_ids,
9
- discussions_enabled,
10
- get_space_hardware,
11
- set_space_hardware,
12
- set_space_timeout,
13
- hardware_types,
14
- resolve_root,
15
- apply_diff,
16
- post_message
17
- } from "./utils.js";
18
-
19
1
  import type {
20
- EventType,
21
- EventListener,
22
- ListenerMap,
23
- Event,
24
- Payload,
25
- PostResponse,
26
- UploadResponse,
27
- Status,
2
+ ApiData,
3
+ ApiInfo,
4
+ ClientOptions,
5
+ Config,
6
+ DuplicateOptions,
7
+ EndpointInfo,
8
+ JsApiData,
28
9
  SpaceStatus,
29
- SpaceStatusCallback
30
- } from "./types.js";
31
-
32
- import { FileData } from "./upload";
33
-
34
- import type { Config } from "./types.js";
35
-
36
- type event = <K extends EventType>(
37
- eventType: K,
38
- listener: EventListener<K>
39
- ) => SubmitReturn;
40
- type predict = (
41
- endpoint: string | number,
42
- data?: unknown[],
43
- event_data?: unknown
44
- ) => Promise<unknown>;
45
-
46
- export type client_return = {
47
- predict: predict;
48
- config: Config;
49
- submit: (
50
- endpoint: string | number,
51
- data?: unknown[],
52
- event_data?: unknown,
53
- trigger_id?: number | null
54
- ) => SubmitReturn;
55
- component_server: (
56
- component_id: number,
57
- fn_name: string,
58
- data: unknown[]
59
- ) => any;
60
- view_api: (c?: Config) => Promise<ApiInfo<JsApiData>>;
61
- };
62
-
63
- type SubmitReturn = {
64
- on: event;
65
- off: event;
66
- cancel: () => Promise<void>;
67
- destroy: () => void;
68
- };
69
-
70
- const QUEUE_FULL_MSG = "This application is too busy. Keep trying!";
71
- const BROKEN_CONNECTION_MSG = "Connection errored out.";
72
-
73
- export let NodeBlob;
74
-
75
- export async function duplicate(
76
- app_reference: string,
77
- options: {
78
- hf_token: `hf_${string}`;
79
- private?: boolean;
80
- status_callback: SpaceStatusCallback;
81
- hardware?: (typeof hardware_types)[number];
82
- timeout?: number;
83
- }
84
- ): Promise<client_return> {
85
- const { hf_token, private: _private, hardware, timeout } = options;
86
-
87
- if (hardware && !hardware_types.includes(hardware)) {
88
- throw new Error(
89
- `Invalid hardware type provided. Valid types are: ${hardware_types
90
- .map((v) => `"${v}"`)
91
- .join(",")}.`
92
- );
10
+ Status,
11
+ SubmitReturn,
12
+ UploadResponse,
13
+ client_return
14
+ } from "./types";
15
+ import { view_api } from "./utils/view_api";
16
+ import { upload_files } from "./utils/upload_files";
17
+ import { upload, FileData } from "./upload";
18
+ import { handle_blob } from "./utils/handle_blob";
19
+ import { post_data } from "./utils/post_data";
20
+ import { predict } from "./utils/predict";
21
+ import { duplicate } from "./utils/duplicate";
22
+ import { submit } from "./utils/submit";
23
+ import { RE_SPACE_NAME, process_endpoint } from "./helpers/api_info";
24
+ import {
25
+ map_names_to_ids,
26
+ resolve_config,
27
+ get_jwt
28
+ } from "./helpers/init_helpers";
29
+ import { check_space_status } from "./helpers/spaces";
30
+ import { open_stream } from "./utils/stream";
31
+ import { API_INFO_ERROR_MSG, CONFIG_ERROR_MSG } from "./constants";
32
+
33
+ export class NodeBlob extends Blob {
34
+ constructor(blobParts?: BlobPart[], options?: BlobPropertyBag) {
35
+ super(blobParts, options);
93
36
  }
94
- const headers = {
95
- Authorization: `Bearer ${hf_token}`
96
- };
97
-
98
- const user = (
99
- await (
100
- await fetch(`https://huggingface.co/api/whoami-v2`, {
101
- headers
102
- })
103
- ).json()
104
- ).name;
105
-
106
- const space_name = app_reference.split("/")[1];
107
- const body: {
108
- repository: string;
109
- private?: boolean;
110
- } = {
111
- repository: `${user}/${space_name}`
112
- };
37
+ }
113
38
 
114
- if (_private) {
115
- body.private = true;
39
+ export class Client {
40
+ app_reference: string;
41
+ options: ClientOptions;
42
+
43
+ config: Config | undefined;
44
+ api_info: ApiInfo<JsApiData> | undefined;
45
+ api_map: Record<string, number> = {};
46
+ session_hash: string = Math.random().toString(36).substring(2);
47
+ jwt: string | false = false;
48
+ last_status: Record<string, Status["stage"]> = {};
49
+
50
+ // streaming
51
+ stream_status = { open: false };
52
+ pending_stream_messages: Record<string, any[][]> = {};
53
+ pending_diff_streams: Record<string, any[][]> = {};
54
+ event_callbacks: Record<string, (data?: unknown) => Promise<void>> = {};
55
+ unclosed_events: Set<string> = new Set();
56
+ heartbeat_event: EventSource | null = null;
57
+
58
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
59
+ return fetch(input, init);
116
60
  }
117
61
 
118
- try {
119
- const response = await fetch(
120
- `https://huggingface.co/api/spaces/${app_reference}/duplicate`,
121
- {
122
- method: "POST",
123
- headers: { "Content-Type": "application/json", ...headers },
124
- body: JSON.stringify(body)
125
- }
126
- );
127
-
128
- if (response.status === 409) {
129
- return client(`${user}/${space_name}`, options);
130
- }
131
- const duplicated_space = await response.json();
132
-
133
- let original_hardware;
134
-
135
- if (!hardware) {
136
- original_hardware = await get_space_hardware(app_reference, hf_token);
62
+ stream_factory(url: URL): EventSource | null {
63
+ if (typeof window === "undefined" || typeof EventSource === "undefined") {
64
+ import("eventsource")
65
+ .then((EventSourceModule) => {
66
+ return new EventSourceModule.default(url.toString());
67
+ })
68
+ .catch((error) =>
69
+ console.error("Failed to load EventSource module:", error)
70
+ );
71
+ } else {
72
+ return new EventSource(url.toString());
137
73
  }
138
-
139
- const requested_hardware = hardware || original_hardware || "cpu-basic";
140
- await set_space_hardware(
141
- `${user}/${space_name}`,
142
- requested_hardware,
143
- hf_token
144
- );
145
-
146
- await set_space_timeout(`${user}/${space_name}`, timeout || 300, hf_token);
147
- return client(duplicated_space.url, options);
148
- } catch (e: any) {
149
- throw new Error(e);
74
+ return null;
150
75
  }
151
- }
152
76
 
153
- interface Client {
154
- post_data: (
155
- url: string,
156
- body: unknown,
157
- token?: `hf_${string}`
158
- ) => Promise<[PostResponse, number]>;
77
+ view_api: () => Promise<ApiInfo<JsApiData>>;
159
78
  upload_files: (
160
- root: string,
161
- files: File[],
162
- token?: `hf_${string}`,
79
+ root_url: string,
80
+ files: (Blob | File)[],
163
81
  upload_id?: string
164
82
  ) => Promise<UploadResponse>;
165
- client: (
166
- app_reference: string,
167
- options: {
168
- hf_token?: `hf_${string}`;
169
- status_callback?: SpaceStatusCallback;
170
- }
171
- ) => Promise<client_return>;
83
+ upload: (
84
+ file_data: FileData[],
85
+ root_url: string,
86
+ upload_id?: string,
87
+ max_file_size?: number
88
+ ) => Promise<(FileData | null)[] | null>;
172
89
  handle_blob: (
173
90
  endpoint: string,
174
91
  data: unknown[],
175
- api_info: ApiInfo<JsApiData>,
176
- token?: `hf_${string}`
92
+ endpoint_info: EndpointInfo<ApiData | JsApiData>
177
93
  ) => Promise<unknown[]>;
178
- }
179
-
180
- export function api_factory(
181
- fetch_implementation: typeof fetch,
182
- EventSource_factory: (url: URL) => EventSource
183
- ): Client {
184
- return { post_data, upload_files, client, handle_blob };
185
-
186
- async function post_data(
94
+ post_data: (
187
95
  url: string,
188
96
  body: unknown,
189
- token?: `hf_${string}`,
190
- additional_headers?: Record<string, string>
191
- ): Promise<[PostResponse, number]> {
192
- const headers: {
193
- Authorization?: string;
194
- "Content-Type": "application/json";
195
- } = { "Content-Type": "application/json" };
196
- if (token) {
197
- headers.Authorization = `Bearer ${token}`;
198
- }
199
-
200
- try {
201
- var response = await fetch_implementation(url, {
202
- method: "POST",
203
- body: JSON.stringify(body),
204
- headers: { ...headers, ...additional_headers }
205
- });
206
- } catch (e) {
207
- return [{ error: BROKEN_CONNECTION_MSG }, 500];
208
- }
209
- let output: PostResponse;
210
- let status: int;
211
- try {
212
- output = await response.json();
213
- status = response.status;
214
- } catch (e) {
215
- output = { error: `Could not parse server response: ${e}` };
216
- status = 500;
217
- }
218
- return [output, status];
97
+ additional_headers?: any
98
+ ) => Promise<unknown[]>;
99
+ submit: (
100
+ endpoint: string | number,
101
+ data: unknown[],
102
+ event_data?: unknown,
103
+ trigger_id?: number | null
104
+ ) => SubmitReturn;
105
+ predict: (
106
+ endpoint: string | number,
107
+ data?: unknown[],
108
+ event_data?: unknown
109
+ ) => Promise<unknown>;
110
+ open_stream: () => void;
111
+ private resolve_config: (endpoint: string) => Promise<Config | undefined>;
112
+ constructor(app_reference: string, options: ClientOptions = {}) {
113
+ this.app_reference = app_reference;
114
+ this.options = options;
115
+
116
+ this.view_api = view_api.bind(this);
117
+ this.upload_files = upload_files.bind(this);
118
+ this.handle_blob = handle_blob.bind(this);
119
+ this.post_data = post_data.bind(this);
120
+ this.submit = submit.bind(this);
121
+ this.predict = predict.bind(this);
122
+ this.open_stream = open_stream.bind(this);
123
+ this.resolve_config = resolve_config.bind(this);
124
+ this.upload = upload.bind(this);
219
125
  }
220
126
 
221
- async function upload_files(
222
- root: string,
223
- files: (Blob | File)[],
224
- token?: `hf_${string}`,
225
- upload_id?: string
226
- ): Promise<UploadResponse> {
227
- const headers: {
228
- Authorization?: string;
229
- } = {};
230
- if (token) {
231
- headers.Authorization = `Bearer ${token}`;
232
- }
233
- const chunkSize = 1000;
234
- const uploadResponses = [];
235
- for (let i = 0; i < files.length; i += chunkSize) {
236
- const chunk = files.slice(i, i + chunkSize);
237
- const formData = new FormData();
238
- chunk.forEach((file) => {
239
- formData.append("files", file);
240
- });
241
- try {
242
- const upload_url = upload_id
243
- ? `${root}/upload?upload_id=${upload_id}`
244
- : `${root}/upload`;
245
- var response = await fetch_implementation(upload_url, {
246
- method: "POST",
247
- body: formData,
248
- headers
249
- });
250
- } catch (e) {
251
- return { error: BROKEN_CONNECTION_MSG };
252
- }
253
- const output: UploadResponse["files"] = await response.json();
254
- uploadResponses.push(...output);
127
+ private async init(): Promise<void> {
128
+ if (
129
+ (typeof window === "undefined" || !("WebSocket" in window)) &&
130
+ !global.WebSocket
131
+ ) {
132
+ const ws = await import("ws");
133
+ // @ts-ignore
134
+ NodeBlob = (await import("node:buffer")).Blob;
135
+ global.WebSocket = ws.WebSocket as unknown as typeof WebSocket;
255
136
  }
256
- return { files: uploadResponses };
257
- }
258
-
259
- async function client(
260
- app_reference: string,
261
- options: {
262
- hf_token?: `hf_${string}`;
263
- status_callback?: SpaceStatusCallback;
264
- } = {}
265
- ): Promise<client_return> {
266
- return new Promise(async (res) => {
267
- const { status_callback, hf_token } = options;
268
- const return_obj = {
269
- predict,
270
- submit,
271
- view_api,
272
- component_server
273
- };
274
-
275
- if (
276
- (typeof window === "undefined" || !("WebSocket" in window)) &&
277
- !global.Websocket
278
- ) {
279
- const ws = await import("ws");
280
- NodeBlob = (await import("node:buffer")).Blob;
281
- //@ts-ignore
282
- global.WebSocket = ws.WebSocket;
283
- }
284
-
285
- const { ws_protocol, http_protocol, host, space_id } =
286
- await process_endpoint(app_reference, hf_token);
287
-
288
- const session_hash = Math.random().toString(36).substring(2);
289
- const last_status: Record<string, Status["stage"]> = {};
290
- let stream_open = false;
291
- let pending_stream_messages: Record<string, any[]> = {}; // Event messages may be received by the SSE stream before the initial data POST request is complete. To resolve this race condition, we store the messages in a dictionary and process them when the POST request is complete.
292
- let pending_diff_streams: Record<string, any[][]> = {};
293
- let event_stream: EventSource | null = null;
294
- const event_callbacks: Record<string, () => Promise<void>> = {};
295
- const unclosed_events: Set<string> = new Set();
296
- let config: Config;
297
- let api_map: Record<string, number> = {};
298
137
 
299
- let jwt: false | string = false;
300
-
301
- if (hf_token && space_id) {
302
- jwt = await get_jwt(space_id, hf_token);
303
- }
304
-
305
- async function config_success(_config: Config): Promise<client_return> {
306
- config = _config;
307
- if (window.location.protocol === "https:") {
308
- config.root = config.root.replace("http://", "https://");
309
- }
310
- api_map = map_names_to_ids(_config?.dependencies || []);
311
- if (config.auth_required) {
312
- return {
313
- config,
314
- ...return_obj
315
- };
316
- }
317
- try {
318
- api = await view_api(config);
319
- } catch (e) {
320
- console.error(`Could not get api details: ${e.message}`);
321
- }
322
-
323
- return {
324
- config,
325
- ...return_obj
326
- };
327
- }
328
- let api: ApiInfo<JsApiData>;
329
- async function handle_space_sucess(status: SpaceStatus): Promise<void> {
330
- if (status_callback) status_callback(status);
331
- if (status.status === "running")
332
- try {
333
- config = await resolve_config(
334
- fetch_implementation,
335
- `${http_protocol}//${host}`,
336
- hf_token
138
+ try {
139
+ await this._resolve_config().then(async ({ config }) => {
140
+ if (config) {
141
+ this.config = config;
142
+ if (this.config && this.config.connect_heartbeat) {
143
+ // connect to the heartbeat endpoint via GET request
144
+ const heartbeat_url = new URL(
145
+ `${this.config.root}/heartbeat/${this.session_hash}`
337
146
  );
147
+ this.heartbeat_event = this.stream_factory(heartbeat_url); // Just connect to the endpoint without parsing the response. Ref: https://github.com/gradio-app/gradio/pull/7974#discussion_r1557717540
338
148
 
339
- const _config = await config_success(config);
340
- res(_config);
341
- } catch (e) {
342
- console.error(e);
343
- if (status_callback) {
344
- status_callback({
345
- status: "error",
346
- message: "Could not load this space.",
347
- load_status: "error",
348
- detail: "NOT_FOUND"
349
- });
149
+ if (this.config.space_id && this.options.hf_token) {
150
+ this.jwt = await get_jwt(
151
+ this.config.space_id,
152
+ this.options.hf_token
153
+ );
350
154
  }
351
155
  }
352
- }
353
-
354
- try {
355
- config = await resolve_config(
356
- fetch_implementation,
357
- `${http_protocol}//${host}`,
358
- hf_token
359
- );
360
-
361
- const _config = await config_success(config);
362
- // connect to the heartbeat endpoint via GET request
363
- const heartbeat_url = new URL(
364
- `${config.root}/heartbeat/${session_hash}`
365
- );
366
- EventSource_factory(heartbeat_url); // Just connect to the endpoint without parsing the response. Ref: https://github.com/gradio-app/gradio/pull/7974#discussion_r1557717540
367
- res(_config);
368
- } catch (e) {
369
- console.error(e);
370
- if (space_id) {
371
- check_space_status(
372
- space_id,
373
- RE_SPACE_NAME.test(space_id) ? "space_name" : "subdomain",
374
- handle_space_sucess
375
- );
376
- } else {
377
- if (status_callback)
378
- status_callback({
379
- status: "error",
380
- message: "Could not load this space.",
381
- load_status: "error",
382
- detail: "NOT_FOUND"
383
- });
384
- }
385
- }
386
-
387
- function predict(
388
- endpoint: string,
389
- data: unknown[],
390
- event_data?: unknown
391
- ): Promise<unknown> {
392
- let data_returned = false;
393
- let status_complete = false;
394
- let dependency;
395
- if (typeof endpoint === "number") {
396
- dependency = config.dependencies[endpoint];
397
- } else {
398
- const trimmed_endpoint = endpoint.replace(/^\//, "");
399
- dependency = config.dependencies[api_map[trimmed_endpoint]];
400
156
  }
157
+ });
158
+ } catch (e) {
159
+ throw Error(CONFIG_ERROR_MSG + (e as Error).message);
160
+ }
401
161
 
402
- if (dependency.types.continuous) {
403
- throw new Error(
404
- "Cannot call predict on this function as it may run forever. Use submit instead"
405
- );
406
- }
407
-
408
- return new Promise((res, rej) => {
409
- const app = submit(endpoint, data, event_data);
410
- let result;
411
-
412
- app
413
- .on("data", (d) => {
414
- // if complete message comes before data, resolve here
415
- if (status_complete) {
416
- app.destroy();
417
- res(d);
418
- }
419
- data_returned = true;
420
- result = d;
421
- })
422
- .on("status", (status) => {
423
- if (status.stage === "error") rej(status);
424
- if (status.stage === "complete") {
425
- status_complete = true;
426
- // if complete message comes after data, resolve here
427
- if (data_returned) {
428
- app.destroy();
429
- res(result);
430
- }
431
- }
432
- });
433
- });
434
- }
435
-
436
- function submit(
437
- endpoint: string | number,
438
- data: unknown[],
439
- event_data?: unknown,
440
- trigger_id: number | null = null
441
- ): SubmitReturn {
442
- let fn_index: number;
443
- let api_info;
444
- let dependency;
445
-
446
- if (typeof endpoint === "number") {
447
- fn_index = endpoint;
448
- api_info = api.unnamed_endpoints[fn_index];
449
- dependency = config.dependencies[endpoint];
450
- } else {
451
- const trimmed_endpoint = endpoint.replace(/^\//, "");
452
-
453
- fn_index = api_map[trimmed_endpoint];
454
- api_info = api.named_endpoints[endpoint.trim()];
455
- dependency = config.dependencies[api_map[trimmed_endpoint]];
456
- }
457
-
458
- if (typeof fn_index !== "number") {
459
- throw new Error(
460
- "There is no endpoint matching that name of fn_index matching that number."
461
- );
462
- }
463
-
464
- let websocket: WebSocket;
465
- let eventSource: EventSource;
466
- let protocol = config.protocol ?? "ws";
467
-
468
- const _endpoint = typeof endpoint === "number" ? "/predict" : endpoint;
469
- let payload: Payload;
470
- let event_id: string | null = null;
471
- let complete: false | Record<string, any> = false;
472
- const listener_map: ListenerMap<EventType> = {};
473
- let url_params = "";
474
- if (typeof window !== "undefined") {
475
- url_params = new URLSearchParams(window.location.search).toString();
476
- }
477
-
478
- handle_blob(`${config.root}`, data, api_info, hf_token).then(
479
- (_payload) => {
480
- payload = {
481
- data: _payload || [],
482
- event_data,
483
- fn_index,
484
- trigger_id
485
- };
486
- if (skip_queue(fn_index, config)) {
487
- fire_event({
488
- type: "status",
489
- endpoint: _endpoint,
490
- stage: "pending",
491
- queue: false,
492
- fn_index,
493
- time: new Date()
494
- });
495
-
496
- post_data(
497
- `${config.root}/run${
498
- _endpoint.startsWith("/") ? _endpoint : `/${_endpoint}`
499
- }${url_params ? "?" + url_params : ""}`,
500
- {
501
- ...payload,
502
- session_hash
503
- },
504
- hf_token
505
- )
506
- .then(([output, status_code]) => {
507
- const data = output.data;
508
- if (status_code == 200) {
509
- fire_event({
510
- type: "data",
511
- endpoint: _endpoint,
512
- fn_index,
513
- data: data,
514
- time: new Date()
515
- });
516
-
517
- fire_event({
518
- type: "status",
519
- endpoint: _endpoint,
520
- fn_index,
521
- stage: "complete",
522
- eta: output.average_duration,
523
- queue: false,
524
- time: new Date()
525
- });
526
- } else {
527
- fire_event({
528
- type: "status",
529
- stage: "error",
530
- endpoint: _endpoint,
531
- fn_index,
532
- message: output.error,
533
- queue: false,
534
- time: new Date()
535
- });
536
- }
537
- })
538
- .catch((e) => {
539
- fire_event({
540
- type: "status",
541
- stage: "error",
542
- message: e.message,
543
- endpoint: _endpoint,
544
- fn_index,
545
- queue: false,
546
- time: new Date()
547
- });
548
- });
549
- } else if (protocol == "ws") {
550
- fire_event({
551
- type: "status",
552
- stage: "pending",
553
- queue: true,
554
- endpoint: _endpoint,
555
- fn_index,
556
- time: new Date()
557
- });
558
- let url = new URL(`${ws_protocol}://${resolve_root(
559
- host,
560
- config.path,
561
- true
562
- )}
563
- /queue/join${url_params ? "?" + url_params : ""}`);
564
-
565
- if (jwt) {
566
- url.searchParams.set("__sign", jwt);
567
- }
568
-
569
- websocket = new WebSocket(url);
570
-
571
- websocket.onclose = (evt) => {
572
- if (!evt.wasClean) {
573
- fire_event({
574
- type: "status",
575
- stage: "error",
576
- broken: true,
577
- message: BROKEN_CONNECTION_MSG,
578
- queue: true,
579
- endpoint: _endpoint,
580
- fn_index,
581
- time: new Date()
582
- });
583
- }
584
- };
585
-
586
- websocket.onmessage = function (event) {
587
- const _data = JSON.parse(event.data);
588
- const { type, status, data } = handle_message(
589
- _data,
590
- last_status[fn_index]
591
- );
592
-
593
- if (type === "update" && status && !complete) {
594
- // call 'status' listeners
595
- fire_event({
596
- type: "status",
597
- endpoint: _endpoint,
598
- fn_index,
599
- time: new Date(),
600
- ...status
601
- });
602
- if (status.stage === "error") {
603
- websocket.close();
604
- }
605
- } else if (type === "hash") {
606
- websocket.send(JSON.stringify({ fn_index, session_hash }));
607
- return;
608
- } else if (type === "data") {
609
- websocket.send(JSON.stringify({ ...payload, session_hash }));
610
- } else if (type === "complete") {
611
- complete = status;
612
- } else if (type === "log") {
613
- fire_event({
614
- type: "log",
615
- log: data.log,
616
- level: data.level,
617
- endpoint: _endpoint,
618
- fn_index
619
- });
620
- } else if (type === "generating") {
621
- fire_event({
622
- type: "status",
623
- time: new Date(),
624
- ...status,
625
- stage: status?.stage!,
626
- queue: true,
627
- endpoint: _endpoint,
628
- fn_index
629
- });
630
- }
631
- if (data) {
632
- fire_event({
633
- type: "data",
634
- time: new Date(),
635
- data: data.data,
636
- endpoint: _endpoint,
637
- fn_index
638
- });
639
-
640
- if (complete) {
641
- fire_event({
642
- type: "status",
643
- time: new Date(),
644
- ...complete,
645
- stage: status?.stage!,
646
- queue: true,
647
- endpoint: _endpoint,
648
- fn_index
649
- });
650
- websocket.close();
651
- }
652
- }
653
- };
654
-
655
- // different ws contract for gradio versions older than 3.6.0
656
- //@ts-ignore
657
- if (semiver(config.version || "2.0.0", "3.6") < 0) {
658
- addEventListener("open", () =>
659
- websocket.send(JSON.stringify({ hash: session_hash }))
660
- );
661
- }
662
- } else if (protocol == "sse") {
663
- fire_event({
664
- type: "status",
665
- stage: "pending",
666
- queue: true,
667
- endpoint: _endpoint,
668
- fn_index,
669
- time: new Date()
670
- });
671
- var params = new URLSearchParams({
672
- fn_index: fn_index.toString(),
673
- session_hash: session_hash
674
- }).toString();
675
- let url = new URL(
676
- `${config.root}/queue/join?${
677
- url_params ? url_params + "&" : ""
678
- }${params}`
679
- );
162
+ this.api_info = await this.view_api();
163
+ this.api_map = map_names_to_ids(this.config?.dependencies || []);
164
+ }
680
165
 
681
- eventSource = EventSource_factory(url);
166
+ static async connect(
167
+ app_reference: string,
168
+ options: ClientOptions = {}
169
+ ): Promise<Client> {
170
+ const client = new this(app_reference, options); // this refers to the class itself, not the instance
171
+ await client.init();
172
+ return client;
173
+ }
682
174
 
683
- eventSource.onmessage = async function (event) {
684
- const _data = JSON.parse(event.data);
685
- const { type, status, data } = handle_message(
686
- _data,
687
- last_status[fn_index]
688
- );
175
+ close(): void {
176
+ this.heartbeat_event?.close();
177
+ }
689
178
 
690
- if (type === "update" && status && !complete) {
691
- // call 'status' listeners
692
- fire_event({
693
- type: "status",
694
- endpoint: _endpoint,
695
- fn_index,
696
- time: new Date(),
697
- ...status
698
- });
699
- if (status.stage === "error") {
700
- eventSource.close();
701
- }
702
- } else if (type === "data") {
703
- event_id = _data.event_id as string;
704
- let [_, status] = await post_data(
705
- `${config.root}/queue/data`,
706
- {
707
- ...payload,
708
- session_hash,
709
- event_id
710
- },
711
- hf_token
712
- );
713
- if (status !== 200) {
714
- fire_event({
715
- type: "status",
716
- stage: "error",
717
- message: BROKEN_CONNECTION_MSG,
718
- queue: true,
719
- endpoint: _endpoint,
720
- fn_index,
721
- time: new Date()
722
- });
723
- eventSource.close();
724
- }
725
- } else if (type === "complete") {
726
- complete = status;
727
- } else if (type === "log") {
728
- fire_event({
729
- type: "log",
730
- log: data.log,
731
- level: data.level,
732
- endpoint: _endpoint,
733
- fn_index
734
- });
735
- } else if (type === "generating") {
736
- fire_event({
737
- type: "status",
738
- time: new Date(),
739
- ...status,
740
- stage: status?.stage!,
741
- queue: true,
742
- endpoint: _endpoint,
743
- fn_index
744
- });
745
- }
746
- if (data) {
747
- fire_event({
748
- type: "data",
749
- time: new Date(),
750
- data: data.data,
751
- endpoint: _endpoint,
752
- fn_index
753
- });
179
+ static async duplicate(
180
+ app_reference: string,
181
+ options: DuplicateOptions = {}
182
+ ): Promise<Client> {
183
+ return duplicate(app_reference, options);
184
+ }
754
185
 
755
- if (complete) {
756
- fire_event({
757
- type: "status",
758
- time: new Date(),
759
- ...complete,
760
- stage: status?.stage!,
761
- queue: true,
762
- endpoint: _endpoint,
763
- fn_index
764
- });
765
- eventSource.close();
766
- }
767
- }
768
- };
769
- } else if (
770
- protocol == "sse_v1" ||
771
- protocol == "sse_v2" ||
772
- protocol == "sse_v2.1" ||
773
- protocol == "sse_v3"
774
- ) {
775
- // latest API format. v2 introduces sending diffs for intermediate outputs in generative functions, which makes payloads lighter.
776
- // v3 only closes the stream when the backend sends the close stream message.
777
- fire_event({
778
- type: "status",
779
- stage: "pending",
780
- queue: true,
781
- endpoint: _endpoint,
782
- fn_index,
783
- time: new Date()
784
- });
785
- let hostname = window.location.hostname;
786
- let hfhubdev = "dev.spaces.huggingface.tech";
787
- const origin = hostname.includes(".dev.")
788
- ? `https://moon-${hostname.split(".")[1]}.${hfhubdev}`
789
- : `https://huggingface.co`;
790
- const zerogpu_auth_promise =
791
- dependency.zerogpu && window.parent != window && config.space_id
792
- ? post_message<Headers>("zerogpu-headers", origin)
793
- : Promise.resolve(null);
794
- const post_data_promise = zerogpu_auth_promise.then((headers) => {
795
- return post_data(
796
- `${config.root}/queue/join?${url_params}`,
797
- {
798
- ...payload,
799
- session_hash
800
- },
801
- hf_token,
802
- headers
803
- );
804
- });
805
- post_data_promise.then(([response, status]) => {
806
- if (status === 503) {
807
- fire_event({
808
- type: "status",
809
- stage: "error",
810
- message: QUEUE_FULL_MSG,
811
- queue: true,
812
- endpoint: _endpoint,
813
- fn_index,
814
- time: new Date()
815
- });
816
- } else if (status !== 200) {
817
- fire_event({
818
- type: "status",
819
- stage: "error",
820
- message: BROKEN_CONNECTION_MSG,
821
- queue: true,
822
- endpoint: _endpoint,
823
- fn_index,
824
- time: new Date()
825
- });
826
- } else {
827
- event_id = response.event_id as string;
828
- let callback = async function (_data: object): void {
829
- try {
830
- const { type, status, data } = handle_message(
831
- _data,
832
- last_status[fn_index]
833
- );
186
+ private async _resolve_config(): Promise<any> {
187
+ const { http_protocol, host, space_id } = await process_endpoint(
188
+ this.app_reference,
189
+ this.options.hf_token
190
+ );
834
191
 
835
- if (type == "heartbeat") {
836
- return;
837
- }
192
+ const { status_callback } = this.options;
193
+ let config: Config | undefined;
838
194
 
839
- if (type === "update" && status && !complete) {
840
- // call 'status' listeners
841
- fire_event({
842
- type: "status",
843
- endpoint: _endpoint,
844
- fn_index,
845
- time: new Date(),
846
- ...status
847
- });
848
- } else if (type === "complete") {
849
- complete = status;
850
- } else if (type == "unexpected_error") {
851
- console.error("Unexpected error", status?.message);
852
- fire_event({
853
- type: "status",
854
- stage: "error",
855
- message:
856
- status?.message || "An Unexpected Error Occurred!",
857
- queue: true,
858
- endpoint: _endpoint,
859
- fn_index,
860
- time: new Date()
861
- });
862
- } else if (type === "log") {
863
- fire_event({
864
- type: "log",
865
- log: data.log,
866
- level: data.level,
867
- endpoint: _endpoint,
868
- fn_index
869
- });
870
- return;
871
- } else if (type === "generating") {
872
- fire_event({
873
- type: "status",
874
- time: new Date(),
875
- ...status,
876
- stage: status?.stage!,
877
- queue: true,
878
- endpoint: _endpoint,
879
- fn_index
880
- });
881
- if (
882
- data &&
883
- ["sse_v2", "sse_v2.1", "sse_v3"].includes(protocol)
884
- ) {
885
- apply_diff_stream(event_id!, data);
886
- }
887
- }
888
- if (data) {
889
- fire_event({
890
- type: "data",
891
- time: new Date(),
892
- data: data.data,
893
- endpoint: _endpoint,
894
- fn_index
895
- });
195
+ try {
196
+ config = await this.resolve_config(`${http_protocol}//${host}`);
896
197
 
897
- if (complete) {
898
- fire_event({
899
- type: "status",
900
- time: new Date(),
901
- ...complete,
902
- stage: status?.stage!,
903
- queue: true,
904
- endpoint: _endpoint,
905
- fn_index
906
- });
907
- }
908
- }
198
+ if (!config) {
199
+ throw new Error(CONFIG_ERROR_MSG);
200
+ }
909
201
 
910
- if (
911
- status?.stage === "complete" ||
912
- status?.stage === "error"
913
- ) {
914
- if (event_callbacks[event_id]) {
915
- delete event_callbacks[event_id];
916
- }
917
- if (event_id in pending_diff_streams) {
918
- delete pending_diff_streams[event_id];
919
- }
920
- }
921
- } catch (e) {
922
- console.error("Unexpected client exception", e);
923
- fire_event({
924
- type: "status",
925
- stage: "error",
926
- message: "An Unexpected Error Occurred!",
927
- queue: true,
928
- endpoint: _endpoint,
929
- fn_index,
930
- time: new Date()
931
- });
932
- if (["sse_v2", "sse_v2.1"].includes(protocol)) {
933
- close_stream();
934
- }
935
- }
936
- };
937
- if (event_id in pending_stream_messages) {
938
- pending_stream_messages[event_id].forEach((msg) =>
939
- callback(msg)
940
- );
941
- delete pending_stream_messages[event_id];
942
- }
943
- event_callbacks[event_id] = callback;
944
- unclosed_events.add(event_id);
945
- if (!stream_open) {
946
- open_stream();
947
- }
948
- }
949
- });
950
- }
951
- }
202
+ return this.config_success(config);
203
+ } catch (e) {
204
+ console.error(e);
205
+ if (space_id) {
206
+ check_space_status(
207
+ space_id,
208
+ RE_SPACE_NAME.test(space_id) ? "space_name" : "subdomain",
209
+ this.handle_space_success
952
210
  );
953
-
954
- function apply_diff_stream(event_id: string, data: any): void {
955
- let is_first_generation = !pending_diff_streams[event_id];
956
- if (is_first_generation) {
957
- pending_diff_streams[event_id] = [];
958
- data.data.forEach((value: any, i: number) => {
959
- pending_diff_streams[event_id][i] = value;
960
- });
961
- } else {
962
- data.data.forEach((value: any, i: number) => {
963
- let new_data = apply_diff(
964
- pending_diff_streams[event_id][i],
965
- value
966
- );
967
- pending_diff_streams[event_id][i] = new_data;
968
- data.data[i] = new_data;
969
- });
970
- }
971
- }
972
-
973
- function fire_event<K extends EventType>(event: Event<K>): void {
974
- const narrowed_listener_map: ListenerMap<K> = listener_map;
975
- const listeners = narrowed_listener_map[event.type] || [];
976
- listeners?.forEach((l) => l(event));
977
- }
978
-
979
- function on<K extends EventType>(
980
- eventType: K,
981
- listener: EventListener<K>
982
- ): SubmitReturn {
983
- const narrowed_listener_map: ListenerMap<K> = listener_map;
984
- const listeners = narrowed_listener_map[eventType] || [];
985
- narrowed_listener_map[eventType] = listeners;
986
- listeners?.push(listener);
987
-
988
- return { on, off, cancel, destroy };
989
- }
990
-
991
- function off<K extends EventType>(
992
- eventType: K,
993
- listener: EventListener<K>
994
- ): SubmitReturn {
995
- const narrowed_listener_map: ListenerMap<K> = listener_map;
996
- let listeners = narrowed_listener_map[eventType] || [];
997
- listeners = listeners?.filter((l) => l !== listener);
998
- narrowed_listener_map[eventType] = listeners;
999
-
1000
- return { on, off, cancel, destroy };
1001
- }
1002
-
1003
- async function cancel(): Promise<void> {
1004
- const _status: Status = {
1005
- stage: "complete",
1006
- queue: false,
1007
- time: new Date()
1008
- };
1009
- complete = _status;
1010
- fire_event({
1011
- ..._status,
1012
- type: "status",
1013
- endpoint: _endpoint,
1014
- fn_index: fn_index
211
+ } else {
212
+ if (status_callback)
213
+ status_callback({
214
+ status: "error",
215
+ message: "Could not load this space.",
216
+ load_status: "error",
217
+ detail: "NOT_FOUND"
1015
218
  });
1016
-
1017
- let cancel_request = {};
1018
- if (protocol === "ws") {
1019
- if (websocket && websocket.readyState === 0) {
1020
- websocket.addEventListener("open", () => {
1021
- websocket.close();
1022
- });
1023
- } else {
1024
- websocket.close();
1025
- }
1026
- cancel_request = { fn_index, session_hash };
1027
- } else {
1028
- eventSource.close();
1029
- cancel_request = { event_id };
1030
- }
1031
-
1032
- try {
1033
- await fetch_implementation(`${config.root}/reset`, {
1034
- headers: { "Content-Type": "application/json" },
1035
- method: "POST",
1036
- body: JSON.stringify(cancel_request)
1037
- });
1038
- } catch (e) {
1039
- console.warn(
1040
- "The `/reset` endpoint could not be called. Subsequent endpoint results may be unreliable."
1041
- );
1042
- }
1043
- }
1044
-
1045
- function destroy(): void {
1046
- for (const event_type in listener_map) {
1047
- listener_map[event_type as "data" | "status"].forEach((fn) => {
1048
- off(event_type as "data" | "status", fn);
1049
- });
1050
- }
1051
- }
1052
-
1053
- return {
1054
- on,
1055
- off,
1056
- cancel,
1057
- destroy
1058
- };
1059
219
  }
220
+ }
221
+ }
1060
222
 
1061
- function open_stream(): void {
1062
- stream_open = true;
1063
- let params = new URLSearchParams({
1064
- session_hash: session_hash
1065
- }).toString();
1066
- let url = new URL(`${config.root}/queue/data?${params}`);
1067
- event_stream = EventSource_factory(url);
1068
- event_stream.onmessage = async function (event) {
1069
- let _data = JSON.parse(event.data);
1070
- if (_data.msg === "close_stream") {
1071
- close_stream();
1072
- return;
1073
- }
1074
- const event_id = _data.event_id;
1075
- if (!event_id) {
1076
- await Promise.all(
1077
- Object.keys(event_callbacks).map((event_id) =>
1078
- event_callbacks[event_id](_data)
1079
- )
1080
- );
1081
- } else if (event_callbacks[event_id]) {
1082
- if (
1083
- _data.msg === "process_completed" &&
1084
- ["sse", "sse_v1", "sse_v2", "sse_v2.1"].includes(config.protocol)
1085
- ) {
1086
- unclosed_events.delete(event_id);
1087
- if (unclosed_events.size === 0) {
1088
- close_stream();
1089
- }
1090
- }
1091
- let fn = event_callbacks[event_id];
1092
- window.setTimeout(fn, 0, _data); // need to do this to put the event on the end of the event loop, so the browser can refresh between callbacks and not freeze in case of quick generations. See https://github.com/gradio-app/gradio/pull/7055
1093
- } else {
1094
- if (!pending_stream_messages[event_id]) {
1095
- pending_stream_messages[event_id] = [];
1096
- }
1097
- pending_stream_messages[event_id].push(_data);
1098
- }
1099
- };
1100
- event_stream.onerror = async function (event) {
1101
- await Promise.all(
1102
- Object.keys(event_callbacks).map((event_id) =>
1103
- event_callbacks[event_id]({
1104
- msg: "unexpected_error",
1105
- message: BROKEN_CONNECTION_MSG
1106
- })
1107
- )
1108
- );
1109
- close_stream();
1110
- };
1111
- }
223
+ private async config_success(
224
+ _config: Config
225
+ ): Promise<Config | client_return> {
226
+ this.config = _config;
1112
227
 
1113
- function close_stream(): void {
1114
- stream_open = false;
1115
- event_stream?.close();
228
+ if (typeof window !== "undefined") {
229
+ if (window.location.protocol === "https:") {
230
+ this.config.root = this.config.root.replace("http://", "https://");
1116
231
  }
232
+ }
1117
233
 
1118
- async function component_server(
1119
- component_id: number,
1120
- fn_name: string,
1121
- data: unknown[] | { binary: boolean; data: Record<string, any> }
1122
- ): Promise<any> {
1123
- const headers: {
1124
- Authorization?: string;
1125
- "Content-Type": "application/json";
1126
- } = {};
1127
-
1128
- let root_url: string;
1129
- let component = config.components.find(
1130
- (comp) => comp.id === component_id
1131
- );
1132
- if (component?.props?.root_url) {
1133
- root_url = component.props.root_url;
1134
- } else {
1135
- root_url = config.root;
1136
- }
1137
-
1138
- let body: FormData | string;
1139
- if (data.binary) {
1140
- body = new FormData();
1141
- for (const key in data.data) {
1142
- if (key === "binary") continue;
1143
- body.append(key, data.data[key]);
1144
- }
1145
- body.set("component_id", component_id);
1146
- body.set("fn_name", fn_name);
1147
- body.set("session_hash", session_hash);
1148
- } else {
1149
- body = JSON.stringify({
1150
- data: data,
1151
- component_id,
1152
- fn_name,
1153
- session_hash
1154
- });
1155
-
1156
- headers["Content-Type"] = "application/json";
1157
- }
1158
- if (hf_token) {
1159
- headers.Authorization = `Bearer ${hf_token}`;
1160
- }
234
+ if (this.config.auth_required) {
235
+ return this.prepare_return_obj();
236
+ }
1161
237
 
1162
- try {
1163
- const response = await fetch_implementation(
1164
- `${root_url}/component_server/`,
1165
- {
1166
- method: "POST",
1167
- body: body,
1168
- headers
1169
- }
1170
- );
238
+ try {
239
+ this.api_info = await this.view_api();
240
+ } catch (e) {
241
+ console.error(API_INFO_ERROR_MSG + (e as Error).message);
242
+ }
1171
243
 
1172
- if (!response.ok) {
1173
- // console.warn(await response.text());
1174
- throw new Error(
1175
- "Could not connect to component server: " + response.statusText
1176
- );
1177
- }
244
+ return this.prepare_return_obj();
245
+ }
1178
246
 
1179
- const output = await response.json();
1180
- return output;
1181
- } catch (e) {
1182
- console.warn(e);
247
+ async handle_space_success(status: SpaceStatus): Promise<Config | void> {
248
+ const { status_callback } = this.options;
249
+ if (status_callback) status_callback(status);
250
+ if (status.status === "running") {
251
+ try {
252
+ this.config = await this._resolve_config();
253
+ if (!this.config) {
254
+ throw new Error(CONFIG_ERROR_MSG);
1183
255
  }
1184
- }
1185
256
 
1186
- async function view_api(config?: Config): Promise<ApiInfo<JsApiData>> {
1187
- if (api) return api;
257
+ const _config = await this.config_success(this.config);
1188
258
 
1189
- const headers: {
1190
- Authorization?: string;
1191
- "Content-Type": "application/json";
1192
- } = { "Content-Type": "application/json" };
1193
- if (hf_token) {
1194
- headers.Authorization = `Bearer ${hf_token}`;
1195
- }
1196
- let response: Response;
1197
- // @ts-ignore
1198
- if (semiver(config.version || "2.0.0", "3.30") < 0) {
1199
- response = await fetch_implementation(
1200
- "https://gradio-space-api-fetcher-v2.hf.space/api",
1201
- {
1202
- method: "POST",
1203
- body: JSON.stringify({
1204
- serialize: false,
1205
- config: JSON.stringify(config)
1206
- }),
1207
- headers
1208
- }
1209
- );
1210
- } else {
1211
- response = await fetch_implementation(`${config.root}/info`, {
1212
- headers
259
+ return _config as Config;
260
+ } catch (e) {
261
+ console.error(e);
262
+ if (status_callback) {
263
+ status_callback({
264
+ status: "error",
265
+ message: "Could not load this space.",
266
+ load_status: "error",
267
+ detail: "NOT_FOUND"
1213
268
  });
1214
269
  }
1215
-
1216
- if (!response.ok) {
1217
- throw new Error(BROKEN_CONNECTION_MSG);
1218
- }
1219
-
1220
- let api_info = (await response.json()) as
1221
- | ApiInfo<ApiData>
1222
- | { api: ApiInfo<ApiData> };
1223
- if ("api" in api_info) {
1224
- api_info = api_info.api;
1225
- }
1226
-
1227
- if (
1228
- api_info.named_endpoints["/predict"] &&
1229
- !api_info.unnamed_endpoints["0"]
1230
- ) {
1231
- api_info.unnamed_endpoints[0] = api_info.named_endpoints["/predict"];
1232
- }
1233
-
1234
- const x = transform_api_info(api_info, config, api_map);
1235
- return x;
1236
270
  }
1237
- });
1238
- }
1239
-
1240
- async function handle_blob(
1241
- endpoint: string,
1242
- data: unknown[],
1243
- api_info: ApiInfo<JsApiData>,
1244
- token?: `hf_${string}`
1245
- ): Promise<unknown[]> {
1246
- const blob_refs = await walk_and_store_blobs(
1247
- data,
1248
- undefined,
1249
- [],
1250
- true,
1251
- api_info
1252
- );
1253
-
1254
- return Promise.all(
1255
- blob_refs.map(async ({ path, blob, type }) => {
1256
- if (blob) {
1257
- const file_url = (await upload_files(endpoint, [blob], token))
1258
- .files[0];
1259
- return { path, file_url, type, name: blob?.name };
1260
- }
1261
- return { path, type };
1262
- })
1263
- ).then((r) => {
1264
- r.forEach(({ path, file_url, type, name }) => {
1265
- if (type === "Gallery") {
1266
- update_object(data, file_url, path);
1267
- } else if (file_url) {
1268
- const file = new FileData({ path: file_url, orig_name: name });
1269
- update_object(data, file, path);
1270
- }
1271
- });
1272
-
1273
- return data;
1274
- });
1275
- }
1276
- }
1277
-
1278
- export const { post_data, upload_files, client, handle_blob } = api_factory(
1279
- fetch,
1280
- (...args) => new EventSource(...args)
1281
- );
1282
-
1283
- interface ApiData {
1284
- label: string;
1285
- type: {
1286
- type: any;
1287
- description: string;
1288
- };
1289
- component: string;
1290
- example_input?: any;
1291
- }
1292
-
1293
- interface JsApiData {
1294
- label: string;
1295
- type: string;
1296
- component: string;
1297
- example_input: any;
1298
- }
1299
-
1300
- interface EndpointInfo<T extends ApiData | JsApiData> {
1301
- parameters: T[];
1302
- returns: T[];
1303
- }
1304
- interface ApiInfo<T extends ApiData | JsApiData> {
1305
- named_endpoints: {
1306
- [key: string]: EndpointInfo<T>;
1307
- };
1308
- unnamed_endpoints: {
1309
- [key: string]: EndpointInfo<T>;
1310
- };
1311
- }
1312
-
1313
- function get_type(
1314
- type: { [key: string]: any },
1315
- component: string,
1316
- serializer: string,
1317
- signature_type: "return" | "parameter"
1318
- ): string {
1319
- switch (type.type) {
1320
- case "string":
1321
- return "string";
1322
- case "boolean":
1323
- return "boolean";
1324
- case "number":
1325
- return "number";
1326
- }
1327
-
1328
- if (
1329
- serializer === "JSONSerializable" ||
1330
- serializer === "StringSerializable"
1331
- ) {
1332
- return "any";
1333
- } else if (serializer === "ListStringSerializable") {
1334
- return "string[]";
1335
- } else if (component === "Image") {
1336
- return signature_type === "parameter" ? "Blob | File | Buffer" : "string";
1337
- } else if (serializer === "FileSerializable") {
1338
- if (type?.type === "array") {
1339
- return signature_type === "parameter"
1340
- ? "(Blob | File | Buffer)[]"
1341
- : `{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}[]`;
1342
271
  }
1343
- return signature_type === "parameter"
1344
- ? "Blob | File | Buffer"
1345
- : `{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}`;
1346
- } else if (serializer === "GallerySerializable") {
1347
- return signature_type === "parameter"
1348
- ? "[(Blob | File | Buffer), (string | null)][]"
1349
- : `[{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}, (string | null))][]`;
1350
272
  }
1351
- }
1352
273
 
1353
- function get_description(
1354
- type: { type: any; description: string },
1355
- serializer: string
1356
- ): string {
1357
- if (serializer === "GallerySerializable") {
1358
- return "array of [file, label] tuples";
1359
- } else if (serializer === "ListStringSerializable") {
1360
- return "array of strings";
1361
- } else if (serializer === "FileSerializable") {
1362
- return "array of files or single file";
1363
- }
1364
- return type.description;
1365
- }
274
+ public async component_server(
275
+ component_id: number,
276
+ fn_name: string,
277
+ data: unknown[] | { binary: boolean; data: Record<string, any> }
278
+ ): Promise<unknown> {
279
+ if (!this.config) {
280
+ throw new Error(CONFIG_ERROR_MSG);
281
+ }
1366
282
 
1367
- function transform_api_info(
1368
- api_info: ApiInfo<ApiData>,
1369
- config: Config,
1370
- api_map: Record<string, number>
1371
- ): ApiInfo<JsApiData> {
1372
- const new_data = {
1373
- named_endpoints: {},
1374
- unnamed_endpoints: {}
1375
- };
1376
- for (const key in api_info) {
1377
- const cat = api_info[key];
283
+ const headers: {
284
+ Authorization?: string;
285
+ "Content-Type"?: "application/json";
286
+ } = {};
1378
287
 
1379
- for (const endpoint in cat) {
1380
- const dep_index = config.dependencies[endpoint]
1381
- ? endpoint
1382
- : api_map[endpoint.replace("/", "")];
288
+ const { hf_token } = this.options;
289
+ const { session_hash } = this;
1383
290
 
1384
- const info = cat[endpoint];
1385
- new_data[key][endpoint] = {};
1386
- new_data[key][endpoint].parameters = {};
1387
- new_data[key][endpoint].returns = {};
1388
- new_data[key][endpoint].type = config.dependencies[dep_index].types;
1389
- new_data[key][endpoint].parameters = info.parameters.map(
1390
- ({ label, component, type, serializer }) => ({
1391
- label,
1392
- component,
1393
- type: get_type(type, component, serializer, "parameter"),
1394
- description: get_description(type, serializer)
1395
- })
1396
- );
291
+ if (hf_token) {
292
+ headers.Authorization = `Bearer ${this.options.hf_token}`;
293
+ }
1397
294
 
1398
- new_data[key][endpoint].returns = info.returns.map(
1399
- ({ label, component, type, serializer }) => ({
1400
- label,
1401
- component,
1402
- type: get_type(type, component, serializer, "return"),
1403
- description: get_description(type, serializer)
1404
- })
1405
- );
295
+ let root_url: string;
296
+ let component = this.config.components.find(
297
+ (comp) => comp.id === component_id
298
+ );
299
+ if (component?.props?.root_url) {
300
+ root_url = component.props.root_url;
301
+ } else {
302
+ root_url = this.config.root;
1406
303
  }
1407
- }
1408
304
 
1409
- return new_data;
1410
- }
305
+ let body: FormData | string;
1411
306
 
1412
- async function get_jwt(
1413
- space: string,
1414
- token: `hf_${string}`
1415
- ): Promise<string | false> {
1416
- try {
1417
- const r = await fetch(`https://huggingface.co/api/spaces/${space}/jwt`, {
1418
- headers: {
1419
- Authorization: `Bearer ${token}`
307
+ if ("binary" in data) {
308
+ body = new FormData();
309
+ for (const key in data.data) {
310
+ if (key === "binary") continue;
311
+ body.append(key, data.data[key]);
1420
312
  }
1421
- });
1422
-
1423
- const jwt = (await r.json()).token;
1424
-
1425
- return jwt || false;
1426
- } catch (e) {
1427
- console.error(e);
1428
- return false;
1429
- }
1430
- }
1431
-
1432
- function update_object(object, newValue, stack): void {
1433
- while (stack.length > 1) {
1434
- object = object[stack.shift()];
1435
- }
1436
-
1437
- object[stack.shift()] = newValue;
1438
- }
1439
-
1440
- export async function walk_and_store_blobs(
1441
- param,
1442
- type = undefined,
1443
- path = [],
1444
- root = false,
1445
- api_info = undefined
1446
- ): Promise<
1447
- {
1448
- path: string[];
1449
- type: string;
1450
- blob: Blob | false;
1451
- }[]
1452
- > {
1453
- if (Array.isArray(param)) {
1454
- let blob_refs = [];
313
+ body.set("component_id", component_id.toString());
314
+ body.set("fn_name", fn_name);
315
+ body.set("session_hash", session_hash);
316
+ } else {
317
+ body = JSON.stringify({
318
+ data: data,
319
+ component_id,
320
+ fn_name,
321
+ session_hash
322
+ });
1455
323
 
1456
- await Promise.all(
1457
- param.map(async (v, i) => {
1458
- let new_path = path.slice();
1459
- new_path.push(i);
324
+ headers["Content-Type"] = "application/json";
325
+ }
1460
326
 
1461
- const array_refs = await walk_and_store_blobs(
1462
- param[i],
1463
- root ? api_info?.parameters[i]?.component || undefined : type,
1464
- new_path,
1465
- false,
1466
- api_info
1467
- );
327
+ if (hf_token) {
328
+ headers.Authorization = `Bearer ${hf_token}`;
329
+ }
1468
330
 
1469
- blob_refs = blob_refs.concat(array_refs);
1470
- })
1471
- );
331
+ try {
332
+ const response = await this.fetch(`${root_url}/component_server/`, {
333
+ method: "POST",
334
+ body: body,
335
+ headers
336
+ });
1472
337
 
1473
- return blob_refs;
1474
- } else if (globalThis.Buffer && param instanceof globalThis.Buffer) {
1475
- const is_image = type === "Image";
1476
- return [
1477
- {
1478
- path: path,
1479
- blob: is_image ? false : new NodeBlob([param]),
1480
- type
1481
- }
1482
- ];
1483
- } else if (typeof param === "object") {
1484
- let blob_refs = [];
1485
- for (let key in param) {
1486
- if (param.hasOwnProperty(key)) {
1487
- let new_path = path.slice();
1488
- new_path.push(key);
1489
- blob_refs = blob_refs.concat(
1490
- await walk_and_store_blobs(
1491
- param[key],
1492
- undefined,
1493
- new_path,
1494
- false,
1495
- api_info
1496
- )
338
+ if (!response.ok) {
339
+ throw new Error(
340
+ "Could not connect to component server: " + response.statusText
1497
341
  );
1498
342
  }
1499
- }
1500
- return blob_refs;
1501
- }
1502
- return [];
1503
- }
1504
-
1505
- function image_to_data_uri(blob: Blob): Promise<string | ArrayBuffer> {
1506
- return new Promise((resolve, _) => {
1507
- const reader = new FileReader();
1508
- reader.onloadend = () => resolve(reader.result);
1509
- reader.readAsDataURL(blob);
1510
- });
1511
- }
1512
-
1513
- function skip_queue(id: number, config: Config): boolean {
1514
- return (
1515
- !(config?.dependencies?.[id]?.queue === null
1516
- ? config.enable_queue
1517
- : config?.dependencies?.[id]?.queue) || false
1518
- );
1519
- }
1520
343
 
1521
- async function resolve_config(
1522
- fetch_implementation: typeof fetch,
1523
- endpoint?: string,
1524
- token?: `hf_${string}`
1525
- ): Promise<Config> {
1526
- const headers: { Authorization?: string } = {};
1527
- if (token) {
1528
- headers.Authorization = `Bearer ${token}`;
1529
- }
1530
- if (
1531
- typeof window !== "undefined" &&
1532
- window.gradio_config &&
1533
- location.origin !== "http://localhost:9876" &&
1534
- !window.gradio_config.dev_mode
1535
- ) {
1536
- const path = window.gradio_config.root;
1537
- const config = window.gradio_config;
1538
- config.root = resolve_root(endpoint, config.root, false);
1539
- return { ...config, path: path };
1540
- } else if (endpoint) {
1541
- let response = await fetch_implementation(`${endpoint}/config`, {
1542
- headers
1543
- });
1544
-
1545
- if (response.status === 200) {
1546
- const config = await response.json();
1547
- config.path = config.path ?? "";
1548
- config.root = endpoint;
1549
- return config;
344
+ const output = await response.json();
345
+ return output;
346
+ } catch (e) {
347
+ console.warn(e);
1550
348
  }
1551
- throw new Error("Could not get config.");
1552
349
  }
1553
350
 
1554
- throw new Error("No config or app endpoint found");
1555
- }
1556
-
1557
- async function check_space_status(
1558
- id: string,
1559
- type: "subdomain" | "space_name",
1560
- status_callback: SpaceStatusCallback
1561
- ): Promise<void> {
1562
- let endpoint =
1563
- type === "subdomain"
1564
- ? `https://huggingface.co/api/spaces/by-subdomain/${id}`
1565
- : `https://huggingface.co/api/spaces/${id}`;
1566
- let response;
1567
- let _status;
1568
- try {
1569
- response = await fetch(endpoint);
1570
- _status = response.status;
1571
- if (_status !== 200) {
1572
- throw new Error();
1573
- }
1574
- response = await response.json();
1575
- } catch (e) {
1576
- status_callback({
1577
- status: "error",
1578
- load_status: "error",
1579
- message: "Could not get space status",
1580
- detail: "NOT_FOUND"
1581
- });
1582
- return;
1583
- }
1584
-
1585
- if (!response || _status !== 200) return;
1586
- const {
1587
- runtime: { stage },
1588
- id: space_name
1589
- } = response;
1590
-
1591
- switch (stage) {
1592
- case "STOPPED":
1593
- case "SLEEPING":
1594
- status_callback({
1595
- status: "sleeping",
1596
- load_status: "pending",
1597
- message: "Space is asleep. Waking it up...",
1598
- detail: stage
1599
- });
1600
-
1601
- setTimeout(() => {
1602
- check_space_status(id, type, status_callback);
1603
- }, 1000); // poll for status
1604
- break;
1605
- case "PAUSED":
1606
- status_callback({
1607
- status: "paused",
1608
- load_status: "error",
1609
- message:
1610
- "This space has been paused by the author. If you would like to try this demo, consider duplicating the space.",
1611
- detail: stage,
1612
- discussions_enabled: await discussions_enabled(space_name)
1613
- });
1614
- break;
1615
- case "RUNNING":
1616
- case "RUNNING_BUILDING":
1617
- status_callback({
1618
- status: "running",
1619
- load_status: "complete",
1620
- message: "",
1621
- detail: stage
1622
- });
1623
- // load_config(source);
1624
- // launch
1625
- break;
1626
- case "BUILDING":
1627
- status_callback({
1628
- status: "building",
1629
- load_status: "pending",
1630
- message: "Space is building...",
1631
- detail: stage
1632
- });
1633
-
1634
- setTimeout(() => {
1635
- check_space_status(id, type, status_callback);
1636
- }, 1000);
1637
- break;
1638
- default:
1639
- status_callback({
1640
- status: "space_error",
1641
- load_status: "error",
1642
- message: "This space is experiencing an issue.",
1643
- detail: stage,
1644
- discussions_enabled: await discussions_enabled(space_name)
1645
- });
1646
- break;
351
+ private prepare_return_obj(): client_return {
352
+ return {
353
+ config: this.config,
354
+ predict: this.predict,
355
+ submit: this.submit,
356
+ view_api: this.view_api,
357
+ component_server: this.component_server
358
+ };
1647
359
  }
1648
360
  }
1649
361
 
1650
- function handle_message(
1651
- data: any,
1652
- last_status: Status["stage"]
1653
- ): {
1654
- type: "hash" | "data" | "update" | "complete" | "generating" | "log" | "none";
1655
- data?: any;
1656
- status?: Status;
1657
- } {
1658
- const queue = true;
1659
- switch (data.msg) {
1660
- case "send_data":
1661
- return { type: "data" };
1662
- case "send_hash":
1663
- return { type: "hash" };
1664
- case "queue_full":
1665
- return {
1666
- type: "update",
1667
- status: {
1668
- queue,
1669
- message: QUEUE_FULL_MSG,
1670
- stage: "error",
1671
- code: data.code,
1672
- success: data.success
1673
- }
1674
- };
1675
- case "heartbeat":
1676
- return {
1677
- type: "heartbeat"
1678
- };
1679
- case "unexpected_error":
1680
- return {
1681
- type: "unexpected_error",
1682
- status: {
1683
- queue,
1684
- message: data.message,
1685
- stage: "error",
1686
- success: false
1687
- }
1688
- };
1689
- case "estimation":
1690
- return {
1691
- type: "update",
1692
- status: {
1693
- queue,
1694
- stage: last_status || "pending",
1695
- code: data.code,
1696
- size: data.queue_size,
1697
- position: data.rank,
1698
- eta: data.rank_eta,
1699
- success: data.success
1700
- }
1701
- };
1702
- case "progress":
1703
- return {
1704
- type: "update",
1705
- status: {
1706
- queue,
1707
- stage: "pending",
1708
- code: data.code,
1709
- progress_data: data.progress_data,
1710
- success: data.success
1711
- }
1712
- };
1713
- case "log":
1714
- return { type: "log", data: data };
1715
- case "process_generating":
1716
- return {
1717
- type: "generating",
1718
- status: {
1719
- queue,
1720
- message: !data.success ? data.output.error : null,
1721
- stage: data.success ? "generating" : "error",
1722
- code: data.code,
1723
- progress_data: data.progress_data,
1724
- eta: data.average_duration
1725
- },
1726
- data: data.success ? data.output : null
1727
- };
1728
- case "process_completed":
1729
- if ("error" in data.output) {
1730
- return {
1731
- type: "update",
1732
- status: {
1733
- queue,
1734
- message: data.output.error as string,
1735
- stage: "error",
1736
- code: data.code,
1737
- success: data.success
1738
- }
1739
- };
1740
- }
1741
- return {
1742
- type: "complete",
1743
- status: {
1744
- queue,
1745
- message: !data.success ? data.output.error : undefined,
1746
- stage: data.success ? "complete" : "error",
1747
- code: data.code,
1748
- progress_data: data.progress_data
1749
- },
1750
- data: data.success ? data.output : null
1751
- };
1752
-
1753
- case "process_starts":
1754
- return {
1755
- type: "update",
1756
- status: {
1757
- queue,
1758
- stage: "pending",
1759
- code: data.code,
1760
- size: data.rank,
1761
- position: 0,
1762
- success: data.success,
1763
- eta: data.eta
1764
- }
1765
- };
1766
- }
362
+ /**
363
+ * @deprecated This method will be removed in v1.0. Use `Client.connect()` instead.
364
+ * Creates a client instance for interacting with Gradio apps.
365
+ *
366
+ * @param {string} app_reference - The reference or URL to a Gradio space or app.
367
+ * @param {ClientOptions} options - Configuration options for the client.
368
+ * @returns {Promise<Client>} A promise that resolves to a `Client` instance.
369
+ */
370
+ export async function client(
371
+ app_reference: string,
372
+ options: ClientOptions = {}
373
+ ): Promise<Client> {
374
+ return await Client.connect(app_reference, options);
375
+ }
1767
376
 
1768
- return { type: "none", status: { stage: "error", queue } };
377
+ /**
378
+ * @deprecated This method will be removed in v1.0. Use `Client.duplicate()` instead.
379
+ * Creates a duplicate of a space and returns a client instance for the duplicated space.
380
+ *
381
+ * @param {string} app_reference - The reference or URL to a Gradio space or app to duplicate.
382
+ * @param {DuplicateOptions} options - Configuration options for the client.
383
+ * @returns {Promise<Client>} A promise that resolves to a `Client` instance.
384
+ */
385
+ export async function duplicate_space(
386
+ app_reference: string,
387
+ options: DuplicateOptions
388
+ ): Promise<Client> {
389
+ return await Client.duplicate(app_reference, options);
1769
390
  }
391
+
392
+ export type ClientInstance = Client;