Greasy Fork

Greasy Fork is available in English.

多家大模型网页同时回答

只需输入一次问题,就能自动去各家大模型网页提问,免去手动粘贴问题到其他网页、并苦苦等待的麻烦。支持范围:DeepSeek,Kimi,ChatGPT,通义千问,豆包,ChatGPT(zchat原生界面版)

当前为 2025-06-14 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         多家大模型网页同时回答
// @namespace    http://tampermonkey.net/
// @version      1.4.0
// @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
// @connect      localhost
// @license      GPL-3.0-only
// ==/UserScript==

(function () {
    'use strict';
    console.log("ai script, start");

    let MAX_QUEUE = 10; // 历史对话的记忆数量

    const GPT_GAP = 2000; // ChatGPT的轮询间隔(毫秒)
    const BASIC_GAP = 3000; // 其他网站兜底轮询间隔(毫秒)

    const MAX_PLAIN = 50; // localStorage存储的问题原文的最大长度。超过则存哈希
    const HASH_LEN = 16; // 问题的哈希长度
    const checkGap = 100;
    const maxRetries = 150;

    const version = "1.4.0";
    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 testDOMAIN = "https://www.ratetend.com:5002";
    // let testDOMAIN = "http://localhost:8002";


    // 面板的显示、隐藏快捷键设置
    // const PANEL_DISPLAY = 'ctrl+x';
    // const PANEL_DISPLAY_ON_LOAD = 'ctrl+shift+x';

    let userid = getGV("userid");
    if(isEmpty(userid)){
        userid = guid();
        setGV("userid", userid);

    // 本地调试用,连接本地服务器
    }else{
        if(userid === "e4c2d93c-7e5f-49c8-aea69"){
            MAX_QUEUE = 3;
            if(testLocalFlag === 1){
                DOMAIN = testDOMAIN;
            }
        }
    }

    let site = 0;
    let url = window.location.href;
    const HEART_KEY_PREFIX ="lastHeartbeat-";

    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: 10px;
        bottom: 80px;
        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: 5px;
        display: flex;
        flex-direction: column;
    `;
    const panelCompact = `
        min-width: 120px;
    `;
    const itemStyle = `
            display: flex;
            align-items: center;
            padding: 3px 0;
            border-bottom: 1px solid #eee;
    `;
    const wordSpanStyle = `
            flex: 1;
            margin-right: 5px;
            font-size: 14px;
    `;
    const checkboxStyle = `
            margin-right: 1px;
            font-size: 20px;
    `;
    const emptyMessage = `
            padding: 1px;
            text-align: center;
            color: #888;
            font-size: 14px;
        `;
    const headlineStyle =`
            font-weight: bold;

    `;
    const hintStyle =`
            color: #275fe6;
            width: 0;
            height: 0;
            border-top: 8px solid transparent;
            border-right: 8px solid #3498db; /* 箭头颜色 */
            border-bottom: 8px solid transparent;
    `;

    // 面板数据
    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: 'D'},
        { site: 0, word: 'Kimi', alias: 'K' },
        { site: 5, word: 'ChatGPT (zchat)', alias: 'Z' },
        { site: 3, word: 'ChatGPT (官网)', alias: 'C' },
        { 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;
    // 页面加载发送一次心跳
    setGV(HEART_KEY_PREFIX + site, Date.now());

    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(){
        setGV(HEART_KEY_PREFIX + site, Date.now());

        let sites = getSitesOfStorage();
        if(sites.includes(site)){
            receiveNew("轮询线程");
        }
    }, receiveGap);

    /**
    * SSE事件定义
    */

    // chatGPT不支持连接到其他站点
    if(site !== 3 && typeof(EventSource) !== "undefined"){
        const sseUrl = DOMAIN + "/create?sourceId=" + site + "&userid=" + userid + "&version=" + version;
        let eventSource;
        let retryCount = 0;
        const maxAttempts = 20;
        let heartbeatInterval = 20000;
        let heartbeatTimer = null;
        let lastMessageTime = Date.now();

        function createEventSource() {
            if (eventSource) {
                stopHeartbeat();
                eventSource.close(); // 关闭旧连接
            }
            eventSource = new EventSource(sseUrl);

            const onOpenHandler = () => {
                console.log("[SSE] 连接已建立", curDate());
                startHeartbeat();
                lastMessageTime = Date.now();
                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, curDate());
                receiveNew();
            };

            const onErrorHandler = () => {
                stopHeartbeat();
                eventSource.removeEventListener('open', onOpenHandler);
                eventSource.removeEventListener('init', onInitHandler);
                eventSource.removeEventListener('broadcast', onBroadcastHandler);
                eventSource.onerror = null;
                eventSource.onmessage = null;
                handleConnectionError();
                console.log("error");
            };
            function onHeartbeat(e){
                lastMessageTime = Date.now();
                // console.log(e.data);
            }

            eventSource.onopen = onOpenHandler;
            eventSource.addEventListener("init", onInitHandler);
            eventSource.onmessage = (event) => console.log('Message:', event.data);
            eventSource.addEventListener("broadcast", onBroadcastHandler);
            eventSource.addEventListener("heartbeat", onHeartbeat);
            eventSource.onerror = onErrorHandler;
        }

        createEventSource();

        function startHeartbeat() {
            stopHeartbeat();

            heartbeatTimer = setInterval(() => {
                // 检查上次收到消息的时间
                const timeSinceLastMessage = Date.now() - lastMessageTime;
                // console.log(timeSinceLastMessage);

                if (timeSinceLastMessage > heartbeatInterval * 1.5) {
                    console.error('心跳超时,尝试重新连接', timeSinceLastMessage);
                    createEventSource();
                    return;
                }
                // 可以发送一个心跳请求(如果需要)
                // 对于SSE,通常是服务器定期发送心跳
            }, heartbeatInterval);
        }
        function stopHeartbeat() {
            if (heartbeatTimer) {
                clearInterval(heartbeatTimer);
                heartbeatTimer = null;
            }
        }

        function handleConnectionError() {
            if (retryCount >= maxAttempts) {
                console.error('Exceeded maximum reconnect attempts.');
                retryCount = 0;
                if (eventSource) {
                    eventSource.close();
                }
                return;
            }
            retryCount++;

            let gap = 1000 * (2**(retryCount)) + 1000 * getRand(1, 2**(retryCount - 1));
            console.log("连接异常,", gap/1000, "秒后,第", retryCount, "次尝试重连,当前", curDate());

            setTimeout(() => {
                createEventSource();
            }, gap);
        }

    }

    /**
    * 主从节点的逻辑
    */

    // 发送端
    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,
            date: Date.now()
        };
        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);
        }

        addCurrentToStorage();

        let remoteUrl = DOMAIN + "/masterQ";
        let sites = getSitesExcludeCurrent();
        let data = {
                "userid": userid,
                "sites": sites
            };

        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);
            }
        });

        let openCount = 0;
        sites.forEach(site => {
            let lastHeartbeat = getGV(HEART_KEY_PREFIX + site);
            // 如果从节点70秒没有更新心跳时刻,则认为已经关闭,需打开
            if(isEmpty(lastHeartbeat) || Date.now() - lastHeartbeat > 1000* 70){
                openCount++;
                setTimeout(function(){
                    window.open(newSites[site], '_blank');
                }, 200);
            }
        });

    }

    function receiveNew(triggerSource){
        if(receiveLock){
            return;
        }

        let msg = getGV("msg");
        if(isEmpty(msg)){
            return;
        }

        receiveLock = true;

        if(sendLock){
            receiveLock = false;
            return;
        }

        let curSlaveId = getChatId();

        // 舍弃不新鲜的消息
        if(Date.now() - msg.date > 600*1000){
            return
        }

        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;
                    setS("questionBeforeJump", "");
                    return;
                }
                hsetS(T + curSlaveId, LAST_Q, getQuesOrHash(cachedQuestion));
            }

            // 清空跳转用的缓存
            setS("questionBeforeJump", "");
            console.log(curDate() + "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", curDate());
                    hsetS(T + curSlaveId, LAST_Q, getQuesOrHash(question));
                    abstractSend(question, curSlaveId);
                }else{
                    targetUrl = historySites[site] + slaveId;
                }
            // 当前页面是空白,需跳转
            }else{
                targetUrl = historySites[site] + slaveId;
            }
        // 对从节点而言是新对话
        }else{
            // 当前页面有chatId,则跳转空白页
            if(curIdFlag){
                // setS("gotoNewPage-"+curSlaveId, JSON.stringify(uidJson));
                targetUrl = newSites[site];
            // 当前页面已经是空白页
            }else{
                if(!isEmpty(triggerSource)){
                    console.log("触发者:", triggerSource);
                }
                console.log("h3 send", curDate());
                abstractSend(question, "");
                setUid(uid, question);
            }
        }
        receiveLock = false;
        if(!isEmpty(targetUrl)){
            let jumpArray = [question, uid, slaveId];
            setS("questionBeforeJump", JSON.stringify(jumpArray));
            window.location.href = targetUrl;
        }
    }

    /**
    * 异步轮询检查环节
    */

    // 设置uid
    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超时");
                clearInterval(intervalId);
                return;
            }
            let chatId = getChatId();
            if (!isEmpty(chatId)) {

                let uidInterval;
                let innerCount = 0;

                uidInterval = setInterval(function() {
                    innerCount ++;
                    if(innerCount > 5000 / checkGap){
                        clearInterval(uidInterval);
                        return;
                    }
                    let uidLock = getGV("uidLock");
                    if(isEmpty(uidLock) || uidLock === false){
                        clearInterval(uidInterval);

                        // 读取uidJson前加锁
                        setGV("uidLock", true);
                        let uidJson = getGV(uid);
                        if(!isEmpty(uidJson)){
                            if(isEmpty(uidJson[site])){
                                uidJson[site] = chatId;
                            }
                        }else{
                            uidJson = {};
                            uidJson[site] = chatId;
                        }
                        // 更新完uidJson才能解锁
                        setGV(uid, uidJson);
                        setGV("uidLock", false);
                        setS("uid-" + uid, JSON.stringify(uidJson));
                    }else{
                        console.log("uidLock已存在,稍后重试");
                    }
                }, checkGap);

                // 照理说下面的逻辑应在上面的setGV成功后再执行,但这样得写两遍,且理论上一定成功,故放这。
                hsetS(T + chatId, LAST_Q, getQuesOrHash(question));
                hsetS(T + chatId, LEN, 1);
                hsetS(T + chatId, UID_KEY, uid);

                sendLock = false;
                console.log("setUid finish", curDate());

                // 存储管理(删除与添加)
                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(){
                    document.body.click();
                    setTimeout(function(){
                        sendBtn.click();
                        setTimeout(function(){
                            let areaCheck = getTextArea(site);
                            if(!isEmpty(areaCheck) && !isEmpty(areaCheck.textContent)){
                                sendBtn.click();
                            }
                        }, 1000);
                    }, 300);
                }, 200);
                checkQuesList(chatId);
            } else {
                tryCount++;
                if (tryCount > maxRetries) {
                    clearInterval(checkBtnInterval);
                    console.log("tryCount "+tryCount + ", quesFlag "+quesFlag+", sendBtn "+isEmpty(sendBtn));
                    console.warn("sendBtn或问题列表未找到,超时");
                    return;
                }
            }
        }, checkGap);
    }

    function checkQuesList(chatId) {
        console.log("check ques list", curDate());
        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("question has displayed", curDate());
                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, () => {
//         console.log("h2");
//         let visibility = panel.style.visibility;
//         console.log("visibility", visibility);
//         panel.style.visibility = visibility === "hidden" ? "visible" : "hidden";
//     });

//     bindShortcut(PANEL_DISPLAY_ON_LOAD, () => {
//         console.log("hello");
//         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 addCurrentToStorage() {
        let sitesOfStorage = getSitesOfStorage();
        if(!sitesOfStorage.includes(site)){
            sitesOfStorage.unshift(site);
            setGV(CHOSEN_SITE, sitesOfStorage);
        }
    };

    function getSitesExcludeCurrent() {
        let sitesOfStorage = getSitesOfStorage();
        if(sitesOfStorage.includes(site)){
            sitesOfStorage = sitesOfStorage.filter(element => element !== site);
        }
        return sitesOfStorage;
    };

    // 更新存储中的已选单词数字
    function updateStorageSites(word) {
        const selectedSites = words
          .filter(word => document.getElementById(`word-${word}`)?.checked)
          .map(word => wordToSite[word]);

        let siteOfWord = wordToSite[word];
        if (siteOfWord!== site && selectedSites.includes(siteOfWord)) {
            let lastHeartbeat = getGV(HEART_KEY_PREFIX + siteOfWord);
            if(isEmpty(lastHeartbeat) || Date.now() - lastHeartbeat > 1000* 70){
                setTimeout(function(){
                    window.open(newSites[siteOfWord], '_blank');
                }, 1500);
            }
        }

        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 = headlineStyle;
    contentContainer.appendChild(headline);

    let sitesAndCurrent = getSitesAndCurrent();

    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 = sitesAndCurrent.includes(wordToSite[word]);

        // 添加点击事件
        checkbox.addEventListener('change', () => updateStorageSites(word));

        item.appendChild(wordSpan);
        item.appendChild(checkbox);
        contentContainer.appendChild(item);
    });

    // zchat特殊处理
    if(site === 5){
    // 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])
        drawCompactPanel(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 {
            drawCompactPanel(selectedWords);
        }

        isCompactMode = true;
        panel.style.cssText = panelStyle;
    };

    function drawCompactPanel(selectedWords){
        contentContainer.innerHTML = '';
        let hint = document.createElement('div');
        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');
            let alias = wordToAlias[word];
            wordSpan.textContent = alias;
            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(word));

            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(word));
            }
        });

        // 从存储更新面板选中状态
        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);
            }
        });
    }

    function curDate() {
        let timer = new Date();
        let year = timer.getFullYear();
        let month = timer.getMonth() + 1; // 由于月份从0开始,因此需加1
        if (month < 10) month = "0" + month;
        let day = timer.getDate();
        if (day < 10) day = "0" + day;
        let hour = timer.getHours();
        if (hour < 10) hour = "0" + hour;
        let minute = timer.getMinutes();
        if (minute < 10) minute = "0" + minute;
        let second = timer.getSeconds();
        if (second < 10) second = "0" + second;
        return `【${hour}:${minute}:${second}】`;
    }

})();