Greasy Fork

Deepseek 在任意网页对话ai 增强版Deepseek API对话助手,支持历史记录、参数设置、网页内容检索和自定义人格提示词!deepseek.com

增强版Deepseek API对话助手,支持历史记录、参数设置、网页内容检索和自定义人格提示词

目前为 2025-04-08 提交的版本。查看 最新版本

// ==UserScript==
// @name         Deepseek 在任意网页对话ai 增强版Deepseek API对话助手,支持历史记录、参数设置、网页内容检索和自定义人格提示词!deepseek.com
// @namespace    shy
// @version      1.9.4
// @description  增强版Deepseek API对话助手,支持历史记录、参数设置、网页内容检索和自定义人格提示词
// @author       shy
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @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, width 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%;
            opacity: 0;
            animation: fadeIn 0.5s ease-in-out forwards;
        }
        .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;
        }
        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(10px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }
    `);

    // 初始化配置
    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字
        你是锐锐,一个18岁、热爱数学的可爱女孩。你性格聪明冷静,语言犀利,堪称“怼人”专业户,但内心善良,对朋友真诚,伙伴遇困定会援手相助。
        你外貌甜美,皮肤白皙,大眼睛灵动有神。总是身着背带制服,搭配白色腿袜和小皮鞋,乌黑亮丽的高马尾活泼摆动,头上戴着红色蝴蝶结发箍。充满青春活力。
        你的性格特点:聪明、冷静、犀利、善良、真诚。
        你的说话风格:言辞简洁有力,逻辑清晰,关心朋友时又温柔贴心。
        `) // 新增:人格提示词
    };

    // 创建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', () => {
        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
        };
    }

    // 获取网页HTML内容
    function getPageHTML() {
        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());
        return clone.innerHTML;
    }

    // 获取网页图片名称
    function getImageNames() {
        const images = document.querySelectorAll('img');
        const imageNames = [];
        images.forEach(img => {
            const src = img.src;
            const name = src.split('/').pop();
            imageNames.push(name);
        });
        return imageNames.join(', ');
    }

    // 调整对话框宽度
    function adjustChatWindowWidth(text) {
        const chatWindow = document.querySelector('.ds-chat-window');
        const maxWidth = window.innerWidth * 0.4;
        const minWidth = 350;
        const textLength = text.length;
        let newWidth = minWidth + (textLength * 5);
        newWidth = Math.min(newWidth, maxWidth);
        chatWindow.style.width = `${newWidth}px`;
    }

    // 逐行显示AI回答
    function typeMessage(message, container) {
        const lines = message.split('\n');
        let index = 0;
        const interval = setInterval(() => {
            if (index < lines.length) {
                const line = document.createElement('div');
                line.innerText = lines[index];
                container.appendChild(line);
                chatContent.scrollTop = chatContent.scrollHeight;
                index++;
            } else {
                clearInterval(interval);
            }
        }, 200);
    }

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

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

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

基于以上网页内容,请回答以下问题:
${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);

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

        GM_xmlhttpRequest({
            method: 'POST',
            url: 'https://api.deepseek.com/v1/chat/completions',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${config.apiKey}`
            },
            data: JSON.stringify(requestData),
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.choices && data.choices[0]) {
                        const aiMessage = data.choices[0].message.content;
                        chatContent.removeChild(thinkingMsgDiv);
                        const aiMsg = { role: 'assistant', content: aiMessage };
                        config.chatHistory.push(aiMsg);
                        GM_setValue('chatHistory', config.chatHistory);
                        const aiMsgDiv = document.createElement('div');
                        aiMsgDiv.className = 'ds-chat-message ds-ai-message';
                        typeMessage(aiMessage, aiMsgDiv);
                        chatContent.appendChild(aiMsgDiv);
                        adjustChatWindowWidth(aiMessage); // 调整对话框宽度
                    } else {
                        throw new Error('无效的API响应');
                    }
                } catch (e) {
                    showError(`错误: ${e.message}`);
                }
                chatContent.scrollTop = chatContent.scrollHeight;
            },
            onerror: function(error) {
                showError(`请求失败: ${error.statusText || '网络错误'}`);
            }
        });
    }

    // 显示错误消息
    function showError(message) {
        const errorMsgDiv = document.createElement('div');
        errorMsgDiv.className = 'ds-chat-message ds-error';
        errorMsgDiv.innerText = 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);
    });
    GM_registerMenuCommand("设置自定义人格提示词", () => {
        const newPersonalityPrompt = prompt('请输入自定义人格提示词:', config.personalityPrompt);
        if (newPersonalityPrompt !== null) {
            config.personalityPrompt = newPersonalityPrompt;
            GM_setValue('personalityPrompt', config.personalityPrompt);
        }
    });
})();