akhaliq HF Staff commited on
Commit
e6d78bd
·
1 Parent(s): 97f8752

Call the v2 named-endpoint API directly, unwrap the single-Api-output tuple

Browse files

- @gradio/client resolves a legacy fn_index that Server mode's internal
Blocks does not have (KeyError on queue join); the v2 API
(POST /gradio_api/call/v2/generate + SSE) addresses endpoints by name
- the tuple return is one 'Api' output, so it arrives nested one level
- whitelist OUTPUT_DIR for the /gradio_api/file= route

Files changed (2) hide show
  1. app.py +6 -4
  2. index.html +45 -16
app.py CHANGED
@@ -71,6 +71,8 @@ def lower_duration_floor(seconds: float = MIN_UI_DURATION) -> None:
71
  MiniMaxH3ModularPipeline.min_duration = property(lambda self: float(seconds))
72
 
73
 
 
 
74
  PIPE = None
75
  MANAGER = None
76
  LOAD_ERROR: str | None = None
@@ -335,9 +337,8 @@ def generate(prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVA
335
  )
336
  generate_seconds = time.time() - started
337
 
338
- directory = os.path.join(tempfile.gettempdir(), "h3-outputs")
339
- os.makedirs(directory, exist_ok=True)
340
- path = os.path.join(directory, f"h3-{int(time.time() * 1000)}.mp4")
341
  encode_video(frames, fps=FPS, output_path=path, audio=audio, audio_sample_rate=sampling_rate)
342
 
343
  report = (
@@ -393,4 +394,5 @@ def homepage():
393
  load_models()
394
 
395
  if __name__ == "__main__":
396
- app.launch(show_error=True)
 
 
71
  MiniMaxH3ModularPipeline.min_duration = property(lambda self: float(seconds))
72
 
73
 
74
+ OUTPUT_DIR = os.path.join(tempfile.gettempdir(), "h3-outputs")
75
+
76
  PIPE = None
77
  MANAGER = None
78
  LOAD_ERROR: str | None = None
 
337
  )
338
  generate_seconds = time.time() - started
339
 
340
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
341
+ path = os.path.join(OUTPUT_DIR, f"h3-{int(time.time() * 1000)}.mp4")
 
342
  encode_video(frames, fps=FPS, output_path=path, audio=audio, audio_sample_rate=sampling_rate)
343
 
344
  report = (
 
394
  load_models()
395
 
396
  if __name__ == "__main__":
397
+ # allowed_paths: the /gradio_api/file= route only serves whitelisted directories.
398
+ app.launch(show_error=True, allowed_paths=[OUTPUT_DIR])
index.html CHANGED
@@ -243,8 +243,6 @@
243
  <input type="file" id="file-last" accept="image/*" hidden>
244
 
245
  <script type="module">
246
- import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
247
-
248
  const $ = (id) => document.getElementById(id);
249
  const state = { first: null, last: null, busy: false, timer: null };
250
 
@@ -318,8 +316,29 @@ for (const [p, c] of EXAMPLES) {
318
  $("examples").appendChild(b);
319
  }
320
 
321
- /* ---- generate ---- */
322
- const client = await Client.connect(window.location.origin);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
  function setJob(label, cls) {
325
  const el = $("job-state");
@@ -340,19 +359,29 @@ $("run").addEventListener("click", async () => {
340
  state.timer = setInterval(() => $("elapsed").textContent = ((Date.now() - t0) / 1000).toFixed(0) + "s", 500);
341
 
342
  try {
343
- const result = await client.predict("/generate", {
344
- prompt,
345
- image_path: state.first ? handle_file(state.first) : null,
346
- last_image_path: state.last ? handle_file(state.last) : null,
347
- canvas: $("canvas").value,
348
- duration: Number($("duration").value),
349
- steps: Number($("steps").value),
350
- seed: Number($("seed").value),
351
- upsample: $("upsample").checked,
 
 
 
 
352
  });
353
- const [video, report, refined] = result.data;
354
- // Server mode may return FileData with only `path` populated; resolve those through Gradio's file route.
355
- const videoUrl = video.url || (video.path && `${window.location.origin}/gradio_api/file=${video.path}`);
 
 
 
 
 
 
356
  if (!videoUrl) throw new Error("the backend returned no video reference");
357
  $("video").src = videoUrl;
358
  $("monitor").classList.remove("hidden-video");
 
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
  $("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
  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;
382
+ // FileData may carry only `path`; resolve those through Gradio's file route.
383
+ const videoUrl = (video && (video.url || (video.path && `/gradio_api/file=${video.path}`)))
384
+ || (typeof video === "string" && `/gradio_api/file=${video}`);
385
  if (!videoUrl) throw new Error("the backend returned no video reference");
386
  $("video").src = videoUrl;
387
  $("monitor").classList.remove("hidden-video");