您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
只需输入一次问题,就能自动去各家大模型网页提问,免去手动粘贴问题到其他网页、并苦苦等待的麻烦。支持范围:DeepSeek,Kimi,ChatGPT,通义千问,豆包,ChatGPT(zchat原生界面版)
// ==UserScript== // @name 多家大模型网页同时回答 // @namespace http://tampermonkey.net/ // @version 1.2.8 // @description 只需输入一次问题,就能自动去各家大模型网页提问,免去手动粘贴问题到其他网页、并苦苦等待的麻烦。支持范围:DeepSeek,Kimi,ChatGPT,通义千问,豆包,ChatGPT(zchat原生界面版) // @author interest2 // @match https://www.kimi.com/* // @match https://chat.deepseek.com/* // @match https://www.tongyi.com/* // @match https://chatgpt.com/* // @match https://www.doubao.com/* // @match https://chat.zchat.tech/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @connect www.ratetend.com // @license GPL-3.0-only // ==/UserScript== (function () { 'use strict'; console.log("ai script, start"); // 面板的显示、隐藏快捷键设置 const PANEL_DISPLAY = 'ctrl+q'; const PANEL_DISPLAY_ON_LOAD = 'ctrl+shift+q'; const MAX_QUEUE = 20; // 历史对话的记忆数量 const GPT_GAP = 2000; // ChatGPT的轮询间隔(毫秒) const BASIC_GAP = 5000; // 其他网站兜底轮询间隔(毫秒) const MAX_PLAIN = 50; // localStorage存储的问题原文的最大长度。超过则存哈希 const HASH_LEN = 16; // 问题的哈希长度 const checkGap = 100; const maxRetries = 150; let testLocalFlag = 1; // 存储时的特征词 const T = "tool-"; const QUEUE = "tool-queue"; const LEN = "len"; const LAST_Q = "lastQ"; const UID_KEY = "uid"; const DEFAULT_DISPLAY_KEY = "defaultDisplay"; let DOMAIN = "https://www.ratetend.com:5001"; let userid = getGV("userid"); if(isEmpty(userid)){ userid = guid(); setGV("userid", userid); // 本地调试用,连接本地服务器 }else if(testLocalFlag === 1 && userid === "e4c2d93c-7e5f-49c8-aea69"){ DOMAIN = "http://localhost:5021"; } let MAIN_SITE = 0; let site = 0; let url = window.location.href; const keywords = { "kimi": 0, "deepseek": 1, "tongyi": 2, "chatgpt": 3, "doubao": 4, "zchat": 5 }; // 根据当前网址关键词,设置site值 for (const keyword in keywords) { if (url.indexOf(keyword) > -1) { site = keywords[keyword]; break; } } // 各家大模型的网址(新对话,历史对话的前缀) const webSites = { 0: ["https://www.kimi.com/", "chat/"], 1: ["https://chat.deepseek.com/", "a/chat/s/"], 2: ["https://www.tongyi.com/", "?sessionId="], 3: ["https://chatgpt.com/", "c/"], 4: ["https://www.doubao.com/chat", "/"], 5: ["https://chat.zchat.tech/", "c/"] }; const newSites = Object.fromEntries( Object.entries(webSites).map(([key, [baseUrl]]) => [key, baseUrl]) ); const historySites = Object.fromEntries( Object.entries(webSites).map(([key, [baseUrl, suffix]]) => [key, baseUrl + suffix]) ); let pat0 = "[0-9a-z]{20}"; let pat1 = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; let pat2 = "[0-9a-f]{32}"; let pat3 = "[0-9a-f]{16}"; const pattern ={ 0: pat0, 1: pat1, 2: pat2, 3: pat1, 4: pat3, 5: pat1 } // 面板样式 const panelStyle = ` position: fixed; right: 20px; bottom: 20px; max-height: 300px; background: white; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); z-index: 99999999; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; `; const panelCompact = ` min-width: 120px; `; const itemStyle = ` display: flex; align-items: center; padding: 5px 0; border-bottom: 1px solid #eee; `; const wordSpanStyle = ` flex: 1; margin-right: 10px; font-size: 14px; `; const checkboxStyle = ` margin-right: 10px; font-size: 20px; `; const emptyMessage = ` padding: 10px; text-align: center; color: #888; font-size: 14px; `; const hintStyle =` color: #275fe6; `; // 面板数据 const CHOSEN_SITE = "chosenSite"; const panel = document.createElement('div'); const contentContainer = document.createElement('div'); let panelDelay = site === 5 ? 500 : 50; // 模式切换相关变量 let isCompactMode = false; let originalHTML = contentContainer.innerHTML; const wordConfig = [ { site: 1, word: 'DeepSeek', alias: 'DS'}, { site: 0, word: 'Kimi', alias: 'Kimi' }, { site: 5, word: 'ChatGPT (zchat)', alias: 'zchat' }, { site: 3, word: 'ChatGPT (官网)', alias: 'chat' }, { site: 2, word: '通义千问', alias: '通义' }, { site: 4, word: '豆包', alias: '豆包' } ]; // 生成映射 const wordToSite = {}; const siteToWord = {}; const siteToAlias = {}; const wordToAlias = {}; const words = []; wordConfig.forEach(({ site, word, alias }) => { words.push(word); wordToSite[word] = site; siteToWord[site] = word; siteToAlias[site] = alias; wordToAlias[word] = alias; }); // 从url提取各大模型网站的对话唯一标识 function getChatId(){ let url = getUrl(); if(isEmpty(url)){ return ""; } if(site === 4 && url.indexOf("local") > -1){ return ""; } const regex = new RegExp(pattern[site]); let ret = url.match(regex); if(isEmpty(ret)){ return ""; } return ret[0]; } function getUrl(){ return window.location.href; } // 给receive方法整体加锁 let receiveLock = false; // 给发送环节加锁。能否不要这个锁?不能,因为send环节是异步轮询,receive解锁时send未必轮询结束 let sendLock = false; let questionBeforeJump = getS("questionBeforeJump"); if(!isEmpty(questionBeforeJump)){ console.log("页面刚打开,处理跳转信息"); receiveNew(); } setInterval(function(){ masterCheckNew(); }, 1000); let receiveGap = site === 3 ? GPT_GAP :BASIC_GAP; // 当SSE异常,轮询兜底 setInterval(function(){ let sites = getSitesOfStorage(); if(sites.includes(site)){ receiveNew("轮询线程"); } }, receiveGap); // SSE事件 if (typeof(EventSource) === "undefined") { console.error("This browser does not support Server-Sent Events."); return; } const sseUrl = DOMAIN + "/create?sourceId=" + site + "&userid=" + userid; let source; // chatGPT不支持连接到其他站点 if(site !== 3){ let eventSource; let retryCount = 0; const maxAttempts = 20; function createEventSource() { if (eventSource) { eventSource.close(); // 关闭旧连接 } eventSource = new EventSource(sseUrl); const onOpenHandler = () => { console.log("[SSE] 连接已建立", new Date()); retryCount = 0; // 重置重试计数器 }; const onInitHandler = (event) => { console.log("[SSE] 初始化:", event.data); let sites = getSitesOfStorage(); if(sites.includes(site)){ receiveNew("SSE初始化"); } }; const onBroadcastHandler = (event) => { console.log("[SSE] 广播:", event.data, new Date()); receiveNew(); }; const onErrorHandler = () => { eventSource.removeEventListener('open', onOpenHandler); eventSource.removeEventListener('init', onInitHandler); eventSource.removeEventListener('broadcast', onBroadcastHandler); eventSource.onerror = null; eventSource.onmessage = null; handleConnectionError(); }; eventSource.onopen = onOpenHandler; eventSource.addEventListener("init", onInitHandler); eventSource.onmessage = (event) => console.log('Message:', event.data); eventSource.addEventListener("broadcast", onBroadcastHandler); eventSource.onerror = onErrorHandler; } function handleConnectionError() { if (retryCount >= maxAttempts) { console.error('Exceeded maximum reconnect attempts.'); retryCount = 0; if (eventSource) { eventSource.close(); } return; } retryCount++; let gap = 1000; if(retryCount > 6){ gap = 1000 * (2**(retryCount - 3) + getRand(1, 2**(retryCount - 4))) }else if(retryCount > 2){ gap = 5000 + getRand(1, 5) * 1000;; } console.log("连接异常,", gap/1000, "秒后,第", retryCount, "次尝试重连,当前", new Date()); setTimeout(() => { createEventSource(); }, gap); } createEventSource(); } // 发送端 function masterCheckNew(){ if(sendLock){ return; } let masterId = getChatId(); if(isEmpty(masterId)){ return; } let questions = getQuestionList(); let lenNext = questions.length; if(lenNext > 0){ let len = hgetS(T + masterId, LEN) || 0; // console.log("lenNext: "+lenNext+", len: "+len); if(lenNext > len){ let lastestQ = questions[lenNext - 1].textContent; let lastQuestion = hgetS(T + masterId, LAST_Q); if(!isEmpty(lastQuestion) && isEqual(lastestQ, lastQuestion)){ return; } masterReq(masterId, lastestQ); hsetS(T + masterId, LEN, lenNext); } } }; function masterReq(masterId, lastestQ){ let uid = hgetS(T + masterId, UID_KEY); if(isEmpty(uid)){ uid = guid(); hsetS(T + masterId, UID_KEY, uid); } let msg = { uid: uid, question: lastestQ }; console.log(msg); setGV("msg", msg); hsetS(T + masterId, LAST_Q, getQuesOrHash(lastestQ)); let uidJson = getGV(uid); // 若json非空,则其中一定有首次提问的主节点的信息; // 故json若空则必为首次,只有首次会走如下逻辑 if(isEmpty(uidJson)){ uidJson = {}; uidJson[site] = masterId; console.log("master print uidJson: "+JSON.stringify(uidJson)); setGV(uid, uidJson); // 存储管理(删除与添加) dequeue(); enqueue(masterId); } updateStorageSites(); let remoteUrl = DOMAIN + "/masterQ"; let data = { "userid": userid, "sites": getSitesExcludeCurrent() }; GM_xmlhttpRequest({ method: "POST", url: remoteUrl, data: JSON.stringify(data), headers: { "Content-Type": "application/json" }, onload: function(response) { // console.log(response.responseText); }, onerror: function(error) { console.error('请求失败:', error); } }); } function receiveNew(triggerSource){ if(receiveLock){ return; } let msg = getGV("msg"); if(isEmpty(msg)){ return; } receiveLock = true; if(sendLock){ receiveLock = false; return; } let curSlaveId = getChatId(); let question = msg.question; let sameQuestion = false; if(!isEmpty(curSlaveId)){ let lastQuestion = hgetS(T + curSlaveId, LAST_Q); sameQuestion = !isEmpty(lastQuestion) && isEqual(question, lastQuestion); // console.log("question: "+question+", lastQuestion: "+lastQuestion); if(sameQuestion){ receiveLock = false; return; } } let questionBeforeJump = getS("questionBeforeJump"); // 如果是经跳转而来,无需处理主节点信息,直接从缓存取对话内容 if(!isEmpty(questionBeforeJump)){ console.log("questionBeforeJump: " + questionBeforeJump); questionBeforeJump = JSON.parse(questionBeforeJump); let cachedQuestion = questionBeforeJump[0]; let cachedUid = questionBeforeJump[1]; let cachedSlaveId = ""; if(!isEmpty(curSlaveId)){ cachedSlaveId = questionBeforeJump[2]; if(curSlaveId !== cachedSlaveId){ receiveLock = false; return; } hsetS(T + curSlaveId, LAST_Q, getQuesOrHash(cachedQuestion)); } // 清空跳转用的缓存 setS("questionBeforeJump", ""); console.log(new Date() + "h1 send"); abstractSend(cachedQuestion, cachedSlaveId); if(isEmpty(curSlaveId)){ setUid(cachedUid, cachedQuestion); } receiveLock = false; return; } let uid = msg.uid; let targetUrl = ""; let slaveIdFlag = false; let slaveId = ""; let uidJson = getGV(uid); let lastQuestionOfComingSlaveId = ""; // 来者消息的uid,是否关联了从节点的chatId? if(!isEmpty(uidJson)){ // console.log("uidJson " + JSON.stringify(uidJson)); slaveId = uidJson[site]; if(!isEmpty(slaveId)){ lastQuestionOfComingSlaveId = hgetS(T + slaveId, LAST_Q); // console.log("lastQuestionOfComingSlaveId "+lastQuestionOfComingSlaveId); if(isEqual(question, lastQuestionOfComingSlaveId)){ receiveLock = false; return; } slaveIdFlag = true; } } let curIdFlag = !isEmpty(curSlaveId); // 从节点已进行过来者的uid对应的对话 if(slaveIdFlag){ // 当前页面有chatId if(curIdFlag){ // chatId相同则对话,不同则跳转 if(curSlaveId === slaveId){ if(!isEmpty(triggerSource)){ console.log("触发者:", triggerSource); } console.log("h2 send", new Date()); hsetS(T + curSlaveId, LAST_Q, getQuesOrHash(question)); abstractSend(question, curSlaveId); }else{ targetUrl = historySites[site] + slaveId; } // 当前页面是空白,需跳转 }else{ targetUrl = historySites[site] + slaveId; } // 对从节点而言是新对话 }else{ // 当前页面有chatId,则跳转空白页 if(curIdFlag){ targetUrl = newSites[site]; // 当前页面已经是空白页 }else{ if(!isEmpty(triggerSource)){ console.log("触发者:", triggerSource); } console.log("h3 send", new Date()); abstractSend(question, ""); setUid(uid, question); } } receiveLock = false; if(!isEmpty(targetUrl)){ let jumpArray = [question, uid, slaveId]; setS("questionBeforeJump", JSON.stringify(jumpArray)); window.location.href = targetUrl; } } function setUid(uid, question){ let intervalId; let lastUrl = getUrl(); let count = 0; let waitTime = 15000; if(site === 3){ waitTime *= 2; } console.log("ready to setUid"); intervalId = setInterval(function() { count ++; if(count > waitTime / checkGap){ console.log("setUid超时"); setS("tool-timeout-check1", uid); clearInterval(intervalId); } let chatId = getChatId(); if (!isEmpty(chatId)) { let uidJson = getGV(uid); if(!isEmpty(uidJson)){ if(isEmpty(uidJson[site])){ uidJson[site] = chatId; } }else{ uidJson = {}; uidJson[site] = chatId; } hsetS(T + chatId, LAST_Q, getQuesOrHash(question)); hsetS(T + chatId, LEN, 1); hsetS(T + chatId, UID_KEY, uid); // console.log("slave print uidJson: "+JSON.stringify(uidJson)); setGV(uid, uidJson); setS("uid-" + uid, JSON.stringify(uidJson)); sendLock = false; console.log("setUid finish", new Date()); // 存储管理(删除与添加) dequeue(); enqueue(chatId); clearInterval(intervalId); } }, checkGap); } // ① 检查textArea存在 ② 检查sendBtn存在 ③ 检查问题列表长度是否加一 function abstractSend(content, chatId){ updateBoxFromStorage(); reloadCompactMode(); let intervalId; let count = 0; sendLock = true; intervalId = setInterval(function() { count ++; if(count > 5000 / checkGap){ clearInterval(intervalId); } const textarea = getTextArea(site); if (!isEmpty(textarea)) { clearInterval(intervalId); sendContent(textarea, content, chatId); } }, checkGap); } function sendContent(textarea, content, chatId){ // 当豆包是新对话,元素不可见会异常,故适当延迟 let sendGap = (site === 4 && isEmpty(chatId)) ? 1500 : 100; setTimeout(function(){ textarea.focus(); document.execCommand('insertText', false, content); clickAndCheckLen(chatId); }, sendGap); } function clickAndCheckLen(chatId) { let tryCount = 0; const checkBtnInterval = setInterval(() => { let quesFlag = false; if(isEmpty(chatId)){ quesFlag = true; }else{ let len = getQuestionList().length; if(len > 0){ quesFlag = true; } } // ① 检查sendBtn存在 ② checkQuesList()检查问题列表长度是否增加 let sendBtn = getBtn(site); if (quesFlag && !isEmpty(sendBtn)) { clearInterval(checkBtnInterval); setTimeout(function(){ // sendBtn存在不一定立即可以点击,最好延迟一下 sendBtn.click(); if([0].includes[site]){ setTimeout(function(){ let areaCheck = getTextArea(site); if(!isEmpty(areaCheck) && !isEmpty(areaCheck.textContent)){ sendBtn.click(); } }, 1000); } }, 200); checkQuesList(chatId); } else { tryCount++; if (tryCount > maxRetries) { clearInterval(checkBtnInterval); setS("tool-timeout-check2", "xx"); console.log("tryCount "+tryCount + ", quesFlag "+quesFlag+", sendBtn "+isEmpty(sendBtn)); console.warn("sendBtn或问题列表未找到,超时"); return; } } }, checkGap); } function checkQuesList(chatId) { console.log("checkQuesList", new Date()); let tryCount = 0; let cachedLen = hgetS(T + chatId, LEN); let newChatFlag = isEmpty(chatId) || isEmpty(cachedLen) || cachedLen === 0; const checkInterval = setInterval(() => { tryCount++; // 定时器:检查问题列表长度大于上次,则停止,并设置sendLock // 注意,若是chat首个问题,则只要求len=1 let len = getQuestionList().length; let questionDisplayFlag = false; if(newChatFlag){ if(len === 1){ questionDisplayFlag = true; } }else{ if(len > cachedLen){ questionDisplayFlag = true; } } if (questionDisplayFlag) { console.log("questionDisplayFlag", new Date()); clearInterval(checkInterval); if(!isEmpty(chatId)){ hsetS(T + chatId, LEN, len); sendLock = false; // 解锁(如果chatId空,有setUid方法负责解锁) } } else if (tryCount > maxRetries) { console.log("tryCount "+tryCount + ", len "+len+", cachedLen "+cachedLen+", newChatFlag "+newChatFlag); clearInterval(checkInterval); console.warn("问题列表长度未符合判据,超时"); sendLock = false; let areaContent = getTextArea(site).textContent; if(!isEmpty(areaContent)){ location.reload(); } } }, checkGap); } function getQuestionList() { let questions = []; switch (site) { case 0: questions = document.getElementsByClassName("user-content"); break; case 1: { let scrollable = document.getElementsByClassName("scrollable")[1]; if (!isEmpty(scrollable)) { let list = scrollable.firstElementChild.firstElementChild.children; let elementsArray = Array.from(list); questions = elementsArray.filter((item, index) => index % 2 === 0); } } break; case 2: questions = document.querySelectorAll('[class^="bubble-"]'); break; case 3: case 5: questions = document.querySelectorAll('[data-message-author-role="user"]'); break; case 4: { let list = document.querySelectorAll('[data-testid="message_text_content"]'); let elementsArray = Array.from(list); questions = elementsArray.filter((item, index) => index % 2 === 0); } break; default: break; } return questions; } function getTextArea(site) { switch (site) { case 0: return document.getElementsByClassName('chat-input-editor')[0]; case 1: return document.getElementById('chat-input'); case 2: case 4: return document.getElementsByTagName('textarea')[0]; case 3: case 5: return document.getElementById('prompt-textarea'); default: return null; } } function getBtn(site){ switch(site){ case 0: return document.getElementsByClassName('send-button')[0]; case 1: var btns = document.querySelectorAll('[role="button"]'); return btns[btns.length - 1]; case 2: return document.querySelectorAll('[class^="operateBtn-"], [class*=" operateBtn-"]')[0]; case 3: case 5: return document.getElementById('composer-submit-button'); case 4: return document.getElementById('flow-end-msg-send'); } } // 快捷键 bindShortcut(PANEL_DISPLAY, () => { let visibility = panel.style.visibility; console.log("visibility", visibility); panel.style.visibility = visibility === "hidden" ? "visible" : "hidden"; }); bindShortcut(PANEL_DISPLAY_ON_LOAD, () => { let defaultStatus = getGV(DEFAULT_DISPLAY_KEY) === false ? true : false; setGV(DEFAULT_DISPLAY_KEY, defaultStatus); let hintStr = defaultStatus ? "显示":"隐藏"; alert("已切换为:网页打开时,将默认「"+ hintStr +"面板」"); }); // 创建面板容器 panel.style.cssText = panelStyle; // 存储原始内容的容器 panel.appendChild(contentContainer); // 从前端DOM获取面板被选中的元素,并存储 function getSitesFromDomAndSave(){ const checkboxes = document.querySelectorAll('input[type="checkbox"][id^="word-"]'); const selectedSites = []; checkboxes.forEach(checkbox => { if (checkbox.checked) { const word = checkbox.id.split('-')[1]; // 提取选中的文本 selectedSites.push(wordToSite[word]); } }); setGV(CHOSEN_SITE, selectedSites); return selectedSites; }; // 从存储获取已选站点 function getSitesOfStorage(){ try { return getGV(CHOSEN_SITE) || []; } catch (e) { console.error('Failed to parse selectedSites from GV', e); return []; } }; function getSitesAndCurrent() { let sitesOfStorage = getSitesOfStorage(); if(!sitesOfStorage.includes(site)){ sitesOfStorage.unshift(site); } return sitesOfStorage; }; function getSitesExcludeCurrent() { let sitesOfStorage = getSitesOfStorage(); if(sitesOfStorage.includes(site)){ sitesOfStorage = sitesOfStorage.filter(element => element !== site); } return sitesOfStorage; }; // 更新存储中的已选单词数字 function updateStorageSites() { const selectedSites = words .filter(word => document.getElementById(`word-${word}`)?.checked) .map(word => wordToSite[word]); setGV(CHOSEN_SITE, selectedSites); console.log('Current selected sites:', selectedSites); }; // 存储-->复选框 function updateBoxFromStorage() { const selectedSites = getSitesAndCurrent(); // console.log('Syncing checkboxes from stoage:', selectedSites); words.forEach(word => { const checkbox = document.getElementById(`word-${word}`); if (checkbox) { checkbox.checked = selectedSites.includes(wordToSite[word]); } }); }; // 生成单词和选择框 let headline = document.createElement('div'); headline.textContent = "全部模型"; headline.style.cssText = hintStyle; contentContainer.appendChild(headline); words.forEach(word => { const item = document.createElement('div'); item.style.cssText = itemStyle; const wordSpan = document.createElement('span'); wordSpan.textContent = word; wordSpan.style.cssText = wordSpanStyle; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `word-${word}`; checkbox.style.cssText = checkboxStyle; checkbox.checked = getSitesAndCurrent().includes(wordToSite[word]); // 添加点击事件 checkbox.addEventListener('change', updateStorageSites); item.appendChild(wordSpan); item.appendChild(checkbox); contentContainer.appendChild(item); }); // zchat特殊处理 if(site === 5 && getGV(DEFAULT_DISPLAY_KEY) === true){ let lastVisibleState = false; // 记录上一次的可见状态 const observer = new IntersectionObserver((entries, instance) => { entries.forEach(entry => { const isCurrentlyVisible = entry.isIntersecting; // 状态发生变化时触发逻辑 if (lastVisibleState === true && isCurrentlyVisible === false) { document.body.appendChild(panel); instance.unobserve(entry.target); // 停止观察当前元素 } lastVisibleState = isCurrentlyVisible; // 更新状态记录 }); }, { threshold: 0.1 // 阈值可根据需求调整 }); observer.observe(panel); } // 添加到页面 setTimeout(function(){ document.body.appendChild(panel); if(getGV(DEFAULT_DISPLAY_KEY) === false){ panel.style.visibility = "hidden"; } }, panelDelay); // 刷新简略模式 function reloadCompactMode(){ if (!isCompactMode) return; let selectedSites = getSitesAndCurrent(); let selectedWords = selectedSites.map(site => siteToWord[site]) drawPanel(selectedWords); } // 切换到简略模式 function switchToCompactMode(){ if (isCompactMode) return; // 保存原始内容 originalHTML = contentContainer.innerHTML; // 记录选中的项 const selectedWords = words.filter(word => document.getElementById(`word-${word}`)?.checked ); if (selectedWords.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.textContent = '未选择模型'; emptyMsg.style.cssText = emptyMessage; contentContainer.innerHTML = ''; contentContainer.appendChild(emptyMsg); } else { drawPanel(selectedWords); } isCompactMode = true; panel.style.cssText = panelStyle; }; function drawPanel(selectedWords){ contentContainer.innerHTML = ''; let hint = document.createElement('div'); hint.textContent = "查看更多"; hint.style.cssText = hintStyle; contentContainer.appendChild(hint); selectedWords.forEach(word => { const item = document.createElement('div'); item.style.cssText = itemStyle; item.dataset.word = word; const wordSpan = document.createElement('span'); wordSpan.textContent = word; wordSpan.style.cssText = wordSpanStyle; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `word-${word}`; checkbox.style.cssText = checkboxStyle; checkbox.checked = true; // 添加点击事件 checkbox.addEventListener('change', updateStorageSites); item.appendChild(wordSpan); item.appendChild(checkbox); contentContainer.appendChild(item); }); } // 切换到原始模式 function switchToOriginalMode() { if (!isCompactMode) return; // 恢复原始内容 contentContainer.innerHTML = originalHTML; // 重新绑定事件 words.forEach(word => { const checkbox = document.getElementById(`word-${word}`); if (checkbox) { checkbox.addEventListener('change', updateStorageSites); } }); // 从存储更新面板选中状态 updateBoxFromStorage(); isCompactMode = false; panel.style.cssText = panelStyle; }; // 点击面板切换模式 panel.addEventListener('click', (e) => { // 阻止事件冒泡到document e.stopPropagation(); // 如果点击的是复选框或按钮,不切换模式 if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') { return; } // 切换模式 if (isCompactMode) { switchToOriginalMode(); } else { switchToCompactMode(); } }); // 点击页面其他地方切换到简略模式 document.addEventListener('click', (e) => { // 如果点击的是面板内部,不处理 if (panel.contains(e.target)) { return; } // 切换到简略模式 if(panel.style.visibility !== "hidden"){ switchToCompactMode(); } }); // 队列头部添加元素 function enqueue(element) { let queue = JSON.parse(localStorage.getItem(QUEUE) || "[]"); if (queue.length > 0 && queue[0] === element) { return; } queue.unshift(element); localStorage.setItem(QUEUE, JSON.stringify(queue)); } // 当队列长度超过阈值,删除队尾元素 function dequeue() { let queue = JSON.parse(localStorage.getItem(QUEUE) || "[]"); let len = queue.length; if(len > MAX_QUEUE){ let chatIdKey = T + queue[len - 1]; let valJson = JSON.parse(getS(chatIdKey)); if(!isEmpty(valJson)){ let uid = valJson.uid; localStorage.removeItem("uid-" + uid); GM_deleteValue(uid); } localStorage.removeItem(chatIdKey); queue.pop(); localStorage.setItem(QUEUE, JSON.stringify(queue)); } } // localStorage读写json(hashMap) function hgetS(key, jsonKey){ let json = localStorage.getItem(key); if(isEmpty(json)){ return ""; } json = JSON.parse(json); return json[jsonKey]; } function hsetS(key, jsonKey, val){ let json = JSON.parse(localStorage.getItem(key) || "{}"); json[jsonKey] = val; localStorage.setItem(key, JSON.stringify(json)); } function getS(key){ return localStorage.getItem(key); } function setS(key, val){ localStorage.setItem(key, val); } // 油猴设置、读取共享存储 function setGV(key, value){ GM_setValue(key, value); } function getGV(key){ return GM_getValue(key); } function isEqual(latestQ, lastQ){ if(latestQ.length > MAX_PLAIN){ if(lastQ.length === HASH_LEN){ return dHash(latestQ) === lastQ; }else{ return false; } }else{ return latestQ === lastQ; } } function getQuesOrHash(ques){ return ques.length > MAX_PLAIN ? dHash(ques) : ques; } function isEmpty(item){ if(item===null || item===undefined || item.length===0 || item === "null"){ return true; }else{ return false; } } // 自定义哈希 function dHash(str, length = HASH_LEN) { let hash = 5381; for (let i = 0; i < str.length; i++) { hash = (hash * 33) ^ str.charCodeAt(i); } const chars = '0123456789abcdefghijklmnopqrstuvwxyz'; let result = ''; let h = hash >>> 0; // 转为无符号整数 // 简单的伪随机数生成器(带种子) function pseudoRandom(seed) { let value = seed; return () => { value = (value * 1664525 + 1013904223) >>> 0; // 常见的 LCG 参数 return value / 4294967296; // 返回 [0,1) 的浮点数 }; } const rand = pseudoRandom(hash); // 使用 hash 作为种子 for (let i = 0; i < length; i++) { if (h > 0) { result += chars[h % chars.length]; h = Math.floor(h / chars.length); } else { // 使用伪随机数生成字符 const randomIndex = Math.floor(rand() * chars.length); result += chars[randomIndex]; } } return result; } function guid() { return 'xxxxxxxx-xxxx-4xxx-yxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } function getRand(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } /** * 绑定快捷键 * @param {string} combo 组合键格式,如 "ctrl+q" * @param {Function} callback 触发回调 * @param {boolean} preventDefault 是否阻止默认行为 */ function bindShortcut(combo, callback, preventDefault = true) { const keys = combo.toLowerCase().split('+'); const requiredKeys = { ctrl: false, alt: false, shift: false, key: null }; keys.forEach(key => { if (key === 'ctrl') requiredKeys.ctrl = true; else if (key === 'alt') requiredKeys.alt = true; else if (key === 'shift') requiredKeys.shift = true; else requiredKeys.key = key; }); document.addEventListener('keydown', (event) => { if ( event.ctrlKey === requiredKeys.ctrl && event.altKey === requiredKeys.alt && event.shiftKey === requiredKeys.shift && event.key.toLowerCase() === requiredKeys.key ) { if (preventDefault) event.preventDefault(); callback(event); } }); } })();