@gradio/core 0.27.1 → 0.27.2
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 +10 -0
- package/dist/src/api_docs/ApiDocs.svelte +18 -3
- package/dist/src/api_docs/CodeSnippet.svelte +38 -36
- package/dist/src/api_docs/CodeSnippet.svelte.d.ts +1 -0
- package/dist/src/api_docs/EndpointDetail.svelte +23 -1
- package/dist/src/api_docs/EndpointDetail.svelte.d.ts +1 -0
- package/dist/src/api_docs/MCPSnippet.svelte +39 -0
- package/dist/src/api_docs/MCPSnippet.svelte.d.ts +3 -0
- package/dist/src/api_docs/utils.d.ts +2 -0
- package/dist/src/api_docs/utils.js +14 -0
- package/package.json +25 -25
- package/src/api_docs/ApiDocs.svelte +24 -3
- package/src/api_docs/CodeSnippet.svelte +38 -36
- package/src/api_docs/EndpointDetail.svelte +24 -1
- package/src/api_docs/MCPSnippet.svelte +40 -0
- package/src/api_docs/utils.ts +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @gradio/core
|
|
2
2
|
|
|
3
|
+
## 0.27.2
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- [#11764](https://github.com/gradio-app/gradio/pull/11764) [`e6ce731`](https://github.com/gradio-app/gradio/commit/e6ce731bbcf2889c8147e57bc2ca97e2c731ddf5) - Display performance metrics for API/MCP requests in View API page. Thanks @freddyaboulton!
|
|
8
|
+
|
|
9
|
+
### Dependency updates
|
|
10
|
+
|
|
11
|
+
- @gradio/statustracker@0.10.18
|
|
12
|
+
|
|
3
13
|
## 0.27.1
|
|
4
14
|
|
|
5
15
|
### Features
|
|
@@ -69,12 +69,21 @@ async function get_js_info() {
|
|
|
69
69
|
}
|
|
70
70
|
let info;
|
|
71
71
|
let js_info;
|
|
72
|
+
let analytics;
|
|
72
73
|
get_info().then((data) => {
|
|
73
74
|
info = data;
|
|
74
75
|
});
|
|
75
76
|
get_js_info().then((js_api_info) => {
|
|
76
77
|
js_info = js_api_info;
|
|
77
78
|
});
|
|
79
|
+
async function get_summary() {
|
|
80
|
+
let response = await fetch(root.replace(/\/$/, "") + "/monitoring/summary");
|
|
81
|
+
let data = await response.json();
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
get_summary().then((summary) => {
|
|
85
|
+
analytics = summary.functions;
|
|
86
|
+
});
|
|
78
87
|
const dispatch = createEventDispatcher();
|
|
79
88
|
$: selected_tools_array = Array.from(selected_tools);
|
|
80
89
|
$: selected_tools_without_prefix = selected_tools_array.map(remove_tool_prefix);
|
|
@@ -122,7 +131,8 @@ async function fetch_mcp_tools() {
|
|
|
122
131
|
description: tool.description || "",
|
|
123
132
|
parameters: tool.inputSchema?.properties || {},
|
|
124
133
|
meta: tool.meta,
|
|
125
|
-
expanded: false
|
|
134
|
+
expanded: false,
|
|
135
|
+
endpoint_name: tool.endpoint_name
|
|
126
136
|
}));
|
|
127
137
|
selected_tools = new Set(tools.map((tool) => tool.name));
|
|
128
138
|
headers = schema.map((tool) => tool.meta?.headers || []).flat();
|
|
@@ -185,6 +195,8 @@ async function fetch_mcp_tools() {
|
|
|
185
195
|
}
|
|
186
196
|
}
|
|
187
197
|
onMount(() => {
|
|
198
|
+
const controller = new AbortController();
|
|
199
|
+
const signal = controller.signal;
|
|
188
200
|
document.body.style.overflow = "hidden";
|
|
189
201
|
if ("parentIFrame" in window) {
|
|
190
202
|
window.parentIFrame?.scrollTo(0, 0);
|
|
@@ -193,7 +205,7 @@ onMount(() => {
|
|
|
193
205
|
if (is_valid_language(lang_param)) {
|
|
194
206
|
current_language = lang_param;
|
|
195
207
|
}
|
|
196
|
-
fetch(mcp_server_url).then((response) => {
|
|
208
|
+
fetch(mcp_server_url, { signal }).then((response) => {
|
|
197
209
|
mcp_server_active = response.ok;
|
|
198
210
|
if (mcp_server_active) {
|
|
199
211
|
fetch_mcp_tools();
|
|
@@ -205,6 +217,7 @@ onMount(() => {
|
|
|
205
217
|
current_language = "python";
|
|
206
218
|
}
|
|
207
219
|
}
|
|
220
|
+
controller.abort();
|
|
208
221
|
}).catch(() => {
|
|
209
222
|
mcp_server_active = false;
|
|
210
223
|
});
|
|
@@ -214,7 +227,7 @@ onMount(() => {
|
|
|
214
227
|
});
|
|
215
228
|
</script>
|
|
216
229
|
|
|
217
|
-
{#if info}
|
|
230
|
+
{#if info && analytics}
|
|
218
231
|
{#if api_count}
|
|
219
232
|
<div class="banner-wrap">
|
|
220
233
|
<ApiBanner
|
|
@@ -304,6 +317,7 @@ onMount(() => {
|
|
|
304
317
|
{mcp_json_stdio}
|
|
305
318
|
{file_data_present}
|
|
306
319
|
{mcp_docs}
|
|
320
|
+
{analytics}
|
|
307
321
|
/>
|
|
308
322
|
{:else}
|
|
309
323
|
1. Confirm that you have cURL installed on your system.
|
|
@@ -380,6 +394,7 @@ onMount(() => {
|
|
|
380
394
|
api_description={info.named_endpoints[
|
|
381
395
|
"/" + dependency.api_name
|
|
382
396
|
].description}
|
|
397
|
+
{analytics}
|
|
383
398
|
/>
|
|
384
399
|
|
|
385
400
|
<ParametersSnippet
|
|
@@ -10,6 +10,7 @@ export let endpoint_parameters;
|
|
|
10
10
|
export let username;
|
|
11
11
|
export let current_language;
|
|
12
12
|
export let api_description = null;
|
|
13
|
+
export let analytics;
|
|
13
14
|
let python_code;
|
|
14
15
|
let js_code;
|
|
15
16
|
let bash_post_code;
|
|
@@ -29,6 +30,7 @@ $: normalised_root = root.replace(/\/$/, "");
|
|
|
29
30
|
<EndpointDetail
|
|
30
31
|
api_name={dependency.api_name}
|
|
31
32
|
description={api_description}
|
|
33
|
+
{analytics}
|
|
32
34
|
/>
|
|
33
35
|
{#if current_language === "python"}
|
|
34
36
|
<Block>
|
|
@@ -41,13 +43,13 @@ $: normalised_root = root.replace(/\/$/, "");
|
|
|
41
43
|
class="highlight">import</span
|
|
42
44
|
> Client{#if has_file_path}, handle_file{/if}
|
|
43
45
|
|
|
44
|
-
client = Client(<span class="token string">"{space_id || root}"</span
|
|
46
|
+
client = Client(<span class="token string">"{space_id || root}"</span
|
|
45
47
|
>{#if username !== null}, auth=("{username}", **password**){/if})
|
|
46
|
-
result = client.<span class="highlight">predict</span
|
|
48
|
+
result = client.<span class="highlight">predict</span
|
|
47
49
|
>(<!--
|
|
48
|
-
-->{#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i}<!--
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
-->{#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i}<!--
|
|
51
|
+
-->
|
|
52
|
+
{parameter_name
|
|
51
53
|
? parameter_name + "="
|
|
52
54
|
: ""}<span
|
|
53
55
|
>{represent_value(
|
|
@@ -57,11 +59,11 @@ result = client.<span class="highlight">predict</span
|
|
|
57
59
|
)}</span
|
|
58
60
|
>,{/each}<!--
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
<span class="highlight">print</span>(result)</pre>
|
|
62
|
+
-->
|
|
63
|
+
api_name=<span class="api-name">"/{dependency.api_name}"</span><!--
|
|
64
|
+
-->
|
|
65
|
+
)
|
|
66
|
+
<span class="highlight">print</span>(result)</pre>
|
|
65
67
|
</div>
|
|
66
68
|
</code>
|
|
67
69
|
</Block>
|
|
@@ -73,44 +75,44 @@ result = client.<span class="highlight">predict</span
|
|
|
73
75
|
</div>
|
|
74
76
|
<div bind:this={js_code}>
|
|
75
77
|
<pre>import { Client } from "@gradio/client";
|
|
76
|
-
{#each blob_examples as { component, example_input }, i}<!--
|
|
77
|
-
-->
|
|
78
|
-
const response_{i} = await fetch("{example_input.url}");
|
|
79
|
-
const example{component} = await response_{i}.blob();
|
|
78
|
+
{#each blob_examples as { component, example_input }, i}<!--
|
|
79
|
+
-->
|
|
80
|
+
const response_{i} = await fetch("{example_input.url}");
|
|
81
|
+
const example{component} = await response_{i}.blob();
|
|
80
82
|
{/each}<!--
|
|
81
|
-
-->
|
|
82
|
-
const client = await Client.connect(<span class="token string"
|
|
83
|
+
-->
|
|
84
|
+
const client = await Client.connect(<span class="token string"
|
|
83
85
|
>"{space_id || root}"</span
|
|
84
86
|
>{#if username !== null}, {auth: ["{username}", **password**]}{/if});
|
|
85
|
-
const result = await client.predict(<span class="api-name"
|
|
87
|
+
const result = await client.predict(<span class="api-name"
|
|
86
88
|
>"/{dependency.api_name}"</span
|
|
87
89
|
>, { <!--
|
|
88
|
-
-->{#each endpoint_parameters as { label, parameter_name, type, python_type, component, example_input, serializer }, i}<!--
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
-->{#each endpoint_parameters as { label, parameter_name, type, python_type, component, example_input, serializer }, i}<!--
|
|
91
|
+
-->{#if blob_components.includes(component)}<!--
|
|
92
|
+
-->
|
|
93
|
+
<span
|
|
92
94
|
class="example-inputs"
|
|
93
95
|
>{parameter_name}: example{component}</span
|
|
94
96
|
>, <!--
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
--><span class="desc"><!--
|
|
98
|
+
--></span
|
|
97
99
|
><!--
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
-->{:else}<!--
|
|
101
|
+
-->
|
|
102
|
+
<span class="example-inputs"
|
|
101
103
|
>{parameter_name}: {represent_value(
|
|
102
104
|
example_input,
|
|
103
105
|
python_type.type,
|
|
104
106
|
"js"
|
|
105
107
|
)}</span
|
|
106
108
|
>, <!--
|
|
107
|
-
--><!--
|
|
108
|
-
-->{/if}
|
|
109
|
+
--><!--
|
|
110
|
+
-->{/if}
|
|
109
111
|
{/each}
|
|
110
|
-
});
|
|
112
|
+
});
|
|
111
113
|
|
|
112
|
-
console.log(result.data);
|
|
113
|
-
</pre>
|
|
114
|
+
console.log(result.data);
|
|
115
|
+
</pre>
|
|
114
116
|
</div>
|
|
115
117
|
</code>
|
|
116
118
|
</Block>
|
|
@@ -123,18 +125,18 @@ console.log(result.data);
|
|
|
123
125
|
|
|
124
126
|
<div bind:this={bash_post_code}>
|
|
125
127
|
<pre>curl -X POST {normalised_root}{normalised_api_prefix}/call/{dependency.api_name} -s -H "Content-Type: application/json" -d '{"{"}
|
|
126
|
-
|
|
128
|
+
"data": [{#each endpoint_parameters as { label, parameter_name, type, python_type, component, example_input, serializer }, i}
|
|
127
129
|
<!--
|
|
128
|
-
-->{represent_value(
|
|
130
|
+
-->{represent_value(
|
|
129
131
|
example_input,
|
|
130
132
|
python_type.type,
|
|
131
133
|
"bash"
|
|
132
134
|
)}{#if i < endpoint_parameters.length - 1},
|
|
133
135
|
{/if}
|
|
134
136
|
{/each}
|
|
135
|
-
]{"}"}' \
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
]{"}"}' \
|
|
138
|
+
| awk -F'"' '{"{"} print $4{"}"}' \
|
|
139
|
+
| read EVENT_ID; curl -N {normalised_root}{normalised_api_prefix}/call/{dependency.api_name}/$EVENT_ID</pre>
|
|
138
140
|
</div>
|
|
139
141
|
</code>
|
|
140
142
|
</Block>
|
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
<script>export let api_name = null;
|
|
2
2
|
export let description = null;
|
|
3
|
+
export let analytics;
|
|
4
|
+
import { format_latency, get_color_from_success_rate } from "./utils";
|
|
5
|
+
const success_rate = api_name ? analytics[api_name]?.success_rate : 0;
|
|
6
|
+
const color = get_color_from_success_rate(success_rate);
|
|
3
7
|
</script>
|
|
4
8
|
|
|
5
9
|
<h3>
|
|
6
10
|
API name:
|
|
7
11
|
<span class="post">{"/" + api_name}</span>
|
|
8
12
|
<span class="desc">{description}</span>
|
|
13
|
+
{#if analytics && api_name && analytics[api_name]}
|
|
14
|
+
<span class="analytics">
|
|
15
|
+
Total requests: {analytics[api_name].total_requests} (<span style={color}
|
|
16
|
+
>{Math.round(success_rate * 100)}%</span
|
|
17
|
+
>
|
|
18
|
+
successful) | p50/p90/p99:
|
|
19
|
+
{format_latency(analytics[api_name].process_time_percentiles["50th"])}
|
|
20
|
+
/
|
|
21
|
+
{format_latency(analytics[api_name].process_time_percentiles["90th"])}
|
|
22
|
+
/
|
|
23
|
+
{format_latency(analytics[api_name].process_time_percentiles["99th"])}
|
|
24
|
+
</span>
|
|
25
|
+
{/if}
|
|
9
26
|
</h3>
|
|
10
27
|
|
|
11
28
|
<style>
|
|
@@ -27,8 +44,13 @@ export let description = null;
|
|
|
27
44
|
font-weight: var(--weight-semibold);
|
|
28
45
|
}
|
|
29
46
|
|
|
30
|
-
.
|
|
47
|
+
.analytics {
|
|
31
48
|
color: var(--body-text-color-subdued);
|
|
49
|
+
margin-top: var(--size-1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.desc {
|
|
53
|
+
color: var(--body-text-color);
|
|
32
54
|
font-size: var(--text-lg);
|
|
33
55
|
margin-top: var(--size-1);
|
|
34
56
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script>import { Block } from "@gradio/atoms";
|
|
2
2
|
import CopyButton from "./CopyButton.svelte";
|
|
3
3
|
import { Tool, Prompt, Resource } from "@gradio/icons";
|
|
4
|
+
import { format_latency, get_color_from_success_rate } from "./utils";
|
|
4
5
|
export let mcp_server_active;
|
|
5
6
|
export let mcp_server_url;
|
|
6
7
|
export let mcp_server_url_streamable;
|
|
@@ -11,6 +12,7 @@ export let mcp_json_sse;
|
|
|
11
12
|
export let mcp_json_stdio;
|
|
12
13
|
export let file_data_present;
|
|
13
14
|
export let mcp_docs;
|
|
15
|
+
export let analytics;
|
|
14
16
|
let current_transport = "streamable_http";
|
|
15
17
|
let include_file_upload = true;
|
|
16
18
|
const transports = [
|
|
@@ -137,6 +139,9 @@ $: mcp_json_stdio_updated = update_config_with_file_upload(
|
|
|
137
139
|
</div>
|
|
138
140
|
<div class="mcp-tools">
|
|
139
141
|
{#each all_tools.length > 0 ? all_tools : tools as tool}
|
|
142
|
+
{@const success_rate =
|
|
143
|
+
analytics[tool.meta.endpoint_name]?.success_rate || 0}
|
|
144
|
+
{@const color = get_color_from_success_rate(success_rate)}
|
|
140
145
|
<div class="tool-item">
|
|
141
146
|
<div class="tool-header-wrapper">
|
|
142
147
|
{#if all_tools.length > 0}
|
|
@@ -179,6 +184,36 @@ $: mcp_json_stdio_updated = update_config_with_file_upload(
|
|
|
179
184
|
? tool.description
|
|
180
185
|
: "⚠︎ No description provided in function docstring"}
|
|
181
186
|
</span>
|
|
187
|
+
{#if analytics[tool.meta.endpoint_name]}
|
|
188
|
+
<span
|
|
189
|
+
class="tool-analytics"
|
|
190
|
+
style="color: var(--body-text-color-subdued); margin-left: 1em;"
|
|
191
|
+
>
|
|
192
|
+
Total requests: {analytics[tool.meta.endpoint_name]
|
|
193
|
+
.total_requests}
|
|
194
|
+
<span style={color}
|
|
195
|
+
>({Math.round(success_rate * 100)}% successful)</span
|
|
196
|
+
>
|
|
197
|
+
| p50/p90/p99:
|
|
198
|
+
{format_latency(
|
|
199
|
+
analytics[tool.meta.endpoint_name].process_time_percentiles[
|
|
200
|
+
"50th"
|
|
201
|
+
]
|
|
202
|
+
)}
|
|
203
|
+
/
|
|
204
|
+
{format_latency(
|
|
205
|
+
analytics[tool.meta.endpoint_name].process_time_percentiles[
|
|
206
|
+
"90th"
|
|
207
|
+
]
|
|
208
|
+
)}
|
|
209
|
+
/
|
|
210
|
+
{format_latency(
|
|
211
|
+
analytics[tool.meta.endpoint_name].process_time_percentiles[
|
|
212
|
+
"99th"
|
|
213
|
+
]
|
|
214
|
+
)}
|
|
215
|
+
</span>
|
|
216
|
+
{/if}
|
|
182
217
|
</span>
|
|
183
218
|
<span class="tool-arrow">{tool.expanded ? "▼" : "▶"}</span>
|
|
184
219
|
</button>
|
|
@@ -303,6 +338,10 @@ $: mcp_json_stdio_updated = update_config_with_file_upload(
|
|
|
303
338
|
{/if}
|
|
304
339
|
|
|
305
340
|
<style>
|
|
341
|
+
.tool-analytics {
|
|
342
|
+
font-size: 0.95em;
|
|
343
|
+
color: var(--body-text-color-subdued);
|
|
344
|
+
}
|
|
306
345
|
.transport-selection {
|
|
307
346
|
margin-bottom: var(--size-4);
|
|
308
347
|
}
|
|
@@ -18,6 +18,7 @@ declare const __propDef: {
|
|
|
18
18
|
meta: {
|
|
19
19
|
mcp_type: "tool" | "resource" | "prompt";
|
|
20
20
|
file_data_present: boolean;
|
|
21
|
+
endpoint_name: string;
|
|
21
22
|
};
|
|
22
23
|
}[];
|
|
23
24
|
all_tools?: {
|
|
@@ -34,6 +35,7 @@ declare const __propDef: {
|
|
|
34
35
|
meta: {
|
|
35
36
|
mcp_type: "tool" | "resource" | "prompt";
|
|
36
37
|
file_data_present: boolean;
|
|
38
|
+
endpoint_name: string;
|
|
37
39
|
};
|
|
38
40
|
}[];
|
|
39
41
|
selected_tools?: Set<string>;
|
|
@@ -41,6 +43,7 @@ declare const __propDef: {
|
|
|
41
43
|
mcp_json_stdio: any;
|
|
42
44
|
file_data_present: boolean;
|
|
43
45
|
mcp_docs: string;
|
|
46
|
+
analytics: Record<string, any>;
|
|
44
47
|
};
|
|
45
48
|
events: {
|
|
46
49
|
[evt: string]: CustomEvent<any>;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export declare function represent_value(value: string, type: string | undefined, lang?: "js" | "py" | "bash" | null): string | null | number | boolean | Record<string, unknown>;
|
|
2
2
|
export declare function is_potentially_nested_file_data(obj: any): boolean;
|
|
3
|
+
export declare function format_latency(val: number): string;
|
|
4
|
+
export declare function get_color_from_success_rate(success_rate: number): string;
|
|
@@ -133,3 +133,17 @@ function stringify_except_file_function(obj) {
|
|
|
133
133
|
const regexNone = /"UNQUOTEDNone"/g;
|
|
134
134
|
return jsonString.replace(regexNone, "None");
|
|
135
135
|
}
|
|
136
|
+
export function format_latency(val) {
|
|
137
|
+
if (val < 1)
|
|
138
|
+
return `${Math.round(val * 1000)} ms`;
|
|
139
|
+
return `${val.toFixed(2)} s`;
|
|
140
|
+
}
|
|
141
|
+
export function get_color_from_success_rate(success_rate) {
|
|
142
|
+
if (success_rate > 0.9) {
|
|
143
|
+
return "color: green;";
|
|
144
|
+
}
|
|
145
|
+
else if (success_rate > 0.1) {
|
|
146
|
+
return "color: orange;";
|
|
147
|
+
}
|
|
148
|
+
return "color: red;";
|
|
149
|
+
}
|
package/package.json
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/core",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@gradio/accordion": "^0.5.22",
|
|
7
7
|
"@gradio/annotatedimage": "^0.9.29",
|
|
8
|
-
"@gradio/
|
|
8
|
+
"@gradio/audio": "^0.17.26",
|
|
9
9
|
"@gradio/box": "^0.2.23",
|
|
10
|
-
"@gradio/audio": "^0.17.25",
|
|
11
10
|
"@gradio/browserstate": "^0.3.2",
|
|
11
|
+
"@gradio/atoms": "^0.16.5",
|
|
12
12
|
"@gradio/button": "^0.5.11",
|
|
13
|
-
"@gradio/chatbot": "^0.26.22",
|
|
14
13
|
"@gradio/checkbox": "^0.4.28",
|
|
14
|
+
"@gradio/chatbot": "^0.26.23",
|
|
15
15
|
"@gradio/checkboxgroup": "^0.6.27",
|
|
16
|
-
"@gradio/client": "^1.17.1",
|
|
17
16
|
"@gradio/code": "^0.14.15",
|
|
17
|
+
"@gradio/client": "^1.17.1",
|
|
18
18
|
"@gradio/colorpicker": "^0.4.27",
|
|
19
19
|
"@gradio/column": "^0.2.1",
|
|
20
|
-
"@gradio/dataframe": "^0.19.
|
|
20
|
+
"@gradio/dataframe": "^0.19.1",
|
|
21
21
|
"@gradio/dataset": "^0.4.32",
|
|
22
|
+
"@gradio/dropdown": "^0.10.2",
|
|
23
|
+
"@gradio/fallback": "^0.4.27",
|
|
22
24
|
"@gradio/datetime": "^0.3.20",
|
|
23
25
|
"@gradio/downloadbutton": "^0.4.11",
|
|
24
|
-
"@gradio/fallback": "^0.4.27",
|
|
25
|
-
"@gradio/file": "^0.12.28",
|
|
26
|
-
"@gradio/dropdown": "^0.10.2",
|
|
27
26
|
"@gradio/fileexplorer": "^0.5.39",
|
|
27
|
+
"@gradio/file": "^0.12.28",
|
|
28
28
|
"@gradio/form": "^0.2.23",
|
|
29
|
-
"@gradio/gallery": "^0.15.31",
|
|
30
29
|
"@gradio/group": "^0.2.0",
|
|
31
|
-
"@gradio/highlightedtext": "^0.9.10",
|
|
32
30
|
"@gradio/html": "^0.7.0",
|
|
33
|
-
"@gradio/image": "^0.22.17",
|
|
34
31
|
"@gradio/icons": "^0.13.1",
|
|
32
|
+
"@gradio/highlightedtext": "^0.9.10",
|
|
33
|
+
"@gradio/gallery": "^0.15.31",
|
|
35
34
|
"@gradio/imageeditor": "^0.16.5",
|
|
35
|
+
"@gradio/image": "^0.22.17",
|
|
36
36
|
"@gradio/imageslider": "^0.2.13",
|
|
37
37
|
"@gradio/json": "^0.5.29",
|
|
38
38
|
"@gradio/label": "^0.5.19",
|
|
39
|
-
"@gradio/markdown": "^0.13.20",
|
|
40
39
|
"@gradio/model3d": "^0.14.25",
|
|
41
|
-
"@gradio/
|
|
40
|
+
"@gradio/markdown": "^0.13.20",
|
|
42
41
|
"@gradio/number": "^0.6.4",
|
|
43
42
|
"@gradio/multimodaltextbox": "^0.10.17",
|
|
44
43
|
"@gradio/paramviewer": "^0.7.15",
|
|
44
|
+
"@gradio/nativeplot": "^0.7.4",
|
|
45
45
|
"@gradio/plot": "^0.9.22",
|
|
46
|
-
"@gradio/radio": "^0.7.10",
|
|
47
46
|
"@gradio/row": "^0.2.1",
|
|
48
47
|
"@gradio/sidebar": "^0.1.20",
|
|
48
|
+
"@gradio/radio": "^0.7.10",
|
|
49
49
|
"@gradio/simpledropdown": "^0.3.27",
|
|
50
|
-
"@gradio/simpleimage": "^0.8.39",
|
|
51
50
|
"@gradio/simpletextbox": "^0.3.28",
|
|
52
|
-
"@gradio/sketchbox": "^0.6.15",
|
|
53
51
|
"@gradio/state": "^0.1.2",
|
|
54
|
-
"@gradio/
|
|
55
|
-
"@gradio/
|
|
52
|
+
"@gradio/sketchbox": "^0.6.15",
|
|
53
|
+
"@gradio/simpleimage": "^0.8.39",
|
|
54
|
+
"@gradio/statustracker": "^0.10.18",
|
|
56
55
|
"@gradio/tabitem": "^0.5.0",
|
|
57
|
-
"@gradio/
|
|
56
|
+
"@gradio/slider": "^0.6.16",
|
|
58
57
|
"@gradio/textbox": "^0.10.20",
|
|
59
|
-
"@gradio/
|
|
58
|
+
"@gradio/tabs": "^0.4.5",
|
|
60
59
|
"@gradio/theme": "^0.4.0",
|
|
61
|
-
"@gradio/upload": "^0.16.16",
|
|
62
|
-
"@gradio/vibeeditor": "^0.2.2",
|
|
63
60
|
"@gradio/uploadbutton": "^0.9.11",
|
|
64
|
-
"@gradio/
|
|
61
|
+
"@gradio/upload": "^0.16.16",
|
|
62
|
+
"@gradio/timer": "^0.4.5",
|
|
65
63
|
"@gradio/utils": "^0.10.2",
|
|
66
|
-
"@gradio/
|
|
64
|
+
"@gradio/vibeeditor": "^0.2.2",
|
|
65
|
+
"@gradio/wasm": "^0.18.1",
|
|
66
|
+
"@gradio/video": "^0.15.0"
|
|
67
67
|
},
|
|
68
68
|
"msw": {
|
|
69
69
|
"workerDirectory": "public"
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
let js_info: Record<string, any>;
|
|
102
|
+
let analytics: Record<string, any>;
|
|
102
103
|
|
|
103
104
|
get_info().then((data) => {
|
|
104
105
|
info = data;
|
|
@@ -108,6 +109,18 @@
|
|
|
108
109
|
js_info = js_api_info;
|
|
109
110
|
});
|
|
110
111
|
|
|
112
|
+
async function get_summary(): Promise<{
|
|
113
|
+
functions: any;
|
|
114
|
+
}> {
|
|
115
|
+
let response = await fetch(root.replace(/\/$/, "") + "/monitoring/summary");
|
|
116
|
+
let data = await response.json();
|
|
117
|
+
return data;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get_summary().then((summary) => {
|
|
121
|
+
analytics = summary.functions;
|
|
122
|
+
});
|
|
123
|
+
|
|
111
124
|
const dispatch = createEventDispatcher();
|
|
112
125
|
|
|
113
126
|
$: selected_tools_array = Array.from(selected_tools);
|
|
@@ -148,6 +161,7 @@
|
|
|
148
161
|
meta: {
|
|
149
162
|
mcp_type: "tool" | "resource" | "prompt";
|
|
150
163
|
file_data_present: boolean;
|
|
164
|
+
endpoint_name: string;
|
|
151
165
|
};
|
|
152
166
|
}
|
|
153
167
|
|
|
@@ -192,7 +206,8 @@
|
|
|
192
206
|
description: tool.description || "",
|
|
193
207
|
parameters: tool.inputSchema?.properties || {},
|
|
194
208
|
meta: tool.meta,
|
|
195
|
-
expanded: false
|
|
209
|
+
expanded: false,
|
|
210
|
+
endpoint_name: tool.endpoint_name
|
|
196
211
|
}));
|
|
197
212
|
selected_tools = new Set(tools.map((tool) => tool.name));
|
|
198
213
|
headers = schema.map((tool: any) => tool.meta?.headers || []).flat();
|
|
@@ -260,6 +275,9 @@
|
|
|
260
275
|
}
|
|
261
276
|
|
|
262
277
|
onMount(() => {
|
|
278
|
+
const controller = new AbortController();
|
|
279
|
+
const signal = controller.signal;
|
|
280
|
+
|
|
263
281
|
document.body.style.overflow = "hidden";
|
|
264
282
|
if ("parentIFrame" in window) {
|
|
265
283
|
window.parentIFrame?.scrollTo(0, 0);
|
|
@@ -271,7 +289,7 @@
|
|
|
271
289
|
}
|
|
272
290
|
|
|
273
291
|
// Check MCP server status and fetch tools if active
|
|
274
|
-
fetch(mcp_server_url)
|
|
292
|
+
fetch(mcp_server_url, { signal: signal })
|
|
275
293
|
.then((response) => {
|
|
276
294
|
mcp_server_active = response.ok;
|
|
277
295
|
if (mcp_server_active) {
|
|
@@ -284,6 +302,7 @@
|
|
|
284
302
|
current_language = "python";
|
|
285
303
|
}
|
|
286
304
|
}
|
|
305
|
+
controller.abort();
|
|
287
306
|
})
|
|
288
307
|
.catch(() => {
|
|
289
308
|
mcp_server_active = false;
|
|
@@ -295,7 +314,7 @@
|
|
|
295
314
|
});
|
|
296
315
|
</script>
|
|
297
316
|
|
|
298
|
-
{#if info}
|
|
317
|
+
{#if info && analytics}
|
|
299
318
|
{#if api_count}
|
|
300
319
|
<div class="banner-wrap">
|
|
301
320
|
<ApiBanner
|
|
@@ -385,6 +404,7 @@
|
|
|
385
404
|
{mcp_json_stdio}
|
|
386
405
|
{file_data_present}
|
|
387
406
|
{mcp_docs}
|
|
407
|
+
{analytics}
|
|
388
408
|
/>
|
|
389
409
|
{:else}
|
|
390
410
|
1. Confirm that you have cURL installed on your system.
|
|
@@ -461,6 +481,7 @@
|
|
|
461
481
|
api_description={info.named_endpoints[
|
|
462
482
|
"/" + dependency.api_name
|
|
463
483
|
].description}
|
|
484
|
+
{analytics}
|
|
464
485
|
/>
|
|
465
486
|
|
|
466
487
|
<ParametersSnippet
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
export let username: string | null;
|
|
23
23
|
export let current_language: "python" | "javascript" | "bash";
|
|
24
24
|
export let api_description: string | null = null;
|
|
25
|
+
export let analytics: Record<string, any>;
|
|
25
26
|
|
|
26
27
|
let python_code: HTMLElement;
|
|
27
28
|
let js_code: HTMLElement;
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
<EndpointDetail
|
|
45
46
|
api_name={dependency.api_name}
|
|
46
47
|
description={api_description}
|
|
48
|
+
{analytics}
|
|
47
49
|
/>
|
|
48
50
|
{#if current_language === "python"}
|
|
49
51
|
<Block>
|
|
@@ -56,13 +58,13 @@
|
|
|
56
58
|
class="highlight">import</span
|
|
57
59
|
> Client{#if has_file_path}, handle_file{/if}
|
|
58
60
|
|
|
59
|
-
client = Client(<span class="token string">"{space_id || root}"</span
|
|
61
|
+
client = Client(<span class="token string">"{space_id || root}"</span
|
|
60
62
|
>{#if username !== null}, auth=("{username}", **password**){/if})
|
|
61
|
-
result = client.<span class="highlight">predict</span
|
|
63
|
+
result = client.<span class="highlight">predict</span
|
|
62
64
|
>(<!--
|
|
63
|
-
-->{#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i}<!--
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
-->{#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i}<!--
|
|
66
|
+
-->
|
|
67
|
+
{parameter_name
|
|
66
68
|
? parameter_name + "="
|
|
67
69
|
: ""}<span
|
|
68
70
|
>{represent_value(
|
|
@@ -72,11 +74,11 @@ result = client.<span class="highlight">predict</span
|
|
|
72
74
|
)}</span
|
|
73
75
|
>,{/each}<!--
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
<span class="highlight">print</span>(result)</pre>
|
|
77
|
+
-->
|
|
78
|
+
api_name=<span class="api-name">"/{dependency.api_name}"</span><!--
|
|
79
|
+
-->
|
|
80
|
+
)
|
|
81
|
+
<span class="highlight">print</span>(result)</pre>
|
|
80
82
|
</div>
|
|
81
83
|
</code>
|
|
82
84
|
</Block>
|
|
@@ -88,44 +90,44 @@ result = client.<span class="highlight">predict</span
|
|
|
88
90
|
</div>
|
|
89
91
|
<div bind:this={js_code}>
|
|
90
92
|
<pre>import { Client } from "@gradio/client";
|
|
91
|
-
{#each blob_examples as { component, example_input }, i}<!--
|
|
92
|
-
-->
|
|
93
|
-
const response_{i} = await fetch("{example_input.url}");
|
|
94
|
-
const example{component} = await response_{i}.blob();
|
|
93
|
+
{#each blob_examples as { component, example_input }, i}<!--
|
|
94
|
+
-->
|
|
95
|
+
const response_{i} = await fetch("{example_input.url}");
|
|
96
|
+
const example{component} = await response_{i}.blob();
|
|
95
97
|
{/each}<!--
|
|
96
|
-
-->
|
|
97
|
-
const client = await Client.connect(<span class="token string"
|
|
98
|
+
-->
|
|
99
|
+
const client = await Client.connect(<span class="token string"
|
|
98
100
|
>"{space_id || root}"</span
|
|
99
101
|
>{#if username !== null}, {auth: ["{username}", **password**]}{/if});
|
|
100
|
-
const result = await client.predict(<span class="api-name"
|
|
102
|
+
const result = await client.predict(<span class="api-name"
|
|
101
103
|
>"/{dependency.api_name}"</span
|
|
102
104
|
>, { <!--
|
|
103
|
-
-->{#each endpoint_parameters as { label, parameter_name, type, python_type, component, example_input, serializer }, i}<!--
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
-->{#each endpoint_parameters as { label, parameter_name, type, python_type, component, example_input, serializer }, i}<!--
|
|
106
|
+
-->{#if blob_components.includes(component)}<!--
|
|
107
|
+
-->
|
|
108
|
+
<span
|
|
107
109
|
class="example-inputs"
|
|
108
110
|
>{parameter_name}: example{component}</span
|
|
109
111
|
>, <!--
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
--><span class="desc"><!--
|
|
113
|
+
--></span
|
|
112
114
|
><!--
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
-->{:else}<!--
|
|
116
|
+
-->
|
|
117
|
+
<span class="example-inputs"
|
|
116
118
|
>{parameter_name}: {represent_value(
|
|
117
119
|
example_input,
|
|
118
120
|
python_type.type,
|
|
119
121
|
"js"
|
|
120
122
|
)}</span
|
|
121
123
|
>, <!--
|
|
122
|
-
--><!--
|
|
123
|
-
-->{/if}
|
|
124
|
+
--><!--
|
|
125
|
+
-->{/if}
|
|
124
126
|
{/each}
|
|
125
|
-
});
|
|
127
|
+
});
|
|
126
128
|
|
|
127
|
-
console.log(result.data);
|
|
128
|
-
</pre>
|
|
129
|
+
console.log(result.data);
|
|
130
|
+
</pre>
|
|
129
131
|
</div>
|
|
130
132
|
</code>
|
|
131
133
|
</Block>
|
|
@@ -138,18 +140,18 @@ console.log(result.data);
|
|
|
138
140
|
|
|
139
141
|
<div bind:this={bash_post_code}>
|
|
140
142
|
<pre>curl -X POST {normalised_root}{normalised_api_prefix}/call/{dependency.api_name} -s -H "Content-Type: application/json" -d '{"{"}
|
|
141
|
-
|
|
143
|
+
"data": [{#each endpoint_parameters as { label, parameter_name, type, python_type, component, example_input, serializer }, i}
|
|
142
144
|
<!--
|
|
143
|
-
-->{represent_value(
|
|
145
|
+
-->{represent_value(
|
|
144
146
|
example_input,
|
|
145
147
|
python_type.type,
|
|
146
148
|
"bash"
|
|
147
149
|
)}{#if i < endpoint_parameters.length - 1},
|
|
148
150
|
{/if}
|
|
149
151
|
{/each}
|
|
150
|
-
]{"}"}' \
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
]{"}"}' \
|
|
153
|
+
| awk -F'"' '{"{"} print $4{"}"}' \
|
|
154
|
+
| read EVENT_ID; curl -N {normalised_root}{normalised_api_prefix}/call/{dependency.api_name}/$EVENT_ID</pre>
|
|
153
155
|
</div>
|
|
154
156
|
</code>
|
|
155
157
|
</Block>
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
export let api_name: string | null = null;
|
|
3
3
|
export let description: string | null = null;
|
|
4
|
+
export let analytics: Record<string, any>;
|
|
5
|
+
import { format_latency, get_color_from_success_rate } from "./utils";
|
|
6
|
+
|
|
7
|
+
const success_rate = api_name ? analytics[api_name]?.success_rate : 0;
|
|
8
|
+
const color = get_color_from_success_rate(success_rate);
|
|
4
9
|
</script>
|
|
5
10
|
|
|
6
11
|
<h3>
|
|
7
12
|
API name:
|
|
8
13
|
<span class="post">{"/" + api_name}</span>
|
|
9
14
|
<span class="desc">{description}</span>
|
|
15
|
+
{#if analytics && api_name && analytics[api_name]}
|
|
16
|
+
<span class="analytics">
|
|
17
|
+
Total requests: {analytics[api_name].total_requests} (<span style={color}
|
|
18
|
+
>{Math.round(success_rate * 100)}%</span
|
|
19
|
+
>
|
|
20
|
+
successful) | p50/p90/p99:
|
|
21
|
+
{format_latency(analytics[api_name].process_time_percentiles["50th"])}
|
|
22
|
+
/
|
|
23
|
+
{format_latency(analytics[api_name].process_time_percentiles["90th"])}
|
|
24
|
+
/
|
|
25
|
+
{format_latency(analytics[api_name].process_time_percentiles["99th"])}
|
|
26
|
+
</span>
|
|
27
|
+
{/if}
|
|
10
28
|
</h3>
|
|
11
29
|
|
|
12
30
|
<style>
|
|
@@ -28,8 +46,13 @@
|
|
|
28
46
|
font-weight: var(--weight-semibold);
|
|
29
47
|
}
|
|
30
48
|
|
|
31
|
-
.
|
|
49
|
+
.analytics {
|
|
32
50
|
color: var(--body-text-color-subdued);
|
|
51
|
+
margin-top: var(--size-1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.desc {
|
|
55
|
+
color: var(--body-text-color);
|
|
33
56
|
font-size: var(--text-lg);
|
|
34
57
|
margin-top: var(--size-1);
|
|
35
58
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Block } from "@gradio/atoms";
|
|
3
3
|
import CopyButton from "./CopyButton.svelte";
|
|
4
4
|
import { Tool, Prompt, Resource } from "@gradio/icons";
|
|
5
|
+
import { format_latency, get_color_from_success_rate } from "./utils";
|
|
5
6
|
|
|
6
7
|
export let mcp_server_active: boolean;
|
|
7
8
|
export let mcp_server_url: string;
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
export let mcp_json_stdio: any;
|
|
14
15
|
export let file_data_present: boolean;
|
|
15
16
|
export let mcp_docs: string;
|
|
17
|
+
export let analytics: Record<string, any>;
|
|
16
18
|
|
|
17
19
|
interface ToolParameter {
|
|
18
20
|
title?: string;
|
|
@@ -30,6 +32,7 @@
|
|
|
30
32
|
meta: {
|
|
31
33
|
mcp_type: "tool" | "resource" | "prompt";
|
|
32
34
|
file_data_present: boolean;
|
|
35
|
+
endpoint_name: string;
|
|
33
36
|
};
|
|
34
37
|
}
|
|
35
38
|
|
|
@@ -178,6 +181,9 @@
|
|
|
178
181
|
</div>
|
|
179
182
|
<div class="mcp-tools">
|
|
180
183
|
{#each all_tools.length > 0 ? all_tools : tools as tool}
|
|
184
|
+
{@const success_rate =
|
|
185
|
+
analytics[tool.meta.endpoint_name]?.success_rate || 0}
|
|
186
|
+
{@const color = get_color_from_success_rate(success_rate)}
|
|
181
187
|
<div class="tool-item">
|
|
182
188
|
<div class="tool-header-wrapper">
|
|
183
189
|
{#if all_tools.length > 0}
|
|
@@ -220,6 +226,36 @@
|
|
|
220
226
|
? tool.description
|
|
221
227
|
: "⚠︎ No description provided in function docstring"}
|
|
222
228
|
</span>
|
|
229
|
+
{#if analytics[tool.meta.endpoint_name]}
|
|
230
|
+
<span
|
|
231
|
+
class="tool-analytics"
|
|
232
|
+
style="color: var(--body-text-color-subdued); margin-left: 1em;"
|
|
233
|
+
>
|
|
234
|
+
Total requests: {analytics[tool.meta.endpoint_name]
|
|
235
|
+
.total_requests}
|
|
236
|
+
<span style={color}
|
|
237
|
+
>({Math.round(success_rate * 100)}% successful)</span
|
|
238
|
+
>
|
|
239
|
+
| p50/p90/p99:
|
|
240
|
+
{format_latency(
|
|
241
|
+
analytics[tool.meta.endpoint_name].process_time_percentiles[
|
|
242
|
+
"50th"
|
|
243
|
+
]
|
|
244
|
+
)}
|
|
245
|
+
/
|
|
246
|
+
{format_latency(
|
|
247
|
+
analytics[tool.meta.endpoint_name].process_time_percentiles[
|
|
248
|
+
"90th"
|
|
249
|
+
]
|
|
250
|
+
)}
|
|
251
|
+
/
|
|
252
|
+
{format_latency(
|
|
253
|
+
analytics[tool.meta.endpoint_name].process_time_percentiles[
|
|
254
|
+
"99th"
|
|
255
|
+
]
|
|
256
|
+
)}
|
|
257
|
+
</span>
|
|
258
|
+
{/if}
|
|
223
259
|
</span>
|
|
224
260
|
<span class="tool-arrow">{tool.expanded ? "▼" : "▶"}</span>
|
|
225
261
|
</button>
|
|
@@ -344,6 +380,10 @@
|
|
|
344
380
|
{/if}
|
|
345
381
|
|
|
346
382
|
<style>
|
|
383
|
+
.tool-analytics {
|
|
384
|
+
font-size: 0.95em;
|
|
385
|
+
color: var(--body-text-color-subdued);
|
|
386
|
+
}
|
|
347
387
|
.transport-selection {
|
|
348
388
|
margin-bottom: var(--size-4);
|
|
349
389
|
}
|
package/src/api_docs/utils.ts
CHANGED
|
@@ -141,3 +141,17 @@ function stringify_except_file_function(obj: any): string {
|
|
|
141
141
|
const regexNone = /"UNQUOTEDNone"/g;
|
|
142
142
|
return jsonString.replace(regexNone, "None");
|
|
143
143
|
}
|
|
144
|
+
|
|
145
|
+
export function format_latency(val: number): string {
|
|
146
|
+
if (val < 1) return `${Math.round(val * 1000)} ms`;
|
|
147
|
+
return `${val.toFixed(2)} s`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function get_color_from_success_rate(success_rate: number): string {
|
|
151
|
+
if (success_rate > 0.9) {
|
|
152
|
+
return "color: green;";
|
|
153
|
+
} else if (success_rate > 0.1) {
|
|
154
|
+
return "color: orange;";
|
|
155
|
+
}
|
|
156
|
+
return "color: red;";
|
|
157
|
+
}
|