@gradio/core 0.0.4 → 0.1.0-beta.2

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/src/Blocks.svelte CHANGED
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { tick } from "svelte";
2
+ import { tick, onMount } from "svelte";
3
3
  import { _ } from "svelte-i18n";
4
4
  import { Client } from "@gradio/client";
5
5
 
@@ -12,7 +12,7 @@
12
12
  import type { ThemeMode, Payload } from "./types";
13
13
  import { Toast } from "@gradio/statustracker";
14
14
  import type { ToastMessage } from "@gradio/statustracker";
15
- import type { ShareData } from "@gradio/utils";
15
+ import type { ShareData, ValueData } from "@gradio/utils";
16
16
  import MountComponents from "./MountComponents.svelte";
17
17
 
18
18
  import logo from "./images/logo.svg";
@@ -21,7 +21,7 @@
21
21
  import type {
22
22
  LogMessage,
23
23
  RenderMessage,
24
- StatusMessage
24
+ StatusMessage,
25
25
  } from "@gradio/client";
26
26
 
27
27
  setupi18n();
@@ -45,36 +45,50 @@
45
45
  export let fill_height = false;
46
46
  export let ready: boolean;
47
47
  export let username: string | null;
48
+ export let api_prefix: string;
49
+ export let max_file_size: number;
50
+ export let initial_layout: LayoutNode | undefined = undefined;
48
51
 
49
- const {
52
+ let {
50
53
  layout: _layout,
51
54
  targets,
52
55
  update_value,
53
56
  get_data,
57
+ modify_stream,
58
+ get_stream_state,
59
+ set_time_limit,
54
60
  loading_status,
55
61
  scheduled_updates,
56
62
  create_layout,
57
- rerender_layout
63
+ rerender_layout,
58
64
  } = create_components();
59
65
 
60
- $: create_layout({
61
- components,
62
- layout,
63
- dependencies,
64
- root,
65
- app,
66
- options: {
67
- fill_height
68
- }
69
- });
66
+ // @ts-ignore
67
+ $_layout = initial_layout;
68
+
69
+ $: components, layout, dependencies, root, app, fill_height, target, run();
70
70
 
71
71
  $: {
72
72
  ready = !!$_layout;
73
73
  }
74
74
 
75
- let params = new URLSearchParams(window.location.search);
76
- let api_docs_visible = params.get("view") === "api" && show_api;
77
- let api_recorder_visible = params.get("view") === "api-recorder" && show_api;
75
+ async function run(): Promise<void> {
76
+ await create_layout({
77
+ components,
78
+ layout,
79
+ dependencies,
80
+ root: root + api_prefix,
81
+ app,
82
+ options: {
83
+ fill_height,
84
+ },
85
+ });
86
+ }
87
+
88
+ export let search_params: URLSearchParams;
89
+ let api_docs_visible = search_params.get("view") === "api" && show_api;
90
+ let api_recorder_visible =
91
+ search_params.get("view") === "api-recorder" && show_api;
78
92
  function set_api_docs_visible(visible: boolean): void {
79
93
  api_recorder_visible = false;
80
94
  api_docs_visible = visible;
@@ -96,7 +110,7 @@
96
110
  return {
97
111
  id: outputs[i],
98
112
  prop: "value_is_output",
99
- value: true
113
+ value: true,
100
114
  };
101
115
  });
102
116
 
@@ -119,7 +133,7 @@
119
133
  updates.push({
120
134
  id: outputs[i],
121
135
  prop: update_key,
122
- value: update_value
136
+ value: update_value,
123
137
  });
124
138
  }
125
139
  }
@@ -127,7 +141,7 @@
127
141
  updates.push({
128
142
  id: outputs[i],
129
143
  prop: "value",
130
- value
144
+ value,
131
145
  });
132
146
  }
133
147
  });
@@ -144,7 +158,7 @@
144
158
  fn_index: number,
145
159
  type: ToastMessage["type"],
146
160
  duration: number | null = 10,
147
- visible = true
161
+ visible = true,
148
162
  ): ToastMessage & { fn_index: number } {
149
163
  return {
150
164
  message,
@@ -152,13 +166,13 @@
152
166
  type,
153
167
  id: ++_error_id,
154
168
  duration,
155
- visible
169
+ visible,
156
170
  };
157
171
  }
158
172
 
159
173
  export function add_new_message(
160
174
  message: string,
161
- type: ToastMessage["type"]
175
+ type: ToastMessage["type"],
162
176
  ): void {
163
177
  messages = [new_message(message, -1, type), ...messages];
164
178
  }
@@ -166,31 +180,25 @@
166
180
  let _error_id = -1;
167
181
 
168
182
  let user_left_page = false;
169
- document.addEventListener("visibilitychange", function () {
170
- if (document.visibilityState === "hidden") {
171
- user_left_page = true;
172
- }
173
- });
174
183
 
175
184
  const MESSAGE_QUOTE_RE = /^'([^]+)'$/;
176
185
 
177
186
  const DUPLICATE_MESSAGE = $_("blocks.long_requests_queue");
178
187
  const MOBILE_QUEUE_WARNING = $_("blocks.connection_can_break");
179
188
  const MOBILE_RECONNECT_MESSAGE = $_("blocks.lost_connection");
189
+ const WAITING_FOR_INPUTS_MESSAGE = $_("blocks.waiting_for_inputs");
180
190
  const SHOW_DUPLICATE_MESSAGE_ON_ETA = 15;
181
191
  const SHOW_MOBILE_QUEUE_WARNING_ON_ETA = 10;
182
- const is_mobile_device =
183
- /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
184
- navigator.userAgent
185
- );
192
+ let is_mobile_device = false;
186
193
  let showed_duplicate_message = false;
187
194
  let showed_mobile_warning = false;
195
+ let inputs_waiting: number[] = [];
188
196
 
189
197
  // as state updates are not synchronous, we need to ensure updates are flushed before triggering any requests
190
198
  function wait_then_trigger_api_call(
191
199
  dep_index: number,
192
200
  trigger_id: number | null = null,
193
- event_data: unknown = null
201
+ event_data: unknown = null,
194
202
  ): void {
195
203
  let _unsub = (): void => {};
196
204
  function unsub(): void {
@@ -208,13 +216,36 @@
208
216
  }
209
217
  }
210
218
 
219
+ async function get_component_value_or_event_data(
220
+ component_id: number,
221
+ trigger_id: number | null,
222
+ event_data: unknown,
223
+ ): Promise<any> {
224
+ if (
225
+ component_id === trigger_id &&
226
+ event_data &&
227
+ (event_data as ValueData).is_value_data === true
228
+ ) {
229
+ // @ts-ignore
230
+ return event_data.value;
231
+ }
232
+ return get_data(component_id);
233
+ }
234
+
211
235
  async function trigger_api_call(
212
236
  dep_index: number,
213
237
  trigger_id: number | null = null,
214
- event_data: unknown = null
238
+ event_data: unknown = null,
215
239
  ): Promise<void> {
216
240
  let dep = dependencies.find((dep) => dep.id === dep_index)!;
217
-
241
+ if (inputs_waiting.length > 0) {
242
+ for (const input of inputs_waiting) {
243
+ if (dep.inputs.includes(input)) {
244
+ add_new_message(WAITING_FOR_INPUTS_MESSAGE, "warning");
245
+ return;
246
+ }
247
+ }
248
+ }
218
249
  const current_status = loading_status.get_status_for_fn(dep_index);
219
250
  messages = messages.filter(({ fn_index }) => fn_index !== dep_index);
220
251
  if (current_status === "pending" || current_status === "generating") {
@@ -223,17 +254,21 @@
223
254
 
224
255
  let payload: Payload = {
225
256
  fn_index: dep_index,
226
- data: await Promise.all(dep.inputs.map((id) => get_data(id))),
257
+ data: await Promise.all(
258
+ dep.inputs.map((id) =>
259
+ get_component_value_or_event_data(id, trigger_id, event_data),
260
+ ),
261
+ ),
227
262
  event_data: dep.collects_event_data ? event_data : null,
228
- trigger_id: trigger_id
263
+ trigger_id: trigger_id,
229
264
  };
230
265
 
231
266
  if (dep.frontend_fn) {
232
267
  dep
233
268
  .frontend_fn(
234
269
  payload.data.concat(
235
- await Promise.all(dep.outputs.map((id) => get_data(id)))
236
- )
270
+ await Promise.all(dep.outputs.map((id) => get_data(id))),
271
+ ),
237
272
  )
238
273
  .then((v: unknown[]) => {
239
274
  if (dep.backend_fn) {
@@ -249,7 +284,7 @@
249
284
  const submission = submit_map.get(fn_index);
250
285
  submission?.cancel();
251
286
  return submission;
252
- })
287
+ }),
253
288
  );
254
289
  } else {
255
290
  if (dep.backend_fn) {
@@ -259,30 +294,55 @@
259
294
 
260
295
  function trigger_prediction(dep: Dependency, payload: Payload): void {
261
296
  if (dep.trigger_mode === "once") {
262
- if (!dep.pending_request) make_prediction(payload);
297
+ if (!dep.pending_request)
298
+ make_prediction(payload, dep.connection == "stream");
263
299
  } else if (dep.trigger_mode === "multiple") {
264
- make_prediction(payload);
300
+ make_prediction(payload, dep.connection == "stream");
265
301
  } else if (dep.trigger_mode === "always_last") {
266
302
  if (!dep.pending_request) {
267
- make_prediction(payload);
303
+ make_prediction(payload, dep.connection == "stream");
268
304
  } else {
269
305
  dep.final_event = payload;
270
306
  }
271
307
  }
272
308
  }
273
-
274
- async function make_prediction(payload: Payload): Promise<void> {
309
+ $: console.log({ app });
310
+ async function make_prediction(
311
+ payload: Payload,
312
+ streaming = false,
313
+ ): Promise<void> {
275
314
  if (api_recorder_visible) {
276
315
  api_calls = [...api_calls, JSON.parse(JSON.stringify(payload))];
277
316
  }
278
317
 
279
318
  let submission: ReturnType<typeof app.submit>;
319
+ app.set_current_payload(payload);
320
+ if (streaming) {
321
+ if (!submit_map.has(dep_index)) {
322
+ dep.inputs.forEach((id) => modify_stream(id, "waiting"));
323
+ } else if (
324
+ submit_map.has(dep_index) &&
325
+ dep.inputs.some((id) => get_stream_state(id) === "waiting")
326
+ ) {
327
+ return;
328
+ } else if (
329
+ submit_map.has(dep_index) &&
330
+ dep.inputs.some((id) => get_stream_state(id) === "open")
331
+ ) {
332
+ await app.post_data(
333
+ // @ts-ignore
334
+ `${app.config.root + app.config.api_prefix}/stream/${submit_map.get(dep_index).event_id()}`,
335
+ { ...payload, session_hash: app.session_hash },
336
+ );
337
+ return;
338
+ }
339
+ }
280
340
  try {
281
341
  submission = app.submit(
282
342
  payload.fn_index,
283
343
  payload.data as unknown[],
284
344
  payload.event_data,
285
- payload.trigger_id
345
+ payload.trigger_id,
286
346
  );
287
347
  } catch (e) {
288
348
  const fn_index = 0; // Mock value for fn_index
@@ -292,7 +352,7 @@
292
352
  fn_index,
293
353
  eta: 0,
294
354
  queue: false,
295
- queue_position: null
355
+ queue_position: null,
296
356
  });
297
357
  set_status($loading_status);
298
358
  return;
@@ -316,7 +376,7 @@
316
376
  const { data, fn_index } = message;
317
377
  if (dep.pending_request && dep.final_event) {
318
378
  dep.pending_request = false;
319
- make_prediction(dep.final_event);
379
+ make_prediction(dep.final_event, dep.connection == "stream");
320
380
  }
321
381
  dep.pending_request = false;
322
382
  handle_update(data, fn_index);
@@ -348,7 +408,7 @@
348
408
  layout: render_layout,
349
409
  root: root,
350
410
  dependencies: dependencies,
351
- render_id: render_id
411
+ render_id: render_id,
352
412
  });
353
413
  }
354
414
 
@@ -356,18 +416,41 @@
356
416
  const { log, fn_index, level, duration, visible } = msg;
357
417
  messages = [
358
418
  new_message(log, fn_index, level, duration, visible),
359
- ...messages
419
+ ...messages,
360
420
  ];
361
421
  }
362
422
 
423
+ function open_stream_events(
424
+ status: StatusMessage,
425
+ id: number,
426
+ dep: Dependency,
427
+ ): void {
428
+ if (
429
+ status.original_msg === "process_starts" &&
430
+ dep.connection === "stream"
431
+ ) {
432
+ modify_stream(id, "open");
433
+ }
434
+ }
435
+
436
+ /* eslint-disable complexity */
363
437
  function handle_status_update(message: StatusMessage): void {
364
438
  const { fn_index, ...status } = message;
439
+ if (status.stage === "streaming" && status.time_limit) {
440
+ dep.inputs.forEach((id) => {
441
+ set_time_limit(id, status.time_limit);
442
+ });
443
+ }
444
+ dep.inputs.forEach((id) => {
445
+ open_stream_events(message, id, dep);
446
+ });
365
447
  //@ts-ignore
366
448
  loading_status.update({
367
449
  ...status,
450
+ time_limit: status.time_limit,
368
451
  status: status.stage,
369
452
  progress: status.progress_data,
370
- fn_index
453
+ fn_index,
371
454
  });
372
455
  set_status($loading_status);
373
456
  if (
@@ -381,7 +464,7 @@
381
464
  showed_duplicate_message = true;
382
465
  messages = [
383
466
  new_message(DUPLICATE_MESSAGE, fn_index, "warning"),
384
- ...messages
467
+ ...messages,
385
468
  ];
386
469
  }
387
470
  if (
@@ -393,11 +476,11 @@
393
476
  showed_mobile_warning = true;
394
477
  messages = [
395
478
  new_message(MOBILE_QUEUE_WARNING, fn_index, "warning"),
396
- ...messages
479
+ ...messages,
397
480
  ];
398
481
  }
399
482
 
400
- if (status.stage === "complete") {
483
+ if (status.stage === "complete" || status.stage === "generating") {
401
484
  status.changed_state_ids?.forEach((id) => {
402
485
  dependencies
403
486
  .filter((dep) => dep.targets.some(([_id, _]) => _id === id))
@@ -405,19 +488,23 @@
405
488
  wait_then_trigger_api_call(dep.id, payload.trigger_id);
406
489
  });
407
490
  });
491
+ }
492
+ if (status.stage === "complete") {
408
493
  dependencies.forEach(async (dep) => {
409
494
  if (dep.trigger_after === fn_index) {
410
495
  wait_then_trigger_api_call(dep.id, payload.trigger_id);
411
496
  }
412
497
  });
413
-
414
- // submission.destroy();
498
+ dep.inputs.forEach((id) => {
499
+ modify_stream(id, "closed");
500
+ });
501
+ submit_map.delete(dep_index);
415
502
  }
416
503
  if (status.broken && is_mobile_device && user_left_page) {
417
504
  window.setTimeout(() => {
418
505
  messages = [
419
506
  new_message(MOBILE_RECONNECT_MESSAGE, fn_index, "error"),
420
- ...messages
507
+ ...messages,
421
508
  ];
422
509
  }, 0);
423
510
  wait_then_trigger_api_call(dep.id, payload.trigger_id, event_data);
@@ -426,7 +513,7 @@
426
513
  if (status.message) {
427
514
  const _message = status.message.replace(
428
515
  MESSAGE_QUOTE_RE,
429
- (_, b) => b
516
+ (_, b) => b,
430
517
  );
431
518
  messages = [
432
519
  new_message(
@@ -434,9 +521,9 @@
434
521
  fn_index,
435
522
  "error",
436
523
  status.duration,
437
- status.visible
524
+ status.visible,
438
525
  ),
439
- ...messages
526
+ ...messages,
440
527
  ];
441
528
  }
442
529
  dependencies.map(async (dep) => {
@@ -451,13 +538,14 @@
451
538
  }
452
539
  }
453
540
  }
541
+ /* eslint-enable complexity */
454
542
 
455
543
  function trigger_share(title: string | undefined, description: string): void {
456
544
  if (space_id === null) {
457
545
  return;
458
546
  }
459
547
  const discussion_url = new URL(
460
- `https://huggingface.co/spaces/${space_id}/discussions/new`
548
+ `https://huggingface.co/spaces/${space_id}/discussions/new`,
461
549
  );
462
550
  if (title !== undefined && title.length > 0) {
463
551
  discussion_url.searchParams.set("title", title);
@@ -478,7 +566,7 @@
478
566
  if (js) {
479
567
  let blocks_frontend_fn = new AsyncFunction(
480
568
  `let result = await (${js})();
481
- return (!Array.isArray(result)) ? [result] : result;`
569
+ return (!Array.isArray(result)) ? [result] : result;`,
482
570
  );
483
571
  await blocks_frontend_fn();
484
572
  }
@@ -503,12 +591,18 @@
503
591
  }
504
592
  });
505
593
 
506
- if (render_complete) return;
594
+ if (!target || render_complete) return;
507
595
 
508
596
  target.addEventListener("prop_change", (e: Event) => {
509
597
  if (!isCustomEvent(e)) throw new Error("not a custom event");
510
598
  const { id, prop, value } = e.detail;
511
599
  update_value([{ id, prop, value }]);
600
+ if (prop === "input_ready" && value === false) {
601
+ inputs_waiting.push(id);
602
+ }
603
+ if (prop === "input_ready" && value === true) {
604
+ inputs_waiting = inputs_waiting.filter((item) => item !== id);
605
+ }
512
606
  });
513
607
  target.addEventListener("gradio", (e: Event) => {
514
608
  if (!isCustomEvent(e)) throw new Error("not a custom event");
@@ -522,6 +616,17 @@
522
616
  messages = [new_message(data, -1, event), ...messages];
523
617
  } else if (event == "clear_status") {
524
618
  update_status(id, "complete", data);
619
+ } else if (event == "close_stream") {
620
+ const deps = $targets[id]?.[data];
621
+ deps?.forEach((dep_id) => {
622
+ if (submit_map.has(dep_id)) {
623
+ app.post_data(
624
+ // @ts-ignore
625
+ `${app.config.root + app.config.api_prefix}/stream/${submit_map.get(dep_id).event_id()}/close`,
626
+ {},
627
+ );
628
+ }
629
+ });
525
630
  } else {
526
631
  const deps = $targets[id]?.[event];
527
632
 
@@ -541,15 +646,15 @@
541
646
  function update_status(
542
647
  id: number,
543
648
  status: "error" | "complete" | "pending",
544
- data: LoadingStatus
649
+ data: LoadingStatus,
545
650
  ): void {
546
651
  data.status = status;
547
652
  update_value([
548
653
  {
549
654
  id,
550
655
  prop: "loading_status",
551
- value: data
552
- }
656
+ value: data,
657
+ },
553
658
  ]);
554
659
  }
555
660
 
@@ -561,7 +666,7 @@
561
666
  }[] = [];
562
667
  Object.entries(statuses).forEach(([id, loading_status]) => {
563
668
  let dependency = dependencies.find(
564
- (dep) => dep.id == loading_status.fn_index
669
+ (dep) => dep.id == loading_status.fn_index,
565
670
  );
566
671
  if (dependency === undefined) {
567
672
  return;
@@ -571,7 +676,7 @@
571
676
  updates.push({
572
677
  id: parseInt(id),
573
678
  prop: "loading_status",
574
- value: loading_status
679
+ value: loading_status,
575
680
  });
576
681
  });
577
682
 
@@ -581,9 +686,9 @@
581
686
  return {
582
687
  id,
583
688
  prop: "pending",
584
- value: pending_status === "pending"
689
+ value: pending_status === "pending",
585
690
  };
586
- }
691
+ },
587
692
  );
588
693
 
589
694
  update_value([...updates, ...additional_updates]);
@@ -592,6 +697,19 @@
592
697
  function isCustomEvent(event: Event): event is CustomEvent {
593
698
  return "detail" in event;
594
699
  }
700
+
701
+ onMount(() => {
702
+ document.addEventListener("visibilitychange", function () {
703
+ if (document.visibilityState === "hidden") {
704
+ user_left_page = true;
705
+ }
706
+ });
707
+
708
+ is_mobile_device =
709
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
710
+ navigator.userAgent,
711
+ );
712
+ });
595
713
  </script>
596
714
 
597
715
  <svelte:head>
@@ -602,19 +720,19 @@
602
720
 
603
721
  <div class="wrap" style:min-height={app_mode ? "100%" : "auto"}>
604
722
  <div class="contain" style:flex-grow={app_mode ? "1" : "auto"}>
605
- {#if $_layout && app.config}
606
- <MountComponents
607
- rootNode={$_layout}
608
- {root}
609
- {target}
610
- {theme_mode}
611
- on:mount={handle_mount}
612
- {version}
613
- {autoscroll}
614
- max_file_size={app.config.max_file_size}
615
- client={app}
616
- />
617
- {/if}
723
+ <!-- {#if $_layout} -->
724
+ <MountComponents
725
+ rootNode={$_layout}
726
+ {root}
727
+ {target}
728
+ {theme_mode}
729
+ on:mount={handle_mount}
730
+ {version}
731
+ {autoscroll}
732
+ {max_file_size}
733
+ client={app}
734
+ />
735
+ <!-- {/if} -->
618
736
  </div>
619
737
 
620
738
  {#if show_footer}
@@ -18,13 +18,15 @@
18
18
  });
19
19
  </script>
20
20
 
21
- <Render
22
- node={rootNode}
23
- {root}
24
- {target}
25
- {theme_mode}
26
- {version}
27
- {autoscroll}
28
- {max_file_size}
29
- {client}
30
- />
21
+ {#if rootNode}
22
+ <Render
23
+ node={rootNode}
24
+ {root}
25
+ {target}
26
+ {theme_mode}
27
+ {version}
28
+ {autoscroll}
29
+ {max_file_size}
30
+ {client}
31
+ />
32
+ {/if}
package/src/Render.svelte CHANGED
@@ -36,20 +36,24 @@
36
36
  };
37
37
  });
38
38
 
39
- $: node.children =
40
- node.children &&
41
- node.children.filter((v) => {
42
- const valid_node = node.type !== "statustracker";
43
- if (!valid_node) {
44
- filtered_children.push(v);
45
- }
46
- return valid_node;
47
- });
39
+ $: {
40
+ if (node) {
41
+ node.children =
42
+ node.children &&
43
+ node.children.filter((v) => {
44
+ const valid_node = node.type !== "statustracker";
45
+ if (!valid_node) {
46
+ filtered_children.push(v);
47
+ }
48
+ return valid_node;
49
+ });
50
+ }
51
+ }
48
52
 
49
53
  setContext("BLOCK_KEY", parent);
50
54
 
51
55
  $: {
52
- if (node.type === "form") {
56
+ if (node && node.type === "form") {
53
57
  if (node.children?.every((c) => !c.props.visible)) {
54
58
  node.props.visible = false;
55
59
  } else {
@@ -58,7 +62,7 @@
58
62
  }
59
63
  }
60
64
 
61
- $: gradio_class = new Gradio<Record<string, any>>(
65
+ $: node.props.gradio = new Gradio<Record<string, any>>(
62
66
  node.id,
63
67
  target,
64
68
  theme_mode,
@@ -73,7 +77,7 @@
73
77
  </script>
74
78
 
75
79
  <RenderComponent
76
- _id={node.id}
80
+ _id={node?.id}
77
81
  component={node.component}
78
82
  bind:instance={node.instance}
79
83
  bind:value={node.props.value}
@@ -84,7 +88,6 @@
84
88
  {...node.props}
85
89
  {theme_mode}
86
90
  {root}
87
- gradio={gradio_class}
88
91
  >
89
92
  {#if node.children && node.children.length}
90
93
  {#each node.children as _node (_node.id)}