Spaces:
Running on Zero
Running on Zero
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
- app.py +6 -4
- 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 |
-
|
| 339 |
-
os.
|
| 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 |
-
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
});
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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");
|