您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动调用AI总结网页
当前为
// ==UserScript== // @name AI网页内容总结 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 自动调用AI总结网页 // @author AiCoder // @match *://*/* // @connect * // @license MIT // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/turndown.min.js // ==/UserScript== (function() { 'use strict'; // 配置参数 const CONFIG = { // 替换为你的API密钥和端点 apiKey: 'YOUR_API_KEY_HERE', apiEndpoint: 'https://api.openai.com/v1/chat/completions', model: 'gpt-3.5-turbo', maxTokens: 1000, temperature: 0.7, // UI配置 uiPosition: 'bottom-right', // 可选: top-left, top-right, bottom-left, bottom-right theme: 'light', // 可选: light, dark // 自动触发设置 autoSummarize: true, // 是否自动总结 delay: 500, // 页面加载后延迟多少毫秒开始总结 // 自动总结域名列表 autoSummarizeDomains: ['juejin.cn', 'zhihu.com', 'csdn.net', 'jianshu.com'] }; // 保存用户配置 const savedConfig = GM_getValue('aiSummaryConfig'); if (savedConfig) { Object.assign(CONFIG, JSON.parse(savedConfig)); } // 添加样式 GM_addStyle(` #ai-summary-container { position: fixed; ${CONFIG.uiPosition.includes('bottom') ? 'bottom: 20px;' : 'top: 20px;'} ${CONFIG.uiPosition.includes('right') ? 'right: 20px;' : 'left: 20px;'} width: 350px; max-height: 500px; background-color: ${CONFIG.theme === 'light' ? '#ffffff' : '#2d2d2d'}; color: ${CONFIG.theme === 'light' ? '#333333' : '#f0f0f0'}; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); z-index: 9999; overflow: hidden; font-family: Arial, sans-serif; transition: all 0.3s ease; opacity: 0.95; } #ai-summary-container:hover { opacity: 1; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); } #ai-summary-header { padding: 10px 15px; background-color: ${CONFIG.theme === 'light' ? '#f0f0f0' : '#444444'}; border-bottom: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; display: flex; justify-content: space-between; align-items: center; cursor: move; } #ai-summary-title { font-weight: bold; font-size: 14px; margin: 0; } #ai-summary-controls { display: flex; gap: 5px; } #ai-summary-controls button { background: none; border: none; cursor: pointer; font-size: 16px; color: ${CONFIG.theme === 'light' ? '#555' : '#ccc'}; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 4px; } #ai-summary-controls button:hover { background-color: ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; } #ai-summary-content { padding: 15px; overflow-y: auto; max-height: 400px; font-size: 14px; line-height: 1.5; } #ai-summary-content.loading { opacity: 0.7; } #ai-summary-content p { margin: 0 0 10px 0; } #ai-summary-footer { padding: 8px 15px; border-top: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; display: flex; justify-content: space-between; font-size: 12px; color: ${CONFIG.theme === 'light' ? '#888' : '#aaa'}; } #ai-summary-settings { position: absolute; top: 40px; right: 0; background-color: ${CONFIG.theme === 'light' ? '#ffffff' : '#2d2d2d'}; border: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; border-radius: 4px; padding: 10px; width: 250px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); z-index: 10000; display: none; } #ai-summary-settings.visible { display: block; } .settings-group { margin-bottom: 10px; } .settings-group label { display: block; margin-bottom: 5px; font-weight: bold; font-size: 12px; } .settings-group input, .settings-group select { width: 100%; padding: 5px; border: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; border-radius: 4px; background-color: ${CONFIG.theme === 'light' ? '#ffffff' : '#333333'}; color: ${CONFIG.theme === 'light' ? '#333333' : '#f0f0f0'}; } .settings-group input[type="checkbox"] { width: auto; } .settings-actions { display: flex; justify-content: flex-end; gap: 5px; margin-top: 10px; } .settings-actions button { padding: 5px 10px; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; } #save-settings { background-color: #4CAF50; color: white; } #cancel-settings { background-color: #f0f0f0; color: #333; } .cursor-pointer { cursor: pointer; } .typing-effect { border-right: 2px solid ${CONFIG.theme === 'light' ? '#333' : '#f0f0f0'}; white-space: nowrap; overflow: hidden; animation: typing 3.5s steps(40, end), blink-caret 0.75s step-end infinite; } @keyframes typing { from { width: 0 } to { width: 100% } } @keyframes blink-caret { from, to { border-color: transparent } 50% { border-color: ${CONFIG.theme === 'light' ? '#333' : '#f0f0f0'} } } `); // 创建UI function createUI() { const container = document.createElement('div'); container.id = 'ai-summary-container'; container.innerHTML = ` <div id="ai-summary-header"> <h3 id="ai-summary-title">AI网页内容总结</h3> <div id="ai-summary-controls"> <button id="ai-summary-refresh" title="刷新总结">🔄</button> <button id="ai-summary-settings-btn" title="设置">⚙️</button> <button id="ai-summary-minimize" title="最小化">_</button> <button id="ai-summary-close" title="关闭">✕</button> </div> </div> <div id="ai-summary-content"> <p>点击刷新按钮开始总结当前网页内容...</p> </div> <div id="ai-summary-footer"> <span>由AI提供支持</span> <span id="ai-summary-toggle" class="cursor-pointer">自动总结: ${CONFIG.autoSummarize ? '开启' : '关闭'}</span> </div> <div id="ai-summary-settings"> <div class="settings-group"> <label for="api-key">API密钥</label> <input type="password" id="api-key" value="${CONFIG.apiKey}" placeholder="输入你的API密钥"> </div> <div class="settings-group"> <label for="api-endpoint">API端点</label> <input type="text" id="api-endpoint" value="${CONFIG.apiEndpoint}" placeholder="API端点URL"> </div> <div class="settings-group"> <label for="model">模型</label> <input type="text" id="model" value="${CONFIG.model}" placeholder="AI模型名称"> </div> <div class="settings-group"> <label for="position">位置</label> <select id="position"> <option value="top-left" ${CONFIG.uiPosition === 'top-left' ? 'selected' : ''}>左上角</option> <option value="top-right" ${CONFIG.uiPosition === 'top-right' ? 'selected' : ''}>右上角</option> <option value="bottom-left" ${CONFIG.uiPosition === 'bottom-left' ? 'selected' : ''}>左下角</option> <option value="bottom-right" ${CONFIG.uiPosition === 'bottom-right' ? 'selected' : ''}>右下角</option> </select> </div> <div class="settings-group"> <label for="theme">主题</label> <select id="theme"> <option value="light" ${CONFIG.theme === 'light' ? 'selected' : ''}>浅色</option> <option value="dark" ${CONFIG.theme === 'dark' ? 'selected' : ''}>深色</option> </select> </div> <div class="settings-group"> <label> <input type="checkbox" id="auto-summarize" ${CONFIG.autoSummarize ? 'checked' : ''}> 自动总结 </label> </div> <div class="settings-group"> <label for="auto-domains">自动总结域名列表</label> <input type="text" id="auto-domains" value="${CONFIG.autoSummarizeDomains.join(', ')}" placeholder="输入域名,用逗号分隔"> <small style="display: block; margin-top: 5px; font-size: 11px; color: ${CONFIG.theme === 'light' ? '#666' : '#aaa'};">输入域名,用逗号分隔,例如: juejin.cn, zhihu.com</small> </div> <div class="settings-actions"> <button id="cancel-settings">取消</button> <button id="save-settings">保存</button> </div> </div> `; document.body.appendChild(container); makeElementDraggable(container); addEventListeners(); // 检查当前域名是否在自动总结列表中 if (CONFIG.autoSummarize) { const currentDomain = window.location.hostname; const shouldAutoSummarize = CONFIG.autoSummarizeDomains.some(domain => currentDomain.includes(domain)); if (shouldAutoSummarize) { console.log(`当前域名 ${currentDomain} 在自动总结列表中,将自动开始总结`); // 修改内容提示为"正在总结中..." document.getElementById('ai-summary-content').innerHTML = '<p>正在总结中...</p>'; setTimeout(() => { summarizeContent(true); // 传递true表示是自动总结 }, CONFIG.delay); } else { console.log(`当前域名 ${currentDomain} 不在自动总结列表中,不会自动总结`); } } } // 添加事件监听器 function addEventListeners() { // 刷新按钮 document.getElementById('ai-summary-refresh').addEventListener('click', () => { summarizeContent(); }); // 设置按钮 document.getElementById('ai-summary-settings-btn').addEventListener('click', () => { const settingsPanel = document.getElementById('ai-summary-settings'); settingsPanel.classList.toggle('visible'); }); // 最小化按钮 document.getElementById('ai-summary-minimize').addEventListener('click', () => { const container = document.getElementById('ai-summary-container'); const content = document.getElementById('ai-summary-content'); const footer = document.getElementById('ai-summary-footer'); if (content.style.display === 'none') { content.style.display = 'block'; footer.style.display = 'flex'; document.getElementById('ai-summary-minimize').textContent = '_'; } else { content.style.display = 'none'; footer.style.display = 'none'; document.getElementById('ai-summary-minimize').textContent = '□'; } }); // 关闭按钮 document.getElementById('ai-summary-close').addEventListener('click', () => { const container = document.getElementById('ai-summary-container'); container.style.display = 'none'; }); // 自动总结开关 document.getElementById('ai-summary-toggle').addEventListener('click', () => { CONFIG.autoSummarize = !CONFIG.autoSummarize; document.getElementById('ai-summary-toggle').textContent = `自动总结: ${CONFIG.autoSummarize ? '开启' : '关闭'}`; document.getElementById('auto-summarize').checked = CONFIG.autoSummarize; saveConfig(); }); // 取消设置 document.getElementById('cancel-settings').addEventListener('click', () => { document.getElementById('ai-summary-settings').classList.remove('visible'); }); // 保存设置 document.getElementById('save-settings').addEventListener('click', () => { // 获取用户输入的配置 CONFIG.apiKey = document.getElementById('api-key').value; CONFIG.apiEndpoint = document.getElementById('api-endpoint').value; CONFIG.model = document.getElementById('model').value; CONFIG.uiPosition = document.getElementById('position').value; CONFIG.theme = document.getElementById('theme').value; CONFIG.autoSummarize = document.getElementById('auto-summarize').checked; // 获取并处理自动总结域名列表 const domainsInput = document.getElementById('auto-domains').value; CONFIG.autoSummarizeDomains = domainsInput.split(',').map(domain => domain.trim()).filter(domain => domain); // 保存配置 saveConfig(); // 更新UI updateUIWithConfig(); // 隐藏设置面板 document.getElementById('ai-summary-settings').classList.remove('visible'); // 显示保存成功提示 const contentElement = document.getElementById('ai-summary-content'); contentElement.innerHTML = '<p>设置已保存</p>'; setTimeout(() => { contentElement.innerHTML = '<p>点击刷新按钮开始总结当前网页内容...</p>'; }, 2000); }); } // 保存配置到本地存储 function saveConfig() { try { GM_setValue('aiSummaryConfig', JSON.stringify(CONFIG)); console.log('配置已保存'); } catch (error) { console.error('保存配置时出错:', error); alert('保存配置失败,请查看控制台获取详细信息'); } } // 根据配置更新UI function updateUIWithConfig() { const container = document.getElementById('ai-summary-container'); // 更新位置 container.style.top = CONFIG.uiPosition.includes('top') ? '20px' : 'auto'; container.style.bottom = CONFIG.uiPosition.includes('bottom') ? '20px' : 'auto'; container.style.left = CONFIG.uiPosition.includes('left') ? '20px' : 'auto'; container.style.right = CONFIG.uiPosition.includes('right') ? '20px' : 'auto'; // 更新主题 if (CONFIG.theme === 'light') { container.style.backgroundColor = '#ffffff'; container.style.color = '#333333'; } else { container.style.backgroundColor = '#2d2d2d'; container.style.color = '#f0f0f0'; } // 更新自动总结开关文本 document.getElementById('ai-summary-toggle').textContent = `自动总结: ${CONFIG.autoSummarize ? '开启' : '关闭'}`; } // 使元素可拖拽 function makeElementDraggable(element) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; const header = document.getElementById('ai-summary-header'); if (header) { header.onmousedown = dragMouseDown; } else { element.onmousedown = dragMouseDown; } function dragMouseDown(e) { e = e || window.event; e.preventDefault(); // 获取鼠标位置 pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; // 鼠标移动时调用elementDrag document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); // 计算新位置 pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // 设置元素的新位置 element.style.top = (element.offsetTop - pos2) + 'px'; element.style.left = (element.offsetLeft - pos1) + 'px'; // 重置位置配置,因为用户手动拖动了 CONFIG.uiPosition = 'custom'; } function closeDragElement() { // 停止移动 document.onmouseup = null; document.onmousemove = null; } } // 提取网页内容 function extractPageContent() { // 获取页面标题 const title = document.title; // 使用Turndown将HTML转换为Markdown const turndownService = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', emDelimiter: '_' }); // 添加自定义规则,忽略一些不需要的元素 turndownService.addRule('ignoreNavAndFooter', { filter: function(node) { return ( node.nodeName.toLowerCase() === 'nav' || node.nodeName.toLowerCase() === 'footer' || node.classList.contains('nav') || node.classList.contains('footer') || node.classList.contains('menu') || node.id === 'footer' || node.id === 'nav' || node.id === 'menu' ); }, replacement: function() { return ''; } }); // 尝试获取文章内容 let content = ''; let htmlContent = ''; // 尝试获取文章内容 const articleElements = document.querySelectorAll('article, .article, .post, .content, main, .main-content, [role="main"]'); if (articleElements.length > 0) { // 使用第一个找到的文章元素 htmlContent = articleElements[0].innerHTML; } else { // 如果没有找到文章元素,尝试获取所有段落 const paragraphs = document.querySelectorAll('p'); if (paragraphs.length > 0) { // 创建一个临时容器来存放所有段落 const tempContainer = document.createElement('div'); paragraphs.forEach(p => { // 只添加有实际内容的段落 if (p.textContent.trim().length > 0) { tempContainer.appendChild(p.cloneNode(true)); } }); htmlContent = tempContainer.innerHTML; } else { // 如果没有找到段落,获取body的内容 // 但排除一些常见的非内容区域 const body = document.body.cloneNode(true); const elementsToRemove = body.querySelectorAll('header, footer, nav, aside, script, style, .sidebar, .ad, .advertisement, .banner'); elementsToRemove.forEach(el => el.remove()); htmlContent = body.innerHTML; } } // 将HTML转换为Markdown content = turndownService.turndown(htmlContent); // 清理内容(删除多余空白行) content = content.replace(/\n{3,}/g, '\n\n').trim(); // 如果内容太长,截取前10000个字符 if (content.length > 10000) { content = content.substring(0, 10000) + '...'; } return { title, content }; } // 调用AI API进行总结 function summarizeContent(isAuto = false) { // 显示加载状态 const contentElement = document.getElementById('ai-summary-content'); contentElement.classList.add('loading'); contentElement.innerHTML = isAuto ? '<p>正在自动总结内容,请稍候...</p>' : '<p>正在总结内容,请稍候...</p>'; // 提取页面内容 const { title, content } = extractPageContent(); // 如果API密钥未设置,显示提示 if (CONFIG.apiKey === 'YOUR_API_KEY_HERE') { contentElement.classList.remove('loading'); contentElement.innerHTML = '<p>请先在设置中配置你的API密钥</p>'; return; } // 准备请求数据 const requestData = { model: CONFIG.model, messages: [ { role: 'system', content: '你是一个专业的内容总结助手。请简洁明了地总结以下网页内容的要点,使用中文回答。' }, { role: 'user', content: `网页标题: ${title}\n\n网页内容: ${content}\n\n请总结这个网页的主要内容,突出关键信息。` } ], max_tokens: CONFIG.maxTokens, temperature: CONFIG.temperature, stream: true }; // 发送API请求 let summaryText = ''; let lastResponseLength = 0; // 添加此变量来跟踪响应长度 contentElement.innerHTML = ''; GM_xmlhttpRequest({ method: 'POST', url: CONFIG.apiEndpoint, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CONFIG.apiKey}` }, data: JSON.stringify(requestData), timeout: 30000, // 设置30秒超时 onloadstart: function() { // 创建一个段落用于显示流式响应 const paragraph = document.createElement('p'); contentElement.appendChild(paragraph); console.log('开始接收流式响应...'); }, onreadystatechange: function(response) { try { // 处理流式响应 const responseText = response.responseText || ''; // 只处理新数据 if (responseText.length <= lastResponseLength) { return; } // 计算新数据 const newResponseText = responseText.substring(lastResponseLength); lastResponseLength = responseText.length; console.log(`接收到新数据,长度: ${newResponseText.length}, 总长度: ${responseText.length}`); // 将新响应拆分为各个数据行 const lines = newResponseText.split('\n'); let newContent = ''; for (const line of lines) { if (line.startsWith('data: ') && line !== 'data: [DONE]') { try { const jsonStr = line.substring(6); if (jsonStr.trim() === '') continue; const data = JSON.parse(jsonStr); if (data.choices && data.choices[0].delta && data.choices[0].delta.content) { newContent += data.choices[0].delta.content; } } catch (e) { // 可能是不完整的JSON,忽略错误 console.log('解析单行数据时出错 (可能是不完整的JSON):', e.message); } } } // 只要有新内容就立即更新UI if (newContent) { summaryText += newContent; const paragraph = contentElement.querySelector('p'); if (paragraph) { paragraph.innerHTML = renderMarkdown(summaryText); contentElement.scrollTop = contentElement.scrollHeight; // 滚动到底部 } } } catch (error) { console.error('处理流式响应时出错:', error); } }, onload: function(response) { contentElement.classList.remove('loading'); console.log('响应接收完成,状态码:', response.status); if (response.status !== 200) { contentElement.innerHTML = `<p>API请求失败: ${response.status} ${response.statusText}</p>`; console.error('API请求失败:', response.status, response.statusText, response.responseText); return; } // 确保我们有完整的内容 if (summaryText.trim() === '') { console.log('尝试从完整响应中提取内容...'); // 提取完整响应内容的逻辑... // ... existing code for handling complete response ... } else { console.log('流式响应已完成,总内容长度:', summaryText.length); } }, onerror: function(error) { contentElement.classList.remove('loading'); contentElement.innerHTML = `<p>请求出错: ${error}</p>`; console.error('API请求出错:', error); }, ontimeout: function() { contentElement.classList.remove('loading'); contentElement.innerHTML = '<p>请求超时,请检查网络连接或API端点是否正确</p>'; console.error('API请求超时'); } }); } // 在页面加载完成后初始化 // 渲染Markdown文本为HTML function renderMarkdown(text) { if (!text) return ''; // 基本Markdown语法转换 let html = text // 标题 .replace(/^### (.+)$/gm, '<h3>$1</h3>') .replace(/^## (.+)$/gm, '<h2>$1</h2>') .replace(/^# (.+)$/gm, '<h1>$1</h1>') // 粗体 .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') // 斜体 .replace(/\*(.+?)\*/g, '<em>$1</em>') // 代码块 .replace(/```([\s\S]+?)```/g, '<pre><code>$1</code></pre>') // 行内代码 .replace(/`(.+?)`/g, '<code>$1</code>') // 链接 .replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank">$1</a>') // 无序列表 .replace(/^- (.+)$/gm, '<li>$1</li>') // 有序列表 .replace(/^\d+\. (.+)$/gm, '<li>$1</li>') // 段落 .replace(/\n\n/g, '</p><p>'); // 包装在段落标签中 html = '<p>' + html + '</p>'; // 修复列表 html = html.replace(/<p><li>/g, '<ul><li>').replace(/<\/li><\/p>/g, '</li></ul>'); return html; } // 使用事件监听器确保DOM已加载 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createUI); } else { createUI(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址