@gradio/core 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # @gradio/core
2
2
 
3
+ ## 0.23.0
4
+
5
+ ### Features
6
+
7
+ - [#11578](https://github.com/gradio-app/gradio/pull/11578) [`872798a`](https://github.com/gradio-app/gradio/commit/872798a780dd81c834a44b05277f6c9ebe09de8b) - Add an `api_description` parameter for API and MCP server. Thanks @abidlabs!
8
+ - [#11543](https://github.com/gradio-app/gradio/pull/11543) [`ac95ac0`](https://github.com/gradio-app/gradio/commit/ac95ac0d8c2e65d1632376e632fb7d16131334b6) - Connection handling messaging. Thanks @aliabid94!
9
+ - [#11508](https://github.com/gradio-app/gradio/pull/11508) [`f5a6fa8`](https://github.com/gradio-app/gradio/commit/f5a6fa8c52bcb8f508e10ea54a3427f3dab8e3f8) - Handle uploading files in mcp server automatically. Thanks @freddyaboulton!
10
+ - [#11515](https://github.com/gradio-app/gradio/pull/11515) [`2c24ca7`](https://github.com/gradio-app/gradio/commit/2c24ca709396291a344d981c19ed1655028b5d64) - Making it easier for MCP developers call APIs with user credentials. Thanks @freddyaboulton!
11
+ - [#11584](https://github.com/gradio-app/gradio/pull/11584) [`78428cb`](https://github.com/gradio-app/gradio/commit/78428cb29bf6dc66d583b7cf93dd404aef737e75) - Fix reload mode. Thanks @aliabid94!
12
+
13
+ ### Fixes
14
+
15
+ - [#11590](https://github.com/gradio-app/gradio/pull/11590) [`33b6057`](https://github.com/gradio-app/gradio/commit/33b6057dc5cbdfdab15b1ee000b0f0b3d9f3fff6) - Add Gradio File Input keyword to gradio file inputs. Thanks @freddyaboulton!
16
+
17
+ ### Dependency updates
18
+
19
+ - @gradio/video@0.14.21
20
+ - @gradio/client@1.15.6
21
+ - @gradio/statustracker@0.10.15
22
+ - @gradio/button@0.5.7
23
+ - @gradio/upload@0.16.11
24
+ - @gradio/checkbox@0.4.26
25
+ - @gradio/image@0.22.13
26
+ - @gradio/gallery@0.15.27
27
+ - @gradio/plot@0.9.20
28
+ - @gradio/textbox@0.10.17
29
+ - @gradio/file@0.12.24
30
+ - @gradio/code@0.14.11
31
+ - @gradio/paramviewer@0.7.13
32
+ - @gradio/column@0.2.1
33
+
34
+ ## 0.22.0
35
+
36
+ ### Features
37
+
38
+ - [#11572](https://github.com/gradio-app/gradio/pull/11572) [`552a5eb`](https://github.com/gradio-app/gradio/commit/552a5ebf9beb5d543f82a24a546daaf9ad3d88b1) - handle i18n error when browsers aren't set to en. Thanks @hannahblair!
39
+
3
40
  ## 0.21.0
4
41
 
5
42
  ### Features
@@ -35,6 +35,7 @@ export let api_prefix = "";
35
35
  export let max_file_size = void 0;
36
36
  export let initial_layout = void 0;
37
37
  export let css = null;
38
+ let broken_connection = false;
38
39
  let {
39
40
  layout: _layout,
40
41
  targets,
@@ -60,6 +61,7 @@ $:
60
61
  old_dependencies = dependencies;
61
62
  }
62
63
  async function run() {
64
+ await setupi18n(app.config?.i18n_translations || void 0);
63
65
  layout_creating = true;
64
66
  await create_layout({
65
67
  components,
@@ -202,11 +204,13 @@ export function add_new_message(title2, message, type) {
202
204
  messages = [new_message(title2, message, -1, type), ...messages];
203
205
  }
204
206
  let _error_id = -1;
205
- let user_left_page = false;
206
207
  const MESSAGE_QUOTE_RE = /^'([^]+)'$/;
207
208
  const DUPLICATE_MESSAGE = $_("blocks.long_requests_queue");
208
209
  const MOBILE_QUEUE_WARNING = $_("blocks.connection_can_break");
209
- const MOBILE_RECONNECT_MESSAGE = $_("blocks.lost_connection");
210
+ const LOST_CONNECTION_MESSAGE = "Connection to the server was lost. Attempting reconnection...";
211
+ const CHANGED_CONNECTION_MESSAGE = "Reconnected to server, but the server has changed. You may need to <a href=''>refresh the page</a>.";
212
+ const RECONNECTION_MESSAGE = "Connection re-established.";
213
+ const SESSION_NOT_FOUND_MESSAGE = "Session not found - this is likely because the machine you were connected to has changed. <a href=''>Refresh the page</a> to continue.";
210
214
  const WAITING_FOR_INPUTS_MESSAGE = $_("blocks.waiting_for_inputs");
211
215
  const SHOW_DUPLICATE_MESSAGE_ON_ETA = 15;
212
216
  const SHOW_MOBILE_QUEUE_WARNING_ON_ETA = 10;
@@ -321,6 +325,42 @@ async function trigger_api_call(dep_index, trigger_id = null, event_data = null)
321
325
  }
322
326
  }
323
327
  }
328
+ async function reconnect() {
329
+ const connection_status = await app.reconnect();
330
+ if (connection_status === "broken") {
331
+ setTimeout(reconnect, 1e3);
332
+ } else if (connection_status === "changed") {
333
+ broken_connection = false;
334
+ messages = [
335
+ new_message(
336
+ "Changed Connection",
337
+ CHANGED_CONNECTION_MESSAGE,
338
+ -1,
339
+ "info",
340
+ 3,
341
+ true
342
+ ),
343
+ ...messages.map(
344
+ (m) => m.message === LOST_CONNECTION_MESSAGE ? { ...m, visible: false } : m
345
+ )
346
+ ];
347
+ } else if (connection_status === "connected") {
348
+ broken_connection = false;
349
+ messages = [
350
+ new_message(
351
+ "Reconnected",
352
+ RECONNECTION_MESSAGE,
353
+ -1,
354
+ "success",
355
+ null,
356
+ true
357
+ ),
358
+ ...messages.map(
359
+ (m) => m.message === LOST_CONNECTION_MESSAGE ? { ...m, visible: false } : m
360
+ )
361
+ ];
362
+ }
363
+ }
324
364
  async function make_prediction(payload2, streaming = false) {
325
365
  if (allow_video_trim) {
326
366
  screen_recorder.markRemoveSegmentStart();
@@ -438,6 +478,34 @@ async function trigger_api_call(dep_index, trigger_id = null, event_data = null)
438
478
  }
439
479
  }
440
480
  function handle_status_update(message) {
481
+ if (message.broken && !broken_connection) {
482
+ messages = [
483
+ new_message(
484
+ "Broken Connection",
485
+ LOST_CONNECTION_MESSAGE,
486
+ -1,
487
+ "error",
488
+ null,
489
+ true
490
+ ),
491
+ ...messages
492
+ ];
493
+ broken_connection = true;
494
+ setTimeout(reconnect, 1e3);
495
+ }
496
+ if (message.session_not_found) {
497
+ messages = [
498
+ new_message(
499
+ "Session Not Found",
500
+ SESSION_NOT_FOUND_MESSAGE,
501
+ -1,
502
+ "error",
503
+ null,
504
+ true
505
+ ),
506
+ ...messages
507
+ ];
508
+ }
441
509
  const { fn_index, ...status } = message;
442
510
  if (status.stage === "streaming" && status.time_limit) {
443
511
  dep.inputs.forEach((id) => {
@@ -491,16 +559,7 @@ async function trigger_api_call(dep_index, trigger_id = null, event_data = null)
491
559
  });
492
560
  submit_map.delete(dep_index);
493
561
  }
494
- if (status.broken && is_mobile_device && user_left_page) {
495
- window.setTimeout(() => {
496
- messages = [
497
- new_message("Error", MOBILE_RECONNECT_MESSAGE, fn_index, "error"),
498
- ...messages
499
- ];
500
- }, 0);
501
- wait_then_trigger_api_call(dep.id, payload2.trigger_id, event_data);
502
- user_left_page = false;
503
- } else if (status.stage === "error") {
562
+ if (status.stage === "error" && !broken_connection && !message.session_not_found) {
504
563
  if (status.message) {
505
564
  const _message = status.message.replace(
506
565
  MESSAGE_QUOTE_RE,
@@ -671,11 +730,6 @@ function isCustomEvent(event) {
671
730
  }
672
731
  let is_screen_recording = writable(false);
673
732
  onMount(() => {
674
- document.addEventListener("visibilitychange", function() {
675
- if (document.visibilityState === "hidden") {
676
- user_left_page = true;
677
- }
678
- });
679
733
  is_mobile_device = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
680
734
  navigator.userAgent
681
735
  );
@@ -65,16 +65,83 @@ get_js_info().then((js_api_info) => {
65
65
  const dispatch = createEventDispatcher();
66
66
  const mcp_server_url = `${root}gradio_api/mcp/sse`;
67
67
  let tools = [];
68
+ let headers = [];
69
+ let mcp_json_sse;
70
+ let mcp_json_stdio;
71
+ let file_data_present = false;
72
+ const upload_file_mcp_server = {
73
+ command: "uvx",
74
+ args: [
75
+ "--from",
76
+ "gradio[mcp]",
77
+ "gradio",
78
+ "upload-mcp",
79
+ root,
80
+ "<UPLOAD_DIRECTORY>"
81
+ ]
82
+ };
68
83
  async function fetchMcpTools() {
69
84
  try {
70
85
  const response = await fetch(`${root}gradio_api/mcp/schema`);
71
86
  const schema = await response.json();
87
+ file_data_present = schema.map((tool) => tool.meta?.file_data_present).some((present) => present);
72
88
  tools = schema.map((tool) => ({
73
89
  name: tool.name,
74
90
  description: tool.description || "",
75
91
  parameters: tool.inputSchema?.properties || {},
76
92
  expanded: false
77
93
  }));
94
+ headers = schema.map((tool) => tool.meta?.headers || []).flat();
95
+ if (headers.length > 0) {
96
+ mcp_json_sse = {
97
+ mcpServers: {
98
+ gradio: {
99
+ url: mcp_server_url,
100
+ headers: headers.reduce((accumulator, current_key) => {
101
+ accumulator[current_key] = "<YOUR_HEADER_VALUE>";
102
+ return accumulator;
103
+ }, {})
104
+ }
105
+ }
106
+ };
107
+ mcp_json_stdio = {
108
+ mcpServers: {
109
+ gradio: {
110
+ command: "npx",
111
+ args: [
112
+ "mcp-remote",
113
+ mcp_server_url,
114
+ "--transport",
115
+ "sse-only",
116
+ ...headers.map((header) => [
117
+ "--header",
118
+ `${header}: <YOUR_HEADER_VALUE>`
119
+ ]).flat()
120
+ ]
121
+ }
122
+ }
123
+ };
124
+ } else {
125
+ mcp_json_sse = {
126
+ mcpServers: {
127
+ gradio: {
128
+ url: mcp_server_url
129
+ }
130
+ }
131
+ };
132
+ mcp_json_stdio = {
133
+ mcpServers: {
134
+ gradio: {
135
+ command: "npx",
136
+ args: ["mcp-remote", mcp_server_url, "--transport", "sse-only"]
137
+ }
138
+ }
139
+ };
140
+ if (file_data_present) {
141
+ mcp_json_sse.mcpServers.upload_files_to_gradio = upload_file_mcp_server;
142
+ mcp_json_stdio.mcpServers.upload_files_to_gradio = upload_file_mcp_server;
143
+ }
144
+ }
78
145
  } catch (error) {
79
146
  console.error("Failed to fetch MCP tools:", error);
80
147
  tools = [];
@@ -180,7 +247,7 @@ onMount(() => {
180
247
  <div class="mcp-url">
181
248
  <label
182
249
  ><span class="status-indicator active">●</span>MCP Server
183
- URL</label
250
+ URL (SSE)</label
184
251
  >
185
252
  <div class="textbox">
186
253
  <input type="text" readonly value={mcp_server_url} />
@@ -239,45 +306,42 @@ onMount(() => {
239
306
  </div>
240
307
  <p>&nbsp;</p>
241
308
 
242
- <strong>Integration</strong>: To add this MCP to clients that
309
+ <strong>SSE Transport</strong>: To add this MCP to clients that
243
310
  support SSE (e.g. Cursor, Windsurf, Cline), simply add the
244
- following configuration to your MCP config:
311
+ following configuration to your MCP config.
245
312
  <p>&nbsp;</p>
246
313
  <Block>
247
314
  <code>
248
315
  <div class="copy">
249
316
  <CopyButton
250
- code={JSON.stringify(
251
- {
252
- mcpServers: {
253
- gradio: {
254
- url: mcp_server_url
255
- }
256
- }
257
- },
258
- null,
259
- 2
260
- )}
317
+ code={JSON.stringify(mcp_json_sse, null, 2)}
261
318
  />
262
319
  </div>
263
320
  <div>
264
- <pre>{JSON.stringify(
265
- {
266
- mcpServers: {
267
- gradio: {
268
- url: mcp_server_url
269
- }
270
- }
271
- },
272
- null,
273
- 2
274
- )}</pre>
321
+ <pre>{JSON.stringify(mcp_json_sse, null, 2)}</pre>
275
322
  </div>
276
323
  </code>
277
324
  </Block>
278
- <p>&nbsp;</p>
279
- <em>Experimental stdio support</em>: For clients that only
280
- support stdio, first
325
+ {#if file_data_present}
326
+ <p>&nbsp;</p>
327
+ <em>Note about files</em>: Gradio MCP servers that have files
328
+ as inputs need the files as URLs, so the
329
+ <code>upload_files_to_gradio</code>
330
+ tool is included for your convenience. This tool can upload files
331
+ located in the specified <code>UPLOAD_DIRECTORY</code>
332
+ argument (an absolute path in your local machine) or any of its
333
+ subdirectories to the Gradio app. You can omit this tool if you
334
+ are fine manually uploading files yourself and providing the URLs.
335
+ Before using this tool, you must have
336
+ <a
337
+ href="https://docs.astral.sh/uv/getting-started/installation/"
338
+ target="_blank">uv installed</a
339
+ >.
340
+ <p>&nbsp;</p>
341
+ {/if}
342
+
343
+ <strong>STDIO Transport</strong>: For clients that only support
344
+ stdio (e.g. Claude Desktop), first
281
345
  <a href="https://nodejs.org/en/download/" target="_blank"
282
346
  >install Node.js</a
283
347
  >. Then, you can use the following command:
@@ -286,43 +350,11 @@ onMount(() => {
286
350
  <code>
287
351
  <div class="copy">
288
352
  <CopyButton
289
- code={JSON.stringify(
290
- {
291
- mcpServers: {
292
- gradio: {
293
- command: "npx",
294
- args: [
295
- "mcp-remote",
296
- mcp_server_url,
297
- "--transport",
298
- "sse-only"
299
- ]
300
- }
301
- }
302
- },
303
- null,
304
- 2
305
- )}
353
+ code={JSON.stringify(mcp_json_stdio, null, 2)}
306
354
  />
307
355
  </div>
308
356
  <div>
309
- <pre>{JSON.stringify(
310
- {
311
- mcpServers: {
312
- gradio: {
313
- command: "npx",
314
- args: [
315
- "mcp-remote",
316
- mcp_server_url,
317
- "--transport",
318
- "sse-only"
319
- ]
320
- }
321
- }
322
- },
323
- null,
324
- 2
325
- )}</pre>
357
+ <pre>{JSON.stringify(mcp_json_stdio, null, 2)}</pre>
326
358
  </div>
327
359
  </code>
328
360
  </Block>
@@ -398,21 +430,22 @@ onMount(() => {
398
430
  {/if}
399
431
 
400
432
  {#if current_language !== "mcp"}
401
- {#each dependencies as dependency, dependency_index}
433
+ {#each dependencies as dependency}
402
434
  {#if dependency.show_api && info.named_endpoints["/" + dependency.api_name]}
403
435
  <div class="endpoint-container">
404
436
  <CodeSnippet
405
- named={true}
406
437
  endpoint_parameters={info.named_endpoints[
407
438
  "/" + dependency.api_name
408
439
  ].parameters}
409
440
  {dependency}
410
- {dependency_index}
411
441
  {current_language}
412
442
  {root}
413
443
  {space_id}
414
444
  {username}
415
445
  api_prefix={app.api_prefix}
446
+ api_description={info.named_endpoints[
447
+ "/" + dependency.api_name
448
+ ].description}
416
449
  />
417
450
 
418
451
  <ParametersSnippet
@@ -3,14 +3,13 @@ import { represent_value, is_potentially_nested_file_data } from "./utils";
3
3
  import { Block } from "@gradio/atoms";
4
4
  import EndpointDetail from "./EndpointDetail.svelte";
5
5
  export let dependency;
6
- export let dependency_index;
7
6
  export let root;
8
7
  export let api_prefix;
9
8
  export let space_id;
10
9
  export let endpoint_parameters;
11
- export let named;
12
10
  export let username;
13
11
  export let current_language;
12
+ export let api_description = null;
14
13
  let python_code;
15
14
  let js_code;
16
15
  let bash_post_code;
@@ -29,11 +28,10 @@ $:
29
28
  </script>
30
29
 
31
30
  <div class="container">
32
- {#if named}
33
- <EndpointDetail {named} api_name={dependency.api_name} />
34
- {:else}
35
- <EndpointDetail {named} fn_index={dependency_index} />
36
- {/if}
31
+ <EndpointDetail
32
+ api_name={dependency.api_name}
33
+ description={api_description}
34
+ />
37
35
  {#if current_language === "python"}
38
36
  <Block>
39
37
  <code>
@@ -86,9 +84,9 @@ const example{component} = await response_{i}.blob();
86
84
  const client = await Client.connect(<span class="token string"
87
85
  >"{space_id || root}"</span
88
86
  >{#if username !== null}, &lbrace;auth: ["{username}", **password**]&rbrace;{/if});
89
- const result = await client.predict({#if named}<span class="api-name"
90
- >"/{dependency.api_name}"</span
91
- >{:else}{dependency_index}{/if}, &lbrace; <!--
87
+ const result = await client.predict(<span class="api-name"
88
+ >"/{dependency.api_name}"</span
89
+ >, &lbrace; <!--
92
90
  -->{#each endpoint_parameters as { label, parameter_name, type, python_type, component, example_input, serializer }, i}<!--
93
91
  -->{#if blob_components.includes(component)}<!--
94
92
  -->
@@ -3,14 +3,13 @@ import type { Dependency } from "../types";
3
3
  declare const __propDef: {
4
4
  props: {
5
5
  dependency: Dependency;
6
- dependency_index: number;
7
6
  root: string;
8
7
  api_prefix: string;
9
8
  space_id: string | null;
10
9
  endpoint_parameters: any;
11
- named: boolean;
12
10
  username: string | null;
13
11
  current_language: "python" | "javascript" | "bash";
12
+ api_description?: (string | null) | undefined;
14
13
  };
15
14
  events: {
16
15
  [evt: string]: CustomEvent<any>;
@@ -1,19 +1,12 @@
1
1
  <script>export let api_name = null;
2
- export let fn_index = null;
3
- export let named;
2
+ export let description = null;
4
3
  </script>
5
4
 
6
- {#if named}
7
- <h3>
8
- api_name:
9
- <span class="post">{"/" + api_name}</span>
10
- </h3>
11
- {:else}
12
- <h3>
13
- fn_index:
14
- <span class="post">{fn_index}</span>
15
- </h3>
16
- {/if}
5
+ <h3>
6
+ API name:
7
+ <span class="post">{"/" + api_name}</span>
8
+ <span class="desc">{description}</span>
9
+ </h3>
17
10
 
18
11
  <style>
19
12
  h3 {
@@ -33,4 +26,10 @@ export let named;
33
26
  color: var(--color-accent);
34
27
  font-weight: var(--weight-semibold);
35
28
  }
29
+
30
+ .desc {
31
+ color: var(--body-text-color-subdued);
32
+ font-size: var(--text-lg);
33
+ margin-top: var(--size-1);
34
+ }
36
35
  </style>
@@ -2,8 +2,7 @@ import { SvelteComponent } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
4
  api_name?: (string | null) | undefined;
5
- fn_index?: (number | null) | undefined;
6
- named: boolean;
5
+ description?: (string | null) | undefined;
7
6
  };
8
7
  events: {
9
8
  [evt: string]: CustomEvent<any>;
package/dist/src/init.js CHANGED
@@ -48,8 +48,12 @@ export function create_components(initial_layout) {
48
48
  if (instance_map) {
49
49
  // re-render in reload mode
50
50
  components.forEach((c) => {
51
- if (c.props.value == null && c.id in instance_map) {
52
- c.props.value = instance_map[c.id].props.value;
51
+ if (c.props.value == null && c.key) {
52
+ // If the component has a key, we preserve its value by finding a matching instance with the same key
53
+ const matching_instance = Object.values(instance_map).find((instance) => instance.key === c.key);
54
+ if (matching_instance) {
55
+ c.props.value = matching_instance.props.value;
56
+ }
53
57
  }
54
58
  });
55
59
  }
package/package.json CHANGED
@@ -1,67 +1,67 @@
1
1
  {
2
2
  "name": "@gradio/core",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "devDependencies": {
6
- "@gradio/accordion": "^0.5.19",
7
6
  "@gradio/atoms": "^0.16.3",
8
- "@gradio/audio": "^0.17.20",
7
+ "@gradio/audio": "^0.17.21",
8
+ "@gradio/accordion": "^0.5.20",
9
+ "@gradio/annotatedimage": "^0.9.25",
9
10
  "@gradio/box": "^0.2.21",
10
- "@gradio/annotatedimage": "^0.9.24",
11
- "@gradio/button": "^0.5.6",
12
- "@gradio/chatbot": "^0.26.17",
13
- "@gradio/checkbox": "^0.4.25",
14
- "@gradio/checkboxgroup": "^0.6.24",
15
- "@gradio/client": "^1.15.5",
16
- "@gradio/colorpicker": "^0.4.24",
17
- "@gradio/code": "^0.14.10",
11
+ "@gradio/button": "^0.5.7",
12
+ "@gradio/chatbot": "^0.26.18",
13
+ "@gradio/checkbox": "^0.4.26",
14
+ "@gradio/checkboxgroup": "^0.6.25",
15
+ "@gradio/client": "^1.15.6",
16
+ "@gradio/code": "^0.14.11",
17
+ "@gradio/colorpicker": "^0.4.25",
18
+ "@gradio/dataset": "^0.4.27",
19
+ "@gradio/datetime": "^0.3.18",
18
20
  "@gradio/column": "^0.2.1",
19
- "@gradio/dataframe": "^0.18.1",
20
- "@gradio/dataset": "^0.4.26",
21
- "@gradio/datetime": "^0.3.17",
22
- "@gradio/downloadbutton": "^0.4.6",
23
- "@gradio/dropdown": "^0.9.24",
24
- "@gradio/fallback": "^0.4.24",
25
- "@gradio/file": "^0.12.23",
26
- "@gradio/fileexplorer": "^0.5.34",
27
- "@gradio/gallery": "^0.15.26",
21
+ "@gradio/dataframe": "^0.18.3",
22
+ "@gradio/downloadbutton": "^0.4.7",
23
+ "@gradio/dropdown": "^0.9.25",
24
+ "@gradio/fallback": "^0.4.25",
25
+ "@gradio/file": "^0.12.24",
26
+ "@gradio/fileexplorer": "^0.5.35",
28
27
  "@gradio/form": "^0.2.21",
29
28
  "@gradio/group": "^0.2.0",
29
+ "@gradio/gallery": "^0.15.27",
30
+ "@gradio/highlightedtext": "^0.9.8",
31
+ "@gradio/html": "^0.6.17",
30
32
  "@gradio/icons": "^0.12.0",
31
- "@gradio/highlightedtext": "^0.9.7",
32
- "@gradio/html": "^0.6.16",
33
- "@gradio/image": "^0.22.12",
34
- "@gradio/imageeditor": "^0.16.0",
35
- "@gradio/imageslider": "^0.2.8",
36
- "@gradio/label": "^0.5.16",
37
- "@gradio/json": "^0.5.26",
38
- "@gradio/markdown": "^0.13.17",
39
- "@gradio/model3d": "^0.14.19",
33
+ "@gradio/image": "^0.22.13",
34
+ "@gradio/imageeditor": "^0.16.1",
35
+ "@gradio/imageslider": "^0.2.9",
36
+ "@gradio/json": "^0.5.27",
40
37
  "@gradio/browserstate": "^0.3.2",
41
- "@gradio/multimodaltextbox": "^0.10.12",
42
- "@gradio/nativeplot": "^0.7.1",
43
- "@gradio/number": "^0.6.1",
44
- "@gradio/paramviewer": "^0.7.12",
45
- "@gradio/plot": "^0.9.19",
38
+ "@gradio/model3d": "^0.14.20",
39
+ "@gradio/markdown": "^0.13.18",
40
+ "@gradio/label": "^0.5.17",
41
+ "@gradio/nativeplot": "^0.7.2",
42
+ "@gradio/number": "^0.6.2",
43
+ "@gradio/paramviewer": "^0.7.13",
44
+ "@gradio/multimodaltextbox": "^0.10.13",
45
+ "@gradio/plot": "^0.9.20",
46
+ "@gradio/radio": "^0.7.8",
46
47
  "@gradio/row": "^0.2.1",
47
- "@gradio/radio": "^0.7.7",
48
- "@gradio/sidebar": "^0.1.17",
49
- "@gradio/simpleimage": "^0.8.34",
50
- "@gradio/simpledropdown": "^0.3.24",
51
- "@gradio/simpletextbox": "^0.3.25",
52
- "@gradio/sketchbox": "^0.6.12",
53
- "@gradio/slider": "^0.6.13",
54
- "@gradio/tabitem": "^0.5.0",
48
+ "@gradio/sidebar": "^0.1.18",
49
+ "@gradio/simpletextbox": "^0.3.26",
50
+ "@gradio/simpleimage": "^0.8.35",
51
+ "@gradio/sketchbox": "^0.6.13",
52
+ "@gradio/slider": "^0.6.14",
53
+ "@gradio/simpledropdown": "^0.3.25",
55
54
  "@gradio/state": "^0.1.2",
56
- "@gradio/textbox": "^0.10.16",
55
+ "@gradio/statustracker": "^0.10.15",
57
56
  "@gradio/tabs": "^0.4.5",
57
+ "@gradio/textbox": "^0.10.17",
58
+ "@gradio/tabitem": "^0.5.0",
58
59
  "@gradio/theme": "^0.4.0",
59
60
  "@gradio/timer": "^0.4.5",
60
- "@gradio/upload": "^0.16.10",
61
- "@gradio/uploadbutton": "^0.9.6",
62
- "@gradio/statustracker": "^0.10.14",
61
+ "@gradio/upload": "^0.16.11",
62
+ "@gradio/uploadbutton": "^0.9.7",
63
63
  "@gradio/utils": "^0.10.2",
64
- "@gradio/video": "^0.14.20",
64
+ "@gradio/video": "^0.14.21",
65
65
  "@gradio/wasm": "^0.18.1"
66
66
  },
67
67
  "msw": {
package/src/Blocks.svelte CHANGED
@@ -56,6 +56,7 @@
56
56
  export let max_file_size: number | undefined = undefined;
57
57
  export let initial_layout: ComponentMeta | undefined = undefined;
58
58
  export let css: string | null | undefined = null;
59
+ let broken_connection = false;
59
60
 
60
61
  let {
61
62
  layout: _layout,
@@ -89,6 +90,8 @@
89
90
  }
90
91
 
91
92
  async function run(): Promise<void> {
93
+ await setupi18n(app.config?.i18n_translations || undefined);
94
+
92
95
  layout_creating = true;
93
96
  await create_layout({
94
97
  components,
@@ -273,13 +276,17 @@
273
276
 
274
277
  let _error_id = -1;
275
278
 
276
- let user_left_page = false;
277
-
278
279
  const MESSAGE_QUOTE_RE = /^'([^]+)'$/;
279
280
 
280
281
  const DUPLICATE_MESSAGE = $_("blocks.long_requests_queue");
281
282
  const MOBILE_QUEUE_WARNING = $_("blocks.connection_can_break");
282
- const MOBILE_RECONNECT_MESSAGE = $_("blocks.lost_connection");
283
+ const LOST_CONNECTION_MESSAGE =
284
+ "Connection to the server was lost. Attempting reconnection...";
285
+ const CHANGED_CONNECTION_MESSAGE =
286
+ "Reconnected to server, but the server has changed. You may need to <a href=''>refresh the page</a>.";
287
+ const RECONNECTION_MESSAGE = "Connection re-established.";
288
+ const SESSION_NOT_FOUND_MESSAGE =
289
+ "Session not found - this is likely because the machine you were connected to has changed. <a href=''>Refresh the page</a> to continue.";
283
290
  const WAITING_FOR_INPUTS_MESSAGE = $_("blocks.waiting_for_inputs");
284
291
  const SHOW_DUPLICATE_MESSAGE_ON_ETA = 15;
285
292
  const SHOW_MOBILE_QUEUE_WARNING_ON_ETA = 10;
@@ -422,6 +429,43 @@
422
429
  }
423
430
  }
424
431
 
432
+ async function reconnect(): Promise<void> {
433
+ const connection_status = await app.reconnect();
434
+ if (connection_status === "broken") {
435
+ setTimeout(reconnect, 1000);
436
+ } else if (connection_status === "changed") {
437
+ broken_connection = false;
438
+ messages = [
439
+ new_message(
440
+ "Changed Connection",
441
+ CHANGED_CONNECTION_MESSAGE,
442
+ -1,
443
+ "info",
444
+ 3,
445
+ true
446
+ ),
447
+ ...messages.map((m) =>
448
+ m.message === LOST_CONNECTION_MESSAGE ? { ...m, visible: false } : m
449
+ )
450
+ ];
451
+ } else if (connection_status === "connected") {
452
+ broken_connection = false;
453
+ messages = [
454
+ new_message(
455
+ "Reconnected",
456
+ RECONNECTION_MESSAGE,
457
+ -1,
458
+ "success",
459
+ null,
460
+ true
461
+ ),
462
+ ...messages.map((m) =>
463
+ m.message === LOST_CONNECTION_MESSAGE ? { ...m, visible: false } : m
464
+ )
465
+ ];
466
+ }
467
+ }
468
+
425
469
  async function make_prediction(
426
470
  payload: Payload,
427
471
  streaming = false
@@ -565,6 +609,35 @@
565
609
 
566
610
  /* eslint-disable complexity */
567
611
  function handle_status_update(message: StatusMessage): void {
612
+ if (message.broken && !broken_connection) {
613
+ messages = [
614
+ new_message(
615
+ "Broken Connection",
616
+ LOST_CONNECTION_MESSAGE,
617
+ -1,
618
+ "error",
619
+ null,
620
+ true
621
+ ),
622
+ ...messages
623
+ ];
624
+
625
+ broken_connection = true;
626
+ setTimeout(reconnect, 1000);
627
+ }
628
+ if (message.session_not_found) {
629
+ messages = [
630
+ new_message(
631
+ "Session Not Found",
632
+ SESSION_NOT_FOUND_MESSAGE,
633
+ -1,
634
+ "error",
635
+ null,
636
+ true
637
+ ),
638
+ ...messages
639
+ ];
640
+ }
568
641
  const { fn_index, ...status } = message;
569
642
  if (status.stage === "streaming" && status.time_limit) {
570
643
  dep.inputs.forEach((id) => {
@@ -634,16 +707,11 @@
634
707
  });
635
708
  submit_map.delete(dep_index);
636
709
  }
637
- if (status.broken && is_mobile_device && user_left_page) {
638
- window.setTimeout(() => {
639
- messages = [
640
- new_message("Error", MOBILE_RECONNECT_MESSAGE, fn_index, "error"),
641
- ...messages
642
- ];
643
- }, 0);
644
- wait_then_trigger_api_call(dep.id, payload.trigger_id, event_data);
645
- user_left_page = false;
646
- } else if (status.stage === "error") {
710
+ if (
711
+ status.stage === "error" &&
712
+ !broken_connection &&
713
+ !message.session_not_found
714
+ ) {
647
715
  if (status.message) {
648
716
  const _message = status.message.replace(
649
717
  MESSAGE_QUOTE_RE,
@@ -849,12 +917,6 @@
849
917
  let is_screen_recording = writable(false);
850
918
 
851
919
  onMount(() => {
852
- document.addEventListener("visibilitychange", function () {
853
- if (document.visibilityState === "hidden") {
854
- user_left_page = true;
855
- }
856
- });
857
-
858
920
  is_mobile_device =
859
921
  /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
860
922
  navigator.userAgent
@@ -112,11 +112,30 @@
112
112
  }
113
113
 
114
114
  let tools: Tool[] = [];
115
+ let headers: string[] = [];
116
+ let mcp_json_sse: any;
117
+ let mcp_json_stdio: any;
118
+ let file_data_present = false;
119
+
120
+ const upload_file_mcp_server = {
121
+ command: "uvx",
122
+ args: [
123
+ "--from",
124
+ "gradio[mcp]",
125
+ "gradio",
126
+ "upload-mcp",
127
+ root,
128
+ "<UPLOAD_DIRECTORY>"
129
+ ]
130
+ };
115
131
 
116
132
  async function fetchMcpTools() {
117
133
  try {
118
134
  const response = await fetch(`${root}gradio_api/mcp/schema`);
119
135
  const schema = await response.json();
136
+ file_data_present = schema
137
+ .map((tool: any) => tool.meta?.file_data_present)
138
+ .some((present: boolean) => present);
120
139
 
121
140
  tools = schema.map((tool: any) => ({
122
141
  name: tool.name,
@@ -124,6 +143,62 @@
124
143
  parameters: tool.inputSchema?.properties || {},
125
144
  expanded: false
126
145
  }));
146
+ headers = schema.map((tool: any) => tool.meta?.headers || []).flat();
147
+ if (headers.length > 0) {
148
+ mcp_json_sse = {
149
+ mcpServers: {
150
+ gradio: {
151
+ url: mcp_server_url,
152
+ headers: headers.reduce((accumulator, current_key) => {
153
+ // @ts-ignore
154
+ accumulator[current_key] = "<YOUR_HEADER_VALUE>";
155
+ return accumulator;
156
+ }, {})
157
+ }
158
+ }
159
+ };
160
+ mcp_json_stdio = {
161
+ mcpServers: {
162
+ gradio: {
163
+ command: "npx",
164
+ args: [
165
+ "mcp-remote",
166
+ mcp_server_url,
167
+ "--transport",
168
+ "sse-only",
169
+ ...headers
170
+ .map((header) => [
171
+ "--header",
172
+ `${header}: <YOUR_HEADER_VALUE>`
173
+ ])
174
+ .flat()
175
+ ]
176
+ }
177
+ }
178
+ };
179
+ } else {
180
+ mcp_json_sse = {
181
+ mcpServers: {
182
+ gradio: {
183
+ url: mcp_server_url
184
+ }
185
+ }
186
+ };
187
+ mcp_json_stdio = {
188
+ mcpServers: {
189
+ gradio: {
190
+ command: "npx",
191
+ args: ["mcp-remote", mcp_server_url, "--transport", "sse-only"]
192
+ }
193
+ }
194
+ };
195
+ if (file_data_present) {
196
+ mcp_json_sse.mcpServers.upload_files_to_gradio =
197
+ upload_file_mcp_server;
198
+ mcp_json_stdio.mcpServers.upload_files_to_gradio =
199
+ upload_file_mcp_server;
200
+ }
201
+ }
127
202
  } catch (error) {
128
203
  console.error("Failed to fetch MCP tools:", error);
129
204
  tools = [];
@@ -235,7 +310,7 @@
235
310
  <div class="mcp-url">
236
311
  <label
237
312
  ><span class="status-indicator active">●</span>MCP Server
238
- URL</label
313
+ URL (SSE)</label
239
314
  >
240
315
  <div class="textbox">
241
316
  <input type="text" readonly value={mcp_server_url} />
@@ -294,45 +369,42 @@
294
369
  </div>
295
370
  <p>&nbsp;</p>
296
371
 
297
- <strong>Integration</strong>: To add this MCP to clients that
372
+ <strong>SSE Transport</strong>: To add this MCP to clients that
298
373
  support SSE (e.g. Cursor, Windsurf, Cline), simply add the
299
- following configuration to your MCP config:
374
+ following configuration to your MCP config.
300
375
  <p>&nbsp;</p>
301
376
  <Block>
302
377
  <code>
303
378
  <div class="copy">
304
379
  <CopyButton
305
- code={JSON.stringify(
306
- {
307
- mcpServers: {
308
- gradio: {
309
- url: mcp_server_url
310
- }
311
- }
312
- },
313
- null,
314
- 2
315
- )}
380
+ code={JSON.stringify(mcp_json_sse, null, 2)}
316
381
  />
317
382
  </div>
318
383
  <div>
319
- <pre>{JSON.stringify(
320
- {
321
- mcpServers: {
322
- gradio: {
323
- url: mcp_server_url
324
- }
325
- }
326
- },
327
- null,
328
- 2
329
- )}</pre>
384
+ <pre>{JSON.stringify(mcp_json_sse, null, 2)}</pre>
330
385
  </div>
331
386
  </code>
332
387
  </Block>
333
- <p>&nbsp;</p>
334
- <em>Experimental stdio support</em>: For clients that only
335
- support stdio, first
388
+ {#if file_data_present}
389
+ <p>&nbsp;</p>
390
+ <em>Note about files</em>: Gradio MCP servers that have files
391
+ as inputs need the files as URLs, so the
392
+ <code>upload_files_to_gradio</code>
393
+ tool is included for your convenience. This tool can upload files
394
+ located in the specified <code>UPLOAD_DIRECTORY</code>
395
+ argument (an absolute path in your local machine) or any of its
396
+ subdirectories to the Gradio app. You can omit this tool if you
397
+ are fine manually uploading files yourself and providing the URLs.
398
+ Before using this tool, you must have
399
+ <a
400
+ href="https://docs.astral.sh/uv/getting-started/installation/"
401
+ target="_blank">uv installed</a
402
+ >.
403
+ <p>&nbsp;</p>
404
+ {/if}
405
+
406
+ <strong>STDIO Transport</strong>: For clients that only support
407
+ stdio (e.g. Claude Desktop), first
336
408
  <a href="https://nodejs.org/en/download/" target="_blank"
337
409
  >install Node.js</a
338
410
  >. Then, you can use the following command:
@@ -341,43 +413,11 @@
341
413
  <code>
342
414
  <div class="copy">
343
415
  <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
- )}
416
+ code={JSON.stringify(mcp_json_stdio, null, 2)}
361
417
  />
362
418
  </div>
363
419
  <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>
420
+ <pre>{JSON.stringify(mcp_json_stdio, null, 2)}</pre>
381
421
  </div>
382
422
  </code>
383
423
  </Block>
@@ -453,21 +493,22 @@
453
493
  {/if}
454
494
 
455
495
  {#if current_language !== "mcp"}
456
- {#each dependencies as dependency, dependency_index}
496
+ {#each dependencies as dependency}
457
497
  {#if dependency.show_api && info.named_endpoints["/" + dependency.api_name]}
458
498
  <div class="endpoint-container">
459
499
  <CodeSnippet
460
- named={true}
461
500
  endpoint_parameters={info.named_endpoints[
462
501
  "/" + dependency.api_name
463
502
  ].parameters}
464
503
  {dependency}
465
- {dependency_index}
466
504
  {current_language}
467
505
  {root}
468
506
  {space_id}
469
507
  {username}
470
508
  api_prefix={app.api_prefix}
509
+ api_description={info.named_endpoints[
510
+ "/" + dependency.api_name
511
+ ].description}
471
512
  />
472
513
 
473
514
  <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
@@ -114,8 +114,14 @@ export function create_components(initial_layout: ComponentMeta | undefined): {
114
114
  if (instance_map) {
115
115
  // re-render in reload mode
116
116
  components.forEach((c) => {
117
- if (c.props.value == null && c.id in instance_map) {
118
- c.props.value = instance_map[c.id].props.value;
117
+ if (c.props.value == null && c.key) {
118
+ // If the component has a key, we preserve its value by finding a matching instance with the same key
119
+ const matching_instance = Object.values(instance_map).find(
120
+ (instance) => instance.key === c.key
121
+ );
122
+ if (matching_instance) {
123
+ c.props.value = matching_instance.props.value;
124
+ }
119
125
  }
120
126
  });
121
127
  }