feat: add video job cancellation functionality and error tracking in generated videos

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-29 20:04:10 +02:00
parent 3d0a08a8ef
commit 299ad7d943
13 changed files with 282 additions and 61 deletions
+71 -5
View File
@@ -66,9 +66,70 @@ document.addEventListener("DOMContentLoaded", () => {
const videoId = pollDiv.dataset.videoId;
const statusText = document.getElementById("poll-status-text");
const videoContainer = document.getElementById("poll-video-container");
const cancelBtn = document.getElementById("cancel-video-btn");
const cancelMsg = document.getElementById("cancel-msg");
const MAX_POLLS = 120; // ~10 minutes at 5s interval
let pollCount = 0;
let interval = null;
const interval = setInterval(async () => {
const stopPolling = () => {
if (interval) {
clearInterval(interval);
interval = null;
}
};
if (cancelBtn) {
cancelBtn.addEventListener("click", async () => {
cancelBtn.disabled = true;
cancelBtn.textContent = "Cancelling…";
try {
const resp = await fetch(
"/generate/video/" + encodeURIComponent(videoId) + "/cancel",
{ method: "POST" },
);
if (resp.ok) {
stopPolling();
cancelBtn.classList.add("hidden");
if (cancelMsg) {
cancelMsg.textContent = "Job cancelled.";
cancelMsg.classList.remove("hidden", "text-red-500");
cancelMsg.classList.add("text-gray-300");
}
if (statusText) {
statusText.innerHTML = "Status: <strong>cancelled</strong>";
}
} else {
const data = await resp.json().catch(() => ({}));
cancelBtn.disabled = false;
cancelBtn.textContent = "Cancel Job";
if (cancelMsg) {
cancelMsg.textContent = data.detail || "Cancel failed.";
cancelMsg.classList.remove("hidden");
cancelMsg.classList.add("text-red-500");
}
}
} catch (e) {
cancelBtn.disabled = false;
cancelBtn.textContent = "Cancel Job";
if (cancelMsg) {
cancelMsg.textContent = "Network error.";
cancelMsg.classList.remove("hidden");
cancelMsg.classList.add("text-red-500");
}
}
});
}
interval = setInterval(async () => {
try {
pollCount++;
if (pollCount > MAX_POLLS) {
stopPolling();
pollDiv.innerHTML =
'<div class="alert alert-warning">Polling timed out. Please refresh the page to check status.</div>';
return;
}
const resp = await fetch(
"/generate/video/" + encodeURIComponent(videoId) + "/status",
);
@@ -80,7 +141,7 @@ document.addEventListener("DOMContentLoaded", () => {
}
if (data.status === "completed") {
clearInterval(interval);
stopPolling();
if (data.video_url) {
if (videoContainer) {
const vid = document.createElement("video");
@@ -95,10 +156,15 @@ document.addEventListener("DOMContentLoaded", () => {
window.location.reload();
}
}
} else if (data.status === "failed" || data.status === "cancelled") {
clearInterval(interval);
} else if (data.status === "failed") {
stopPolling();
pollDiv.innerHTML =
'<div class="alert alert-error">Generation failed or was cancelled.</div>';
'<div class="alert alert-error">Generation failed.</div>';
} else if (data.status === "cancelled") {
stopPolling();
if (cancelBtn) cancelBtn.classList.add("hidden");
pollDiv.innerHTML =
'<div class="alert alert-info">Job was cancelled.</div>';
}
} catch (e) {
console.error("Video polling error:", e);