akhaliq HF Staff commited on
Commit
d82c64a
·
1 Parent(s): 7db57ff

Back to @gradio/client: ZeroGPU requires its HF iframe auth headers

Browse files

The Server-mode docs are explicit that plain fetch/EventSource calls do
not forward the iframe auth headers ZeroGPU quota handling needs, so
bookings landed on the pod IP's shared quota. The earlier fn_index crash
was a stale tab from the pre-Server build. Keeps the tuple unwrap and
FileData path fallback.

Files changed (1) hide show
  1. index.html +16 -39
index.html CHANGED
@@ -243,6 +243,10 @@
243
  <input type="file" id="file-last" accept="image/*" hidden>
244
 
245
  <script type="module">
 
 
 
 
246
  const $ = (id) => document.getElementById(id);
247
  const state = { first: null, last: null, busy: false, timer: null };
248
 
@@ -316,29 +320,8 @@ for (const [p, c] of EXAMPLES) {
316
  $("examples").appendChild(b);
317
  }
318
 
319
- /* ---- generate: Gradio's v2 call API directly (POST named payload -> SSE stream).
320
- No @gradio/client: its fn_index resolution does not apply to Server mode. ---- */
321
- async function uploadFile(file) {
322
- const fd = new FormData();
323
- fd.append("files", file);
324
- const r = await fetch("/gradio_api/upload", { method: "POST", body: fd });
325
- if (!r.ok) throw new Error(`upload failed (${r.status})`);
326
- const paths = await r.json();
327
- return { path: paths[0], orig_name: file.name, meta: { _type: "gradio.FileData" } };
328
- }
329
-
330
- function awaitResult(eventId) {
331
- return new Promise((resolve, reject) => {
332
- const es = new EventSource(`/gradio_api/call/generate/${eventId}`);
333
- es.addEventListener("complete", (e) => { es.close(); resolve(JSON.parse(e.data)); });
334
- es.addEventListener("error", (e) => {
335
- es.close();
336
- let msg = "generation failed";
337
- try { const d = JSON.parse(e.data); msg = d.message || d.title || msg; } catch (_) { if (e.data) msg = e.data; }
338
- reject(new Error(msg));
339
- });
340
- });
341
- }
342
 
343
  function setJob(label, cls) {
344
  const el = $("job-state");
@@ -359,23 +342,17 @@ $("run").addEventListener("click", async () => {
359
  state.timer = setInterval(() => $("elapsed").textContent = ((Date.now() - t0) / 1000).toFixed(0) + "s", 500);
360
 
361
  try {
362
- const call = await fetch("/gradio_api/call/v2/generate", {
363
- method: "POST",
364
- headers: { "Content-Type": "application/json" },
365
- body: JSON.stringify({
366
- prompt,
367
- image_path: state.first ? await uploadFile(state.first) : null,
368
- last_image_path: state.last ? await uploadFile(state.last) : null,
369
- canvas: $("canvas").value,
370
- duration: Number($("duration").value),
371
- steps: Number($("steps").value),
372
- seed: Number($("seed").value),
373
- upsample: $("upsample").checked,
374
- }),
375
  });
376
- if (!call.ok) throw new Error(`queue join failed (${call.status})`);
377
- const { event_id } = await call.json();
378
- let data = await awaitResult(event_id);
379
  // Server mode returns the tuple as ONE Api output: unwrap [[video, report, refined]] too.
380
  if (data.length === 1 && Array.isArray(data[0])) data = data[0];
381
  const [video, report, refined] = data;
 
243
  <input type="file" id="file-last" accept="image/*" hidden>
244
 
245
  <script type="module">
246
+ // @gradio/client is REQUIRED here (not plain fetch): it forwards the HF iframe auth headers that ZeroGPU
247
+ // quota handling depends on — without them every booking lands on the pod IP's shared quota.
248
+ import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
249
+
250
  const $ = (id) => document.getElementById(id);
251
  const state = { first: null, last: null, busy: false, timer: null };
252
 
 
320
  $("examples").appendChild(b);
321
  }
322
 
323
+ /* ---- generate ---- */
324
+ const client = await Client.connect(window.location.origin);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
 
326
  function setJob(label, cls) {
327
  const el = $("job-state");
 
342
  state.timer = setInterval(() => $("elapsed").textContent = ((Date.now() - t0) / 1000).toFixed(0) + "s", 500);
343
 
344
  try {
345
+ const result = await client.predict("/generate", {
346
+ prompt,
347
+ image_path: state.first ? handle_file(state.first) : null,
348
+ last_image_path: state.last ? handle_file(state.last) : null,
349
+ canvas: $("canvas").value,
350
+ duration: Number($("duration").value),
351
+ steps: Number($("steps").value),
352
+ seed: Number($("seed").value),
353
+ upsample: $("upsample").checked,
 
 
 
 
354
  });
355
+ let data = result.data;
 
 
356
  // Server mode returns the tuple as ONE Api output: unwrap [[video, report, refined]] too.
357
  if (data.length === 1 && Array.isArray(data[0])) data = data[0];
358
  const [video, report, refined] = data;