Greasy Fork

多家大模型网页同时回答

只需输入一次问题,就能自动去各家大模型网页提问,免去手动粘贴问题到其他网页、并苦苦等待的麻烦。支持范围: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);
            }
        });
    }

})();