@gradio/core 0.29.2 → 1.0.0-dev.3

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 (107) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/src/Blocks.svelte +518 -999
  5. package/dist/src/Blocks.svelte.d.ts +31 -46
  6. package/dist/src/Embed.svelte +82 -55
  7. package/dist/src/Embed.svelte.d.ts +39 -30
  8. package/dist/src/Login.svelte +33 -29
  9. package/dist/src/Login.svelte.d.ts +21 -19
  10. package/dist/src/MountComponents.svelte +19 -25
  11. package/dist/src/MountComponents.svelte.d.ts +5 -28
  12. package/dist/src/{init.d.ts → _init.d.ts} +5 -4
  13. package/dist/src/{init.js → _init.js} +31 -108
  14. package/dist/src/api_docs/ApiBanner.svelte +12 -8
  15. package/dist/src/api_docs/ApiBanner.svelte.d.ts +22 -20
  16. package/dist/src/api_docs/ApiDocs.svelte +338 -246
  17. package/dist/src/api_docs/ApiDocs.svelte.d.ts +26 -24
  18. package/dist/src/api_docs/ApiRecorder.svelte +9 -3
  19. package/dist/src/api_docs/ApiRecorder.svelte.d.ts +19 -17
  20. package/dist/src/api_docs/CodeSnippet.svelte +60 -30
  21. package/dist/src/api_docs/CodeSnippet.svelte.d.ts +27 -24
  22. package/dist/src/api_docs/CopyButton.svelte +69 -13
  23. package/dist/src/api_docs/CopyButton.svelte.d.ts +18 -16
  24. package/dist/src/api_docs/CopyMarkdown.svelte +734 -0
  25. package/dist/src/api_docs/CopyMarkdown.svelte.d.ts +37 -0
  26. package/dist/src/api_docs/EndpointDetail.svelte +8 -6
  27. package/dist/src/api_docs/EndpointDetail.svelte.d.ts +20 -18
  28. package/dist/src/api_docs/IconArrowUpRight.svelte +34 -0
  29. package/dist/src/api_docs/IconArrowUpRight.svelte.d.ts +20 -0
  30. package/dist/src/api_docs/IconCaret.svelte +39 -0
  31. package/dist/src/api_docs/IconCaret.svelte.d.ts +20 -0
  32. package/dist/src/api_docs/IconHuggingChat.svelte +62 -0
  33. package/dist/src/api_docs/IconHuggingChat.svelte.d.ts +20 -0
  34. package/dist/src/api_docs/InputPayload.svelte +17 -11
  35. package/dist/src/api_docs/InputPayload.svelte.d.ts +25 -23
  36. package/dist/src/api_docs/InstallSnippet.svelte +9 -6
  37. package/dist/src/api_docs/InstallSnippet.svelte.d.ts +18 -16
  38. package/dist/src/api_docs/MCPSnippet.svelte +119 -99
  39. package/dist/src/api_docs/MCPSnippet.svelte.d.ts +59 -58
  40. package/dist/src/api_docs/NoApi.svelte +8 -5
  41. package/dist/src/api_docs/NoApi.svelte.d.ts +20 -18
  42. package/dist/src/api_docs/ParametersSnippet.svelte +8 -6
  43. package/dist/src/api_docs/ParametersSnippet.svelte.d.ts +21 -19
  44. package/dist/src/api_docs/RecordingSnippet.svelte +124 -110
  45. package/dist/src/api_docs/RecordingSnippet.svelte.d.ts +24 -22
  46. package/dist/src/api_docs/ResponseSnippet.svelte +7 -5
  47. package/dist/src/api_docs/ResponseSnippet.svelte.d.ts +21 -19
  48. package/dist/src/api_docs/Settings.svelte +73 -62
  49. package/dist/src/api_docs/Settings.svelte.d.ts +25 -23
  50. package/dist/src/api_docs/SettingsBanner.svelte +11 -8
  51. package/dist/src/api_docs/SettingsBanner.svelte.d.ts +20 -18
  52. package/dist/src/api_docs/TryButton.svelte +5 -3
  53. package/dist/src/api_docs/TryButton.svelte.d.ts +19 -17
  54. package/dist/src/api_docs/img/IconCheck.svelte +33 -0
  55. package/dist/src/api_docs/img/IconCheck.svelte.d.ts +26 -0
  56. package/dist/src/api_docs/img/IconCopy.svelte +40 -0
  57. package/dist/src/api_docs/img/IconCopy.svelte.d.ts +26 -0
  58. package/dist/src/api_docs/img/clear.svelte.d.ts +22 -21
  59. package/dist/src/dependency.d.ts +142 -0
  60. package/dist/src/dependency.js +653 -0
  61. package/dist/src/init.svelte.d.ts +78 -0
  62. package/dist/src/init.svelte.js +469 -0
  63. package/dist/src/init_utils.d.ts +32 -0
  64. package/dist/src/init_utils.js +73 -0
  65. package/dist/src/lang/en.json +10 -1
  66. package/dist/src/lang/get_lang_names.js +0 -3
  67. package/dist/src/lang/ru.json +10 -1
  68. package/dist/src/stores.d.ts +0 -21
  69. package/dist/src/stories/I18nMultiLanguageTestComponent.svelte +5 -3
  70. package/dist/src/stories/I18nMultiLanguageTestComponent.svelte.d.ts +16 -14
  71. package/dist/src/stories/I18nTestSetup.svelte +14 -10
  72. package/dist/src/stories/I18nTestSetup.svelte.d.ts +18 -16
  73. package/dist/src/types.d.ts +29 -25
  74. package/index.ts +1 -1
  75. package/package.json +59 -59
  76. package/src/Blocks.svelte +344 -1059
  77. package/src/MountComponents.svelte +17 -27
  78. package/src/{init.ts → _init.ts} +49 -126
  79. package/src/api_docs/ApiDocs.svelte +67 -62
  80. package/src/api_docs/ApiRecorder.svelte +3 -0
  81. package/src/api_docs/CodeSnippet.svelte +20 -5
  82. package/src/api_docs/CopyButton.svelte +61 -7
  83. package/src/api_docs/CopyMarkdown.svelte +734 -0
  84. package/src/api_docs/IconArrowUpRight.svelte +34 -0
  85. package/src/api_docs/IconCaret.svelte +39 -0
  86. package/src/api_docs/IconHuggingChat.svelte +62 -0
  87. package/src/api_docs/MCPSnippet.svelte +24 -46
  88. package/src/api_docs/NoApi.svelte +1 -1
  89. package/src/api_docs/ParametersSnippet.svelte +1 -1
  90. package/src/api_docs/ResponseSnippet.svelte +1 -1
  91. package/src/api_docs/Settings.svelte +11 -11
  92. package/src/api_docs/img/IconCheck.svelte +33 -0
  93. package/src/api_docs/img/IconCopy.svelte +40 -0
  94. package/src/dependency.ts +880 -0
  95. package/src/init.svelte.ts +717 -0
  96. package/src/init_utils.ts +99 -0
  97. package/src/lang/en.json +10 -1
  98. package/src/lang/get_lang_names.js +0 -3
  99. package/src/lang/ru.json +10 -1
  100. package/src/stores.ts +22 -22
  101. package/src/types.ts +54 -43
  102. package/dist/src/Render.svelte +0 -105
  103. package/dist/src/Render.svelte.d.ts +0 -31
  104. package/dist/src/RenderComponent.svelte +0 -72
  105. package/dist/src/RenderComponent.svelte.d.ts +0 -33
  106. package/src/Render.svelte +0 -126
  107. package/src/RenderComponent.svelte +0 -91
package/src/Blocks.svelte CHANGED
@@ -1,129 +1,233 @@
1
1
  <script lang="ts">
2
- import { tick, onMount } from "svelte";
2
+ import { tick, onMount, setContext, settled, untrack } from "svelte";
3
3
  import { _ } from "svelte-i18n";
4
4
  import { Client } from "@gradio/client";
5
5
  import { writable } from "svelte/store";
6
6
 
7
- import type { LoadingStatus, LoadingStatusCollection } from "./stores";
7
+ // import type { LoadingStatus, LoadingStatusCollection } from "./stores";
8
8
 
9
- import type { ComponentMeta, Dependency, LayoutNode } from "./types";
10
- import type { UpdateTransaction } from "./init";
9
+ import type {
10
+ ComponentMeta,
11
+ Dependency as IDependency,
12
+ LayoutNode
13
+ } from "./types";
14
+ // import type { UpdateTransaction } from "./_init";
11
15
  import { setupi18n } from "./i18n";
12
16
  import type { ThemeMode, Payload } from "./types";
13
17
  import { Toast } from "@gradio/statustracker";
14
18
  import type { ToastMessage } from "@gradio/statustracker";
15
- import type { ShareData, ValueData } from "@gradio/utils";
19
+ import { type ShareData, type ValueData, GRADIO_ROOT } from "@gradio/utils";
20
+
16
21
  import MountComponents from "./MountComponents.svelte";
17
22
  import { prefix_css } from "./css";
23
+ import { reactive_formatter } from "./gradio_helper";
18
24
 
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";
25
+ import type ApiDocsInterface from "./api_docs/ApiDocs.svelte";
26
+ import type ApiRecorderInterface from "./api_docs/ApiRecorder.svelte";
27
+ import type SettingsInterface from "./api_docs/Settings.svelte";
28
+ // import type { ComponentType } from "svelte";
23
29
 
24
30
  import logo from "./images/logo.svg";
25
31
  import api_logo from "./api_docs/img/api-logo.svg";
26
32
  import settings_logo from "./api_docs/img/settings-logo.svg";
27
33
  import record_stop from "./api_docs/img/record-stop.svg";
28
- import { create_components, AsyncFunction } from "./init";
29
- import type {
30
- LogMessage,
31
- RenderMessage,
32
- StatusMessage
33
- } from "@gradio/client";
34
+ import { AppTree } from "./init.svelte";
35
+ // import type {
36
+ // LogMessage,
37
+ // RenderMessage,
38
+ // StatusMessage,
39
+ // } from "@gradio/client";
34
40
  import * as screen_recorder from "./screen_recorder";
35
41
 
36
- export let root: string;
37
- export let components: ComponentMeta[];
38
- export let layout: LayoutNode;
39
- export let dependencies: Dependency[];
40
- export let title = "Gradio";
41
- export let target: HTMLElement;
42
- export let autoscroll: boolean;
43
- export let show_api = true;
44
- export let show_footer = true;
45
- export let control_page_title = false;
46
- export let app_mode: boolean;
47
- export let theme_mode: ThemeMode;
48
- export let app: Awaited<ReturnType<typeof Client.connect>>;
49
- export let space_id: string | null;
50
- export let version: string;
51
- export let js: string | null;
52
- export let fill_height = false;
53
- export let ready: boolean;
54
- export let username: string | null;
55
- export let api_prefix = "";
56
- export let max_file_size: number | undefined = undefined;
57
- export let initial_layout: ComponentMeta | undefined = undefined;
58
- export let css: string | null | undefined = null;
59
- export let vibe_mode = false;
60
- let broken_connection = false;
42
+ import { DependencyManager } from "./dependency";
61
43
 
62
44
  let {
63
- layout: _layout,
64
- targets,
65
- update_value,
66
- get_data,
67
- modify_stream,
68
- get_stream_state,
69
- set_time_limit,
70
- loading_status,
71
- scheduled_updates,
72
- create_layout,
73
- rerender_layout,
74
- value_change
75
- } = create_components({
76
- initial_layout
45
+ root,
46
+ components,
47
+ layout,
48
+ dependencies,
49
+ title,
50
+ target,
51
+ autoscroll,
52
+ footer_links,
53
+ control_page_title,
54
+ app_mode,
55
+ theme_mode,
56
+ app,
57
+ space_id,
58
+ version,
59
+ js,
60
+ fill_height,
61
+ username,
62
+ api_prefix,
63
+ max_file_size,
64
+ initial_layout,
65
+ css,
66
+ vibe_mode,
67
+ search_params,
68
+ render_complete = false,
69
+ ready = $bindable(false),
70
+ reload_count = $bindable(0)
71
+ }: {
72
+ root: string;
73
+ components: ComponentMeta[];
74
+ layout: LayoutNode;
75
+ dependencies: IDependency[];
76
+ title: string;
77
+ target: HTMLElement;
78
+ autoscroll: boolean;
79
+ footer_links: string[];
80
+ control_page_title: boolean;
81
+ app_mode: boolean;
82
+ theme_mode: ThemeMode;
83
+ app: Awaited<ReturnType<typeof Client.connect>>;
84
+ space_id: string | null;
85
+ version: string;
86
+ js: string | null;
87
+ fill_height: boolean;
88
+ username: string | null;
89
+ api_prefix: string;
90
+ max_file_size: number | undefined;
91
+ initial_layout: ComponentMeta | undefined;
92
+ css: string | null | undefined;
93
+ vibe_mode: boolean;
94
+ search_params: URLSearchParams;
95
+ render_complete: boolean;
96
+ ready: boolean;
97
+ reload_count: number;
98
+ } = $props();
99
+
100
+ components.forEach((comp) => {
101
+ if (!comp.props.i18n) {
102
+ comp.props.i18n = $reactive_formatter;
103
+ }
77
104
  });
78
105
 
79
- $: components, layout, dependencies, root, app, fill_height, target, run();
106
+ let app_tree = new AppTree(
107
+ components,
108
+ layout,
109
+ dependencies,
110
+ {
111
+ root,
112
+ theme: theme_mode,
113
+ version,
114
+ api_prefix,
115
+ max_file_size,
116
+ autoscroll
117
+ },
118
+ app,
119
+ $reactive_formatter
120
+ );
121
+
122
+ setContext(GRADIO_ROOT, {
123
+ register: app_tree.register_component.bind(app_tree),
124
+ dispatcher: gradio_event_dispatcher
125
+ });
80
126
 
81
- $: {
82
- ready = !!$_layout;
83
- }
127
+ let messages: (ToastMessage & { fn_index: number })[] = $state([]);
84
128
 
85
- let old_dependencies = dependencies;
86
- $: if (
87
- dependencies !== old_dependencies &&
88
- render_complete &&
89
- !layout_creating
90
- ) {
91
- // re-run load triggers in SSR mode when page changes
92
- handle_load_triggers();
93
- old_dependencies = dependencies;
129
+ function gradio_event_dispatcher(
130
+ id: number,
131
+ event: string,
132
+ data: unknown
133
+ ): void {
134
+ if (event === "share") {
135
+ const { title, description } = data as ShareData;
136
+ // trigger_share(title, description);
137
+ // TODO: lets combine all of the into a log type with levels
138
+ } else if (event === "error") {
139
+ new_message("Error", data, -1, event, 10, true);
140
+ } else if (event === "warning") {
141
+ new_message("Warning", data, -1, event, 10, true);
142
+ } else if (event === "info") {
143
+ new_message("Info", data, -1, event, 10, true);
144
+ } else if (event == "clear_status") {
145
+ app_tree.update_state(
146
+ id,
147
+ {
148
+ loading_status: {}
149
+ },
150
+ false
151
+ );
152
+ dep_manager.clear_loading_status(id);
153
+ // TODO: the loading_status store should handle this via a method
154
+ // update_status(id, "complete", data);
155
+ } else if (event == "close_stream") {
156
+ dep_manager.close_stream(id);
157
+ } else {
158
+ // Tabs are a bit weird. The Tabs component dispatches 'select' events
159
+ // but the target id corresponds to the child Tab component that was selected.
160
+ // So the id we get from the dispatcher belongs to the Tabs,
161
+ // so we need to pull out the correct id here.
162
+ if (event === "select" && id in app_tree.initial_tabs) {
163
+ // this is the id of the selected tab
164
+ id = data.id;
165
+ }
166
+ dep_manager.dispatch({
167
+ type: "event",
168
+ event_name: event,
169
+ target_id: id,
170
+ event_data: data
171
+ });
172
+ }
94
173
  }
95
174
 
96
- let vibe_editor_width = 350;
175
+ let api_calls: Payload[] = $state([]);
176
+ // We need a callback to add to api_calls from the DependencyManager
177
+ // We can't update a state variable from inside the DependencyManager because
178
+ // svelte won't see it and won't update the UI.
179
+ let add_to_api_calls = (payload: Payload): void => {
180
+ api_calls = [...api_calls, payload];
181
+ };
97
182
 
98
- async function run(): Promise<void> {
99
- await setupi18n(app.config?.i18n_translations || undefined);
100
-
101
- layout_creating = true;
102
- await create_layout({
103
- components,
104
- layout,
105
- dependencies,
106
- root: root + api_prefix,
107
- app,
108
- options: {
109
- fill_height
110
- }
183
+ let dep_manager = new DependencyManager(
184
+ dependencies,
185
+ app,
186
+ app_tree.update_state.bind(app_tree),
187
+ app_tree.get_state.bind(app_tree),
188
+ app_tree.rerender.bind(app_tree),
189
+ new_message,
190
+ add_to_api_calls
191
+ );
192
+
193
+ $effect(() => {
194
+ reload_count;
195
+ untrack(() => {
196
+ app_tree.reload(components, layout, dependencies, {
197
+ root,
198
+ theme: theme_mode,
199
+ version,
200
+ api_prefix,
201
+ max_file_size,
202
+ autoscroll
203
+ });
204
+ dep_manager.reload(
205
+ dependencies,
206
+ app_tree.update_state.bind(app_tree),
207
+ app_tree.get_state.bind(app_tree),
208
+ app_tree.rerender.bind(app_tree),
209
+ app
210
+ );
111
211
  });
112
- layout_creating = false;
113
- }
212
+ });
114
213
 
115
- export let search_params: URLSearchParams;
116
- let api_docs_visible = search_params.get("view") === "api" && show_api;
117
- let settings_visible = search_params.get("view") === "settings";
118
- let api_recorder_visible =
119
- search_params.get("view") === "api-recorder" && show_api;
214
+ let vibe_editor_width = 350;
215
+
216
+ // export let
217
+ let api_docs_visible = $derived(
218
+ search_params.get("view") === "api" && footer_links.includes("api")
219
+ );
220
+ let settings_visible = $derived(search_params.get("view") === "settings");
221
+ let api_recorder_visible = $derived(
222
+ search_params.get("view") === "api-recorder" && footer_links.includes("api")
223
+ );
120
224
  let allow_zoom = true;
121
225
  let allow_video_trim = true;
122
226
 
123
227
  // Lazy component loading state
124
- let ApiDocs: ComponentType<ApiDocs> | null = null;
125
- let ApiRecorder: ComponentType<ApiRecorder> | null = null;
126
- let Settings: ComponentType<Settings> | null = null;
228
+ let ApiDocs: ComponentType<ApiDocsInterface> | null = null;
229
+ let ApiRecorder: ComponentType<ApiRecorderInterface> | null = null;
230
+ let Settings: ComponentType<SettingsInterface> | null = null;
127
231
  let VibeEditor: ComponentType | null = null;
128
232
 
129
233
  async function loadApiDocs(): Promise<void> {
@@ -185,104 +289,19 @@
185
289
  settings_visible = !settings_visible;
186
290
  }
187
291
 
188
- let api_calls: Payload[] = [];
189
-
190
292
  let layout_creating = false;
191
- export let render_complete = false;
192
-
193
- async function handle_update(data: any, fn_index: number): Promise<void> {
194
- const dep = dependencies.find((dep) => dep.id === fn_index);
195
- const input_type = components.find(
196
- (comp) => comp.id === dep?.inputs[0]
197
- )?.type;
198
- if (allow_zoom && dep && input_type !== "dataset") {
199
- if (dep && dep.inputs && dep.inputs.length > 0 && $is_screen_recording) {
200
- screen_recorder.zoom(true, dep.inputs, 1.0);
201
- }
293
+ //
202
294
 
203
- if (
204
- dep &&
205
- dep.outputs &&
206
- dep.outputs.length > 0 &&
207
- $is_screen_recording
208
- ) {
209
- screen_recorder.zoom(false, dep.outputs, 2.0);
210
- }
211
- }
212
-
213
- if (!dep) {
214
- return;
215
- }
216
- const outputs = dep.outputs;
217
- const meta_updates = data?.map((value: any, i: number) => {
218
- return {
219
- id: outputs[i],
220
- prop: "value_is_output",
221
- value: true
222
- };
223
- });
224
-
225
- update_value(meta_updates);
226
-
227
- await tick();
228
-
229
- const updates: UpdateTransaction[] = [];
230
-
231
- data?.forEach((value: any, i: number) => {
232
- if (
233
- typeof value === "object" &&
234
- value !== null &&
235
- value.__type__ === "update"
236
- ) {
237
- for (const [update_key, update_value] of Object.entries(value)) {
238
- if (update_key === "__type__") {
239
- continue;
240
- } else {
241
- updates.push({
242
- id: outputs[i],
243
- prop: update_key,
244
- value: update_value
245
- });
246
- }
247
- }
248
- } else {
249
- updates.push({
250
- id: outputs[i],
251
- prop: "value",
252
- value
253
- });
254
- }
255
- });
256
- update_value(updates);
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
-
271
- await tick();
272
- }
273
-
274
- let submit_map: Map<number, ReturnType<typeof app.submit>> = new Map();
275
-
276
- let messages: (ToastMessage & { fn_index: number })[] = [];
277
295
  function new_message(
278
296
  title: string,
279
297
  message: string,
280
298
  fn_index: number,
281
299
  type: ToastMessage["type"],
282
300
  duration: number | null = 10,
283
- visible = true
284
- ): ToastMessage & { fn_index: number } {
285
- return {
301
+ visible = false
302
+ ): void {
303
+ if (!visible) return;
304
+ messages.push({
286
305
  title,
287
306
  message,
288
307
  fn_index,
@@ -290,15 +309,7 @@
290
309
  id: ++_error_id,
291
310
  duration,
292
311
  visible
293
- };
294
- }
295
-
296
- export function add_new_message(
297
- title: string,
298
- message: string,
299
- type: ToastMessage["type"]
300
- ): void {
301
- messages = [new_message(title, message, -1, type), ...messages];
312
+ });
302
313
  }
303
314
 
304
315
  let _error_id = -1;
@@ -323,743 +334,9 @@
323
334
  let inputs_waiting: number[] = [];
324
335
 
325
336
  // as state updates are not synchronous, we need to ensure updates are flushed before triggering any requests
326
- function wait_then_trigger_api_call(
327
- dep_index: number,
328
- trigger_id: number | null = null,
329
- event_data: unknown = null
330
- ): void {
331
- let _unsub = (): void => {};
332
- function unsub(): void {
333
- _unsub();
334
- }
335
- if ($scheduled_updates) {
336
- _unsub = scheduled_updates.subscribe((updating) => {
337
- if (!updating) {
338
- tick().then(() => {
339
- trigger_api_call(dep_index, trigger_id, event_data);
340
- unsub();
341
- });
342
- }
343
- });
344
- } else {
345
- trigger_api_call(dep_index, trigger_id, event_data);
346
- }
347
- }
348
-
349
- async function get_component_value_or_event_data(
350
- component_id: number,
351
- trigger_id: number | null,
352
- event_data: unknown
353
- ): Promise<any> {
354
- if (
355
- component_id === trigger_id &&
356
- event_data &&
357
- (event_data as ValueData).is_value_data === true
358
- ) {
359
- // @ts-ignore
360
- return event_data.value;
361
- }
362
- return get_data(component_id);
363
- }
364
-
365
- async function trigger_api_call(
366
- dep_index: number,
367
- trigger_id: number | null = null,
368
- event_data: unknown = null
369
- ): Promise<void> {
370
- const _dep = dependencies.find((dep) => dep.id === dep_index);
371
- if (_dep === undefined) {
372
- return;
373
- }
374
- const dep = _dep;
375
- if (inputs_waiting.length > 0) {
376
- for (const input of inputs_waiting) {
377
- if (dep.inputs.includes(input)) {
378
- add_new_message("Warning", WAITING_FOR_INPUTS_MESSAGE, "warning");
379
- return;
380
- }
381
- }
382
- }
383
- const current_status = loading_status.get_status_for_fn(dep_index);
384
- messages = messages.filter(({ fn_index }) => fn_index !== dep_index);
385
- if (current_status === "pending" || current_status === "generating") {
386
- dep.pending_request = true;
387
- }
388
-
389
- let payload: Payload = {
390
- fn_index: dep_index,
391
- data: await Promise.all(
392
- dep.inputs.map((id) =>
393
- get_component_value_or_event_data(id, trigger_id, event_data)
394
- )
395
- ),
396
- event_data: dep.collects_event_data ? event_data : null,
397
- trigger_id: trigger_id
398
- };
399
-
400
- if (dep.frontend_fn && typeof dep.frontend_fn !== "boolean") {
401
- dep
402
- .frontend_fn(
403
- payload.data.concat(
404
- await Promise.all(dep.outputs.map((id) => get_data(id)))
405
- )
406
- )
407
- .then((v: unknown[]) => {
408
- if (dep.backend_fn) {
409
- payload.data = v;
410
- trigger_prediction(dep, payload);
411
- } else {
412
- handle_update(v, dep_index);
413
- }
414
- });
415
- } else if (dep.types.cancel && dep.cancels) {
416
- await Promise.all(
417
- dep.cancels.map(async (fn_index) => {
418
- const submission = submit_map.get(fn_index);
419
- submission?.cancel();
420
- return submission;
421
- })
422
- );
423
- } else {
424
- if (dep.backend_fn) {
425
- if (dep.js_implementation) {
426
- let js_fn = new AsyncFunction(
427
- `let result = await (${dep.js_implementation})(...arguments);
428
- return (!Array.isArray(result)) ? [result] : result;`
429
- );
430
- js_fn(...payload.data)
431
- .then((js_result) => {
432
- handle_update(js_result, dep_index);
433
- payload.js_implementation = true;
434
- })
435
- .catch((error) => {
436
- console.error(error);
437
- payload.js_implementation = false;
438
- });
439
- }
440
- trigger_prediction(dep, payload);
441
- }
442
- }
443
-
444
- function trigger_prediction(dep: Dependency, payload: Payload): void {
445
- if (dep.trigger_mode === "once") {
446
- if (!dep.pending_request)
447
- make_prediction(payload, dep.connection == "stream");
448
- } else if (dep.trigger_mode === "multiple") {
449
- make_prediction(payload, dep.connection == "stream");
450
- } else if (dep.trigger_mode === "always_last") {
451
- if (!dep.pending_request) {
452
- make_prediction(payload, dep.connection == "stream");
453
- } else {
454
- dep.final_event = payload;
455
- }
456
- }
457
- }
458
-
459
- async function reconnect(): Promise<void> {
460
- const connection_status = await app.reconnect();
461
- if (connection_status === "broken") {
462
- setTimeout(reconnect, 1000);
463
- } else if (connection_status === "changed") {
464
- broken_connection = false;
465
- messages = [
466
- new_message(
467
- "Changed Connection",
468
- CHANGED_CONNECTION_MESSAGE,
469
- -1,
470
- "info",
471
- 3,
472
- true
473
- ),
474
- ...messages.map((m) =>
475
- m.message === LOST_CONNECTION_MESSAGE ? { ...m, visible: false } : m
476
- )
477
- ];
478
- } else if (connection_status === "connected") {
479
- broken_connection = false;
480
- messages = [
481
- new_message(
482
- "Reconnected",
483
- RECONNECTION_MESSAGE,
484
- -1,
485
- "success",
486
- null,
487
- true
488
- ),
489
- ...messages.map((m) =>
490
- m.message === LOST_CONNECTION_MESSAGE ? { ...m, visible: false } : m
491
- )
492
- ];
493
- }
494
- }
495
-
496
- async function make_prediction(
497
- payload: Payload,
498
- streaming = false
499
- ): Promise<void> {
500
- if (allow_video_trim) {
501
- screen_recorder.markRemoveSegmentStart();
502
- }
503
- if (api_recorder_visible) {
504
- api_calls = [...api_calls, JSON.parse(JSON.stringify(payload))];
505
- }
506
-
507
- let submission: ReturnType<typeof app.submit>;
508
- app.set_current_payload(payload);
509
- if (streaming) {
510
- if (!submit_map.has(dep_index)) {
511
- dep.inputs.forEach((id) => modify_stream(id, "waiting"));
512
- } else if (
513
- submit_map.has(dep_index) &&
514
- dep.inputs.some((id) => get_stream_state(id) === "waiting")
515
- ) {
516
- return;
517
- } else if (
518
- submit_map.has(dep_index) &&
519
- dep.inputs.some((id) => get_stream_state(id) === "open")
520
- ) {
521
- await app.send_ws_message(
522
- // @ts-ignore
523
- `${app.config.root + app.config.api_prefix}/stream/${submit_map.get(dep_index).event_id()}`,
524
- { ...payload, session_hash: app.session_hash }
525
- );
526
- return;
527
- }
528
- }
529
- try {
530
- submission = app.submit(
531
- payload.fn_index,
532
- payload.data as unknown[],
533
- payload.event_data,
534
- payload.trigger_id
535
- );
536
- } catch (e) {
537
- const fn_index = 0; // Mock value for fn_index
538
- if (app.closed) return; // when a user navigates away in multipage app.
539
- messages = [
540
- new_message("Error", String(e), fn_index, "error"),
541
- ...messages
542
- ];
543
- loading_status.update({
544
- status: "error",
545
- fn_index,
546
- eta: 0,
547
- queue: false,
548
- queue_position: null
549
- });
550
- set_status($loading_status);
551
- return;
552
- }
553
-
554
- submit_map.set(dep_index, submission);
555
-
556
- for await (const message of submission) {
557
- if (payload.js_implementation) {
558
- return;
559
- }
560
- if (message.type === "data") {
561
- handle_data(message);
562
- } else if (message.type === "render") {
563
- handle_render(message);
564
- } else if (message.type === "status") {
565
- handle_status_update(message);
566
- } else if (message.type === "log") {
567
- handle_log(message);
568
- }
569
- }
570
-
571
- function handle_data(message: Payload): void {
572
- const { data, fn_index } = message;
573
- if (dep.pending_request && dep.final_event) {
574
- dep.pending_request = false;
575
- make_prediction(dep.final_event, dep.connection == "stream");
576
- }
577
- dep.pending_request = false;
578
- handle_update(data, fn_index);
579
- set_status($loading_status);
580
- }
581
-
582
- function handle_render(message: RenderMessage): void {
583
- const { data } = message;
584
- let _components: ComponentMeta[] = data.components;
585
- let render_layout: LayoutNode = data.layout;
586
- let _dependencies: Dependency[] = data.dependencies;
587
- let render_id = data.render_id;
588
-
589
- let deps_to_remove: number[] = [];
590
- dependencies.forEach((old_dep, i) => {
591
- if (old_dep.rendered_in === dep.render_id) {
592
- deps_to_remove.push(i);
593
- }
594
- });
595
- deps_to_remove.reverse().forEach((i) => {
596
- dependencies.splice(i, 1);
597
- });
598
- _dependencies.forEach((dep) => {
599
- dependencies.push(dep);
600
- });
601
-
602
- rerender_layout({
603
- components: _components,
604
- layout: render_layout,
605
- root: root + api_prefix,
606
- dependencies: dependencies,
607
- render_id: render_id
608
- });
609
- _dependencies.forEach((dep) => {
610
- if (dep.targets.some((dep) => dep[1] === "load")) {
611
- wait_then_trigger_api_call(dep.id);
612
- }
613
- });
614
- }
615
-
616
- function handle_log(msg: LogMessage): void {
617
- const { title, log, fn_index, level, duration, visible } = msg;
618
- messages = [
619
- new_message(title, log, fn_index, level, duration, visible),
620
- ...messages
621
- ];
622
- }
623
-
624
- function open_stream_events(
625
- status: StatusMessage,
626
- id: number,
627
- dep: Dependency
628
- ): void {
629
- if (
630
- status.original_msg === "process_starts" &&
631
- dep.connection === "stream"
632
- ) {
633
- modify_stream(id, "open");
634
- }
635
- }
636
-
637
- /* eslint-disable complexity */
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
- }
686
- if (message.broken && !broken_connection) {
687
- messages = [
688
- new_message(
689
- "Broken Connection",
690
- LOST_CONNECTION_MESSAGE,
691
- -1,
692
- "error",
693
- null,
694
- true
695
- ),
696
- ...messages
697
- ];
698
-
699
- broken_connection = true;
700
- setTimeout(reconnect, 1000);
701
- }
702
- if (message.session_not_found) {
703
- messages = [
704
- new_message(
705
- "Session Not Found",
706
- SESSION_NOT_FOUND_MESSAGE,
707
- -1,
708
- "error",
709
- null,
710
- true
711
- ),
712
- ...messages
713
- ];
714
- }
715
- const { fn_index, ...status } = message;
716
- if (status.stage === "streaming" && status.time_limit) {
717
- dep.inputs.forEach((id) => {
718
- set_time_limit(id, status.time_limit);
719
- });
720
- }
721
- dep.inputs.forEach((id) => {
722
- open_stream_events(message, id, dep);
723
- });
724
- //@ts-ignore
725
- loading_status.update({
726
- ...status,
727
- time_limit: status.time_limit,
728
- status: status.stage,
729
- progress: status.progress_data,
730
- fn_index
731
- });
732
- set_status($loading_status);
733
- if (
734
- !showed_duplicate_message &&
735
- space_id !== null &&
736
- status.position !== undefined &&
737
- status.position >= 2 &&
738
- status.eta !== undefined &&
739
- status.eta > SHOW_DUPLICATE_MESSAGE_ON_ETA
740
- ) {
741
- showed_duplicate_message = true;
742
- messages = [
743
- new_message("Warning", DUPLICATE_MESSAGE, fn_index, "warning"),
744
- ...messages
745
- ];
746
- }
747
- if (
748
- !showed_mobile_warning &&
749
- is_mobile_device &&
750
- status.eta !== undefined &&
751
- status.eta > SHOW_MOBILE_QUEUE_WARNING_ON_ETA
752
- ) {
753
- showed_mobile_warning = true;
754
- messages = [
755
- new_message("Warning", MOBILE_QUEUE_WARNING, fn_index, "warning"),
756
- ...messages
757
- ];
758
- }
759
-
760
- if (status.stage === "complete" || status.stage === "generating") {
761
- const deps_triggered_by_state: Set<Dependency> = new Set();
762
- status.changed_state_ids?.forEach((id) => {
763
- dependencies
764
- .filter((dep) => dep.targets.some(([_id, _]) => _id === id))
765
- .forEach((dep) => {
766
- deps_triggered_by_state.add(dep);
767
- });
768
- });
769
- deps_triggered_by_state.forEach((dep) => {
770
- wait_then_trigger_api_call(dep.id, payload.trigger_id);
771
- });
772
- }
773
- if (status.stage === "complete") {
774
- dependencies.forEach(async (dep) => {
775
- if (
776
- dep.trigger_after === fn_index &&
777
- !dep.trigger_only_on_failure
778
- ) {
779
- wait_then_trigger_api_call(dep.id, payload.trigger_id);
780
- }
781
- });
782
- dep.inputs.forEach((id) => {
783
- modify_stream(id, "closed");
784
- });
785
- submit_map.delete(dep_index);
786
- }
787
- if (
788
- status.stage === "error" &&
789
- !broken_connection &&
790
- !message.session_not_found
791
- ) {
792
- if (status.message && typeof status.message === "string") {
793
- const _message = status.message.replace(
794
- MESSAGE_QUOTE_RE,
795
- (_, b) => b
796
- );
797
- const _title = status.title ?? "Error";
798
- messages = [
799
- new_message(
800
- _title,
801
- _message,
802
- fn_index,
803
- "error",
804
- status.duration,
805
- status.visible
806
- ),
807
- ...messages
808
- ];
809
- }
810
- dependencies.map(async (dep) => {
811
- if (
812
- dep.trigger_after === fn_index &&
813
- (!dep.trigger_only_on_success || dep.trigger_only_on_failure)
814
- ) {
815
- wait_then_trigger_api_call(dep.id, payload.trigger_id);
816
- }
817
- });
818
- }
819
- }
820
- if (allow_video_trim) {
821
- screen_recorder.markRemoveSegmentEnd();
822
- }
823
- }
824
- }
825
- /* eslint-enable complexity */
826
-
827
- function trigger_share(title: string | undefined, description: string): void {
828
- if (space_id === null) {
829
- return;
830
- }
831
- const discussion_url = new URL(
832
- `https://huggingface.co/spaces/${space_id}/discussions/new`
833
- );
834
- if (title !== undefined && title.length > 0) {
835
- discussion_url.searchParams.set("title", title);
836
- }
837
- discussion_url.searchParams.set("description", description);
838
- window.open(discussion_url.toString(), "_blank");
839
- }
840
-
841
- function handle_error_close(e: Event & { detail: number }): void {
842
- const _id = e.detail;
843
- messages = messages.filter((m) => m.id !== _id);
844
- }
845
-
846
- const is_external_url = (link: string | null): boolean =>
847
- !!(link && new URL(link, location.href).origin !== location.origin);
848
-
849
- async function handle_mount(): Promise<void> {
850
- if (js) {
851
- let blocks_frontend_fn = new AsyncFunction(
852
- `let result = await (${js})();
853
- return (!Array.isArray(result)) ? [result] : result;`
854
- );
855
- await blocks_frontend_fn();
856
- }
857
-
858
- await tick();
859
-
860
- var a = target.getElementsByTagName("a");
861
-
862
- for (var i = 0; i < a.length; i++) {
863
- const _target = a[i].getAttribute("target");
864
- const _link = a[i].getAttribute("href");
865
-
866
- // only target anchor tags with external links
867
- if (is_external_url(_link) && _target !== "_blank")
868
- a[i].setAttribute("target", "_blank");
869
- }
870
- handle_load_triggers();
871
-
872
- if (!target || render_complete) return;
873
-
874
- target.addEventListener("prop_change", (e: Event) => {
875
- if (!isCustomEvent(e)) throw new Error("not a custom event");
876
- const { id, prop, value } = e.detail;
877
- if (prop === "value") {
878
- update_value([
879
- {
880
- id,
881
- prop: "loading_status",
882
- value: { validation_error: undefined }
883
- }
884
- ]);
885
- }
886
- update_value([{ id, prop, value }]);
887
- if (prop === "input_ready" && value === false) {
888
- inputs_waiting.push(id);
889
- }
890
- if (prop === "input_ready" && value === true) {
891
- inputs_waiting = inputs_waiting.filter((item) => item !== id);
892
- }
893
- });
894
- target.addEventListener("gradio", (e: Event) => {
895
- if (!isCustomEvent(e)) throw new Error("not a custom event");
896
-
897
- const { id, event, data } = e.detail;
898
-
899
- if (event === "share") {
900
- const { title, description } = data as ShareData;
901
- trigger_share(title, description);
902
- } else if (event === "error") {
903
- messages = [new_message("Error", data, -1, event), ...messages];
904
- } else if (event === "warning") {
905
- messages = [new_message("Warning", data, -1, event), ...messages];
906
- } else if (event === "info") {
907
- messages = [new_message("Info", data, -1, event), ...messages];
908
- } else if (event == "clear_status") {
909
- update_status(id, "complete", data);
910
- } else if (event == "close_stream") {
911
- const deps = $targets[id]?.[data];
912
- deps?.forEach((dep_id) => {
913
- if (submit_map.has(dep_id)) {
914
- // @ts-ignore
915
- const url = `${app.config.root + app.config.api_prefix}/stream/${submit_map.get(dep_id).event_id()}`;
916
- app.post_data(`${url}/close`, {});
917
- app.close_ws(url);
918
- }
919
- });
920
- } else {
921
- const deps = $targets[id]?.[event];
922
-
923
- deps?.forEach((dep_id) => {
924
- requestAnimationFrame(() => {
925
- wait_then_trigger_api_call(dep_id, id, data);
926
- });
927
- });
928
- }
929
- });
930
-
931
- render_complete = true;
932
- }
933
-
934
- value_change((id, value) => {
935
- const deps = $targets[id]?.["change"];
936
-
937
- deps?.forEach((dep_id) => {
938
- requestAnimationFrame(() => {
939
- wait_then_trigger_api_call(dep_id, id, value);
940
- });
941
- });
942
- });
943
-
944
- const handle_load_triggers = (): void => {
945
- dependencies.forEach((dep) => {
946
- if (dep.targets.some((dep) => dep[1] === "load")) {
947
- wait_then_trigger_api_call(dep.id);
948
- }
949
- });
950
- };
951
-
952
- $: set_status($loading_status);
953
-
954
- function update_status(
955
- id: number,
956
- status: "error" | "complete" | "pending",
957
- data: LoadingStatus
958
- ): void {
959
- data.status = status;
960
- update_value([
961
- {
962
- id,
963
- prop: "loading_status",
964
- value: data
965
- }
966
- ]);
967
- }
968
-
969
- function set_status(statuses: LoadingStatusCollection): void {
970
- let updates: {
971
- id: number;
972
- prop: string;
973
- value: LoadingStatus;
974
- }[] = [];
975
- Object.entries(statuses).forEach(([id, loading_status]) => {
976
- if (app.closed && loading_status.status === "error") {
977
- // when a user navigates away in multipage app.
978
- return;
979
- }
980
- let dependency = dependencies.find(
981
- (dep) => dep.id == loading_status.fn_index
982
- );
983
- if (dependency === undefined) {
984
- return;
985
- }
986
- loading_status.scroll_to_output = dependency.scroll_to_output;
987
- loading_status.show_progress = dependency.show_progress;
988
- updates.push({
989
- id: parseInt(id),
990
- prop: "loading_status",
991
- value: loading_status
992
- });
993
- });
994
-
995
- const inputs_to_update = loading_status.get_inputs_to_update();
996
- const additional_updates = Array.from(inputs_to_update).map(
997
- ([id, pending_status]) => {
998
- return {
999
- id,
1000
- prop: "pending",
1001
- value: pending_status === "pending"
1002
- };
1003
- }
1004
- );
1005
-
1006
- update_value([...updates, ...additional_updates]);
1007
- }
1008
-
1009
- function isCustomEvent(event: Event): event is CustomEvent {
1010
- return "detail" in event;
1011
- }
1012
337
 
1013
338
  let is_screen_recording = writable(false);
1014
339
 
1015
- onMount(() => {
1016
- is_mobile_device =
1017
- /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
1018
- navigator.userAgent
1019
- );
1020
-
1021
- screen_recorder.initialize(
1022
- root,
1023
- (title, message, type) => {
1024
- add_new_message(title, message, type);
1025
- },
1026
- (isRecording) => {
1027
- $is_screen_recording = isRecording;
1028
- }
1029
- );
1030
-
1031
- const handleVibeEditorResize = (event: CustomEvent): void => {
1032
- vibe_editor_width = event.detail.width;
1033
- };
1034
-
1035
- window.addEventListener(
1036
- "vibeEditorResize",
1037
- handleVibeEditorResize as EventListener
1038
- );
1039
-
1040
- // Load components if they should be visible on initial page load
1041
- if (api_docs_visible) {
1042
- loadApiDocs();
1043
- }
1044
- if (api_recorder_visible) {
1045
- loadApiRecorder();
1046
- }
1047
- if (settings_visible) {
1048
- loadSettings();
1049
- }
1050
- if (vibe_mode) {
1051
- loadVibeEditor();
1052
- }
1053
- });
1054
-
1055
- function screen_recording(): void {
1056
- if ($is_screen_recording) {
1057
- screen_recorder.stopRecording();
1058
- } else {
1059
- screen_recorder.startRecording();
1060
- }
1061
- }
1062
-
1063
340
  let footer_height = 0;
1064
341
 
1065
342
  let root_container: HTMLElement;
@@ -1077,7 +354,20 @@
1077
354
  }
1078
355
  }
1079
356
 
357
+ function screen_recording(): void {
358
+ if ($is_screen_recording) {
359
+ screen_recorder.stopRecording();
360
+ } else {
361
+ screen_recorder.startRecording();
362
+ }
363
+ }
364
+
1080
365
  onMount(() => {
366
+ is_mobile_device =
367
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
368
+ navigator.userAgent
369
+ );
370
+
1081
371
  if ("parentIFrame" in window) {
1082
372
  window.parentIFrame?.autoResize(false);
1083
373
  }
@@ -1090,14 +380,22 @@
1090
380
  subtree: true,
1091
381
  attributes: true
1092
382
  });
1093
-
1094
383
  res.observe(root_container);
1095
384
 
385
+ app_tree.ready.then(() => {
386
+ ready = true;
387
+ dep_manager.dispatch_load_events();
388
+ });
389
+
1096
390
  return () => {
1097
391
  mut.disconnect();
1098
392
  res.disconnect();
1099
393
  };
1100
394
  });
395
+
396
+ function handle_close(id: number): void {
397
+ messages = messages.filter((m) => m.id !== id);
398
+ }
1101
399
  </script>
1102
400
 
1103
401
  <svelte:head>
@@ -1116,163 +414,150 @@
1116
414
  bind:this={root_container}
1117
415
  style:margin-right={vibe_mode ? `${vibe_editor_width}px` : "0"}
1118
416
  >
1119
- {#if $_layout && app.config}
1120
- <MountComponents
1121
- rootNode={$_layout}
1122
- {root}
1123
- {target}
1124
- {theme_mode}
1125
- on:mount={handle_mount}
1126
- {version}
1127
- {autoscroll}
1128
- {max_file_size}
1129
- client={app}
1130
- />
1131
- {/if}
1132
- </div>
1133
-
1134
- {#if show_footer}
1135
- <footer bind:clientHeight={footer_height}>
1136
- {#if show_api}
417
+ <MountComponents node={app_tree.root} />
418
+
419
+ {#if footer_links.length > 0}
420
+ <footer bind:clientHeight={footer_height}>
421
+ {#if footer_links.includes("api")}
422
+ <button
423
+ on:click={() => {
424
+ set_api_docs_visible(!api_docs_visible);
425
+ }}
426
+ on:mouseenter={() => {
427
+ loadApiDocs();
428
+ loadApiRecorder();
429
+ }}
430
+ class="show-api"
431
+ >
432
+ {#if app.config?.mcp_server}
433
+ {$_("errors.use_via_api_or_mcp")}
434
+ {:else}
435
+ {$_("errors.use_via_api")}
436
+ {/if}
437
+ <img src={api_logo} alt={$_("common.logo")} />
438
+ </button>
439
+ {/if}
440
+ {#if footer_links.includes("gradio")}
441
+ <div class="divider show-api-divider">·</div>
442
+ <a
443
+ href="https://gradio.app"
444
+ class="built-with"
445
+ target="_blank"
446
+ rel="noreferrer"
447
+ >
448
+ {$_("common.built_with_gradio")}
449
+ <img src={logo} alt={$_("common.logo")} />
450
+ </a>
451
+ {/if}
1137
452
  <button
453
+ class:hidden={!$is_screen_recording}
1138
454
  on:click={() => {
1139
- set_api_docs_visible(!api_docs_visible);
1140
- }}
1141
- on:mouseenter={() => {
1142
- loadApiDocs();
1143
- loadApiRecorder();
455
+ screen_recording();
1144
456
  }}
1145
- class="show-api"
457
+ class="record"
1146
458
  >
1147
- {#if app.config?.mcp_server}
1148
- {$_("errors.use_via_api_or_mcp")}
1149
- {:else}
1150
- {$_("errors.use_via_api")}
1151
- {/if}
1152
- <img src={api_logo} alt={$_("common.logo")} />
459
+ {$_("common.stop_recording")}
460
+ <img src={record_stop} alt={$_("common.stop_recording")} />
1153
461
  </button>
1154
- <div class="divider show-api-divider">·</div>
1155
- {/if}
1156
- <a
1157
- href="https://gradio.app"
1158
- class="built-with"
1159
- target="_blank"
1160
- rel="noreferrer"
1161
- >
1162
- {$_("common.built_with_gradio")}
1163
- <img src={logo} alt={$_("common.logo")} />
1164
- </a>
1165
- <div class="divider" class:hidden={!$is_screen_recording}>·</div>
1166
- <button
1167
- class:hidden={!$is_screen_recording}
1168
- on:click={() => {
1169
- screen_recording();
1170
- }}
1171
- class="record"
1172
- >
1173
- {$_("common.stop_recording")}
1174
- <img src={record_stop} alt={$_("common.stop_recording")} />
1175
- </button>
1176
- <div class="divider">·</div>
1177
- <button
1178
- on:click={() => {
1179
- set_settings_visible(!settings_visible);
1180
- }}
1181
- on:mouseenter={() => {
1182
- loadSettings();
1183
- }}
1184
- class="settings"
1185
- >
1186
- {$_("common.settings")}
1187
- <img src={settings_logo} alt={$_("common.settings")} />
1188
- </button>
1189
- </footer>
1190
- {/if}
1191
- </div>
1192
-
1193
- {#if api_recorder_visible && ApiRecorder}
1194
- <!-- TODO: fix -->
1195
- <!-- svelte-ignore a11y-click-events-have-key-events-->
1196
- <!-- svelte-ignore a11y-no-static-element-interactions-->
1197
- <div
1198
- id="api-recorder-container"
1199
- on:click={() => {
1200
- set_api_docs_visible(true);
1201
- api_recorder_visible = false;
1202
- }}
1203
- >
1204
- <svelte:component this={ApiRecorder} {api_calls} {dependencies} />
462
+ <div class="divider">·</div>
463
+ {#if footer_links.includes("settings")}
464
+ <div class="divider" class:hidden={!$is_screen_recording}>·</div>
465
+ <button
466
+ on:click={() => {
467
+ set_settings_visible(!settings_visible);
468
+ }}
469
+ on:mouseenter={() => {
470
+ loadSettings();
471
+ }}
472
+ class="settings"
473
+ >
474
+ {$_("common.settings")}
475
+ <img src={settings_logo} alt={$_("common.settings")} />
476
+ </button>
477
+ {/if}
478
+ </footer>
479
+ {/if}
1205
480
  </div>
1206
- {/if}
1207
-
1208
- {#if api_docs_visible && $_layout && ApiDocs}
1209
- <div class="api-docs">
481
+ {#if api_recorder_visible && ApiRecorder}
1210
482
  <!-- TODO: fix -->
1211
483
  <!-- svelte-ignore a11y-click-events-have-key-events-->
1212
484
  <!-- svelte-ignore a11y-no-static-element-interactions-->
1213
485
  <div
1214
- class="backdrop"
486
+ id="api-recorder-container"
1215
487
  on:click={() => {
1216
- set_api_docs_visible(false);
488
+ set_api_docs_visible(true);
489
+ api_recorder_visible = false;
1217
490
  }}
1218
- />
1219
- <div class="api-docs-wrap">
1220
- <svelte:component
1221
- this={ApiDocs}
1222
- root_node={$_layout}
1223
- on:close={(event) => {
491
+ >
492
+ <svelte:component this={ApiRecorder} {api_calls} {dependencies} />
493
+ </div>
494
+ {/if}
495
+
496
+ {#if api_docs_visible && app_tree.root && ApiDocs}
497
+ <div class="api-docs">
498
+ <!-- TODO: fix -->
499
+ <!-- svelte-ignore a11y-click-events-have-key-events-->
500
+ <!-- svelte-ignore a11y-no-static-element-interactions-->
501
+ <div
502
+ class="backdrop"
503
+ on:click={() => {
1224
504
  set_api_docs_visible(false);
1225
- api_calls = [];
1226
- api_recorder_visible = api_recorder_visible =
1227
- event.detail?.api_recorder_visible;
1228
505
  }}
1229
- {dependencies}
1230
- {root}
1231
- {app}
1232
- {space_id}
1233
- {api_calls}
1234
- {username}
1235
506
  />
507
+ <div class="api-docs-wrap">
508
+ <svelte:component
509
+ this={ApiDocs}
510
+ root_node={app_tree.root}
511
+ on:close={(event) => {
512
+ set_api_docs_visible(false);
513
+ api_calls = [];
514
+ api_recorder_visible = api_recorder_visible =
515
+ event.detail?.api_recorder_visible;
516
+ }}
517
+ {dependencies}
518
+ {root}
519
+ {app}
520
+ {space_id}
521
+ {api_calls}
522
+ {username}
523
+ />
524
+ </div>
1236
525
  </div>
1237
- </div>
1238
- {/if}
526
+ {/if}
1239
527
 
1240
- {#if settings_visible && $_layout && app.config && Settings}
1241
- <div class="api-docs">
1242
- <!-- TODO: fix -->
1243
- <!-- svelte-ignore a11y-click-events-have-key-events-->
1244
- <!-- svelte-ignore a11y-no-static-element-interactions-->
1245
- <div
1246
- class="backdrop"
1247
- on:click={() => {
1248
- set_settings_visible(false);
1249
- }}
1250
- />
1251
- <div class="api-docs-wrap">
1252
- <svelte:component
1253
- this={Settings}
1254
- bind:allow_zoom
1255
- bind:allow_video_trim
1256
- on:close={() => {
528
+ {#if settings_visible && app.config && app_tree.root && Settings}
529
+ <div class="api-docs">
530
+ <!-- TODO: fix -->
531
+ <!-- svelte-ignore a11y-click-events-have-key-events-->
532
+ <!-- svelte-ignore a11y-no-static-element-interactions-->
533
+ <div
534
+ class="backdrop"
535
+ on:click={() => {
1257
536
  set_settings_visible(false);
1258
537
  }}
1259
- on:start_recording={() => {
1260
- screen_recording();
1261
- }}
1262
- pwa_enabled={app.config.pwa}
1263
- {root}
1264
- {space_id}
1265
538
  />
539
+ <div class="api-docs-wrap">
540
+ <svelte:component
541
+ this={Settings}
542
+ bind:allow_zoom
543
+ bind:allow_video_trim
544
+ on:close={() => {
545
+ set_settings_visible(false);
546
+ }}
547
+ on:start_recording={() => {
548
+ screen_recording();
549
+ }}
550
+ pwa_enabled={app.config.pwa}
551
+ {root}
552
+ {space_id}
553
+ />
554
+ </div>
1266
555
  </div>
1267
- </div>
1268
- {/if}
556
+ {/if}
557
+ </div>
1269
558
 
1270
559
  {#if messages}
1271
- <Toast {messages} on:close={handle_error_close} />
1272
- {/if}
1273
-
1274
- {#if vibe_mode && VibeEditor}
1275
- <svelte:component this={VibeEditor} {app} {root} />
560
+ <Toast {messages} on_close={handle_close} />
1276
561
  {/if}
1277
562
 
1278
563
  <style>