diff --git a/README.md b/README.md index 5528ff8..1c76474 100644 --- a/README.md +++ b/README.md @@ -106,8 +106,9 @@ O1KEY_ASYNC_API_BASE_URL=https://your-async-api-domain.com 1. 点击 ComfyUI 左侧工具栏中“令牌管理”下方的“更新”按钮。 2. 等待版本检查。如果发现新版本,确认后等待更新完成;需要时可在面板中重新检查。 -3. 按完成提示重启 ComfyUI。如果面板提示需要技术支持,请联系维护人员。 -4. 刷新页面后,左侧“更新”下方会出现“检测”按钮,可用来确认本次版本已加载。 +3. 更新完成后,ComfyUI 会自动重启并刷新页面。如果面板提示需要技术支持,请联系维护人员完成配置后再重启。 + +> 首次从旧版本更新到支持自动重启的版本时,请按旧版面板提示手动重启一次;之后的更新会自动重启。 > 如果当前安装包含自定义修改,面板会停止更新并保留现有内容。此时请联系维护人员处理。 diff --git a/docs/architecture.md b/docs/architecture.md index bc6ab8e..5d3f6fd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -109,9 +109,10 @@ 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, 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. +`web/js/o1keyUpdateButton.js` places an Update button directly below the Token Manager button in the left toolbar, before Restart. 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. After a successful update with unchanged requirements, the frontend invokes the shared `web/js/o1keyRestart.js` flow, waits for a new process boot ID and a ready system endpoint, then reloads the page. A requirements change still asks for maintenance before restart; an automatic restart failure leaves the manual Restart button available. 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. +Because an update runs in the old frontend and server process, installations upgrading from a version without automatic restart must restart manually once to load this behavior. Because the directory is auto-loaded, unused or experimental JavaScript must not be left here. diff --git a/tests/test_o1key_restart_frontend.mjs b/tests/test_o1key_restart_frontend.mjs new file mode 100644 index 0000000..1497431 --- /dev/null +++ b/tests/test_o1key_restart_frontend.mjs @@ -0,0 +1,83 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import vm from "node:vm"; + +const source = fs.readFileSync(new URL("../web/js/o1keyRestart.js", import.meta.url), "utf8") + .replace(/^export /gm, ""); +const requests = []; +const responses = []; +const reloads = []; +const attributes = new Map(); +const icon = { className: "pi pi-refresh side-bar-button-icon" }; +const label = { textContent: "重启" }; +const button = { + disabled: false, + setAttribute(name, value) { attributes.set(name, value); }, + querySelector(selector) { + return selector === ".side-bar-button-icon" ? icon : label; + }, +}; +const document = { + querySelector(selector) { + assert.equal(selector, "#o1key-restart-button"); + return button; + }, +}; +const window = { + location: { + href: "http://127.0.0.1:8188/?workflow=test", + replace(url) { reloads.push(url); }, + }, + setTimeout() { return 1; }, + clearTimeout() {}, +}; +const reply = (data, status = 200) => ({ + ok: status < 400, + status, + async json() { return data; }, +}); +async function fetch(url, options) { + requests.push({ url, options }); + const next = responses.shift(); + assert.ok(next, `Unexpected request: ${url}`); + return next; +} +const context = vm.createContext({ document, window, fetch, AbortController, URL, Date }); +vm.runInContext(source, context); + +responses.push( + reply({ ready: true, boot_id: "old" }), + reply({ error: "restart rejected" }, 500), +); +await assert.rejects(context.startComfyUIRestart(), /restart rejected/); +assert.equal(reloads.length, 0); +assert.equal(button.disabled, false); +assert.equal(attributes.get("aria-busy"), "false"); +assert.equal(label.textContent, "重启"); +assert.match(icon.className, /pi-refresh/); +requests.length = 0; + +responses.push( + reply({ ready: true, boot_id: "old" }), + reply({ success: true, boot_id: "old" }), + reply({ ready: true, boot_id: "new" }), + reply({}), +); +await context.startComfyUIRestart(); +assert.equal(requests.length, 4); +assert.match(requests[0].url, /^\/o1key\/restart\/status\?/); +assert.equal(requests[1].url, "/o1key/restart"); +assert.equal(requests[1].options.method, "POST"); +assert.equal(requests[1].options.body, "{}"); +assert.match(requests[2].url, /^\/o1key\/restart\/status\?/); +assert.match(requests[3].url, /^\/api\/system_stats\?/); +assert.equal(reloads.length, 1); +const reloadedUrl = new URL(reloads[0]); +assert.equal(reloadedUrl.searchParams.get("workflow"), "test"); +assert.ok(reloadedUrl.searchParams.has("o1key_restart")); +assert.equal(button.disabled, true); +assert.equal(attributes.get("aria-busy"), "true"); +assert.equal(label.textContent, "重启中"); +await assert.rejects(context.startComfyUIRestart(), /正在重启/); +assert.equal(requests.length, 4); +assert.equal(reloads.length, 1); diff --git a/tests/test_o1key_update_button.mjs b/tests/test_o1key_update_button.mjs index 78dc154..815452e 100644 --- a/tests/test_o1key_update_button.mjs +++ b/tests/test_o1key_update_button.mjs @@ -96,7 +96,8 @@ let observer; const requests = []; const responses = []; let fetchFailure = false; -let dismissNotice; +let restartCalls = 0; +let restartFailure = false; const reply = (body, status = 200) => ({ ok: status < 400, status, async json() { return body; } }); const app = { registerExtension(value) { extension = value; } }; const api = { @@ -111,11 +112,11 @@ class MutationObserver { observe() {} disconnect() { this.disconnected = true; } } -const window = { - setTimeout(callback) { dismissNotice = callback; return 1; }, - clearTimeout() { dismissNotice = null; }, -}; -vm.runInNewContext(source, { app, api, document, window, MutationObserver }); +async function startComfyUIRestart() { + restartCalls++; + if (restartFailure) throw new Error("restart failed"); +} +vm.runInNewContext(source, { app, api, document, startComfyUIRestart, MutationObserver }); extension.setup(); assert.ok(observer); @@ -135,29 +136,15 @@ 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], detect); -assert.equal(toolbar.children[3], restart); +assert.equal(toolbar.children[2], 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.equal(document.querySelector("#o1key-detect-button"), null); assert.ok(observer.disconnected); extension.setup(); -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); +assert.equal(toolbar.children.length, 3); responses.push(reply({ update_available: false })); update.listeners.get("click")(); @@ -212,22 +199,34 @@ await new Promise(setImmediate); assert.equal(dialog.open, true); assert.equal(dialog.dataset.state, "available"); -responses.push(reply({ updated: true, version: "abc1234", requirements_changed: false })); +responses.push(reply({ updated: true, version: "abc1234", requirements_changed: true })); await primary.listeners.get("click")(); assert.equal(requests.at(-1).path, "/o1key/update"); assert.equal(requests.at(-1).options.method, "POST"); assert.equal(requests.at(-1).options.headers["X-O1Key-Update"], "1"); assert.match(status.textContent, /更新完成/); -assert.match(suggestion.textContent, /重启 ComfyUI/); +assert.match(suggestion.textContent, /联系技术支持/); +assert.doesNotMatch(suggestion.textContent, /requirements\.txt/); assert.equal(primary.textContent, "完成"); assert.equal(dialog.dataset.state, "success"); +assert.equal(restartCalls, 0); responses.push(reply({ update_available: true })); await dialog.checkForUpdate(); -responses.push(reply({ updated: true, requirements_changed: true })); +restartFailure = true; +responses.push(reply({ updated: true, requirements_changed: false })); await primary.listeners.get("click")(); -assert.match(suggestion.textContent, /联系技术支持/); -assert.doesNotMatch(suggestion.textContent, /requirements\.txt/); +assert.equal(restartCalls, 1); +assert.equal(dialog.dataset.state, "restart_failed"); +assert.match(status.textContent, /自动重启失败/); +assert.match(suggestion.textContent, /左侧“重启”按钮/); +assert.equal(primary.textContent, "关闭"); +await primary.listeners.get("click")(); +assert.equal(dialog.open, false); +responses.push(reply({ update_available: true })); +update.listeners.get("click")(); +await new Promise(setImmediate); +assert.equal(dialog.dataset.state, "available"); const requestsBeforeError = requests.length; responses.push(reply({ code: "local_changes", error: "插件目录有未提交的代码修改。", suggestion: "请先提交" }, 409)); @@ -254,3 +253,19 @@ assert.equal(primary.disabled, false); assert.equal(head.children.length, 1); header.children[2].listeners.get("click")(); assert.equal(dialog.open, false); + +fetchFailure = false; +restartFailure = false; +responses.push(reply({ update_available: true })); +update.listeners.get("click")(); +await new Promise(setImmediate); +responses.push(reply({ updated: true, requirements_changed: false })); +await primary.listeners.get("click")(); +assert.equal(restartCalls, 2); +assert.equal(dialog.dataset.state, "restarting"); +assert.match(status.textContent, /正在重启 ComfyUI/); +assert.match(suggestion.textContent, /页面会自动刷新/); +assert.equal(primary.disabled, true); +assert.equal(secondary.disabled, true); +assert.equal(header.children[2].disabled, true); +assert.equal(primary.textContent, "重启中…"); diff --git a/web/js/o1keyApiSettings.js b/web/js/o1keyApiSettings.js index 7bc04ae..8b5ac1d 100644 --- a/web/js/o1keyApiSettings.js +++ b/web/js/o1keyApiSettings.js @@ -1,10 +1,8 @@ import { app } from "../../../scripts/app.js"; +import { startComfyUIRestart } from "./o1keyRestart.js"; const DIALOG_ID = "o1key-api-settings-dialog"; const STYLE_ID = "o1key-api-settings-styles"; -const RESTART_STATUS_URL = "/o1key/restart/status"; -const RESTART_TIMEOUT_MS = 120_000; -let restartInProgress = false; function ensureStyles() { if (document.querySelector(`#${STYLE_ID}`)) return; @@ -98,106 +96,11 @@ async function readJson(response) { return data; } -function delay(milliseconds) { - return new Promise((resolve) => window.setTimeout(resolve, milliseconds)); -} - -async function fetchWithTimeout(url, options = {}, timeout = 2_500) { - const controller = new AbortController(); - const timer = window.setTimeout(() => controller.abort(), timeout); - try { - return await fetch(url, { - ...options, - cache: "no-store", - signal: controller.signal, - }); - } finally { - window.clearTimeout(timer); - } -} - -async function getRestartStatus() { - const separator = RESTART_STATUS_URL.includes("?") ? "&" : "?"; - const response = await fetchWithTimeout( - `${RESTART_STATUS_URL}${separator}_=${Date.now()}` - ); - return readJson(response); -} - -function setRestartButtonState(button, busy) { - button.disabled = busy; - button.setAttribute("aria-busy", String(busy)); - button.setAttribute("aria-label", busy ? "ComfyUI 正在重启" : "重启"); - button.setAttribute("title", busy ? "ComfyUI 正在重启" : "重启"); - - const icon = button.querySelector(".side-bar-button-icon"); - if (icon) { - icon.className = busy - ? "pi pi-spinner pi-spin side-bar-button-icon" - : "pi pi-refresh side-bar-button-icon"; - } - - const label = button.querySelector(".side-bar-button-label"); - if (label) label.textContent = busy ? "重启中" : "重启"; -} - -async function waitForRestart(oldBootId) { - const deadline = Date.now() + RESTART_TIMEOUT_MS; - - while (Date.now() < deadline) { - try { - const status = await getRestartStatus(); - if (status.ready && status.boot_id && status.boot_id !== oldBootId) { - const systemStatus = await fetchWithTimeout( - `/api/system_stats?_=${Date.now()}` - ); - if (systemStatus.ok) return; - } - } catch { - // 旧进程退出到新进程监听端口之间,接口暂时不可用是正常状态。 - } - await delay(1_000); - } - - throw new Error("等待 ComfyUI 重启超时,请查看终端中的错误信息。"); -} - -function reloadAfterRestart() { - const url = new URL(window.location.href); - url.searchParams.set("o1key_restart", Date.now().toString()); - window.location.replace(url.toString()); -} - -async function restartComfyUI(event) { - if (restartInProgress) return; +async function restartComfyUI() { if (!window.confirm("确定要重启 ComfyUI 吗?当前正在执行的任务会被中断。")) return; - - const button = event.currentTarget; - restartInProgress = true; - setRestartButtonState(button, true); - try { - let currentStatus = null; - try { - currentStatus = await getRestartStatus(); - } catch { - // POST 响应还会返回旧进程的 boot_id,因此预检失败不阻断重启。 - } - - const response = await fetchWithTimeout("/o1key/restart", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: "{}", - }, 5_000); - const result = await readJson(response); - const oldBootId = result.boot_id || currentStatus?.boot_id; - if (!oldBootId) throw new Error("无法确认当前 ComfyUI 进程标识。"); - - await waitForRestart(oldBootId); - reloadAfterRestart(); + await startComfyUIRestart(); } catch (error) { - restartInProgress = false; - setRestartButtonState(button, false); window.alert(error?.message || "ComfyUI 重启失败,请查看终端中的错误信息。"); } } diff --git a/web/js/o1keyRestart.js b/web/js/o1keyRestart.js new file mode 100644 index 0000000..a8cf49c --- /dev/null +++ b/web/js/o1keyRestart.js @@ -0,0 +1,115 @@ +const RESTART_STATUS_URL = "/o1key/restart/status"; +const RESTART_TIMEOUT_MS = 120_000; +let restartInProgress = false; + +async function readJson(response) { + const data = await response.json().catch(() => ({})); + if (!response.ok) throw new Error(data.error || `请求失败 (${response.status})`); + return data; +} + +function delay(milliseconds) { + return new Promise((resolve) => window.setTimeout(resolve, milliseconds)); +} + +async function fetchWithTimeout(url, options = {}, timeout = 2_500) { + const controller = new AbortController(); + const timer = window.setTimeout(() => controller.abort(), timeout); + try { + return await fetch(url, { + ...options, + cache: "no-store", + signal: controller.signal, + }); + } finally { + window.clearTimeout(timer); + } +} + +async function getRestartStatus() { + const separator = RESTART_STATUS_URL.includes("?") ? "&" : "?"; + const response = await fetchWithTimeout( + `${RESTART_STATUS_URL}${separator}_=${Date.now()}` + ); + return readJson(response); +} + +function setRestartButtonState(button, busy) { + if (!button) return; + button.disabled = busy; + button.setAttribute("aria-busy", String(busy)); + button.setAttribute("aria-label", busy ? "ComfyUI 正在重启" : "重启"); + button.setAttribute("title", busy ? "ComfyUI 正在重启" : "重启"); + + const icon = button.querySelector(".side-bar-button-icon"); + if (icon) { + icon.className = busy + ? "pi pi-spinner pi-spin side-bar-button-icon" + : "pi pi-refresh side-bar-button-icon"; + } + + const label = button.querySelector(".side-bar-button-label"); + if (label) label.textContent = busy ? "重启中" : "重启"; +} + +async function waitForRestart(oldBootId) { + const deadline = Date.now() + RESTART_TIMEOUT_MS; + + while (Date.now() < deadline) { + try { + const status = await getRestartStatus(); + if (status.ready && status.boot_id && status.boot_id !== oldBootId) { + const systemStatus = await fetchWithTimeout( + `/api/system_stats?_=${Date.now()}` + ); + if (systemStatus.ok) return; + } + } catch { + // 旧进程退出到新进程监听端口之间,接口暂时不可用是正常状态。 + } + await delay(1_000); + } + + throw new Error("等待 ComfyUI 重启超时,请查看终端中的错误信息。"); +} + +function reloadAfterRestart() { + const url = new URL(window.location.href); + url.searchParams.set("o1key_restart", Date.now().toString()); + window.location.replace(url.toString()); +} + +export async function startComfyUIRestart() { + if (restartInProgress) throw new Error("ComfyUI 正在重启,请稍候。"); + restartInProgress = true; + let reloadRequested = false; + const button = document.querySelector("#o1key-restart-button"); + setRestartButtonState(button, true); + + try { + let currentStatus = null; + try { + currentStatus = await getRestartStatus(); + } catch { + // POST 响应还会返回旧进程的 boot_id,因此预检失败不阻断重启。 + } + + const response = await fetchWithTimeout("/o1key/restart", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: "{}", + }, 5_000); + const result = await readJson(response); + const oldBootId = result.boot_id || currentStatus?.boot_id; + if (!oldBootId) throw new Error("无法确认当前 ComfyUI 进程标识。"); + + await waitForRestart(oldBootId); + reloadAfterRestart(); + reloadRequested = true; + } catch (error) { + setRestartButtonState(button, false); + throw error; + } finally { + if (!reloadRequested) restartInProgress = false; + } +} diff --git a/web/js/o1keyUpdateButton.js b/web/js/o1keyUpdateButton.js index 16cbad0..f731297 100644 --- a/web/js/o1keyUpdateButton.js +++ b/web/js/o1keyUpdateButton.js @@ -1,13 +1,11 @@ import { app } from "../../../scripts/app.js"; import { api } from "../../../scripts/api.js"; +import { startComfyUIRestart } from "./o1keyRestart.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: ["当前安装包含自定义修改,暂时无法自动更新。", "请联系技术支持处理,以免丢失现有内容。"], @@ -207,26 +205,6 @@ 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; } @@ -328,15 +306,17 @@ function createUpdateDialog() { function setState(state) { dialog.dataset.state = state; - const busy = state === "checking" || state === "updating"; + const busy = state === "checking" || state === "updating" || state === "restarting"; primary.disabled = busy; - close.disabled = state === "updating"; - secondary.disabled = state === "updating"; + close.disabled = state === "updating" || state === "restarting"; + secondary.disabled = state === "updating" || state === "restarting"; primary.textContent = { checking: "检查中…", available: "立即更新", updating: "更新中…", + restarting: "重启中…", success: "完成", + restart_failed: "关闭", }[state] || "重新检查"; secondary.textContent = state === "available" ? "稍后再说" : "关闭"; } @@ -414,14 +394,19 @@ function createUpdateDialog() { return; } if (result.updated) { + if (!result.requirements_changed) { + setState("restarting"); + show("更新完成,正在重启 ComfyUI…", "重启完成后页面会自动刷新,请稍候。", "busy"); + try { + await startComfyUIRestart(); + } catch { + setState("restart_failed"); + show("更新完成,但自动重启失败。", "请点击左侧“重启”按钮;如果问题持续,请联系技术支持。", "error"); + } + return; + } setState("success"); - show( - "更新完成。", - result.requirements_changed - ? "新版本还需要维护人员完成配置,请联系技术支持后重启 ComfyUI。" - : "请重启 ComfyUI,并刷新页面以使用新版本。", - "success", - ); + show("更新完成。", "新版本还需要维护人员完成配置,请联系技术支持后重启 ComfyUI。", "success"); } else { setState("current"); show("已是最新版本。", "无需更新。", "success"); @@ -433,11 +418,11 @@ function createUpdateDialog() { } primary.addEventListener("click", () => { if (dialog.dataset.state === "available") return startUpdate(); - if (dialog.dataset.state === "success") return dialog.close(); + if (dialog.dataset.state === "success" || dialog.dataset.state === "restart_failed") return dialog.close(); return checkForUpdate(); }); dialog.addEventListener("cancel", (event) => { - if (dialog.dataset.state === "updating") event.preventDefault(); + if (dialog.dataset.state === "updating" || dialog.dataset.state === "restarting") event.preventDefault(); }); dialog.checkForUpdate = checkForUpdate; @@ -458,29 +443,6 @@ 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; @@ -506,26 +468,6 @@ 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; }