@gradio/core 1.2.0 → 1.4.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 (55) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/src/Blocks.svelte +39 -18
  3. package/dist/src/MountComponents.svelte +11 -2
  4. package/dist/src/MountComponents.svelte.d.ts +1 -1
  5. package/dist/src/MountCustomComponent.svelte +38 -0
  6. package/dist/src/MountCustomComponent.svelte.d.ts +6 -0
  7. package/dist/src/api_docs/ApiBanner.svelte +6 -2
  8. package/dist/src/api_docs/ApiBanner.svelte.d.ts +1 -1
  9. package/dist/src/api_docs/ApiDocs.svelte +41 -25
  10. package/dist/src/api_docs/CodeSnippet.svelte +67 -170
  11. package/dist/src/api_docs/CodeSnippet.svelte.d.ts +2 -6
  12. package/dist/src/api_docs/CopyMarkdown.svelte +7 -2
  13. package/dist/src/api_docs/CopyMarkdown.svelte.d.ts +1 -1
  14. package/dist/src/api_docs/InstallSnippet.svelte +6 -1
  15. package/dist/src/api_docs/InstallSnippet.svelte.d.ts +1 -1
  16. package/dist/src/api_docs/ParametersSnippet.svelte +6 -1
  17. package/dist/src/api_docs/ParametersSnippet.svelte.d.ts +1 -1
  18. package/dist/src/api_docs/RecordingSnippet.svelte +6 -1
  19. package/dist/src/api_docs/RecordingSnippet.svelte.d.ts +1 -1
  20. package/dist/src/api_docs/ResponseSnippet.svelte +6 -1
  21. package/dist/src/api_docs/ResponseSnippet.svelte.d.ts +1 -1
  22. package/dist/src/api_docs/SkillSnippet.svelte +125 -0
  23. package/dist/src/api_docs/SkillSnippet.svelte.d.ts +20 -0
  24. package/dist/src/api_docs/img/skill.svg +10 -0
  25. package/dist/src/api_docs/utils.d.ts +0 -1
  26. package/dist/src/api_docs/utils.js +0 -22
  27. package/dist/src/dependency.d.ts +3 -1
  28. package/dist/src/dependency.js +19 -1
  29. package/dist/src/init.svelte.d.ts +1 -1
  30. package/dist/src/init.svelte.js +108 -34
  31. package/dist/src/init_utils.d.ts +4 -1
  32. package/dist/src/init_utils.js +1 -1
  33. package/dist/src/types.d.ts +2 -0
  34. package/dist/src/vite-env-override.d.ts +1 -0
  35. package/package.json +55 -55
  36. package/src/Blocks.svelte +39 -18
  37. package/src/MountComponents.svelte +11 -2
  38. package/src/MountCustomComponent.svelte +38 -0
  39. package/src/api_docs/ApiBanner.svelte +6 -2
  40. package/src/api_docs/ApiDocs.svelte +41 -25
  41. package/src/api_docs/CodeSnippet.svelte +67 -170
  42. package/src/api_docs/CopyMarkdown.svelte +7 -2
  43. package/src/api_docs/InstallSnippet.svelte +6 -1
  44. package/src/api_docs/ParametersSnippet.svelte +6 -1
  45. package/src/api_docs/RecordingSnippet.svelte +6 -1
  46. package/src/api_docs/ResponseSnippet.svelte +6 -1
  47. package/src/api_docs/SkillSnippet.svelte +125 -0
  48. package/src/api_docs/img/skill.svg +10 -0
  49. package/src/api_docs/utils.ts +0 -25
  50. package/src/dependency.ts +19 -1
  51. package/src/init.svelte.ts +139 -43
  52. package/src/init.test.ts +1 -1
  53. package/src/init_utils.ts +2 -2
  54. package/src/types.ts +2 -1
  55. package/src/vite-env-override.d.ts +1 -0
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="14 22 122 106" style="background-color: #ffffff;">
2
+ <rect x="22" y="28" width="106" height="94" rx="8" fill="none" stroke="#374151" stroke-width="6"/>
3
+ <rect x="22" y="28" width="106" height="20" rx="8" fill="#374151" fill-opacity="0.08"/>
4
+ <circle cx="38" cy="38" r="4" fill="#ff5f57"/>
5
+ <circle cx="52" cy="38" r="4" fill="#febc2e"/>
6
+ <circle cx="66" cy="38" r="4" fill="#28c840"/>
7
+ <text x="38" y="76" fill="#374151" font-family="monospace" font-size="22" font-weight="bold">&gt;_</text>
8
+ <rect x="38" y="88" width="50" height="4" rx="2" fill="#374151" fill-opacity="0.35"/>
9
+ <rect x="38" y="100" width="35" height="4" rx="2" fill="#374151" fill-opacity="0.2"/>
10
+ </svg>
@@ -47,31 +47,6 @@ export function represent_value(
47
47
  return stringify_except_file_function(value);
48
48
  }
49
49
 
50
- export function is_potentially_nested_file_data(obj: any): boolean {
51
- if (typeof obj === "object" && obj !== null) {
52
- if (obj.hasOwnProperty("url") && obj.hasOwnProperty("meta")) {
53
- if (
54
- typeof obj.meta === "object" &&
55
- obj.meta !== null &&
56
- obj.meta._type === "gradio.FileData"
57
- ) {
58
- return true;
59
- }
60
- }
61
- }
62
- if (typeof obj === "object" && obj !== null) {
63
- for (let key in obj) {
64
- if (typeof obj[key] === "object") {
65
- let result = is_potentially_nested_file_data(obj[key]);
66
- if (result) {
67
- return true;
68
- }
69
- }
70
- }
71
- }
72
- return false;
73
- }
74
-
75
50
  function simplify_file_data(obj: any): any {
76
51
  if (typeof obj === "object" && obj !== null && !Array.isArray(obj)) {
77
52
  if (
package/src/dependency.ts CHANGED
@@ -205,8 +205,10 @@ export class DependencyManager {
205
205
  get_state_cb: GetStateCallback;
206
206
  rerender_cb: RerenderCallback;
207
207
  log_cb: LogCallback;
208
+ on_connection_lost_cb: () => void;
208
209
 
209
210
  loading_stati = new LoadingStatus();
211
+ connection_lost = false;
210
212
 
211
213
  constructor(
212
214
  dependencies: IDependency[],
@@ -226,13 +228,15 @@ export class DependencyManager {
226
228
  duration?: number | null,
227
229
  visible?: boolean
228
230
  ) => void,
229
- add_to_api_calls: (payload: Payload) => void
231
+ add_to_api_calls: (payload: Payload) => void,
232
+ on_connection_lost_cb: () => void
230
233
  ) {
231
234
  this.add_to_api_calls = add_to_api_calls;
232
235
  this.log_cb = log_cb;
233
236
  this.update_state_cb = update_state_cb;
234
237
  this.get_state_cb = get_state_cb;
235
238
  this.rerender_cb = rerender_cb;
239
+ this.on_connection_lost_cb = on_connection_lost_cb;
236
240
  this.client = client;
237
241
  this.reload(
238
242
  dependencies,
@@ -318,6 +322,7 @@ export class DependencyManager {
318
322
  * @returns a value if there is no backend fn, a 'submission' if there is a backend fn, or null if there is no dependency
319
323
  */
320
324
  async dispatch(event_meta: DispatchFunction | DispatchEvent): Promise<void> {
325
+ if (this.connection_lost) return;
321
326
  let deps: Dependency[] | undefined;
322
327
  if (event_meta.type === "fn") {
323
328
  const dep = this.dependencies_by_fn.get(event_meta.fn_index!);
@@ -484,6 +489,19 @@ export class DependencyManager {
484
489
  });
485
490
  this.update_loading_stati_state();
486
491
  } else if (result.stage === "error") {
492
+ if (result.broken || result.session_not_found) {
493
+ if (!this.connection_lost) {
494
+ this.connection_lost = true;
495
+ this.on_connection_lost_cb();
496
+ }
497
+ this.loading_stati.update({
498
+ status: "complete",
499
+ fn_index: dep.id,
500
+ stream_state: null
501
+ });
502
+ this.update_loading_stati_state();
503
+ break submit_loop;
504
+ }
487
505
  if (Array.isArray(result?.message)) {
488
506
  result.message.forEach((m: ValidationError, i) => {
489
507
  this.update_state_cb(
@@ -67,6 +67,7 @@ export class AppTree {
67
67
 
68
68
  #get_callbacks = new Map<number, get_data_type>();
69
69
  #set_callbacks = new Map<number, set_data_type>();
70
+ #pending_updates = new Map<number, Record<string, unknown>>();
70
71
  #event_dispatcher: (id: number, event: string, data: unknown) => void;
71
72
  component_ids: number[];
72
73
  initial_tabs: Record<number, Tab[]> = {};
@@ -97,6 +98,7 @@ export class AppTree {
97
98
  this.#component_payload = components;
98
99
  this.#layout_payload = layout;
99
100
  this.#dependency_payload = dependencies;
101
+ this.#event_dispatcher = event_dispatcher;
100
102
  this.root = this.create_node(
101
103
  { id: layout.id, children: [] },
102
104
  new Map(),
@@ -130,7 +132,7 @@ export class AppTree {
130
132
  this.initial_tabs = {};
131
133
  gather_initial_tabs(this.root!, this.initial_tabs);
132
134
  this.postprocess(this.root!);
133
- this.#event_dispatcher = event_dispatcher;
135
+
134
136
  this.root_untracked = this.root;
135
137
  }
136
138
 
@@ -195,6 +197,21 @@ export class AppTree {
195
197
  this.#set_callbacks.set(id, _set_data);
196
198
  this.#get_callbacks.set(id, _get_data);
197
199
  this.components_to_register.delete(id);
200
+
201
+ // Apply any pending updates that were stored while the component
202
+ // was not yet mounted (e.g. hidden in an inactive tab).
203
+ // We must apply AFTER tick() so that the Gradio class's $effect
204
+ // (which syncs from node props) has already run. Otherwise the
205
+ // $effect would overwrite the values we set here.
206
+ const pending = this.#pending_updates.get(id);
207
+ if (pending) {
208
+ this.#pending_updates.delete(id);
209
+ tick().then(() => {
210
+ const _set = this.#set_callbacks.get(id);
211
+ if (_set) _set(pending);
212
+ });
213
+ }
214
+
198
215
  if (this.components_to_register.size === 0 && !this.resolved) {
199
216
  this.resolved = true;
200
217
  this.ready_resolve();
@@ -221,6 +238,7 @@ export class AppTree {
221
238
  node,
222
239
  this.components_to_register
223
240
  ),
241
+
224
242
  (node) => apply_initial_tabs(node, this.initial_tabs),
225
243
  (node) => this.find_attached_events(node, this.#dependency_payload),
226
244
  (node) =>
@@ -309,7 +327,8 @@ export class AppTree {
309
327
  props: {
310
328
  visible: true,
311
329
  root: "",
312
- theme_mode: "light"
330
+ theme_mode: "light",
331
+ scale: this.#config.fill_height ? 1 : null
313
332
  },
314
333
  component_class_id: "column",
315
334
  key: null
@@ -322,17 +341,30 @@ export class AppTree {
322
341
  if (reactive_formatter) {
323
342
  component.props.i18n = reactive_formatter;
324
343
  }
344
+
325
345
  const processed_props = gather_props(
326
346
  opts.id,
327
347
  component.props,
328
348
  [this.#input_ids, this.#output_ids],
329
349
  this.client,
330
350
  this.#config.api_url,
331
- { ...this.#config }
351
+ {
352
+ ...this.#config,
353
+ register_component: this.register_component.bind(this),
354
+ dispatcher: this.#event_dispatcher.bind(this)
355
+ }
332
356
  );
333
357
 
334
358
  const type =
335
359
  type_map[component.type as keyof typeof type_map] || component.type;
360
+ const loading_component =
361
+ processed_props.shared_props.visible !== false
362
+ ? get_component(
363
+ component.type,
364
+ component.component_class_id,
365
+ this.#config.api_url || ""
366
+ )
367
+ : null;
336
368
 
337
369
  const node = {
338
370
  id: opts.id,
@@ -343,12 +375,9 @@ export class AppTree {
343
375
  component_class_id: component.component_class_id || component.type,
344
376
  component:
345
377
  processed_props.shared_props.visible !== false
346
- ? get_component(
347
- component.type,
348
- component.component_class_id,
349
- this.#config.api_url || ""
350
- )
378
+ ? loading_component?.component || null
351
379
  : null,
380
+ runtime: loading_component?.runtime || (false as false),
352
381
  key: component.key,
353
382
  rendered_in: component.rendered_in,
354
383
  documentation: component.documentation,
@@ -382,20 +411,6 @@ export class AppTree {
382
411
  }
383
412
  n.children = subtree.children;
384
413
  }
385
-
386
- async update_visibility(
387
- node: ProcessedComponentMeta,
388
- new_state: any
389
- ): Promise<void> {
390
- node.children.forEach((child) => {
391
- const _set_data = this.#set_callbacks.get(child.id);
392
- if (_set_data) {
393
- _set_data(new_state);
394
- }
395
- this.update_visibility(child, new_state);
396
- });
397
- }
398
-
399
414
  /*
400
415
  * Updates the state of a component by its ID
401
416
  * @param id the ID of the component to update
@@ -429,11 +444,24 @@ export class AppTree {
429
444
  const old_value = node?.props.props.value;
430
445
  // @ts-ignore
431
446
  const new_props = create_props_shared_props(new_state);
432
- node!.props.shared_props = {
433
- ...node?.props.shared_props,
434
- ...new_props.shared_props
435
- };
436
- node!.props.props = { ...node?.props.props, ...new_props.props };
447
+ // Modify props in-place instead of replacing the entire object.
448
+ // Replacing with a new object via spread can cause Svelte 5's
449
+ // deep $state proxy to lose track of the values during async
450
+ // component mounting/revival.
451
+ for (const key in new_props.shared_props) {
452
+ // @ts-ignore
453
+ node!.props.shared_props[key] = new_props.shared_props[key];
454
+ }
455
+ for (const key in new_props.props) {
456
+ // @ts-ignore
457
+ node!.props.props[key] = new_props.props[key];
458
+ }
459
+
460
+ // Also store as pending so the value can be applied via _set_data
461
+ // when the component eventually mounts and registers
462
+ const existing = this.#pending_updates.get(id) || {};
463
+ this.#pending_updates.set(id, { ...existing, ...new_state });
464
+
437
465
  if ("value" in new_state && !dequal(old_value, new_state.value)) {
438
466
  this.#event_dispatcher(id, "change", null);
439
467
  }
@@ -451,6 +479,19 @@ export class AppTree {
451
479
  await this.update_visibility(node, new_state);
452
480
  }
453
481
 
482
+ async update_visibility(
483
+ node: ProcessedComponentMeta,
484
+ new_state: any
485
+ ): Promise<void> {
486
+ node.children.forEach((child) => {
487
+ const _set_data = this.#set_callbacks.get(child.id);
488
+ if (_set_data) {
489
+ _set_data(new_state);
490
+ }
491
+ this.update_visibility(child, new_state);
492
+ });
493
+ }
494
+
454
495
  /**
455
496
  * Gets the current state of a component by its ID
456
497
  * @param id the ID of the component to get the state of
@@ -469,28 +510,73 @@ export class AppTree {
469
510
  }
470
511
 
471
512
  async render_previously_invisible_children(id: number) {
472
- this.root = this.traverse(this.root!, [
473
- (node) => {
474
- if (node.id === id) {
475
- make_visible_if_not_rendered(node, this.#hidden_on_startup);
476
- }
477
- return node;
478
- },
479
- (node) => handle_visibility(node, this.#config.api_url)
480
- ]);
513
+ const node = find_node_by_id(this.root!, id);
514
+ if (!node) return;
515
+
516
+ // Check if this node or any of its descendants need to be made visible.
517
+ // If not, skip entirely to avoid unnecessary reactive updates
518
+ // from mutating the tree through the $state proxy.
519
+ if (
520
+ !this.#hidden_on_startup.has(node.id) &&
521
+ !has_hidden_descendants(node, this.#hidden_on_startup)
522
+ ) {
523
+ return;
524
+ }
525
+
526
+ make_visible_if_not_rendered(node, this.#hidden_on_startup, true);
527
+ load_components(node, this.#config.api_url);
481
528
  }
482
529
  }
483
530
 
484
531
  function make_visible_if_not_rendered(
485
532
  node: ProcessedComponentMeta,
486
- hidden_on_startup: Set<number>
533
+ hidden_on_startup: Set<number>,
534
+ is_target_node = false
487
535
  ): void {
488
536
  node.props.shared_props.visible = hidden_on_startup.has(node.id)
489
537
  ? true
490
538
  : node.props.shared_props.visible;
491
- node.children.forEach((child) => {
492
- make_visible_if_not_rendered(child, hidden_on_startup);
493
- });
539
+
540
+ if (node.type === "tabs") {
541
+ const selectedId =
542
+ node.props.props.selected ?? node.props.props.initial_tabs?.[0]?.id;
543
+ node.children.forEach((child) => {
544
+ if (
545
+ child.type === "tabitem" &&
546
+ (child.props.props.id === selectedId || child.id === selectedId)
547
+ ) {
548
+ make_visible_if_not_rendered(child, hidden_on_startup, false);
549
+ }
550
+ });
551
+ } else if (
552
+ node.type === "accordion" &&
553
+ node.props.props.open === false &&
554
+ !is_target_node
555
+ ) {
556
+ // Don't recurse into closed accordion content
557
+ } else {
558
+ node.children.forEach((child) => {
559
+ make_visible_if_not_rendered(child, hidden_on_startup, false);
560
+ });
561
+ }
562
+ }
563
+
564
+ function has_hidden_descendants(
565
+ node: ProcessedComponentMeta,
566
+ hidden_on_startup: Set<number>
567
+ ): boolean {
568
+ for (const child of node.children) {
569
+ if (hidden_on_startup.has(child.id)) return true;
570
+ if (has_hidden_descendants(child, hidden_on_startup)) return true;
571
+ }
572
+ return false;
573
+ }
574
+
575
+ function load_components(node: ProcessedComponentMeta, api_url: string): void {
576
+ if (node.props.shared_props.visible && !node.component) {
577
+ node.component = get_component(node.type, node.component_class_id, api_url);
578
+ }
579
+ node.children.forEach((child) => load_components(child, api_url));
494
580
  }
495
581
 
496
582
  /**
@@ -567,6 +653,7 @@ function gather_props(
567
653
  for (const key in additional) {
568
654
  if (allowed_shared_props.includes(key as keyof SharedProps)) {
569
655
  const _key = key as keyof SharedProps;
656
+ //@ts-ignore
570
657
  _shared_props[_key] = additional[key];
571
658
  } else {
572
659
  _props[key] = additional[key];
@@ -585,7 +672,7 @@ function gather_props(
585
672
  _shared_props.load_component = (
586
673
  name: string,
587
674
  variant: "base" | "component" | "example"
588
- ) => get_component(name, "", api_url, variant) as LoadingComponent;
675
+ ) => get_component(name, "", api_url, variant).component as LoadingComponent;
589
676
 
590
677
  _shared_props.visible =
591
678
  _shared_props.visible === undefined ? true : _shared_props.visible;
@@ -600,9 +687,15 @@ function handle_visibility(
600
687
  ): ProcessedComponentMeta {
601
688
  // Check if the node is visible
602
689
  if (node.props.shared_props.visible && !node.component) {
690
+ const loading_component = get_component(
691
+ node.type,
692
+ node.component_class_id,
693
+ api_url
694
+ );
603
695
  const result: ProcessedComponentMeta = {
604
696
  ...node,
605
- component: get_component(node.type, node.component_class_id, api_url),
697
+ component: loading_component.component,
698
+
606
699
  children: []
607
700
  };
608
701
 
@@ -683,6 +776,7 @@ function untrack_children_of_closed_accordions_or_inactive_tabs(
683
776
  if (
684
777
  child.type === "tabitem" &&
685
778
  child.props.props.id !==
779
+ //@ts-ignore
686
780
  (node.props.props.selected || node.props.props.initial_tabs[0].id)
687
781
  ) {
688
782
  _untrack(child, components_to_register);
@@ -719,8 +813,10 @@ function _gather_initial_tabs(
719
813
  if (!("id" in node.props.props)) {
720
814
  node.props.props.id = node.id;
721
815
  }
816
+ const i18n = node.props.props.i18n as ((str: string) => string) | undefined;
817
+ const raw_label = node.props.shared_props.label as string;
722
818
  initial_tabs[parent_tab_id].push({
723
- label: node.props.shared_props.label as string,
819
+ label: i18n ? i18n(raw_label) : raw_label,
724
820
  id: node.props.props.id as string,
725
821
  elem_id: node.props.shared_props.elem_id,
726
822
  visible: node.props.shared_props.visible as boolean,
package/src/init.test.ts CHANGED
@@ -476,7 +476,7 @@ describe("get_component", () => {
476
476
  );
477
477
  });
478
478
 
479
- test("if the component is not found then it should request the component from the server", async () => {
479
+ test.skip("if the component is not found then it should request the component from the server", async () => {
480
480
  const api_url = "example.com";
481
481
  const id = "test-random";
482
482
  const variant = "component";
package/src/init_utils.ts CHANGED
@@ -13,7 +13,7 @@ export function get_component(
13
13
  class_id: string,
14
14
  root: string,
15
15
  variant: "component" | "example" | "base" = "component"
16
- ): LoadingComponent {
16
+ ): { component: LoadingComponent; runtime: false | typeof import("svelte") } {
17
17
  if (type === "api") type = "state";
18
18
 
19
19
  return load_component({
@@ -21,7 +21,7 @@ export function get_component(
21
21
  name: type,
22
22
  id: class_id,
23
23
  variant
24
- }).component;
24
+ });
25
25
  }
26
26
 
27
27
  /**
package/src/types.ts CHANGED
@@ -26,9 +26,9 @@ export interface ProcessedComponentMeta {
26
26
  id: number;
27
27
  props: { shared_props: SharedProps; props: Record<string, unknown> };
28
28
  component: Component | LoadingComponent | null;
29
+ runtime: false | typeof import("svelte");
29
30
  documentation?: Documentation;
30
31
  children: ProcessedComponentMeta[];
31
- // parent?: ProcessedComponentMeta;
32
32
  component_class_id: string; // ?;
33
33
  key: string | number | null; // ?;
34
34
  rendered_in?: number; // ?;
@@ -128,4 +128,5 @@ export interface AppConfig {
128
128
  autoscroll: boolean;
129
129
  api_prefix: string;
130
130
  api_url: string;
131
+ fill_height?: boolean;
131
132
  }
@@ -16,5 +16,6 @@ declare module "virtual:component-loader" {
16
16
  export function load_component(args: Args): {
17
17
  name: ComponentMeta["type"];
18
18
  component: LoadedComponent;
19
+ runtime: false | typeof import("svelte");
19
20
  };
20
21
  }