Fix: return single JSON string from @app .api for reliable Gradio Server serialization
Browse filesThe tuple return type was not being serialized by Gradio Server mode,
resulting in empty data reaching the frontend. Now returns a single
JSON string (-> str) which is the simplest and most reliable return
type for @app .api(). Frontend parses the JSON and resolves file paths
via /api/file endpoint. This keeps the Gradio client + ZeroGPU queue.
- app.py +11 -10
- index.html +42 -30
app.py
CHANGED
|
@@ -4405,7 +4405,8 @@ if __name__ == "__main__":
|
|
| 4405 |
return HTMLResponse(content="Not found", status_code=404)
|
| 4406 |
return FileResponse(resolved)
|
| 4407 |
|
| 4408 |
-
# Expose Gradio api endpoint – returns
|
|
|
|
| 4409 |
# File outputs are returned as absolute paths; the frontend resolves them via /api/file.
|
| 4410 |
@app.api(name="run_generation")
|
| 4411 |
def api_run_generation(
|
|
@@ -4423,7 +4424,7 @@ if __name__ == "__main__":
|
|
| 4423 |
validation_timestep_shift: float = 3.5,
|
| 4424 |
cfg_text_scale: float = 4.0,
|
| 4425 |
enable_frame_interpolation: bool = False,
|
| 4426 |
-
) ->
|
| 4427 |
video_path = extract_path(input_video)
|
| 4428 |
image_path = extract_path(input_image)
|
| 4429 |
|
|
@@ -4444,14 +4445,14 @@ if __name__ == "__main__":
|
|
| 4444 |
enable_frame_interpolation=enable_frame_interpolation,
|
| 4445 |
)
|
| 4446 |
|
| 4447 |
-
# Return
|
| 4448 |
-
# The frontend
|
| 4449 |
-
return (
|
| 4450 |
-
str(output_video) if output_video else "",
|
| 4451 |
-
str(output_image) if output_image else "",
|
| 4452 |
-
str(output_text) if output_text else "",
|
| 4453 |
-
str(status) if status else "",
|
| 4454 |
-
)
|
| 4455 |
|
| 4456 |
app.launch(
|
| 4457 |
server_name=args.server_name,
|
|
|
|
| 4405 |
return HTMLResponse(content="Not found", status_code=404)
|
| 4406 |
return FileResponse(resolved)
|
| 4407 |
|
| 4408 |
+
# Expose Gradio api endpoint – returns a single JSON string for maximum client compatibility.
|
| 4409 |
+
# Using a single str return avoids all Gradio Server tuple/FileData serialization issues.
|
| 4410 |
# File outputs are returned as absolute paths; the frontend resolves them via /api/file.
|
| 4411 |
@app.api(name="run_generation")
|
| 4412 |
def api_run_generation(
|
|
|
|
| 4424 |
validation_timestep_shift: float = 3.5,
|
| 4425 |
cfg_text_scale: float = 4.0,
|
| 4426 |
enable_frame_interpolation: bool = False,
|
| 4427 |
+
) -> str:
|
| 4428 |
video_path = extract_path(input_video)
|
| 4429 |
image_path = extract_path(input_image)
|
| 4430 |
|
|
|
|
| 4445 |
enable_frame_interpolation=enable_frame_interpolation,
|
| 4446 |
)
|
| 4447 |
|
| 4448 |
+
# Return a single JSON string with all result data.
|
| 4449 |
+
# The frontend parses this and resolves file paths via /api/file?path=...
|
| 4450 |
+
return json.dumps({
|
| 4451 |
+
"video": str(output_video) if output_video else "",
|
| 4452 |
+
"image": str(output_image) if output_image else "",
|
| 4453 |
+
"text": str(output_text) if output_text else "",
|
| 4454 |
+
"status": str(status) if status else "",
|
| 4455 |
+
})
|
| 4456 |
|
| 4457 |
app.launch(
|
| 4458 |
server_name=args.server_name,
|
index.html
CHANGED
|
@@ -2376,40 +2376,52 @@
|
|
| 2376 |
return;
|
| 2377 |
}
|
| 2378 |
|
| 2379 |
-
// The API
|
| 2380 |
-
|
| 2381 |
-
|
| 2382 |
-
|
| 2383 |
-
|
| 2384 |
-
|
| 2385 |
-
|
| 2386 |
-
|
| 2387 |
-
|
| 2388 |
-
|
| 2389 |
-
|
| 2390 |
-
|
| 2391 |
-
|
| 2392 |
-
|
| 2393 |
-
|
| 2394 |
-
|
| 2395 |
-
|
| 2396 |
-
|
| 2397 |
-
// FileData object with url or path
|
| 2398 |
-
if (raw.url) return raw.url;
|
| 2399 |
-
if (raw.path) {
|
| 2400 |
-
if (raw.path.startsWith("http")) return raw.path;
|
| 2401 |
-
return "/api/file?path=" + encodeURIComponent(raw.path);
|
| 2402 |
}
|
| 2403 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2404 |
}
|
| 2405 |
|
| 2406 |
-
const videoUrl = resolveMediaUrl(
|
| 2407 |
-
const imageUrl = resolveMediaUrl(
|
| 2408 |
-
console.log("Resolved URLs:", { videoUrl, imageUrl });
|
| 2409 |
|
| 2410 |
-
// If there's
|
| 2411 |
-
if (
|
| 2412 |
-
alert(`Inference Status: ${
|
| 2413 |
return;
|
| 2414 |
}
|
| 2415 |
|
|
|
|
| 2376 |
return;
|
| 2377 |
}
|
| 2378 |
|
| 2379 |
+
// The API returns a single JSON string. result.data may be:
|
| 2380 |
+
// - An array with one element: ["json_string"]
|
| 2381 |
+
// - A direct string: "json_string"
|
| 2382 |
+
let rawData = result.data;
|
| 2383 |
+
if (Array.isArray(rawData)) rawData = rawData[0];
|
| 2384 |
+
console.log("rawData after unwrap:", rawData);
|
| 2385 |
+
|
| 2386 |
+
let parsed;
|
| 2387 |
+
try {
|
| 2388 |
+
if (typeof rawData === "string") {
|
| 2389 |
+
parsed = JSON.parse(rawData);
|
| 2390 |
+
} else if (typeof rawData === "object" && rawData !== null) {
|
| 2391 |
+
// Already an object (in case Gradio deserializes it)
|
| 2392 |
+
parsed = rawData;
|
| 2393 |
+
} else {
|
| 2394 |
+
console.warn("displayResult: Unexpected rawData type:", typeof rawData);
|
| 2395 |
+
resetOutputViewer();
|
| 2396 |
+
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2397 |
}
|
| 2398 |
+
} catch (e) {
|
| 2399 |
+
console.error("displayResult: Failed to parse JSON:", e, rawData);
|
| 2400 |
+
resetOutputViewer();
|
| 2401 |
+
return;
|
| 2402 |
+
}
|
| 2403 |
+
|
| 2404 |
+
console.log("displayResult parsed:", parsed);
|
| 2405 |
+
|
| 2406 |
+
const videoPath = parsed.video || "";
|
| 2407 |
+
const imagePath = parsed.image || "";
|
| 2408 |
+
const textData = parsed.text || "";
|
| 2409 |
+
const statusMsg = parsed.status || "";
|
| 2410 |
+
|
| 2411 |
+
// Helper: resolve a file path to a servable URL
|
| 2412 |
+
function resolveMediaUrl(filePath) {
|
| 2413 |
+
if (!filePath || filePath.trim() === "") return null;
|
| 2414 |
+
if (filePath.startsWith("http")) return filePath;
|
| 2415 |
+
return "/api/file?path=" + encodeURIComponent(filePath);
|
| 2416 |
}
|
| 2417 |
|
| 2418 |
+
const videoUrl = resolveMediaUrl(videoPath);
|
| 2419 |
+
const imageUrl = resolveMediaUrl(imagePath);
|
| 2420 |
+
console.log("Resolved URLs:", { videoUrl, imageUrl, textData, statusMsg });
|
| 2421 |
|
| 2422 |
+
// If there's only a status message with no actual content
|
| 2423 |
+
if (statusMsg && !videoUrl && !imageUrl && !textData) {
|
| 2424 |
+
alert(`Inference Status: ${statusMsg}`);
|
| 2425 |
return;
|
| 2426 |
}
|
| 2427 |
|