@gradio/client 2.3.1 → 2.5.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 +21 -0
- package/README.md +4 -2
- package/dist/__vite-browser-external-Cgmn0awE.js +7 -0
- package/dist/browser.js +1949 -1963
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/helpers/api_info.d.ts.map +1 -1
- package/dist/helpers/data.d.ts.map +1 -1
- package/dist/helpers/init_helpers.d.ts +11 -2
- package/dist/helpers/init_helpers.d.ts.map +1 -1
- package/dist/index.cjs +2584 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2471 -2466
- package/dist/index.min.js +1962 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/duplicate.d.ts.map +1 -1
- package/dist/utils/predict.d.ts.map +1 -1
- package/dist/utils/run_history.d.ts +64 -0
- package/dist/utils/run_history.d.ts.map +1 -0
- package/dist/utils/stream.d.ts.map +1 -1
- package/dist/utils/submit.d.ts.map +1 -1
- package/package.json +11 -4
- package/scripts/verify_dist.mjs +46 -0
- package/src/client.ts +54 -5
- package/src/constants.ts +10 -0
- package/src/helpers/api_info.ts +46 -19
- package/src/helpers/data.ts +8 -3
- package/src/helpers/init_helpers.ts +54 -7
- package/src/index.ts +12 -0
- package/src/test/api_info.test.ts +77 -12
- package/src/test/data.test.ts +26 -3
- package/src/test/init.test.ts +21 -3
- package/src/test/init_helpers.test.ts +67 -1
- package/src/test/refresh.test.ts +31 -0
- package/src/test/run_history.test.ts +345 -0
- package/src/test/submit.test.ts +1 -3
- package/src/types.ts +29 -0
- package/src/utils/duplicate.ts +2 -0
- package/src/utils/predict.ts +14 -1
- package/src/utils/run_history.ts +520 -0
- package/src/utils/stream.ts +17 -4
- package/src/utils/submit.ts +105 -12
- package/vite.config.ts +20 -4
- package/dist/__vite-browser-external-DYxpcVy9.js +0 -4
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
import type { GradioEvent, StatusMessage } from "../types";
|
|
2
|
+
|
|
3
|
+
// Keyed on the app id, so two different apps served on the same port never
|
|
4
|
+
// share a history. The id is regenerated whenever an app starts, so a restart
|
|
5
|
+
// begins a fresh history and leaves the previous one orphaned; `prune_apps`
|
|
6
|
+
// keeps those from accumulating.
|
|
7
|
+
//
|
|
8
|
+
// The key also carries the logged-in user, because local storage outlives a
|
|
9
|
+
// session: without it, whoever logs into an authenticated app next on this
|
|
10
|
+
// browser would open the previous user's runs.
|
|
11
|
+
const KEY_ROOT = "gradio:run-history:";
|
|
12
|
+
const STORAGE_PREFIX = `${KEY_ROOT}v2:`;
|
|
13
|
+
const REPLAY_PREFIX = `${KEY_ROOT}replay:v2:`;
|
|
14
|
+
const MAX_RUNS = 100;
|
|
15
|
+
const MAX_APPS = 8;
|
|
16
|
+
|
|
17
|
+
export type AppId = string | number | null | undefined;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Which history to read or write. `Config` satisfies this shape, so callers
|
|
21
|
+
* that hold one can pass it straight through.
|
|
22
|
+
*/
|
|
23
|
+
export interface RunHistoryScope {
|
|
24
|
+
app_id?: AppId;
|
|
25
|
+
/** The authenticated user, when the app uses `auth`. */
|
|
26
|
+
username?: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Run history is a side effect of submitting, never the point of it, so no
|
|
31
|
+
* failure in here may propagate into the caller and break the app. Every
|
|
32
|
+
* exported function routes through this.
|
|
33
|
+
*/
|
|
34
|
+
function safely<T>(operation: () => T, fallback: T): T {
|
|
35
|
+
try {
|
|
36
|
+
return operation();
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.warn("Could not update the run history.", error);
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type RunStatus = "running" | "completed" | "failed";
|
|
44
|
+
|
|
45
|
+
export interface StoredRunComponent {
|
|
46
|
+
type: string;
|
|
47
|
+
component_class_id: string;
|
|
48
|
+
props: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface StoredRun {
|
|
52
|
+
id: string;
|
|
53
|
+
endpoint: string | number;
|
|
54
|
+
api_name: string;
|
|
55
|
+
fn_index: number;
|
|
56
|
+
page: string;
|
|
57
|
+
inputs: unknown;
|
|
58
|
+
outputs: unknown | null;
|
|
59
|
+
input_components?: (StoredRunComponent | null)[];
|
|
60
|
+
output_components?: (StoredRunComponent | null)[];
|
|
61
|
+
status: RunStatus;
|
|
62
|
+
error?: string;
|
|
63
|
+
started_at: string;
|
|
64
|
+
/** When the server started running the function, if it reported queueing. */
|
|
65
|
+
process_started_at?: string;
|
|
66
|
+
completed_at?: string;
|
|
67
|
+
/** How long the function itself took, server-reported where available. */
|
|
68
|
+
duration_ms?: number;
|
|
69
|
+
/** How long the run waited in the queue before the function started. */
|
|
70
|
+
queued_ms?: number;
|
|
71
|
+
/** Whether the run produced its output in chunks, i.e. came from a generator. */
|
|
72
|
+
streamed?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface StartRunOptions extends RunHistoryScope {
|
|
76
|
+
endpoint: string | number;
|
|
77
|
+
api_name: string;
|
|
78
|
+
fn_index: number;
|
|
79
|
+
inputs: unknown;
|
|
80
|
+
input_components?: (StoredRunComponent | null)[];
|
|
81
|
+
output_components?: (StoredRunComponent | null)[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The URL of the run history page for an app. `root` never carries a trailing
|
|
86
|
+
* slash, so it cannot simply be concatenated with the path.
|
|
87
|
+
*/
|
|
88
|
+
export function run_history_url(
|
|
89
|
+
root: string,
|
|
90
|
+
api_prefix = "/gradio_api"
|
|
91
|
+
): string {
|
|
92
|
+
return `${root.replace(/\/+$/, "")}${api_prefix}/runs`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function storage_key(scope: RunHistoryScope | null | undefined): string | null {
|
|
96
|
+
if (typeof window === "undefined") return null;
|
|
97
|
+
const app_id = scope?.app_id;
|
|
98
|
+
if (app_id === null || app_id === undefined || app_id === "") return null;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
if (!window.localStorage) return null;
|
|
102
|
+
// Encoded so that a username containing the separator cannot be made to
|
|
103
|
+
// collide with another user's key.
|
|
104
|
+
const user = scope?.username
|
|
105
|
+
? `:user:${encodeURIComponent(scope.username)}`
|
|
106
|
+
: "";
|
|
107
|
+
return `${STORAGE_PREFIX}${app_id}${user}`;
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function replay_key(scope: RunHistoryScope | null | undefined): string | null {
|
|
114
|
+
const key = storage_key(scope);
|
|
115
|
+
return key ? key.replace(STORAGE_PREFIX, REPLAY_PREFIX) : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** When a run was most recently saved under a key, for deciding what to drop. */
|
|
119
|
+
function last_saved_at(key: string): number {
|
|
120
|
+
try {
|
|
121
|
+
const runs = JSON.parse(window.localStorage.getItem(key) || "[]");
|
|
122
|
+
// Runs are stored newest first.
|
|
123
|
+
return Array.isArray(runs) && runs.length
|
|
124
|
+
? Date.parse(runs[0]?.started_at) || 0
|
|
125
|
+
: 0;
|
|
126
|
+
} catch {
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Drops the histories of long-gone app instances. Every restart mints a new app
|
|
133
|
+
* id, so without this the browser would keep every history an app ever had and
|
|
134
|
+
* eventually run out of room for the current one.
|
|
135
|
+
*/
|
|
136
|
+
function prune_apps(current_key: string): void {
|
|
137
|
+
const keys = Object.keys(window.localStorage).filter((key) =>
|
|
138
|
+
key.startsWith(KEY_ROOT)
|
|
139
|
+
);
|
|
140
|
+
const stale = [
|
|
141
|
+
// Keys written by an older layout can never be read again.
|
|
142
|
+
...keys.filter(
|
|
143
|
+
(key) => !key.startsWith(STORAGE_PREFIX) && !key.startsWith(REPLAY_PREFIX)
|
|
144
|
+
),
|
|
145
|
+
...keys
|
|
146
|
+
.filter((key) => key.startsWith(STORAGE_PREFIX) && key !== current_key)
|
|
147
|
+
.sort((a, b) => last_saved_at(b) - last_saved_at(a))
|
|
148
|
+
.slice(MAX_APPS - 1)
|
|
149
|
+
];
|
|
150
|
+
for (const key of stale) {
|
|
151
|
+
try {
|
|
152
|
+
window.localStorage.removeItem(key);
|
|
153
|
+
window.sessionStorage.removeItem(
|
|
154
|
+
key.replace(STORAGE_PREFIX, REPLAY_PREFIX)
|
|
155
|
+
);
|
|
156
|
+
} catch {
|
|
157
|
+
// Nothing to do if the browser will not let us clean up.
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function make_id(): string {
|
|
163
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
164
|
+
return crypto.randomUUID();
|
|
165
|
+
}
|
|
166
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function clone_for_storage(value: unknown): unknown {
|
|
170
|
+
const seen = new WeakSet<object>();
|
|
171
|
+
try {
|
|
172
|
+
return JSON.parse(
|
|
173
|
+
JSON.stringify(value, (_key, item) => {
|
|
174
|
+
if (typeof item === "bigint") return item.toString();
|
|
175
|
+
if (typeof item === "object" && item !== null) {
|
|
176
|
+
if (seen.has(item)) return "[Circular]";
|
|
177
|
+
seen.add(item);
|
|
178
|
+
}
|
|
179
|
+
return item;
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
} catch {
|
|
183
|
+
return "[Unserializable value]";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function read_run_history_impl(
|
|
188
|
+
scope: RunHistoryScope | null | undefined
|
|
189
|
+
): StoredRun[] {
|
|
190
|
+
const key = storage_key(scope);
|
|
191
|
+
if (!key) return [];
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const value = JSON.parse(window.localStorage.getItem(key) || "[]");
|
|
195
|
+
return Array.isArray(value) ? value : [];
|
|
196
|
+
} catch {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function write_run_history(
|
|
202
|
+
scope: RunHistoryScope | null | undefined,
|
|
203
|
+
runs: StoredRun[]
|
|
204
|
+
): void {
|
|
205
|
+
const key = storage_key(scope);
|
|
206
|
+
if (!key) return;
|
|
207
|
+
|
|
208
|
+
let next = runs.slice(0, MAX_RUNS);
|
|
209
|
+
let pruned = false;
|
|
210
|
+
while (next.length > 0) {
|
|
211
|
+
try {
|
|
212
|
+
window.localStorage.setItem(key, JSON.stringify(next));
|
|
213
|
+
return;
|
|
214
|
+
} catch {
|
|
215
|
+
// Reclaim the space held by app instances that are long gone before
|
|
216
|
+
// giving up on the oldest runs of the current one.
|
|
217
|
+
if (!pruned) {
|
|
218
|
+
pruned = true;
|
|
219
|
+
prune_apps(key);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
next = next.slice(0, -1);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
window.localStorage.removeItem(key);
|
|
227
|
+
} catch {
|
|
228
|
+
// Run history must never interfere with an app submission.
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function clear_run_history_impl(
|
|
233
|
+
scope: RunHistoryScope | null | undefined
|
|
234
|
+
): void {
|
|
235
|
+
const key = storage_key(scope);
|
|
236
|
+
if (!key) return;
|
|
237
|
+
try {
|
|
238
|
+
window.localStorage.removeItem(key);
|
|
239
|
+
} catch {
|
|
240
|
+
// Storage may be disabled by the browser.
|
|
241
|
+
}
|
|
242
|
+
notify_run_history_change();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function delete_run_history_impl(
|
|
246
|
+
scope: RunHistoryScope | null | undefined,
|
|
247
|
+
id: string
|
|
248
|
+
): void {
|
|
249
|
+
write_run_history(
|
|
250
|
+
scope,
|
|
251
|
+
read_run_history_impl(scope).filter((run) => run.id !== id)
|
|
252
|
+
);
|
|
253
|
+
notify_run_history_change();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const CHANGE_EVENT = "gradio:run-history-change";
|
|
257
|
+
|
|
258
|
+
function notify_run_history_change(): void {
|
|
259
|
+
if (typeof window === "undefined") return;
|
|
260
|
+
try {
|
|
261
|
+
window.dispatchEvent(new Event(CHANGE_EVENT));
|
|
262
|
+
} catch {
|
|
263
|
+
// Nothing depends on the notification arriving.
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Subscribes to runs being added, deleted or cleared. `storage` covers other
|
|
269
|
+
* tabs; the custom event covers this one, which `storage` never fires for.
|
|
270
|
+
* Only counts change, not per-run progress, so listeners stay cheap.
|
|
271
|
+
*
|
|
272
|
+
* @returns a function that unsubscribes.
|
|
273
|
+
*/
|
|
274
|
+
function on_run_history_change_impl(listener: () => void): () => void {
|
|
275
|
+
if (typeof window === "undefined") return () => {};
|
|
276
|
+
window.addEventListener(CHANGE_EVENT, listener);
|
|
277
|
+
window.addEventListener("storage", listener);
|
|
278
|
+
return () => {
|
|
279
|
+
window.removeEventListener(CHANGE_EVENT, listener);
|
|
280
|
+
window.removeEventListener("storage", listener);
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function stage_run_history_replay_impl(
|
|
285
|
+
scope: RunHistoryScope | null | undefined,
|
|
286
|
+
run: StoredRun
|
|
287
|
+
): void {
|
|
288
|
+
const key = replay_key(scope);
|
|
289
|
+
if (!key) return;
|
|
290
|
+
try {
|
|
291
|
+
window.sessionStorage.setItem(key, JSON.stringify(run));
|
|
292
|
+
} catch {
|
|
293
|
+
// Session storage may be disabled by the browser.
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function consume_run_history_replay_impl(
|
|
298
|
+
scope: RunHistoryScope | null | undefined
|
|
299
|
+
): StoredRun | null {
|
|
300
|
+
const key = replay_key(scope);
|
|
301
|
+
if (!key) return null;
|
|
302
|
+
try {
|
|
303
|
+
const value = window.sessionStorage.getItem(key);
|
|
304
|
+
window.sessionStorage.removeItem(key);
|
|
305
|
+
return value ? (JSON.parse(value) as StoredRun) : null;
|
|
306
|
+
} catch {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function start_run_history_impl(options: StartRunOptions): string | null {
|
|
312
|
+
const key = storage_key(options);
|
|
313
|
+
if (!key) return null;
|
|
314
|
+
|
|
315
|
+
// A new app id means a new key, so clear out the ones left behind before
|
|
316
|
+
// adding to this one.
|
|
317
|
+
prune_apps(key);
|
|
318
|
+
|
|
319
|
+
const run: StoredRun = {
|
|
320
|
+
id: make_id(),
|
|
321
|
+
endpoint: options.endpoint,
|
|
322
|
+
api_name: options.api_name,
|
|
323
|
+
fn_index: options.fn_index,
|
|
324
|
+
page: `${window.location.pathname}${window.location.search}`,
|
|
325
|
+
inputs: clone_for_storage(options.inputs),
|
|
326
|
+
outputs: null,
|
|
327
|
+
...(options.input_components
|
|
328
|
+
? {
|
|
329
|
+
input_components: clone_for_storage(
|
|
330
|
+
options.input_components
|
|
331
|
+
) as (StoredRunComponent | null)[]
|
|
332
|
+
}
|
|
333
|
+
: {}),
|
|
334
|
+
...(options.output_components
|
|
335
|
+
? {
|
|
336
|
+
output_components: clone_for_storage(
|
|
337
|
+
options.output_components
|
|
338
|
+
) as (StoredRunComponent | null)[]
|
|
339
|
+
}
|
|
340
|
+
: {}),
|
|
341
|
+
status: "running",
|
|
342
|
+
started_at: new Date().toISOString()
|
|
343
|
+
};
|
|
344
|
+
write_run_history(options, [run, ...read_run_history_impl(options)]);
|
|
345
|
+
notify_run_history_change();
|
|
346
|
+
return run.id;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function update_run_inputs_impl(
|
|
350
|
+
scope: RunHistoryScope | null | undefined,
|
|
351
|
+
id: string | null,
|
|
352
|
+
inputs: unknown
|
|
353
|
+
): void {
|
|
354
|
+
if (!id) return;
|
|
355
|
+
|
|
356
|
+
const runs = read_run_history_impl(scope);
|
|
357
|
+
const run = runs.find((item) => item.id === id);
|
|
358
|
+
if (!run) return;
|
|
359
|
+
run.inputs = clone_for_storage(inputs);
|
|
360
|
+
write_run_history(scope, runs);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function update_run_history_impl(
|
|
364
|
+
scope: RunHistoryScope | null | undefined,
|
|
365
|
+
id: string | null,
|
|
366
|
+
event: GradioEvent
|
|
367
|
+
): void {
|
|
368
|
+
if (!id) return;
|
|
369
|
+
|
|
370
|
+
const runs = read_run_history_impl(scope);
|
|
371
|
+
const run = runs.find((item) => item.id === id);
|
|
372
|
+
if (!run) return;
|
|
373
|
+
|
|
374
|
+
let finished = false;
|
|
375
|
+
if (event.type === "data") {
|
|
376
|
+
run.outputs = clone_for_storage(event.data);
|
|
377
|
+
deferred_outputs.set(id, run.outputs);
|
|
378
|
+
} else if (
|
|
379
|
+
event.type === "status" &&
|
|
380
|
+
event.original_msg === "process_starts"
|
|
381
|
+
) {
|
|
382
|
+
mark_process_start(run, event.time);
|
|
383
|
+
} else if (
|
|
384
|
+
event.type === "status" &&
|
|
385
|
+
(event.stage === "generating" || event.stage === "streaming")
|
|
386
|
+
) {
|
|
387
|
+
run.streamed = true;
|
|
388
|
+
} else if (event.type === "status" && event.stage === "complete") {
|
|
389
|
+
run.status = "completed";
|
|
390
|
+
mark_complete(run, event);
|
|
391
|
+
finished = true;
|
|
392
|
+
} else if (event.type === "status" && event.stage === "error") {
|
|
393
|
+
run.status = "failed";
|
|
394
|
+
run.error =
|
|
395
|
+
typeof event.message === "string"
|
|
396
|
+
? event.message
|
|
397
|
+
: JSON.stringify(event.message || "Unknown error");
|
|
398
|
+
mark_complete(run, event);
|
|
399
|
+
finished = true;
|
|
400
|
+
} else {
|
|
401
|
+
// Logs, renders and progress updates change nothing worth saving, and
|
|
402
|
+
// writing serialises every run we hold.
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (finished) {
|
|
407
|
+
// Every call re-reads from storage, so a chunk that was held back above is
|
|
408
|
+
// missing from `run`. Put the last one back before this final write.
|
|
409
|
+
if (deferred_outputs.has(id)) run.outputs = deferred_outputs.get(id);
|
|
410
|
+
deferred_outputs.delete(id);
|
|
411
|
+
last_write.delete(id);
|
|
412
|
+
} else if (run.streamed) {
|
|
413
|
+
// A generator emits an event per chunk. Saving each one would stringify
|
|
414
|
+
// the whole history on the main thread over and over, so write at most
|
|
415
|
+
// once an interval and let the run's completion flush the rest.
|
|
416
|
+
const since = Date.now() - (last_write.get(id) ?? 0);
|
|
417
|
+
if (since < STREAM_WRITE_INTERVAL_MS) return;
|
|
418
|
+
last_write.set(id, Date.now());
|
|
419
|
+
deferred_outputs.delete(id);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
write_run_history(scope, runs);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const STREAM_WRITE_INTERVAL_MS = 500;
|
|
426
|
+
/** When each in-flight streamed run was last written. */
|
|
427
|
+
const last_write = new Map<string, number>();
|
|
428
|
+
/** The newest output of a streamed run that has not been written yet. */
|
|
429
|
+
const deferred_outputs = new Map<string, unknown>();
|
|
430
|
+
|
|
431
|
+
function mark_process_start(run: StoredRun, time: Date | undefined): void {
|
|
432
|
+
// The queue tells us when the function actually started, which lets us
|
|
433
|
+
// report a runtime that excludes however long the run sat in the queue.
|
|
434
|
+
if (run.process_started_at) return;
|
|
435
|
+
const started = time || new Date();
|
|
436
|
+
run.process_started_at = started.toISOString();
|
|
437
|
+
run.queued_ms = Math.max(0, started.getTime() - Date.parse(run.started_at));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function mark_complete(run: StoredRun, event: StatusMessage): void {
|
|
441
|
+
const completed = event.time || new Date();
|
|
442
|
+
run.completed_at = completed.toISOString();
|
|
443
|
+
// `cache_duration` is how long the function took on the server, which the
|
|
444
|
+
// queue reports on every completed run (not just cached ones). A generator
|
|
445
|
+
// reports it per chunk though, so the final value covers only the last one
|
|
446
|
+
// and the elapsed time is the honest number for a streamed run.
|
|
447
|
+
const server_duration =
|
|
448
|
+
!run.streamed &&
|
|
449
|
+
typeof event.cache_duration === "number" &&
|
|
450
|
+
event.cache_duration >= 0
|
|
451
|
+
? event.cache_duration * 1000
|
|
452
|
+
: null;
|
|
453
|
+
run.duration_ms =
|
|
454
|
+
server_duration ??
|
|
455
|
+
Math.max(
|
|
456
|
+
0,
|
|
457
|
+
completed.getTime() - Date.parse(run.process_started_at || run.started_at)
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Public API. Each of these is a no-op if anything goes wrong.
|
|
462
|
+
|
|
463
|
+
export function read_run_history(
|
|
464
|
+
scope: RunHistoryScope | null | undefined
|
|
465
|
+
): StoredRun[] {
|
|
466
|
+
return safely(() => read_run_history_impl(scope), []);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function clear_run_history(
|
|
470
|
+
scope: RunHistoryScope | null | undefined
|
|
471
|
+
): void {
|
|
472
|
+
safely(() => clear_run_history_impl(scope), undefined);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function delete_run_history(
|
|
476
|
+
scope: RunHistoryScope | null | undefined,
|
|
477
|
+
id: string
|
|
478
|
+
): void {
|
|
479
|
+
safely(() => delete_run_history_impl(scope, id), undefined);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export function stage_run_history_replay(
|
|
483
|
+
scope: RunHistoryScope | null | undefined,
|
|
484
|
+
run: StoredRun
|
|
485
|
+
): void {
|
|
486
|
+
safely(() => stage_run_history_replay_impl(scope, run), undefined);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export function consume_run_history_replay(
|
|
490
|
+
scope: RunHistoryScope | null | undefined
|
|
491
|
+
): StoredRun | null {
|
|
492
|
+
return safely(() => consume_run_history_replay_impl(scope), null);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export function start_run_history(options: StartRunOptions): string | null {
|
|
496
|
+
return safely(() => start_run_history_impl(options), null);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function update_run_inputs(
|
|
500
|
+
scope: RunHistoryScope | null | undefined,
|
|
501
|
+
id: string | null,
|
|
502
|
+
inputs: unknown
|
|
503
|
+
): void {
|
|
504
|
+
safely(() => update_run_inputs_impl(scope, id, inputs), undefined);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export function update_run_history(
|
|
508
|
+
scope: RunHistoryScope | null | undefined,
|
|
509
|
+
id: string | null,
|
|
510
|
+
event: GradioEvent
|
|
511
|
+
): void {
|
|
512
|
+
safely(() => update_run_history_impl(scope, id, event), undefined);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function on_run_history_change(listener: () => void): () => void {
|
|
516
|
+
return safely(
|
|
517
|
+
() => on_run_history_change_impl(() => safely(listener, undefined)),
|
|
518
|
+
() => {}
|
|
519
|
+
);
|
|
520
|
+
}
|
package/src/utils/stream.ts
CHANGED
|
@@ -62,9 +62,15 @@ export async function open_stream(this: Client): Promise<void> {
|
|
|
62
62
|
}
|
|
63
63
|
let fn: (data: any) => void = event_callbacks[event_id];
|
|
64
64
|
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
if (
|
|
66
|
+
typeof window !== "undefined" &&
|
|
67
|
+
typeof document !== "undefined" &&
|
|
68
|
+
document.visibilityState !== "hidden"
|
|
69
|
+
) {
|
|
70
|
+
// Put the event at the end of the event loop so the browser can refresh
|
|
71
|
+
// between callbacks and not freeze during quick generations. Hidden tabs
|
|
72
|
+
// throttle timers, so process those messages immediately instead.
|
|
73
|
+
setTimeout(fn, 0, _data); // See https://github.com/gradio-app/gradio/pull/7055
|
|
68
74
|
} else {
|
|
69
75
|
fn(_data);
|
|
70
76
|
}
|
|
@@ -111,7 +117,14 @@ export function apply_diff_stream(
|
|
|
111
117
|
});
|
|
112
118
|
} else {
|
|
113
119
|
data.data.forEach((value: any, i: number) => {
|
|
114
|
-
|
|
120
|
+
// A new output can appear mid-stream if the app was hot-reloaded
|
|
121
|
+
// while a generator was running; such outputs are diffed against
|
|
122
|
+
// null on the server, so start from null when we haven't seen it.
|
|
123
|
+
const prev =
|
|
124
|
+
i < pending_diff_streams[event_id].length
|
|
125
|
+
? pending_diff_streams[event_id][i]
|
|
126
|
+
: null;
|
|
127
|
+
let new_data = apply_diff(prev, value);
|
|
115
128
|
pending_diff_streams[event_id][i] = new_data;
|
|
116
129
|
data.data[i] = new_data;
|
|
117
130
|
});
|