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