Files
magicwang1111-ComfyUI-Accou…/web/js/apiRecovery.js
T

110 lines
5.3 KiB
JavaScript

import { app } from "/scripts/app.js";
import { api } from "/scripts/api.js";
async function showRecoveredVideos(promptId) {
const job = await api.getJobDetail(promptId);
const videos = Object.entries(job?.outputs || {})
.filter(([key]) => key.startsWith("api_recovery_"))
.flatMap(([, output]) => output.video || []);
if (!videos.length) throw new Error("Recovered videos are unavailable");
const dialog = document.createElement("dialog");
dialog.className = "am-recovery-dialog";
const title = document.createElement("h2");
title.textContent = "API 结果已恢复";
const description = document.createElement("p");
description.textContent = "原工作流已中断,后续节点未执行。以下是从原 API 任务取回的视频。";
const close = document.createElement("button");
close.textContent = "关闭";
close.addEventListener("click", () => dialog.close());
dialog.append(title, description, close);
for (const reference of videos) {
const video = document.createElement("video");
video.controls = true;
video.preload = "metadata";
video.src = api.apiURL("/view?" + new URLSearchParams({
filename: reference.filename, subfolder: reference.subfolder || "", type: reference.type || "output",
}));
dialog.append(video);
}
dialog.addEventListener("close", () => {
for (const video of dialog.querySelectorAll("video")) {
video.pause();
video.removeAttribute("src");
video.load();
}
dialog.remove();
}, { once: true });
document.body.append(dialog);
dialog.showModal();
}
app.registerExtension({
name: "ComfyUI.AccountManager.APIRecovery",
setup() {
const style = document.createElement("style");
style.textContent = `
.am-recovered-video-button { position:absolute; right:4px; bottom:2px; z-index:1; padding:3px 7px; border:1px solid #6199ca; border-radius:5px; background:#223e57; color:#fff; font-size:11px; cursor:pointer; }
.am-recovery-dialog { margin:auto; padding:20px; width:min(900px,90vw); max-height:90vh; overflow:auto; border:1px solid #666; border-radius:10px; background:var(--comfy-menu-bg,#202020); color:var(--input-text,#eee); }
.am-recovery-dialog::backdrop { background:#0009; }
.am-recovery-dialog h2 { font-size:20px; margin:0 0 8px; }
.am-recovery-dialog p { margin:0 55px 14px 0; }
.am-recovery-dialog > button { position:absolute; top:20px; right:20px; cursor:pointer; }
.am-recovery-dialog video { display:block; width:100%; max-height:65vh; margin-top:12px; }
`;
document.head.append(style);
const recovered = new Set();
let frame;
const updateCards = () => {
frame = undefined;
for (const card of document.querySelectorAll("[data-job-id]")) {
const promptId = card.dataset.jobId;
const existing = card.querySelector(".am-recovered-video-button");
if (!recovered.has(promptId)) {
existing?.remove();
continue;
}
if (existing) continue;
card.style.position = "relative";
const button = document.createElement("button");
button.className = "am-recovered-video-button";
button.textContent = "查看已恢复视频";
button.addEventListener("click", async event => {
event.stopPropagation();
button.disabled = true;
try {
await showRecoveredVideos(promptId);
} catch (error) {
console.error("[Account Manager] Recovered video could not be opened", error);
app.extensionManager.toast.add({ severity: "warn", summary: "无法读取已恢复视频,请稍后重试", life: 5000 });
} finally {
button.disabled = false;
}
});
card.append(button);
}
};
const scheduleUpdate = () => { frame ??= requestAnimationFrame(updateCards); };
const fetchApi = api.fetchApi;
api.fetchApi = async function (route, ...args) {
const response = await fetchApi.call(this, route, ...args);
if (response.ok && /^\/(?:api\/)?jobs(?:\?|$)/.test(route)) {
response.clone().json().then(({ jobs }) => {
for (const job of jobs) {
const available = job.api_result_recovered || (
job.preview_output?.nodeId?.startsWith("api_recovery_") && job.preview_output.mediaType === "video"
);
if (available) recovered.add(job.id);
else recovered.delete(job.id);
}
scheduleUpdate();
}).catch(() => {});
}
return response;
};
new MutationObserver(scheduleUpdate).observe(document.body, { childList: true, subtree: true });
// The queue may have loaded before extension setup completed.
api.getHistory().catch(() => {});
},
});