110 lines
5.3 KiB
JavaScript
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(() => {});
|
|
},
|
|
});
|