Polish customer update flow and prepare ZIP installation
This commit is contained in:
+466
-90
@@ -1,100 +1,476 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { api } from "../../../scripts/api.js";
|
||||
|
||||
const RELEASE_URL = "https://git.o1key.com/publisher/comfyui_o1key";
|
||||
let registered = false;
|
||||
const BUTTON_ID = "o1key-update-button";
|
||||
const DIALOG_ID = "o1key-update-dialog";
|
||||
const STYLE_ID = "o1key-update-styles";
|
||||
let mountObserver = null;
|
||||
|
||||
const UPDATE_ERRORS = {
|
||||
local_changes: ["当前安装包含自定义修改,暂时无法自动更新。", "请联系技术支持处理,以免丢失现有内容。"],
|
||||
not_git: ["当前安装方式暂不支持在线更新。", "请联系技术支持。"],
|
||||
git_missing: ["当前环境暂不支持在线更新。", "请联系技术支持。"],
|
||||
wrong_branch: ["当前版本暂时无法自动更新。", "请联系技术支持。"],
|
||||
diverged: ["当前版本暂时无法自动更新。", "请联系技术支持。"],
|
||||
merge_blocked: ["更新被现有文件阻止。", "现有文件已保留,请联系技术支持。"],
|
||||
fetch_failed: ["暂时无法检查新版本。", "请检查网络连接,稍后重试。"],
|
||||
timeout: ["检查新版本超时。", "请检查网络连接,稍后重试。"],
|
||||
update_in_progress: ["更新正在进行中。", "请稍候再试。"],
|
||||
};
|
||||
|
||||
function ensureStyles() {
|
||||
if (document.querySelector(`#${STYLE_ID}`)) return;
|
||||
const style = document.createElement("style");
|
||||
style.id = STYLE_ID;
|
||||
style.textContent = `
|
||||
#${DIALOG_ID} {
|
||||
box-sizing: border-box;
|
||||
width: min(480px, calc(100vw - 32px));
|
||||
max-height: calc(100vh - 32px);
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--border-color, #454545);
|
||||
border-radius: 12px;
|
||||
color: var(--fg-color, #eee);
|
||||
background: var(--comfy-menu-bg, #202020);
|
||||
box-shadow: 0 24px 72px rgba(0, 0, 0, .48);
|
||||
font-family: inherit;
|
||||
}
|
||||
#${DIALOG_ID}::backdrop { background: rgba(0, 0, 0, .62); }
|
||||
#${DIALOG_ID} .o1key-update-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 13px;
|
||||
padding: 21px 24px;
|
||||
border-bottom: 1px solid var(--border-color, #3b3b3b);
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-brand {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex: none;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border: 1px solid rgba(96, 165, 250, .35);
|
||||
border-radius: 10px;
|
||||
color: var(--p-primary-color, #60a5fa);
|
||||
background: rgba(96, 165, 250, .12);
|
||||
font-size: 17px;
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-heading { flex: 1; min-width: 0; }
|
||||
#${DIALOG_ID} .o1key-update-eyebrow {
|
||||
margin: 0 0 3px;
|
||||
color: var(--p-primary-color, #60a5fa);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .12em;
|
||||
}
|
||||
#${DIALOG_ID} h2 {
|
||||
margin: 0;
|
||||
color: var(--p-primary-color, #60a5fa);
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
#${DIALOG_ID} button { font: inherit; }
|
||||
#${DIALOG_ID} .o1key-update-close {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
color: var(--fg-color, #ccc);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
opacity: .72;
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-close:hover { background: rgba(127, 127, 127, .16); opacity: 1; }
|
||||
#${DIALOG_ID} .o1key-update-body { padding: 22px 24px 24px; }
|
||||
#${DIALOG_ID} .o1key-update-description {
|
||||
margin: 0 0 17px;
|
||||
color: var(--fg-color, #ddd);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
opacity: .75;
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-result {
|
||||
display: flex;
|
||||
gap: 13px;
|
||||
min-height: 72px;
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border-color, #454545);
|
||||
border-radius: 9px;
|
||||
background: var(--comfy-input-bg, #292929);
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-result[data-kind="error"] {
|
||||
border-color: rgba(239, 119, 119, .42);
|
||||
background: rgba(239, 119, 119, .08);
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-result[data-kind="success"],
|
||||
#${DIALOG_ID} .o1key-update-result[data-kind="available"] {
|
||||
border-color: rgba(96, 165, 250, .4);
|
||||
background: rgba(96, 165, 250, .08);
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-state-icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-top: 2px;
|
||||
color: var(--fg-color, #bbb);
|
||||
font-size: 17px;
|
||||
opacity: .72;
|
||||
}
|
||||
#${DIALOG_ID} [data-kind="error"] .o1key-update-state-icon { color: #ef7777; opacity: 1; }
|
||||
#${DIALOG_ID} [data-kind="success"] .o1key-update-state-icon,
|
||||
#${DIALOG_ID} [data-kind="available"] .o1key-update-state-icon {
|
||||
color: var(--p-primary-color, #60a5fa);
|
||||
opacity: 1;
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-state-icon.is-loading { opacity: 1; }
|
||||
#${DIALOG_ID} .o1key-update-state-icon.is-loading::before {
|
||||
content: "";
|
||||
box-sizing: border-box;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgba(96, 165, 250, .25);
|
||||
border-top-color: var(--p-primary-color, #60a5fa);
|
||||
border-radius: 50%;
|
||||
animation: o1key-update-spin .8s linear infinite;
|
||||
}
|
||||
@keyframes o1key-update-spin { to { transform: rotate(360deg); } }
|
||||
#${DIALOG_ID} .o1key-update-result-copy { min-width: 0; }
|
||||
#${DIALOG_ID} .o1key-update-status {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 650;
|
||||
line-height: 1.5;
|
||||
}
|
||||
#${DIALOG_ID} [data-kind="busy"] .o1key-update-status,
|
||||
#${DIALOG_ID} [data-kind="success"] .o1key-update-status,
|
||||
#${DIALOG_ID} [data-kind="available"] .o1key-update-status {
|
||||
color: var(--p-primary-color, #60a5fa);
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-suggestion {
|
||||
margin: 5px 0 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
opacity: .72;
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 9px;
|
||||
padding: 16px 24px 20px;
|
||||
border-top: 1px solid var(--border-color, #3b3b3b);
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-actions button {
|
||||
min-width: 96px;
|
||||
min-height: 36px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 7px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background .15s, border-color .15s, transform .15s;
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-secondary {
|
||||
border: 1px solid var(--border-color, #555);
|
||||
color: var(--p-primary-color, #60a5fa);
|
||||
background: var(--comfy-input-bg, #292929);
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-secondary:hover {
|
||||
border-color: var(--p-primary-color, #60a5fa);
|
||||
background: rgba(96, 165, 250, .1);
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-primary {
|
||||
border: 1px solid var(--p-primary-500, #3b82f6);
|
||||
color: #fff;
|
||||
background: var(--p-primary-500, #3b82f6);
|
||||
}
|
||||
#${DIALOG_ID} .o1key-update-primary:hover {
|
||||
border-color: var(--p-primary-600, #2563eb);
|
||||
background: var(--p-primary-600, #2563eb);
|
||||
}
|
||||
#${DIALOG_ID} button:focus-visible {
|
||||
outline: 2px solid var(--p-primary-color, #60a5fa);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
#${DIALOG_ID} button:disabled { cursor: default; opacity: .55; }
|
||||
#${DIALOG_ID} button:disabled:hover { transform: none; }
|
||||
@media (max-width: 420px) {
|
||||
#${DIALOG_ID} .o1key-update-header { padding: 18px; }
|
||||
#${DIALOG_ID} .o1key-update-body { padding: 18px; }
|
||||
#${DIALOG_ID} .o1key-update-actions { padding: 14px 18px 18px; }
|
||||
#${DIALOG_ID} .o1key-update-actions button { flex: 1; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function createUpdateDialog() {
|
||||
const dialog = document.createElement("dialog");
|
||||
dialog.id = DIALOG_ID;
|
||||
dialog.setAttribute("aria-labelledby", "o1key-update-title");
|
||||
|
||||
const root = document.createElement("div");
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "o1key-update-header";
|
||||
|
||||
const brand = document.createElement("span");
|
||||
brand.className = "pi pi-download o1key-update-brand";
|
||||
brand.setAttribute("aria-hidden", "true");
|
||||
|
||||
const heading = document.createElement("div");
|
||||
heading.className = "o1key-update-heading";
|
||||
|
||||
const eyebrow = document.createElement("p");
|
||||
eyebrow.className = "o1key-update-eyebrow";
|
||||
eyebrow.textContent = "O1KEY";
|
||||
|
||||
const title = document.createElement("h2");
|
||||
title.id = "o1key-update-title";
|
||||
title.textContent = "更新 O1Key 节点包";
|
||||
heading.append(eyebrow, title);
|
||||
|
||||
const close = document.createElement("button");
|
||||
close.type = "button";
|
||||
close.className = "o1key-update-close";
|
||||
close.textContent = "×";
|
||||
close.setAttribute("aria-label", "关闭");
|
||||
close.addEventListener("click", () => dialog.close());
|
||||
header.append(brand, heading, close);
|
||||
|
||||
const body = document.createElement("div");
|
||||
body.className = "o1key-update-body";
|
||||
|
||||
const description = document.createElement("p");
|
||||
description.className = "o1key-update-description";
|
||||
description.textContent = "检查是否有新版本可用。";
|
||||
|
||||
const resultCard = document.createElement("div");
|
||||
resultCard.className = "o1key-update-result";
|
||||
|
||||
const statusIcon = document.createElement("span");
|
||||
statusIcon.className = "pi pi-info-circle o1key-update-state-icon";
|
||||
statusIcon.setAttribute("aria-hidden", "true");
|
||||
|
||||
const resultCopy = document.createElement("div");
|
||||
resultCopy.className = "o1key-update-result-copy";
|
||||
|
||||
const status = document.createElement("p");
|
||||
status.className = "o1key-update-status";
|
||||
status.setAttribute("role", "status");
|
||||
status.setAttribute("aria-live", "polite");
|
||||
|
||||
const suggestion = document.createElement("p");
|
||||
suggestion.className = "o1key-update-suggestion";
|
||||
resultCopy.append(status, suggestion);
|
||||
resultCard.append(statusIcon, resultCopy);
|
||||
body.append(description, resultCard);
|
||||
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "o1key-update-actions";
|
||||
|
||||
const secondary = document.createElement("button");
|
||||
secondary.type = "button";
|
||||
secondary.className = "o1key-update-secondary";
|
||||
secondary.textContent = "关闭";
|
||||
secondary.addEventListener("click", () => dialog.close());
|
||||
|
||||
const primary = document.createElement("button");
|
||||
primary.type = "button";
|
||||
primary.className = "o1key-update-primary";
|
||||
actions.append(secondary, primary);
|
||||
|
||||
function show(message, nextStep = "", kind = "info") {
|
||||
status.textContent = message;
|
||||
suggestion.textContent = nextStep;
|
||||
resultCard.dataset.kind = kind;
|
||||
statusIcon.className = kind === "busy"
|
||||
? "o1key-update-state-icon is-loading"
|
||||
: `pi ${{
|
||||
error: "pi-exclamation-circle",
|
||||
success: "pi-check-circle",
|
||||
available: "pi-arrow-circle-down",
|
||||
}[kind] || "pi-info-circle"} o1key-update-state-icon`;
|
||||
}
|
||||
|
||||
function setState(state) {
|
||||
dialog.dataset.state = state;
|
||||
const busy = state === "checking" || state === "updating";
|
||||
primary.disabled = busy;
|
||||
close.disabled = state === "updating";
|
||||
secondary.disabled = state === "updating";
|
||||
primary.textContent = {
|
||||
checking: "检查中…",
|
||||
available: "立即更新",
|
||||
updating: "更新中…",
|
||||
success: "完成",
|
||||
}[state] || "重新检查";
|
||||
secondary.textContent = state === "available" ? "稍后再说" : "关闭";
|
||||
}
|
||||
|
||||
function showError(response, result) {
|
||||
setState("error");
|
||||
if (response.status === 404) {
|
||||
show("更新功能尚未就绪。", "请重启 ComfyUI 并刷新页面后重试。", "error");
|
||||
return;
|
||||
}
|
||||
const [message, nextStep] = UPDATE_ERRORS[result?.code] ||
|
||||
["暂时无法完成更新。", "请稍后重试;如果问题持续,请联系技术支持。"];
|
||||
show(message, nextStep, "error");
|
||||
}
|
||||
|
||||
async function checkForUpdate() {
|
||||
if (dialog.dataset.state === "checking" || dialog.dataset.state === "updating") return;
|
||||
setState("checking");
|
||||
show("正在检查新版本…", "请稍候。", "busy");
|
||||
try {
|
||||
const checkResponse = await api.fetchApi("/o1key/update/check", { cache: "no-store" });
|
||||
if (checkResponse.status === 404) return showError(checkResponse);
|
||||
let checkResult;
|
||||
try {
|
||||
checkResult = await checkResponse.json();
|
||||
} catch {
|
||||
setState("error");
|
||||
show("暂时无法读取检查结果。", "请刷新页面后重试。", "error");
|
||||
return;
|
||||
}
|
||||
if (!checkResponse.ok) {
|
||||
showError(checkResponse, checkResult);
|
||||
return;
|
||||
}
|
||||
if (typeof checkResult.update_available !== "boolean") {
|
||||
setState("error");
|
||||
show("暂时无法读取检查结果。", "请刷新页面后重试。", "error");
|
||||
return;
|
||||
}
|
||||
if (!checkResult.update_available) {
|
||||
setState("current");
|
||||
show("已是最新版本。", "无需更新。", "success");
|
||||
return;
|
||||
}
|
||||
setState("available");
|
||||
show("发现新版本。", "现在更新即可使用最新功能。", "available");
|
||||
} catch {
|
||||
setState("error");
|
||||
show("暂时无法完成操作。", "请确认 ComfyUI 正在运行,然后重试。", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function startUpdate() {
|
||||
if (dialog.dataset.state !== "available") return;
|
||||
setState("updating");
|
||||
show("正在更新…", "请保持 ComfyUI 运行,等待完成。", "busy");
|
||||
try {
|
||||
const response = await api.fetchApi("/o1key/update", {
|
||||
method: "POST",
|
||||
headers: { "X-O1Key-Update": "1" },
|
||||
});
|
||||
if (response.status === 404) return showError(response);
|
||||
let result;
|
||||
try {
|
||||
result = await response.json();
|
||||
} catch {
|
||||
setState("error");
|
||||
show("暂时无法读取更新结果。", "请刷新页面后重试。", "error");
|
||||
return;
|
||||
}
|
||||
if (!response.ok) return showError(response, result);
|
||||
if (typeof result.updated !== "boolean") {
|
||||
setState("error");
|
||||
show("暂时无法读取更新结果。", "请刷新页面后重试。", "error");
|
||||
return;
|
||||
}
|
||||
if (result.updated) {
|
||||
setState("success");
|
||||
show(
|
||||
"更新完成。",
|
||||
result.requirements_changed
|
||||
? "新版本还需要维护人员完成配置,请联系技术支持后重启 ComfyUI。"
|
||||
: "请重启 ComfyUI,并刷新页面以使用新版本。",
|
||||
"success",
|
||||
);
|
||||
} else {
|
||||
setState("current");
|
||||
show("已是最新版本。", "无需更新。", "success");
|
||||
}
|
||||
} catch {
|
||||
setState("error");
|
||||
show("暂时无法完成操作。", "请确认 ComfyUI 正在运行,然后重试。", "error");
|
||||
}
|
||||
}
|
||||
primary.addEventListener("click", () => {
|
||||
if (dialog.dataset.state === "available") return startUpdate();
|
||||
if (dialog.dataset.state === "success") return dialog.close();
|
||||
return checkForUpdate();
|
||||
});
|
||||
dialog.addEventListener("cancel", (event) => {
|
||||
if (dialog.dataset.state === "updating") event.preventDefault();
|
||||
});
|
||||
dialog.checkForUpdate = checkForUpdate;
|
||||
|
||||
root.append(header, body, actions);
|
||||
dialog.append(root);
|
||||
document.body.appendChild(dialog);
|
||||
setState("idle");
|
||||
show("准备检查更新。", "", "info");
|
||||
return dialog;
|
||||
}
|
||||
|
||||
function openUpdateDialog() {
|
||||
ensureStyles();
|
||||
const dialog = document.querySelector(`#${DIALOG_ID}`) || createUpdateDialog();
|
||||
if (!dialog.open) {
|
||||
dialog.showModal();
|
||||
void dialog.checkForUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
function mountUpdateButton() {
|
||||
const tokenButton = document.querySelector("#o1key-token-manager-button");
|
||||
if (!tokenButton?.parentElement) return false;
|
||||
|
||||
let updateButton = document.querySelector(`#${BUTTON_ID}`);
|
||||
if (!updateButton) {
|
||||
updateButton = tokenButton.cloneNode(true);
|
||||
updateButton.id = BUTTON_ID;
|
||||
updateButton.type = "button";
|
||||
updateButton.classList.remove("side-bar-button-selected", "o1key-token-manager-button", "o1key-restart-button");
|
||||
updateButton.classList.add("o1key-update-button");
|
||||
updateButton.setAttribute("aria-label", "更新");
|
||||
updateButton.setAttribute("title", "更新 O1Key 节点包");
|
||||
updateButton.removeAttribute("aria-pressed");
|
||||
|
||||
const icon = updateButton.querySelector(".side-bar-button-icon");
|
||||
if (icon) icon.className = "pi pi-download side-bar-button-icon";
|
||||
const label = updateButton.querySelector(".side-bar-button-label");
|
||||
if (label) label.textContent = "更新";
|
||||
|
||||
updateButton.addEventListener("click", openUpdateDialog);
|
||||
}
|
||||
|
||||
if (tokenButton.nextElementSibling !== updateButton) tokenButton.after(updateButton);
|
||||
return true;
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "o1key.updateButton",
|
||||
setup() {
|
||||
if (registered || app.extensionManager.getSidebarTabs?.().some((tab) => tab.id === "o1key-update")) return;
|
||||
registered = true;
|
||||
|
||||
app.extensionManager.registerSidebarTab({
|
||||
id: "o1key-update",
|
||||
title: "更新",
|
||||
tooltip: "更新 O1Key 节点包",
|
||||
icon: "pi pi-download",
|
||||
type: "custom",
|
||||
render(container) {
|
||||
const root = document.createElement("div");
|
||||
root.id = "o1key-update-panel";
|
||||
root.style.padding = "16px";
|
||||
|
||||
const title = document.createElement("h3");
|
||||
title.textContent = "更新 O1Key 节点包";
|
||||
|
||||
const description = document.createElement("p");
|
||||
description.textContent = "从 O1Key 发布仓库的 main 分支获取更新。只允许安全快进,不会清理或覆盖本地文件。";
|
||||
|
||||
const source = document.createElement("p");
|
||||
source.textContent = `发布地址:${RELEASE_URL}`;
|
||||
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.textContent = "检查并更新";
|
||||
button.className = "p-button";
|
||||
|
||||
const status = document.createElement("p");
|
||||
status.setAttribute("role", "status");
|
||||
status.setAttribute("aria-live", "polite");
|
||||
|
||||
const suggestion = document.createElement("p");
|
||||
|
||||
function show(message, nextStep = "", error = false) {
|
||||
status.textContent = message;
|
||||
status.style.color = error ? "var(--error-color, #ef7777)" : "";
|
||||
suggestion.textContent = nextStep;
|
||||
}
|
||||
|
||||
button.addEventListener("click", async () => {
|
||||
if (button.disabled || !window.confirm(`从 ${RELEASE_URL} 的 main 分支检查并更新?本地修改不会被覆盖。`)) return;
|
||||
button.disabled = true;
|
||||
show("正在检查本地文件并连接发布仓库…", "请保持 ComfyUI 运行,等待结果。");
|
||||
try {
|
||||
const response = await api.fetchApi("/o1key/update", {
|
||||
method: "POST",
|
||||
headers: { "X-O1Key-Update": "1" },
|
||||
});
|
||||
if (response.status === 404) {
|
||||
show("更新接口尚未加载。", "重启 ComfyUI 并刷新浏览器后再试。", true);
|
||||
return;
|
||||
}
|
||||
let result;
|
||||
try {
|
||||
result = await response.json();
|
||||
} catch {
|
||||
show("无法读取更新结果。", "确认 ComfyUI 正常运行,刷新页面后重试。", true);
|
||||
return;
|
||||
}
|
||||
if (!response.ok) {
|
||||
show(result.error || "更新未完成。", result.suggestion || "检查 ComfyUI 日志后重试。", true);
|
||||
return;
|
||||
}
|
||||
if (typeof result.updated !== "boolean" || typeof result.version !== "string") {
|
||||
show("更新接口返回了无效结果。", "重启 ComfyUI 并刷新浏览器后重试。", true);
|
||||
return;
|
||||
}
|
||||
if (result.updated) {
|
||||
show(
|
||||
`已更新到 ${result.version}。`,
|
||||
result.requirements_changed
|
||||
? "依赖列表已变化:先在 ComfyUI 使用的 Python 环境中安装 requirements.txt,再重启 ComfyUI。"
|
||||
: "请重启 ComfyUI,并刷新浏览器以加载新版本。",
|
||||
);
|
||||
} else {
|
||||
show(`已是最新版本(${result.version})。`, "无需重启。");
|
||||
}
|
||||
} catch {
|
||||
show("无法连接本地 ComfyUI 更新接口。", "确认 ComfyUI 正在运行,刷新页面后重试。", true);
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
root.append(title, description, source, button, status, suggestion);
|
||||
container.replaceChildren(root);
|
||||
},
|
||||
if (mountUpdateButton() || mountObserver) return;
|
||||
mountObserver = new MutationObserver(() => {
|
||||
if (!mountUpdateButton()) return;
|
||||
mountObserver.disconnect();
|
||||
mountObserver = null;
|
||||
});
|
||||
mountObserver.observe(document.body, { childList: true, subtree: true });
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user