@gradio/core 1.0.0 → 1.0.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.
@@ -1,6 +1,8 @@
1
1
  import { determine_interactivity, get_component, get_inputs_outputs } from "./init_utils";
2
2
  import { translate_if_needed } from "./i18n";
3
3
  import { tick } from "svelte";
4
+ import { dequal } from "dequal";
5
+ import {} from "@gradio/utils";
4
6
  import { allowed_shared_props } from "@gradio/utils";
5
7
  import { Client } from "@gradio/client";
6
8
  const type_map = {
@@ -21,6 +23,7 @@ export class AppTree {
21
23
  client;
22
24
  /** the root node of the processed layout tree */
23
25
  root = $state();
26
+ root_untracked;
24
27
  /** a set of all component IDs that are inputs to dependencies */
25
28
  #input_ids = new Set();
26
29
  /** a set of all component IDs that are outputs of dependencies */
@@ -29,18 +32,23 @@ export class AppTree {
29
32
  #pending_components = [];
30
33
  #get_callbacks = new Map();
31
34
  #set_callbacks = new Map();
35
+ #event_dispatcher;
32
36
  component_ids;
33
37
  initial_tabs = {};
34
38
  components_to_register = new Set();
35
39
  ready;
36
40
  ready_resolve;
37
41
  resolved = false;
38
- constructor(components, layout, dependencies, config, app, reactive_formatter) {
42
+ #hidden_on_startup = new Set();
43
+ constructor(components, layout, dependencies, config, app, reactive_formatter, event_dispatcher) {
39
44
  this.ready = new Promise((resolve) => {
40
45
  this.ready_resolve = resolve;
41
46
  });
42
47
  this.reactive_formatter = reactive_formatter;
43
- this.#config = config;
48
+ this.#config = {
49
+ ...config,
50
+ api_url: new URL(config.api_prefix, config.root).toString()
51
+ };
44
52
  this.#component_payload = components;
45
53
  this.#layout_payload = layout;
46
54
  this.#dependency_payload = dependencies;
@@ -63,11 +71,16 @@ export class AppTree {
63
71
  this.initial_tabs = {};
64
72
  gather_initial_tabs(this.root, this.initial_tabs);
65
73
  this.postprocess(this.root);
74
+ this.#event_dispatcher = event_dispatcher;
75
+ this.root_untracked = this.root;
66
76
  }
67
77
  reload(components, layout, dependencies, config) {
68
78
  this.#layout_payload = layout;
69
79
  this.#component_payload = components;
70
- this.#config = config;
80
+ this.#config = {
81
+ ...config,
82
+ api_url: new URL(config.api_prefix, config.root).toString()
83
+ };
71
84
  this.#dependency_payload = dependencies;
72
85
  this.root = this.create_node({ id: layout.id, children: [] }, new Map(), true);
73
86
  for (const comp of components) {
@@ -115,12 +128,13 @@ export class AppTree {
115
128
  process() { }
116
129
  postprocess(tree) {
117
130
  this.root = this.traverse(tree, [
118
- (node) => handle_visibility(node, this.#config.root),
119
- (node) => untrack_children_of_invisible_parents(node, this.#config.root, this.components_to_register),
120
- (node) => handle_empty_forms(node, this.#config.root, this.components_to_register),
121
- (node) => translate_props(node, this.#config.root),
122
- (node) => apply_initial_tabs(node, this.#config.root, this.initial_tabs),
123
- (node) => this.find_attached_events(node, this.#dependency_payload)
131
+ (node) => handle_visibility(node, this.#config.api_url),
132
+ (node) => untrack_children_of_invisible_parents(node, this.components_to_register),
133
+ (node) => handle_empty_forms(node, this.components_to_register),
134
+ (node) => translate_props(node),
135
+ (node) => apply_initial_tabs(node, this.initial_tabs),
136
+ (node) => this.find_attached_events(node, this.#dependency_payload),
137
+ (node) => untrack_children_of_closed_accordions_or_inactive_tabs(node, this.components_to_register, this.#hidden_on_startup)
124
138
  ]);
125
139
  }
126
140
  find_attached_events(node, dependencies) {
@@ -191,7 +205,7 @@ export class AppTree {
191
205
  if (reactive_formatter) {
192
206
  component.props.i18n = reactive_formatter;
193
207
  }
194
- const processed_props = gather_props(opts.id, component.props, [this.#input_ids, this.#output_ids], this.client, { ...this.#config });
208
+ const processed_props = gather_props(opts.id, component.props, [this.#input_ids, this.#output_ids], this.client, this.#config.api_url, { ...this.#config });
195
209
  const type = type_map[component.type] || component.type;
196
210
  const node = {
197
211
  id: opts.id,
@@ -201,11 +215,12 @@ export class AppTree {
201
215
  show_progress_on: null,
202
216
  component_class_id: component.component_class_id || component.type,
203
217
  component: processed_props.shared_props.visible !== false
204
- ? get_component(component.type, component.component_class_id, this.#config.root || "")
218
+ ? get_component(component.type, component.component_class_id, this.#config.api_url || "")
205
219
  : null,
206
220
  key: component.key,
207
221
  rendered_in: component.rendered_in,
208
- documentation: component.documentation
222
+ documentation: component.documentation,
223
+ original_visibility: processed_props.shared_props.visible
209
224
  };
210
225
  return node;
211
226
  }
@@ -224,6 +239,15 @@ export class AppTree {
224
239
  }
225
240
  n.children = subtree.children;
226
241
  }
242
+ async update_visibility(node, new_state) {
243
+ node.children.forEach((child) => {
244
+ const _set_data = this.#set_callbacks.get(child.id);
245
+ if (_set_data) {
246
+ _set_data(new_state);
247
+ }
248
+ this.update_visibility(child, new_state);
249
+ });
250
+ }
227
251
  /*
228
252
  * Updates the state of a component by its ID
229
253
  * @param id the ID of the component to update
@@ -242,20 +266,44 @@ export class AppTree {
242
266
  this.root = this.traverse(this.root, [
243
267
  //@ts-ignore
244
268
  (n) => set_visibility_for_updated_node(n, id, new_state.visible),
245
- (n) => handle_visibility(n, this.#config.root)
269
+ //@ts-ignore
270
+ (n) => update_parent_visibility(n, id, new_state.visible),
271
+ (n) => handle_visibility(n, this.#config.api_url)
246
272
  ]);
273
+ await tick();
247
274
  already_updated_visibility = true;
248
275
  }
249
276
  const _set_data = this.#set_callbacks.get(id);
250
- if (!_set_data)
251
- return;
252
- _set_data(new_state);
277
+ if (!_set_data) {
278
+ const old_value = node?.props.props.value;
279
+ // @ts-ignore
280
+ const new_props = create_props_shared_props(new_state);
281
+ node.props.shared_props = {
282
+ ...node?.props.shared_props,
283
+ ...new_props.shared_props
284
+ };
285
+ node.props.props = { ...node?.props.props, ...new_props.props };
286
+ if ("value" in new_state && !dequal(old_value, new_state.value)) {
287
+ this.#event_dispatcher(id, "change", null);
288
+ }
289
+ }
290
+ else if (_set_data) {
291
+ _set_data(new_state);
292
+ }
253
293
  if (!check_visibility || already_updated_visibility)
254
294
  return;
255
295
  // need to let the UI settle before traversing again
256
296
  // otherwise there could be
257
297
  await tick();
258
- this.root = this.traverse(this.root, (n) => handle_visibility(n, this.#config.root));
298
+ // Update the visibility in a way that does not
299
+ // re-render the root/tree. Doing that would nuke
300
+ // any values currently in the UI.
301
+ // @ts-ignore
302
+ await this.update_visibility(node, new_state);
303
+ const parent_node = find_parent(this.root, id);
304
+ if (parent_node)
305
+ // @ts-ignore
306
+ update_parent_visibility(parent_node, id, new_state.visible);
259
307
  }
260
308
  /**
261
309
  * Gets the current state of a component by its ID
@@ -264,15 +312,34 @@ export class AppTree {
264
312
  */
265
313
  async get_state(id) {
266
314
  const _get_data = this.#get_callbacks.get(id);
267
- const component = this.#component_payload.find((c) => c.id === id);
315
+ const component = find_node_by_id(this.root, id);
268
316
  if (!_get_data && !component)
269
317
  return null;
270
318
  if (_get_data)
271
319
  return await _get_data();
272
320
  if (component)
273
- return Promise.resolve({ value: component.props.value });
321
+ return Promise.resolve({ value: component.props.props.value });
274
322
  return null;
275
323
  }
324
+ async render_previously_invisible_children(id) {
325
+ this.root = this.traverse(this.root, [
326
+ (node) => {
327
+ if (node.id === id) {
328
+ make_visible_if_not_rendered(node, this.#hidden_on_startup);
329
+ }
330
+ return node;
331
+ },
332
+ (node) => handle_visibility(node, this.#config.api_url)
333
+ ]);
334
+ }
335
+ }
336
+ function make_visible_if_not_rendered(node, hidden_on_startup) {
337
+ node.props.shared_props.visible = hidden_on_startup.has(node.id)
338
+ ? true
339
+ : node.props.shared_props.visible;
340
+ node.children.forEach((child) => {
341
+ make_visible_if_not_rendered(child, hidden_on_startup);
342
+ });
276
343
  }
277
344
  /**
278
345
  * Process the server function names and return a dictionary of functions
@@ -296,15 +363,7 @@ export function process_server_fn(id, server_fns, app) {
296
363
  return acc;
297
364
  }, {});
298
365
  }
299
- /**
300
- * Gathers the props for a component
301
- * @param id the ID of the component
302
- * @param props the props of the component
303
- * @param dependencies the component's dependencies
304
- * @param additional any additional props to include
305
- * @returns the gathered props as an object with `shared_props` and `props` keys
306
- */
307
- function gather_props(id, props, dependencies, client, additional = {}) {
366
+ function create_props_shared_props(props) {
308
367
  const _shared_props = {};
309
368
  const _props = {};
310
369
  for (const key in props) {
@@ -316,14 +375,24 @@ function gather_props(id, props, dependencies, client, additional = {}) {
316
375
  else if (allowed_shared_props.includes(key)) {
317
376
  const _key = key;
318
377
  _shared_props[_key] = props[key];
319
- if (_key === "server_fns") {
320
- _shared_props.server = process_server_fn(id, props.server_fns, client);
321
- }
322
378
  }
323
379
  else {
324
380
  _props[key] = props[key];
325
381
  }
326
382
  }
383
+ return { shared_props: _shared_props, props: _props };
384
+ }
385
+ /**
386
+ * Gathers the props for a component
387
+ * @param id the ID of the component
388
+ * @param props the props of the component
389
+ * @param dependencies the component's dependencies
390
+ * @param additional any additional props to include
391
+ * @returns the gathered props as an object with `shared_props` and `props` keys
392
+ */
393
+ function gather_props(id, props, dependencies, client, api_url, additional = {}) {
394
+ const { shared_props: _shared_props, props: _props } = create_props_shared_props(props);
395
+ _shared_props.server = process_server_fn(id, props.server_fns, client);
327
396
  for (const key in additional) {
328
397
  if (allowed_shared_props.includes(key)) {
329
398
  const _key = key;
@@ -336,22 +405,22 @@ function gather_props(id, props, dependencies, client, additional = {}) {
336
405
  _shared_props.client = client;
337
406
  _shared_props.id = id;
338
407
  _shared_props.interactive = determine_interactivity(id, _shared_props.interactive, _props.value, dependencies);
339
- _shared_props.load_component = (name, variant) => get_component(name, "", _shared_props.root || "", variant);
408
+ _shared_props.load_component = (name, variant) => get_component(name, "", api_url, variant);
340
409
  _shared_props.visible =
341
410
  _shared_props.visible === undefined ? true : _shared_props.visible;
342
411
  _shared_props.loading_status = {};
343
412
  return { shared_props: _shared_props, props: _props };
344
413
  }
345
- function handle_visibility(node, root) {
414
+ function handle_visibility(node, api_url) {
346
415
  // Check if the node is visible
347
416
  if (node.props.shared_props.visible && !node.component) {
348
417
  const result = {
349
418
  ...node,
350
- component: get_component(node.type, node.component_class_id, root),
419
+ component: get_component(node.type, node.component_class_id, api_url),
351
420
  children: []
352
421
  };
353
422
  if (node.children) {
354
- result.children = node.children.map((child) => handle_visibility(child, root));
423
+ result.children = node.children.map((child) => handle_visibility(child, api_url));
355
424
  }
356
425
  return result;
357
426
  }
@@ -372,14 +441,46 @@ function _untrack(node, components_to_register) {
372
441
  }
373
442
  return;
374
443
  }
375
- function untrack_children_of_invisible_parents(node, root, components_to_register) {
444
+ function untrack_children_of_invisible_parents(node, components_to_register) {
376
445
  // Check if the node is visible
377
446
  if (node.props.shared_props.visible !== true) {
378
447
  _untrack(node, components_to_register);
379
448
  }
380
449
  return node;
381
450
  }
382
- function handle_empty_forms(node, root, components_to_register) {
451
+ function mark_component_invisible_if_visible(node, hidden_on_startup) {
452
+ if (node.props.shared_props.visible === true) {
453
+ hidden_on_startup.add(node.id);
454
+ node.props.shared_props.visible = false;
455
+ }
456
+ node.children.forEach((child) => {
457
+ mark_component_invisible_if_visible(child, hidden_on_startup);
458
+ });
459
+ return node;
460
+ }
461
+ function untrack_children_of_closed_accordions_or_inactive_tabs(node, components_to_register, hidden_on_startup) {
462
+ // Check if the node is an accordion or tabs
463
+ if (node.type === "accordion" && node.props.props.open === false) {
464
+ _untrack(node, components_to_register);
465
+ if (node.children) {
466
+ node.children.forEach((child) => {
467
+ mark_component_invisible_if_visible(child, hidden_on_startup);
468
+ });
469
+ }
470
+ }
471
+ if (node.type === "tabs") {
472
+ node.children.forEach((child) => {
473
+ if (child.type === "tabitem" &&
474
+ child.props.props.id !==
475
+ (node.props.props.selected || node.props.props.initial_tabs[0].id)) {
476
+ _untrack(child, components_to_register);
477
+ mark_component_invisible_if_visible(child, hidden_on_startup);
478
+ }
479
+ });
480
+ }
481
+ return node;
482
+ }
483
+ function handle_empty_forms(node, components_to_register) {
383
484
  // Check if the node is visible
384
485
  if (node.type === "form") {
385
486
  const all_children_invisible = node.children.every((child) => child.props.shared_props.visible === false);
@@ -391,7 +492,28 @@ function handle_empty_forms(node, root, components_to_register) {
391
492
  }
392
493
  return node;
393
494
  }
394
- function translate_props(node, root) {
495
+ function update_parent_visibility(node, child_made_visible, visibility_state) {
496
+ // This function was added to address a tricky situation:
497
+ // Form components are wrapped in a Form component automatically.
498
+ // If all the children of the Form are invisible, the Form itself is marked invisible.
499
+ // in AppTree.postprocess -> handle_empty_forms
500
+ // This is to avoid rendering empty forms in the UI. They look ugly.
501
+ // So what happens when a child inside the Form is made visible again?
502
+ // The Form needs to become visible again too.
503
+ // If the child is made invisible, the form should be too if all other children are invisible.
504
+ // However, we are not doing this now since what we want to do is fetch the latest visibility of all
505
+ // the children from the UI. However, get_data only returns the props, not the shared props.
506
+ if (node.type === "form" &&
507
+ node.children.length &&
508
+ node.children.some((child) => child.id === child_made_visible)) {
509
+ if (visibility_state === true)
510
+ node.props.shared_props.visible = true;
511
+ else if (!visibility_state && node.children.length === 1)
512
+ node.props.shared_props.visible = "hidden";
513
+ }
514
+ return node;
515
+ }
516
+ function translate_props(node) {
395
517
  const supported_props = [
396
518
  "description",
397
519
  "info",
@@ -413,11 +535,14 @@ function translate_props(node, root) {
413
535
  }
414
536
  return node;
415
537
  }
416
- function apply_initial_tabs(node, root, initial_tabs) {
538
+ function apply_initial_tabs(node, initial_tabs) {
417
539
  if (node.type === "tabs" && node.id in initial_tabs) {
418
540
  const tabs = initial_tabs[node.id].sort((a, b) => a.order - b.order);
419
541
  node.props.props.initial_tabs = tabs;
420
542
  }
543
+ else if (node.type === "tabitem") {
544
+ node.props.props.component_id = node.id;
545
+ }
421
546
  return node;
422
547
  }
423
548
  function _gather_initial_tabs(node, initial_tabs, parent_tab_id, order) {
@@ -434,7 +559,8 @@ function _gather_initial_tabs(node, initial_tabs, parent_tab_id, order) {
434
559
  elem_id: node.props.shared_props.elem_id,
435
560
  visible: node.props.shared_props.visible,
436
561
  interactive: node.props.shared_props.interactive,
437
- scale: node.props.shared_props.scale || null
562
+ scale: node.props.shared_props.scale || null,
563
+ component_id: node.id
438
564
  });
439
565
  node.props.props.order = order;
440
566
  }
@@ -467,3 +593,17 @@ function find_node_by_id(tree, id) {
467
593
  }
468
594
  return null;
469
595
  }
596
+ function find_parent(tree, id) {
597
+ if (tree.children) {
598
+ for (const child of tree.children) {
599
+ if (child.id === id) {
600
+ return tree;
601
+ }
602
+ const result = find_parent(child, id);
603
+ if (result) {
604
+ return result;
605
+ }
606
+ }
607
+ }
608
+ return null;
609
+ }
@@ -28,6 +28,7 @@ export interface ProcessedComponentMeta {
28
28
  component_class_id: string;
29
29
  key: string | number | null;
30
30
  rendered_in?: number;
31
+ original_visibility: boolean | "hidden";
31
32
  }
32
33
  /** Dictates whether a dependency is continous and/or a generator */
33
34
  export interface DependencyTypes {
@@ -107,5 +108,6 @@ export interface AppConfig {
107
108
  max_file_size?: number;
108
109
  autoscroll: boolean;
109
110
  api_prefix: string;
111
+ api_url: string;
110
112
  }
111
113
  export {};
package/package.json CHANGED
@@ -1,67 +1,67 @@
1
1
  {
2
2
  "name": "@gradio/core",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "devDependencies": {
6
- "@gradio/accordion": "^0.5.25",
7
- "@gradio/audio": "^0.20.0",
6
+ "@gradio/accordion": "^0.5.26",
8
7
  "@gradio/atoms": "^0.19.0",
8
+ "@gradio/annotatedimage": "^0.10.1",
9
9
  "@gradio/box": "^0.2.26",
10
+ "@gradio/audio": "^0.21.0",
10
11
  "@gradio/browserstate": "^0.3.3",
11
- "@gradio/button": "^0.5.14",
12
- "@gradio/annotatedimage": "^0.10.1",
12
+ "@gradio/button": "^0.6.0",
13
13
  "@gradio/chatbot": "^0.28.0",
14
- "@gradio/checkbox": "^0.5.0",
15
- "@gradio/client": "^2.0.0",
14
+ "@gradio/checkbox": "^0.5.1",
15
+ "@gradio/code": "^0.16.0",
16
16
  "@gradio/checkboxgroup": "^0.8.0",
17
+ "@gradio/colorpicker": "^0.5.1",
17
18
  "@gradio/column": "^0.3.0",
18
- "@gradio/code": "^0.16.0",
19
- "@gradio/colorpicker": "^0.5.0",
20
- "@gradio/dataframe": "^0.21.0",
19
+ "@gradio/client": "^2.0.0",
21
20
  "@gradio/dataset": "^0.5.0",
22
- "@gradio/downloadbutton": "^0.4.13",
21
+ "@gradio/dataframe": "^0.21.1",
22
+ "@gradio/dropdown": "^0.10.7",
23
23
  "@gradio/datetime": "^0.3.23",
24
- "@gradio/dropdown": "^0.10.6",
25
- "@gradio/file": "^0.13.1",
24
+ "@gradio/downloadbutton": "^0.4.14",
26
25
  "@gradio/fallback": "^0.4.30",
26
+ "@gradio/file": "^0.13.1",
27
27
  "@gradio/fileexplorer": "^0.5.42",
28
+ "@gradio/form": "^0.2.27",
29
+ "@gradio/gallery": "^0.15.36",
28
30
  "@gradio/group": "^0.3.0",
29
- "@gradio/gallery": "^0.15.35",
30
- "@gradio/form": "^0.2.26",
31
- "@gradio/highlightedtext": "^0.9.14",
32
- "@gradio/html": "^0.8.0",
33
- "@gradio/icons": "^0.15.0",
31
+ "@gradio/highlightedtext": "^0.9.15",
32
+ "@gradio/html": "^0.8.1",
34
33
  "@gradio/image": "^0.24.0",
34
+ "@gradio/icons": "^0.15.0",
35
35
  "@gradio/imageeditor": "^0.18.2",
36
- "@gradio/imageslider": "^0.3.1",
37
36
  "@gradio/json": "^0.5.32",
38
- "@gradio/model3d": "^0.15.1",
39
- "@gradio/markdown": "^0.13.23",
37
+ "@gradio/imageslider": "^0.3.1",
40
38
  "@gradio/label": "^0.5.22",
41
- "@gradio/multimodaltextbox": "^0.11.0",
42
- "@gradio/nativeplot": "^0.9.0",
43
- "@gradio/paramviewer": "^0.9.0",
39
+ "@gradio/markdown": "^0.13.23",
40
+ "@gradio/model3d": "^0.15.1",
41
+ "@gradio/multimodaltextbox": "^0.11.1",
44
42
  "@gradio/number": "^0.7.2",
45
43
  "@gradio/plot": "^0.9.25",
46
- "@gradio/row": "^0.3.0",
44
+ "@gradio/nativeplot": "^0.9.1",
45
+ "@gradio/paramviewer": "^0.9.0",
47
46
  "@gradio/radio": "^0.8.0",
47
+ "@gradio/row": "^0.3.0",
48
48
  "@gradio/sidebar": "^0.1.24",
49
- "@gradio/simpledropdown": "^0.3.30",
50
49
  "@gradio/simpleimage": "^0.9.1",
50
+ "@gradio/simpledropdown": "^0.3.30",
51
51
  "@gradio/simpletextbox": "^0.3.31",
52
- "@gradio/slider": "^0.7.0",
52
+ "@gradio/slider": "^0.7.1",
53
53
  "@gradio/state": "^0.2.0",
54
+ "@gradio/tabitem": "^0.6.3",
54
55
  "@gradio/statustracker": "^0.12.0",
55
- "@gradio/tabitem": "^0.6.2",
56
- "@gradio/tabs": "^0.5.2",
57
- "@gradio/textbox": "^0.12.0",
56
+ "@gradio/textbox": "^0.12.1",
57
+ "@gradio/tabs": "^0.5.3",
58
58
  "@gradio/theme": "^0.5.0",
59
59
  "@gradio/timer": "^0.4.6",
60
- "@gradio/uploadbutton": "^0.9.13",
61
- "@gradio/upload": "^0.17.2",
60
+ "@gradio/uploadbutton": "^0.9.14",
61
+ "@gradio/utils": "^0.10.4",
62
62
  "@gradio/vibeeditor": "^0.3.1",
63
- "@gradio/utils": "^0.10.3",
64
- "@gradio/video": "^0.17.0"
63
+ "@gradio/video": "^0.18.0",
64
+ "@gradio/upload": "^0.17.2"
65
65
  },
66
66
  "msw": {
67
67
  "workerDirectory": "public"