@gradio/core 0.19.3 → 0.21.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/src/Blocks.svelte CHANGED
@@ -9,7 +9,6 @@
9
9
  import type { ComponentMeta, Dependency, LayoutNode } from "./types";
10
10
  import type { UpdateTransaction } from "./init";
11
11
  import { setupi18n } from "./i18n";
12
- import { ApiDocs, ApiRecorder, Settings } from "./api_docs/";
13
12
  import type { ThemeMode, Payload } from "./types";
14
13
  import { Toast } from "@gradio/statustracker";
15
14
  import type { ToastMessage } from "@gradio/statustracker";
@@ -17,6 +16,11 @@
17
16
  import MountComponents from "./MountComponents.svelte";
18
17
  import { prefix_css } from "./css";
19
18
 
19
+ import type ApiDocs from "./api_docs/ApiDocs.svelte";
20
+ import type ApiRecorder from "./api_docs/ApiRecorder.svelte";
21
+ import type Settings from "./api_docs/Settings.svelte";
22
+ import type { ComponentType } from "svelte";
23
+
20
24
  import logo from "./images/logo.svg";
21
25
  import api_logo from "./api_docs/img/api-logo.svg";
22
26
  import settings_logo from "./api_docs/img/settings-logo.svg";
@@ -107,8 +111,39 @@
107
111
  let allow_zoom = true;
108
112
  let allow_video_trim = true;
109
113
 
110
- function set_api_docs_visible(visible: boolean): void {
114
+ // Lazy component loading state
115
+ let ApiDocs: ComponentType<ApiDocs> | null = null;
116
+ let ApiRecorder: ComponentType<ApiRecorder> | null = null;
117
+ let Settings: ComponentType<Settings> | null = null;
118
+
119
+ async function loadApiDocs(): Promise<void> {
120
+ if (!ApiDocs || !ApiRecorder) {
121
+ const api_docs_module = await import("./api_docs/ApiDocs.svelte");
122
+ const api_recorder_module = await import("./api_docs/ApiRecorder.svelte");
123
+ if (!ApiDocs) ApiDocs = api_docs_module.default;
124
+ if (!ApiRecorder) ApiRecorder = api_recorder_module.default;
125
+ }
126
+ }
127
+
128
+ async function loadApiRecorder(): Promise<void> {
129
+ if (!ApiRecorder) {
130
+ const api_recorder_module = await import("./api_docs/ApiRecorder.svelte");
131
+ ApiRecorder = api_recorder_module.default;
132
+ }
133
+ }
134
+
135
+ async function loadSettings(): Promise<void> {
136
+ if (!Settings) {
137
+ const settings_module = await import("./api_docs/Settings.svelte");
138
+ Settings = settings_module.default;
139
+ }
140
+ }
141
+
142
+ async function set_api_docs_visible(visible: boolean): Promise<void> {
111
143
  api_recorder_visible = false;
144
+ if (visible) {
145
+ await loadApiDocs();
146
+ }
112
147
  api_docs_visible = visible;
113
148
  let params = new URLSearchParams(window.location.search);
114
149
  if (visible) {
@@ -119,7 +154,10 @@
119
154
  history.replaceState(null, "", "?" + params.toString());
120
155
  }
121
156
 
122
- function set_settings_visible(visible: boolean): void {
157
+ async function set_settings_visible(visible: boolean): Promise<void> {
158
+ if (visible) {
159
+ await loadSettings();
160
+ }
123
161
  let params = new URLSearchParams(window.location.search);
124
162
  if (visible) {
125
163
  params.set("view", "settings");
@@ -831,6 +869,17 @@
831
869
  $is_screen_recording = isRecording;
832
870
  }
833
871
  );
872
+
873
+ // Load components if they should be visible on initial page load
874
+ if (api_docs_visible) {
875
+ loadApiDocs();
876
+ }
877
+ if (api_recorder_visible) {
878
+ loadApiRecorder();
879
+ }
880
+ if (settings_visible) {
881
+ loadSettings();
882
+ }
834
883
  });
835
884
 
836
885
  function screen_recording(): void {
@@ -840,11 +889,6 @@
840
889
  screen_recorder.startRecording();
841
890
  }
842
891
  }
843
-
844
- let i18n_ready = false;
845
- setupi18n(app.config?.i18n_translations ?? undefined).then(() => {
846
- i18n_ready = true;
847
- });
848
892
  </script>
849
893
 
850
894
  <svelte:head>
@@ -858,7 +902,7 @@
858
902
 
859
903
  <div class="wrap" style:min-height={app_mode ? "100%" : "auto"}>
860
904
  <div class="contain" style:flex-grow={app_mode ? "1" : "auto"}>
861
- {#if $_layout && app.config && i18n_ready}
905
+ {#if $_layout && app.config}
862
906
  <MountComponents
863
907
  rootNode={$_layout}
864
908
  {root}
@@ -880,6 +924,10 @@
880
924
  on:click={() => {
881
925
  set_api_docs_visible(!api_docs_visible);
882
926
  }}
927
+ on:mouseenter={() => {
928
+ loadApiDocs();
929
+ loadApiRecorder();
930
+ }}
883
931
  class="show-api"
884
932
  >
885
933
  {#if app.config?.mcp_server}
@@ -916,6 +964,9 @@
916
964
  on:click={() => {
917
965
  set_settings_visible(!settings_visible);
918
966
  }}
967
+ on:mouseenter={() => {
968
+ loadSettings();
969
+ }}
919
970
  class="settings"
920
971
  >
921
972
  {$_("common.settings")}
@@ -925,7 +976,7 @@
925
976
  {/if}
926
977
  </div>
927
978
 
928
- {#if api_recorder_visible}
979
+ {#if api_recorder_visible && ApiRecorder}
929
980
  <!-- TODO: fix -->
930
981
  <!-- svelte-ignore a11y-click-events-have-key-events-->
931
982
  <!-- svelte-ignore a11y-no-static-element-interactions-->
@@ -936,11 +987,11 @@
936
987
  api_recorder_visible = false;
937
988
  }}
938
989
  >
939
- <ApiRecorder {api_calls} {dependencies} />
990
+ <svelte:component this={ApiRecorder} {api_calls} {dependencies} />
940
991
  </div>
941
992
  {/if}
942
993
 
943
- {#if api_docs_visible && $_layout}
994
+ {#if api_docs_visible && $_layout && ApiDocs}
944
995
  <div class="api-docs">
945
996
  <!-- TODO: fix -->
946
997
  <!-- svelte-ignore a11y-click-events-have-key-events-->
@@ -952,12 +1003,14 @@
952
1003
  }}
953
1004
  />
954
1005
  <div class="api-docs-wrap">
955
- <ApiDocs
1006
+ <svelte:component
1007
+ this={ApiDocs}
956
1008
  root_node={$_layout}
957
1009
  on:close={(event) => {
958
1010
  set_api_docs_visible(false);
959
1011
  api_calls = [];
960
- api_recorder_visible = event.detail?.api_recorder_visible;
1012
+ api_recorder_visible = api_recorder_visible =
1013
+ event.detail?.api_recorder_visible;
961
1014
  }}
962
1015
  {dependencies}
963
1016
  {root}
@@ -970,7 +1023,7 @@
970
1023
  </div>
971
1024
  {/if}
972
1025
 
973
- {#if settings_visible && $_layout && app.config}
1026
+ {#if settings_visible && $_layout && app.config && Settings}
974
1027
  <div class="api-docs">
975
1028
  <!-- TODO: fix -->
976
1029
  <!-- svelte-ignore a11y-click-events-have-key-events-->
@@ -982,13 +1035,14 @@
982
1035
  }}
983
1036
  />
984
1037
  <div class="api-docs-wrap">
985
- <Settings
1038
+ <svelte:component
1039
+ this={Settings}
986
1040
  bind:allow_zoom
987
1041
  bind:allow_video_trim
988
- on:close={(event) => {
1042
+ on:close={() => {
989
1043
  set_settings_visible(false);
990
1044
  }}
991
- on:start_recording={(event) => {
1045
+ on:start_recording={() => {
992
1046
  screen_recording();
993
1047
  }}
994
1048
  pwa_enabled={app.config.pwa}
package/src/Render.svelte CHANGED
@@ -54,7 +54,11 @@
54
54
 
55
55
  $: {
56
56
  if (node && node.type === "form") {
57
- if (node.children?.every((c) => !c.props.visible)) {
57
+ if (
58
+ node.children?.every(
59
+ (c) => typeof c.props.visible === "boolean" && !c.props.visible
60
+ )
61
+ ) {
58
62
  node.props.visible = false;
59
63
  } else {
60
64
  node.props.visible = true;
@@ -76,34 +80,39 @@
76
80
  );
77
81
  </script>
78
82
 
79
- <RenderComponent
80
- _id={node?.id}
81
- component={node.component}
82
- bind:instance={node.instance}
83
- bind:value={node.props.value}
84
- elem_id={("elem_id" in node.props && node.props.elem_id) ||
85
- `component-${node.id}`}
86
- elem_classes={("elem_classes" in node.props && node.props.elem_classes) || []}
87
- {target}
88
- {...node.props}
89
- {theme_mode}
90
- {root}
91
- visible={typeof node.props.visible === "boolean" ? node.props.visible : true}
92
- >
93
- {#if node.children && node.children.length}
94
- {#each node.children as _node (_node.id)}
95
- <svelte:self
96
- node={_node}
97
- component={_node.component}
98
- {target}
99
- id={_node.id}
100
- {root}
101
- {theme_mode}
102
- on:destroy
103
- on:mount
104
- {max_file_size}
105
- {client}
106
- />
107
- {/each}
108
- {/if}
109
- </RenderComponent>
83
+ {#if node.component}
84
+ <RenderComponent
85
+ _id={node?.id}
86
+ component={node.component}
87
+ bind:instance={node.instance}
88
+ bind:value={node.props.value}
89
+ elem_id={("elem_id" in node.props && node.props.elem_id) ||
90
+ `component-${node.id}`}
91
+ elem_classes={("elem_classes" in node.props && node.props.elem_classes) ||
92
+ []}
93
+ {target}
94
+ {...node.props}
95
+ {theme_mode}
96
+ {root}
97
+ visible={typeof node.props.visible === "boolean"
98
+ ? node.props.visible
99
+ : true}
100
+ >
101
+ {#if node.children && node.children.length}
102
+ {#each node.children as _node (_node.id)}
103
+ <svelte:self
104
+ node={_node}
105
+ component={_node.component}
106
+ {target}
107
+ id={_node.id}
108
+ {root}
109
+ {theme_mode}
110
+ on:destroy
111
+ on:mount
112
+ {max_file_size}
113
+ {client}
114
+ />
115
+ {/each}
116
+ {/if}
117
+ </RenderComponent>
118
+ {/if}
@@ -49,7 +49,7 @@
49
49
  return ProxiedMyClass;
50
50
  }
51
51
 
52
- const _component = wrap(component);
52
+ let _component = wrap(component);
53
53
 
54
54
  const supported_props = [
55
55
  "description",
@@ -72,20 +72,19 @@
72
72
  $: value = translate_if_needed(value);
73
73
  </script>
74
74
 
75
- <!-- {#if visible} -->
76
- <svelte:component
77
- this={_component}
78
- bind:this={instance}
79
- bind:value
80
- on:prop_change
81
- {elem_id}
82
- {elem_classes}
83
- {target}
84
- {visible}
85
- {...$$restProps}
86
- {theme_mode}
87
- {root}
88
- >
89
- <slot />
90
- </svelte:component>
91
- <!-- {/if} -->
75
+ {#if visible}
76
+ <svelte:component
77
+ this={_component}
78
+ bind:this={instance}
79
+ bind:value
80
+ on:prop_change
81
+ {elem_id}
82
+ {elem_classes}
83
+ {target}
84
+ {...$$restProps}
85
+ {theme_mode}
86
+ {root}
87
+ >
88
+ <slot />
89
+ </svelte:component>
90
+ {/if}
package/src/i18n.test.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  beforeEach,
8
8
  afterEach
9
9
  } from "vitest";
10
- import { process_langs } from "./i18n";
10
+ import { Lang, process_langs } from "./i18n";
11
11
  import languagesByAnyCode from "wikidata-lang/indexes/by_any_code";
12
12
  import BCP47 from "./lang/BCP47_codes";
13
13
  import {
@@ -36,22 +36,31 @@ describe("i18n", () => {
36
36
  test("languages are loaded correctly", () => {
37
37
  const langs = process_langs();
38
38
  assert.ok(langs.en);
39
- assert.ok(langs.en.common);
39
+ assert.ok((langs.en as { type: "static"; data: Lang }).data.common);
40
40
  });
41
41
 
42
- test("language codes follow the correct format", () => {
42
+ test("language codes follow the correct format", async () => {
43
43
  const langs = Object.entries(process_langs());
44
44
 
45
- langs.forEach(([code, translation]) => {
46
- const BCP47_REGEX = /^.{2}-.{2}$/;
45
+ await Promise.all(
46
+ langs.map(async ([code, translation]) => {
47
+ const BCP47_REGEX = /^.{2}-.{2}$/;
47
48
 
48
- if (BCP47_REGEX.test(code)) {
49
- assert.ok(BCP47.includes(code));
50
- } else {
51
- assert.exists(languagesByAnyCode[code]);
52
- }
53
- assert.ok(translation.common);
54
- });
49
+ if (BCP47_REGEX.test(code)) {
50
+ assert.ok(BCP47.includes(code));
51
+ } else {
52
+ assert.exists(languagesByAnyCode[code]);
53
+ }
54
+
55
+ let data: Lang;
56
+ if (translation.type === "lazy") {
57
+ data = await translation.data();
58
+ } else {
59
+ data = translation.data;
60
+ }
61
+ assert.ok(data.common);
62
+ })
63
+ );
55
64
  });
56
65
 
57
66
  describe("basic functions", () => {
@@ -106,7 +115,7 @@ describe("i18n", () => {
106
115
  }
107
116
  };
108
117
 
109
- load_translations(custom_translations);
118
+ load_translations({ processed_langs: {}, custom_translations });
110
119
 
111
120
  expect(mockAddMessages).toHaveBeenCalledTimes(2);
112
121
  expect(mockAddMessages).toHaveBeenCalledWith(
package/src/i18n.ts CHANGED
@@ -1,21 +1,66 @@
1
- import { addMessages, init, getLocaleFromNavigator, locale } from "svelte-i18n";
1
+ import {
2
+ addMessages,
3
+ init,
4
+ getLocaleFromNavigator,
5
+ locale,
6
+ register,
7
+ waitLocale
8
+ } from "svelte-i18n";
2
9
  import { formatter } from "./gradio_helper";
3
10
 
4
- const langs = import.meta.glob("./lang/*.json", {
5
- eager: true
6
- });
11
+ const lang_map = {
12
+ ar: "العربية",
13
+ ca: "Català",
14
+ ckb: "کوردی",
15
+ de: "Deutsch",
16
+ en: "English",
17
+ es: "Español",
18
+ eu: "Euskara",
19
+ fa: "فارسی",
20
+ fi: "Suomi",
21
+ fr: "Français",
22
+ he: "עברית",
23
+ hi: "हिंदी",
24
+ ja: "日本語",
25
+ ko: "한국어",
26
+ lt: "Lietuvių",
27
+ nb: "Norsk bokmål",
28
+ nl: "Nederlands",
29
+ pl: "Polski",
30
+ "pt-BR": "Português do Brasil",
31
+ pt: "Português",
32
+ ro: "Română",
33
+ ru: "Русский",
34
+ sv: "Svenska",
35
+ ta: "தமிழ்",
36
+ th: "ภาษาไทย",
37
+ tr: "Türkçe",
38
+ uk: "Українська",
39
+ ur: "اردو",
40
+ uz: "O'zbek",
41
+ "zh-CN": "简体中文",
42
+ "zh-TW": "繁體中文"
43
+ };
44
+
45
+ const langs = import.meta.glob("./lang/*.json");
46
+ import en from "./lang/en.json";
7
47
 
8
48
  export interface I18nData {
9
49
  __type__: "translation_metadata";
10
50
  key: string;
11
51
  }
12
52
 
53
+ export type Lang = {
54
+ [key: string]: Record<string, string> | string;
55
+ };
56
+
13
57
  export interface LangsRecord {
14
- [lang: string]: any;
58
+ [lang: string]:
59
+ | { type: "lazy"; data: () => Promise<Lang> }
60
+ | { type: "static"; data: Lang };
15
61
  }
16
62
 
17
63
  export function is_translation_metadata(obj: any): obj is I18nData {
18
- console.log(obj);
19
64
  const result =
20
65
  obj &&
21
66
  typeof obj === "object" &&
@@ -83,12 +128,17 @@ export function translate_if_needed(value: any): string {
83
128
  }
84
129
 
85
130
  export function process_langs(): LangsRecord {
86
- return Object.fromEntries(
87
- Object.entries(langs).map(([path, module]) => [
131
+ const lazy_langs = Object.fromEntries(
132
+ Object.entries(langs).map(([path, mod]) => [
88
133
  path.split("/").pop()!.split(".")[0],
89
- (module as Record<string, any>).default
134
+ { type: "lazy", data: mod }
90
135
  ])
91
136
  );
137
+
138
+ return {
139
+ ...lazy_langs,
140
+ en: { type: "static", data: en }
141
+ };
92
142
  }
93
143
 
94
144
  const processed_langs = process_langs();
@@ -96,7 +146,7 @@ const available_locales = Object.keys(processed_langs);
96
146
 
97
147
  export const language_choices: [string, string][] = Object.entries(
98
148
  processed_langs
99
- ).map(([code, data]) => [data._name || code, code]);
149
+ ).map(([code]) => [lang_map[code as keyof typeof lang_map] || code, code]);
100
150
 
101
151
  export let all_common_keys: Set<string> = new Set();
102
152
 
@@ -116,8 +166,8 @@ export async function setupi18n(
116
166
  previous_translations = custom_translations;
117
167
 
118
168
  load_translations({
119
- ...processed_langs,
120
- ...(custom_translations ?? {})
169
+ processed_langs,
170
+ custom_translations: custom_translations ?? {}
121
171
  });
122
172
 
123
173
  const browser_locale = getLocaleFromNavigator();
@@ -140,20 +190,6 @@ export async function setupi18n(
140
190
  initialLocale: initial_locale
141
191
  });
142
192
 
143
- for (const lang_code in processed_langs) {
144
- if (
145
- processed_langs[lang_code] &&
146
- typeof processed_langs[lang_code] === "object" &&
147
- processed_langs[lang_code].common &&
148
- typeof processed_langs[lang_code].common === "object"
149
- ) {
150
- const common_ns = processed_langs[lang_code].common;
151
- for (const key in common_ns) {
152
- all_common_keys.add(`common.${key}`);
153
- }
154
- }
155
- }
156
-
157
193
  i18n_initialized = true;
158
194
  }
159
195
 
@@ -175,16 +211,28 @@ export function get_initial_locale(
175
211
  return fallback_locale;
176
212
  }
177
213
 
178
- export function load_translations(
179
- translations: LangsRecord | null | undefined
180
- ): void {
214
+ export function load_translations(translations: {
215
+ processed_langs: LangsRecord;
216
+ custom_translations: Record<string, Record<string, string>>;
217
+ }): void {
181
218
  if (!translations) {
182
219
  return;
183
220
  }
184
221
 
185
222
  try {
186
- for (const lang in translations) {
187
- addMessages(lang, translations[lang]);
223
+ for (const lang in translations.custom_translations) {
224
+ addMessages(lang, translations.custom_translations[lang]);
225
+ }
226
+
227
+ for (const lang in translations.processed_langs) {
228
+ if (
229
+ lang === "en" &&
230
+ translations.processed_langs[lang].type === "static"
231
+ ) {
232
+ addMessages(lang, en);
233
+ } else if (translations.processed_langs[lang].type === "lazy") {
234
+ register(lang, translations.processed_langs[lang].data);
235
+ }
188
236
  }
189
237
  } catch (e) {
190
238
  console.error("Error loading translations:", e);