// ==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);
});
})();