Add update verification button to sidebar
This commit is contained in:
@@ -107,6 +107,7 @@ O1KEY_ASYNC_API_BASE_URL=https://your-async-api-domain.com
|
||||
1. 点击 ComfyUI 左侧工具栏中“令牌管理”下方的“更新”按钮。
|
||||
2. 等待版本检查。如果发现新版本,确认后等待更新完成;需要时可在面板中重新检查。
|
||||
3. 按完成提示重启 ComfyUI。如果面板提示需要技术支持,请联系维护人员。
|
||||
4. 刷新页面后,左侧“更新”下方会出现“检测”按钮,可用来确认本次版本已加载。
|
||||
|
||||
> 如果当前安装包含自定义修改,面板会停止更新并保留现有内容。此时请联系维护人员处理。
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ Like `clients`, the `utils` package uses lazy exports to reduce startup work.
|
||||
|
||||
Every JavaScript file in `web/` is served as a ComfyUI extension. Major responsibilities include settings, chat, cases, notes, element management, workflow migration, upload helpers, previews, painting, trimming, and the panel-style image generator.
|
||||
|
||||
`web/js/o1keyUpdateButton.js` places an Update button directly below the Token Manager button in the left toolbar, before Restart. Opening its dialog immediately calls `GET /o1key/update/check` and asks for confirmation only when a newer version is available; confirmation calls `POST /o1key/update`. Both routes delegate to `utils/updater.py` and share a lock. The updater fetches the public `main` branch from `https://git.o1key.com/publisher/comfyui_o1key.git` without changing the user's `origin`. It reports an already current installation before checking local modifications, and only fast-forwards a clean local Git `main` when an update exists. Local tracked changes, divergent history, and file collisions receive structured error codes; the UI maps those codes to customer-facing messages without exposing the repository or Git details. It never resets or cleans the worktree. A completed update requires a ComfyUI restart to load new Python and JavaScript code.
|
||||
`web/js/o1keyUpdateButton.js` places an Update button directly below the Token Manager button in the left toolbar, then a temporary Detect button before Restart. Detect displays a short local notice to confirm the new frontend loaded. Opening the Update dialog immediately calls `GET /o1key/update/check` and asks for confirmation only when a newer version is available; confirmation calls `POST /o1key/update`. Both routes delegate to `utils/updater.py` and share a lock. The updater fetches the public `main` branch from `https://git.o1key.com/publisher/comfyui_o1key.git` without changing the user's `origin`. It reports an already current installation before checking local modifications, and only fast-forwards a clean local Git `main` when an update exists. Local tracked changes, divergent history, and file collisions receive structured error codes; the UI maps those codes to customer-facing messages without exposing the repository or Git details. It never resets or cleans the worktree. A completed update requires a ComfyUI restart to load new Python and JavaScript code.
|
||||
|
||||
The updater dialog keeps the check result, update confirmation, progress, and retry actions in one ComfyUI-styled modal. Opening it runs the check; a newer version changes the primary action to “立即更新”, while “稍后再说” closes without updating.
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ let observer;
|
||||
const requests = [];
|
||||
const responses = [];
|
||||
let fetchFailure = false;
|
||||
let dismissNotice;
|
||||
const reply = (body, status = 200) => ({ ok: status < 400, status, async json() { return body; } });
|
||||
const app = { registerExtension(value) { extension = value; } };
|
||||
const api = {
|
||||
@@ -110,7 +111,11 @@ class MutationObserver {
|
||||
observe() {}
|
||||
disconnect() { this.disconnected = true; }
|
||||
}
|
||||
vm.runInNewContext(source, { app, api, document, window: {}, MutationObserver });
|
||||
const window = {
|
||||
setTimeout(callback) { dismissNotice = callback; return 1; },
|
||||
clearTimeout() { dismissNotice = null; },
|
||||
};
|
||||
vm.runInNewContext(source, { app, api, document, window, MutationObserver });
|
||||
|
||||
extension.setup();
|
||||
assert.ok(observer);
|
||||
@@ -130,14 +135,29 @@ toolbar.append(token, restart);
|
||||
observer.callback();
|
||||
|
||||
const update = document.querySelector("#o1key-update-button");
|
||||
const detect = document.querySelector("#o1key-detect-button");
|
||||
assert.equal(toolbar.children[0], token);
|
||||
assert.equal(toolbar.children[1], update);
|
||||
assert.equal(toolbar.children[2], restart);
|
||||
assert.equal(toolbar.children[2], detect);
|
||||
assert.equal(toolbar.children[3], restart);
|
||||
assert.equal(update.querySelector(".side-bar-button-label").textContent, "更新");
|
||||
assert.match(update.querySelector(".side-bar-button-icon").className, /pi-download/);
|
||||
assert.equal(detect.querySelector(".side-bar-button-label").textContent, "检测");
|
||||
assert.match(detect.querySelector(".side-bar-button-icon").className, /pi-check-circle/);
|
||||
assert.ok(observer.disconnected);
|
||||
extension.setup();
|
||||
assert.equal(toolbar.children.length, 3);
|
||||
assert.equal(toolbar.children.length, 4);
|
||||
|
||||
detect.listeners.get("click")();
|
||||
const notice = document.querySelector("#o1key-detect-notice");
|
||||
assert.equal(notice.children[1].textContent, "检测按钮已加载");
|
||||
assert.equal(notice.hidden, false);
|
||||
assert.equal(requests.length, 0);
|
||||
dismissNotice();
|
||||
assert.equal(notice.hidden, true);
|
||||
detect.listeners.get("click")();
|
||||
assert.equal(notice.hidden, false);
|
||||
assert.equal(document.body.children.filter((child) => child.id === "o1key-detect-notice").length, 1);
|
||||
|
||||
responses.push(reply({ update_available: false }));
|
||||
update.listeners.get("click")();
|
||||
|
||||
@@ -2,9 +2,12 @@ import { app } from "../../../scripts/app.js";
|
||||
import { api } from "../../../scripts/api.js";
|
||||
|
||||
const BUTTON_ID = "o1key-update-button";
|
||||
const DETECT_BUTTON_ID = "o1key-detect-button";
|
||||
const DETECT_NOTICE_ID = "o1key-detect-notice";
|
||||
const DIALOG_ID = "o1key-update-dialog";
|
||||
const STYLE_ID = "o1key-update-styles";
|
||||
let mountObserver = null;
|
||||
let detectNoticeTimer = null;
|
||||
|
||||
const UPDATE_ERRORS = {
|
||||
local_changes: ["当前安装包含自定义修改,暂时无法自动更新。", "请联系技术支持处理,以免丢失现有内容。"],
|
||||
@@ -204,6 +207,26 @@ function ensureStyles() {
|
||||
}
|
||||
#${DIALOG_ID} button:disabled { cursor: default; opacity: .55; }
|
||||
#${DIALOG_ID} button:disabled:hover { transform: none; }
|
||||
#${DETECT_NOTICE_ID} {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
z-index: 100000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid rgba(96, 165, 250, .45);
|
||||
border-radius: 9px;
|
||||
color: var(--p-primary-color, #60a5fa);
|
||||
background: var(--comfy-menu-bg, #202020);
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, .32);
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
#${DETECT_NOTICE_ID}[hidden] { display: none; }
|
||||
@media (max-width: 420px) {
|
||||
#${DIALOG_ID} .o1key-update-header { padding: 18px; }
|
||||
#${DIALOG_ID} .o1key-update-body { padding: 18px; }
|
||||
@@ -435,6 +458,29 @@ function openUpdateDialog() {
|
||||
}
|
||||
}
|
||||
|
||||
function showDetectNotice() {
|
||||
ensureStyles();
|
||||
let notice = document.querySelector(`#${DETECT_NOTICE_ID}`);
|
||||
if (!notice) {
|
||||
notice = document.createElement("div");
|
||||
notice.id = DETECT_NOTICE_ID;
|
||||
notice.setAttribute("role", "status");
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "pi pi-check-circle";
|
||||
icon.setAttribute("aria-hidden", "true");
|
||||
const label = document.createElement("span");
|
||||
label.textContent = "检测按钮已加载";
|
||||
notice.append(icon, label);
|
||||
document.body.appendChild(notice);
|
||||
}
|
||||
notice.hidden = false;
|
||||
if (detectNoticeTimer !== null) window.clearTimeout(detectNoticeTimer);
|
||||
detectNoticeTimer = window.setTimeout(() => {
|
||||
notice.hidden = true;
|
||||
detectNoticeTimer = null;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function mountUpdateButton() {
|
||||
const tokenButton = document.querySelector("#o1key-token-manager-button");
|
||||
if (!tokenButton?.parentElement) return false;
|
||||
@@ -459,6 +505,27 @@ function mountUpdateButton() {
|
||||
}
|
||||
|
||||
if (tokenButton.nextElementSibling !== updateButton) tokenButton.after(updateButton);
|
||||
|
||||
let detectButton = document.querySelector(`#${DETECT_BUTTON_ID}`);
|
||||
if (!detectButton) {
|
||||
detectButton = tokenButton.cloneNode(true);
|
||||
detectButton.id = DETECT_BUTTON_ID;
|
||||
detectButton.type = "button";
|
||||
detectButton.classList.remove("side-bar-button-selected", "o1key-token-manager-button", "o1key-restart-button");
|
||||
detectButton.classList.add("o1key-detect-button");
|
||||
detectButton.setAttribute("aria-label", "检测");
|
||||
detectButton.setAttribute("title", "检测按钮");
|
||||
detectButton.removeAttribute("aria-pressed");
|
||||
|
||||
const icon = detectButton.querySelector(".side-bar-button-icon");
|
||||
if (icon) icon.className = "pi pi-check-circle side-bar-button-icon";
|
||||
const label = detectButton.querySelector(".side-bar-button-label");
|
||||
if (label) label.textContent = "检测";
|
||||
|
||||
detectButton.addEventListener("click", showDetectNotice);
|
||||
}
|
||||
|
||||
if (updateButton.nextElementSibling !== detectButton) updateButton.after(detectButton);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user