@gradio/core 0.16.1 → 0.18.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 +43 -0
- package/dist/src/Blocks.svelte +75 -4
- package/dist/src/Render.svelte +2 -2
- package/dist/src/RenderComponent.svelte +21 -1
- package/dist/src/api_docs/ApiDocs.svelte +6 -1
- package/dist/src/api_docs/Settings.svelte +74 -1
- package/dist/src/api_docs/Settings.svelte.d.ts +3 -0
- package/dist/src/api_docs/img/record-stop.svg +1 -0
- package/dist/src/api_docs/img/record.svg +1 -0
- package/dist/src/gradio_helper.d.ts +1 -11
- package/dist/src/gradio_helper.js +3 -19
- package/dist/src/i18n.d.ts +12 -5
- package/dist/src/i18n.js +93 -12
- package/dist/src/init.js +40 -20
- package/dist/src/lang/en.json +6 -1
- package/dist/src/lang/es.json +2 -1
- package/dist/src/lang/fr.json +2 -1
- package/dist/src/lang/ja.json +2 -1
- package/dist/src/lang/ko.json +2 -1
- package/dist/src/lang/lt.json +2 -1
- package/dist/src/lang/nb.json +2 -1
- package/dist/src/lang/nl.json +2 -1
- package/dist/src/lang/pl.json +2 -1
- package/dist/src/lang/pt-BR.json +1 -0
- package/dist/src/lang/pt.json +2 -1
- package/dist/src/lang/ro.json +2 -1
- package/dist/src/lang/ru.json +2 -1
- package/dist/src/lang/sv.json +2 -1
- package/dist/src/lang/ta.json +2 -1
- package/dist/src/lang/th.json +2 -1
- package/dist/src/lang/tr.json +2 -1
- package/dist/src/lang/uk.json +2 -1
- package/dist/src/lang/ur.json +2 -1
- package/dist/src/lang/uz.json +2 -1
- package/dist/src/lang/zh-CN.json +2 -1
- package/dist/src/lang/zh-TW.json +2 -1
- package/dist/src/screen_recorder.d.ts +16 -0
- package/dist/src/screen_recorder.js +255 -0
- package/package.json +53 -53
- package/src/Blocks.svelte +86 -6
- package/src/Render.svelte +2 -2
- package/src/RenderComponent.svelte +21 -1
- package/src/api_docs/ApiDocs.svelte +7 -1
- package/src/api_docs/Settings.svelte +77 -1
- package/src/api_docs/img/record-stop.svg +1 -0
- package/src/api_docs/img/record.svg +1 -0
- package/src/gradio_helper.ts +5 -21
- package/src/i18n.test.ts +120 -1
- package/src/i18n.ts +126 -24
- package/src/init.ts +48 -26
- package/src/lang/en.json +6 -1
- package/src/lang/es.json +2 -1
- package/src/lang/fr.json +2 -1
- package/src/lang/ja.json +2 -1
- package/src/lang/ko.json +2 -1
- package/src/lang/lt.json +2 -1
- package/src/lang/nb.json +2 -1
- package/src/lang/nl.json +2 -1
- package/src/lang/pl.json +2 -1
- package/src/lang/pt-BR.json +1 -0
- package/src/lang/pt.json +2 -1
- package/src/lang/ro.json +2 -1
- package/src/lang/ru.json +2 -1
- package/src/lang/sv.json +2 -1
- package/src/lang/ta.json +2 -1
- package/src/lang/th.json +2 -1
- package/src/lang/tr.json +2 -1
- package/src/lang/uk.json +2 -1
- package/src/lang/ur.json +2 -1
- package/src/lang/uz.json +2 -1
- package/src/lang/zh-CN.json +2 -1
- package/src/lang/zh-TW.json +2 -1
- package/src/screen_recorder.ts +361 -0
package/src/lang/uz.json
CHANGED
|
@@ -97,7 +97,8 @@
|
|
|
97
97
|
"runtime_error": "Bajarilish vaqti xatosi mavjud",
|
|
98
98
|
"space_not_working": "\"Space ishlamayapti, chunki\" {0}",
|
|
99
99
|
"space_paused": "Space to'xtatilgan",
|
|
100
|
-
"use_via_api": "API orqali foydalaning"
|
|
100
|
+
"use_via_api": "API orqali foydalaning",
|
|
101
|
+
"use_via_api_or_mcp": "API yoki MCP orqali foydalaning"
|
|
101
102
|
},
|
|
102
103
|
"file": {
|
|
103
104
|
"uploading": "Yuklanmoqda..."
|
package/src/lang/zh-CN.json
CHANGED
|
@@ -87,7 +87,8 @@
|
|
|
87
87
|
"runtime_error": "存在运行时错误",
|
|
88
88
|
"space_not_working": "\"Space 无法工作,原因:\" {0}",
|
|
89
89
|
"space_paused": "Space 已暂停",
|
|
90
|
-
"use_via_api": "通过 API 使用"
|
|
90
|
+
"use_via_api": "通过 API 使用",
|
|
91
|
+
"use_via_api_or_mcp": "通过 API 或 MCP 使用"
|
|
91
92
|
},
|
|
92
93
|
"file": {
|
|
93
94
|
"uploading": "正在上传..."
|
package/src/lang/zh-TW.json
CHANGED
|
@@ -97,7 +97,8 @@
|
|
|
97
97
|
"runtime_error": "有執行時錯誤",
|
|
98
98
|
"space_not_working": "\"Space 無法運作,因為\" {0}",
|
|
99
99
|
"space_paused": "Space 已暫停",
|
|
100
|
-
"use_via_api": "透過 API 使用"
|
|
100
|
+
"use_via_api": "透過 API 使用",
|
|
101
|
+
"use_via_api_or_mcp": "透過 API 或 MCP 使用"
|
|
101
102
|
},
|
|
102
103
|
"file": {
|
|
103
104
|
"uploading": "上傳中..."
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import type { ToastMessage } from "@gradio/statustracker";
|
|
2
|
+
|
|
3
|
+
let isRecording = false;
|
|
4
|
+
let mediaRecorder: MediaRecorder | null = null;
|
|
5
|
+
let recordedChunks: Blob[] = [];
|
|
6
|
+
let recordingStartTime = 0;
|
|
7
|
+
let animationFrameId: number | null = null;
|
|
8
|
+
let removeSegment: { start?: number; end?: number } = {};
|
|
9
|
+
let root: string;
|
|
10
|
+
|
|
11
|
+
let add_message_callback: (
|
|
12
|
+
title: string,
|
|
13
|
+
message: string,
|
|
14
|
+
type: ToastMessage["type"]
|
|
15
|
+
) => void;
|
|
16
|
+
let onRecordingStateChange: ((isRecording: boolean) => void) | null = null;
|
|
17
|
+
let zoomEffects: {
|
|
18
|
+
boundingBox: { topLeft: [number, number]; bottomRight: [number, number] };
|
|
19
|
+
start_frame: number;
|
|
20
|
+
duration?: number;
|
|
21
|
+
}[] = [];
|
|
22
|
+
|
|
23
|
+
export function initialize(
|
|
24
|
+
rootPath: string,
|
|
25
|
+
add_new_message: (
|
|
26
|
+
title: string,
|
|
27
|
+
message: string,
|
|
28
|
+
type: ToastMessage["type"]
|
|
29
|
+
) => void,
|
|
30
|
+
recordingStateCallback?: (isRecording: boolean) => void
|
|
31
|
+
): void {
|
|
32
|
+
root = rootPath;
|
|
33
|
+
add_message_callback = add_new_message;
|
|
34
|
+
if (recordingStateCallback) {
|
|
35
|
+
onRecordingStateChange = recordingStateCallback;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function startRecording(): Promise<void> {
|
|
40
|
+
if (isRecording) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const originalTitle = document.title;
|
|
46
|
+
document.title = "[Sharing] Gradio Tab";
|
|
47
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
48
|
+
video: {
|
|
49
|
+
width: { ideal: 1920 },
|
|
50
|
+
height: { ideal: 1080 },
|
|
51
|
+
frameRate: { ideal: 30 }
|
|
52
|
+
},
|
|
53
|
+
audio: true,
|
|
54
|
+
selfBrowserSurface: "include"
|
|
55
|
+
} as MediaStreamConstraints);
|
|
56
|
+
document.title = originalTitle;
|
|
57
|
+
|
|
58
|
+
const options = {
|
|
59
|
+
videoBitsPerSecond: 5000000
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
mediaRecorder = new MediaRecorder(stream, options);
|
|
63
|
+
|
|
64
|
+
recordedChunks = [];
|
|
65
|
+
removeSegment = {};
|
|
66
|
+
|
|
67
|
+
mediaRecorder.ondataavailable = handleDataAvailable;
|
|
68
|
+
mediaRecorder.onstop = handleStop;
|
|
69
|
+
|
|
70
|
+
mediaRecorder.start(1000);
|
|
71
|
+
isRecording = true;
|
|
72
|
+
if (onRecordingStateChange) {
|
|
73
|
+
onRecordingStateChange(true);
|
|
74
|
+
}
|
|
75
|
+
recordingStartTime = Date.now();
|
|
76
|
+
} catch (error: any) {
|
|
77
|
+
add_message_callback(
|
|
78
|
+
"Recording Error",
|
|
79
|
+
"Failed to start recording: " + error.message,
|
|
80
|
+
"error"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function stopRecording(): void {
|
|
86
|
+
if (!isRecording || !mediaRecorder) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
mediaRecorder.stop();
|
|
91
|
+
isRecording = false;
|
|
92
|
+
if (onRecordingStateChange) {
|
|
93
|
+
onRecordingStateChange(false);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function isCurrentlyRecording(): boolean {
|
|
98
|
+
return isRecording;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function markRemoveSegmentStart(): void {
|
|
102
|
+
if (!isRecording) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const currentTime = (Date.now() - recordingStartTime) / 1000;
|
|
107
|
+
removeSegment.start = currentTime;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function markRemoveSegmentEnd(): void {
|
|
111
|
+
if (!isRecording || removeSegment.start === undefined) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const currentTime = (Date.now() - recordingStartTime) / 1000;
|
|
116
|
+
removeSegment.end = currentTime;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function clearRemoveSegment(): void {
|
|
120
|
+
removeSegment = {};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function addZoomEffect(
|
|
124
|
+
is_input: boolean,
|
|
125
|
+
params: {
|
|
126
|
+
boundingBox: {
|
|
127
|
+
topLeft: [number, number];
|
|
128
|
+
bottomRight: [number, number];
|
|
129
|
+
};
|
|
130
|
+
duration?: number;
|
|
131
|
+
}
|
|
132
|
+
): void {
|
|
133
|
+
if (!isRecording) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const FPS = 30;
|
|
138
|
+
const currentTime = (Date.now() - recordingStartTime) / 1000;
|
|
139
|
+
const currentFrame = is_input
|
|
140
|
+
? Math.floor((currentTime - 2) * FPS)
|
|
141
|
+
: Math.floor(currentTime * FPS);
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
params.boundingBox &&
|
|
145
|
+
params.boundingBox.topLeft &&
|
|
146
|
+
params.boundingBox.bottomRight &&
|
|
147
|
+
params.boundingBox.topLeft.length === 2 &&
|
|
148
|
+
params.boundingBox.bottomRight.length === 2
|
|
149
|
+
) {
|
|
150
|
+
const newEffectDuration = params.duration || 2.0;
|
|
151
|
+
const newEffectEndFrame =
|
|
152
|
+
currentFrame + Math.floor(newEffectDuration * FPS);
|
|
153
|
+
|
|
154
|
+
const hasOverlap = zoomEffects.some((existingEffect) => {
|
|
155
|
+
const existingEffectEndFrame =
|
|
156
|
+
existingEffect.start_frame +
|
|
157
|
+
Math.floor((existingEffect.duration || 2.0) * FPS);
|
|
158
|
+
return (
|
|
159
|
+
(currentFrame >= existingEffect.start_frame &&
|
|
160
|
+
currentFrame <= existingEffectEndFrame) ||
|
|
161
|
+
(newEffectEndFrame >= existingEffect.start_frame &&
|
|
162
|
+
newEffectEndFrame <= existingEffectEndFrame) ||
|
|
163
|
+
(currentFrame <= existingEffect.start_frame &&
|
|
164
|
+
newEffectEndFrame >= existingEffectEndFrame)
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (!hasOverlap) {
|
|
169
|
+
zoomEffects.push({
|
|
170
|
+
boundingBox: params.boundingBox,
|
|
171
|
+
start_frame: currentFrame,
|
|
172
|
+
duration: newEffectDuration
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function zoom(
|
|
179
|
+
is_input: boolean,
|
|
180
|
+
elements: number[],
|
|
181
|
+
duration = 2.0
|
|
182
|
+
): void {
|
|
183
|
+
if (!isRecording) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
if (!elements || elements.length === 0) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let minLeft = Infinity;
|
|
194
|
+
let minTop = Infinity;
|
|
195
|
+
let maxRight = 0;
|
|
196
|
+
let maxBottom = 0;
|
|
197
|
+
let foundElements = false;
|
|
198
|
+
|
|
199
|
+
for (const elementId of elements) {
|
|
200
|
+
const selector = `#component-${elementId}`;
|
|
201
|
+
const element = document.querySelector(selector);
|
|
202
|
+
|
|
203
|
+
if (element) {
|
|
204
|
+
foundElements = true;
|
|
205
|
+
const rect = element.getBoundingClientRect();
|
|
206
|
+
|
|
207
|
+
minLeft = Math.min(minLeft, rect.left);
|
|
208
|
+
minTop = Math.min(minTop, rect.top);
|
|
209
|
+
maxRight = Math.max(maxRight, rect.right);
|
|
210
|
+
maxBottom = Math.max(maxBottom, rect.bottom);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!foundElements) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const viewportWidth = window.innerWidth;
|
|
219
|
+
const viewportHeight = window.innerHeight;
|
|
220
|
+
|
|
221
|
+
const boxWidth = Math.min(maxRight, viewportWidth) - Math.max(0, minLeft);
|
|
222
|
+
const boxHeight =
|
|
223
|
+
Math.min(maxBottom, viewportHeight) - Math.max(0, minTop);
|
|
224
|
+
|
|
225
|
+
const widthPercentage = boxWidth / viewportWidth;
|
|
226
|
+
const heightPercentage = boxHeight / viewportHeight;
|
|
227
|
+
|
|
228
|
+
if (widthPercentage >= 0.8 || heightPercentage >= 0.8) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const isSafari = /^((?!chrome|android).)*safari/i.test(
|
|
233
|
+
navigator.userAgent
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
let topLeft: [number, number] = [
|
|
237
|
+
Math.max(0, minLeft) / viewportWidth,
|
|
238
|
+
Math.max(0, minTop) / viewportHeight
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
let bottomRight: [number, number] = [
|
|
242
|
+
Math.min(maxRight, viewportWidth) / viewportWidth,
|
|
243
|
+
Math.min(maxBottom, viewportHeight) / viewportHeight
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
if (isSafari) {
|
|
247
|
+
topLeft[0] = Math.max(0, topLeft[0] * 0.9);
|
|
248
|
+
bottomRight[0] = Math.min(1, bottomRight[0] * 0.9);
|
|
249
|
+
const width = bottomRight[0] - topLeft[0];
|
|
250
|
+
const center = (topLeft[0] + bottomRight[0]) / 2;
|
|
251
|
+
const newCenter = center * 0.9;
|
|
252
|
+
topLeft[0] = Math.max(0, newCenter - width / 2);
|
|
253
|
+
bottomRight[0] = Math.min(1, newCenter + width / 2);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
topLeft[0] = Math.max(0, topLeft[0]);
|
|
257
|
+
topLeft[1] = Math.max(0, topLeft[1]);
|
|
258
|
+
bottomRight[0] = Math.min(1, bottomRight[0]);
|
|
259
|
+
bottomRight[1] = Math.min(1, bottomRight[1]);
|
|
260
|
+
|
|
261
|
+
addZoomEffect(is_input, {
|
|
262
|
+
boundingBox: {
|
|
263
|
+
topLeft,
|
|
264
|
+
bottomRight
|
|
265
|
+
},
|
|
266
|
+
duration: duration
|
|
267
|
+
});
|
|
268
|
+
}, 300);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
// pass
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function handleDataAvailable(event: BlobEvent): void {
|
|
275
|
+
if (event.data.size > 0) {
|
|
276
|
+
recordedChunks.push(event.data);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function handleStop(): void {
|
|
281
|
+
isRecording = false;
|
|
282
|
+
if (onRecordingStateChange) {
|
|
283
|
+
onRecordingStateChange(false);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const blob = new Blob(recordedChunks, {
|
|
287
|
+
type: "video/mp4"
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
handleRecordingComplete(blob);
|
|
291
|
+
|
|
292
|
+
const screenStream = mediaRecorder?.stream?.getTracks() || [];
|
|
293
|
+
screenStream.forEach((track) => track.stop());
|
|
294
|
+
|
|
295
|
+
if (animationFrameId !== null) {
|
|
296
|
+
cancelAnimationFrame(animationFrameId);
|
|
297
|
+
animationFrameId = null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function handleRecordingComplete(recordedBlob: Blob): Promise<void> {
|
|
302
|
+
try {
|
|
303
|
+
add_message_callback(
|
|
304
|
+
"Processing video",
|
|
305
|
+
"This may take a few seconds...",
|
|
306
|
+
"info"
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const formData = new FormData();
|
|
310
|
+
formData.append("video", recordedBlob, "recording.mp4");
|
|
311
|
+
|
|
312
|
+
if (removeSegment.start !== undefined && removeSegment.end !== undefined) {
|
|
313
|
+
formData.append("remove_segment_start", removeSegment.start.toString());
|
|
314
|
+
formData.append("remove_segment_end", removeSegment.end.toString());
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (zoomEffects.length > 0) {
|
|
318
|
+
formData.append("zoom_effects", JSON.stringify(zoomEffects));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const response = await fetch(root + "/gradio_api/process_recording", {
|
|
322
|
+
method: "POST",
|
|
323
|
+
body: formData
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`Server returned ${response.status}: ${response.statusText}`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const processedBlob = await response.blob();
|
|
333
|
+
const defaultFilename = `gradio-screen-recording-${new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "")}.mp4`;
|
|
334
|
+
saveWithDownloadAttribute(processedBlob, defaultFilename);
|
|
335
|
+
zoomEffects = [];
|
|
336
|
+
} catch (error) {
|
|
337
|
+
add_message_callback(
|
|
338
|
+
"Processing Error",
|
|
339
|
+
"Failed to process recording. Saving original version.",
|
|
340
|
+
"warning"
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
const defaultFilename = `gradio-screen-recording-${new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "")}.mp4`;
|
|
344
|
+
saveWithDownloadAttribute(recordedBlob, defaultFilename);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function saveWithDownloadAttribute(blob: Blob, suggestedName: string): void {
|
|
349
|
+
const url = URL.createObjectURL(blob);
|
|
350
|
+
const a = document.createElement("a");
|
|
351
|
+
a.style.display = "none";
|
|
352
|
+
a.href = url;
|
|
353
|
+
a.download = suggestedName;
|
|
354
|
+
|
|
355
|
+
document.body.appendChild(a);
|
|
356
|
+
a.click();
|
|
357
|
+
setTimeout(() => {
|
|
358
|
+
document.body.removeChild(a);
|
|
359
|
+
URL.revokeObjectURL(url);
|
|
360
|
+
}, 100);
|
|
361
|
+
}
|