@gradio/core 0.29.2 → 1.0.0-dev.3

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.
Files changed (107) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/src/Blocks.svelte +518 -999
  5. package/dist/src/Blocks.svelte.d.ts +31 -46
  6. package/dist/src/Embed.svelte +82 -55
  7. package/dist/src/Embed.svelte.d.ts +39 -30
  8. package/dist/src/Login.svelte +33 -29
  9. package/dist/src/Login.svelte.d.ts +21 -19
  10. package/dist/src/MountComponents.svelte +19 -25
  11. package/dist/src/MountComponents.svelte.d.ts +5 -28
  12. package/dist/src/{init.d.ts → _init.d.ts} +5 -4
  13. package/dist/src/{init.js → _init.js} +31 -108
  14. package/dist/src/api_docs/ApiBanner.svelte +12 -8
  15. package/dist/src/api_docs/ApiBanner.svelte.d.ts +22 -20
  16. package/dist/src/api_docs/ApiDocs.svelte +338 -246
  17. package/dist/src/api_docs/ApiDocs.svelte.d.ts +26 -24
  18. package/dist/src/api_docs/ApiRecorder.svelte +9 -3
  19. package/dist/src/api_docs/ApiRecorder.svelte.d.ts +19 -17
  20. package/dist/src/api_docs/CodeSnippet.svelte +60 -30
  21. package/dist/src/api_docs/CodeSnippet.svelte.d.ts +27 -24
  22. package/dist/src/api_docs/CopyButton.svelte +69 -13
  23. package/dist/src/api_docs/CopyButton.svelte.d.ts +18 -16
  24. package/dist/src/api_docs/CopyMarkdown.svelte +734 -0
  25. package/dist/src/api_docs/CopyMarkdown.svelte.d.ts +37 -0
  26. package/dist/src/api_docs/EndpointDetail.svelte +8 -6
  27. package/dist/src/api_docs/EndpointDetail.svelte.d.ts +20 -18
  28. package/dist/src/api_docs/IconArrowUpRight.svelte +34 -0
  29. package/dist/src/api_docs/IconArrowUpRight.svelte.d.ts +20 -0
  30. package/dist/src/api_docs/IconCaret.svelte +39 -0
  31. package/dist/src/api_docs/IconCaret.svelte.d.ts +20 -0
  32. package/dist/src/api_docs/IconHuggingChat.svelte +62 -0
  33. package/dist/src/api_docs/IconHuggingChat.svelte.d.ts +20 -0
  34. package/dist/src/api_docs/InputPayload.svelte +17 -11
  35. package/dist/src/api_docs/InputPayload.svelte.d.ts +25 -23
  36. package/dist/src/api_docs/InstallSnippet.svelte +9 -6
  37. package/dist/src/api_docs/InstallSnippet.svelte.d.ts +18 -16
  38. package/dist/src/api_docs/MCPSnippet.svelte +119 -99
  39. package/dist/src/api_docs/MCPSnippet.svelte.d.ts +59 -58
  40. package/dist/src/api_docs/NoApi.svelte +8 -5
  41. package/dist/src/api_docs/NoApi.svelte.d.ts +20 -18
  42. package/dist/src/api_docs/ParametersSnippet.svelte +8 -6
  43. package/dist/src/api_docs/ParametersSnippet.svelte.d.ts +21 -19
  44. package/dist/src/api_docs/RecordingSnippet.svelte +124 -110
  45. package/dist/src/api_docs/RecordingSnippet.svelte.d.ts +24 -22
  46. package/dist/src/api_docs/ResponseSnippet.svelte +7 -5
  47. package/dist/src/api_docs/ResponseSnippet.svelte.d.ts +21 -19
  48. package/dist/src/api_docs/Settings.svelte +73 -62
  49. package/dist/src/api_docs/Settings.svelte.d.ts +25 -23
  50. package/dist/src/api_docs/SettingsBanner.svelte +11 -8
  51. package/dist/src/api_docs/SettingsBanner.svelte.d.ts +20 -18
  52. package/dist/src/api_docs/TryButton.svelte +5 -3
  53. package/dist/src/api_docs/TryButton.svelte.d.ts +19 -17
  54. package/dist/src/api_docs/img/IconCheck.svelte +33 -0
  55. package/dist/src/api_docs/img/IconCheck.svelte.d.ts +26 -0
  56. package/dist/src/api_docs/img/IconCopy.svelte +40 -0
  57. package/dist/src/api_docs/img/IconCopy.svelte.d.ts +26 -0
  58. package/dist/src/api_docs/img/clear.svelte.d.ts +22 -21
  59. package/dist/src/dependency.d.ts +142 -0
  60. package/dist/src/dependency.js +653 -0
  61. package/dist/src/init.svelte.d.ts +78 -0
  62. package/dist/src/init.svelte.js +469 -0
  63. package/dist/src/init_utils.d.ts +32 -0
  64. package/dist/src/init_utils.js +73 -0
  65. package/dist/src/lang/en.json +10 -1
  66. package/dist/src/lang/get_lang_names.js +0 -3
  67. package/dist/src/lang/ru.json +10 -1
  68. package/dist/src/stores.d.ts +0 -21
  69. package/dist/src/stories/I18nMultiLanguageTestComponent.svelte +5 -3
  70. package/dist/src/stories/I18nMultiLanguageTestComponent.svelte.d.ts +16 -14
  71. package/dist/src/stories/I18nTestSetup.svelte +14 -10
  72. package/dist/src/stories/I18nTestSetup.svelte.d.ts +18 -16
  73. package/dist/src/types.d.ts +29 -25
  74. package/index.ts +1 -1
  75. package/package.json +59 -59
  76. package/src/Blocks.svelte +344 -1059
  77. package/src/MountComponents.svelte +17 -27
  78. package/src/{init.ts → _init.ts} +49 -126
  79. package/src/api_docs/ApiDocs.svelte +67 -62
  80. package/src/api_docs/ApiRecorder.svelte +3 -0
  81. package/src/api_docs/CodeSnippet.svelte +20 -5
  82. package/src/api_docs/CopyButton.svelte +61 -7
  83. package/src/api_docs/CopyMarkdown.svelte +734 -0
  84. package/src/api_docs/IconArrowUpRight.svelte +34 -0
  85. package/src/api_docs/IconCaret.svelte +39 -0
  86. package/src/api_docs/IconHuggingChat.svelte +62 -0
  87. package/src/api_docs/MCPSnippet.svelte +24 -46
  88. package/src/api_docs/NoApi.svelte +1 -1
  89. package/src/api_docs/ParametersSnippet.svelte +1 -1
  90. package/src/api_docs/ResponseSnippet.svelte +1 -1
  91. package/src/api_docs/Settings.svelte +11 -11
  92. package/src/api_docs/img/IconCheck.svelte +33 -0
  93. package/src/api_docs/img/IconCopy.svelte +40 -0
  94. package/src/dependency.ts +880 -0
  95. package/src/init.svelte.ts +717 -0
  96. package/src/init_utils.ts +99 -0
  97. package/src/lang/en.json +10 -1
  98. package/src/lang/get_lang_names.js +0 -3
  99. package/src/lang/ru.json +10 -1
  100. package/src/stores.ts +22 -22
  101. package/src/types.ts +54 -43
  102. package/dist/src/Render.svelte +0 -105
  103. package/dist/src/Render.svelte.d.ts +0 -31
  104. package/dist/src/RenderComponent.svelte +0 -72
  105. package/dist/src/RenderComponent.svelte.d.ts +0 -33
  106. package/src/Render.svelte +0 -126
  107. package/src/RenderComponent.svelte +0 -91
@@ -0,0 +1,734 @@
1
+ <script lang="ts">
2
+ import IconCopy from "./img/IconCopy.svelte";
3
+ import IconCheck from "./img/IconCheck.svelte";
4
+ import IconArrowUpRight from "./IconArrowUpRight.svelte";
5
+ import IconCaret from "./IconCaret.svelte";
6
+ import IconHuggingChat from "./IconHuggingChat.svelte";
7
+
8
+ import { tick } from "svelte";
9
+
10
+ import { represent_value } from "./utils";
11
+ import type { Dependency } from "../types";
12
+
13
+ export let current_language: "python" | "javascript" | "bash" | "mcp";
14
+ export let space_id: string | null;
15
+ export let root: string;
16
+ export let api_count: number;
17
+ export let tools: any[];
18
+ export let py_docs: string;
19
+ export let js_docs: string;
20
+ export let bash_docs: string;
21
+ export let mcp_docs: string;
22
+ export let spaces_docs_suffix: string;
23
+ export let mcp_server_active: boolean;
24
+ export let mcp_server_url_streamable: string;
25
+ export let config_snippets: Record<string, string>;
26
+ export let markdown_code_snippets: Record<string, Record<string, string>>;
27
+ export let dependencies: Dependency[];
28
+ export let info: any;
29
+ export let js_info: any;
30
+
31
+ let markdown_content: Record<string, string> = {
32
+ python: "",
33
+ javascript: "",
34
+ bash: "",
35
+ mcp: ""
36
+ };
37
+ /* eslint-disable complexity */
38
+ $: markdown_content.python = `
39
+ # Python API documentation for ${space_id || root}
40
+ API Endpoints: ${api_count}
41
+
42
+ 1. Install the Python client [docs](${py_docs}) if you don't already have it installed.
43
+
44
+ \`\`\`bash
45
+ pip install gradio_client
46
+ \`\`\`
47
+
48
+ 2. Find the API endpoint below corresponding to your desired function in the app. Copy the code snippet, replacing the placeholder values with your own input data. ${space_id ? "If this is a private Space, you may need to pass your Hugging Face token as well. [Read more](" + py_docs + spaces_docs_suffix + ")." : ""}
49
+
50
+ ${dependencies
51
+ .filter(
52
+ (d) =>
53
+ d.api_visibility === "public" && info.named_endpoints["/" + d.api_name]
54
+ )
55
+ .map(
56
+ (d) =>
57
+ `### API Name: /${d.api_name}
58
+ ${info?.named_endpoints["/" + d.api_name]?.description ? "Description: " + info?.named_endpoints["/" + d.api_name]?.description : ""}
59
+
60
+ \`\`\`python
61
+ ${markdown_code_snippets[d.api_name as keyof typeof markdown_code_snippets]?.python}
62
+ \`\`\`
63
+
64
+ Accepts ${info?.named_endpoints["/" + d.api_name]?.parameters?.length} parameter${info?.named_endpoints["/" + d.api_name]?.parameters?.length != 1 ? "s" : ""}:
65
+
66
+ ${info?.named_endpoints["/" + d.api_name]?.parameters
67
+ ?.map((p: any) => {
68
+ const required = !p.parameter_has_default;
69
+ const defaultValue = !required
70
+ ? `Default: ${represent_value(p.parameter_default, p.python_type.type, "py")}`
71
+ : "Required";
72
+ const type = `${p.python_type.type}${p.parameter_has_default && p.parameter_default === null ? " | None" : ""}`;
73
+ return `${p.parameter_name || `[${js_info.named_endpoints["/" + d.api_name]?.parameters.findIndex((p: any) => p.parameter_name === p.parameter_name)}]`}:\n- Type: ${type}\n- ${defaultValue}\n- The input value that is provided in the ${p.label} ${p.component} component. ${p.python_type.description}`;
74
+ })
75
+ .join("\n\n")}
76
+
77
+ Returns ${info?.named_endpoints["/" + d.api_name]?.returns?.length > 1 ? `tuple of ${info?.named_endpoints["/" + d.api_name]?.returns?.length} elements` : "1 element"}:
78
+
79
+ ${info?.named_endpoints["/" + d.api_name]?.returns
80
+ ?.map((r: any, i: number) => {
81
+ const type = r.python_type.type;
82
+ return `${info?.named_endpoints["/" + d.api_name]?.returns?.length > 1 ? `[${i}]: ` : ""}- Type: ${type}\n- The output value that appears in the "${r.label}" ${r.component} component.`;
83
+ })
84
+ .join("\n\n")}
85
+ `
86
+ )
87
+ .join("\n\n\n")}
88
+ `;
89
+ $: markdown_content.javascript = `
90
+ # JavaScript API documentation for ${space_id || root}
91
+ API Endpoints: ${api_count}
92
+
93
+ 1. Install the JavaScript client [docs](${js_docs}) if you don't already have it installed.
94
+
95
+ \`\`\`bash
96
+ npm i -D @gradio/client
97
+ \`\`\`
98
+
99
+ 2. Find the API endpoint below corresponding to your desired function in the app. Copy the code snippet, replacing the placeholder values with your own input data. ${space_id ? "If this is a private Space, you may need to pass your Hugging Face token as well. [Read more](" + js_docs + spaces_docs_suffix + ")." : ""}
100
+
101
+ ${dependencies
102
+ .filter(
103
+ (d) =>
104
+ d.api_visibility === "public" && info.named_endpoints["/" + d.api_name]
105
+ )
106
+ .map(
107
+ (d) =>
108
+ `### API Name: /${d.api_name}
109
+ ${info?.named_endpoints["/" + d.api_name]?.description ? "Description: " + info?.named_endpoints["/" + d.api_name]?.description : ""}
110
+
111
+ \`\`\`javascript
112
+ ${markdown_code_snippets[d.api_name as keyof typeof markdown_code_snippets]?.javascript}
113
+ \`\`\`
114
+
115
+ Accepts ${info?.named_endpoints["/" + d.api_name]?.parameters?.length} parameter${info?.named_endpoints["/" + d.api_name]?.parameters?.length != 1 ? "s" : ""}:
116
+
117
+ ${info?.named_endpoints["/" + d.api_name]?.parameters
118
+ ?.map((p: any) => {
119
+ const required = !p.parameter_has_default;
120
+ const defaultValue = !required
121
+ ? `Default: ${represent_value(p.parameter_default, p.python_type.type, "py")}`
122
+ : "Required";
123
+ const type = `${js_info.named_endpoints["/" + d.api_name]?.parameters.find((_p: any) => _p.parameter_name === p.parameter_name)?.type || "any"}`;
124
+ return `${p.parameter_name || `[${js_info.named_endpoints["/" + d.api_name]?.parameters.findIndex((_p: any) => _p.parameter_name === p.parameter_name)}]`}:\n- Type: ${type}\n- ${defaultValue}\n- The input value that is provided in the ${p.label} ${p.component} component. ${p.python_type.description}`;
125
+ })
126
+ .join("\n\n")}
127
+
128
+ Returns ${info?.named_endpoints["/" + d.api_name]?.returns?.length > 1 ? `list of ${info?.named_endpoints["/" + d.api_name]?.returns?.length} elements` : "1 element"}:
129
+
130
+ ${info?.named_endpoints["/" + d.api_name]?.returns
131
+ ?.map((r: any, i: number) => {
132
+ const type = js_info.named_endpoints["/" + d.api_name]?.returns[i]?.type;
133
+ return `${info?.named_endpoints["/" + d.api_name]?.returns?.length > 1 ? `[${i}]: ` : ""}- Type: ${type}\n- The output value that appears in the "${r.label}" ${r.component} component.`;
134
+ })
135
+ .join("\n\n")}`
136
+ )
137
+ .join("\n\n\n")}
138
+ `;
139
+ $: markdown_content.bash = `
140
+ # Bash API documentation for ${space_id || root}
141
+ API Endpoints: ${api_count}
142
+
143
+ 1. Confirm that you have cURL installed on your system.
144
+
145
+ \`\`\`bash
146
+ curl --version
147
+ \`\`\`
148
+
149
+ 2. Find the API endpoint below corresponding to your desired function in the app. Copy the code snippet, replacing the placeholder values with your own input data.
150
+
151
+ Making a prediction and getting a result requires 2 requests: a POST and a GET request. The POST request returns an EVENT_ID, which is used in the second GET request to fetch the results. In these snippets, we've used awk and read to parse the results, combining these two requests into one command for ease of use. See [curl docs](${bash_docs}).
152
+
153
+ ${dependencies
154
+ .filter(
155
+ (d) =>
156
+ d.api_visibility === "public" && info.named_endpoints["/" + d.api_name]
157
+ )
158
+ .map(
159
+ (d) =>
160
+ `### API Name: /${d.api_name}
161
+ ${info?.named_endpoints["/" + d.api_name]?.description ? "Description: " + info?.named_endpoints["/" + d.api_name]?.description : ""}
162
+
163
+ \`\`\`bash
164
+ ${markdown_code_snippets[d.api_name as keyof typeof markdown_code_snippets]?.bash}
165
+ \`\`\`
166
+
167
+ Accepts ${info?.named_endpoints["/" + d.api_name]?.parameters?.length} parameter${info?.named_endpoints["/" + d.api_name]?.parameters?.length != 1 ? "s" : ""}:
168
+
169
+ ${info?.named_endpoints["/" + d.api_name]?.parameters
170
+ ?.map((p: any) => {
171
+ const defaultValue = "Required";
172
+ const type = `${js_info.named_endpoints["/" + d.api_name]?.parameters.find((_p: any) => _p.parameter_name === p.parameter_name)?.type || "any"}`;
173
+ return `${`[${js_info.named_endpoints["/" + d.api_name]?.parameters.findIndex((_p: any) => _p.parameter_name === p.parameter_name)}]`}:\n- Type: ${type}\n- ${defaultValue}\n- The input value that is provided in the ${p.label} ${p.component} component. ${p.python_type.description}`;
174
+ })
175
+ .join("\n\n")}
176
+
177
+ Returns ${info?.named_endpoints["/" + d.api_name]?.returns?.length > 1 ? `list of ${info?.named_endpoints["/" + d.api_name]?.returns?.length} elements` : "1 element"}:
178
+
179
+ ${info?.named_endpoints["/" + d.api_name]?.returns
180
+ ?.map((r: any, i: number) => {
181
+ const type = js_info.named_endpoints["/" + d.api_name]?.returns[i]?.type;
182
+ return `${info?.named_endpoints["/" + d.api_name]?.returns?.length > 1 ? `[${i}]: ` : ""}- Type: ${type}\n- The output value that appears in the "${r.label}" ${r.component} component.`;
183
+ })
184
+ .join("\n\n")}
185
+ `
186
+ )
187
+ .join("\n\n\n")}
188
+ `;
189
+ $: markdown_content.mcp = `
190
+ # MCP documentation for ${space_id || root}
191
+ MCP Tools: ${tools.length}
192
+
193
+ ${
194
+ !mcp_server_active
195
+ ? `This Gradio app can also serve as an MCP server, with an MCP tool corresponding to each API endpoint.
196
+ To enable this, launch this Gradio app with \`.launch(mcp_server=True)\` or set the \`.launch(mcp_server=True)\`or set the \`GRADIO_MCP_SERVER\` env variable to \`"True"\`.`
197
+ : `
198
+
199
+ This page documents two transports: Streamable HTTP and STDIO.
200
+
201
+ ### Streamable HTTP
202
+
203
+ MCP Server URL (Streamable HTTP): ${mcp_server_url_streamable}
204
+
205
+ ${tools.length} available MCP tools, resources, and prompts:
206
+
207
+ ${tools
208
+ .map(
209
+ (tool) =>
210
+ `### ${tool.name}
211
+ Type: ${tool.meta.mcp_type}
212
+ Description: ${tool.description ? tool.description : "No description provided in function docstring"}
213
+ Parameters: ${Object.keys(tool.parameters).length}
214
+ ${Object.keys(tool.parameters)
215
+ .map((parameter) => {
216
+ return `- ${parameter} (${tool.parameters[parameter].type}): ${tool.parameters[parameter].description ? tool.parameters[parameter].description : "No description provided in function docstring"}`;
217
+ })
218
+ .join("\n")}
219
+ `
220
+ )
221
+ .join("\n\n")}
222
+
223
+ Streamable HTTP Transport: To add this MCP to clients that support Streamable HTTP, simply add the following configuration to your MCP config.
224
+
225
+ \`\`\`json
226
+ ${config_snippets.streamable_http}
227
+ \`\`\`
228
+
229
+ The \`upload_files_to_gradio\` tool uploads files from your local \`UPLOAD_DIRECTORY\` (or any of its subdirectories) to the Gradio app.
230
+ This is needed because MCP servers require files to be provided as URLs. You can omit this tool if you prefer to upload files manually. This tool requires [uv](https://docs.astral.sh/uv/getting-started/installation/) to be installed.
231
+
232
+ ### STDIO Transport
233
+
234
+
235
+ ${tools.length} available MCP tools, resources, and prompts:
236
+
237
+ ${tools
238
+ .map(
239
+ (tool) =>
240
+ `### ${tool.name}
241
+ Type: ${tool.meta.mcp_type}
242
+ Description: ${tool.description ? tool.description : "No description provided in function docstring"}
243
+ Parameters: ${Object.keys(tool.parameters).length}
244
+ ${Object.keys(tool.parameters)
245
+ .map((parameter) => {
246
+ return `- ${parameter} (${tool.parameters[parameter].type}): ${tool.parameters[parameter].description ? tool.parameters[parameter].description : "No description provided in function docstring"}`;
247
+ })
248
+ .join("\n")}
249
+ `
250
+ )
251
+ .join("\n\n")}
252
+
253
+ STDIO Transport: For clients that only support stdio (e.g. Claude Desktop), first [install Node.js](https://nodejs.org/en/download/). Then, you can use the following command:
254
+
255
+ \`\`\`json
256
+ ${config_snippets.stdio}
257
+ \`\`\`
258
+
259
+ The \`upload_files_to_gradio\` tool uploads files from your local \`UPLOAD_DIRECTORY\` (or any of its subdirectories) to the Gradio app.
260
+ This is needed because MCP servers require files to be provided as URLs. You can omit this tool if you prefer to upload files manually. This tool requires [uv](https://docs.astral.sh/uv/getting-started/installation/) to be installed.
261
+
262
+ Read more about the MCP in the [Gradio docs](${mcp_docs}).
263
+ `
264
+ }
265
+
266
+ `;
267
+ /* eslint-enable complexity */
268
+
269
+ let current_language_label =
270
+ current_language === "python"
271
+ ? "Python"
272
+ : current_language === "javascript"
273
+ ? "JavaScript"
274
+ : current_language === "bash"
275
+ ? "Bash"
276
+ : "MCP";
277
+
278
+ $: current_language;
279
+ $: current_language_label =
280
+ current_language === "python"
281
+ ? "Python"
282
+ : current_language === "javascript"
283
+ ? "JavaScript"
284
+ : current_language === "bash"
285
+ ? "Bash"
286
+ : "MCP";
287
+
288
+ let label = `Copy ${current_language_label} Docs as Markdown for LLMs`;
289
+ $: label = `Copy ${current_language_label} Docs as Markdown for LLMs`;
290
+
291
+ let copied = false;
292
+ $: copied;
293
+
294
+ let open = false;
295
+ let triggerEl: HTMLDivElement | null = null;
296
+ let menuEl: HTMLDivElement | null = null;
297
+ let menuStyle = "";
298
+
299
+ const isClient = typeof window !== "undefined";
300
+
301
+ function openMenu(): void {
302
+ open = true;
303
+ if (isClient && triggerEl) {
304
+ void tick().then(() => {
305
+ if (!triggerEl) return;
306
+ const rect = triggerEl.getBoundingClientRect();
307
+ const gutter = 6;
308
+ const minWidth = Math.max(rect.width + 80, 220);
309
+ const right = Math.max(window.innerWidth - rect.right, gutter);
310
+ menuStyle = `top:${rect.bottom + gutter}px;right:${right}px;min-width:${minWidth}px;`;
311
+ });
312
+ }
313
+ }
314
+
315
+ function closeMenu(): void {
316
+ open = false;
317
+ }
318
+
319
+ function toggleMenu(): void {
320
+ open ? closeMenu() : openMenu();
321
+ }
322
+
323
+ function buildUrl(): string {
324
+ const encodedPromptText = encodeURIComponent(
325
+ `--------------------------------
326
+ ${markdown_content[current_language]}
327
+ --------------------------------
328
+
329
+ Read the documentation above so I can ask questions about it.`
330
+ );
331
+ return `https://huggingface.co/chat/?prompt=${encodedPromptText}`;
332
+ }
333
+
334
+ function openHuggingChat(): void {
335
+ if (isClient) {
336
+ window.open(buildUrl(), "_blank", "noopener,noreferrer");
337
+ }
338
+ closeMenu();
339
+ }
340
+
341
+ function handleWindowPointer(event: MouseEvent): void {
342
+ if (!open || !isClient) return;
343
+ const targetNode = event.target as Node;
344
+ if (menuEl?.contains(targetNode) || triggerEl?.contains(targetNode)) {
345
+ return;
346
+ }
347
+ closeMenu();
348
+ }
349
+
350
+ function handleWindowKeydown(event: KeyboardEvent): void {
351
+ if (event.key === "Escape" && open) {
352
+ closeMenu();
353
+ }
354
+ }
355
+
356
+ function handleWindowResize(): void {
357
+ if (open) closeMenu();
358
+ }
359
+
360
+ function handleWindowScroll(): void {
361
+ if (open) closeMenu();
362
+ }
363
+
364
+ async function copyMarkdown(
365
+ current_language: "python" | "javascript" | "bash" | "mcp"
366
+ ): Promise<void> {
367
+ try {
368
+ if (!markdown_content[current_language]) {
369
+ console.warn("Nothing to copy");
370
+ return;
371
+ }
372
+
373
+ const hasNavigatorClipboard =
374
+ typeof navigator !== "undefined" &&
375
+ !!navigator.clipboard &&
376
+ typeof navigator.clipboard.writeText === "function";
377
+
378
+ if (hasNavigatorClipboard) {
379
+ await navigator.clipboard.writeText(markdown_content[current_language]);
380
+ } else {
381
+ console.warn("Clipboard API unavailable");
382
+ return;
383
+ }
384
+
385
+ copied = true;
386
+ setTimeout(() => {
387
+ copied = false;
388
+ }, 1500);
389
+ } catch (error) {
390
+ console.error("Failed to write to clipboard", error);
391
+ }
392
+ }
393
+ </script>
394
+
395
+ <svelte:window
396
+ on:mousedown={handleWindowPointer}
397
+ on:keydown={handleWindowKeydown}
398
+ on:resize={handleWindowResize}
399
+ on:scroll={handleWindowScroll}
400
+ />
401
+
402
+ <div class="container-wrapper">
403
+ <div bind:this={triggerEl} class="trigger-wrapper">
404
+ <button
405
+ on:click={() => copyMarkdown(current_language)}
406
+ class="copy-button"
407
+ aria-live="polite"
408
+ >
409
+ <span class="icon-wrapper">
410
+ {#if copied}
411
+ <IconCheck />
412
+ {:else}
413
+ <IconCopy />
414
+ {/if}
415
+ </span>
416
+ <span
417
+ >{copied ? `Copied ${current_language_label} Docs!` : "Copy Page"}</span
418
+ >
419
+ </button>
420
+ <button
421
+ on:click={toggleMenu}
422
+ class="menu-toggle-button"
423
+ aria-haspopup="menu"
424
+ aria-expanded={open}
425
+ aria-label={open ? "Close copy menu" : "Open copy menu"}
426
+ >
427
+ <IconCaret
428
+ classNames={`caret-icon ${open ? "rotate-180" : "rotate-0"}`}
429
+ />
430
+ </button>
431
+ </div>
432
+
433
+ {#if open}
434
+ <div
435
+ class="backdrop-overlay"
436
+ aria-hidden="true"
437
+ style="background: transparent;"
438
+ on:click={closeMenu}
439
+ ></div>
440
+ <div
441
+ bind:this={menuEl}
442
+ role="menu"
443
+ class="menu-dropdown"
444
+ style={menuStyle}
445
+ aria-label="Copy menu"
446
+ >
447
+ <button
448
+ role="menuitem"
449
+ on:click={() => {
450
+ copyMarkdown(current_language);
451
+ closeMenu();
452
+ }}
453
+ class="base-menu-item"
454
+ >
455
+ <div class="menu-icon-container">
456
+ <IconCopy classNames="menu-icon" />
457
+ </div>
458
+ <div class="menu-text-container">
459
+ <div class="menu-text-primary">Copy Page</div>
460
+ <div class="menu-text-secondary">
461
+ {label}
462
+ </div>
463
+ </div>
464
+ </button>
465
+
466
+ <button
467
+ role="menuitem"
468
+ on:click={() => {
469
+ openHuggingChat();
470
+ closeMenu();
471
+ }}
472
+ class="base-menu-item"
473
+ >
474
+ <div class="menu-icon-container">
475
+ <IconHuggingChat classNames="menu-icon" />
476
+ </div>
477
+ <div class="menu-text-container">
478
+ <div class="menu-text-primary">
479
+ Open in HuggingChat
480
+ <IconArrowUpRight classNames="menu-icon-arrow" />
481
+ </div>
482
+ <div class="menu-text-secondary">
483
+ Ask Questions About The {current_language_label} Docs
484
+ </div>
485
+ </div>
486
+ </button>
487
+ </div>
488
+ {/if}
489
+ </div>
490
+
491
+ <style>
492
+ .container-wrapper {
493
+ display: flex;
494
+ align-items: center;
495
+ flex-shrink: 0;
496
+ min-width: 100px;
497
+ justify-content: flex-end;
498
+ margin-left: auto;
499
+ }
500
+
501
+ @media (max-width: 640px) {
502
+ .container-wrapper {
503
+ min-width: 50px;
504
+ }
505
+ }
506
+
507
+ .trigger-wrapper {
508
+ display: inline-flex;
509
+ border-radius: 0.375rem;
510
+ }
511
+
512
+ @media (max-width: 640px) {
513
+ .trigger-wrapper {
514
+ border-radius: 0.125rem;
515
+ }
516
+ }
517
+
518
+ .icon-wrapper {
519
+ display: inline-flex;
520
+ align-items: center;
521
+ justify-content: center;
522
+ border-radius: 0.375rem;
523
+ padding: 0.125rem;
524
+ }
525
+
526
+ @media (max-width: 640px) {
527
+ .icon-wrapper {
528
+ padding: 0;
529
+ }
530
+ }
531
+
532
+ .menu-toggle-button {
533
+ display: inline-flex;
534
+ align-items: center;
535
+ justify-content: center;
536
+ width: 1.5rem;
537
+ height: 1.5rem;
538
+ font-size: 0.875rem;
539
+ color: rgb(107, 114, 128);
540
+ border-radius: 0;
541
+ border-top-right-radius: 0.375rem;
542
+ border-bottom-right-radius: 0.375rem;
543
+ border: 1px solid rgb(229, 231, 235);
544
+ border-left: none;
545
+ background-color: white;
546
+ transition: all 0.2s ease-in-out;
547
+ }
548
+
549
+ .menu-toggle-button:disabled {
550
+ pointer-events: none;
551
+ }
552
+
553
+ .menu-toggle-button:hover {
554
+ color: rgb(55, 65, 81);
555
+ box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.05);
556
+ }
557
+
558
+ @media (max-width: 640px) {
559
+ .menu-toggle-button {
560
+ width: 1.25rem;
561
+ height: 1.25rem;
562
+ border-top-right-radius: 0.125rem;
563
+ border-bottom-right-radius: 0.125rem;
564
+ }
565
+ }
566
+
567
+ @media (prefers-color-scheme: dark) {
568
+ .menu-toggle-button {
569
+ border-color: rgb(38, 38, 38);
570
+ background-color: rgb(10, 10, 10);
571
+ color: rgb(229, 231, 235);
572
+ }
573
+
574
+ .menu-toggle-button:hover {
575
+ color: white;
576
+ background-color: rgb(31, 41, 55);
577
+ }
578
+ }
579
+
580
+ .backdrop-overlay {
581
+ position: fixed;
582
+ top: 0;
583
+ right: 0;
584
+ bottom: 0;
585
+ left: 0;
586
+ z-index: 40;
587
+ }
588
+
589
+ .menu-dropdown {
590
+ position: fixed;
591
+ z-index: 50;
592
+ backdrop-filter: blur(24px);
593
+ border-radius: 0.75rem;
594
+ max-height: 420px;
595
+ overflow-y: auto;
596
+ padding: 0.25rem;
597
+ border: 1px solid rgb(229, 231, 235);
598
+ display: flex;
599
+ flex-direction: column;
600
+ background-color: white;
601
+ color: rgb(31, 41, 55);
602
+ }
603
+
604
+ @media (prefers-color-scheme: dark) {
605
+ .menu-dropdown {
606
+ border-color: rgb(38, 38, 38);
607
+ background-color: rgb(10, 10, 10);
608
+ color: rgb(229, 231, 235);
609
+ }
610
+ }
611
+
612
+ .menu-icon-container {
613
+ border: 1px solid rgb(229, 231, 235);
614
+ border-radius: 0.5rem;
615
+ padding: 0.375rem;
616
+ }
617
+
618
+ @media (prefers-color-scheme: dark) {
619
+ .menu-icon-container {
620
+ border-color: rgb(38, 38, 38);
621
+ }
622
+ }
623
+
624
+ .menu-text-container {
625
+ display: flex;
626
+ flex-direction: column;
627
+ padding-left: 0.25rem;
628
+ padding-right: 0.25rem;
629
+ }
630
+
631
+ .menu-text-primary {
632
+ font-size: 11px;
633
+ font-weight: 500;
634
+ color: rgb(31, 41, 55);
635
+ display: flex;
636
+ align-items: center;
637
+ gap: 0.25rem;
638
+ }
639
+
640
+ @media (prefers-color-scheme: dark) {
641
+ .menu-text-primary {
642
+ color: rgb(209, 213, 219);
643
+ }
644
+ }
645
+
646
+ .menu-text-secondary {
647
+ font-size: 0.75rem;
648
+ color: rgb(75, 85, 99);
649
+ }
650
+
651
+ @media (prefers-color-scheme: dark) {
652
+ .menu-text-secondary {
653
+ color: rgb(156, 163, 175);
654
+ }
655
+ }
656
+
657
+ .copy-button {
658
+ display: inline-flex;
659
+ align-items: center;
660
+ gap: 4px;
661
+ height: 24px;
662
+ padding-left: 8px;
663
+ padding-right: 6px;
664
+ font-size: 11px;
665
+ font-weight: 500;
666
+ color: rgb(31, 41, 55);
667
+ border: 1px solid rgb(229, 231, 235);
668
+ border-radius: 6px;
669
+ border-top-right-radius: 0;
670
+ border-bottom-right-radius: 0;
671
+ background-color: white;
672
+
673
+ @media (max-width: 640px) {
674
+ gap: 2px;
675
+ height: 20px;
676
+ padding-left: 6px;
677
+ padding-right: 6px;
678
+ font-size: 9px;
679
+ border-top-left-radius: 4px;
680
+ border-bottom-left-radius: 4px;
681
+ }
682
+
683
+ &:hover {
684
+ box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
685
+ }
686
+
687
+ @media (prefers-color-scheme: dark) {
688
+ border-color: rgb(38, 38, 38);
689
+ background-color: rgb(10, 10, 10);
690
+ color: rgb(229, 231, 235);
691
+
692
+ &:hover {
693
+ background-color: rgb(38, 38, 38);
694
+ }
695
+ }
696
+ }
697
+
698
+ .base-menu-item {
699
+ cursor: pointer;
700
+ font-size: 11px;
701
+ position: relative;
702
+ width: 100%;
703
+ user-select: none;
704
+ outline: none;
705
+ display: flex;
706
+ align-items: center;
707
+ gap: 0.375rem;
708
+ padding-left: 0.375rem;
709
+ padding-right: 0.375rem;
710
+ padding-top: 0.375rem;
711
+ padding-bottom: 0.375rem;
712
+ border-radius: 0.75rem;
713
+ text-align: left;
714
+ transition: all 0.2s ease-in-out;
715
+ border-color: #e5e7eb;
716
+ background-color: #ffffff;
717
+ }
718
+
719
+ .base-menu-item:hover {
720
+ box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.05);
721
+ }
722
+
723
+ @media (prefers-color-scheme: dark) {
724
+ .base-menu-item {
725
+ border-color: #1f2937;
726
+ background-color: #0f1117;
727
+ color: #e5e7eb;
728
+ }
729
+
730
+ .base-menu-item:hover {
731
+ background-color: #1f2937;
732
+ }
733
+ }
734
+ </style>