[油猴脚本] 网页版 AI 聊天增强(支持 DeepSeek 和 QwenChat 等)

无法访问 greasyfork 的用户可以访问这个国内镜像 https://gf.qytechs.cn/zh-CN

加宽聊天界面

占满网页剩余空间

支持 deepseek/ChatGPT/Claude/Kimi/通义/智谱 GLM/天工/Deepseek/Gemini/腾讯元宝/grok

https://greasyfork.org/zh-CN/scripts/499377-wider-ai-chat 安装这个油猴脚本即可

聊天问题列表

显示聊天会话中,你已经提的问题列表,点击跳转到对应的聊天处

image.png

改造自 https://greasyfork.org/zh-CN/scripts/528739

支持 deepseek、 QwenChat、Grok、GitHub Copilot、通义千问等)

// ==UserScript==
// @name         webAI聊天问题列表导航
// @namespace    http://tampermonkey.net/
// @version      2.10
// @description  通过点击按钮显示用户问题列表,支持导航到特定问题、分页功能、正序/倒序切换,优化性能并美化UI,适配CSP限制
// @author       yutao
// @match        https://grok.com/chat/*
// @match        https://github.com/copilot/*
// @match        https://yuanbao.tencent.com/chat/*
// @match        https://chat.qwenlm.ai/*
// @match        https://chat.qwen.ai/*
// @match        https://copilot.microsoft.com/chats/*
// @match        https://chatgpt.com/c/*
// @match        https://chat.deepseek.com/*
// @grant        none
// MIT License
//
// Copyright (c) [2025] [yutao]
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.@license
// ==/UserScript==

(function () {
    'use strict';

    // 配置对象,定义不同网站的聊天消息选择器和条件
    const config = {
        'grok.com': {
            messageSelector: 'div.message-bubble',
            textSelector: 'span.whitespace-pre-wrap',
            userCondition: (element) => element.classList.contains('bg-foreground') &&
                window.getComputedStyle(element).backgroundColor !== 'rgb(224, 247, 250)'
        },
        'github.com': {
            messageSelector: 'div.UserMessage-module__container--cAvvK.ChatMessage-module__userMessage--xvIFp',
            textSelector: null,
            userCondition: (element) => element.classList.contains('ChatMessage-module__userMessage--xvIFp')
        },
        'yuanbao.tencent.com': {
            messageSelector: 'div.agent-chat__bubble__content',
            textSelector: 'div.hyc-content-text',
            userCondition: (element) => true
        },
        'chat.qwenlm.ai': {
            messageSelector: 'div.rounded-3xl.bg-gray-50.dark\\:bg-gray-850',
            textSelector: 'p',
            userCondition: (element) => true
        },
        'chat.qwen.ai': {
            //messageSelector: 'div.rounded-3xl.bg-gray-50.dark\\:bg-gray-850',
            messageSelector: '.user-message div:has(> p)',
            textSelector: '',
            userCondition: (element) => true
        },
        'copilot.microsoft.com': {
            messageSelector: 'div.self-end.rounded-2xl',
            textSelector: null,
            userCondition: (element) => element.classList.contains('self-end')
        },
        'chatgpt.com': {
            messageSelector: 'div.rounded-3xl.bg-token-message-surface',
            textSelector: 'div.whitespace-pre-wrap',
            userCondition: (element) => true
        },
        'chat.deepseek.com': {
            messageSelector: 'div.fbb737a4',
            textSelector: null,
            userCondition: (element) => true
        }
    };

    // 获取当前域名并选择配置
    const hostname = window.location.hostname;
    const currentConfig = config[hostname] || {
        messageSelector: 'div[class*=message], div[class*=chat], div[class*=user]',
        textSelector: null,
        userCondition: (element) => true
    };

    // 创建美化后的浮动按钮
    const button = document.createElement('button');
    button.textContent = '问题';
    button.style.position = 'fixed';
    button.style.bottom = '120px';
    button.style.right = '20px';
    button.style.zIndex = '1000';
    button.style.padding = '4px 8px';
    button.style.background = 'linear-gradient(135deg, #007BFF, #00C4FF)';
    button.style.color = '#fff';
    button.style.border = 'none';
    button.style.borderRadius = '8px';
    button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
    button.style.cursor = 'pointer';
    button.style.fontFamily = 'Arial, sans-serif';
    button.style.fontSize = '14px';
    button.style.transition = 'transform 0.2s, box-shadow 0.2s';
    button.addEventListener('mouseover', () => {
        button.style.transform = 'scale(1.05)';
        button.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
    });
    button.addEventListener('mouseout', () => {
        button.style.transform = 'scale(1)';
        button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
    });
    document.body.appendChild(button);
    moveableDialog(button, button);

    // 创建美化后的悬浮窗
    const floatWindow = document.createElement('div');
    floatWindow.style.position = 'fixed';
    floatWindow.style.bottom = '70px';
    floatWindow.style.right = '20px';
    floatWindow.style.width = '820px';
    floatWindow.style.maxHeight = '420px';
    floatWindow.style.background = '#ffffff';
    floatWindow.style.border = '1px solid #e0e0e0';
    floatWindow.style.borderRadius = '10px';
    floatWindow.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
    floatWindow.style.padding = '15px';
    floatWindow.style.overflowY = 'auto';
    floatWindow.style.display = 'none';
    floatWindow.style.zIndex = '1000';
    floatWindow.style.fontFamily = 'Arial, sans-serif';
    floatWindow.style.transition = 'opacity 0.2s';
    floatWindow.className = 'chat-float-window';
    document.body.appendChild(floatWindow);

    // 监听body被点击
    document.body.addEventListener('click', (event) => {
        // 挡在floatWindow内或分页按钮或问题列表按钮时跳过
        if(event.target.closest('.chat-float-window')||event.target===button||event.target.matches('.chat-float-window-page')){
            return;
        }
        if(floatWindow.style.opacity==='1') {
            floatWindow.style.opacity = '0';
            setTimeout(() => {
                floatWindow.style.display = 'none';
                button.textContent = '问题';
            }, 200);
        }
    });

    // 分页相关变量
    let questions = [];
    const pageSize = 10;
    let currentPage = 1;
    let isReversed = false;

    // 创建排序切换按钮
    const sortButton = document.createElement('button');
    sortButton.textContent = '正序';
    sortButton.style.marginBottom = '10px';
    sortButton.style.padding = '5px 10px';
    sortButton.style.background = '#007BFF';
    sortButton.style.color = '#fff';
    sortButton.style.border = 'none';
    sortButton.style.borderRadius = '4px';
    sortButton.style.cursor = 'pointer';
    sortButton.style.fontSize = '12px';
    sortButton.addEventListener('click', () => {
        isReversed = !isReversed;
        sortButton.textContent = isReversed ? '倒序' : '正序';
        findAllQuestions();
    });
    floatWindow.appendChild(sortButton);

    // 创建关闭按钮
    const closeButton = document.createElement('button');
    closeButton.textContent = '关闭';
    closeButton.style.float = 'right';
    closeButton.style.marginBottom = '10px';
    closeButton.style.padding = '5px 10px';
    closeButton.style.background = '#007BFF';
    closeButton.style.color = '#fff';
    closeButton.style.border = 'none';
    closeButton.style.borderRadius = '4px';
    closeButton.style.cursor = 'pointer';
    closeButton.style.fontSize = '12px';
    closeButton.addEventListener('click', () => {
        floatWindow.style.opacity = '0';
        setTimeout(() => {
            floatWindow.style.display = 'none';
            button.textContent = '问题';
        }, 200);
    });
    floatWindow.appendChild(closeButton);

    // 创建分页控件
    const paginationContainer = document.createElement('div');
    paginationContainer.style.display = 'flex';
    paginationContainer.style.justifyContent = 'center';
    paginationContainer.style.marginTop = '10px';
    paginationContainer.style.gap = '5px';

    // 问题列表容器
    const listContainer = document.createElement('ul');
    listContainer.style.listStyle = 'none';
    listContainer.style.padding = '0';
    listContainer.style.margin = '0';
    floatWindow.appendChild(listContainer);
    floatWindow.appendChild(paginationContainer);

    // 获取文本内容的辅助函数
    function getTextContent(element) {
        return element ? element.textContent.trim() : '';
    }

    // 查找所有用户问题的函数
    function findAllQuestions() {
        const chatContainer = document.querySelector('.chat-container, #chat, main, article') || document.body;
        const potentialMessages = chatContainer.querySelectorAll(currentConfig.messageSelector);
        questions = [];

        for (let i = 0; i < potentialMessages.length; i++) {
            const element = potentialMessages[i];
            const textElement = currentConfig.textSelector ? element.querySelector(currentConfig.textSelector) : element;
            const text = getTextContent(textElement);

            if (text && currentConfig.userCondition(element)) {
                questions.push({ element, text });
            }
        }

        if (isReversed) {
            questions.reverse();
        }

        renderPage(currentPage);
        updatePagination();
    }

    // 渲染指定页的问题(使用 DOM 操作替代 innerHTML)
    function renderPage(page) {
        // 清空列表容器
        while (listContainer.firstChild) {
            listContainer.removeChild(listContainer.firstChild);
        }

        const start = (page - 1) * pageSize;
        const end = page * pageSize;
        const pageQuestions = questions.slice(start, end);

        pageQuestions.forEach((q, idx) => {
            const listItem = document.createElement('li');
            const shortText = q.text.substring(0, 150) + (q.text.length > 150 ? '...' : '');
            listItem.textContent = `${isReversed ? questions.length - start - idx : start + idx + 1}: ${shortText}`;
            listItem.style.padding = '8px 12px';
            listItem.style.cursor = 'pointer';
            listItem.style.fontSize = '13px';
            listItem.style.color = '#333';
            listItem.style.whiteSpace = 'nowrap';
            listItem.style.overflow = 'hidden';
            listItem.style.textOverflow = 'ellipsis';
            listItem.style.borderBottom = '1px solid #f0f0f0';
            listItem.style.transition = 'background 0.2s';
            listItem.title = q.text;
            listItem.addEventListener('mouseover', () => {
                listItem.style.background = '#f5f5f5';
            });
            listItem.addEventListener('mouseout', () => {
                listItem.style.background = 'none';
            });
            listItem.addEventListener('click', () => {
                q.element.scrollIntoView({ behavior: 'smooth', block: 'start' });
                floatWindow.style.opacity = '0';
                setTimeout(() => {
                    floatWindow.style.display = 'none';
                    button.textContent = '问题';
                }, 200);
                //console.log(`${questions.indexOf(q) + 1}: ${q.text.substring(0, 150)}...`);
            });
            listContainer.appendChild(listItem);
        });
    }

    // 更新分页控件
    function updatePagination() {
        // 清空分页容器
        while (paginationContainer.firstChild) {
            paginationContainer.removeChild(paginationContainer.firstChild);
        }

        const totalPages = Math.ceil(questions.length / pageSize);
        if (totalPages) {
            const prevButton = document.createElement('button');
            prevButton.textContent = '上一页';
            prevButton.style.padding = '5px 10px';
            prevButton.style.border = 'none';
            prevButton.style.background = currentPage === 1 ? '#f0f0f0' : '#007BFF';
            prevButton.style.color = currentPage === 1 ? '#aaa' : '#fff';
            prevButton.style.cursor = currentPage === 1 ? 'not-allowed' : 'pointer';
            prevButton.style.borderRadius = '4px';
            prevButton.disabled = currentPage === 1;
            prevButton.className= 'chat-float-window-page';
            prevButton.addEventListener('click', () => {
                if (currentPage > 1) {
                    currentPage--;
                    renderPage(currentPage);
                    updatePagination();
                }
            });
            paginationContainer.appendChild(prevButton);

            for (let i = 1; i <= totalPages; i++) {
                const pageButton = document.createElement('button');
                pageButton.textContent = i;
                pageButton.style.padding = '5px 10px';
                pageButton.style.border = 'none';
                pageButton.style.background = currentPage === i ? '#007BFF' : '#f0f0f0';
                pageButton.style.color = currentPage === i ? '#fff' : '#333';
                pageButton.style.cursor = 'pointer';
                pageButton.style.borderRadius = '4px';
                pageButton.className= 'chat-float-window-page';
                pageButton.addEventListener('click', () => {
                    currentPage = i;
                    renderPage(currentPage);
                    updatePagination();
                });
                paginationContainer.appendChild(pageButton);
            }

            const nextButton = document.createElement('button');
            nextButton.textContent = '下一页';
            nextButton.style.padding = '5px 10px';
            nextButton.style.border = 'none';
            nextButton.style.background = currentPage === totalPages ? '#f0f0f0' : '#007BFF';
            nextButton.style.color = currentPage === totalPages ? '#aaa' : '#fff';
            nextButton.style.cursor = currentPage === totalPages ? 'not-allowed' : 'pointer';
            nextButton.style.borderRadius = '4px';
            nextButton.disabled = currentPage === totalPages;
            nextButton.className= 'chat-float-window-page';
            nextButton.addEventListener('click', () => {
                if (currentPage < totalPages) {
                    currentPage++;
                    renderPage(currentPage);
                    updatePagination();
                }
            });
            paginationContainer.appendChild(nextButton);
        }
    }

    // 点击切换悬浮窗显示状态
    button.addEventListener('click', () => {
        if(isGlobalDragging) return;
        if (floatWindow.style.display === 'none' || floatWindow.style.display === '') {
            findAllQuestions();
            if (questions.length === 0) {
                alert('未找到任何问题!');
                return;
            }
            floatWindow.style.display = 'block';
            floatWindow.style.opacity = '1';
            button.textContent = '隐藏';
        } else {
            floatWindow.style.opacity = '0';
            setTimeout(() => {
                floatWindow.style.display = 'none';
                button.textContent = '问题';
            }, 200);
        }
    });

    // 监听用户输入新问题后触发查找
    function setupInputListener() {
        const input = document.querySelector('textarea, input[type="text"], [contenteditable]');
        if (input) {
            input.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    setTimeout(findAllQuestions, 1000);
                }
            });
        }
    }

    let isGlobalDragging = false;
    function moveableDialog(dialog, dragEl) {
        let isDragging = false;
        let offsetX, offsetY;
        let dialogRect = dialog.getBoundingClientRect();

        const dragHandler = (e) => {
            let clientX, clientY;

            // 处理触摸事件
            if (e.type.startsWith('touch')) {
                if (e.touches.length > 0) {
                    clientX = e.touches[0].clientX;
                    clientY = e.touches[0].clientY;
                } else {
                    return; // 如果没有触摸点,直接返回
                }
            } else {
                clientX = e.clientX;
                clientY = e.clientY;
            }

            if (e.type === 'mousedown' || e.type === 'touchstart') {
                // 开始拖动
                isDragging = true;
                document.removeEventListener('mousemove', dragHandler);
                document.removeEventListener('mouseup', dragHandler);
                document.removeEventListener('touchmove', dragHandler);
                document.removeEventListener('touchend', dragHandler);
                document.addEventListener('mousemove', dragHandler);
                document.addEventListener('mouseup', dragHandler);
                document.addEventListener('touchmove', dragHandler);
                document.addEventListener('touchend', dragHandler);
                offsetX = clientX - dialog.offsetLeft;
                offsetY = clientY - dialog.offsetTop;
                setTimeout(() => {
                    if (!isNaN(parseFloat(dialog.style.left)) && !isNaN(parseFloat(dialog.style.top))) {
                        dialog.style.right = 'auto';
                        dialog.style.bottom = 'auto';
                    }
                }, 100);
            } else if ((e.type === 'mousemove' || e.type === 'touchmove') && isDragging) {
                // 拖动中
                isGlobalDragging = true;
                const x = clientX - offsetX;
                const y = clientY - offsetY;
                // 限制不超过窗口大小
                const maxX = window.innerWidth - dialogRect.width;
                const maxY = window.innerHeight - dialogRect.height;
                const clampedX = Math.max(0, Math.min(x, maxX));
                const clampedY = Math.max(0, Math.min(y, maxY));
                dialog.style.left = clampedX + 'px';
                dialog.style.top = clampedY + 'px';
            } else if (e.type === 'mouseup' || e.type === 'touchend') {
                // 拖动结束
                isDragging = false;
                setTimeout(()=>{isGlobalDragging = false;}, 300);
                document.removeEventListener('mousemove', dragHandler);
                document.removeEventListener('mouseup', dragHandler);
                document.removeEventListener('touchmove', dragHandler);
                document.removeEventListener('touchend', dragHandler);
            }
            e.preventDefault();
        };

        // 注册拖动句柄
        dragEl.removeEventListener('mousedown', dragHandler);
        dragEl.removeEventListener('touchstart', dragHandler);
        dragEl.addEventListener('mousedown', dragHandler);
        dragEl.addEventListener('touchstart', dragHandler);

        // 改变窗口大小事件
        window.addEventListener("resize", (event) => {
            if (!isNaN(parseFloat(dialog.style.left)) && !isNaN(parseFloat(dialog.style.top))) {
                if (parseFloat(dialog.style.left) > window.innerWidth) {
                    dialog.style.left = (window.innerWidth - dialogRect.width) + 'px';
                }
                if (parseFloat(dialog.style.top) > window.innerHeight) {
                    dialog.style.top = (window.innerHeight - dialogRect.height) + 'px';
                }
            }
        });
    }

    // 页面加载后初始化
    window.addEventListener('load', () => {
        setTimeout(() => {
            findAllQuestions();
            setupInputListener();
        }, 2000);
    });
})();

保存聊天内容

把本会话的所有聊天内容保存为 html 文件

image.png

支持 ChatGPT,deepseek 和 QwenChat

改造自 https://greasyfork.org/zh-CN/scripts/461763

// ==UserScript==
// @name         AI对话保存
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  AI对话保存,兼容ChatGPT和DeepSeek,安装后右下角出现一个悬浮窗按钮,点击即可保存当前对话,自动询问文件名,自动添加时间戳,保存为html格式
// @author       thunder-sword
// @match        https://chat.openai.com/*
// @match        https://chat.qwen.ai/*
// @match        https://chat.deepseek.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=openai.com
// @license      MIT
// @grant        none
// ==/UserScript==

/* 作用:根据指定的html字符串下载html文件,可指定文件名,自动获取当前页面中的css,自动添加时间戳 */
function downloadHtml(html, fileName='page.html', getCSS=true, addTimeSuffix=true){
    var result=`<head></head>`;
    if(getCSS){
        /* 获取当前页面 css */
        const css = Array.from(document.styleSheets)
        .filter(styleSheet => {
            try {
                return styleSheet.cssRules; // 尝试访问 cssRules,如果不抛出错误则说明不是跨域的
            } catch (e) {
                return false; // 跨域样式表,跳过
            }
        })
        .map(styleSheet => Array.from(styleSheet.cssRules).map(rule => rule.cssText))
        .flat()
        .join("\n");
        result=`<head><style>\n${css}\n</style></head>`;
    }
    result+='<body>'+html+'</body>';
    const file = new File([result], "page.html", { type: "text/html" });
    const a = document.createElement("a");
    a.href = URL.createObjectURL(file);
    fileName = fileName.endsWith(".html") ? fileName : fileName + ".html";
    if(addTimeSuffix){
        var currentTime = new Date();
        fileName=fileName.slice(0,-5)+`-${currentTime.getFullYear()}${(currentTime.getMonth()+1).toString().padStart(2, "0")}${currentTime.getDate().toString().padStart(2, "0")}.html`;
    }
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
}
var copyScript =`var cs = document.querySelectorAll('.bg-black > div > button');
for (let i = 0; i < cs.length; i++) {
    /* 为按钮元素添加点击事件监听器 */
    cs[i].addEventListener('click', function() {
        /* 获取需要复制的文本内容 */
        let text = cs[i].parentNode.parentNode.querySelector('div.p-4 > code').innerText;

        /* 将文本内容复制到剪贴板 */
        navigator.clipboard.writeText(text).then(function() {
            /* 复制成功 */
            /* alert('文本已复制到剪贴板!'); */

            /* 更新按钮文字,提示复制成功 */
            cs[i].innerHTML = '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="20 6 9 17 4 12"></polyline></svg>Copied!';

            /* 设置定时器,延迟两秒钟恢复按钮的原状 */
            setTimeout(function() {
                cs[i].innerHTML = '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>Copy code';
            }, 2000);
        }, function() {
            /* 复制失败 */
            /* alert('文本复制失败!'); */
        });
    });
}`;
/* 作用:保存ai对话记录,可弹窗询问要保存的文件名,默认为false */
function savaAiRecording(fileName='page.html', askFileName=false){
    let defaultName = '';
    if (location.href.indexOf("chat.qwen.ai")!==-1) {
        defaultName = document.querySelector('#sidebar a[aria-label="chat-item"]:not([class*=group-hover]) .text-left')?.textContent || document.title.split('|')[0].trim() || '';
    }
    if (location.href.indexOf("chat.deepseek.com")!==-1) {
        defaultName = document.querySelector('div[style="outline: none;"]')?.textContent || '';
    }
    if (location.href.indexOf("chatgpt.com")!==-1) {
        defaultName = document.title || '';
    }
    /* askFileName为true时弹窗询问文件名 */
    fileName=askFileName?prompt('输入要保存的文件名:', defaultName):fileName;
    var body=document.createElement('body');
    if (location.href.indexOf("chat.qwen.ai")!==-1) {
        const messagesContainer = document.querySelector("#messages-container");
        messagesContainer.style.height = '100vh';
        body.innerHTML = messagesContainer.outerHTML;
    } else {
        body.innerHTML=document.body.innerHTML;
    }
    /* 删除所有script标签 */
    var ps = body.querySelectorAll('script');
    for (var i = 0; i < ps.length; i++) {
        ps[i].parentNode.removeChild(ps[i]);
    }
    /* 删除所有style标签,因为downloadHtml会自动再获取一次 */
    ps = body.querySelectorAll('style');
    for (var i = 0; i < ps.length; i++) {
        ps[i].parentNode.removeChild(ps[i]);
    }
    /* 删除下边框 */
    var element=body.querySelector('#__next > div > div > main > div.absolute');
    element && element.remove();
    /* 删除DeepSeek侧边框 */
    element=body.querySelector('div#root > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div:nth-child(1)');
    element && element.remove();
    /* 删除侧边框 */
    element=body.querySelector('#__next > div > div.hidden');
    element && element.remove();
    /* 删除侧边框间隔 */
    element=body.querySelector('#__next > div > div');
    if(element){element.className='';}
    /* 添加script标签,用于修复一键复制 */
    var script=document.createElement('script');
    script.innerHTML=copyScript;
    body.appendChild(script);
    if (location.href.indexOf("chat.qwen.ai")!==-1) {
        const style = `<style>
            ::-webkit-scrollbar {
              width: 10px;
            }

            ::-webkit-scrollbar-track {
              background: #fafafa;
              border-radius: 5px;
              box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.1);
            }

            ::-webkit-scrollbar-thumb {
              background: #c1c1c1;
              border-radius: 5px;
              border: 2px solid #fafafa;
            }

            ::-webkit-scrollbar-thumb:hover {
              background: #a8a8a8;
            }

            /* Firefox */
            body {
              scrollbar-width: thin;
              scrollbar-color: #c1c1c1 #fafafa;
            }
        </style>`;
        body.insertAdjacentHTML('afterbegin', style);
    }
    downloadHtml(body.innerHTML, fileName);
}

//版本号:v0.0.2
//作用:创建一个在右下角出现的悬浮窗按钮,多个按钮会自动排序,点击即可执行对应函数
// 保存按钮
function createFloatButton(name, func){

     //没有容器则生成容器
    let box=document.querySelector("body > div#ths_button_container");
    if(!box){
        box=document.createElement('div');
        box.id="ths_button_container";
        box.style.cssText = `
    position: fixed;
    bottom: 86px;
    right: 18px;
    min-height: 30px; /* 设置一个最小高度,确保容器有一定高度 */
    display: flex;
    z-index: 999;
    flex-direction: column;
    user-select: none;
    `;
        document.body.appendChild(box);
    }

    // 创建一个 div 元素
    var floatWindow = document.createElement('div');

    // 设置 div 的内容
    //floatWindow.innerHTML = '点我执行代码';
    floatWindow.innerHTML = name;

    // 设置 div 的样式
    floatWindow.style.cssText = `
    /*padding: 5px;
    background-color: #333;
    color: #fff;
    border-radius: 5px;
    font-size: 16px;
    text-align: center;
    opacity: 1;
    z-index: 999;
    margin: 5px;
    cursor: pointer;*/ /* 鼠标可以选中 */

    padding: 4px 8px;
    background-color: rgb(2 170 255);
    color: rgb(255, 255, 255);
    border-radius: 8px;
    font-size: 14px;
    text-align: center;
    opacity: 1;
    z-index: 99999;
    margin: 0;
    cursor: pointer;
    `;
    if (location.href.indexOf("chat.deepseek.com")!==-1) {
        floatWindow.style.cssText += 'padding: 2px 8px;';
    }

    // 将悬浮窗的优先级提高
    floatWindow.style.zIndex = "99999";

    var isDragging = false;
    var currentX;
    var currentY;
    var initialX;
    var initialY;
    var xOffset = 0;
    var yOffset = 0;
    var cursorX;
    var cursorY;

    floatWindow.addEventListener("mousedown", function(e) {
        if (!isDragging) {
            cursorX = e.clientX;
            cursorY = e.clientY;
            initialX = cursorX - xOffset;
            initialY = cursorY - yOffset;
            isDragging = true;
        }
    });
    floatWindow.addEventListener("mousemove", function(e) {
        if (isDragging) {
            e.preventDefault();
            currentX = e.clientX - initialX;
            currentY = e.clientY - initialY;

            xOffset = currentX;
            yOffset = currentY;

            setTranslate(currentX, currentY, floatWindow);
        }
    });
    floatWindow.addEventListener("mouseup", async function(e) {
        initialX = currentX;
        initialY = currentY;

        isDragging = false;
        // 如果点击时鼠标的位置没有改变,就认为是真正的点击
        if (cursorX === e.clientX && cursorY === e.clientY) {
            await func();
        }
    });

    // 为悬浮窗添加事件处理程序,用来监听触摸开始和触摸移动事件
    // 这些事件处理程序的实现方式与上面的鼠标事件处理程序类似
    floatWindow.addEventListener('touchstart', (event) => {
        if (!isDragging) {
            cursorX = event.touches[0].clientX;
            cursorY = event.touches[0].clientY;
            initialX = cursorX - xOffset;
            initialY = cursorY - yOffset;
            isDragging = true;
        }
    });
    floatWindow.addEventListener('touchmove', (event) => {
        if (isDragging) {
            currentX = event.touches[0].clientX - initialX;
            currentY = event.touches[0].clientY - initialY;

            xOffset = currentX;
            yOffset = currentY;

            setTranslate(currentX, currentY, floatWindow);
        }
    });

    // 为悬浮窗添加事件处理程序,用来监听触摸结束事件
    // 这个事件处理程序的实现方式与上面的鼠标事件处理程序类似
    floatWindow.addEventListener('touchend', async () => {
        initialX = currentX;
        initialY = currentY;

        isDragging = false;
        // 如果点击时鼠标的位置没有改变,就认为是真正的点击
        if (cursorX === event.touches[0].clientX && cursorY === event.touches[0].clientY) {
            await func();
        }
    });

    function setTranslate(xPos, yPos, el) {
        el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
    }

    //将悬浮窗添加到box元素中
    box.appendChild(floatWindow);
}


// AI对话保存
createFloatButton("保存", ()=>{
    savaAiRecording('',true);
});

可通过 url 参数搜索

网页版增加 query string 的搜索功能,q 是 keyword , r=true 是否开启深度思考,s=true 是开启联网搜索

可配合这个使用,非常方便 [js] 工作栏增加搜索选中文本或访问选中链接,支持 ai 搜索和翻译

支持 deepseek,QwenChat 和通义千问

改造自 https://greasyfork.org/zh-CN/scripts/527071

deepseek 油猴脚本

// ==UserScript==
// @name         DeepSeek 网页版 URL 增加 query 搜索
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  为 DeepSeek 网页版增加 query string 的搜索功能,q 是 keyword , r=true 是否开启深度思考,s=true 是开启联网搜索
// @author       阿依帝 with DeepSeek R1 (https://space.bilibili.com/103021226)
// @match        https://chat.deepseek.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=deepseek.com
// @license      MIT
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 解析URL参数
    function getQueryParam(name) {
        const params = new URLSearchParams(window.location.search);
        return params.get(name);
    }

    // 查找包含指定文本的按钮
    function findButtonByText(text) {
        const xpath = `//div[@role='button']//span[contains(text(), '${text}')]`;
        const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        return result.singleNodeValue?.closest('div[role="button"]');
    }

    // 检查按钮是否激活
    function isButtonActive(button) {
        return getComputedStyle(button).getPropertyValue('--ds-button-color').includes('#DBEAFE'); //77, 107, 254
    }

    // 触发React的输入事件
    function setReactInputValue(element, value) {
        const inputEvent = new Event('input', { bubbles: true, composed: true });
        const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
            window.HTMLTextAreaElement.prototype,
            'value'
        ).set;
        nativeInputValueSetter.call(element, value);
        element.dispatchEvent(inputEvent);
    }

    // 处理模式切换
    async function toggleMode(button, shouldEnable) {
        if (!button) return;
         await new Promise(r => setTimeout(r, 100));
        const isActive = isButtonActive(button);
        if (shouldEnable && !isActive) {
            button.click();
            await new Promise(r => setTimeout(r, 200));
        }
        if (!shouldEnable && isActive) {
            button.click();
            await new Promise(r => setTimeout(r, 200));
        }
    }

    // 主处理函数
    async function processQueryParams() {
        // 获取参数(已修复括号问题)
        const qParam = getQueryParam('q');
        const query = qParam ? decodeURIComponent(qParam) : '';
        const needDeepThinking = getQueryParam('r') === 'true';
        const search = getQueryParam('s') === 'true';

        if (!query) return;

        // 等待必要元素加载
        const maxWaitTime = 5000;
        const startTime = Date.now();

        // 等待输入框加载
        let textarea;
        while (!(textarea = document.getElementById('chat-input')) && Date.now() - startTime < maxWaitTime) {
            await new Promise(r => setTimeout(r, 100));
        }

        if (!textarea) {
            console.error('找不到输入框');
            return;
        }

        // 填充查询内容
        setReactInputValue(textarea, query);

        // 强制开启联网搜索
        const webSearchBtn = findButtonByText('联网搜索');
        await toggleMode(webSearchBtn, search);

        // 处理深度思考模式
        const deepThinkBtn = findButtonByText('深度思考');
        await toggleMode(deepThinkBtn, needDeepThinking);

        // 点击发送按钮
        const sendBtn = document.querySelector('div[role="button"][aria-disabled="false"]');
        if (sendBtn) {
            sendBtn.click();
        } else {
            const observer = new MutationObserver(() => {
                const activeSendBtn = document.querySelector('div[role="button"][aria-disabled="false"]');
                if (activeSendBtn) {
                    observer.disconnect();
                    activeSendBtn.click();
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }
    }

    // 页面加载完成后执行
    window.addEventListener('load', () => {
        setTimeout(processQueryParams, 1000);
    });
})();

QwenChat 油猴脚本

// ==UserScript==
// @name         QWen Chat 网页版 URL 增加 query 搜索
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  为 QWen Chat 网页版增加 query string 的搜索功能,q 是 keyword , r=true 是否开启深度思考,s=true 是开启联网搜索,temporary-chat=true是开启临时对话
// @author       Wilson 改自 阿依帝 with DeepSeek R1 (https://space.bilibili.com/103021226)
// @match        https://chat.qwen.ai/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=qwen.ai
// @license      MIT
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 解析URL参数
    function getQueryParam(name) {
        const params = new URLSearchParams(window.location.search);
        return params.get(name);
    }

    // 查找包含指定文本的按钮
    function findButtonByText(text) {
        const xpath = `//button//span[contains(text(), '${text}')]`;
        const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        return result.singleNodeValue?.closest('button');
    }

    // 检查按钮是否激活
    function isButtonActive(button) {
        return getComputedStyle(button).getPropertyValue('backgroundColor').includes('rgb(224, 223, 255)');
    }

    // 触发React的输入事件
    function setReactInputValue(element, value) {
        const inputEvent = new Event('input', { bubbles: true, composed: true });
        const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
            window.HTMLTextAreaElement.prototype,
            'value'
        ).set;
        nativeInputValueSetter.call(element, value);
        element.dispatchEvent(inputEvent);
    }

    // 处理模式切换
    async function toggleMode(button, shouldEnable) {
        if (!button) return;
         await new Promise(r => setTimeout(r, 100));
        const isActive = isButtonActive(button);
        if (shouldEnable && !isActive) {
            button.click();
            await new Promise(r => setTimeout(r, 200));
        }
        if (!shouldEnable && isActive) {
            button.click();
            await new Promise(r => setTimeout(r, 200));
        }
    }

    // 主处理函数
    async function processQueryParams() {
        // 获取参数(已修复括号问题)
        const qParam = getQueryParam('q');
        const query = qParam ? decodeURIComponent(qParam) : '';
        const needDeepThinking = getQueryParam('r') === 'true';
        const search = getQueryParam('s') === 'true';
        //alert(query+'|'+needDeepThinking+'|'+search);

        if (!query) return;

        // 等待必要元素加载
        const maxWaitTime = 5000;
        const startTime = Date.now();

        // 等待输入框加载
        let textarea;
        while (!(textarea = document.getElementById('chat-input')) && Date.now() - startTime < maxWaitTime) {
            await new Promise(r => setTimeout(r, 100));
        }

        if (!textarea) {
            console.error('找不到输入框');
            return;
        }

        // 填充查询内容
        setReactInputValue(textarea, query);

        // 强制开启联网搜索
        const webSearchBtn = findButtonByText('搜索');
        await toggleMode(webSearchBtn, search);

        // 处理深度思考模式
        const deepThinkBtn = findButtonByText('深度思考');
        await toggleMode(deepThinkBtn, needDeepThinking);

        // 点击发送按钮
        const sendBtn = document.querySelector('#send-message-button');
        if (sendBtn) {
            sendBtn.click();
        } else {
            const observer = new MutationObserver(() => {
                const activeSendBtn = document.querySelector('#send-message-button');
                if (activeSendBtn) {
                    observer.disconnect();
                    activeSendBtn.click();
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }
    }

    // 页面加载完成后执行
    window.addEventListener('load', () => {
        setTimeout(processQueryParams, 1000);
    });
})();

通义千问油猴脚本

// ==UserScript==
// @name         通义千问 网页版 URL 增加 query 搜索
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  为通义千问网页版增加 query string 的搜索功能,q 是 keyword
// @author       Wilson 改自 阿依帝 with DeepSeek R1 (https://space.bilibili.com/103021226)
// @match        https://tongyi.aliyun.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tongyi.aliyun.com
// @license      MIT
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 解析URL参数
    function getQueryParam(name) {
        const params = new URLSearchParams(window.location.search);
        return params.get(name);
    }

    // 触发React的输入事件
    function setReactInputValue(element, value) {
        const inputEvent = new Event('input', { bubbles: true, composed: true });
        const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
            window.HTMLTextAreaElement.prototype,
            'value'
        ).set;
        nativeInputValueSetter.call(element, value);
        element.dispatchEvent(inputEvent);
    }

    // 主处理函数
    async function processQueryParams() {
        // 获取参数(已修复括号问题)
        const qParam = getQueryParam('q');
        const query = qParam ? decodeURIComponent(qParam) : '';

        if (!query) return;

        // 等待必要元素加载
        const maxWaitTime = 5000;
        const startTime = Date.now();

        // 等待输入框加载
        let textarea;
        while (!(textarea = document.querySelector('.ant-input')) && Date.now() - startTime < maxWaitTime) {
            await new Promise(r => setTimeout(r, 100));
        }

        if (!textarea) {
            console.error('找不到输入框');
            return;
        }

        // 填充查询内容
        setReactInputValue(textarea, query);

        // 点击发送按钮
        const sendBtn = Array.from(document.querySelectorAll('use')).find(node => node.getAttribute('xlink:href') === '#icon-fasong_default')?.closest('div');
        if (sendBtn) {
            sendBtn.click();
        } else {
            const observer = new MutationObserver(() => {
                const activeSendBtn = Array.from(document.querySelectorAll('use')).find(node => node.getAttribute('xlink:href') === '#icon-fasong_default')?.closest('div');
                if (activeSendBtn) {
                    observer.disconnect();
                    activeSendBtn.click();
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }
    }

    // 页面加载完成后执行
    window.addEventListener('load', () => {
        setTimeout(processQueryParams, 1000);
    });
})();

AI 划词搜索

兼容所有网页

image.png

// ==UserScript==
// @name         AI搜索
// @namespace    http://tampermonkey.net/
// @version      2025-03-13
// @description  AI搜索
// @author       Wilson
// @match        http*://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // AI搜索引擎, %s% 是搜索关键词
    // https://chat.qwen.ai/?q=%s%
    const AISearch = 'https://chat.baidu.com/search?word=%s%';

    GM_addStyle(`
     /* 提示框样式 */
     #search-tip {
      position: absolute;
      display: none;
      padding: 5px 10px;
      background-color: #007bff;
      color: white;
      border-radius: 4px;
      cursor: pointer;
      box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
      z-index: 1000;
      white-space: nowrap; /* 防止文本换行 */
    }
    `);

    document.body.insertAdjacentHTML('beforeend', `
    <!-- 搜索提示框 -->
    <div id="search-tip">AI搜<span id="selected-text"></span></div>
    `);

    // 获取相关 DOM 元素
    const searchTip = document.getElementById('search-tip');
    const selectedTextSpan = document.getElementById('selected-text');

    // 监听鼠标松开事件
    document.addEventListener('mouseup', (event) => {
      // 获取选中的文本
      const selection = window.getSelection().toString().trim();

      // 如果没有选中文本,则隐藏提示框并退出
      if (!selection) {
        searchTip.style.display = 'none';
        return;
      }

      // 更新提示框内容
      //selectedTextSpan.textContent = selection;

      // 获取鼠标位置(基于文档坐标)
      const { pageX, pageY } = event;

      // 设置提示框的位置(略微偏移鼠标位置)
      searchTip.style.left = `${pageX + 10}px`; // 向右偏移 10px
      searchTip.style.top = `${pageY + 10}px`; // 向下偏移 10px

      // 显示提示框
      searchTip.style.display = 'block';

      // 点击提示框时跳转到百度搜索
      searchTip.onclick = () => {
        const encodedQuery = encodeURIComponent(selection); // 对选中文本进行编码
        window.open(AISearch.replace('%s%', encodedQuery), '_blank'); // 跳转到百度搜索
      };
    });

    // 点击页面其他地方时隐藏提示框
    document.addEventListener('mousedown', (event) => {
      if (!searchTip.contains(event.target)) {
        searchTip.style.display = 'none';
      }
    });
})();

UI 美化

目前仅支持 QwenChat。

主要对问题的背景色进行了调整,方便滚动浏览时分清每条问答,之前的颜色太浅,不容易区分。

还让对聊天页面的滚动条显示出来,原来是不显示的,只能滚轮滚动,不能用鼠标拖动。

// ==UserScript==
// @name         AI美化
// @namespace    http://tampermonkey.net/ai
// @version      2025-03-12
// @description  AI UI美化
// @author       Wilson
// @match        https://chat.qwen.ai/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    if (location.href.indexOf("chat.qwen.ai")!==-1) {
        GM_addStyle(`
            /* 用户问题 */
            .user-message div:has(> p){background-color: #E0E0FD;}

            ::-webkit-scrollbar {
              width: 10px;
            }

            ::-webkit-scrollbar-track {
              background: #fafafa;
              border-radius: 5px;
              box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.1);
            }

            ::-webkit-scrollbar-thumb {
              background: #c1c1c1;
              border-radius: 5px;
              border: 2px solid #fafafa;
            }

            ::-webkit-scrollbar-thumb:hover {
              background: #a8a8a8;
            }

            /* Firefox */
            body {
              scrollbar-width: thin;
              scrollbar-color: #c1c1c1 #fafafa;
            }
        `);
    }
})();

更多 AI 的支持

更多 AI 的支持,感兴趣的小伙伴可以根据上面的脚本继续改造。

补充

这里的 QwenChat 是指 https://chat.qwen.ai/ 不是指 https://tongyi.aliyun.com/qianwen/

感觉比国内版的通义千问强大和好用。

  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    24797 引用 • 101977 回帖 • 1 关注
  • 油猴
    8 引用 • 34 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...