// ==UserScript== // @name Website Summary // @namespace http://tampermonkey.net/ // @version 0.9.2 // @description 网页内容智能总结,支持自定义API和提示词 // @author Your Name // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @require https://cdn.jsdelivr.net/npm/marked@4.3.0/marked.min.js // @run-at document-end // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 配置项 let config = { apiUrl: GM_getValue('apiUrl', 'https://api.openai.com/v1/chat/completions'), apiKey: GM_getValue('apiKey', ''), model: GM_getValue('model', 'gpt-3.5-turbo'), prompt: GM_getValue('prompt', `You are a professional content summarizer in chinese. Your task is to create a clear, concise, and well-structured summary of the webpage content. Follow these guidelines: 1. Output Format: - Use Markdown formatting - Start with a brief overview - Use appropriate headings (h2, h3) - Include bullet points for key points - Use bold for important terms - Use blockquotes for notable quotes - Use code blocks for technical content 2. Content Structure: - Main Topic/Title - Key Points - Important Details - Conclusions/Summary 3. Writing Style: - Clear and concise language - Professional tone - Logical flow - Easy to understand - Focus on essential information 4. Important Rules: - DO NOT show your reasoning process - DO NOT include meta-commentary - DO NOT explain your methodology - DO NOT use phrases like "this summary shows" or "the content indicates" - Start directly with the content summary 5. Length Guidelines: - Overview: 1-2 sentences - Key Points: 3-5 bullet points - Important Details: 2-3 paragraphs - Summary: 1-2 sentences Remember: Focus on delivering the information directly without any meta-analysis or explanation of your process. Please summarize the following content:`), iconPosition: GM_getValue('iconPosition', { y: 20 }) }; // 等待外部库加载完成 function waitForLibrary(name, callback, maxAttempts = 50) { let attempts = 0; const checkLibrary = () => { if (window[name]) { callback(window[name]); return; } attempts++; if (attempts < maxAttempts) { setTimeout(checkLibrary, 100); } }; checkLibrary(); } // 创建图标 function createIcon() { const icon = document.createElement('div'); icon.id = 'website-summary-icon'; icon.innerHTML = ` `; icon.style.cssText = ` position: fixed; z-index: 999999; width: 40px; height: 40px; background: rgba(255, 255, 255, 0.95); border-radius: 20px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; color: #007AFF; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); right: 20px; top: ${config.iconPosition.y || 20}px; `; icon.addEventListener('mouseover', () => { icon.style.transform = 'scale(1.1)'; icon.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.2)'; icon.style.color = '#0056b3'; }); icon.addEventListener('mouseout', () => { icon.style.transform = 'scale(1)'; icon.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.1)'; icon.style.color = '#007AFF'; }); icon.addEventListener('click', async () => { const container = document.getElementById('website-summary-container'); if (!container) return; container.style.display = 'block'; container.querySelector('#website-summary-content').innerHTML = '

正在获取总结...

'; try { const content = getPageContent(); if (!content) throw new Error('无法获取页面内容'); const summary = await getSummary(content); if (!summary) throw new Error('获取总结失败'); renderContent(summary); } catch (error) { console.error('总结过程出错:', error); container.querySelector('#website-summary-content').innerHTML = `

获取总结失败:${error.message}
请检查API配置是否正确

`; } }); icon.addEventListener('contextmenu', (e) => createContextMenu(e, icon)); makeDraggable(icon); document.body.appendChild(icon); return icon; } // 创建UI元素 function createUI() { const container = document.createElement('div'); container.id = 'website-summary-container'; container.style.cssText = ` position: fixed; z-index: 999998; background: rgba(255, 255, 255, 0.95); border-radius: 12px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 16px; width: 80%; max-width: 800px; max-height: 80vh; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; display: none; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); left: 50%; top: 50%; transform: translate(-50%, -50%); overflow: hidden; `; const header = document.createElement('div'); header.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; cursor: move; padding-bottom: 8px; border-bottom: 1px solid #eee; `; const title = document.createElement('h3'); title.textContent = '网页总结'; title.style.margin = '0'; title.style.fontSize = '18px'; title.style.color = '#333'; const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; gap: 8px; align-items: center; `; const copyBtn = document.createElement('button'); copyBtn.textContent = '复制'; copyBtn.style.cssText = ` background: #4CAF50; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background-color 0.2s; `; copyBtn.addEventListener('mouseover', () => { copyBtn.style.backgroundColor = '#45a049'; }); copyBtn.addEventListener('mouseout', () => { copyBtn.style.backgroundColor = '#4CAF50'; }); copyBtn.addEventListener('click', () => { const content = document.getElementById('website-summary-content').innerText; navigator.clipboard.writeText(content).then(() => { const originalText = copyBtn.textContent; copyBtn.textContent = '已复制'; setTimeout(() => { copyBtn.textContent = originalText; }, 2000); }).catch(err => { console.error('复制失败:', err); }); }); const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.cssText = ` background: none; border: none; font-size: 24px; cursor: pointer; padding: 0 8px; color: #666; transition: color 0.2s; `; closeBtn.addEventListener('mouseover', () => { closeBtn.style.color = '#ff4444'; }); closeBtn.addEventListener('mouseout', () => { closeBtn.style.color = '#666'; }); const content = document.createElement('div'); content.id = 'website-summary-content'; content.style.cssText = ` max-height: calc(80vh - 60px); overflow-y: auto; font-size: 14px; line-height: 1.6; padding: 8px 0; `; buttonContainer.appendChild(copyBtn); buttonContainer.appendChild(closeBtn); header.appendChild(title); header.appendChild(buttonContainer); container.appendChild(header); container.appendChild(content); document.body.appendChild(container); closeBtn.addEventListener('click', () => { container.style.display = 'none'; }); makeDraggable(container); return container; } // 创建设置界面 function createSettingsUI() { const settingsContainer = document.createElement('div'); settingsContainer.id = 'website-summary-settings'; settingsContainer.style.cssText = ` position: fixed; z-index: 1000000; background: rgba(255, 255, 255, 0.98); border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); padding: 20px; width: 400px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; display: none; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); left: 50%; top: 50%; transform: translate(-50%, -50%); `; const header = document.createElement('div'); header.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; cursor: move; `; const title = document.createElement('h3'); title.textContent = '设置'; title.style.margin = '0'; const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.cssText = ` background: none; border: none; font-size: 24px; cursor: pointer; padding: 0 8px; color: #666; `; const form = document.createElement('form'); form.style.cssText = ` display: flex; flex-direction: column; gap: 16px; `; // 创建输入字段 const apiUrlInput = document.createElement('input'); apiUrlInput.type = 'text'; apiUrlInput.id = 'apiUrl'; apiUrlInput.value = config.apiUrl; apiUrlInput.placeholder = '输入API URL'; const apiKeyInput = document.createElement('input'); apiKeyInput.type = 'password'; apiKeyInput.id = 'apiKey'; apiKeyInput.value = config.apiKey; apiKeyInput.placeholder = '输入API Key'; const modelInput = document.createElement('input'); modelInput.type = 'text'; modelInput.id = 'model'; modelInput.value = config.model; modelInput.placeholder = '输入AI模型名称'; const promptInput = document.createElement('textarea'); promptInput.id = 'prompt'; promptInput.value = config.prompt; promptInput.placeholder = '输入提示词'; promptInput.style.height = '100px'; promptInput.style.resize = 'vertical'; // 添加标签和输入框到表单 const addFormField = (label, input) => { const fieldContainer = document.createElement('div'); fieldContainer.style.cssText = ` display: flex; flex-direction: column; gap: 4px; `; const labelElement = document.createElement('label'); labelElement.textContent = label; labelElement.style.cssText = ` font-size: 14px; color: #333; font-weight: 500; `; input.style.cssText = ` width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-family: inherit; `; fieldContainer.appendChild(labelElement); fieldContainer.appendChild(input); form.appendChild(fieldContainer); }; addFormField('API URL', apiUrlInput); addFormField('API Key', apiKeyInput); addFormField('AI 模型', modelInput); addFormField('提示词', promptInput); const saveBtn = document.createElement('button'); saveBtn.textContent = '保存设置'; saveBtn.style.cssText = ` background: #007AFF; color: white; border: none; padding: 10px; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; transition: background-color 0.2s; `; saveBtn.addEventListener('mouseover', () => { saveBtn.style.backgroundColor = '#0056b3'; }); saveBtn.addEventListener('mouseout', () => { saveBtn.style.backgroundColor = '#007AFF'; }); // 修改保存逻辑 saveBtn.addEventListener('click', (e) => { e.preventDefault(); // 获取表单值 const newApiUrl = apiUrlInput.value.trim(); const newApiKey = apiKeyInput.value.trim(); const newModel = modelInput.value.trim(); const newPrompt = promptInput.value.trim(); // 保存到GM存储 GM_setValue('apiUrl', newApiUrl); GM_setValue('apiKey', newApiKey); GM_setValue('model', newModel); GM_setValue('prompt', newPrompt); // 更新内存中的配置 config.apiUrl = newApiUrl; config.apiKey = newApiKey; config.model = newModel; config.prompt = newPrompt; // 显示保存成功提示 const successMsg = document.createElement('div'); successMsg.textContent = '设置已保存'; successMsg.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: #4CAF50; color: white; padding: 10px 20px; border-radius: 4px; z-index: 1000001; font-size: 14px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); `; document.body.appendChild(successMsg); setTimeout(() => successMsg.remove(), 2000); // 关闭设置界面 settingsContainer.style.display = 'none'; }); header.appendChild(title); header.appendChild(closeBtn); form.appendChild(saveBtn); settingsContainer.appendChild(header); settingsContainer.appendChild(form); document.body.appendChild(settingsContainer); closeBtn.addEventListener('click', () => { settingsContainer.style.display = 'none'; }); makeDraggable(settingsContainer); return settingsContainer; } // 修改右键菜单 function createContextMenu(e, icon) { e.preventDefault(); const menu = document.createElement('div'); menu.style.cssText = ` position: fixed; z-index: 1000000; background: rgba(255, 255, 255, 0.98); border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 8px 0; min-width: 150px; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); `; const menuItems = [ { text: '打开设置', action: () => { const settings = document.getElementById('website-summary-settings'); if (settings) { settings.style.display = 'block'; } }} ]; menuItems.forEach(item => { const menuItem = document.createElement('div'); menuItem.textContent = item.text; menuItem.style.cssText = ` padding: 8px 16px; cursor: pointer; transition: background-color 0.2s; `; menuItem.addEventListener('mouseover', () => { menuItem.style.backgroundColor = 'rgba(0, 0, 0, 0.05)'; }); menuItem.addEventListener('mouseout', () => { menuItem.style.backgroundColor = 'transparent'; }); menuItem.addEventListener('click', () => { item.action(); menu.remove(); }); menu.appendChild(menuItem); }); menu.style.left = `${e.clientX}px`; menu.style.top = `${e.clientY}px`; document.body.appendChild(menu); const closeMenu = (e) => { if (!menu.contains(e.target) && e.target !== icon) { menu.remove(); } }; document.addEventListener('click', closeMenu); menu.addEventListener('click', () => { document.removeEventListener('click', closeMenu); }); } // 实现拖拽功能 function makeDraggable(element) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; const header = element.querySelector('div') || element; header.addEventListener('mousedown', dragMouseDown); function dragMouseDown(e) { e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.addEventListener('mouseup', closeDragElement); document.addEventListener('mousemove', elementDrag); } function elementDrag(e) { e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; if (element.id === 'website-summary-icon') { const newTop = element.offsetTop - pos2; const maxTop = window.innerHeight - element.offsetHeight; const clampedTop = Math.max(0, Math.min(newTop, maxTop)); element.style.top = clampedTop + "px"; element.style.right = "20px"; element.style.left = "auto"; } else { element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; } } function closeDragElement() { document.removeEventListener('mouseup', closeDragElement); document.removeEventListener('mousemove', elementDrag); if (element.id === 'website-summary-icon') { config.iconPosition = { y: element.offsetTop }; GM_setValue('iconPosition', config.iconPosition); } } } // 获取页面内容 function getPageContent() { try { const clone = document.body.cloneNode(true); const elementsToRemove = clone.querySelectorAll('script, style, iframe, nav, header, footer, .ad, .advertisement, .social-share, .comment, .related-content'); elementsToRemove.forEach(el => el.remove()); return clone.innerText.replace(/\s+/g, ' ').trim().slice(0, 4000); } catch (error) { console.error('获取页面内容失败:', error); return document.body.innerText.slice(0, 4000); } } // 调用API获取总结 function getSummary(content) { return new Promise((resolve, reject) => { // 直接从config中获取API Key const currentApiKey = config.apiKey; const currentApiUrl = config.apiUrl; const currentModel = config.model; const currentPrompt = config.prompt; console.log('当前API Key:', currentApiKey ? '已设置' : '未设置'); console.log('当前API URL:', currentApiUrl); console.log('当前Model:', currentModel); if (!currentApiKey || currentApiKey.trim() === '') { resolve('请先设置API Key'); return; } const requestData = { model: currentModel, messages: [ { role: 'system', content: '你是一个专业的网页内容总结助手,善于使用markdown格式来组织信息。' }, { role: 'user', content: currentPrompt + content } ], temperature: 0.7 }; GM_xmlhttpRequest({ method: 'POST', url: currentApiUrl, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${currentApiKey}` }, data: JSON.stringify(requestData), onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.error) { console.error('API错误:', data.error); resolve('API调用失败: ' + data.error.message); return; } if (data.choices && data.choices[0] && data.choices[0].message) { resolve(data.choices[0].message.content); } else { console.error('API响应格式错误:', data); resolve('API响应格式错误,请检查配置。'); } } catch (error) { console.error('解析API响应失败:', error); resolve('解析API响应失败,请检查网络连接。'); } }, onerror: function(error) { console.error('API调用失败:', error); resolve('API调用失败,请检查网络连接和API配置。'); } }); }); } // 渲染Markdown function renderContent(content) { const container = document.getElementById('website-summary-content'); if (!container) return; try { if (!content) throw new Error('内容为空'); // 处理Markdown内容 let html = window.marked.parse(content); container.innerHTML = html; const style = document.createElement('style'); style.textContent = ` #website-summary-content { font-size: 14px; line-height: 1.6; color: #333; } #website-summary-content h1, #website-summary-content h2, #website-summary-content h3 { margin-top: 20px; margin-bottom: 10px; color: #222; } #website-summary-content p { margin: 10px 0; } #website-summary-content code { background: #f5f5f5; padding: 2px 4px; border-radius: 3px; font-family: monospace; } #website-summary-content pre { background: #f5f5f5; padding: 15px; border-radius: 5px; overflow-x: auto; } #website-summary-content blockquote { border-left: 4px solid #ddd; margin: 10px 0; padding-left: 15px; color: #666; } #website-summary-content ul, #website-summary-content ol { margin: 10px 0; padding-left: 20px; } #website-summary-content li { margin: 5px 0; } #website-summary-content table { border-collapse: collapse; width: 100%; margin: 10px 0; } #website-summary-content th, #website-summary-content td { border: 1px solid #ddd; padding: 8px; text-align: left; } #website-summary-content th { background: #f5f5f5; } `; document.head.appendChild(style); } catch (error) { console.error('渲染内容失败:', error); container.innerHTML = '

渲染内容失败,请刷新页面重试。

'; } } // 初始化 function init() { try { waitForLibrary('marked', (marked) => { marked.setOptions({ breaks: true, gfm: true }); }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { createIcon(); createUI(); createSettingsUI(); }); } else { createIcon(); createUI(); createSettingsUI(); } } catch (error) { console.error('初始化失败:', error); } } // 启动脚本 init(); })();