// ==UserScript==
// @name 咽沙学习小助手测试版V0.91 gpt3对话,解题
// @namespace http://tampermonkey.net/
// @version 0.91
// @description 创建一个明显可见且可拖动的聊天窗口,集成 OpenAI API 互动
// @author Your Name
// @match *://*/*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
'use strict';
const API_SECRET_KEY = "sk-zkix22izlS26fVKhfAtHnRa2A8rT9xZyQqAu6Sf8NfiXPPtdYEYN";
const BASE_URL = "https://model-bridge.okeeper.com/v1/";
// 添加 CSS 样式 - 确保样式定义正确
GM_addStyle(`
/* 确保所有相关元素都有合适的样式 */
#button-container {
width: 250px;
height: 60px; /* 调整父容器高度 */
border-radius: 15px;
font-size: 18px;
position: fixed;
right: 20px;
bottom: 180px; /* 修改为距离底部180px */
z-index: 10000;
cursor: pointer;
font-family: "STKaiti", "华文楷体", "Kaiti", serif;
overflow: visible; /* 确保子元素可以超出父容器 */
color: black;
background: linear-gradient(135deg, #ffecb3, #ffdead);
transition: all 0.3s ease-in-out;
}
.option-button {
width: 250px; /* 增加宽度 */
height: 60px; /* 增加高度 */
border-radius: 15px; /* 设置圆角 */
border: none;
background-color: #fff9db; /* 更浅的背景颜色 */
cursor: pointer;
font-family: "STKaiti", "华文楷体", "Kaiti", serif;
display: none; /* 默认隐藏 */
text-align: left;
position: absolute;
left: 0;
top: -60px; /* 初始位置在父容器上方 */
opacity: 0;
transition: all 0.3s ease-in-out;
}
#chat-window, #auto-answer-chat-window {
width: 300px;
height: 400px; /* 增加了聊天窗口的高度 */
background-color: #e6e6fa; /* 浅紫色背景 */
border: 1px solid #ccc;
position: fixed;
bottom: 60px; /* 修改了聊天窗口初始位置为距离底部60px */
right: 20px;
z-index: 9999;
box-shadow: 0 0 10px rgba(0,0,0,0.2);
padding: 10px;
border-radius: 5px;
overflow: hidden;
display: none; /* 初始隐藏 */
}
#chat-content, #auto-answer-chat-content {
height: calc(100% - 120px); /* 减去输入框、按钮和复制按钮的高度 */
overflow-y: auto; /* 允许上下滚动 */
}
#input-container {
position: absolute;
bottom: 0; /* 紧贴聊天窗口底部 */
left: 0;
right: 0;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff; /* 区分聊天内容与输入区域 */
border-top: 1px solid #ccc; /* 分隔线 */
}
#chat-input {
width: calc(100% - 70px); /* 为发送按钮留出空间 */
height: 30px;
margin: 5px;
box-sizing: border-box;
}
#send-button {
width: 50px;
height: 30px;
margin: 5px;
box-sizing: border-box;
}
.copy-button {
width: 100%;
height: 30px;
margin: 5px 0;
box-sizing: border-box;
background-color: #f0f0f0;
border: 1px solid #ccc;
cursor: pointer;
text-align: center;
line-height: 30px;
}
@keyframes gradientBG {
0% { background-color: #ffecb3; }
50% { background-color: #fffdd0; }
100% { background-color: #ffdead; }
}
button#show-hide-chat-button {
animation: gradientBG 5s infinite;
}
`);
function createButtonContainer() {
const buttonContainer = document.createElement('div');
buttonContainer.id = 'button-container';
return buttonContainer;
}
function createParentButton(buttonContainer) {
const parentButton = document.createElement('button');
parentButton.textContent = '咽沙学习小助手测试版 V0.91';
parentButton.style.width = '100%';
parentButton.style.height = '100%';
parentButton.style.border = 'none';
parentButton.style.backgroundColor = 'transparent';
parentButton.style.cursor = 'pointer';
parentButton.style.fontFamily = '"STKaiti", "华文楷体", "Kaiti", serif';
parentButton.style.fontSize = '18px';
buttonContainer.appendChild(parentButton);
return parentButton;
}
function createOptionButtons(buttonContainer, chatWindowId) {
for (let i = 0; i < 5; i++) {
const optionButton = document.createElement('button');
optionButton.className = 'option-button';
if (i === 4) { // 对话GPT按钮
optionButton.textContent = '-对话GPT 【本按钮可切换窗口显示、隐藏】';
optionButton.onclick = () => toggleChatWindowVisibility('chat-window');
} else if (i === 1) { // 获取页面自动解答按钮
optionButton.textContent = '-获取页面自动解答';
optionButton.onclick = () => {
toggleChatWindowVisibility('auto-answer-chat-window');
generateAutoAnswer();
};
} else {
optionButton.onclick = () => parentButton.click(); // 收起子容器
}
buttonContainer.appendChild(optionButton);
}
}
function toggleChatWindowVisibility(chatWindowId) {
const chatWindow = document.getElementById(chatWindowId);
chatWindow.style.display = chatWindow.style.display === 'none' ? 'block' : 'none';
}
// 改进获取页面文本逻辑,只提取可选中的文本节点
function getPageText() {
let allText = '';
const texts = [];
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
while (walker.nextNode()) {
const node = walker.currentNode;
if (node.parentElement.tagName !== 'SCRIPT' && node.parentElement.tagName !== 'STYLE') {
texts.push(node.nodeValue.trim());
}
}
return texts.filter(text => text).join('\n'); // 过滤掉空字符串并连接成一个整体
}
async function sendMessageToAI(message) {
try {
const response = await fetch(`${BASE_URL}chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_SECRET_KEY}`
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": message}
]
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!data.choices || !data.choices.length) {
throw new Error('Invalid response from AI service.');
}
return data;
} catch (error) {
console.error("Error fetching AI response:", error);
throw error;
}
}
function createChatWindow(isAutoAnswer) {
const chatWindowId = isAutoAnswer ? 'auto-answer-chat-window' : 'chat-window';
const chatWindow = document.createElement('div');
chatWindow.id = chatWindowId;
// 创建一个带有固定高度和滚动条的内容容器
const chatContentId = `${chatWindowId}-content`;
const chatContent = document.createElement('div');
chatContent.id = chatContentId;
chatContent.className = 'chat-content'; // 应用新的样式类
chatWindow.appendChild(chatContent);
if (isAutoAnswer) {
const instruction = document.createElement('div');
instruction.id = 'auto-answer-instruction';
instruction.textContent = '按下下方按钮,获取页面解答。(仅支持可被选中的文本)';
chatWindow.appendChild(instruction);
const generateAnswerButton = document.createElement('button');
generateAnswerButton.id = 'generate-answer-button';
generateAnswerButton.textContent = '点击生成页面解答';
generateAnswerButton.onclick = async function() {
generateAutoAnswer();
};
chatWindow.appendChild(generateAnswerButton);
} else {
// 对话GPT窗口逻辑
const inputContainer = document.createElement('div');
inputContainer.id = 'input-container';
const userInput = document.createElement('input');
userInput.type = 'text';
userInput.placeholder = '输入你的问题...';
inputContainer.appendChild(userInput);
const sendButton = document.createElement('button');
sendButton.textContent = '发送';
sendButton.onclick = async function() {
const userMessage = userInput.value.trim();
if (userMessage) {
userInput.value = '';
const loadingMessage = document.createElement('div');
loadingMessage.textContent = '正在加载中…请耐心等待';
chatContent.appendChild(loadingMessage);
chatContent.scrollTop = chatContent.scrollHeight;
try {
const apiResponse = await sendMessageToAI(userMessage);
appendChatMessage(chatContent, '用户:', userMessage);
appendChatMessage(chatContent, '回答:', apiResponse.choices[0].message.content);
addCopyButton(chatContent, apiResponse.choices[0].message.content);
// 移除加载提示
setTimeout(() => chatContent.removeChild(loadingMessage), 0); // 确保先插入新内容再移除加载提示
} catch (error) {
const errorMessage = document.createElement('div');
errorMessage.textContent = '获取解答时出错,请稍后再试。';
chatContent.appendChild(errorMessage);
chatContent.scrollTop = chatContent.scrollHeight;
// 移除加载提示
setTimeout(() => chatContent.removeChild(loadingMessage), 0); // 确保先插入新内容再移除加载提示
}
}
};
inputContainer.appendChild(sendButton);
chatWindow.appendChild(inputContainer);
}
document.body.appendChild(chatWindow);
}
function appendChatMessage(container, label, message) {
const messageDiv = document.createElement('div');
messageDiv.innerHTML = `${label}
${message.replace(/\n/g, '
')}`;
container.appendChild(messageDiv);
container.scrollTop = container.scrollHeight;
}
function addCopyButton(container, content) {
const copyButton = document.createElement('div');
copyButton.className = 'copy-button';
copyButton.textContent = '点击复制此次回答';
copyButton.onclick = () => navigator.clipboard.writeText(content).then(() => console.log('回答已复制到剪贴板')).catch(err => console.error('无法复制回答:', err));
container.appendChild(copyButton);
container.scrollTop = container.scrollHeight;
}
function generateAutoAnswer() {
// 获取正确的聊天内容容器ID
const chatWindow = document.getElementById('auto-answer-chat-window');
const chatContentId = 'auto-answer-chat-content';
let chatContent = chatWindow.querySelector(`#${chatContentId}`);
if (!chatContent) { // 如果未找到,则创建
chatContent = document.createElement('div');
chatContent.id = chatContentId;
chatContent.className = 'chat-content'; // 应用新的样式类
chatWindow.insertBefore(chatContent, chatWindow.firstChild); // 插入到最顶部
}
const pageText = getPageText();
const prompt = `
提供一段包含问题及其选项的文本,请为每个问题进行编号,并组织成清晰的问题集。然后对每道题的每个选项进行详细解答,包括知识点分析、正确答案推导及错误选项解释。请使用中文整理并输出。
——
${pageText}
`;
// 清空聊天记录并插入加载提示
chatContent.innerHTML = ''; // 清空聊天记录
const loadingMessage = document.createElement('div');
loadingMessage.textContent = '正在加载中…请耐心等待';
chatContent.appendChild(loadingMessage);
chatContent.scrollTop = chatContent.scrollHeight;
sendMessageToAI(prompt)
.then(apiResponse => {
// 移除加载提示
chatContent.removeChild(loadingMessage);
// 添加AI回复内容
appendChatMessage(chatContent, '用户:', '获取页面自动解答');
appendChatMessage(chatContent, '回答:', apiResponse.choices[0].message.content);
addCopyButton(chatContent, apiResponse.choices[0].message.content);
})
.catch(error => {
// 移除加载提示
chatContent.removeChild(loadingMessage);
// 显示错误信息
const errorMessage = document.createElement('div');
errorMessage.textContent = '获取解答时出错,请稍后再试。';
chatContent.appendChild(errorMessage);
chatContent.scrollTop = chatContent.scrollHeight;
});
}
const buttonContainer = createButtonContainer();
document.body.insertBefore(buttonContainer, document.body.firstChild); // 插入到body的第一个子元素之前
const parentButton = createParentButton(buttonContainer);
let isExpanded = false;
parentButton.onclick = function(event) {
if (isExpanded) {
hideButtons();
} else {
showButtons();
}
isExpanded = !isExpanded;
};
function forceRedraw(element) {
element.style.zIndex = 1;
void element.offsetWidth;
element.style.zIndex = '';
}
function showButtons() {
const buttons = Array.from(buttonContainer.querySelectorAll('.option-button'));
const initialTopOffset = -60; // 初始偏移量,即父容器上方60px
buttons.forEach((button, index) => {
setTimeout(() => {
button.style.display = 'block';
button.style.top = `${initialTopOffset - index * 60}px`; // 使用 top 定位,60px 是按钮的高度
button.style.opacity = '1';
forceRedraw(button);
}, index * 200); // 每隔0.2秒显示下一个按钮
});
}
function hideButtons() {
const buttons = Array.from(buttonContainer.querySelectorAll('.option-button')).reverse();
buttons.forEach((button, index) => {
setTimeout(() => {
button.style.opacity = '0';
setTimeout(() => {
button.style.display = 'none';
button.style.top = ''; // 清除定位
forceRedraw(button);
}, 200); // 等待透明度动画完成后再隐藏按钮
}, index * 200); // 每隔0.2秒隐藏下一个按钮
});
}
createOptionButtons(buttonContainer, 'chat-window');
createChatWindow(false); // 对话GPT窗口
createChatWindow(true); // 获取页面自动解答窗口
})();