// ==UserScript==
// @name AI自动斗蛐蛐助手
// @author Jerry_Chiang&deepseek
// @namespace http://tampermonkey.net/
// @version 2.3
// @description 使用硅基流动的api,支持DeepSeek系列模型的AI自动斗蛐蛐助手,也可自行设置其他兼容openai协议的api
// @match https://chatgpt.com/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// 配置常量
const CONFIG = {
apiKey: 'sk-',//apikey
apiEndpoint: 'https://api.siliconflow.cn/v1/chat/completions',//apiurl
defaultModel: 'deepseek-ai/DeepSeek-V3',
availableModels: [
'deepseek-ai/DeepSeek-R1',
'deepseek-ai/DeepSeek-V3',
'deepseek-ai/DeepSeek-R1-Distill-Llama-8B',
'meta-llama/Meta-Llama-3.1-8B-Instruct'
],
apiParams: {
temperature: 0.7,
top_p: 0.7,
top_k: 50,
max_tokens: 512,
frequency_penalty: 0.5,
presence_penalty: 0,
stream: false,
n: 1,
stop: ["null"],
response_format: { type: "text" }
}
};
// 状态变量
let isRunning = false;
let observer = null;
// 创建UI界面
function createUI() {
const uiHTML = `
<div id="ai-control" style="position:fixed; top:20px; right:20px; background:#1a1a1a; color:#fff; padding:20px; border-radius:8px; z-index:9999; width:380px; box-shadow:0 4px 6px rgba(0,0,0,0.1); cursor:grab;">
<h3 id="drag-handle" style="margin:0 0 15px; color:#fff; cursor:move;">AI斗蛐蛐控制台</h3>
<!-- 模型选择 -->
<div style="margin-bottom:10px;">
<select id="model-select" style="width:100%; padding:6px; background:#333; color:#fff; border:1px solid #444;"></select>
</div>
<!-- 系统提示词 -->
<div style="margin-bottom:10px;">
<input type="text" id="system-prompt" style="width:100%; padding:6px; background:#333; color:#fff; border:1px solid #444;" placeholder="系统提示词(可选)">
</div>
<!-- 初始提示 -->
<textarea id="init-prompt" style="width:100%; height:60px; margin-bottom:10px; background:#333; color:#fff; border:1px solid #444; padding:6px;" placeholder="用户初始消息..."></textarea>
<!-- API参数调整 -->
<div style="display:grid; grid-template-columns:repeat(2, 1fr); gap:8px; margin-bottom:15px;">
<div>
<label style="font-size:12px;">温度 (0-2)</label>
<input type="number" id="temperature" step="0.1" value="${CONFIG.apiParams.temperature}" style="width:100%; padding:4px; background:#333; color:#fff; border:1px solid #444;">
</div>
<div>
<label style="font-size:12px;">最大长度</label>
<input type="number" id="max_tokens" value="${CONFIG.apiParams.max_tokens}" style="width:100%; padding:4px; background:#333; color:#fff; border:1px solid #444;">
</div>
<div>
<label style="font-size:12px;">Top P (0-1)</label>
<input type="number" id="top_p" step="0.1" value="${CONFIG.apiParams.top_p}" style="width:100%; padding:4px; background:#333; color:#fff; border:1px solid #444;">
</div>
<div>
<label style="font-size:12px;">Top K</label>
<input type="number" id="top_k" value="${CONFIG.apiParams.top_k}" style="width:100%; padding:4px; background:#333; color:#fff; border:1px solid #444;">
</div>
<div>
<label style="font-size:12px;">频率惩罚</label>
<input type="number" id="frequency_penalty" step="0.1" value="${CONFIG.apiParams.frequency_penalty}" style="width:100%; padding:4px; background:#333; color:#fff; border:1px solid #444;">
</div>
</div>
<!-- 控制按钮 -->
<div style="display:flex; gap:10px; margin-bottom:15px;">
<button id="start-btn" style="flex:1; padding:8px; background:#28a745; border:none; color:#fff; border-radius:4px; cursor:pointer;">开始</button>
<button id="stop-btn" style="flex:1; padding:8px; background:#dc3545; border:none; color:#fff; border-radius:4px; cursor:pointer;">停止</button>
</div>
<!-- 日志面板 -->
<div id="log" style="height:200px; overflow-y:auto; background:#222; padding:10px; border-radius:4px; font-size:12px; line-height:1.4;"></div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', uiHTML);
// 初始化模型选择
const modelSelect = document.getElementById('model-select');
CONFIG.availableModels.forEach(model => {
const option = document.createElement('option');
option.value = model;
option.textContent = model;
modelSelect.appendChild(option);
});
modelSelect.value = GM_getValue('selectedModel', CONFIG.defaultModel);
modelSelect.addEventListener('change', () => GM_setValue('selectedModel', modelSelect.value));
makeDraggable(document.getElementById('ai-control'));
}
// 使UI可拖动
function makeDraggable(element) {
let offsetX, offsetY, isDragging = false;
const handle = document.getElementById("drag-handle");
handle.addEventListener('mousedown', (e) => {
isDragging = true;
offsetX = e.clientX - element.getBoundingClientRect().left;
offsetY = e.clientY - element.getBoundingClientRect().top;
element.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
element.style.left = `${e.clientX - offsetX}px`;
element.style.top = `${e.clientY - offsetY}px`;
});
document.addEventListener('mouseup', () => {
isDragging = false;
element.style.cursor = 'grab';
});
}
// 日志记录功能
function addLog(message, type = 'info') {
const logElement = document.getElementById('log');
const colors = { info: '#fff', error: '#ff4444', success: '#44ff44' };
logElement.innerHTML += `<div style="color:${colors[type]}; margin:5px 0;">${new Date().toLocaleTimeString()} - ${message}</div>`;
logElement.scrollTop = logElement.scrollHeight;
}
// 获取当前API参数
function getCurrentParams() {
return {
temperature: parseFloat(document.getElementById('temperature').value) || CONFIG.apiParams.temperature,
top_p: parseFloat(document.getElementById('top_p').value) || CONFIG.apiParams.top_p,
top_k: parseInt(document.getElementById('top_k').value) || CONFIG.apiParams.top_k,
max_tokens: parseInt(document.getElementById('max_tokens').value) || CONFIG.apiParams.max_tokens,
frequency_penalty: parseFloat(document.getElementById('frequency_penalty').value) || CONFIG.apiParams.frequency_penalty,
presence_penalty: 0,
stream: false,
n: 1,
stop: ["null"],
response_format: { type: "text" }
};
}
// 调用DeepSeek-V3 API
async function callDeepSeekAPI(message) {
const systemPrompt = document.getElementById('system-prompt').value.trim();
const params = getCurrentParams();
const modelSelect = document.getElementById('model-select').value;
const messages = [];
if (systemPrompt) {
messages.push({ role: "system", content: systemPrompt });
}
messages.push({ role: "user", content: message });
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: CONFIG.apiEndpoint,
headers: {
'Authorization': `Bearer ${CONFIG.apiKey}`,
'Content-Type': 'application/json'
},
timeout: 60000, // 60秒超时
data: JSON.stringify({
model: modelSelect,
messages: messages,
...params
}),
onload: function (response) {
try {
if (response.status >= 200 && response.status < 300) {
const data = JSON.parse(response.responseText);
if (data.choices?.[0]?.message?.content) {
resolve(data.choices[0].message.content);
} else {
reject('API返回无效的响应格式');
}
} else {
let errorMsg = `HTTP错误: ${response.status}`;
try {
const errData = JSON.parse(response.responseText);
errorMsg += ` - ${errData.error?.message || errData.message}`;
} catch(e) {}
reject(errorMsg);
}
} catch (e) {
reject('响应解析失败: ' + e.message);
}
},
onerror: function (error) {
let errorMsg = '网络请求失败';
try {
const errData = JSON.parse(error.responseText);
errorMsg = errData.error?.message || errData.message;
} catch(e) {}
reject(errorMsg);
},
ontimeout: function () {
reject('请求超时(60秒)');
}
});
});
}
// 发送消息到ChatGPT
async function sendToChatGPT(message) {
const inputBox = document.querySelector('#prompt-textarea');
if (!inputBox) return;
inputBox.focus();
inputBox.textContent = message;
inputBox.dispatchEvent(new Event('input', { bubbles: true }));
await new Promise(resolve => setTimeout(resolve, 500));
const sendButton = document.querySelector('button[data-testid="send-button"]');
if (sendButton) {
sendButton.click();
addLog(`已发送消息: ${message}`, 'success');
}
}
// 监听回复
function watchResponse(callback) {
let responseTimer = null;
let lastContent = '';
const maxWaitTime = 5000; // 5秒超时
const observer = new MutationObserver(() => {
const targetElement = document.querySelector('article:last-child [data-message-author-role="assistant"]');
if (!targetElement) return;
// 排除正在输入状态
const isTyping = targetElement.querySelector('.result-streaming');
if (isTyping) {
addLog('检测到流式输出中...', 'info');
return;
}
const currentContent = targetElement.textContent.trim();
// 内容发生变化时处理
if (currentContent && currentContent !== lastContent) {
lastContent = currentContent;
// 清除旧定时器
if (responseTimer) clearTimeout(responseTimer);
// 启动新定时器
responseTimer = setTimeout(() => {
observer.disconnect();
callback(lastContent);
addLog('获取到完整响应内容', 'success');
}, maxWaitTime);
}
});
// 监听整个对话容器
const chatContainer = document.querySelector('main');
if (chatContainer) {
observer.observe(chatContainer, {
childList: true,
subtree: true
});
}
}
// 主对话循环
async function chatLoop(initialMessage) {
let currentMessage = initialMessage;
await sendToChatGPT(currentMessage);
while (isRunning) {
try {
// 监听一次回复
const chatGPTReply = await new Promise(resolve => watchResponse(resolve));
if (!isRunning) break; // 检查是否已停止
addLog(`ChatGPT 回复: ${chatGPTReply}`, 'info');
addLog('正在调用DeepSeek API...');
const apiResponse = await callDeepSeekAPI(chatGPTReply);
addLog(`API 响应: ${apiResponse}`, 'success');
if (!isRunning) break; // 再次检查是否已停止
currentMessage = apiResponse;
await sendToChatGPT(currentMessage);
await new Promise(resolve => setTimeout(resolve, 3000));
} catch (error) {
addLog(`错误: ${error}`, 'error');
stopChatLoop();
}
}
}
// 停止对话
function stopChatLoop() {
isRunning = false;
if (observer) {
observer.disconnect();
observer = null;
}
addLog('对话已强制停止', 'error');
}
// 初始化
(function init() {
GM_addStyle(`
#ai-control input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
#ai-control label {
display: block;
margin-bottom: 2px;
color: #888;
}
`);
createUI();
document.getElementById('start-btn').addEventListener('click', () => {
if (!isRunning) {
const initialMessage = document.getElementById('init-prompt').value.trim();
if (!initialMessage) {
addLog('请输入初始对话内容', 'error');
return;
}
isRunning = true;
addLog('对话已启动', 'success');
chatLoop(initialMessage);
}
});
document.getElementById('stop-btn').addEventListener('click', stopChatLoop);
addLog('系统初始化完成');
})();
})();