Spaces:
Running on Zero
Running on Zero
Back to @gradio/client: ZeroGPU requires its HF iframe auth headers
Browse filesThe 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.
- 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
|
| 320 |
-
|
| 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
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
steps: Number($("steps").value),
|
| 372 |
-
seed: Number($("seed").value),
|
| 373 |
-
upsample: $("upsample").checked,
|
| 374 |
-
}),
|
| 375 |
});
|
| 376 |
-
|
| 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;
|