778 lines
21 KiB
JavaScript
778 lines
21 KiB
JavaScript
(() => {
|
||
if (window.top !== window || window.__zncmPageAssistantLoaded) {
|
||
return;
|
||
}
|
||
window.__zncmPageAssistantLoaded = true;
|
||
|
||
const HOST_ID = "zncm-page-assistant-host";
|
||
const state = {
|
||
enabled: true,
|
||
taskPollingEnabled: true,
|
||
webBaseUrl: "",
|
||
open: false,
|
||
busy: false,
|
||
threadId: "",
|
||
pageUrl: location.href,
|
||
messages: [],
|
||
};
|
||
|
||
let host = null;
|
||
let shadow = null;
|
||
let ui = {};
|
||
|
||
function sendRuntimeMessage(message) {
|
||
return new Promise((resolve, reject) => {
|
||
chrome.runtime.sendMessage(message, (response) => {
|
||
const error = chrome.runtime.lastError;
|
||
if (error) {
|
||
reject(new Error(error.message));
|
||
return;
|
||
}
|
||
resolve(response || {});
|
||
});
|
||
});
|
||
}
|
||
|
||
function makeElement(tag, className = "", text = "") {
|
||
const element = document.createElement(tag);
|
||
if (className) {
|
||
element.className = className;
|
||
}
|
||
if (text) {
|
||
element.textContent = text;
|
||
}
|
||
return element;
|
||
}
|
||
|
||
function setStatus(message) {
|
||
if (ui.status) {
|
||
ui.status.textContent = message || "";
|
||
}
|
||
}
|
||
|
||
function setBusy(busy) {
|
||
state.busy = busy;
|
||
if (ui.send) ui.send.disabled = busy;
|
||
if (ui.input) ui.input.disabled = busy;
|
||
if (ui.panel) ui.panel.classList.toggle("is-busy", busy);
|
||
}
|
||
|
||
function resetThreadIfPageChanged() {
|
||
if (state.pageUrl === location.href) {
|
||
return;
|
||
}
|
||
state.pageUrl = location.href;
|
||
state.threadId = "";
|
||
state.messages = [];
|
||
renderMessages();
|
||
setStatus("页面已变化,下一次提问会重新读取当前页面。");
|
||
}
|
||
|
||
function destroyWidget() {
|
||
if (host) {
|
||
host.remove();
|
||
}
|
||
host = null;
|
||
shadow = null;
|
||
ui = {};
|
||
}
|
||
|
||
function renderMessages() {
|
||
if (!ui.messages) {
|
||
return;
|
||
}
|
||
ui.messages.replaceChildren();
|
||
if (state.messages.length === 0) {
|
||
const empty = makeElement("div", "empty");
|
||
const emptyTitle = makeElement("strong", "", "问问当前页面");
|
||
const emptyText = makeElement("span", "", "可以提问页面里的数据、表格、段落和结论。");
|
||
empty.append(emptyTitle, emptyText);
|
||
ui.messages.appendChild(empty);
|
||
return;
|
||
}
|
||
|
||
for (const message of state.messages) {
|
||
const item = makeElement("div", `message ${message.role}`);
|
||
const avatar = makeElement("div", "avatar", message.role === "user" ? "你" : "zn");
|
||
const bubble = makeElement("div", "bubble");
|
||
const label = makeElement("div", "message-label", message.role === "user" ? "你" : "zncm");
|
||
const body = makeElement("div", "message-body", message.text);
|
||
if (message.pending) {
|
||
body.classList.add("pending");
|
||
}
|
||
bubble.append(label, body);
|
||
item.append(avatar, bubble);
|
||
ui.messages.appendChild(item);
|
||
}
|
||
ui.messages.scrollTop = ui.messages.scrollHeight;
|
||
}
|
||
|
||
function setOpen(open) {
|
||
state.open = open;
|
||
if (!ui.panel || !ui.launcher) {
|
||
return;
|
||
}
|
||
ui.panel.classList.toggle("open", open);
|
||
ui.launcher.classList.toggle("open", open);
|
||
ui.launcher.setAttribute("aria-expanded", String(open));
|
||
if (open) {
|
||
resetThreadIfPageChanged();
|
||
setTimeout(() => ui.input?.focus(), 50);
|
||
}
|
||
}
|
||
|
||
async function ask(question) {
|
||
const normalized = String(question || "").trim();
|
||
if (!normalized || state.busy) {
|
||
return;
|
||
}
|
||
|
||
resetThreadIfPageChanged();
|
||
setBusy(true);
|
||
setStatus("正在读取页面并发送给 zncm...");
|
||
state.messages.push({ role: "user", text: normalized });
|
||
const pending = { role: "assistant", text: "zncm 正在理解当前页面...", pending: true };
|
||
state.messages.push(pending);
|
||
renderMessages();
|
||
ui.input.value = "";
|
||
|
||
try {
|
||
if (typeof deerExtractCurrentPage !== "function") {
|
||
throw new Error("页面读取脚本未加载,请刷新当前页面后重试。");
|
||
}
|
||
const page = deerExtractCurrentPage();
|
||
const response = await sendRuntimeMessage({
|
||
type: "zncm-page-agent-ask",
|
||
page,
|
||
question: normalized,
|
||
threadId: state.threadId || "",
|
||
});
|
||
if (!response.ok) {
|
||
throw new Error(response.message || "zncm 没有返回答案。");
|
||
}
|
||
state.threadId = response.thread_id || state.threadId;
|
||
pending.text = response.answer || "zncm 没有返回答案。";
|
||
pending.pending = false;
|
||
setStatus(response.truncated ? "已回答。页面内容较长,本次按配置做了截断。" : "已回答。");
|
||
} catch (error) {
|
||
pending.text = error instanceof Error ? error.message : "页面问答失败。";
|
||
pending.pending = false;
|
||
setStatus("本次问答失败。");
|
||
} finally {
|
||
setBusy(false);
|
||
renderMessages();
|
||
ui.input.focus();
|
||
}
|
||
}
|
||
|
||
function buildWidget() {
|
||
host = document.createElement("div");
|
||
host.id = HOST_ID;
|
||
document.documentElement.appendChild(host);
|
||
shadow = host.attachShadow({ mode: "open" });
|
||
|
||
const style = document.createElement("style");
|
||
style.textContent = `
|
||
:host {
|
||
all: initial;
|
||
color-scheme: light;
|
||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||
}
|
||
|
||
.launcher,
|
||
.panel {
|
||
position: fixed;
|
||
z-index: 2147483647;
|
||
font: 14px/1.45 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||
}
|
||
|
||
.launcher {
|
||
right: 20px;
|
||
bottom: 20px;
|
||
display: grid;
|
||
grid-template-columns: auto 1fr;
|
||
align-items: center;
|
||
gap: 8px;
|
||
height: 44px;
|
||
min-width: 112px;
|
||
border: 1px solid rgba(15, 118, 110, 0.28);
|
||
border-radius: 8px;
|
||
padding: 0 12px 0 10px;
|
||
background: #0f766e;
|
||
color: #fff;
|
||
box-shadow: 0 12px 34px rgba(15, 118, 110, 0.28), 0 2px 8px rgba(17, 24, 39, 0.12);
|
||
cursor: pointer;
|
||
font-weight: 750;
|
||
transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease;
|
||
}
|
||
|
||
.launcher:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 16px 40px rgba(15, 118, 110, 0.32), 0 4px 12px rgba(17, 24, 39, 0.14);
|
||
}
|
||
|
||
.launcher-mark {
|
||
display: grid;
|
||
place-items: center;
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 7px;
|
||
background: rgba(255, 255, 255, 0.18);
|
||
font-size: 12px;
|
||
letter-spacing: 0;
|
||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.launcher.open {
|
||
background: #115e59;
|
||
}
|
||
|
||
.panel {
|
||
right: 20px;
|
||
bottom: 72px;
|
||
width: min(430px, calc(100vw - 24px));
|
||
max-height: min(640px, calc(100vh - 96px));
|
||
display: none;
|
||
grid-template-rows: auto auto minmax(180px, 1fr) auto;
|
||
overflow: hidden;
|
||
border: 1px solid #d6d8dc;
|
||
border-radius: 8px;
|
||
background: #ffffff;
|
||
color: #171717;
|
||
box-shadow: 0 24px 70px rgba(17, 24, 39, 0.2), 0 1px 2px rgba(17, 24, 39, 0.08);
|
||
}
|
||
|
||
.panel.open {
|
||
display: grid;
|
||
animation: zncm-panel-in 160ms ease-out;
|
||
}
|
||
|
||
@keyframes zncm-panel-in {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(8px) scale(0.985);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0) scale(1);
|
||
}
|
||
}
|
||
|
||
.header {
|
||
display: grid;
|
||
gap: 10px;
|
||
padding: 14px;
|
||
background: linear-gradient(180deg, #f4fffc 0%, #ffffff 84%);
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.header-top {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
|
||
.brand {
|
||
display: flex;
|
||
align-items: center;
|
||
min-width: 0;
|
||
gap: 10px;
|
||
}
|
||
|
||
.brand-mark {
|
||
display: grid;
|
||
place-items: center;
|
||
width: 34px;
|
||
height: 34px;
|
||
flex: 0 0 auto;
|
||
border-radius: 8px;
|
||
background: #0f766e;
|
||
color: #fff;
|
||
font-weight: 800;
|
||
box-shadow: 0 8px 20px rgba(15, 118, 110, 0.18);
|
||
}
|
||
|
||
.brand-copy {
|
||
display: grid;
|
||
min-width: 0;
|
||
gap: 1px;
|
||
}
|
||
|
||
.brand-copy strong {
|
||
color: #111827;
|
||
font-size: 15px;
|
||
line-height: 1.25;
|
||
}
|
||
|
||
.brand-copy span {
|
||
overflow: hidden;
|
||
color: #6b7280;
|
||
font-size: 12px;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
flex: 0 0 auto;
|
||
gap: 6px;
|
||
}
|
||
|
||
.icon-button {
|
||
display: grid;
|
||
place-items: center;
|
||
width: auto;
|
||
min-width: 34px;
|
||
height: 30px;
|
||
padding: 0 8px;
|
||
border: 1px solid #dfe3e8;
|
||
border-radius: 7px;
|
||
background: #fff;
|
||
color: #374151;
|
||
cursor: pointer;
|
||
font: inherit;
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
|
||
}
|
||
|
||
.icon-button:hover {
|
||
border-color: #0f766e;
|
||
color: #0f766e;
|
||
background: #f0fdfa;
|
||
}
|
||
|
||
.page-chip {
|
||
display: grid;
|
||
grid-template-columns: auto 1fr;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
border: 1px solid #dbeafe;
|
||
border-radius: 8px;
|
||
padding: 8px 10px;
|
||
background: #f8fbff;
|
||
color: #334155;
|
||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.page-chip-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 999px;
|
||
background: #0f766e;
|
||
}
|
||
|
||
.page-chip-text {
|
||
overflow: hidden;
|
||
font-size: 12px;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.status {
|
||
min-height: 20px;
|
||
padding: 9px 14px;
|
||
border-bottom: 1px solid #f0f2f5;
|
||
color: #475569;
|
||
background: #fbfcfd;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.messages {
|
||
display: grid;
|
||
align-content: start;
|
||
gap: 12px;
|
||
overflow: auto;
|
||
padding: 14px;
|
||
background: linear-gradient(180deg, #f7f8fa 0%, #f3f6f8 100%);
|
||
}
|
||
|
||
.empty {
|
||
display: grid;
|
||
gap: 5px;
|
||
padding: 16px;
|
||
border: 1px solid #dfe7ed;
|
||
border-radius: 8px;
|
||
background: linear-gradient(180deg, #ffffff 0%, #fbfefd 100%);
|
||
color: #4b5563;
|
||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.04);
|
||
}
|
||
|
||
.empty strong {
|
||
color: #111827;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.empty span {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.message {
|
||
display: grid;
|
||
grid-template-columns: 28px minmax(0, 1fr);
|
||
gap: 8px;
|
||
align-items: start;
|
||
}
|
||
|
||
.message.user {
|
||
grid-template-columns: minmax(0, 1fr) 28px;
|
||
}
|
||
|
||
.message.user .avatar {
|
||
grid-column: 2;
|
||
}
|
||
|
||
.message.user .bubble {
|
||
grid-column: 1;
|
||
justify-self: end;
|
||
}
|
||
|
||
.avatar {
|
||
display: grid;
|
||
place-items: center;
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 8px;
|
||
background: #e6fffb;
|
||
color: #0f766e;
|
||
font-size: 11px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.message.user .avatar {
|
||
background: #111827;
|
||
color: #fff;
|
||
}
|
||
|
||
.bubble {
|
||
display: grid;
|
||
gap: 4px;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.message-label {
|
||
color: #6b7280;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.message.user .message-label {
|
||
text-align: right;
|
||
}
|
||
|
||
.message-body {
|
||
max-width: 330px;
|
||
padding: 10px 12px;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
color: #171717;
|
||
white-space: pre-wrap;
|
||
overflow-wrap: anywhere;
|
||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||
}
|
||
|
||
.message.user .message-body {
|
||
border-color: #0f172a;
|
||
background: #0f172a;
|
||
color: #fff;
|
||
}
|
||
|
||
.message-body.pending {
|
||
color: #6b7280;
|
||
}
|
||
|
||
.message-body.pending::after {
|
||
content: "";
|
||
display: inline-block;
|
||
width: 18px;
|
||
height: 4px;
|
||
margin-left: 6px;
|
||
border-radius: 999px;
|
||
background: linear-gradient(90deg, #0f766e 0 33%, transparent 33% 100%);
|
||
background-size: 9px 4px;
|
||
animation: zncm-thinking 900ms linear infinite;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
@keyframes zncm-thinking {
|
||
from {
|
||
background-position: 0 0;
|
||
}
|
||
to {
|
||
background-position: 18px 0;
|
||
}
|
||
}
|
||
|
||
.composer-wrap {
|
||
display: grid;
|
||
gap: 10px;
|
||
padding: 12px 14px 14px;
|
||
background: #fff;
|
||
border-top: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.quick {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 7px;
|
||
}
|
||
|
||
.quick button {
|
||
border: 1px solid #d6d8dc;
|
||
border-radius: 8px;
|
||
padding: 8px 10px;
|
||
background: #fbfcfd;
|
||
color: #374151;
|
||
cursor: pointer;
|
||
font: inherit;
|
||
font-size: 12px;
|
||
text-align: left;
|
||
transition: background 140ms ease, border-color 140ms ease, color 140ms ease, transform 140ms ease;
|
||
}
|
||
|
||
.quick button:hover {
|
||
border-color: #0f766e;
|
||
color: #0f766e;
|
||
background: #f0fdfa;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.composer {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto;
|
||
gap: 8px;
|
||
align-items: end;
|
||
}
|
||
|
||
.composer textarea {
|
||
min-height: 46px;
|
||
max-height: 130px;
|
||
resize: vertical;
|
||
border: 1px solid #cfd6dd;
|
||
border-radius: 8px;
|
||
padding: 10px 11px;
|
||
color: #171717;
|
||
background: #fbfcfd;
|
||
font: inherit;
|
||
outline: none;
|
||
}
|
||
|
||
.composer textarea:focus {
|
||
border-color: #0f766e;
|
||
box-shadow: 0 0 0 3px rgba(15, 118, 110, 0.12);
|
||
}
|
||
|
||
.composer button {
|
||
height: 42px;
|
||
border: 0;
|
||
border-radius: 8px;
|
||
padding: 0 14px;
|
||
background: #0f766e;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
font: inherit;
|
||
font-weight: 750;
|
||
box-shadow: 0 8px 18px rgba(15, 118, 110, 0.2);
|
||
transition: transform 140ms ease, background 140ms ease, box-shadow 140ms ease;
|
||
}
|
||
|
||
.composer button:hover {
|
||
background: #115e59;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 10px 22px rgba(15, 118, 110, 0.24);
|
||
}
|
||
|
||
.panel.is-busy .brand-mark,
|
||
.panel.is-busy .launcher-mark {
|
||
opacity: 0.82;
|
||
}
|
||
|
||
.composer button:disabled,
|
||
.quick button:disabled,
|
||
.icon-button:disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.launcher {
|
||
right: 12px;
|
||
bottom: 12px;
|
||
min-width: 96px;
|
||
}
|
||
|
||
.panel {
|
||
right: 12px;
|
||
bottom: 64px;
|
||
width: calc(100vw - 24px);
|
||
max-height: calc(100vh - 80px);
|
||
}
|
||
|
||
.message-body {
|
||
max-width: calc(100vw - 104px);
|
||
}
|
||
|
||
.quick {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
`;
|
||
|
||
const launcher = makeElement("button", "launcher");
|
||
launcher.type = "button";
|
||
launcher.title = "打开 zncm 页面问答";
|
||
launcher.setAttribute("aria-expanded", "false");
|
||
launcher.append(makeElement("span", "launcher-mark", "zn"), makeElement("span", "", "页面问答"));
|
||
|
||
const panel = makeElement("section", "panel");
|
||
const header = makeElement("header", "header");
|
||
const headerTop = makeElement("div", "header-top");
|
||
const brand = makeElement("div", "brand");
|
||
const brandCopy = makeElement("div", "brand-copy");
|
||
const title = makeElement("strong", "", "zncm 页面问答");
|
||
const subtitle = makeElement("span", "", "读取当前页面并回答");
|
||
brandCopy.append(title, subtitle);
|
||
brand.append(makeElement("div", "brand-mark", "zn"), brandCopy);
|
||
|
||
const actions = makeElement("div", "header-actions");
|
||
const settings = makeElement("button", "icon-button", "设置");
|
||
settings.type = "button";
|
||
settings.title = "打开插件设置";
|
||
const minimize = makeElement("button", "icon-button", "-");
|
||
minimize.type = "button";
|
||
minimize.title = "收起";
|
||
actions.append(settings, minimize);
|
||
headerTop.append(brand, actions);
|
||
|
||
const pageChip = makeElement("div", "page-chip");
|
||
const pageTitle = makeElement("span", "page-chip-text", document.title || location.hostname);
|
||
pageChip.append(makeElement("span", "page-chip-dot"), pageTitle);
|
||
header.append(headerTop, pageChip);
|
||
|
||
const status = makeElement("div", "status", "已连接当前页面。");
|
||
const messages = makeElement("div", "messages");
|
||
|
||
const composerWrap = makeElement("div", "composer-wrap");
|
||
const quick = makeElement("div", "quick");
|
||
for (const text of ["总结此页", "提取关键数据", "列出待办", "解释表格"]) {
|
||
const chip = makeElement("button", "", text);
|
||
chip.type = "button";
|
||
chip.addEventListener("click", () => ask(text));
|
||
quick.appendChild(chip);
|
||
}
|
||
|
||
const composer = makeElement("form", "composer");
|
||
const input = makeElement("textarea");
|
||
input.placeholder = "问当前页面...";
|
||
input.rows = 2;
|
||
const send = makeElement("button", "", "发送");
|
||
send.type = "submit";
|
||
composer.append(input, send);
|
||
composerWrap.append(quick, composer);
|
||
|
||
panel.append(header, status, messages, composerWrap);
|
||
shadow.append(style, panel, launcher);
|
||
|
||
ui = { launcher, panel, subtitle, pageTitle, status, messages, input, send };
|
||
renderMessages();
|
||
|
||
launcher.addEventListener("click", () => setOpen(!state.open));
|
||
minimize.addEventListener("click", () => setOpen(false));
|
||
settings.addEventListener("click", () => {
|
||
sendRuntimeMessage({ type: "zncm-open-options" }).catch((error) => {
|
||
setStatus(error instanceof Error ? error.message : "无法打开插件设置。");
|
||
});
|
||
});
|
||
composer.addEventListener("submit", (event) => {
|
||
event.preventDefault();
|
||
ask(input.value);
|
||
});
|
||
input.addEventListener("keydown", (event) => {
|
||
if (event.key === "Enter" && !event.shiftKey) {
|
||
event.preventDefault();
|
||
ask(input.value);
|
||
}
|
||
});
|
||
}
|
||
|
||
function ensureWidget() {
|
||
if (!state.enabled) {
|
||
destroyWidget();
|
||
return;
|
||
}
|
||
if (document.getElementById(HOST_ID)) {
|
||
return;
|
||
}
|
||
buildWidget();
|
||
}
|
||
|
||
async function loadSettings() {
|
||
const settings = await chrome.storage.sync.get(DEER_CLIPPER_DEFAULTS);
|
||
state.enabled = Boolean(settings.pageAssistantEnabled);
|
||
state.taskPollingEnabled = Boolean(settings.taskPollingEnabled);
|
||
state.webBaseUrl = deerNormalizeBaseUrl(settings.webBaseUrl || DEER_CLIPPER_DEFAULTS.webBaseUrl);
|
||
ensureWidget();
|
||
}
|
||
|
||
chrome.storage.onChanged.addListener((changes, areaName) => {
|
||
if (areaName !== "sync") {
|
||
return;
|
||
}
|
||
if (changes.pageAssistantEnabled) {
|
||
state.enabled = Boolean(changes.pageAssistantEnabled.newValue);
|
||
ensureWidget();
|
||
if (state.enabled) {
|
||
setStatus("页面问答框已开启。");
|
||
}
|
||
}
|
||
if (changes.taskPollingEnabled) {
|
||
state.taskPollingEnabled = Boolean(changes.taskPollingEnabled.newValue);
|
||
}
|
||
if (changes.webBaseUrl) {
|
||
state.webBaseUrl = deerNormalizeBaseUrl(changes.webBaseUrl.newValue || DEER_CLIPPER_DEFAULTS.webBaseUrl);
|
||
}
|
||
});
|
||
|
||
function isZncmPage() {
|
||
const appPath = `${location.pathname}${location.hash}`.toLowerCase();
|
||
if (
|
||
appPath.includes("/page/workspace") ||
|
||
appPath.includes("/chats/") ||
|
||
appPath.includes("#/login")
|
||
) {
|
||
return true;
|
||
}
|
||
try {
|
||
const configured = new URL(state.webBaseUrl);
|
||
return configured.origin === location.origin;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// MV3 service workers may be suspended between their short polling timers,
|
||
// while chrome.alarms cannot reliably run more often than every 30 seconds.
|
||
// A visible zncm tab provides a cheap wake-up signal so browser_act requests
|
||
// are normally claimed within the backend/model timeout window.
|
||
setInterval(() => {
|
||
if (!state.taskPollingEnabled || !isZncmPage()) {
|
||
return;
|
||
}
|
||
chrome.runtime.sendMessage({ type: "zncm-poll-now" }).catch(() => {});
|
||
}, 3000);
|
||
|
||
setInterval(() => {
|
||
if (!state.enabled || !ui.pageTitle) {
|
||
return;
|
||
}
|
||
if (state.pageUrl !== location.href) {
|
||
resetThreadIfPageChanged();
|
||
}
|
||
ui.pageTitle.textContent = document.title || location.hostname;
|
||
}, 1200);
|
||
|
||
void loadSettings();
|
||
})();
|