// ==UserScript==
// @name DeepSeek R1 网页划词提问
// @namespace http://tampermonkey.net/
// @version 0.15
// @description 支持弹窗交互的专业解释工具(优化版)
// @author Your Name
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @connect api.deepseek.com
// @license GPL-3.0 License
// ==/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.5;
font-size: 14px;
color: #333;
}
.ds-msg-content p {
margin: 8px 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: 12px 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: 10px 0;
padding-left: 20px;
}
.ds-msg-content li {
margin: 6px 0;
padding-left: 6px;
}
.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: 600px;
height: 400px;
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;
`;
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: 10px 0;
padding: 12px;
border-radius: 6px;
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;
`;
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;
}
});
})();