Greasy Fork

Greasy Fork is available in English.

Deepseek 网页对话助手增强版

支持流式响应、历史记录、参数设置和网页内容检索

当前为 2025-04-08 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Deepseek 网页对话助手增强版
// @namespace    shy
// @version      1.9.5
// @description  支持流式响应、历史记录、参数设置和网页内容检索
// @author       shy
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @connect      api.deepseek.com
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 添加CSS样式
    GM_addStyle(`
        .ds-chat-icon {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 50px;
            height: 50px;
            background-color: rgba(0, 123, 255, 0.5);
            border-radius: 50%;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #fff;
            font-size: 24px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
            transition: transform 0.2s, box-shadow 0.2s;
            z-index: 2147483647;
            backdrop-filter: blur(5px);
            border: 1px solid rgba(255, 255, 255, 0.4);
        }
        .ds-chat-icon:hover {
            transform: scale(1.05);
            box-shadow: 0 6px 8px rgba(0, 0, 0, 0.3);
            background-color: rgba(0, 123, 255, 0.6);
        }
        .ds-chat-window {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 350px;
            max-width: 40vw;
            max-height: 70vh;
            background-color: rgba(249, 249, 249, 0.3);
            border: 1px solid #ddd;
            border-radius: 15px;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
            display: none;
            flex-direction: column;
            overflow: hidden;
            transition: opacity 0.3s, transform 0.3s;
            opacity: 0;
            transform: translateY(20px);
            z-index: 2147483646;
            backdrop-filter: blur(5px);
        }
        .ds-chat-window.active {
            display: flex;
            opacity: 1;
            transform: translateY(0);
        }
        .ds-chat-header {
            padding: 10px 15px;
            background-color: rgba(0, 123, 255, 0.3);
            color: white;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-radius: 15px 15px 0 0;
        }
        .ds-chat-title {
            font-weight: bold;
        }
        .ds-chat-close {
            cursor: pointer;
            font-size: 18px;
        }
        .ds-chat-content {
            flex: 1;
            padding: 15px;
            overflow-y: auto;
            background-color: rgba(255, 255, 255, 0.3);
            border-bottom: 1px solid #ddd;
        }
        .ds-chat-message {
            margin-bottom: 10px;
            padding: 8px 12px;
            border-radius: 8px;
            line-height: 1.4;
            word-wrap: break-word;
        }
        .ds-user-message {
            background-color: rgba(227, 242, 253, 0.7);
            margin-left: auto;
            text-align: right;
        }
        .ds-ai-message {
            background-color: rgba(241, 241, 241, 0.7);
            margin-right: 20%;
        }
        .ds-chat-input-area {
            padding: 10px;
            display: flex;
            flex-direction: column;
            backdrop-filter: blur(5px);
            background-color: rgba(255, 255, 255, 0.3);
            border-top: 1px solid rgba(221, 221, 221, 0.5);
        }
        .ds-chat-input {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 8px;
            margin-bottom: 8px;
            outline: none;
            transition: border-color 0.3s;
        }
        .ds-chat-input:focus {
            border-color: #007bff;
        }
        .ds-chat-settings {
            display: flex;
            justify-content: space-between;
            font-size: 12px;
            color: #666;
        }
        .ds-chat-settings-btn {
            cursor: pointer;
            text-decoration: underline;
        }
        .ds-thinking {
            color: #666;
            font-style: italic;
        }
        .ds-error {
            color: #ff0000;
        }
        .ds-context-toggle {
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            font-size: 12px;
        }
        .ds-context-toggle input {
            margin-right: 5px;
        }
        .ds-context-summary {
            font-size: 11px;
            color: #666;
            margin-top: 5px;
            font-style: italic;
        }
    `);

    // 初始化配置
    let config = {
        apiKey: GM_getValue('apiKey', ''),
        model: GM_getValue('model', 'deepseek-chat'),
        temperature: GM_getValue('temperature', 0.7),
        maxTokens: GM_getValue('maxTokens', 5000),
        chatHistory: GM_getValue('chatHistory', []),
        usePageContext: GM_getValue('usePageContext', true),
        personalityPrompt: GM_getValue('personalityPrompt', '输出文本控制不能超过600字\n你是锐锐,一个18岁、热爱数学的可爱女孩。你性格聪明冷静,语言犀利,堪称"怼人"专业户,但内心善良,对朋友真诚,伙伴遇困定会援手相助。\n你外貌甜美,皮肤白皙,大眼睛灵动有神。总是身着背带制服,搭配白色腿袜和小皮鞋,乌黑亮丽的高马尾活泼摆动,头上戴着红色蝴蝶结发箍。充满青春活力。\n你的性格特点:聪明、冷静、犀利、善良、真诚。\n你的说话风格:言辞简洁有力,逻辑清晰,关心朋友时又温柔贴心。')
    };

    // 创建UI元素
    const icon = document.createElement('div');
    icon.className = 'ds-chat-icon';
    icon.innerText = 'AI';
    document.body.appendChild(icon);

    const chatWindow = document.createElement('div');
    chatWindow.className = 'ds-chat-window';
    document.body.appendChild(chatWindow);

    const chatHeader = document.createElement('div');
    chatHeader.className = 'ds-chat-header';
    chatWindow.appendChild(chatHeader);

    const chatTitle = document.createElement('div');
    chatTitle.className = 'ds-chat-title';
    chatTitle.innerText = 'Deepseek Chat';
    chatHeader.appendChild(chatTitle);

    const closeBtn = document.createElement('div');
    closeBtn.className = 'ds-chat-close';
    closeBtn.innerText = '×';
    chatHeader.appendChild(closeBtn);

    const chatContent = document.createElement('div');
    chatContent.className = 'ds-chat-content';
    chatWindow.appendChild(chatContent);

    const inputArea = document.createElement('div');
    inputArea.className = 'ds-chat-input-area';
    chatWindow.appendChild(inputArea);

    const contextToggle = document.createElement('div');
    contextToggle.className = 'ds-context-toggle';
    inputArea.appendChild(contextToggle);

    const contextCheckbox = document.createElement('input');
    contextCheckbox.type = 'checkbox';
    contextCheckbox.id = 'ds-context-checkbox';
    contextCheckbox.checked = config.usePageContext;
    contextToggle.appendChild(contextCheckbox);

    const contextLabel = document.createElement('label');
    contextLabel.htmlFor = 'ds-context-checkbox';
    contextLabel.innerText = '包含当前网页内容';
    contextToggle.appendChild(contextLabel);

    const contextSummary = document.createElement('div');
    contextSummary.className = 'ds-context-summary';
    contextSummary.innerText = '当前网页: ' + document.title;
    inputArea.appendChild(contextSummary);

    const inputBox = document.createElement('textarea');
    inputBox.className = 'ds-chat-input';
    inputBox.placeholder = '输入你的问题...';
    inputBox.rows = 3;
    inputArea.appendChild(inputBox);

    const settingsArea = document.createElement('div');
    settingsArea.className = 'ds-chat-settings';
    inputArea.appendChild(settingsArea);

    const settingsBtn = document.createElement('span');
    settingsBtn.className = 'ds-chat-settings-btn';
    settingsBtn.innerText = '⚙️';
    settingsArea.appendChild(settingsBtn);

    const clearBtn = document.createElement('span');
    clearBtn.className = 'ds-chat-settings-btn';
    clearBtn.innerText = '🗑️';
    settingsArea.appendChild(clearBtn);

    // 显示历史消息
    function displayHistory() {
        chatContent.innerHTML = '';
        config.chatHistory.forEach(msg => {
            const msgDiv = document.createElement('div');
            msgDiv.className = `ds-chat-message ds-${msg.role}-message`;
            msgDiv.innerText = msg.content;
            chatContent.appendChild(msgDiv);
        });
        chatContent.scrollTop = chatContent.scrollHeight;
    }

    displayHistory();

    // 事件监听
    icon.addEventListener('click', () => {
        chatWindow.classList.toggle('active');
        icon.style.display = 'none';
    });

    closeBtn.addEventListener('click', () => {
        chatWindow.classList.remove('active');
        icon.style.display = 'flex';
    });

    contextCheckbox.addEventListener('change', () => {
        config.usePageContext = contextCheckbox.checked;
        GM_setValue('usePageContext', config.usePageContext);
    });

    settingsBtn.addEventListener('click', () => {
        const newApiKey = prompt('DeepSeek API密钥:', config.apiKey);
        if (newApiKey !== null) {
            config.apiKey = newApiKey;
            GM_setValue('apiKey', config.apiKey);
        }

        const newModel = prompt('模型 (deepseek-chat, deepseek-coder等):', config.model);
        if (newModel !== null) {
            config.model = newModel;
            GM_setValue('model', config.model);
        }

        const newTemp = parseFloat(prompt('Temperature (0-2):', config.temperature));
        if (!isNaN(newTemp) && newTemp >= 0 && newTemp <= 2) {
            config.temperature = newTemp;
            GM_setValue('temperature', config.temperature);
        }

        const newMaxTokens = parseInt(prompt('最大令牌数:', config.maxTokens));
        if (!isNaN(newMaxTokens) && newMaxTokens > 0) {
            config.maxTokens = newMaxTokens;
            GM_setValue('maxTokens', config.maxTokens);
        }

        const newPersonalityPrompt = prompt('自定义人格提示词:', config.personalityPrompt);
        if (newPersonalityPrompt !== null) {
            config.personalityPrompt = newPersonalityPrompt;
            GM_setValue('personalityPrompt', config.personalityPrompt);
        }
    });

    clearBtn.addEventListener('click', () => {
        if (confirm('确定要清空聊天历史吗?')) {
            config.chatHistory = [];
            GM_setValue('chatHistory', config.chatHistory);
            chatContent.innerHTML = '';
        }
    });

    // 获取网页主要内容
    function getPageContent() {
        const mainContent = document.querySelector('main, article, .main, .content, #content') || document.body;
        const clone = mainContent.cloneNode(true);
        const elementsToRemove = clone.querySelectorAll('script, style, noscript, iframe, nav, footer, header, aside');
        elementsToRemove.forEach(el => el.remove());
        let text = clone.textContent
            .replace(/\s+/g, ' ')
            .trim()
            .substring(0, 5000);
        return {
            url: window.location.href,
            title: document.title,
            content: text
        };
    }

    // 流式响应处理
    function handleStreamResponse(response, aiMsgDiv) {
        const decoder = new TextDecoder();
        const reader = response.body.getReader();
        let buffer = '';
        let aiMessage = '';

        function readChunk() {
            return reader.read().then(({ done, value }) => {
                if (done) {
                    // 完成响应后保存完整消息
                    config.chatHistory.push({ role: 'assistant', content: aiMessage });
                    GM_setValue('chatHistory', config.chatHistory);
                    return;
                }

                buffer += decoder.decode(value, { stream: true });
                const lines = buffer.split('\n');
                buffer = lines.pop();

                for (const line of lines) {
                    if (line.startsWith('data:') && line !== 'data: [DONE]') {
                        try {
                            const data = JSON.parse(line.substring(5));
                            if (data.choices && data.choices[0].delta && data.choices[0].delta.content) {
                                const content = data.choices[0].delta.content;
                                aiMessage += content;
                                aiMsgDiv.innerText = aiMessage;
                                chatContent.scrollTop = chatContent.scrollHeight;
                            }
                        } catch (e) {
                            console.error('解析流数据错误:', e);
                        }
                    }
                }

                return readChunk();
            });
        }

        return readChunk();
    }

    // 发送消息函数
    function sendMessage(message) {
        if (!message.trim()) return;

        if (!config.apiKey) {
            alert('请先设置API密钥!');
            settingsBtn.click();
            return;
        }

        let finalMessage = message;
        if (config.usePageContext) {
            const pageContent = getPageContent();
            finalMessage = `[当前网页信息]
标题: ${pageContent.title}
URL: ${pageContent.url}
内容摘要: ${pageContent.content}

基于以上网页内容,请回答以下问题:
${message}`;
        }

        const userMsg = { role: 'user', content: message };
        config.chatHistory.push(userMsg);
        GM_setValue('chatHistory', config.chatHistory);

        const userMsgDiv = document.createElement('div');
        userMsgDiv.className = 'ds-chat-message ds-user-message';
        userMsgDiv.innerText = message;
        chatContent.appendChild(userMsgDiv);

        const thinkingMsgDiv = document.createElement('div');
        thinkingMsgDiv.className = 'ds-chat-message ds-thinking';
        thinkingMsgDiv.innerText = '思考中...';
        chatContent.appendChild(thinkingMsgDiv);

        const aiMsgDiv = document.createElement('div');
        aiMsgDiv.className = 'ds-chat-message ds-ai-message';
        chatContent.appendChild(aiMsgDiv);

        chatContent.scrollTop = chatContent.scrollHeight;

        const requestData = {
            model: config.model,
            messages: [
                { role: 'system', content: config.personalityPrompt },
                ...config.chatHistory.map(msg => ({
                    role: msg.role,
                    content: msg.role === 'user' && config.usePageContext ? finalMessage : msg.content
                }))
            ],
            temperature: config.temperature,
            max_tokens: config.maxTokens,
            stream: true  // 启用流式响应
        };

        fetch('https://api.deepseek.com/v1/chat/completions', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${config.apiKey}`
            },
            body: JSON.stringify(requestData)
        }).then(response => {
            chatContent.removeChild(thinkingMsgDiv);
            if (!response.ok) {
                throw new Error(response.statusText);
            }
            if (!response.body) {
                throw new Error('响应体不可读');
            }
            return handleStreamResponse(response, aiMsgDiv);
        }).catch(error => {
            chatContent.removeChild(thinkingMsgDiv);
            const errorMsgDiv = document.createElement('div');
            errorMsgDiv.className = 'ds-chat-message ds-error';
            errorMsgDiv.innerText = `错误: ${error.message}`;
            chatContent.appendChild(errorMsgDiv);
            chatContent.scrollTop = chatContent.scrollHeight;
        });
    }

    // 输入框事件
    inputBox.addEventListener('keydown', (e) => {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            const message = inputBox.value.trim();
            if (message) {
                sendMessage(message);
                inputBox.value = '';
            }
        }
    });

    // 注册菜单命令
    GM_registerMenuCommand("设置DeepSeek API", () => settingsBtn.click());
    GM_registerMenuCommand("清空聊天历史", () => clearBtn.click());
    GM_registerMenuCommand("切换网页上下文", () => {
        contextCheckbox.checked = !contextCheckbox.checked;
        config.usePageContext = contextCheckbox.checked;
        GM_setValue('usePageContext', config.usePageContext);
    });
})();