@gradio/client 0.0.1
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.
- package/README.md +46 -0
- package/package.json +9 -0
- package/src/client.ts +540 -0
- package/src/index.ts +2 -0
- package/src/types.ts +88 -0
- package/src/utils.ts +101 -0
package/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# `@gradio/client`
|
2
|
+
|
3
|
+
A javascript client to call Gradio APIs.
|
4
|
+
|
5
|
+
**usage**
|
6
|
+
|
7
|
+
```ts
|
8
|
+
import { client } from "@gradio/client";
|
9
|
+
|
10
|
+
const app = client();
|
11
|
+
|
12
|
+
const prediction = app.predict(endpoint, payload);
|
13
|
+
|
14
|
+
// listen for predictions
|
15
|
+
prediction.on("data", (event: { data: Array<unknown>; type: "data" }) => {});
|
16
|
+
|
17
|
+
// listen for status updates
|
18
|
+
prediction.on("status", (event: { data: Status; type: "data" }) => {});
|
19
|
+
|
20
|
+
interface Status {
|
21
|
+
status: "pending" | "error" | "complete" | "generating";
|
22
|
+
size: number;
|
23
|
+
position?: number;
|
24
|
+
eta?: number;
|
25
|
+
message?: string;
|
26
|
+
progress?: Array<{
|
27
|
+
progress: number | null;
|
28
|
+
index: number | null;
|
29
|
+
length: number | null;
|
30
|
+
unit: string | null;
|
31
|
+
desc: string | null;
|
32
|
+
}>;
|
33
|
+
}
|
34
|
+
|
35
|
+
// stop listening
|
36
|
+
prediction.off("data");
|
37
|
+
|
38
|
+
// cancel a prediction if it is a generator
|
39
|
+
prediction.cancel();
|
40
|
+
|
41
|
+
// chainable
|
42
|
+
const prediction_two = app
|
43
|
+
.predict(endpoint, payload)
|
44
|
+
.on("data", data_callback)
|
45
|
+
.on("status", status_callback);
|
46
|
+
```
|
package/package.json
ADDED
package/src/client.ts
ADDED
@@ -0,0 +1,540 @@
|
|
1
|
+
import {
|
2
|
+
process_endpoint,
|
3
|
+
RE_SPACE_NAME,
|
4
|
+
map_names_to_ids,
|
5
|
+
discussions_enabled
|
6
|
+
} from "./utils";
|
7
|
+
|
8
|
+
import type {
|
9
|
+
EventType,
|
10
|
+
EventListener,
|
11
|
+
ListenerMap,
|
12
|
+
Event,
|
13
|
+
Config,
|
14
|
+
Payload,
|
15
|
+
PostResponse,
|
16
|
+
UploadResponse,
|
17
|
+
Status,
|
18
|
+
SpaceStatus,
|
19
|
+
SpaceStatusCallback
|
20
|
+
} from "./types";
|
21
|
+
|
22
|
+
type event = <K extends EventType>(
|
23
|
+
eventType: K,
|
24
|
+
listener: EventListener<K>
|
25
|
+
) => ReturnType<predict>;
|
26
|
+
type predict = (endpoint: string, payload: Payload) => {};
|
27
|
+
|
28
|
+
type client_return = {
|
29
|
+
predict: predict;
|
30
|
+
config: Config;
|
31
|
+
on: event;
|
32
|
+
off: event;
|
33
|
+
cancel: (endpoint: string, fn_index?: number) => void;
|
34
|
+
};
|
35
|
+
|
36
|
+
const QUEUE_FULL_MSG = "This application is too busy. Keep trying!";
|
37
|
+
const BROKEN_CONNECTION_MSG = "Connection errored out.";
|
38
|
+
|
39
|
+
export async function post_data(
|
40
|
+
url: string,
|
41
|
+
body: unknown
|
42
|
+
): Promise<[PostResponse, number]> {
|
43
|
+
try {
|
44
|
+
var response = await fetch(url, {
|
45
|
+
method: "POST",
|
46
|
+
body: JSON.stringify(body),
|
47
|
+
headers: { "Content-Type": "application/json" }
|
48
|
+
});
|
49
|
+
} catch (e) {
|
50
|
+
return [{ error: BROKEN_CONNECTION_MSG }, 500];
|
51
|
+
}
|
52
|
+
const output: PostResponse = await response.json();
|
53
|
+
return [output, response.status];
|
54
|
+
}
|
55
|
+
|
56
|
+
export async function upload_files(
|
57
|
+
root: string,
|
58
|
+
files: Array<File>
|
59
|
+
): Promise<UploadResponse> {
|
60
|
+
const formData = new FormData();
|
61
|
+
files.forEach((file) => {
|
62
|
+
formData.append("files", file);
|
63
|
+
});
|
64
|
+
try {
|
65
|
+
var response = await fetch(`${root}/upload`, {
|
66
|
+
method: "POST",
|
67
|
+
body: formData
|
68
|
+
});
|
69
|
+
} catch (e) {
|
70
|
+
return { error: BROKEN_CONNECTION_MSG };
|
71
|
+
}
|
72
|
+
const output: UploadResponse["files"] = await response.json();
|
73
|
+
return { files: output };
|
74
|
+
}
|
75
|
+
|
76
|
+
export async function client(
|
77
|
+
app_reference: string,
|
78
|
+
space_status_callback?: SpaceStatusCallback
|
79
|
+
): Promise<client_return> {
|
80
|
+
return new Promise(async (res, rej) => {
|
81
|
+
const return_obj = {
|
82
|
+
predict,
|
83
|
+
on,
|
84
|
+
off,
|
85
|
+
cancel
|
86
|
+
};
|
87
|
+
|
88
|
+
const listener_map: ListenerMap<EventType> = {};
|
89
|
+
const { ws_protocol, http_protocol, host, space_id } =
|
90
|
+
await process_endpoint(app_reference);
|
91
|
+
const session_hash = Math.random().toString(36).substring(2);
|
92
|
+
const ws_map = new Map<number, WebSocket>();
|
93
|
+
const last_status: Record<string, Status["status"]> = {};
|
94
|
+
let config: Config;
|
95
|
+
let api_map: Record<string, number> = {};
|
96
|
+
|
97
|
+
function config_success(_config: Config) {
|
98
|
+
config = _config;
|
99
|
+
api_map = map_names_to_ids(_config?.dependencies || []);
|
100
|
+
return {
|
101
|
+
config,
|
102
|
+
...return_obj
|
103
|
+
};
|
104
|
+
}
|
105
|
+
|
106
|
+
function on<K extends EventType>(eventType: K, listener: EventListener<K>) {
|
107
|
+
const narrowed_listener_map: ListenerMap<K> = listener_map;
|
108
|
+
let listeners = narrowed_listener_map[eventType] || [];
|
109
|
+
narrowed_listener_map[eventType] = listeners;
|
110
|
+
listeners?.push(listener);
|
111
|
+
|
112
|
+
return { ...return_obj, config };
|
113
|
+
}
|
114
|
+
|
115
|
+
function off<K extends EventType>(
|
116
|
+
eventType: K,
|
117
|
+
listener: EventListener<K>
|
118
|
+
) {
|
119
|
+
const narrowed_listener_map: ListenerMap<K> = listener_map;
|
120
|
+
let listeners = narrowed_listener_map[eventType] || [];
|
121
|
+
listeners = listeners?.filter((l) => l !== listener);
|
122
|
+
narrowed_listener_map[eventType] = listeners;
|
123
|
+
|
124
|
+
return { ...return_obj, config };
|
125
|
+
}
|
126
|
+
|
127
|
+
function cancel(endpoint: string, fn_index?: number) {
|
128
|
+
const _index =
|
129
|
+
typeof fn_index === "number" ? fn_index : api_map[endpoint];
|
130
|
+
|
131
|
+
fire_event({
|
132
|
+
type: "status",
|
133
|
+
endpoint,
|
134
|
+
fn_index: _index,
|
135
|
+
status: "complete",
|
136
|
+
queue: false
|
137
|
+
});
|
138
|
+
|
139
|
+
ws_map.get(_index)?.close();
|
140
|
+
}
|
141
|
+
|
142
|
+
function fire_event<K extends EventType>(event: Event<K>) {
|
143
|
+
const narrowed_listener_map: ListenerMap<K> = listener_map;
|
144
|
+
let listeners = narrowed_listener_map[event.type] || [];
|
145
|
+
listeners?.forEach((l) => l(event));
|
146
|
+
}
|
147
|
+
|
148
|
+
async function handle_space_sucess(status: SpaceStatus) {
|
149
|
+
if (space_status_callback) space_status_callback(status);
|
150
|
+
if (status.status === "running")
|
151
|
+
try {
|
152
|
+
console.log(host);
|
153
|
+
config = await resolve_config(`${http_protocol}//${host}`);
|
154
|
+
res(config_success(config));
|
155
|
+
} catch (e) {
|
156
|
+
if (space_status_callback) {
|
157
|
+
space_status_callback({
|
158
|
+
status: "error",
|
159
|
+
message: "Could not load this space.",
|
160
|
+
load_status: "error",
|
161
|
+
detail: "NOT_FOUND"
|
162
|
+
});
|
163
|
+
}
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
try {
|
168
|
+
config = await resolve_config(`${http_protocol}//${host}`);
|
169
|
+
res(config_success(config));
|
170
|
+
} catch (e) {
|
171
|
+
if (space_id) {
|
172
|
+
check_space_status(
|
173
|
+
space_id,
|
174
|
+
RE_SPACE_NAME.test(space_id) ? "space_name" : "subdomain",
|
175
|
+
handle_space_sucess
|
176
|
+
);
|
177
|
+
} else {
|
178
|
+
if (space_status_callback)
|
179
|
+
space_status_callback({
|
180
|
+
status: "error",
|
181
|
+
message: "Could not load this space.",
|
182
|
+
load_status: "error",
|
183
|
+
detail: "NOT_FOUND"
|
184
|
+
});
|
185
|
+
}
|
186
|
+
}
|
187
|
+
function make_predict(endpoint: string, payload: Payload) {
|
188
|
+
return new Promise((res, rej) => {
|
189
|
+
const trimmed_endpoint = endpoint.replace(/^\//, "");
|
190
|
+
let fn_index =
|
191
|
+
typeof payload.fn_index === "number"
|
192
|
+
? payload.fn_index
|
193
|
+
: api_map[trimmed_endpoint];
|
194
|
+
|
195
|
+
if (skip_queue(fn_index, config)) {
|
196
|
+
fire_event({
|
197
|
+
type: "status",
|
198
|
+
endpoint,
|
199
|
+
status: "pending",
|
200
|
+
queue: false,
|
201
|
+
fn_index
|
202
|
+
});
|
203
|
+
|
204
|
+
post_data(
|
205
|
+
`${http_protocol}//${host + config.path}/run${
|
206
|
+
endpoint.startsWith("/") ? endpoint : `/${endpoint}`
|
207
|
+
}`,
|
208
|
+
{
|
209
|
+
...payload,
|
210
|
+
session_hash
|
211
|
+
}
|
212
|
+
)
|
213
|
+
.then(([output, status_code]) => {
|
214
|
+
if (status_code == 200) {
|
215
|
+
fire_event({
|
216
|
+
type: "status",
|
217
|
+
endpoint,
|
218
|
+
fn_index,
|
219
|
+
status: "complete",
|
220
|
+
eta: output.average_duration,
|
221
|
+
queue: false
|
222
|
+
});
|
223
|
+
|
224
|
+
fire_event({
|
225
|
+
type: "data",
|
226
|
+
endpoint,
|
227
|
+
fn_index,
|
228
|
+
data: output.data
|
229
|
+
});
|
230
|
+
} else {
|
231
|
+
fire_event({
|
232
|
+
type: "status",
|
233
|
+
status: "error",
|
234
|
+
endpoint,
|
235
|
+
fn_index,
|
236
|
+
message: output.error,
|
237
|
+
queue: false
|
238
|
+
});
|
239
|
+
}
|
240
|
+
})
|
241
|
+
.catch((e) => {
|
242
|
+
fire_event({
|
243
|
+
type: "status",
|
244
|
+
status: "error",
|
245
|
+
message: e.message,
|
246
|
+
endpoint,
|
247
|
+
fn_index,
|
248
|
+
queue: false
|
249
|
+
});
|
250
|
+
throw new Error(e.message);
|
251
|
+
});
|
252
|
+
} else {
|
253
|
+
fire_event({
|
254
|
+
type: "status",
|
255
|
+
status: "pending",
|
256
|
+
queue: true,
|
257
|
+
endpoint,
|
258
|
+
fn_index
|
259
|
+
});
|
260
|
+
|
261
|
+
const ws_endpoint = `${ws_protocol}://${
|
262
|
+
host + config.path
|
263
|
+
}/queue/join`;
|
264
|
+
|
265
|
+
const websocket = new WebSocket(ws_endpoint);
|
266
|
+
|
267
|
+
ws_map.set(fn_index, websocket);
|
268
|
+
websocket.onclose = (evt) => {
|
269
|
+
if (!evt.wasClean) {
|
270
|
+
fire_event({
|
271
|
+
type: "status",
|
272
|
+
status: "error",
|
273
|
+
message: BROKEN_CONNECTION_MSG,
|
274
|
+
queue: true,
|
275
|
+
endpoint,
|
276
|
+
fn_index
|
277
|
+
});
|
278
|
+
}
|
279
|
+
};
|
280
|
+
|
281
|
+
websocket.onmessage = function (event) {
|
282
|
+
const _data = JSON.parse(event.data);
|
283
|
+
const { type, status, data } = handle_message(
|
284
|
+
_data,
|
285
|
+
last_status[fn_index]
|
286
|
+
);
|
287
|
+
|
288
|
+
if (type === "update" && status) {
|
289
|
+
// call 'status' listeners
|
290
|
+
fire_event({ type: "status", endpoint, fn_index, ...status });
|
291
|
+
if (status.status === "error") {
|
292
|
+
websocket.close();
|
293
|
+
rej(status);
|
294
|
+
}
|
295
|
+
} else if (type === "hash") {
|
296
|
+
websocket.send(JSON.stringify({ fn_index, session_hash }));
|
297
|
+
return;
|
298
|
+
} else if (type === "data") {
|
299
|
+
websocket.send(JSON.stringify({ ...payload, session_hash }));
|
300
|
+
} else if (type === "complete") {
|
301
|
+
fire_event({
|
302
|
+
type: "status",
|
303
|
+
...status,
|
304
|
+
status: status?.status!,
|
305
|
+
queue: true,
|
306
|
+
endpoint,
|
307
|
+
fn_index
|
308
|
+
});
|
309
|
+
websocket.close();
|
310
|
+
} else if (type === "generating") {
|
311
|
+
fire_event({
|
312
|
+
type: "status",
|
313
|
+
...status,
|
314
|
+
status: status?.status!,
|
315
|
+
queue: true,
|
316
|
+
endpoint,
|
317
|
+
fn_index
|
318
|
+
});
|
319
|
+
}
|
320
|
+
if (data) {
|
321
|
+
fire_event({
|
322
|
+
type: "data",
|
323
|
+
data: data.data,
|
324
|
+
endpoint,
|
325
|
+
fn_index
|
326
|
+
});
|
327
|
+
res({ data: data.data });
|
328
|
+
}
|
329
|
+
};
|
330
|
+
}
|
331
|
+
});
|
332
|
+
}
|
333
|
+
|
334
|
+
/**
|
335
|
+
* Run a prediction.
|
336
|
+
* @param endpoint - The prediction endpoint to use.
|
337
|
+
* @param status_callback - A function that is called with the current status of the prediction immediately and every time it updates.
|
338
|
+
* @return Returns the data for the prediction or an error message.
|
339
|
+
*/
|
340
|
+
function predict(endpoint: string, payload: Payload) {
|
341
|
+
return make_predict(endpoint, payload);
|
342
|
+
}
|
343
|
+
});
|
344
|
+
}
|
345
|
+
|
346
|
+
function skip_queue(id: number, config: Config) {
|
347
|
+
return (
|
348
|
+
!(config?.dependencies?.[id].queue === null
|
349
|
+
? config.enable_queue
|
350
|
+
: config?.dependencies?.[id].queue) || false
|
351
|
+
);
|
352
|
+
}
|
353
|
+
|
354
|
+
async function resolve_config(endpoint?: string): Promise<Config> {
|
355
|
+
if (window.gradio_config && location.origin !== "http://localhost:9876") {
|
356
|
+
const path = window.gradio_config.root;
|
357
|
+
const config = window.gradio_config;
|
358
|
+
config.root = endpoint + config.root;
|
359
|
+
return { ...config, path: path };
|
360
|
+
} else if (endpoint) {
|
361
|
+
let response = await fetch(`${endpoint}/config`);
|
362
|
+
|
363
|
+
if (response.status === 200) {
|
364
|
+
const config = await response.json();
|
365
|
+
config.path = config.path ?? "";
|
366
|
+
config.root = endpoint;
|
367
|
+
return config;
|
368
|
+
} else {
|
369
|
+
throw new Error("Could not get config.");
|
370
|
+
}
|
371
|
+
}
|
372
|
+
|
373
|
+
throw new Error("No config or app endpoint found");
|
374
|
+
}
|
375
|
+
|
376
|
+
async function check_space_status(
|
377
|
+
id: string,
|
378
|
+
type: "subdomain" | "space_name",
|
379
|
+
space_status_callback: SpaceStatusCallback
|
380
|
+
) {
|
381
|
+
let endpoint =
|
382
|
+
type === "subdomain"
|
383
|
+
? `https://huggingface.co/api/spaces/by-subdomain/${id}`
|
384
|
+
: `https://huggingface.co/api/spaces/${id}`;
|
385
|
+
let response;
|
386
|
+
let _status;
|
387
|
+
try {
|
388
|
+
response = await fetch(endpoint);
|
389
|
+
_status = response.status;
|
390
|
+
if (_status !== 200) {
|
391
|
+
throw new Error();
|
392
|
+
}
|
393
|
+
response = await response.json();
|
394
|
+
} catch (e) {
|
395
|
+
space_status_callback({
|
396
|
+
status: "error",
|
397
|
+
load_status: "error",
|
398
|
+
message: "Could not get space status",
|
399
|
+
detail: "NOT_FOUND"
|
400
|
+
});
|
401
|
+
return;
|
402
|
+
}
|
403
|
+
|
404
|
+
if (!response || _status !== 200) return;
|
405
|
+
const {
|
406
|
+
runtime: { stage },
|
407
|
+
id: space_name
|
408
|
+
} = response;
|
409
|
+
|
410
|
+
switch (stage) {
|
411
|
+
case "STOPPED":
|
412
|
+
case "SLEEPING":
|
413
|
+
space_status_callback({
|
414
|
+
status: "sleeping",
|
415
|
+
load_status: "pending",
|
416
|
+
message: "Space is asleep. Waking it up...",
|
417
|
+
detail: stage
|
418
|
+
});
|
419
|
+
|
420
|
+
setTimeout(() => {
|
421
|
+
check_space_status(id, type, space_status_callback);
|
422
|
+
}, 1000);
|
423
|
+
break;
|
424
|
+
// poll for status
|
425
|
+
case "RUNNING":
|
426
|
+
case "RUNNING_BUILDING":
|
427
|
+
space_status_callback({
|
428
|
+
status: "running",
|
429
|
+
load_status: "complete",
|
430
|
+
message: "",
|
431
|
+
detail: stage
|
432
|
+
});
|
433
|
+
// load_config(source);
|
434
|
+
// launch
|
435
|
+
break;
|
436
|
+
case "BUILDING":
|
437
|
+
space_status_callback({
|
438
|
+
status: "building",
|
439
|
+
load_status: "pending",
|
440
|
+
message: "Space is building...",
|
441
|
+
detail: stage
|
442
|
+
});
|
443
|
+
|
444
|
+
setTimeout(() => {
|
445
|
+
check_space_status(id, type, space_status_callback);
|
446
|
+
}, 1000);
|
447
|
+
break;
|
448
|
+
default:
|
449
|
+
space_status_callback({
|
450
|
+
status: "space_error",
|
451
|
+
load_status: "error",
|
452
|
+
message: "This space is experiencing an issue.",
|
453
|
+
detail: stage,
|
454
|
+
discussions_enabled: await discussions_enabled(space_name)
|
455
|
+
});
|
456
|
+
break;
|
457
|
+
}
|
458
|
+
}
|
459
|
+
|
460
|
+
function handle_message(
|
461
|
+
data: any,
|
462
|
+
last_status: Status["status"]
|
463
|
+
): {
|
464
|
+
type: "hash" | "data" | "update" | "complete" | "generating" | "none";
|
465
|
+
data?: any;
|
466
|
+
status?: Status;
|
467
|
+
} {
|
468
|
+
const queue = true;
|
469
|
+
switch (data.msg) {
|
470
|
+
case "send_data":
|
471
|
+
return { type: "data" };
|
472
|
+
case "send_hash":
|
473
|
+
return { type: "hash" };
|
474
|
+
case "queue_full":
|
475
|
+
return {
|
476
|
+
type: "update",
|
477
|
+
status: {
|
478
|
+
queue,
|
479
|
+
message: QUEUE_FULL_MSG,
|
480
|
+
status: "error"
|
481
|
+
}
|
482
|
+
};
|
483
|
+
case "estimation":
|
484
|
+
return {
|
485
|
+
type: "update",
|
486
|
+
status: {
|
487
|
+
queue,
|
488
|
+
status: last_status || "pending",
|
489
|
+
size: data.queue_size,
|
490
|
+
position: data.rank,
|
491
|
+
eta: data.rank_eta
|
492
|
+
}
|
493
|
+
};
|
494
|
+
case "progress":
|
495
|
+
return {
|
496
|
+
type: "update",
|
497
|
+
status: {
|
498
|
+
queue,
|
499
|
+
status: "pending",
|
500
|
+
progress: data.progress_data
|
501
|
+
}
|
502
|
+
};
|
503
|
+
case "process_generating":
|
504
|
+
return {
|
505
|
+
type: "generating",
|
506
|
+
status: {
|
507
|
+
queue,
|
508
|
+
message: !data.success ? data.output.error : null,
|
509
|
+
status: data.success ? "generating" : "error",
|
510
|
+
progress: data.progress_data,
|
511
|
+
eta: data.average_duration
|
512
|
+
},
|
513
|
+
data: data.success ? data.output : null
|
514
|
+
};
|
515
|
+
case "process_completed":
|
516
|
+
return {
|
517
|
+
type: "complete",
|
518
|
+
status: {
|
519
|
+
queue,
|
520
|
+
message: !data.success ? data.output.error : undefined,
|
521
|
+
status: data.success ? "complete" : "error",
|
522
|
+
progress: data.progress_data,
|
523
|
+
eta: data.output.average_duration
|
524
|
+
},
|
525
|
+
data: data.success ? data.output : null
|
526
|
+
};
|
527
|
+
case "process_starts":
|
528
|
+
return {
|
529
|
+
type: "update",
|
530
|
+
status: {
|
531
|
+
queue,
|
532
|
+
status: "pending",
|
533
|
+
size: data.rank,
|
534
|
+
position: 0
|
535
|
+
}
|
536
|
+
};
|
537
|
+
}
|
538
|
+
|
539
|
+
return { type: "none", status: { status: "error", queue } };
|
540
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
export interface Config {
|
2
|
+
auth_required: boolean | undefined;
|
3
|
+
auth_message: string;
|
4
|
+
components: any[];
|
5
|
+
css: string | null;
|
6
|
+
dependencies: any[];
|
7
|
+
dev_mode: boolean;
|
8
|
+
enable_queue: boolean;
|
9
|
+
layout: any;
|
10
|
+
mode: "blocks" | "interface";
|
11
|
+
root: string;
|
12
|
+
theme: string;
|
13
|
+
title: string;
|
14
|
+
version: string;
|
15
|
+
is_space: boolean;
|
16
|
+
is_colab: boolean;
|
17
|
+
show_api: boolean;
|
18
|
+
stylesheets: string[];
|
19
|
+
path: string;
|
20
|
+
}
|
21
|
+
|
22
|
+
export interface Payload {
|
23
|
+
data: Array<unknown>;
|
24
|
+
fn_index: number;
|
25
|
+
}
|
26
|
+
|
27
|
+
export interface PostResponse {
|
28
|
+
error?: string;
|
29
|
+
[x: string]: any;
|
30
|
+
}
|
31
|
+
export interface UploadResponse {
|
32
|
+
error?: string;
|
33
|
+
files?: Array<string>;
|
34
|
+
}
|
35
|
+
|
36
|
+
export interface Status {
|
37
|
+
queue: boolean;
|
38
|
+
status: "pending" | "error" | "complete" | "generating";
|
39
|
+
size?: number;
|
40
|
+
position?: number;
|
41
|
+
eta?: number;
|
42
|
+
message?: string;
|
43
|
+
progress?: Array<{
|
44
|
+
progress: number | null;
|
45
|
+
index: number | null;
|
46
|
+
length: number | null;
|
47
|
+
unit: string | null;
|
48
|
+
desc: string | null;
|
49
|
+
}>;
|
50
|
+
}
|
51
|
+
|
52
|
+
export interface SpaceStatusNormal {
|
53
|
+
status: "sleeping" | "running" | "building" | "error" | "stopped";
|
54
|
+
detail:
|
55
|
+
| "SLEEPING"
|
56
|
+
| "RUNNING"
|
57
|
+
| "RUNNING_BUILDING"
|
58
|
+
| "BUILDING"
|
59
|
+
| "NOT_FOUND";
|
60
|
+
load_status: "pending" | "error" | "complete" | "generating";
|
61
|
+
message: string;
|
62
|
+
}
|
63
|
+
export interface SpaceStatusError {
|
64
|
+
status: "space_error";
|
65
|
+
detail: "NO_APP_FILE" | "CONFIG_ERROR" | "BUILD_ERROR" | "RUNTIME_ERROR";
|
66
|
+
load_status: "error";
|
67
|
+
message: string;
|
68
|
+
discussions_enabled: boolean;
|
69
|
+
}
|
70
|
+
export type SpaceStatus = SpaceStatusNormal | SpaceStatusError;
|
71
|
+
|
72
|
+
export type status_callback_function = (a: Status) => void;
|
73
|
+
export type SpaceStatusCallback = (a: SpaceStatus) => void;
|
74
|
+
|
75
|
+
export type EventType = "data" | "status";
|
76
|
+
|
77
|
+
export interface EventMap {
|
78
|
+
data: Record<string, any>;
|
79
|
+
status: Status;
|
80
|
+
}
|
81
|
+
|
82
|
+
export type Event<K extends EventType> = {
|
83
|
+
[P in K]: EventMap[P] & { type: P; endpoint: string; fn_index: number };
|
84
|
+
}[K];
|
85
|
+
export type EventListener<K extends EventType> = (event: Event<K>) => void;
|
86
|
+
export type ListenerMap<K extends EventType> = {
|
87
|
+
[P in K]?: EventListener<K>[];
|
88
|
+
};
|
package/src/utils.ts
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
import type { Config } from "./types";
|
2
|
+
|
3
|
+
export function determine_protocol(endpoint: string): {
|
4
|
+
ws_protocol: "ws" | "wss";
|
5
|
+
http_protocol: "http:" | "https:";
|
6
|
+
host: string;
|
7
|
+
} {
|
8
|
+
if (endpoint.startsWith("http")) {
|
9
|
+
const { protocol, host } = new URL(endpoint);
|
10
|
+
|
11
|
+
if (host.endsWith("hf.space")) {
|
12
|
+
return {
|
13
|
+
ws_protocol: "wss",
|
14
|
+
host: host,
|
15
|
+
http_protocol: protocol as "http:" | "https:"
|
16
|
+
};
|
17
|
+
} else {
|
18
|
+
return {
|
19
|
+
ws_protocol: protocol === "https:" ? "wss" : "ws",
|
20
|
+
http_protocol: protocol as "http:" | "https:",
|
21
|
+
host
|
22
|
+
};
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
// default to secure if no protocol is provided
|
27
|
+
return {
|
28
|
+
ws_protocol: "wss",
|
29
|
+
http_protocol: "https:",
|
30
|
+
host: endpoint
|
31
|
+
};
|
32
|
+
}
|
33
|
+
|
34
|
+
export const RE_SPACE_NAME = /^[^\/]*\/[^\/]*$/;
|
35
|
+
export const RE_SPACE_DOMAIN = /.*hf\.space\/{0,1}$/;
|
36
|
+
export async function process_endpoint(app_reference: string): Promise<{
|
37
|
+
space_id: string | false;
|
38
|
+
host: string;
|
39
|
+
ws_protocol: "ws" | "wss";
|
40
|
+
http_protocol: "http:" | "https:";
|
41
|
+
}> {
|
42
|
+
const _app_reference = app_reference.trim();
|
43
|
+
|
44
|
+
if (RE_SPACE_NAME.test(_app_reference)) {
|
45
|
+
const _host = (
|
46
|
+
await (
|
47
|
+
await fetch(`https://huggingface.co/api/spaces/${_app_reference}/host`)
|
48
|
+
).json()
|
49
|
+
).host;
|
50
|
+
return {
|
51
|
+
space_id: app_reference,
|
52
|
+
...determine_protocol(_host)
|
53
|
+
};
|
54
|
+
}
|
55
|
+
|
56
|
+
if (RE_SPACE_DOMAIN.test(_app_reference)) {
|
57
|
+
const { ws_protocol, http_protocol, host } =
|
58
|
+
determine_protocol(_app_reference);
|
59
|
+
|
60
|
+
return {
|
61
|
+
space_id: host.replace(".hf.space", ""),
|
62
|
+
ws_protocol,
|
63
|
+
http_protocol,
|
64
|
+
host
|
65
|
+
};
|
66
|
+
}
|
67
|
+
|
68
|
+
return {
|
69
|
+
space_id: false,
|
70
|
+
...determine_protocol(_app_reference)
|
71
|
+
};
|
72
|
+
}
|
73
|
+
|
74
|
+
export function map_names_to_ids(fns: Config["dependencies"]) {
|
75
|
+
let apis: Record<string, number> = {};
|
76
|
+
|
77
|
+
fns.forEach(({ api_name }, i) => {
|
78
|
+
if (api_name) apis[api_name] = i;
|
79
|
+
});
|
80
|
+
|
81
|
+
return apis;
|
82
|
+
}
|
83
|
+
|
84
|
+
const RE_DISABLED_DISCUSSION =
|
85
|
+
/^(?=[^]*\b[dD]iscussions{0,1}\b)(?=[^]*\b[dD]isabled\b)[^]*$/;
|
86
|
+
export async function discussions_enabled(space_id: string) {
|
87
|
+
try {
|
88
|
+
const r = await fetch(
|
89
|
+
`https://huggingface.co/api/spaces/${space_id}/discussions`,
|
90
|
+
{
|
91
|
+
method: "HEAD"
|
92
|
+
}
|
93
|
+
);
|
94
|
+
const error = r.headers.get("x-error-message");
|
95
|
+
|
96
|
+
if (error && RE_DISABLED_DISCUSSION.test(error)) return false;
|
97
|
+
else return true;
|
98
|
+
} catch (e) {
|
99
|
+
return false;
|
100
|
+
}
|
101
|
+
}
|