// ==UserScript== // @name 咽沙学习小助手测试版V0.9 公开测试版 // @namespace http://tampermonkey.net/ // @version 0.9 // @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, .option-button, #chat-window, #auto-answer-chat-window, #input-container, .copy-button { font-family: "STKaiti", "华文楷体", "Kaiti", serif; } #button-container { position: fixed; /* 固定定位 */ top: 100px; /* 调整为距离顶部100px */ left: 0; /* 左边 */ z-index: 10000; /* 确保始终位于最上层 */ background-color: rgba(255, 255, 255, 0.8); /* 半透明背景色 */ border-bottom: 1px solid black; /* 底部边界线 */ width: 100%; /* 宽度为整个屏幕 */ text-align: center; } .option-button { display: none; /* 默认隐藏子按钮 */ margin-top: 5px; padding: 10px; cursor: pointer; border: 1px solid #ccc; background-color: white; transition: all 0.3s ease; } .option-button:hover { background-color: #e6e6fa; } #parent-button { cursor: pointer; padding: 10px; text-align: center; background-color: #e6e6fa; border: 1px solid #ccc; } #chat-window, #auto-answer-chat-window { position: fixed; bottom: 0; right: 0; width: 400px; height: 600px; background-color: white; border: 1px solid black; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); overflow: hidden; display: none; z-index: 10001; /* 确保聊天窗口在最上方 */ } .chat-content { height: 80%; overflow-y: auto; padding: 10px; border-bottom: 1px solid #ccc; } #input-container { display: flex; justify-content: space-between; align-items: center; padding: 10px; } .copy-button { padding: 5px 10px; cursor: pointer; border: 1px solid #ccc; background-color: white; transition: all 0.3s ease; } .copy-button:hover { background-color: #e6e6fa; } `); function createButtonContainer() { const buttonContainer = document.createElement('div'); buttonContainer.id = 'button-container'; return buttonContainer; } function createParentButton(buttonContainer) { const parentButton = document.createElement('button'); parentButton.id = 'parent-button'; parentButton.textContent = '咽沙学习小助手测试版 V0.9'; 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(chatWindowId); } else if (i === 1) { // 获取页面自动解答按钮 optionButton.textContent = '-获取页面自动解答'; optionButton.onclick = () => toggleChatWindowVisibility('auto-answer-chat-window'); } 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() { const pageText = getPageText(); const prompt = ` 提供一段包含问题及其选项的文本,请为每个问题进行编号,并组织成清晰的问题集。然后对每道题的每个选项进行详细解答,包括知识点分析、正确答案推导及错误选项解释。请使用中文整理并输出。 —— ${pageText} `; const loadingMessage = document.createElement('div'); loadingMessage.textContent = '正在加载中…请耐心等待'; chatContent.innerHTML = ''; // 清空聊天记录 chatContent.appendChild(loadingMessage); chatContent.scrollTop = chatContent.scrollHeight; try { const apiResponse = await sendMessageToAI(prompt); const aiMessage = document.createElement('div'); aiMessage.innerHTML = '解答:
' + apiResponse.choices[0].message.content.replace(/\n/g, '
'); chatContent.appendChild(aiMessage); chatContent.scrollTop = chatContent.scrollHeight; const copyButton = document.createElement('div'); copyButton.className = 'copy-button'; copyButton.textContent = '点击复制此次回答'; copyButton.onclick = () => navigator.clipboard.writeText(apiResponse.choices[0].message.content).then(() => console.log('回答已复制到剪贴板')).catch(err => console.error('无法复制回答:', err)); chatContent.appendChild(copyButton); chatContent.scrollTop = chatContent.scrollHeight; // 移除加载提示 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); // 确保先插入新内容再移除加载提示 } }; 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); const aiMessage = document.createElement('div'); aiMessage.innerHTML = '解答:
' + apiResponse.choices[0].message.content.replace(/\n/g, '
'); chatContent.appendChild(aiMessage); chatContent.scrollTop = chatContent.scrollHeight; const copyButton = document.createElement('div'); copyButton.className = 'copy-button'; copyButton.textContent = '点击复制此次回答'; copyButton.onclick = () => navigator.clipboard.writeText(apiResponse.choices[0].message.content).then(() => console.log('回答已复制到剪贴板')).catch(err => console.error('无法复制回答:', err)); chatContent.appendChild(copyButton); chatContent.scrollTop = chatContent.scrollHeight; // 移除加载提示 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); } 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')); buttons.forEach((button, index) => { setTimeout(() => { button.style.display = 'inline-block'; // 更改为 inline-block 以便在一行显示 button.style.opacity = '1'; forceRedraw(button); }, index * 200); }); } 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'; forceRedraw(button); }, 200); }, index * 200); }); } createOptionButtons(buttonContainer, 'chat-window'); createChatWindow(false); // 对话GPT窗口 createChatWindow(true); // 获取页面自动解答窗口 })();