@gradio/core 0.22.0 → 0.23.1

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.
@@ -52,6 +52,21 @@
52
52
  export let api_calls: Payload[] = [];
53
53
  let current_language: "python" | "javascript" | "bash" | "mcp" = "python";
54
54
 
55
+ function set_query_param(key: string, value: string) {
56
+ const url = new URL(window.location.href);
57
+ url.searchParams.set(key, value);
58
+ history.replaceState(null, "", url.toString());
59
+ }
60
+
61
+ function get_query_param(key: string): string | null {
62
+ const url = new URL(window.location.href);
63
+ return url.searchParams.get(key);
64
+ }
65
+
66
+ function is_valid_language(lang: string | null): boolean {
67
+ return ["python", "javascript", "bash", "mcp"].includes(lang ?? "");
68
+ }
69
+
55
70
  const langs = [
56
71
  ["python", "Python", python],
57
72
  ["javascript", "JavaScript", javascript],
@@ -112,11 +127,30 @@
112
127
  }
113
128
 
114
129
  let tools: Tool[] = [];
130
+ let headers: string[] = [];
131
+ let mcp_json_sse: any;
132
+ let mcp_json_stdio: any;
133
+ let file_data_present = false;
134
+
135
+ const upload_file_mcp_server = {
136
+ command: "uvx",
137
+ args: [
138
+ "--from",
139
+ "gradio[mcp]",
140
+ "gradio",
141
+ "upload-mcp",
142
+ root,
143
+ "<UPLOAD_DIRECTORY>"
144
+ ]
145
+ };
115
146
 
116
147
  async function fetchMcpTools() {
117
148
  try {
118
149
  const response = await fetch(`${root}gradio_api/mcp/schema`);
119
150
  const schema = await response.json();
151
+ file_data_present = schema
152
+ .map((tool: any) => tool.meta?.file_data_present)
153
+ .some((present: boolean) => present);
120
154
 
121
155
  tools = schema.map((tool: any) => ({
122
156
  name: tool.name,
@@ -124,6 +158,62 @@
124
158
  parameters: tool.inputSchema?.properties || {},
125
159
  expanded: false
126
160
  }));
161
+ headers = schema.map((tool: any) => tool.meta?.headers || []).flat();
162
+ if (headers.length > 0) {
163
+ mcp_json_sse = {
164
+ mcpServers: {
165
+ gradio: {
166
+ url: mcp_server_url,
167
+ headers: headers.reduce((accumulator, current_key) => {
168
+ // @ts-ignore
169
+ accumulator[current_key] = "<YOUR_HEADER_VALUE>";
170
+ return accumulator;
171
+ }, {})
172
+ }
173
+ }
174
+ };
175
+ mcp_json_stdio = {
176
+ mcpServers: {
177
+ gradio: {
178
+ command: "npx",
179
+ args: [
180
+ "mcp-remote",
181
+ mcp_server_url,
182
+ "--transport",
183
+ "sse-only",
184
+ ...headers
185
+ .map((header) => [
186
+ "--header",
187
+ `${header}: <YOUR_HEADER_VALUE>`
188
+ ])
189
+ .flat()
190
+ ]
191
+ }
192
+ }
193
+ };
194
+ } else {
195
+ mcp_json_sse = {
196
+ mcpServers: {
197
+ gradio: {
198
+ url: mcp_server_url
199
+ }
200
+ }
201
+ };
202
+ mcp_json_stdio = {
203
+ mcpServers: {
204
+ gradio: {
205
+ command: "npx",
206
+ args: ["mcp-remote", mcp_server_url, "--transport", "sse-only"]
207
+ }
208
+ }
209
+ };
210
+ if (file_data_present) {
211
+ mcp_json_sse.mcpServers.upload_files_to_gradio =
212
+ upload_file_mcp_server;
213
+ mcp_json_stdio.mcpServers.upload_files_to_gradio =
214
+ upload_file_mcp_server;
215
+ }
216
+ }
127
217
  } catch (error) {
128
218
  console.error("Failed to fetch MCP tools:", error);
129
219
  tools = [];
@@ -136,12 +226,24 @@
136
226
  window.parentIFrame?.scrollTo(0, 0);
137
227
  }
138
228
 
229
+ const lang_param = get_query_param("lang");
230
+ if (is_valid_language(lang_param)) {
231
+ current_language = lang_param as "python" | "javascript" | "bash" | "mcp";
232
+ }
233
+
139
234
  // Check MCP server status and fetch tools if active
140
235
  fetch(mcp_server_url)
141
236
  .then((response) => {
142
237
  mcp_server_active = response.ok;
143
238
  if (mcp_server_active) {
144
239
  fetchMcpTools();
240
+ if (!is_valid_language(lang_param)) {
241
+ current_language = "mcp";
242
+ }
243
+ } else {
244
+ if (!is_valid_language(lang_param)) {
245
+ current_language = "python";
246
+ }
145
247
  }
146
248
  })
147
249
  .catch(() => {
@@ -177,7 +279,10 @@
177
279
  <li
178
280
  class="snippet
179
281
  {current_language === language ? 'current-lang' : 'inactive-lang'}"
180
- on:click={() => (current_language = language)}
282
+ on:click={() => {
283
+ current_language = language;
284
+ set_query_param("lang", language);
285
+ }}
181
286
  >
182
287
  <img src={img} alt="" />
183
288
  {display_name}
@@ -235,7 +340,7 @@
235
340
  <div class="mcp-url">
236
341
  <label
237
342
  ><span class="status-indicator active">●</span>MCP Server
238
- URL</label
343
+ URL (SSE)</label
239
344
  >
240
345
  <div class="textbox">
241
346
  <input type="text" readonly value={mcp_server_url} />
@@ -294,45 +399,42 @@
294
399
  </div>
295
400
  <p>&nbsp;</p>
296
401
 
297
- <strong>Integration</strong>: To add this MCP to clients that
402
+ <strong>SSE Transport</strong>: To add this MCP to clients that
298
403
  support SSE (e.g. Cursor, Windsurf, Cline), simply add the
299
- following configuration to your MCP config:
404
+ following configuration to your MCP config.
300
405
  <p>&nbsp;</p>
301
406
  <Block>
302
407
  <code>
303
408
  <div class="copy">
304
409
  <CopyButton
305
- code={JSON.stringify(
306
- {
307
- mcpServers: {
308
- gradio: {
309
- url: mcp_server_url
310
- }
311
- }
312
- },
313
- null,
314
- 2
315
- )}
410
+ code={JSON.stringify(mcp_json_sse, null, 2)}
316
411
  />
317
412
  </div>
318
413
  <div>
319
- <pre>{JSON.stringify(
320
- {
321
- mcpServers: {
322
- gradio: {
323
- url: mcp_server_url
324
- }
325
- }
326
- },
327
- null,
328
- 2
329
- )}</pre>
414
+ <pre>{JSON.stringify(mcp_json_sse, null, 2)}</pre>
330
415
  </div>
331
416
  </code>
332
417
  </Block>
333
- <p>&nbsp;</p>
334
- <em>Experimental stdio support</em>: For clients that only
335
- support stdio, first
418
+ {#if file_data_present}
419
+ <p>&nbsp;</p>
420
+ <em>Note about files</em>: Gradio MCP servers that have files
421
+ as inputs need the files as URLs, so the
422
+ <code>upload_files_to_gradio</code>
423
+ tool is included for your convenience. This tool can upload files
424
+ located in the specified <code>UPLOAD_DIRECTORY</code>
425
+ argument (an absolute path in your local machine) or any of its
426
+ subdirectories to the Gradio app. You can omit this tool if you
427
+ are fine manually uploading files yourself and providing the URLs.
428
+ Before using this tool, you must have
429
+ <a
430
+ href="https://docs.astral.sh/uv/getting-started/installation/"
431
+ target="_blank">uv installed</a
432
+ >.
433
+ <p>&nbsp;</p>
434
+ {/if}
435
+
436
+ <strong>STDIO Transport</strong>: For clients that only support
437
+ stdio (e.g. Claude Desktop), first
336
438
  <a href="https://nodejs.org/en/download/" target="_blank"
337
439
  >install Node.js</a
338
440
  >. Then, you can use the following command:
@@ -341,43 +443,11 @@
341
443
  <code>
342
444
  <div class="copy">
343
445
  <CopyButton
344
- code={JSON.stringify(
345
- {
346
- mcpServers: {
347
- gradio: {
348
- command: "npx",
349
- args: [
350
- "mcp-remote",
351
- mcp_server_url,
352
- "--transport",
353
- "sse-only"
354
- ]
355
- }
356
- }
357
- },
358
- null,
359
- 2
360
- )}
446
+ code={JSON.stringify(mcp_json_stdio, null, 2)}
361
447
  />
362
448
  </div>
363
449
  <div>
364
- <pre>{JSON.stringify(
365
- {
366
- mcpServers: {
367
- gradio: {
368
- command: "npx",
369
- args: [
370
- "mcp-remote",
371
- mcp_server_url,
372
- "--transport",
373
- "sse-only"
374
- ]
375
- }
376
- }
377
- },
378
- null,
379
- 2
380
- )}</pre>
450
+ <pre>{JSON.stringify(mcp_json_stdio, null, 2)}</pre>
381
451
  </div>
382
452
  </code>
383
453
  </Block>
@@ -453,21 +523,22 @@
453
523
  {/if}
454
524
 
455
525
  {#if current_language !== "mcp"}
456
- {#each dependencies as dependency, dependency_index}
526
+ {#each dependencies as dependency}
457
527
  {#if dependency.show_api && info.named_endpoints["/" + dependency.api_name]}
458
528
  <div class="endpoint-container">
459
529
  <CodeSnippet
460
- named={true}
461
530
  endpoint_parameters={info.named_endpoints[
462
531
  "/" + dependency.api_name
463
532
  ].parameters}
464
533
  {dependency}
465
- {dependency_index}
466
534
  {current_language}
467
535
  {root}
468
536
  {space_id}
469
537
  {username}
470
538
  api_prefix={app.api_prefix}
539
+ api_description={info.named_endpoints[
540
+ "/" + dependency.api_name
541
+ ].description}
471
542
  />
472
543
 
473
544
  <ParametersSnippet
@@ -15,14 +15,13 @@
15
15
  }
16
16
 
17
17
  export let dependency: Dependency;
18
- export let dependency_index: number;
19
18
  export let root: string;
20
19
  export let api_prefix: string;
21
20
  export let space_id: string | null;
22
21
  export let endpoint_parameters: any;
23
- export let named: boolean;
24
22
  export let username: string | null;
25
23
  export let current_language: "python" | "javascript" | "bash";
24
+ export let api_description: string | null = null;
26
25
 
27
26
  let python_code: HTMLElement;
28
27
  let js_code: HTMLElement;
@@ -42,11 +41,10 @@
42
41
  </script>
43
42
 
44
43
  <div class="container">
45
- {#if named}
46
- <EndpointDetail {named} api_name={dependency.api_name} />
47
- {:else}
48
- <EndpointDetail {named} fn_index={dependency_index} />
49
- {/if}
44
+ <EndpointDetail
45
+ api_name={dependency.api_name}
46
+ description={api_description}
47
+ />
50
48
  {#if current_language === "python"}
51
49
  <Block>
52
50
  <code>
@@ -99,9 +97,9 @@ const example{component} = await response_{i}.blob();
99
97
  const client = await Client.connect(<span class="token string"
100
98
  >"{space_id || root}"</span
101
99
  >{#if username !== null}, &lbrace;auth: ["{username}", **password**]&rbrace;{/if});
102
- const result = await client.predict({#if named}<span class="api-name"
103
- >"/{dependency.api_name}"</span
104
- >{:else}{dependency_index}{/if}, &lbrace; <!--
100
+ const result = await client.predict(<span class="api-name"
101
+ >"/{dependency.api_name}"</span
102
+ >, &lbrace; <!--
105
103
  -->{#each endpoint_parameters as { label, parameter_name, type, python_type, component, example_input, serializer }, i}<!--
106
104
  -->{#if blob_components.includes(component)}<!--
107
105
  -->
@@ -1,20 +1,13 @@
1
1
  <script lang="ts">
2
2
  export let api_name: string | null = null;
3
- export let fn_index: number | null = null;
4
- export let named: boolean;
3
+ export let description: string | null = null;
5
4
  </script>
6
5
 
7
- {#if named}
8
- <h3>
9
- api_name:
10
- <span class="post">{"/" + api_name}</span>
11
- </h3>
12
- {:else}
13
- <h3>
14
- fn_index:
15
- <span class="post">{fn_index}</span>
16
- </h3>
17
- {/if}
6
+ <h3>
7
+ API name:
8
+ <span class="post">{"/" + api_name}</span>
9
+ <span class="desc">{description}</span>
10
+ </h3>
18
11
 
19
12
  <style>
20
13
  h3 {
@@ -34,4 +27,10 @@
34
27
  color: var(--color-accent);
35
28
  font-weight: var(--weight-semibold);
36
29
  }
30
+
31
+ .desc {
32
+ color: var(--body-text-color-subdued);
33
+ font-size: var(--text-lg);
34
+ margin-top: var(--size-1);
35
+ }
37
36
  </style>
package/src/init.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { writable, type Writable, get } from "svelte/store";
2
+ import { dequal } from "dequal";
2
3
 
3
4
  import type {
4
5
  ComponentMeta,
@@ -28,8 +29,15 @@ const raf = is_browser
28
29
  * Create a store with the layout and a map of targets
29
30
  * @returns A store with the layout and a map of targets
30
31
  */
31
- let has_run = new Set<number>();
32
- export function create_components(initial_layout: ComponentMeta | undefined): {
32
+ export function create_components(
33
+ {
34
+ initial_layout = undefined
35
+ }: {
36
+ initial_layout: ComponentMeta | undefined;
37
+ } = {
38
+ initial_layout: undefined
39
+ }
40
+ ): {
33
41
  layout: Writable<ComponentMeta>;
34
42
  targets: Writable<TargetMap>;
35
43
  update_value: (updates: UpdateTransaction[]) => void;
@@ -56,6 +64,7 @@ export function create_components(initial_layout: ComponentMeta | undefined): {
56
64
  root: string;
57
65
  dependencies: Dependency[];
58
66
  }) => void;
67
+ value_change: (cb: (id: number, value: any) => void) => void;
59
68
  } {
60
69
  let _component_map: Map<number, ComponentMeta>;
61
70
 
@@ -73,6 +82,12 @@ export function create_components(initial_layout: ComponentMeta | undefined): {
73
82
  let keys_per_render_id: Record<number, (string | number)[]> = {};
74
83
  let _rootNode: ComponentMeta;
75
84
 
85
+ let value_change_cb: ((id: number, value: any) => void) | null = null;
86
+
87
+ function value_change(cb: (id: number, value: any) => void): void {
88
+ value_change_cb = cb;
89
+ }
90
+
76
91
  // Store current layout and root for dynamic visibility recalculation
77
92
  let current_layout: LayoutNode;
78
93
  let current_root: string;
@@ -114,8 +129,14 @@ export function create_components(initial_layout: ComponentMeta | undefined): {
114
129
  if (instance_map) {
115
130
  // re-render in reload mode
116
131
  components.forEach((c) => {
117
- if (c.props.value == null && c.id in instance_map) {
118
- c.props.value = instance_map[c.id].props.value;
132
+ if (c.props.value == null && c.key) {
133
+ // If the component has a key, we preserve its value by finding a matching instance with the same key
134
+ const matching_instance = Object.values(instance_map).find(
135
+ (instance) => instance.key === c.key
136
+ );
137
+ if (matching_instance) {
138
+ c.props.value = matching_instance.props.value;
139
+ }
119
140
  }
120
141
  });
121
142
  }
@@ -126,7 +147,6 @@ export function create_components(initial_layout: ComponentMeta | undefined): {
126
147
  pending_updates = [];
127
148
  constructor_map = new Map();
128
149
  _component_map = new Map();
129
-
130
150
  instance_map = {};
131
151
 
132
152
  // Store current layout and root for dynamic visibility recalculation
@@ -321,19 +341,12 @@ export function create_components(initial_layout: ComponentMeta | undefined): {
321
341
  const constructor_key = instance.component_class_id || instance.type;
322
342
  let component_constructor = constructor_map.get(constructor_key);
323
343
 
324
- // Only load component if it was preloaded (i.e., it's visible)
325
344
  if (component_constructor) {
326
345
  instance.component = (await component_constructor)?.default;
327
346
  }
328
- // If component wasn't preloaded, leave it unloaded for now
329
- // It will be loaded later when/if it becomes visible
330
347
  }
331
348
  instance.parent = parent;
332
349
 
333
- // if (instance.type === "timer") {
334
- // console.log("timer", instance, constructor_map);
335
- // }
336
-
337
350
  if (instance.type === "dataset") {
338
351
  instance.props.component_map = get_component(
339
352
  instance.type,
@@ -500,6 +513,7 @@ export function create_components(initial_layout: ComponentMeta | undefined): {
500
513
  const instance = instance_map[update.id];
501
514
  if (!instance) continue;
502
515
  let new_value;
516
+ const old_value = instance.props[update.prop];
503
517
  if (update.value instanceof Map) new_value = new Map(update.value);
504
518
  else if (update.value instanceof Set)
505
519
  new_value = new Set(update.value);
@@ -509,6 +523,14 @@ export function create_components(initial_layout: ComponentMeta | undefined): {
509
523
  new_value = { ...update.value };
510
524
  else new_value = update.value;
511
525
  instance.props[update.prop] = new_value;
526
+
527
+ if (
528
+ update.prop === "value" &&
529
+ !is_visible(instance) &&
530
+ !dequal(old_value, new_value)
531
+ ) {
532
+ value_change_cb?.(update.id, new_value);
533
+ }
512
534
  }
513
535
  }
514
536
  return layout;
@@ -625,7 +647,8 @@ export function create_components(initial_layout: ComponentMeta | undefined): {
625
647
  loading_status,
626
648
  scheduled_updates: update_scheduled_store,
627
649
  create_layout: create_layout,
628
- rerender_layout
650
+ rerender_layout,
651
+ value_change
629
652
  };
630
653
  }
631
654
 
@@ -1088,3 +1111,15 @@ export function preload_all_components(
1088
1111
 
1089
1112
  return constructor_map;
1090
1113
  }
1114
+
1115
+ function is_visible(component: ComponentMeta): boolean {
1116
+ if (
1117
+ typeof component.props.visible === "boolean" &&
1118
+ component.props.visible === false
1119
+ ) {
1120
+ return false;
1121
+ } else if (component.parent) {
1122
+ return is_visible(component.parent);
1123
+ }
1124
+ return true;
1125
+ }