@gradio/core 0.27.1 → 0.28.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 +45 -0
- package/dist/src/Blocks.svelte +80 -2
- package/dist/src/Embed.svelte +45 -4
- package/dist/src/Embed.svelte.d.ts +1 -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/dist/src/i18n.d.ts +2 -1
- package/dist/src/i18n.js +3 -3
- package/dist/src/init.js +19 -15
- package/dist/src/lang/id.json +154 -0
- package/dist/src/navbar_store.d.ts +6 -0
- package/dist/src/navbar_store.js +2 -0
- package/dist/src/stores.d.ts +2 -0
- package/dist/src/stores.js +10 -0
- package/dist/src/stories/I18nMultiLanguageTest.stories.d.ts +0 -1
- package/dist/src/stories/I18nMultiLanguageTest.stories.js +1 -1
- package/package.json +57 -52
- package/src/Blocks.svelte +102 -2
- package/src/Embed.svelte +69 -4
- 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/src/i18n.ts +5 -3
- package/src/init.ts +23 -15
- package/src/lang/id.json +154 -0
- package/src/navbar_store.ts +9 -0
- package/src/stores.ts +19 -0
- package/src/stories/I18nMultiLanguageTest.stories.ts +1 -1
package/src/Blocks.svelte
CHANGED
|
@@ -255,6 +255,19 @@
|
|
|
255
255
|
});
|
|
256
256
|
update_value(updates);
|
|
257
257
|
|
|
258
|
+
// Handle navbar updates separately since they need to be updated in the store.
|
|
259
|
+
updates.forEach((update) => {
|
|
260
|
+
const component = components.find((comp) => comp.id === update.id);
|
|
261
|
+
if (component && component.type === "navbar") {
|
|
262
|
+
import("./navbar_store").then(({ navbar_config }) => {
|
|
263
|
+
navbar_config.update((current) => ({
|
|
264
|
+
...current,
|
|
265
|
+
[update.prop]: update.value
|
|
266
|
+
}));
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
258
271
|
await tick();
|
|
259
272
|
}
|
|
260
273
|
|
|
@@ -623,6 +636,53 @@
|
|
|
623
636
|
|
|
624
637
|
/* eslint-disable complexity */
|
|
625
638
|
function handle_status_update(message: StatusMessage): void {
|
|
639
|
+
if (message.code === "validation_error") {
|
|
640
|
+
const dep = dependencies.find((dep) => dep.id === message.fn_index);
|
|
641
|
+
if (
|
|
642
|
+
dep === undefined ||
|
|
643
|
+
message.message === undefined ||
|
|
644
|
+
typeof message.message === "string"
|
|
645
|
+
) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const validation_error_data: {
|
|
650
|
+
id: number;
|
|
651
|
+
prop: string;
|
|
652
|
+
value: unknown;
|
|
653
|
+
}[] = [];
|
|
654
|
+
|
|
655
|
+
message.message.forEach((message, i) => {
|
|
656
|
+
if (message.is_valid) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
validation_error_data.push({
|
|
660
|
+
id: dep.inputs[i],
|
|
661
|
+
prop: "validation_error",
|
|
662
|
+
value: message.message
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
validation_error_data.push({
|
|
666
|
+
id: dep.inputs[i],
|
|
667
|
+
prop: "loading_status",
|
|
668
|
+
value: { validation_error: message.message }
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
if (validation_error_data.length > 0) {
|
|
673
|
+
update_value(validation_error_data);
|
|
674
|
+
loading_status.update({
|
|
675
|
+
status: "complete",
|
|
676
|
+
fn_index: message.fn_index,
|
|
677
|
+
eta: 0,
|
|
678
|
+
queue: false,
|
|
679
|
+
queue_position: null
|
|
680
|
+
});
|
|
681
|
+
set_status($loading_status);
|
|
682
|
+
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
626
686
|
if (message.broken && !broken_connection) {
|
|
627
687
|
messages = [
|
|
628
688
|
new_message(
|
|
@@ -729,7 +789,7 @@
|
|
|
729
789
|
!broken_connection &&
|
|
730
790
|
!message.session_not_found
|
|
731
791
|
) {
|
|
732
|
-
if (status.message) {
|
|
792
|
+
if (status.message && typeof status.message === "string") {
|
|
733
793
|
const _message = status.message.replace(
|
|
734
794
|
MESSAGE_QUOTE_RE,
|
|
735
795
|
(_, b) => b
|
|
@@ -814,6 +874,11 @@
|
|
|
814
874
|
target.addEventListener("prop_change", (e: Event) => {
|
|
815
875
|
if (!isCustomEvent(e)) throw new Error("not a custom event");
|
|
816
876
|
const { id, prop, value } = e.detail;
|
|
877
|
+
if (prop === "value") {
|
|
878
|
+
update_value([
|
|
879
|
+
{ id, prop: "loading_status", value: { validation_error: undefined } }
|
|
880
|
+
]);
|
|
881
|
+
}
|
|
817
882
|
update_value([{ id, prop, value }]);
|
|
818
883
|
if (prop === "input_ready" && value === false) {
|
|
819
884
|
inputs_waiting.push(id);
|
|
@@ -990,6 +1055,40 @@
|
|
|
990
1055
|
screen_recorder.startRecording();
|
|
991
1056
|
}
|
|
992
1057
|
}
|
|
1058
|
+
|
|
1059
|
+
let footer_height = 0;
|
|
1060
|
+
|
|
1061
|
+
let root_container: HTMLElement;
|
|
1062
|
+
$: root_node = $_layout && get_root_node(root_container);
|
|
1063
|
+
|
|
1064
|
+
function get_root_node(container: HTMLElement | null): HTMLElement | null {
|
|
1065
|
+
if (!container) return null;
|
|
1066
|
+
return container.children[container.children.length - 1] as HTMLElement;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
onMount(() => {
|
|
1070
|
+
if ("parentIFrame" in window) {
|
|
1071
|
+
window.parentIFrame?.autoResize(false);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const mut = new MutationObserver((mutations) => {
|
|
1075
|
+
if ("parentIFrame" in window) {
|
|
1076
|
+
const box = root_node?.getBoundingClientRect();
|
|
1077
|
+
if (!box) return;
|
|
1078
|
+
window.parentIFrame?.size(box.bottom + footer_height + 32);
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
mut.observe(root_container, {
|
|
1083
|
+
childList: true,
|
|
1084
|
+
subtree: true,
|
|
1085
|
+
attributes: true
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
return () => {
|
|
1089
|
+
mut.disconnect();
|
|
1090
|
+
};
|
|
1091
|
+
});
|
|
993
1092
|
</script>
|
|
994
1093
|
|
|
995
1094
|
<svelte:head>
|
|
@@ -1005,6 +1104,7 @@
|
|
|
1005
1104
|
<div
|
|
1006
1105
|
class="contain"
|
|
1007
1106
|
style:flex-grow={app_mode ? "1" : "auto"}
|
|
1107
|
+
bind:this={root_container}
|
|
1008
1108
|
style:margin-right={vibe_mode ? `${vibe_editor_width}px` : "0"}
|
|
1009
1109
|
>
|
|
1010
1110
|
{#if $_layout && app.config}
|
|
@@ -1023,7 +1123,7 @@
|
|
|
1023
1123
|
</div>
|
|
1024
1124
|
|
|
1025
1125
|
{#if show_footer}
|
|
1026
|
-
<footer>
|
|
1126
|
+
<footer bind:clientHeight={footer_height}>
|
|
1027
1127
|
{#if show_api}
|
|
1028
1128
|
<button
|
|
1029
1129
|
on:click={() => {
|
package/src/Embed.svelte
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { getContext } from "svelte";
|
|
2
|
+
import { getContext, onMount } from "svelte";
|
|
3
3
|
import space_logo from "./images/spaces.svg";
|
|
4
4
|
import { _ } from "svelte-i18n";
|
|
5
|
+
import { navbar_config } from "./navbar_store";
|
|
6
|
+
|
|
5
7
|
export let wrapper: HTMLDivElement;
|
|
6
8
|
export let version: string;
|
|
7
9
|
export let initial_height: string;
|
|
@@ -16,9 +18,63 @@
|
|
|
16
18
|
export let pages: [string, string][] = [];
|
|
17
19
|
export let current_page = "";
|
|
18
20
|
export let root: string;
|
|
21
|
+
export let components: any[] = [];
|
|
19
22
|
|
|
20
23
|
const set_page: ((page: string) => void) | undefined =
|
|
21
24
|
getContext("set_lite_page");
|
|
25
|
+
|
|
26
|
+
let navbar_component = components.find((c) => c.type === "navbar");
|
|
27
|
+
let navbar: {
|
|
28
|
+
visible: boolean;
|
|
29
|
+
main_page_name: string | false;
|
|
30
|
+
value: [string, string][] | null;
|
|
31
|
+
} | null = navbar_component
|
|
32
|
+
? {
|
|
33
|
+
visible: navbar_component.props.visible,
|
|
34
|
+
main_page_name: navbar_component.props.main_page_name,
|
|
35
|
+
value: navbar_component.props.value
|
|
36
|
+
}
|
|
37
|
+
: null;
|
|
38
|
+
|
|
39
|
+
if (navbar) {
|
|
40
|
+
navbar_config.set(navbar);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
$: if ($navbar_config) {
|
|
44
|
+
navbar = {
|
|
45
|
+
visible: $navbar_config.visible ?? true,
|
|
46
|
+
main_page_name: $navbar_config.main_page_name ?? "Home",
|
|
47
|
+
value: $navbar_config.value ?? null
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
$: show_navbar =
|
|
52
|
+
pages.length > 1 && (navbar === null || navbar.visible !== false);
|
|
53
|
+
|
|
54
|
+
$: effective_pages = (() => {
|
|
55
|
+
let base_pages =
|
|
56
|
+
navbar &&
|
|
57
|
+
navbar.main_page_name !== false &&
|
|
58
|
+
navbar.main_page_name !== "Home"
|
|
59
|
+
? pages.map(([route, label], index) =>
|
|
60
|
+
index === 0 && route === "" && label === "Home"
|
|
61
|
+
? ([route, navbar!.main_page_name] as [string, string])
|
|
62
|
+
: ([route, label] as [string, string])
|
|
63
|
+
)
|
|
64
|
+
: pages;
|
|
65
|
+
|
|
66
|
+
if (navbar?.value && navbar.value.length > 0) {
|
|
67
|
+
const existing_routes = new Set(base_pages.map(([route]) => route));
|
|
68
|
+
const additional_pages = navbar.value
|
|
69
|
+
.map(
|
|
70
|
+
([page_name, page_path]) => [page_path, page_name] as [string, string]
|
|
71
|
+
)
|
|
72
|
+
.filter(([route]) => !existing_routes.has(route));
|
|
73
|
+
return [...base_pages, ...additional_pages];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return base_pages;
|
|
77
|
+
})();
|
|
22
78
|
</script>
|
|
23
79
|
|
|
24
80
|
<div
|
|
@@ -31,10 +87,10 @@
|
|
|
31
87
|
style:flex-grow={!display ? "1" : "auto"}
|
|
32
88
|
data-iframe-height
|
|
33
89
|
>
|
|
34
|
-
{#if
|
|
90
|
+
{#if show_navbar}
|
|
35
91
|
<div class="nav-holder">
|
|
36
92
|
<nav class="fillable" class:fill_width>
|
|
37
|
-
{#each
|
|
93
|
+
{#each effective_pages as [route, label], i}
|
|
38
94
|
{#if is_lite}
|
|
39
95
|
<button
|
|
40
96
|
class:active={route === current_page}
|
|
@@ -46,9 +102,18 @@
|
|
|
46
102
|
</button>
|
|
47
103
|
{:else}
|
|
48
104
|
<a
|
|
49
|
-
href={
|
|
105
|
+
href={route.startsWith("http://") || route.startsWith("https://")
|
|
106
|
+
? route
|
|
107
|
+
: `${root}/${route}`}
|
|
50
108
|
class:active={route === current_page}
|
|
51
109
|
data-sveltekit-reload
|
|
110
|
+
target={route.startsWith("http://") ||
|
|
111
|
+
route.startsWith("https://")
|
|
112
|
+
? "_blank"
|
|
113
|
+
: "_self"}
|
|
114
|
+
rel={route.startsWith("http://") || route.startsWith("https://")
|
|
115
|
+
? "noopener noreferrer"
|
|
116
|
+
: ""}
|
|
52
117
|
>{label}
|
|
53
118
|
</a>
|
|
54
119
|
{/if}
|
|
@@ -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
|
+
}
|
package/src/i18n.ts
CHANGED
|
@@ -71,13 +71,14 @@ export function is_translation_metadata(obj: any): obj is I18nData {
|
|
|
71
71
|
return result;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
export const i18n_marker = "__i18n__";
|
|
75
|
+
|
|
74
76
|
// handles strings with embedded JSON metadata of shape "__i18n__{"key": "some.key"}"
|
|
75
77
|
export function translate_if_needed(value: any): string {
|
|
76
78
|
if (typeof value !== "string") {
|
|
77
79
|
return value;
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
const i18n_marker = "__i18n__";
|
|
81
82
|
const marker_index = value.indexOf(i18n_marker);
|
|
82
83
|
|
|
83
84
|
if (marker_index === -1) {
|
|
@@ -155,7 +156,8 @@ let i18n_initialized = false;
|
|
|
155
156
|
let previous_translations: Record<string, Record<string, string>> | undefined;
|
|
156
157
|
|
|
157
158
|
export async function setupi18n(
|
|
158
|
-
custom_translations?: Record<string, Record<string, string
|
|
159
|
+
custom_translations?: Record<string, Record<string, string>>,
|
|
160
|
+
preferred_locale?: string
|
|
159
161
|
): Promise<void> {
|
|
160
162
|
const should_reinitialize =
|
|
161
163
|
i18n_initialized && custom_translations !== previous_translations;
|
|
@@ -171,7 +173,7 @@ export async function setupi18n(
|
|
|
171
173
|
custom_translations: custom_translations ?? {}
|
|
172
174
|
});
|
|
173
175
|
|
|
174
|
-
const browser_locale = getLocaleFromNavigator();
|
|
176
|
+
const browser_locale = preferred_locale ?? getLocaleFromNavigator();
|
|
175
177
|
|
|
176
178
|
let initial_locale =
|
|
177
179
|
browser_locale && available_locales.includes(browser_locale)
|