@gradio/core 1.0.0-dev.3 → 1.0.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.
@@ -3,9 +3,11 @@
3
3
  import CopyButton from "./CopyButton.svelte";
4
4
  import { Tool, Prompt, Resource } from "@gradio/icons";
5
5
  import { format_latency, get_color_from_success_rate } from "./utils";
6
+ import PercentileChart from "./PercentileChart.svelte";
6
7
 
7
8
  export let mcp_server_active: boolean;
8
9
  export let mcp_server_url_streamable: string;
10
+ export let root: string;
9
11
  export let tools: Tool[];
10
12
  export let all_tools: Tool[] = [];
11
13
  export let selected_tools: Set<string> = new Set();
@@ -69,7 +71,7 @@
69
71
  "gradio[mcp]",
70
72
  "gradio",
71
73
  "upload-mcp",
72
- mcp_server_url_streamable,
74
+ root,
73
75
  "<UPLOAD_DIRECTORY>"
74
76
  ]
75
77
  };
@@ -218,34 +220,19 @@
218
220
  : "⚠︎ No description provided in function docstring"}
219
221
  </span>
220
222
  {#if analytics[tool.meta.endpoint_name]}
221
- <span
222
- class="tool-analytics"
223
- style="color: var(--body-text-color-subdued); margin-left: 1em;"
224
- >
225
- Total requests: {analytics[tool.meta.endpoint_name]
226
- .total_requests}
227
- <span style={color}
228
- >({Math.round(success_rate * 100)}% successful)</span
223
+ {@const endpoint_analytics = analytics[tool.meta.endpoint_name]}
224
+ {@const p50 =
225
+ endpoint_analytics.process_time_percentiles["50th"]}
226
+ <div class="tool-analytics-wrapper" style="margin-left: 1em;">
227
+ <span
228
+ class="tool-analytics"
229
+ style="color: var(--body-text-color-subdued);"
229
230
  >
230
- &nbsp;|&nbsp; p50/p90/p99:
231
- {format_latency(
232
- analytics[tool.meta.endpoint_name].process_time_percentiles[
233
- "50th"
234
- ]
235
- )}
236
- /
237
- {format_latency(
238
- analytics[tool.meta.endpoint_name].process_time_percentiles[
239
- "90th"
240
- ]
241
- )}
242
- /
243
- {format_latency(
244
- analytics[tool.meta.endpoint_name].process_time_percentiles[
245
- "99th"
246
- ]
247
- )}
248
- </span>
231
+ {endpoint_analytics.total_requests} requests ({Math.round(
232
+ success_rate * 100
233
+ )}% successful, p50: {format_latency(p50)})
234
+ </span>
235
+ </div>
249
236
  {/if}
250
237
  </span>
251
238
  <span class="tool-arrow">{tool.expanded ? "▼" : "▶"}</span>
@@ -354,10 +341,16 @@
354
341
  {/if}
355
342
 
356
343
  <style>
344
+ .tool-analytics-wrapper {
345
+ position: relative;
346
+ display: inline-block;
347
+ }
348
+
357
349
  .tool-analytics {
358
350
  font-size: 0.95em;
359
351
  color: var(--body-text-color-subdued);
360
352
  }
353
+
361
354
  .transport-selection {
362
355
  margin-bottom: var(--size-4);
363
356
  }
@@ -0,0 +1,125 @@
1
+ <script lang="ts">
2
+ import { format_latency } from "./utils";
3
+
4
+ export let p50: number;
5
+ export let p90: number;
6
+ export let p99: number;
7
+
8
+ $: max_latency = Math.max(p50, p90, p99);
9
+ </script>
10
+
11
+ <div class="tooltip-chart">
12
+ <div class="tooltip-arrow"></div>
13
+ <div class="chart-bars">
14
+ <div class="chart-bar-container">
15
+ <div class="chart-bar-label">p50</div>
16
+ <div class="chart-bar-wrapper">
17
+ <div
18
+ class="chart-bar"
19
+ style="width: {(p50 / max_latency) * 100}%"
20
+ ></div>
21
+ </div>
22
+ <div class="chart-bar-value">{format_latency(p50)}</div>
23
+ </div>
24
+ <div class="chart-bar-container">
25
+ <div class="chart-bar-label">p90</div>
26
+ <div class="chart-bar-wrapper">
27
+ <div
28
+ class="chart-bar"
29
+ style="width: {(p90 / max_latency) * 100}%"
30
+ ></div>
31
+ </div>
32
+ <div class="chart-bar-value">{format_latency(p90)}</div>
33
+ </div>
34
+ <div class="chart-bar-container">
35
+ <div class="chart-bar-label">p99</div>
36
+ <div class="chart-bar-wrapper">
37
+ <div
38
+ class="chart-bar"
39
+ style="width: {(p99 / max_latency) * 100}%"
40
+ ></div>
41
+ </div>
42
+ <div class="chart-bar-value">{format_latency(p99)}</div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <style>
48
+ .tooltip-chart {
49
+ background: var(--background-fill-primary);
50
+ border: 1px solid var(--border-color-primary);
51
+ border-radius: var(--radius-md);
52
+ padding: var(--size-3);
53
+ box-shadow: var(--shadow-drop-lg);
54
+ min-width: 200px;
55
+ position: relative;
56
+ }
57
+
58
+ .tooltip-arrow {
59
+ position: absolute;
60
+ bottom: -8px;
61
+ left: 50%;
62
+ transform: translateX(-50%);
63
+ width: 0;
64
+ height: 0;
65
+ border-left: 8px solid transparent;
66
+ border-right: 8px solid transparent;
67
+ border-top: 8px solid var(--border-color-primary);
68
+ }
69
+
70
+ .tooltip-arrow::after {
71
+ content: "";
72
+ position: absolute;
73
+ bottom: 1px;
74
+ left: 50%;
75
+ transform: translateX(-50%);
76
+ width: 0;
77
+ height: 0;
78
+ border-left: 7px solid transparent;
79
+ border-right: 7px solid transparent;
80
+ border-top: 7px solid var(--background-fill-primary);
81
+ }
82
+
83
+ .chart-bars {
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: var(--size-2);
87
+ }
88
+
89
+ .chart-bar-container {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: var(--size-2);
93
+ }
94
+
95
+ .chart-bar-label {
96
+ font-size: var(--text-sm);
97
+ font-weight: var(--weight-semibold);
98
+ color: var(--body-text-color);
99
+ min-width: 30px;
100
+ }
101
+
102
+ .chart-bar-wrapper {
103
+ flex: 1;
104
+ height: 16px;
105
+ background: var(--background-fill-secondary);
106
+ border-radius: var(--radius-sm);
107
+ overflow: hidden;
108
+ position: relative;
109
+ }
110
+
111
+ .chart-bar {
112
+ height: 100%;
113
+ background: var(--color-accent);
114
+ border-radius: var(--radius-sm);
115
+ transition: width 0.3s ease;
116
+ }
117
+
118
+ .chart-bar-value {
119
+ font-size: var(--text-sm);
120
+ color: var(--body-text-color);
121
+ min-width: 50px;
122
+ text-align: right;
123
+ font-family: var(--font-mono);
124
+ }
125
+ </style>
package/src/dependency.ts CHANGED
@@ -9,7 +9,11 @@ import { AsyncFunction } from "./init_utils";
9
9
  import { Client, type client_return } from "@gradio/client";
10
10
  import { LoadingStatus, type LoadingStatusArgs } from "@gradio/statustracker";
11
11
  import type { ToastMessage } from "@gradio/statustracker";
12
- import type { StatusMessage, ValidationError } from "@gradio/client";
12
+ import type {
13
+ StatusMessage,
14
+ ValidationError,
15
+ LogMessage
16
+ } from "@gradio/client";
13
17
  const MESSAGE_QUOTE_RE = /^'([^]+)'$/;
14
18
 
15
19
  const NOVALUE = Symbol("NOVALUE");
@@ -36,6 +40,8 @@ export class Dependency {
36
40
  // in the case of chained events, it would be the id of the initial trigger
37
41
  original_trigger_id: number | null = null;
38
42
  show_progress_on: number[] | null = null;
43
+ component_prop_inputs: number[] = [];
44
+ show_progress: "full" | "minimal" | "hidden";
39
45
 
40
46
  functions: {
41
47
  frontend?: (...args: unknown[]) => Promise<unknown[]>;
@@ -49,6 +55,7 @@ export class Dependency {
49
55
  this.inputs = dep_config.inputs;
50
56
  this.outputs = dep_config.outputs;
51
57
  this.connection_type = dep_config.connection;
58
+ this.show_progress = dep_config.show_progress;
52
59
  this.functions = {
53
60
  frontend: dep_config.js
54
61
  ? process_frontend_fn(
@@ -70,6 +77,7 @@ export class Dependency {
70
77
  this.cancels = dep_config.cancels;
71
78
  this.trigger_modes = dep_config.trigger_mode;
72
79
  this.show_progress_on = dep_config.show_progress_on || null;
80
+ this.component_prop_inputs = dep_config.component_prop_inputs || [];
73
81
 
74
82
  for (let i = 0; i < dep_config.event_specific_args?.length || 0; i++) {
75
83
  const key = dep_config.event_specific_args[i];
@@ -146,6 +154,25 @@ interface DispatchEvent {
146
154
  event_data: unknown;
147
155
  }
148
156
 
157
+ type UpdateStateCallback = (
158
+ id: number,
159
+ state: Record<string, unknown>,
160
+ check_visibility?: boolean
161
+ ) => Promise<void>;
162
+ type GetStateCallback = (id: number) => Promise<Record<string, unknown> | null>;
163
+ type RerenderCallback = (
164
+ components: ComponentMeta[],
165
+ layout: LayoutNode
166
+ ) => void;
167
+ type LogCallback = (
168
+ title: string,
169
+ message: string,
170
+ fn_index: number,
171
+ type: ToastMessage["type"],
172
+ duration?: number | null,
173
+ visible?: boolean
174
+ ) => void;
175
+
149
176
  /**
150
177
  * Manages all dependencies for an app acting as a bridge between app state and Dependencies
151
178
  * Responsible for registering dependencies and dispatching events to them
@@ -167,21 +194,11 @@ export class DependencyManager {
167
194
  queue: Set<number> = new Set();
168
195
  add_to_api_calls: (payload: Payload) => void;
169
196
 
170
- update_state_cb: (
171
- id: number,
172
- state: Record<string, unknown>,
173
- check_visibility?: boolean
174
- ) => Promise<void>;
175
- get_state_cb: (id: number) => Promise<Record<string, unknown> | null>;
176
- rerender_cb: (components: ComponentMeta[], layout: LayoutNode) => void;
177
- log_cb: (
178
- title: string,
179
- message: string,
180
- fn_index: number,
181
- type: ToastMessage["type"],
182
- duration?: number | null,
183
- visible?: boolean
184
- ) => void;
197
+ update_state_cb: UpdateStateCallback;
198
+ get_state_cb: GetStateCallback;
199
+ rerender_cb: RerenderCallback;
200
+ log_cb: LogCallback;
201
+
185
202
  loading_stati = new LoadingStatus();
186
203
 
187
204
  constructor(
@@ -205,33 +222,39 @@ export class DependencyManager {
205
222
  add_to_api_calls: (payload: Payload) => void
206
223
  ) {
207
224
  this.add_to_api_calls = add_to_api_calls;
208
- this.client = client;
209
225
  this.log_cb = log_cb;
210
- // this.update_state_cb = update_state_cb;
211
- // this.get_state_cb = get_state_cb;
212
- // this.rerender_cb = rerender_cb;
213
- this.reload(dependencies, update_state_cb, get_state_cb, rerender_cb);
226
+ this.update_state_cb = update_state_cb;
227
+ this.get_state_cb = get_state_cb;
228
+ this.rerender_cb = rerender_cb;
229
+ this.client = client;
230
+ this.reload(
231
+ dependencies,
232
+ update_state_cb,
233
+ get_state_cb,
234
+ rerender_cb,
235
+ client
236
+ );
214
237
  }
215
238
 
216
239
  reload(
217
240
  dependencies: IDependency[],
218
- update_state,
219
- get_state,
220
- rerender,
221
- client
241
+ update_state: UpdateStateCallback,
242
+ get_state: GetStateCallback,
243
+ rerender: RerenderCallback,
244
+ client: Client
222
245
  ) {
223
246
  const { by_id, by_event } = this.create(dependencies);
224
247
  this.dependencies_by_event = by_event;
225
248
  this.dependencies_by_fn = by_id;
249
+ this.client = client;
250
+ this.update_state_cb = update_state;
251
+ this.get_state_cb = get_state;
252
+ this.rerender_cb = rerender;
226
253
  for (const [dep_id, dep] of this.dependencies_by_fn) {
227
254
  for (const [output_id] of dep.targets) {
228
255
  this.set_event_args(output_id, dep.event_args);
229
256
  }
230
257
  }
231
- this.client = client;
232
- this.update_state_cb = update_state;
233
- this.get_state_cb = get_state;
234
- this.rerender_cb = rerender;
235
258
  this.register_loading_stati(by_id);
236
259
  }
237
260
  register_loading_stati(deps: Map<number, Dependency>): void {
@@ -239,7 +262,8 @@ export class DependencyManager {
239
262
  this.loading_stati.register(
240
263
  dep.id,
241
264
  dep.show_progress_on || dep.outputs,
242
- dep.inputs
265
+ dep.inputs,
266
+ dep.show_progress
243
267
  );
244
268
  }
245
269
  }
@@ -324,7 +348,10 @@ export class DependencyManager {
324
348
  this.update_loading_stati_state();
325
349
  }
326
350
 
327
- const data_payload = await this.gather_state(dep.inputs);
351
+ const data_payload = await this.gather_state(
352
+ dep.inputs,
353
+ dep.component_prop_inputs
354
+ );
328
355
  const unset_args = await Promise.all(
329
356
  dep.targets.map(([output_id]) =>
330
357
  this.set_event_args(output_id, dep.event_args)
@@ -354,7 +381,7 @@ export class DependencyManager {
354
381
  data: data_payload,
355
382
  event_data: event_meta.event_data
356
383
  };
357
- submission!.send_chunk(payload);
384
+ submission!.send_chunk(payload as any);
358
385
  unset_args.forEach((fn) => fn());
359
386
  continue;
360
387
  }
@@ -375,7 +402,7 @@ export class DependencyManager {
375
402
  if (dep_submission.type === "void") {
376
403
  unset_args.forEach((fn) => fn());
377
404
  } else if (dep_submission.type === "data") {
378
- this.handle_data(dep.outputs, dep_submission.data);
405
+ await this.handle_data(dep.outputs, dep_submission.data);
379
406
  unset_args.forEach((fn) => fn());
380
407
  } else {
381
408
  let stream_state: "open" | "closed" | "waiting" | null = null;
@@ -408,7 +435,7 @@ export class DependencyManager {
408
435
  index += 1;
409
436
  if (result === null) continue;
410
437
  if (result.type === "data") {
411
- this.handle_data(dep.outputs, result.data);
438
+ await this.handle_data(dep.outputs, result.data);
412
439
  }
413
440
  if (result.type === "status") {
414
441
  if (
@@ -495,8 +522,9 @@ export class DependencyManager {
495
522
  (_, b) => b
496
523
  );
497
524
  this.log_cb(
498
- result._title ?? "Error",
499
- _message,
525
+ //@ts-ignore
526
+ result?._title ?? "Error",
527
+ _message || "",
500
528
  fn_index,
501
529
  "error",
502
530
  status.duration,
@@ -516,6 +544,12 @@ export class DependencyManager {
516
544
  }
517
545
 
518
546
  if (result.type === "render") {
547
+ this.loading_stati.update({
548
+ status: "complete",
549
+ fn_index: dep.id,
550
+ stream_state: null
551
+ });
552
+ this.update_loading_stati_state();
519
553
  const { layout, components, render_id, dependencies } =
520
554
  result.data;
521
555
 
@@ -660,52 +694,61 @@ export class DependencyManager {
660
694
  * @param data the data to update the components with
661
695
  * */
662
696
  async handle_data(outputs: number[], data: unknown[]) {
663
- outputs.forEach(async (output_id, i) => {
664
- const _data = data[i] === undefined ? NOVALUE : data[i];
665
- if (_data === NOVALUE) return;
666
-
667
- if (is_prop_update(_data)) {
668
- let pending_visibility_update = false;
669
- let pending_visibility_value = null;
670
- for (const [update_key, update_value] of Object.entries(_data)) {
671
- if (update_key === "__type__") continue;
672
- if (update_key === "visible") {
673
- pending_visibility_update = true;
674
- pending_visibility_value = update_value;
675
- continue;
697
+ await Promise.all(
698
+ outputs.map(async (output_id, i) => {
699
+ const _data = data[i] === undefined ? NOVALUE : data[i];
700
+ if (_data === NOVALUE) return;
701
+
702
+ if (is_prop_update(_data)) {
703
+ let pending_visibility_update = false;
704
+ let pending_visibility_value = null;
705
+ for (const [update_key, update_value] of Object.entries(_data)) {
706
+ if (update_key === "__type__") continue;
707
+ if (update_key === "visible") {
708
+ pending_visibility_update = true;
709
+ pending_visibility_value = update_value;
710
+ continue;
711
+ }
712
+ await this.update_state_cb(
713
+ outputs[i],
714
+ {
715
+ [update_key]: update_value
716
+ },
717
+ false
718
+ );
676
719
  }
677
- await this.update_state_cb(
678
- outputs[i],
679
- {
680
- [update_key]: update_value
681
- },
682
- false
683
- );
684
- }
685
- if (pending_visibility_update) {
686
- await this.update_state_cb(
687
- outputs[i],
688
- {
689
- visible: pending_visibility_value
690
- },
691
- true
692
- );
720
+ if (pending_visibility_update) {
721
+ await this.update_state_cb(
722
+ outputs[i],
723
+ {
724
+ visible: pending_visibility_value
725
+ },
726
+ true
727
+ );
728
+ }
729
+ } else {
730
+ await this.update_state_cb(output_id, { value: _data }, false);
693
731
  }
694
- } else {
695
- await this.update_state_cb(output_id, { value: _data }, false);
696
- }
697
- });
732
+ })
733
+ );
698
734
  }
699
735
 
700
736
  /**
701
737
  * Gathers the current state of the inputs
702
738
  *
703
739
  * @param ids the ids of the components to gather state from
740
+ * @param prop_indices the indices (relative to ids array) that should return all component props instead of just the value
704
741
  * @returns an array of the current state of the components, in the same order as the ids
705
742
  */
706
- async gather_state(ids: number[]): Promise<(unknown | null)[]> {
743
+ async gather_state(
744
+ ids: number[],
745
+ prop_indices: number[] = []
746
+ ): Promise<(unknown | null)[]> {
707
747
  return (await Promise.all(ids.map((id) => this.get_state_cb(id)))).map(
708
- (state) => {
748
+ (state, index) => {
749
+ if (prop_indices.includes(index)) {
750
+ return state ?? null;
751
+ }
709
752
  return state?.value ?? null;
710
753
  }
711
754
  );
@@ -722,7 +765,8 @@ export class DependencyManager {
722
765
  args: Record<string, unknown>
723
766
  ): Promise<() => void> {
724
767
  let current_args: Record<string, unknown> = {};
725
- const current_state = await this.get_state_cb(id);
768
+ const current_state = await this.get_state_cb?.(id);
769
+ if (!current_state) return () => {};
726
770
  for (const [key] of Object.entries(args)) {
727
771
  current_args[key] = current_state?.[key] ?? null;
728
772
  }