DeepSeek R1 网页划词提问

支持弹窗交互的专业解释工具

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         DeepSeek R1 网页划词提问
// @namespace    http://tampermonkey.net/
// @version      0.13
// @description  支持弹窗交互的专业解释工具
// @author       Your Name
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      GPL-3.0 License
// @connect      api.deepseek.com
// ==/UserScript==

(function() {
    'use strict';

    const API_URL = 'https://api.deepseek.com/v1/chat/completions';
    let currentPopup = null;
    let currentButton = null;
    let currentToast = null;

    // 专业排版样式
    const style = document.createElement('style');
    style.textContent = `
        @keyframes fadeIn {
            from { opacity: 0; transform: translate(-50%, -45%); }
            to { opacity: 1; transform: translate(-50%, -50%); }
        }

        .ds-msg-content {
            line-height: 1.7;
            font-size: 14px;
            color: #333;
        }

        .ds-msg-content p {
            margin: 12px 0;
        }

        .ds-msg-content code {
            background: rgba(175,184,193,0.2);
            padding: 2px 4px;
            border-radius: 4px;
            font-family: 'SFMono-Regular', Consolas, monospace;
        }

        .ds-msg-content pre {
            background: #f8f9fa;
            padding: 14px;
            border-radius: 8px;
            overflow-x: auto;
            margin: 16px 0;
            border: 1px solid #eee;
        }

        .ds-msg-content pre code {
            background: transparent;
            padding: 0;
            font-size: 13px;
        }

        .ds-msg-content ul,
        .ds-msg-content ol {
            margin: 12px 0;
            padding-left: 24px;
        }

        .ds-msg-content li {
            margin: 8px 0;
            padding-left: 8px;
        }

        .ds-msg-content strong {
            font-weight: 600;
            color: #2d2d2d;
        }

        .ds-msg-content em {
            font-style: italic;
        }

        .ds-popup {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
        }
    `;
    document.head.appendChild(style);

    GM_registerMenuCommand('设置DeepSeek API密钥', () => {
        const apiKey = prompt('请输入您的DeepSeek API密钥:', GM_getValue('api_key', ''));
        if (apiKey !== null) {
            GM_setValue('api_key', apiKey.trim());
        }
    });

    function showToast(message, duration=2000) {
        if (currentToast) currentToast.remove();

        const toast = document.createElement('div');
        toast.textContent = message;
        toast.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0,0,0,0.8);
            color: white;
            padding: 8px 16px;
            border-radius: 4px;
            z-index: 99999;
            font-size: 14px;
            animation: fadeIn 0.3s;
        `;

        document.body.appendChild(toast);
        currentToast = toast;

        setTimeout(() => toast.remove(), duration);
    }

    function createActionButton(x, y) {
        if (currentButton) currentButton.remove();

        const btn = document.createElement('div');
        btn.innerHTML = '🔍Deepseek';
        btn.style.cssText = `
            position: absolute;
            left: ${x + 15}px;
            top: ${y}px;
            background: #4CAF50;
            color: white;
            padding: 5px 10px;
            border-radius: 4px;
            cursor: pointer;
            z-index: 10000;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            font-size: 12px;
            animation: fadeIn 0.2s;
        `;

        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            btn.remove();
            currentButton = null;
        });

        return btn;
    }

    function safeHTML(content) {
        const div = document.createElement('div');
        div.textContent = content;
        return div.innerHTML;
    }

    function formatContent(text) {
        let html = safeHTML(text)
            .replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
            .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
            .replace(/\*(.*?)\*/g, '<em>$1</em>')
            .replace(/^-\s+(.+)$/gm, '<li>$1</li>')
            .replace(/(<li>.*<\/li>)/gs, '<ul>$1</ul>')
            .replace(/\n/g, '<br>');

        return `<div class="ds-msg-content">${html}</div>`;
    }

    function showInteractivePopup(messages) {
        if (currentPopup) currentPopup.remove();

        const popup = document.createElement('div');
        popup.className = 'ds-popup';
        popup.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: min(90%, 600px);
            min-width: 300px;
            min-height: 200px;
            background: white;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            z-index: 100000;
            animation: fadeIn 0.3s;
            display: flex;
            flex-direction: column;
            max-height: 80vh;
            resize: both;
            overflow: hidden;
        `;

        // 拖拽功能
        let isDragging = false;
        let startX, startY, initLeft, initTop;

        const handleMove = (e) => {
            if (!isDragging) return;
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            popup.style.left = `${initLeft + dx}px`;
            popup.style.top = `${initTop + dy}px`;
            popup.style.transform = 'none';
            checkBoundary();
        };

        const handleMouseUp = () => {
            isDragging = false;
            document.removeEventListener('mousemove', handleMove);
            document.removeEventListener('mouseup', handleMouseUp);
        };

        // 调整大小功能
        const resizeHandle = document.createElement('div');
        resizeHandle.style.cssText = `
            position: absolute;
            right: 0;
            bottom: 0;
            width: 15px;
            height: 15px;
            cursor: nwse-resize;
            background: transparent;
            z-index: 100;
        `;

        let isResizing = false;
        let startWidth, startHeight, startResizeX, startResizeY;

        resizeHandle.addEventListener('mousedown', (e) => {
            isResizing = true;
            startResizeX = e.clientX;
            startResizeY = e.clientY;
            const style = getComputedStyle(popup);
            startWidth = parseInt(style.width);
            startHeight = parseInt(style.height);
            document.addEventListener('mousemove', handleResize);
            document.addEventListener('mouseup', () => {
                isResizing = false;
                document.removeEventListener('mousemove', handleResize);
            });
        });

        function handleResize(e) {
            if (!isResizing) return;
            const dx = e.clientX - startResizeX;
            const dy = e.clientY - startResizeY;

            const newWidth = Math.max(300, startWidth + dx);
            const newHeight = Math.max(200, startHeight + dy);

            popup.style.width = `${newWidth}px`;
            popup.style.height = `${newHeight}px`;
            checkBoundary();
        }

        // 弹窗头部
        const header = document.createElement('div');
        header.style.cssText = `
            padding: 12px;
            border-bottom: 1px solid #eee;
            display: flex;
            justify-content: space-between;
            align-items: center;
            background: #f8f9fa;
            border-radius: 8px 8px 0 0;
            cursor: move;
            user-select: none;
        `;

        header.addEventListener('mousedown', (e) => {
            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;
            const rect = popup.getBoundingClientRect();
            initLeft = rect.left;
            initTop = rect.top;
            document.addEventListener('mousemove', handleMove);
            document.addEventListener('mouseup', handleMouseUp);
        });

        // 弹窗内容
        const title = document.createElement('span');
        title.textContent = 'DeepSeek:';
        title.style.cssText = 'font-weight: 600; color: #333;';

        const closeBtn = document.createElement('div');
        closeBtn.innerHTML = '×';
        closeBtn.style.cssText = `
            cursor: pointer;
            font-size: 24px;
            color: #666;
            padding: 0 8px;
            line-height: 1;
            &:hover { color: #333; }
        `;
        closeBtn.onclick = () => popup.remove();

        const content = document.createElement('div');
        content.style.cssText = `
            flex: 1;
            padding: 16px;
            overflow-y: auto;
        `;

        messages.slice(1).forEach(msg => {
            const msgDiv = document.createElement('div');
            msgDiv.style.cssText = `
                margin: 12px 0;
                padding: 14px;
                border-radius: 8px;
                background: ${msg.role === 'user' ? '#f5f6f7' : '#f0f7ff'};
            `;
            msgDiv.innerHTML = formatContent(msg.content);
            content.appendChild(msgDiv);
        });

        // 输入区域
        const inputContainer = document.createElement('div');
        inputContainer.style.cssText = `
            padding: 12px;
            border-top: 1px solid #eee;
            background: #f8f9fa;
        `;

        const input = document.createElement('textarea');
        input.style.cssText = `
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            resize: vertical;
            min-height: 40px;
            font-family: inherit;
        `;
        input.placeholder = '输入后续问题...';

        const sendBtn = document.createElement('button');
        sendBtn.textContent = '发送';
        sendBtn.style.cssText = `
            margin-top: 8px;
            padding: 8px 20px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            float: right;
            transition: opacity 0.2s;
            &:hover { opacity: 0.9; }
        `;

        sendBtn.onclick = () => {
            const userMessage = input.value.trim();
            if (!userMessage) return;

            messages.push({ role: 'user', content: userMessage });
            input.value = '';
        };

        // 组装元素
        header.append(title, closeBtn);
        inputContainer.append(input, sendBtn);
        popup.append(header, content, inputContainer, resizeHandle);
        document.body.appendChild(popup);
        currentPopup = popup;

        // 边界检查
        function checkBoundary() {
            const rect = popup.getBoundingClientRect();
            const buffer = 20;

            if (rect.left < -buffer) popup.style.left = `${-buffer}px`;
            if (rect.top < -buffer) popup.style.top = `${-buffer}px`;
            if (rect.right > window.innerWidth + buffer) {
                popup.style.left = `${window.innerWidth - rect.width + buffer}px`;
            }
            if (rect.bottom > window.innerHeight + buffer) {
                popup.style.top = `${window.innerHeight - rect.height + buffer}px`;
            }
        }

        content.scrollTop = content.scrollHeight;
    }

    function getExplanation(text) {
        const apiKey = GM_getValue('api_key');
        if (!apiKey) {
            alert('请先通过油猴菜单设置API密钥');
            return;
        }

        showToast('Deep...Seek...稍等...速度取决于API...', 6000);

        const initialMessages = [
            {
                "role": "system",
                "content": "你是一个智能百科助手,请用简洁易懂的中文回答,保持自然的口语化风格。使用Markdown基础格式进行排版。"
            },
            {
                "role": "user",
                "content": `解释分析推理总结:${text}`
            }
        ];

        GM_xmlhttpRequest({
            method: "POST",
            url: API_URL,
            headers: {
                "Content-Type": "application/json",
                "Authorization": `Bearer ${apiKey}`
            },
            data: JSON.stringify({
                model: "deepseek-reasoner",
                messages: initialMessages,
                temperature: 0.7,
                max_tokens: 512
            }),
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    const result = data.choices[0].message.content;
                    initialMessages.push({ role: 'assistant', content: result });
                    showInteractivePopup(initialMessages);
                } catch (e) {
                    showInteractivePopup([...initialMessages,
                        { role: 'assistant', content: '⚠️ 解析响应失败,请重试' }
                    ]);
                }
            },
            onerror: function(err) {
                showInteractivePopup([...initialMessages,
                    { role: 'assistant', content: '⚠️ 请求失败,请检查网络连接' }
                ]);
            }
        });
    }

    // 文本选择监听
    document.addEventListener('mouseup', function(e) {
        const selection = window.getSelection().toString().trim();
        if (selection && selection.length > 2) {
            const range = window.getSelection().getRangeAt(0);
            const rect = range.getBoundingClientRect();
            const btn = createActionButton(rect.right + window.scrollX, rect.top + window.scrollY);
            btn.onclick = () => getExplanation(selection);
            document.body.appendChild(btn);
            currentButton = btn;
        }
    });

    document.addEventListener('mousedown', (e) => {
        if (currentButton && !currentButton.contains(e.target)) {
            currentButton.remove();
            currentButton = null;
        }
    });
})();