@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/package.json CHANGED
@@ -1,69 +1,68 @@
1
1
  {
2
2
  "name": "@gradio/core",
3
- "version": "0.27.2",
3
+ "version": "0.29.0",
4
4
  "type": "module",
5
5
  "devDependencies": {
6
- "@gradio/accordion": "^0.5.22",
7
- "@gradio/annotatedimage": "^0.9.29",
8
- "@gradio/audio": "^0.17.26",
9
- "@gradio/box": "^0.2.23",
6
+ "@gradio/annotatedimage": "^0.10.0",
7
+ "@gradio/accordion": "^0.5.24",
8
+ "@gradio/atoms": "^0.18.0",
9
+ "@gradio/box": "^0.2.25",
10
10
  "@gradio/browserstate": "^0.3.2",
11
- "@gradio/atoms": "^0.16.5",
12
- "@gradio/button": "^0.5.11",
13
- "@gradio/checkbox": "^0.4.28",
14
- "@gradio/chatbot": "^0.26.23",
15
- "@gradio/checkboxgroup": "^0.6.27",
16
- "@gradio/code": "^0.14.15",
17
- "@gradio/client": "^1.17.1",
18
- "@gradio/colorpicker": "^0.4.27",
19
- "@gradio/column": "^0.2.1",
20
- "@gradio/dataframe": "^0.19.1",
21
- "@gradio/dataset": "^0.4.32",
22
- "@gradio/dropdown": "^0.10.2",
23
- "@gradio/fallback": "^0.4.27",
24
- "@gradio/datetime": "^0.3.20",
25
- "@gradio/downloadbutton": "^0.4.11",
26
- "@gradio/fileexplorer": "^0.5.39",
27
- "@gradio/file": "^0.12.28",
28
- "@gradio/form": "^0.2.23",
29
- "@gradio/group": "^0.2.0",
30
- "@gradio/html": "^0.7.0",
31
- "@gradio/icons": "^0.13.1",
32
- "@gradio/highlightedtext": "^0.9.10",
33
- "@gradio/gallery": "^0.15.31",
34
- "@gradio/imageeditor": "^0.16.5",
35
- "@gradio/image": "^0.22.17",
36
- "@gradio/imageslider": "^0.2.13",
37
- "@gradio/json": "^0.5.29",
38
- "@gradio/label": "^0.5.19",
39
- "@gradio/model3d": "^0.14.25",
40
- "@gradio/markdown": "^0.13.20",
41
- "@gradio/number": "^0.6.4",
42
- "@gradio/multimodaltextbox": "^0.10.17",
43
- "@gradio/paramviewer": "^0.7.15",
44
- "@gradio/nativeplot": "^0.7.4",
45
- "@gradio/plot": "^0.9.22",
46
- "@gradio/row": "^0.2.1",
47
- "@gradio/sidebar": "^0.1.20",
48
- "@gradio/radio": "^0.7.10",
49
- "@gradio/simpledropdown": "^0.3.27",
50
- "@gradio/simpletextbox": "^0.3.28",
11
+ "@gradio/audio": "^0.19.0",
12
+ "@gradio/button": "^0.5.13",
13
+ "@gradio/chatbot": "^0.27.0",
14
+ "@gradio/checkbox": "^0.4.30",
15
+ "@gradio/checkboxgroup": "^0.7.0",
16
+ "@gradio/code": "^0.15.0",
17
+ "@gradio/colorpicker": "^0.4.29",
18
+ "@gradio/client": "^1.19.0",
19
+ "@gradio/column": "^0.2.2",
20
+ "@gradio/dataset": "^0.4.34",
21
+ "@gradio/downloadbutton": "^0.4.12",
22
+ "@gradio/dataframe": "^0.19.3",
23
+ "@gradio/datetime": "^0.3.22",
24
+ "@gradio/fallback": "^0.4.29",
25
+ "@gradio/dropdown": "^0.10.4",
26
+ "@gradio/file": "^0.13.0",
27
+ "@gradio/fileexplorer": "^0.5.41",
28
+ "@gradio/form": "^0.2.25",
29
+ "@gradio/gallery": "^0.15.33",
30
+ "@gradio/highlightedtext": "^0.9.12",
31
+ "@gradio/group": "^0.2.1",
32
+ "@gradio/html": "^0.7.2",
33
+ "@gradio/image": "^0.23.0",
34
+ "@gradio/icons": "^0.14.0",
35
+ "@gradio/imageslider": "^0.3.0",
36
+ "@gradio/imageeditor": "^0.17.0",
37
+ "@gradio/label": "^0.5.21",
38
+ "@gradio/json": "^0.5.31",
39
+ "@gradio/markdown": "^0.13.22",
40
+ "@gradio/model3d": "^0.15.0",
41
+ "@gradio/multimodaltextbox": "^0.10.19",
42
+ "@gradio/paramviewer": "^0.8.0",
43
+ "@gradio/number": "^0.7.1",
44
+ "@gradio/nativeplot": "^0.7.6",
45
+ "@gradio/plot": "^0.9.24",
46
+ "@gradio/radio": "^0.7.12",
47
+ "@gradio/simpledropdown": "^0.3.29",
48
+ "@gradio/sidebar": "^0.1.22",
49
+ "@gradio/simpleimage": "^0.9.0",
50
+ "@gradio/row": "^0.2.2",
51
+ "@gradio/simpletextbox": "^0.3.30",
52
+ "@gradio/slider": "^0.6.18",
53
+ "@gradio/sketchbox": "^0.6.17",
51
54
  "@gradio/state": "^0.1.2",
52
- "@gradio/sketchbox": "^0.6.15",
53
- "@gradio/simpleimage": "^0.8.39",
54
- "@gradio/statustracker": "^0.10.18",
55
- "@gradio/tabitem": "^0.5.0",
56
- "@gradio/slider": "^0.6.16",
57
- "@gradio/textbox": "^0.10.20",
58
- "@gradio/tabs": "^0.4.5",
55
+ "@gradio/tabitem": "^0.6.1",
56
+ "@gradio/tabs": "^0.5.1",
57
+ "@gradio/statustracker": "^0.11.1",
58
+ "@gradio/textbox": "^0.11.1",
59
59
  "@gradio/theme": "^0.4.0",
60
- "@gradio/uploadbutton": "^0.9.11",
61
- "@gradio/upload": "^0.16.16",
62
60
  "@gradio/timer": "^0.4.5",
61
+ "@gradio/upload": "^0.17.0",
62
+ "@gradio/vibeeditor": "^0.2.4",
63
+ "@gradio/uploadbutton": "^0.9.12",
63
64
  "@gradio/utils": "^0.10.2",
64
- "@gradio/vibeeditor": "^0.2.2",
65
- "@gradio/wasm": "^0.18.1",
66
- "@gradio/video": "^0.15.0"
65
+ "@gradio/video": "^0.16.0"
67
66
  },
68
67
  "msw": {
69
68
  "workerDirectory": "public"
@@ -80,6 +79,11 @@
80
79
  "svelte": "./dist/src/Login.svelte",
81
80
  "types": "./dist/src/Login.svelte.d.ts"
82
81
  },
82
+ "./navbar_store": {
83
+ "gradio": "./src/navbar_store.ts",
84
+ "import": "./src/navbar_store.ts",
85
+ "types": "./src/navbar_store.ts"
86
+ },
83
87
  "./package.json": "./package.json",
84
88
  ".": {
85
89
  "gradio": "./index.ts",
package/src/Blocks.svelte CHANGED
@@ -255,6 +255,19 @@
255
255
  });
256
256
  update_value(updates);
257
257
 
258
+ // Handle navbar updates separately since they need to be updated in the store.
259
+ updates.forEach((update) => {
260
+ const component = components.find((comp) => comp.id === update.id);
261
+ if (component && component.type === "navbar") {
262
+ import("./navbar_store").then(({ navbar_config }) => {
263
+ navbar_config.update((current) => ({
264
+ ...current,
265
+ [update.prop]: update.value
266
+ }));
267
+ });
268
+ }
269
+ });
270
+
258
271
  await tick();
259
272
  }
260
273
 
@@ -623,6 +636,53 @@
623
636
 
624
637
  /* eslint-disable complexity */
625
638
  function handle_status_update(message: StatusMessage): void {
639
+ if (message.code === "validation_error") {
640
+ const dep = dependencies.find((dep) => dep.id === message.fn_index);
641
+ if (
642
+ dep === undefined ||
643
+ message.message === undefined ||
644
+ typeof message.message === "string"
645
+ ) {
646
+ return;
647
+ }
648
+
649
+ const validation_error_data: {
650
+ id: number;
651
+ prop: string;
652
+ value: unknown;
653
+ }[] = [];
654
+
655
+ message.message.forEach((message, i) => {
656
+ if (message.is_valid) {
657
+ return;
658
+ }
659
+ validation_error_data.push({
660
+ id: dep.inputs[i],
661
+ prop: "validation_error",
662
+ value: message.message
663
+ });
664
+
665
+ validation_error_data.push({
666
+ id: dep.inputs[i],
667
+ prop: "loading_status",
668
+ value: { validation_error: message.message }
669
+ });
670
+ });
671
+
672
+ if (validation_error_data.length > 0) {
673
+ update_value(validation_error_data);
674
+ loading_status.update({
675
+ status: "complete",
676
+ fn_index: message.fn_index,
677
+ eta: 0,
678
+ queue: false,
679
+ queue_position: null
680
+ });
681
+ set_status($loading_status);
682
+
683
+ return;
684
+ }
685
+ }
626
686
  if (message.broken && !broken_connection) {
627
687
  messages = [
628
688
  new_message(
@@ -729,7 +789,7 @@
729
789
  !broken_connection &&
730
790
  !message.session_not_found
731
791
  ) {
732
- if (status.message) {
792
+ if (status.message && typeof status.message === "string") {
733
793
  const _message = status.message.replace(
734
794
  MESSAGE_QUOTE_RE,
735
795
  (_, b) => b
@@ -814,6 +874,11 @@
814
874
  target.addEventListener("prop_change", (e: Event) => {
815
875
  if (!isCustomEvent(e)) throw new Error("not a custom event");
816
876
  const { id, prop, value } = e.detail;
877
+ if (prop === "value") {
878
+ update_value([
879
+ { id, prop: "loading_status", value: { validation_error: undefined } }
880
+ ]);
881
+ }
817
882
  update_value([{ id, prop, value }]);
818
883
  if (prop === "input_ready" && value === false) {
819
884
  inputs_waiting.push(id);
@@ -990,6 +1055,46 @@
990
1055
  screen_recorder.startRecording();
991
1056
  }
992
1057
  }
1058
+
1059
+ let footer_height = 0;
1060
+
1061
+ let root_container: HTMLElement;
1062
+ $: root_node = $_layout && get_root_node(root_container);
1063
+
1064
+ function get_root_node(container: HTMLElement | null): HTMLElement | null {
1065
+ if (!container) return null;
1066
+ return container.children[container.children.length - 1] as HTMLElement;
1067
+ }
1068
+
1069
+ function handle_resize(): void {
1070
+ if ("parentIFrame" in window) {
1071
+ const box = root_node?.getBoundingClientRect();
1072
+ if (!box) return;
1073
+ window.parentIFrame?.size(box.bottom + footer_height + 32);
1074
+ }
1075
+ }
1076
+
1077
+ onMount(() => {
1078
+ if ("parentIFrame" in window) {
1079
+ window.parentIFrame?.autoResize(false);
1080
+ }
1081
+
1082
+ const mut = new MutationObserver(handle_resize);
1083
+ const res = new ResizeObserver(handle_resize);
1084
+
1085
+ mut.observe(root_container, {
1086
+ childList: true,
1087
+ subtree: true,
1088
+ attributes: true
1089
+ });
1090
+
1091
+ res.observe(root_container);
1092
+
1093
+ return () => {
1094
+ mut.disconnect();
1095
+ res.disconnect();
1096
+ };
1097
+ });
993
1098
  </script>
994
1099
 
995
1100
  <svelte:head>
@@ -1005,6 +1110,7 @@
1005
1110
  <div
1006
1111
  class="contain"
1007
1112
  style:flex-grow={app_mode ? "1" : "auto"}
1113
+ bind:this={root_container}
1008
1114
  style:margin-right={vibe_mode ? `${vibe_editor_width}px` : "0"}
1009
1115
  >
1010
1116
  {#if $_layout && app.config}
@@ -1023,7 +1129,7 @@
1023
1129
  </div>
1024
1130
 
1025
1131
  {#if show_footer}
1026
- <footer>
1132
+ <footer bind:clientHeight={footer_height}>
1027
1133
  {#if show_api}
1028
1134
  <button
1029
1135
  on:click={() => {
package/src/Embed.svelte CHANGED
@@ -1,24 +1,85 @@
1
1
  <script lang="ts">
2
- import { getContext } from "svelte";
2
+ import { getContext, onMount } from "svelte";
3
3
  import space_logo from "./images/spaces.svg";
4
4
  import { _ } from "svelte-i18n";
5
+ import { navbar_config } from "./navbar_store";
6
+
5
7
  export let wrapper: HTMLDivElement;
6
8
  export let version: string;
7
9
  export let initial_height: string;
8
10
  export let fill_width: boolean;
9
11
  export let is_embed: boolean;
10
- export let is_lite: boolean;
11
12
 
12
13
  export let space: string | null;
13
14
  export let display: boolean;
14
15
  export let info: boolean;
15
16
  export let loaded: boolean;
16
- export let pages: [string, string][] = [];
17
+ export let pages: [string, string, boolean][] = [];
17
18
  export let current_page = "";
18
19
  export let root: string;
20
+ export let components: any[] = [];
21
+
22
+ let navbar_component = components.find((c) => c.type === "navbar");
23
+ let navbar: {
24
+ visible: boolean;
25
+ main_page_name: string | false;
26
+ value: [string, string][] | null;
27
+ } | null = navbar_component
28
+ ? {
29
+ visible: navbar_component.props.visible,
30
+ main_page_name: navbar_component.props.main_page_name,
31
+ value: navbar_component.props.value
32
+ }
33
+ : null;
34
+
35
+ if (navbar) {
36
+ navbar_config.set(navbar);
37
+ }
38
+
39
+ $: if ($navbar_config) {
40
+ navbar = {
41
+ visible: $navbar_config.visible ?? true,
42
+ main_page_name: $navbar_config.main_page_name ?? "Home",
43
+ value: $navbar_config.value ?? null
44
+ };
45
+ }
46
+
47
+ $: show_navbar =
48
+ pages.length > 1 && (navbar === null || navbar.visible !== false);
49
+
50
+ $: effective_pages = (() => {
51
+ let visible_pages = pages.filter(([route, label, show], index) => {
52
+ if (index === 0 && route === "") {
53
+ return navbar?.main_page_name !== false;
54
+ }
55
+ return show !== false;
56
+ });
57
+
58
+ let base_pages =
59
+ navbar &&
60
+ navbar.main_page_name !== false &&
61
+ navbar.main_page_name !== "Home"
62
+ ? visible_pages.map(([route, label, show], index) =>
63
+ index === 0 && route === "" && label === "Home"
64
+ ? ([route, navbar!.main_page_name] as [string, string])
65
+ : ([route, label] as [string, string])
66
+ )
67
+ : visible_pages.map(
68
+ ([route, label]) => [route, label] as [string, string]
69
+ );
70
+
71
+ if (navbar?.value && navbar.value.length > 0) {
72
+ const existing_routes = new Set(base_pages.map(([route]) => route));
73
+ const additional_pages = navbar.value
74
+ .map(
75
+ ([page_name, page_path]) => [page_path, page_name] as [string, string]
76
+ )
77
+ .filter(([route]) => !existing_routes.has(route));
78
+ return [...base_pages, ...additional_pages];
79
+ }
19
80
 
20
- const set_page: ((page: string) => void) | undefined =
21
- getContext("set_lite_page");
81
+ return base_pages;
82
+ })();
22
83
  </script>
23
84
 
24
85
  <div
@@ -31,27 +92,24 @@
31
92
  style:flex-grow={!display ? "1" : "auto"}
32
93
  data-iframe-height
33
94
  >
34
- {#if pages.length > 1}
95
+ {#if show_navbar}
35
96
  <div class="nav-holder">
36
97
  <nav class="fillable" class:fill_width>
37
- {#each pages as [route, label], i}
38
- {#if is_lite}
39
- <button
40
- class:active={route === current_page}
41
- on:click={(e) => {
42
- e.preventDefault();
43
- set_page?.(route);
44
- }}
45
- >{label}
46
- </button>
47
- {:else}
48
- <a
49
- href={`${root}/${route}`}
50
- class:active={route === current_page}
51
- data-sveltekit-reload
52
- >{label}
53
- </a>
54
- {/if}
98
+ {#each effective_pages as [route, label], i}
99
+ <a
100
+ href={route.startsWith("http://") || route.startsWith("https://")
101
+ ? route
102
+ : `${root}/${route}`}
103
+ class:active={route === current_page}
104
+ data-sveltekit-reload
105
+ target={route.startsWith("http://") || route.startsWith("https://")
106
+ ? "_blank"
107
+ : "_self"}
108
+ rel={route.startsWith("http://") || route.startsWith("https://")
109
+ ? "noopener noreferrer"
110
+ : ""}
111
+ >{label}
112
+ </a>
55
113
  {/each}
56
114
  </nav>
57
115
  </div>
@@ -97,16 +155,14 @@
97
155
  margin: 0 auto;
98
156
  padding: 0 var(--size-8);
99
157
  }
100
- nav a,
101
- button {
158
+ nav a {
102
159
  padding: var(--size-1) var(--size-2);
103
160
  border-radius: var(--block-radius);
104
161
  border-width: var(--block-border-width);
105
162
  border-color: transparent;
106
163
  color: var(--body-text-color-subdued);
107
164
  }
108
- nav a.active,
109
- button.active {
165
+ nav a.active {
110
166
  color: var(--body-text-color);
111
167
  border-color: var(--block-border-color);
112
168
  background-color: var(--block-background-fill);
package/src/Render.svelte CHANGED
@@ -54,13 +54,20 @@
54
54
 
55
55
  $: {
56
56
  if (node && node.type === "form") {
57
- if (
58
- node.children?.every(
59
- (c) => typeof c.props.visible === "boolean" && !c.props.visible
60
- )
61
- ) {
62
- node.props.visible = false;
57
+ // Check if all children are invisible (false or "hidden")
58
+ const allChildrenInvisible = node.children?.every(
59
+ (c) => c.props.visible === false || c.props.visible === "hidden"
60
+ );
61
+
62
+ if (allChildrenInvisible) {
63
+ // Check if any child is "hidden" vs false
64
+ const hasHiddenChild = node.children?.some(
65
+ (c) => c.props.visible === "hidden"
66
+ );
67
+ // If any child is "hidden", form should be "hidden", otherwise false
68
+ node.props.visible = hasHiddenChild ? "hidden" : false;
63
69
  } else {
70
+ // If any child is visible, form should be visible
64
71
  node.props.visible = true;
65
72
  }
66
73
  }
@@ -94,7 +101,8 @@
94
101
  {...node.props}
95
102
  {theme_mode}
96
103
  {root}
97
- visible={typeof node.props.visible === "boolean"
104
+ visible={typeof node.props.visible === "boolean" ||
105
+ node.props.visible === "hidden"
98
106
  ? node.props.visible
99
107
  : true}
100
108
  >
@@ -17,7 +17,7 @@
17
17
  export let elem_id: string;
18
18
  export let elem_classes: string[];
19
19
  export let _id: number;
20
- export let visible: boolean;
20
+ export let visible: boolean | "hidden";
21
21
 
22
22
  const s = (id: number, p: string, v: any): CustomEvent =>
23
23
  new CustomEvent("prop_change", { detail: { id, prop: p, value: v } });
@@ -58,13 +58,13 @@
58
58
  class="highlight">import</span
59
59
  > Client{#if has_file_path}, handle_file{/if}
60
60
 
61
- client = Client(<span class="token string">"{space_id || root}"</span
61
+ client = Client(<span class="token string">"{space_id || root}"</span
62
62
  >{#if username !== null}, auth=("{username}", **password**){/if})
63
- result = client.<span class="highlight">predict</span
63
+ result = client.<span class="highlight">predict</span
64
64
  >(<!--
65
- -->{#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i}<!--
66
- -->
67
- {parameter_name
65
+ -->{#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i}<!--
66
+ -->
67
+ {parameter_name
68
68
  ? parameter_name + "="
69
69
  : ""}<span
70
70
  >{represent_value(
@@ -74,11 +74,11 @@
74
74
  )}</span
75
75
  >,{/each}<!--
76
76
 
77
- -->
78
- api_name=<span class="api-name">"/{dependency.api_name}"</span><!--
79
- -->
80
- )
81
- <span class="highlight">print</span>(result)</pre>
77
+ -->
78
+ api_name=<span class="api-name">"/{dependency.api_name}"</span><!--
79
+ -->
80
+ )
81
+ <span class="highlight">print</span>(result)</pre>
82
82
  </div>
83
83
  </code>
84
84
  </Block>
package/src/i18n.ts CHANGED
@@ -71,13 +71,14 @@ export function is_translation_metadata(obj: any): obj is I18nData {
71
71
  return result;
72
72
  }
73
73
 
74
+ export const i18n_marker = "__i18n__";
75
+
74
76
  // handles strings with embedded JSON metadata of shape "__i18n__{"key": "some.key"}"
75
77
  export function translate_if_needed(value: any): string {
76
78
  if (typeof value !== "string") {
77
79
  return value;
78
80
  }
79
81
 
80
- const i18n_marker = "__i18n__";
81
82
  const marker_index = value.indexOf(i18n_marker);
82
83
 
83
84
  if (marker_index === -1) {
@@ -154,8 +155,26 @@ export let all_common_keys: Set<string> = new Set();
154
155
  let i18n_initialized = false;
155
156
  let previous_translations: Record<string, Record<string, string>> | undefined;
156
157
 
158
+ function get_lang_from_preferred_locale(header: string): string | null {
159
+ const options = header
160
+ .split(",")
161
+ .map((value) =>
162
+ value.includes(";") ? value.split(";").slice(0, 2) : [value, 1]
163
+ );
164
+ options.sort(
165
+ (a, b) => parseFloat(b[1] as string) - parseFloat(a[1] as string)
166
+ );
167
+ for (const [lang, _] of options) {
168
+ if (available_locales.includes(lang as string)) {
169
+ return lang as string;
170
+ }
171
+ }
172
+ return null;
173
+ }
174
+
157
175
  export async function setupi18n(
158
- custom_translations?: Record<string, Record<string, string>>
176
+ custom_translations?: Record<string, Record<string, string>>,
177
+ preferred_locale?: string
159
178
  ): Promise<void> {
160
179
  const should_reinitialize =
161
180
  i18n_initialized && custom_translations !== previous_translations;
@@ -171,12 +190,16 @@ export async function setupi18n(
171
190
  custom_translations: custom_translations ?? {}
172
191
  });
173
192
 
193
+ let initial_locale: string | null = null;
174
194
  const browser_locale = getLocaleFromNavigator();
175
-
176
- let initial_locale =
177
- browser_locale && available_locales.includes(browser_locale)
178
- ? browser_locale
179
- : null;
195
+ if (preferred_locale) {
196
+ initial_locale = get_lang_from_preferred_locale(preferred_locale);
197
+ } else {
198
+ initial_locale =
199
+ browser_locale && available_locales.includes(browser_locale)
200
+ ? browser_locale
201
+ : null;
202
+ }
180
203
 
181
204
  if (!initial_locale) {
182
205
  const normalized_locale = browser_locale?.split("-")[0];