@gradio/core 0.27.2 → 0.29.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # @gradio/core
2
2
 
3
+ ## 0.29.0
4
+
5
+ ### Features
6
+
7
+ - [#11894](https://github.com/gradio-app/gradio/pull/11894) [`cf10724`](https://github.com/gradio-app/gradio/commit/cf107246c104f3d8be1adf7a4c3d85558a0a5497) - Remove additional indentation in python API docs. Thanks @freddyaboulton!
8
+ - [#11858](https://github.com/gradio-app/gradio/pull/11858) [`3f8ea13`](https://github.com/gradio-app/gradio/commit/3f8ea13a8ca92abf0ad34392e403a449fda3c6c2) - remove lite. Thanks @pngwn!
9
+ - [#11902](https://github.com/gradio-app/gradio/pull/11902) [`6d39644`](https://github.com/gradio-app/gradio/commit/6d39644ee1000b04728eb4909cee405e7ee3b5b2) - Add navbar visibility controls and customization options. Thanks @abidlabs!
10
+
11
+ ### Fixes
12
+
13
+ - [#11919](https://github.com/gradio-app/gradio/pull/11919) [`3b15f63`](https://github.com/gradio-app/gradio/commit/3b15f6370bc143c0acd91225574250bccf0a8877) - ensure spaces iframe resizes when images load. Thanks @pngwn!
14
+ - [#11784](https://github.com/gradio-app/gradio/pull/11784) [`d9dd3f5`](https://github.com/gradio-app/gradio/commit/d9dd3f54b7fb34cf7118e549d39fc63937ca3489) - Add "hidden" option to component's `visible` kwarg to render but visually hide the component. Thanks @pngwn!
15
+ - [#11866](https://github.com/gradio-app/gradio/pull/11866) [`02df670`](https://github.com/gradio-app/gradio/commit/02df670eccf365c253f38b682a3000fe316eef22) - Fix i18n when accept-language header contains multiple values. Thanks @freddyaboulton!
16
+ - [#11903](https://github.com/gradio-app/gradio/pull/11903) [`895d884`](https://github.com/gradio-app/gradio/commit/895d884a7345894f95df5f131d6517e935fd275a) - enhanced_the_arabic_translation. Thanks @makhlwf!
17
+
18
+ ### Dependency updates
19
+
20
+ - @gradio/paramviewer@0.8.0
21
+ - @gradio/code@0.15.0
22
+ - @gradio/statustracker@0.11.1
23
+ - @gradio/tabitem@0.6.1
24
+ - @gradio/tabs@0.5.1
25
+ - @gradio/atoms@0.18.0
26
+ - @gradio/checkbox@0.4.30
27
+ - @gradio/client@1.19.0
28
+ - @gradio/upload@0.17.0
29
+ - @gradio/button@0.5.13
30
+ - @gradio/textbox@0.11.1
31
+ - @gradio/gallery@0.15.33
32
+ - @gradio/plot@0.9.24
33
+ - @gradio/image@0.23.0
34
+ - @gradio/column@0.2.2
35
+ - @gradio/dropdown@0.10.4
36
+ - @gradio/file@0.13.0
37
+ - @gradio/video@0.16.0
38
+
39
+ ## 0.28.0
40
+
41
+ ### Features
42
+
43
+ - [#11814](https://github.com/gradio-app/gradio/pull/11814) [`013784a`](https://github.com/gradio-app/gradio/commit/013784a7086047651e8e661a38bde7d5c7f10db7) - add validation support. Thanks @pngwn!
44
+ - [#11833](https://github.com/gradio-app/gradio/pull/11833) [`a446fcb`](https://github.com/gradio-app/gradio/commit/a446fcba6f3fe59c32194beb7f27fb6f80b61347) - Add gr.Navbar component for multipage apps. Thanks @abidlabs!
45
+ - [#11783](https://github.com/gradio-app/gradio/pull/11783) [`f407daf`](https://github.com/gradio-app/gradio/commit/f407daf8046f37e042ab8b86730ff0ab8d174bcf) - Add Walkthrough and Step compoents to facilitate multi-step workflows. Thanks @pngwn!
46
+
47
+ ### Fixes
48
+
49
+ - [#11815](https://github.com/gradio-app/gradio/pull/11815) [`1a477c5`](https://github.com/gradio-app/gradio/commit/1a477c5202c13097d1089fb70a32a08db22d7660) - Fix i18n string visible during load and i18n not respecting HTML. Thanks @freddyaboulton!
50
+ - [#11749](https://github.com/gradio-app/gradio/pull/11749) [`70f4532`](https://github.com/gradio-app/gradio/commit/70f4532a4dc7576dbdbe1d0a43a05644a0dfcf43) - fix various iFrame related UI issues when deploying to spaces. Thanks @pngwn!
51
+
52
+ ### Dependency updates
53
+
54
+ - @gradio/client@1.18.0
55
+ - @gradio/icons@0.14.0
56
+ - @gradio/atoms@0.17.0
57
+ - @gradio/statustracker@0.11.0
58
+ - @gradio/gallery@0.15.32
59
+ - @gradio/plot@0.9.23
60
+ - @gradio/upload@0.16.17
61
+ - @gradio/file@0.12.29
62
+ - @gradio/image@0.22.18
63
+ - @gradio/video@0.15.1
64
+ - @gradio/tabitem@0.6.0
65
+ - @gradio/tabs@0.5.0
66
+ - @gradio/code@0.14.16
67
+ - @gradio/paramviewer@0.7.16
68
+ - @gradio/column@0.2.1
69
+ - @gradio/textbox@0.11.0
70
+ - @gradio/dropdown@0.10.3
71
+ - @gradio/button@0.5.12
72
+ - @gradio/checkbox@0.4.29
73
+
3
74
  ## 0.27.2
4
75
 
5
76
  ### Features
@@ -193,6 +193,17 @@ async function handle_update(data, fn_index) {
193
193
  }
194
194
  });
195
195
  update_value(updates);
196
+ updates.forEach((update) => {
197
+ const component = components.find((comp) => comp.id === update.id);
198
+ if (component && component.type === "navbar") {
199
+ import("./navbar_store").then(({ navbar_config }) => {
200
+ navbar_config.update((current) => ({
201
+ ...current,
202
+ [update.prop]: update.value
203
+ }));
204
+ });
205
+ }
206
+ });
196
207
  await tick();
197
208
  }
198
209
  let submit_map = /* @__PURE__ */ new Map();
@@ -485,6 +496,40 @@ async function trigger_api_call(dep_index, trigger_id = null, event_data = null)
485
496
  }
486
497
  }
487
498
  function handle_status_update(message) {
499
+ if (message.code === "validation_error") {
500
+ const dep2 = dependencies.find((dep3) => dep3.id === message.fn_index);
501
+ if (dep2 === void 0 || message.message === void 0 || typeof message.message === "string") {
502
+ return;
503
+ }
504
+ const validation_error_data = [];
505
+ message.message.forEach((message2, i) => {
506
+ if (message2.is_valid) {
507
+ return;
508
+ }
509
+ validation_error_data.push({
510
+ id: dep2.inputs[i],
511
+ prop: "validation_error",
512
+ value: message2.message
513
+ });
514
+ validation_error_data.push({
515
+ id: dep2.inputs[i],
516
+ prop: "loading_status",
517
+ value: { validation_error: message2.message }
518
+ });
519
+ });
520
+ if (validation_error_data.length > 0) {
521
+ update_value(validation_error_data);
522
+ loading_status.update({
523
+ status: "complete",
524
+ fn_index: message.fn_index,
525
+ eta: 0,
526
+ queue: false,
527
+ queue_position: null
528
+ });
529
+ set_status($loading_status);
530
+ return;
531
+ }
532
+ }
488
533
  if (message.broken && !broken_connection) {
489
534
  messages = [
490
535
  new_message(
@@ -567,7 +612,7 @@ async function trigger_api_call(dep_index, trigger_id = null, event_data = null)
567
612
  submit_map.delete(dep_index);
568
613
  }
569
614
  if (status.stage === "error" && !broken_connection && !message.session_not_found) {
570
- if (status.message) {
615
+ if (status.message && typeof status.message === "string") {
571
616
  const _message = status.message.replace(
572
617
  MESSAGE_QUOTE_RE,
573
618
  (_2, b) => b
@@ -636,6 +681,11 @@ async function handle_mount() {
636
681
  target.addEventListener("prop_change", (e) => {
637
682
  if (!isCustomEvent(e)) throw new Error("not a custom event");
638
683
  const { id, prop, value } = e.detail;
684
+ if (prop === "value") {
685
+ update_value([
686
+ { id, prop: "loading_status", value: { validation_error: void 0 } }
687
+ ]);
688
+ }
639
689
  update_value([{ id, prop, value }]);
640
690
  if (prop === "input_ready" && value === false) {
641
691
  inputs_waiting.push(id);
@@ -780,6 +830,37 @@ function screen_recording() {
780
830
  screen_recorder.startRecording();
781
831
  }
782
832
  }
833
+ let footer_height = 0;
834
+ let root_container;
835
+ $: root_node = $_layout && get_root_node(root_container);
836
+ function get_root_node(container) {
837
+ if (!container) return null;
838
+ return container.children[container.children.length - 1];
839
+ }
840
+ function handle_resize() {
841
+ if ("parentIFrame" in window) {
842
+ const box = root_node?.getBoundingClientRect();
843
+ if (!box) return;
844
+ window.parentIFrame?.size(box.bottom + footer_height + 32);
845
+ }
846
+ }
847
+ onMount(() => {
848
+ if ("parentIFrame" in window) {
849
+ window.parentIFrame?.autoResize(false);
850
+ }
851
+ const mut = new MutationObserver(handle_resize);
852
+ const res = new ResizeObserver(handle_resize);
853
+ mut.observe(root_container, {
854
+ childList: true,
855
+ subtree: true,
856
+ attributes: true
857
+ });
858
+ res.observe(root_container);
859
+ return () => {
860
+ mut.disconnect();
861
+ res.disconnect();
862
+ };
863
+ });
783
864
  </script>
784
865
 
785
866
  <svelte:head>
@@ -795,6 +876,7 @@ function screen_recording() {
795
876
  <div
796
877
  class="contain"
797
878
  style:flex-grow={app_mode ? "1" : "auto"}
879
+ bind:this={root_container}
798
880
  style:margin-right={vibe_mode ? `${vibe_editor_width}px` : "0"}
799
881
  >
800
882
  {#if $_layout && app.config}
@@ -813,7 +895,7 @@ function screen_recording() {
813
895
  </div>
814
896
 
815
897
  {#if show_footer}
816
- <footer>
898
+ <footer bind:clientHeight={footer_height}>
817
899
  {#if show_api}
818
900
  <button
819
901
  on:click={() => {
@@ -1,12 +1,12 @@
1
- <script>import { getContext } from "svelte";
1
+ <script>import { getContext, onMount } from "svelte";
2
2
  import space_logo from "./images/spaces.svg";
3
3
  import { _ } from "svelte-i18n";
4
+ import { navbar_config } from "./navbar_store";
4
5
  export let wrapper;
5
6
  export let version;
6
7
  export let initial_height;
7
8
  export let fill_width;
8
9
  export let is_embed;
9
- export let is_lite;
10
10
  export let space;
11
11
  export let display;
12
12
  export let info;
@@ -14,7 +14,45 @@ export let loaded;
14
14
  export let pages = [];
15
15
  export let current_page = "";
16
16
  export let root;
17
- const set_page = getContext("set_lite_page");
17
+ export let components = [];
18
+ let navbar_component = components.find((c) => c.type === "navbar");
19
+ let navbar = navbar_component ? {
20
+ visible: navbar_component.props.visible,
21
+ main_page_name: navbar_component.props.main_page_name,
22
+ value: navbar_component.props.value
23
+ } : null;
24
+ if (navbar) {
25
+ navbar_config.set(navbar);
26
+ }
27
+ $: if ($navbar_config) {
28
+ navbar = {
29
+ visible: $navbar_config.visible ?? true,
30
+ main_page_name: $navbar_config.main_page_name ?? "Home",
31
+ value: $navbar_config.value ?? null
32
+ };
33
+ }
34
+ $: show_navbar = pages.length > 1 && (navbar === null || navbar.visible !== false);
35
+ $: effective_pages = (() => {
36
+ let visible_pages = pages.filter(([route, label, show], index) => {
37
+ if (index === 0 && route === "") {
38
+ return navbar?.main_page_name !== false;
39
+ }
40
+ return show !== false;
41
+ });
42
+ let base_pages = navbar && navbar.main_page_name !== false && navbar.main_page_name !== "Home" ? visible_pages.map(
43
+ ([route, label, show], index) => index === 0 && route === "" && label === "Home" ? [route, navbar.main_page_name] : [route, label]
44
+ ) : visible_pages.map(
45
+ ([route, label]) => [route, label]
46
+ );
47
+ if (navbar?.value && navbar.value.length > 0) {
48
+ const existing_routes = new Set(base_pages.map(([route]) => route));
49
+ const additional_pages = navbar.value.map(
50
+ ([page_name, page_path]) => [page_path, page_name]
51
+ ).filter(([route]) => !existing_routes.has(route));
52
+ return [...base_pages, ...additional_pages];
53
+ }
54
+ return base_pages;
55
+ })();
18
56
  </script>
19
57
 
20
58
  <div
@@ -27,27 +65,24 @@ const set_page = getContext("set_lite_page");
27
65
  style:flex-grow={!display ? "1" : "auto"}
28
66
  data-iframe-height
29
67
  >
30
- {#if pages.length > 1}
68
+ {#if show_navbar}
31
69
  <div class="nav-holder">
32
70
  <nav class="fillable" class:fill_width>
33
- {#each pages as [route, label], i}
34
- {#if is_lite}
35
- <button
36
- class:active={route === current_page}
37
- on:click={(e) => {
38
- e.preventDefault();
39
- set_page?.(route);
40
- }}
41
- >{label}
42
- </button>
43
- {:else}
44
- <a
45
- href={`${root}/${route}`}
46
- class:active={route === current_page}
47
- data-sveltekit-reload
48
- >{label}
49
- </a>
50
- {/if}
71
+ {#each effective_pages as [route, label], i}
72
+ <a
73
+ href={route.startsWith("http://") || route.startsWith("https://")
74
+ ? route
75
+ : `${root}/${route}`}
76
+ class:active={route === current_page}
77
+ data-sveltekit-reload
78
+ target={route.startsWith("http://") || route.startsWith("https://")
79
+ ? "_blank"
80
+ : "_self"}
81
+ rel={route.startsWith("http://") || route.startsWith("https://")
82
+ ? "noopener noreferrer"
83
+ : ""}
84
+ >{label}
85
+ </a>
51
86
  {/each}
52
87
  </nav>
53
88
  </div>
@@ -93,16 +128,14 @@ const set_page = getContext("set_lite_page");
93
128
  margin: 0 auto;
94
129
  padding: 0 var(--size-8);
95
130
  }
96
- nav a,
97
- button {
131
+ nav a {
98
132
  padding: var(--size-1) var(--size-2);
99
133
  border-radius: var(--block-radius);
100
134
  border-width: var(--block-border-width);
101
135
  border-color: transparent;
102
136
  color: var(--body-text-color-subdued);
103
137
  }
104
- nav a.active,
105
- button.active {
138
+ nav a.active {
106
139
  color: var(--body-text-color);
107
140
  border-color: var(--block-border-color);
108
141
  background-color: var(--block-background-fill);
@@ -6,14 +6,14 @@ declare const __propDef: {
6
6
  initial_height: string;
7
7
  fill_width: boolean;
8
8
  is_embed: boolean;
9
- is_lite: boolean;
10
9
  space: string | null;
11
10
  display: boolean;
12
11
  info: boolean;
13
12
  loaded: boolean;
14
- pages?: [string, string][];
13
+ pages?: [string, string, boolean][];
15
14
  current_page?: string;
16
15
  root: string;
16
+ components?: any[];
17
17
  };
18
18
  events: {
19
19
  [evt: string]: CustomEvent<any>;
@@ -39,10 +39,14 @@ $: {
39
39
  setContext("BLOCK_KEY", parent);
40
40
  $: {
41
41
  if (node && node.type === "form") {
42
- if (node.children?.every(
43
- (c) => typeof c.props.visible === "boolean" && !c.props.visible
44
- )) {
45
- node.props.visible = false;
42
+ const allChildrenInvisible = node.children?.every(
43
+ (c) => c.props.visible === false || c.props.visible === "hidden"
44
+ );
45
+ if (allChildrenInvisible) {
46
+ const hasHiddenChild = node.children?.some(
47
+ (c) => c.props.visible === "hidden"
48
+ );
49
+ node.props.visible = hasHiddenChild ? "hidden" : false;
46
50
  } else {
47
51
  node.props.visible = true;
48
52
  }
@@ -76,7 +80,8 @@ $: node.props.gradio = new Gradio(
76
80
  {...node.props}
77
81
  {theme_mode}
78
82
  {root}
79
- visible={typeof node.props.visible === "boolean"
83
+ visible={typeof node.props.visible === "boolean" ||
84
+ node.props.visible === "hidden"
80
85
  ? node.props.visible
81
86
  : true}
82
87
  >
@@ -12,7 +12,7 @@ declare const __propDef: {
12
12
  elem_id: string;
13
13
  elem_classes: string[];
14
14
  _id: number;
15
- visible: boolean;
15
+ visible: boolean | "hidden";
16
16
  };
17
17
  events: {
18
18
  prop_change: any;
@@ -43,13 +43,13 @@ $: normalised_root = root.replace(/\/$/, "");
43
43
  class="highlight">import</span
44
44
  > Client{#if has_file_path}, handle_file{/if}
45
45
 
46
- client = Client(<span class="token string">"{space_id || root}"</span
46
+ client = Client(<span class="token string">"{space_id || root}"</span
47
47
  >{#if username !== null}, auth=("{username}", **password**){/if})
48
- result = client.<span class="highlight">predict</span
48
+ result = client.<span class="highlight">predict</span
49
49
  >(<!--
50
- -->{#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i}<!--
51
- -->
52
- {parameter_name
50
+ -->{#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i}<!--
51
+ -->
52
+ {parameter_name
53
53
  ? parameter_name + "="
54
54
  : ""}<span
55
55
  >{represent_value(
@@ -59,11 +59,11 @@ $: normalised_root = root.replace(/\/$/, "");
59
59
  )}</span
60
60
  >,{/each}<!--
61
61
 
62
- -->
63
- api_name=<span class="api-name">"/{dependency.api_name}"</span><!--
64
- -->
65
- )
66
- <span class="highlight">print</span>(result)</pre>
62
+ -->
63
+ api_name=<span class="api-name">"/{dependency.api_name}"</span><!--
64
+ -->
65
+ )
66
+ <span class="highlight">print</span>(result)</pre>
67
67
  </div>
68
68
  </code>
69
69
  </Block>
@@ -15,11 +15,12 @@ export interface LangsRecord {
15
15
  };
16
16
  }
17
17
  export declare function is_translation_metadata(obj: any): obj is I18nData;
18
+ export declare const i18n_marker = "__i18n__";
18
19
  export declare function translate_if_needed(value: any): string;
19
20
  export declare function process_langs(): LangsRecord;
20
21
  export declare const language_choices: [string, string][];
21
22
  export declare let all_common_keys: Set<string>;
22
- export declare function setupi18n(custom_translations?: Record<string, Record<string, string>>): Promise<void>;
23
+ export declare function setupi18n(custom_translations?: Record<string, Record<string, string>>, preferred_locale?: string): Promise<void>;
23
24
  export declare function changeLocale(new_locale: string): void;
24
25
  export declare function get_initial_locale(browser_locale: string | null, available_locales: string[], fallback_locale?: string): string;
25
26
  export declare function load_translations(translations: {
package/dist/src/i18n.js CHANGED
@@ -43,12 +43,12 @@ export function is_translation_metadata(obj) {
43
43
  typeof obj.key === "string";
44
44
  return result;
45
45
  }
46
+ export const i18n_marker = "__i18n__";
46
47
  // handles strings with embedded JSON metadata of shape "__i18n__{"key": "some.key"}"
47
48
  export function translate_if_needed(value) {
48
49
  if (typeof value !== "string") {
49
50
  return value;
50
51
  }
51
- const i18n_marker = "__i18n__";
52
52
  const marker_index = value.indexOf(i18n_marker);
53
53
  if (marker_index === -1) {
54
54
  return value;
@@ -108,7 +108,19 @@ export const language_choices = Object.entries(processed_langs).map(([code]) =>
108
108
  export let all_common_keys = new Set();
109
109
  let i18n_initialized = false;
110
110
  let previous_translations;
111
- export async function setupi18n(custom_translations) {
111
+ function get_lang_from_preferred_locale(header) {
112
+ const options = header
113
+ .split(",")
114
+ .map((value) => value.includes(";") ? value.split(";").slice(0, 2) : [value, 1]);
115
+ options.sort((a, b) => parseFloat(b[1]) - parseFloat(a[1]));
116
+ for (const [lang, _] of options) {
117
+ if (available_locales.includes(lang)) {
118
+ return lang;
119
+ }
120
+ }
121
+ return null;
122
+ }
123
+ export async function setupi18n(custom_translations, preferred_locale) {
112
124
  const should_reinitialize = i18n_initialized && custom_translations !== previous_translations;
113
125
  if (i18n_initialized && !should_reinitialize) {
114
126
  return;
@@ -118,10 +130,17 @@ export async function setupi18n(custom_translations) {
118
130
  processed_langs,
119
131
  custom_translations: custom_translations ?? {}
120
132
  });
133
+ let initial_locale = null;
121
134
  const browser_locale = getLocaleFromNavigator();
122
- let initial_locale = browser_locale && available_locales.includes(browser_locale)
123
- ? browser_locale
124
- : null;
135
+ if (preferred_locale) {
136
+ initial_locale = get_lang_from_preferred_locale(preferred_locale);
137
+ }
138
+ else {
139
+ initial_locale =
140
+ browser_locale && available_locales.includes(browser_locale)
141
+ ? browser_locale
142
+ : null;
143
+ }
125
144
  if (!initial_locale) {
126
145
  const normalized_locale = browser_locale?.split("-")[0];
127
146
  initial_locale =