Created
June 1, 2025 00:29
-
-
Save hatrd/5e0b835ee5f840cb9be4c2814e7af71d to your computer and use it in GitHub Desktop.
用 Deepseek 智能总结当前网页
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name DeepSeek 网页总结助手 | |
| // @namespace https://chat.openai.com/ | |
| // @version 1.3 | |
| // @description 框选网页内容,自动用 DeepSeek 生成总结,可自定义 Prompt 和 API Key!支持自动检测正文和Markdown渲染 | |
| // @author You | |
| // @match *://*/* | |
| // @grant GM_xmlhttpRequest | |
| // @grant GM_setValue | |
| // @grant GM_getValue | |
| // @grant GM_registerMenuCommand | |
| // @connect api.deepseek.com | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| // 缓存变量 | |
| let lastSummaryResult = null; | |
| let buttonsVisible = GM_getValue('deepseek_buttons_visible', true); | |
| // 注册TamperMonkey菜单 | |
| GM_registerMenuCommand('⚙️ 设置 API Key 和 Prompt', () => { | |
| createSettingsPanel(); | |
| }); | |
| GM_registerMenuCommand(buttonsVisible ? '👁️ 隐藏DeepSeek按钮' : '👁️ 显示DeepSeek按钮', () => { | |
| toggleButtons(); | |
| }); | |
| GM_registerMenuCommand('🐛 切换调试模式', () => { | |
| const currentDebugMode = GM_getValue('deepseek_debug_mode', false); | |
| GM_setValue('deepseek_debug_mode', !currentDebugMode); | |
| // 显示通知 | |
| const newStatus = !currentDebugMode ? '已开启' : '已关闭'; | |
| alert(`🐛 调试模式${newStatus}\n${!currentDebugMode ? '总结结果将显示详细的调试信息' : '总结结果将不再显示调试信息'}`); | |
| }); | |
| // 切换按钮显示状态 | |
| function toggleButtons() { | |
| buttonsVisible = !buttonsVisible; | |
| GM_setValue('deepseek_buttons_visible', buttonsVisible); | |
| if (buttonsVisible) { | |
| autoSummaryButton.style.display = 'flex'; | |
| } else { | |
| autoSummaryButton.style.display = 'none'; | |
| } | |
| // 重新注册菜单命令 | |
| // location.reload(); | |
| } | |
| // 简单的Markdown渲染器 | |
| function renderMarkdown(text) { | |
| return text | |
| // 标题 | |
| .replace(/^### (.*$)/gim, '<h3>$1</h3>') | |
| .replace(/^## (.*$)/gim, '<h2>$1</h2>') | |
| .replace(/^# (.*$)/gim, '<h1>$1</h1>') | |
| // 粗体 | |
| .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') | |
| .replace(/__(.*?)__/g, '<strong>$1</strong>') | |
| // 斜体 | |
| .replace(/\*(.*?)\*/g, '<em>$1</em>') | |
| .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(/^\* (.*$)/gim, '<li>$1</li>') | |
| .replace(/^- (.*$)/gim, '<li>$1</li>') | |
| .replace(/^\d+\. (.*$)/gim, '<li>$1</li>') | |
| // 换行 | |
| .replace(/\n\n/g, '</p><p>') | |
| .replace(/\n/g, '<br>'); | |
| // 包装列表项 | |
| let result = '<p>' + result + '</p>'; | |
| result = result.replace(/(<li>.*<\/li>)/gs, '<ul>$1</ul>'); | |
| result = result.replace(/<\/ul>\s*<ul>/g, ''); | |
| return result; | |
| } | |
| // 自动检测网页正文内容 | |
| function extractMainContent() { | |
| // 常见的正文选择器,按优先级排序 | |
| const contentSelectors = [ | |
| // 语义化标签 | |
| 'main', | |
| 'article', | |
| '[role="main"]', | |
| '.main-content', | |
| '.content', | |
| '.post-content', | |
| '.article-content', | |
| '.entry-content', | |
| '.page-content', | |
| // 常见的内容类名 | |
| '.content-body', | |
| '.article-body', | |
| '.post-body', | |
| '.main-text', | |
| '.text-content', | |
| '.article-text', | |
| // 中文网站常见类名 | |
| '.article', | |
| '.post', | |
| '.content-wrap', | |
| '.main-wrap', | |
| '.container .content', | |
| // 特定网站适配 | |
| '.markdown-body', // GitHub | |
| '.Post-RichText', // 知乎 | |
| '.content_area', // CSDN | |
| '.article-content', // 简书 | |
| '.rich_media_content', // 微信公众号 | |
| '.content', // 通用 | |
| ]; | |
| let bestContent = null; | |
| let maxScore = 0; | |
| for (const selector of contentSelectors) { | |
| const elements = document.querySelectorAll(selector); | |
| for (const element of elements) { | |
| const text = element.innerText || element.textContent || ''; | |
| const textLength = text.trim().length; | |
| // 跳过太短的内容 | |
| if (textLength < 100) continue; | |
| // 计算内容质量分数 | |
| let score = textLength; | |
| // 加分项 | |
| if (element.tagName === 'MAIN' || element.tagName === 'ARTICLE') score += 1000; | |
| if (element.querySelector('p')) score += 500; // 包含段落 | |
| if (element.querySelector('h1, h2, h3, h4, h5, h6')) score += 300; // 包含标题 | |
| // 减分项 | |
| if (element.querySelector('nav')) score -= 200; // 包含导航 | |
| if (element.querySelector('.sidebar')) score -= 200; // 包含侧边栏 | |
| if (element.querySelector('.footer')) score -= 200; // 包含页脚 | |
| if (element.querySelector('.header')) score -= 200; // 包含页头 | |
| if (element.querySelector('.ad, .advertisement')) score -= 300; // 包含广告 | |
| // 检查是否包含太多链接(可能是导航区域) | |
| const links = element.querySelectorAll('a'); | |
| const linkRatio = links.length / (textLength / 100); | |
| if (linkRatio > 5) score -= 400; | |
| if (score > maxScore) { | |
| maxScore = score; | |
| bestContent = element; | |
| } | |
| } | |
| } | |
| // 如果没找到合适的内容,尝试body但排除明显的非内容区域 | |
| if (!bestContent) { | |
| const bodyText = document.body.innerText || document.body.textContent || ''; | |
| if (bodyText.trim().length > 200) { | |
| // 创建一个临时元素来清理内容 | |
| const tempDiv = document.createElement('div'); | |
| tempDiv.innerHTML = document.body.innerHTML; | |
| // 移除明显的非内容元素 | |
| const removeSelectors = [ | |
| 'nav', 'header', 'footer', 'aside', | |
| '.nav', '.header', '.footer', '.sidebar', '.menu', | |
| '.advertisement', '.ad', '.ads', '.banner', | |
| 'script', 'style', 'noscript' | |
| ]; | |
| removeSelectors.forEach(selector => { | |
| const elements = tempDiv.querySelectorAll(selector); | |
| elements.forEach(el => el.remove()); | |
| }); | |
| bestContent = tempDiv; | |
| } | |
| } | |
| if (bestContent) { | |
| let text = bestContent.innerText || bestContent.textContent || ''; | |
| // 清理文本 | |
| text = text | |
| .replace(/\s+/g, ' ') // 合并多个空白字符 | |
| .replace(/\n\s*\n/g, '\n') // 合并多个换行 | |
| .trim(); | |
| // 限制长度,避免内容过长 | |
| if (text.length > 16000) { | |
| // 智能截断:尝试在句号、换行或段落处截断 | |
| let cutPoint = 16000; | |
| const sentenceEnd = text.lastIndexOf('。', cutPoint); | |
| const paragraphEnd = text.lastIndexOf('\n\n', cutPoint); | |
| const lineEnd = text.lastIndexOf('\n', cutPoint); | |
| // 选择最佳截断点 | |
| if (sentenceEnd > cutPoint - 500) { | |
| cutPoint = sentenceEnd + 1; | |
| } else if (paragraphEnd > cutPoint - 1000) { | |
| cutPoint = paragraphEnd; | |
| } else if (lineEnd > cutPoint - 200) { | |
| cutPoint = lineEnd; | |
| } | |
| text = text.substring(0, cutPoint) + '\n\n[内容过长,已智能截断...]'; | |
| } | |
| return text; | |
| } | |
| return null; | |
| } | |
| // 创建模态背景 | |
| function createModalBackground() { | |
| const overlay = document.createElement('div'); | |
| overlay.className = 'deepseek-modal-overlay'; | |
| overlay.style.cssText = ` | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.5); | |
| z-index: 9998; | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| `; | |
| document.body.appendChild(overlay); | |
| // 淡入效果 | |
| setTimeout(() => overlay.style.opacity = '1', 10); | |
| return overlay; | |
| } | |
| // 创建设置面板 | |
| function createSettingsPanel() { | |
| // 检查是否已存在设置面板 | |
| const existingPanel = document.getElementById('deepseek-settings-panel'); | |
| if (existingPanel) { | |
| existingPanel.remove(); | |
| const existingOverlay = document.querySelector('.deepseek-modal-overlay'); | |
| if (existingOverlay) existingOverlay.remove(); | |
| return; | |
| } | |
| const overlay = createModalBackground(); | |
| const panel = document.createElement('div'); | |
| panel.id = 'deepseek-settings-panel'; | |
| panel.style.cssText = ` | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%) scale(0.9); | |
| background: #fff; | |
| border: none; | |
| padding: 25px; | |
| border-radius: 15px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.3); | |
| z-index: 9999; | |
| max-width: 400px; | |
| width: 90%; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| opacity: 0; | |
| transition: all 0.3s ease; | |
| `; | |
| panel.innerHTML = ` | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> | |
| <h3 style="margin: 0; color: #333; font-size: 18px;">🛠️ DeepSeek 设置</h3> | |
| <button id="deepseek-close" style=" | |
| background: none; | |
| border: none; | |
| font-size: 24px; | |
| cursor: pointer; | |
| color: #666; | |
| padding: 0; | |
| width: 30px; | |
| height: 30px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: background-color 0.2s; | |
| " title="关闭">×</button> | |
| </div> | |
| <div style="margin-bottom: 20px;"> | |
| <label style="display: block; margin-bottom: 8px; font-weight: 500; color: #333;">API Key:</label> | |
| <input id="deepseek-key" type="password" style=" | |
| width: 100%; | |
| padding: 12px; | |
| border: 2px solid #e1e5e9; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| transition: border-color 0.2s; | |
| box-sizing: border-box; | |
| " placeholder="sk-xxx" /> | |
| </div> | |
| <div style="margin-bottom: 25px;"> | |
| <label style="display: block; margin-bottom: 8px; font-weight: 500; color: #333;">Prompt 模板:</label> | |
| <textarea id="deepseek-prompt" style=" | |
| width: 100%; | |
| height: 100px; | |
| padding: 12px; | |
| border: 2px solid #e1e5e9; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| resize: vertical; | |
| font-family: inherit; | |
| transition: border-color 0.2s; | |
| box-sizing: border-box; | |
| " placeholder="请总结以下内容:{content}"></textarea> | |
| <small style="color: #666; font-size: 12px;">使用 {content} 作为内容占位符</small> | |
| </div> | |
| <div style="margin-bottom: 25px;"> | |
| <label style="display: flex; align-items: center; cursor: pointer; font-weight: 500; color: #333;"> | |
| <input type="checkbox" id="deepseek-debug" style="margin-right: 8px; transform: scale(1.2);"> | |
| 显示调试信息(输入/输出长度等) | |
| </label> | |
| <small style="color: #666; font-size: 12px; margin-left: 24px;">开启后会在总结结果中显示详细的调试信息</small> | |
| </div> | |
| <div style="display: flex; gap: 10px;"> | |
| <button id="deepseek-save" style=" | |
| flex: 1; | |
| padding: 12px 20px; | |
| background: #007bff; | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| ">💾 保存配置</button> | |
| <button id="deepseek-cancel" style=" | |
| padding: 12px 20px; | |
| background: #6c757d; | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| ">取消</button> | |
| </div> | |
| `; | |
| document.body.appendChild(panel); | |
| // 添加样式效果 | |
| setTimeout(() => { | |
| panel.style.opacity = '1'; | |
| panel.style.transform = 'translate(-50%, -50%) scale(1)'; | |
| }, 10); | |
| // 加载保存的配置 | |
| const savedKey = GM_getValue('deepseek_api_key', ''); | |
| const savedPrompt = localStorage.getItem('deepseek_prompt_template') || "告诉我这篇文章最有价值的信息是什么:==={content}===,用中文回复"; | |
| const savedDebug = GM_getValue('deepseek_debug_mode', false); | |
| if (savedKey) document.getElementById('deepseek-key').value = savedKey; | |
| document.getElementById('deepseek-prompt').value = savedPrompt; | |
| document.getElementById('deepseek-debug').checked = savedDebug; | |
| // 关闭面板函数 | |
| function closePanel() { | |
| panel.style.opacity = '0'; | |
| panel.style.transform = 'translate(-50%, -50%) scale(0.9)'; | |
| overlay.style.opacity = '0'; | |
| setTimeout(() => { | |
| panel.remove(); | |
| overlay.remove(); | |
| }, 300); | |
| } | |
| // 事件监听 | |
| document.getElementById('deepseek-save').onclick = () => { | |
| const key = document.getElementById('deepseek-key').value.trim(); | |
| const prompt = document.getElementById('deepseek-prompt').value.trim(); | |
| const debugMode = document.getElementById('deepseek-debug').checked; | |
| if (!key) { | |
| alert('⚠️ 请输入 API Key'); | |
| return; | |
| } | |
| GM_setValue('deepseek_api_key', key); | |
| localStorage.setItem('deepseek_prompt_template', prompt); | |
| GM_setValue('deepseek_debug_mode', debugMode); | |
| // 显示成功提示 | |
| const saveBtn = document.getElementById('deepseek-save'); | |
| const originalText = saveBtn.innerHTML; | |
| saveBtn.innerHTML = '✅ 已保存'; | |
| saveBtn.style.background = '#28a745'; | |
| setTimeout(() => { | |
| saveBtn.innerHTML = originalText; | |
| saveBtn.style.background = '#007bff'; | |
| closePanel(); | |
| }, 1000); | |
| }; | |
| document.getElementById('deepseek-close').onclick = closePanel; | |
| document.getElementById('deepseek-cancel').onclick = closePanel; | |
| // 点击背景关闭 | |
| overlay.onclick = closePanel; | |
| // ESC键关闭 | |
| const escHandler = (e) => { | |
| if (e.key === 'Escape') { | |
| closePanel(); | |
| document.removeEventListener('keydown', escHandler); | |
| } | |
| }; | |
| document.addEventListener('keydown', escHandler); | |
| // 输入框焦点样式 | |
| const inputs = panel.querySelectorAll('input, textarea'); | |
| inputs.forEach(input => { | |
| input.addEventListener('focus', () => { | |
| input.style.borderColor = '#007bff'; | |
| }); | |
| input.addEventListener('blur', () => { | |
| input.style.borderColor = '#e1e5e9'; | |
| }); | |
| }); | |
| // 按钮悬停效果 | |
| document.getElementById('deepseek-save').addEventListener('mouseenter', function() { | |
| this.style.background = '#0056b3'; | |
| }); | |
| document.getElementById('deepseek-save').addEventListener('mouseleave', function() { | |
| this.style.background = '#007bff'; | |
| }); | |
| document.getElementById('deepseek-cancel').addEventListener('mouseenter', function() { | |
| this.style.background = '#545b62'; | |
| }); | |
| document.getElementById('deepseek-cancel').addEventListener('mouseleave', function() { | |
| this.style.background = '#6c757d'; | |
| }); | |
| document.getElementById('deepseek-close').addEventListener('mouseenter', function() { | |
| this.style.backgroundColor = '#f8f9fa'; | |
| }); | |
| document.getElementById('deepseek-close').addEventListener('mouseleave', function() { | |
| this.style.backgroundColor = 'transparent'; | |
| }); | |
| } | |
| // 显示总结结果弹窗 | |
| function showResultPopup(summary, duration, isMarkdown = true, originalContent = '') { | |
| // 移除已存在的结果弹窗 | |
| const existingPopup = document.getElementById('deepseek-result-popup'); | |
| if (existingPopup) existingPopup.remove(); | |
| const overlay = createModalBackground(); | |
| const popup = document.createElement('div'); | |
| popup.id = 'deepseek-result-popup'; | |
| popup.style.cssText = ` | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%) scale(0.9); | |
| background: #fff; | |
| border: none; | |
| padding: 0; | |
| border-radius: 15px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.3); | |
| z-index: 9999; | |
| max-width: 800px; | |
| width: 90%; | |
| max-height: 85vh; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| opacity: 0; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| flex-direction: column; | |
| `; | |
| const durationText = duration ? ` (${duration}ms)` : ''; | |
| const currentUrl = window.location.href; | |
| const domain = window.location.hostname; | |
| popup.innerHTML = ` | |
| <div style=" | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 20px 25px; | |
| border-bottom: 1px solid #e1e5e9; | |
| background: #f8f9fa; | |
| border-radius: 15px 15px 0 0; | |
| "> | |
| <h3 style="margin: 0; color: #333; font-size: 18px;">📝 总结结果${durationText}</h3> | |
| <div style="display: flex; gap: 8px; align-items: center;"> | |
| <button id="save-result" style=" | |
| background: #17a2b8; | |
| color: white; | |
| border: none; | |
| padding: 6px 12px; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| " title="保存为文件">💾 保存</button> | |
| <button id="copy-result" style=" | |
| background: #28a745; | |
| color: white; | |
| border: none; | |
| padding: 6px 12px; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| " title="复制到剪贴板">📋 复制</button> | |
| <button id="close-result" style=" | |
| background: none; | |
| border: none; | |
| font-size: 24px; | |
| cursor: pointer; | |
| color: #666; | |
| padding: 0; | |
| width: 30px; | |
| height: 30px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: background-color 0.2s; | |
| " title="关闭">×</button> | |
| </div> | |
| </div> | |
| <div style=" | |
| padding: 25px; | |
| overflow-y: auto; | |
| flex: 1; | |
| line-height: 1.6; | |
| color: #333; | |
| font-size: 14px; | |
| " id="result-content"></div> | |
| `; | |
| document.body.appendChild(popup); | |
| // 设置内容 - 支持Markdown渲染 | |
| const contentDiv = document.getElementById('result-content'); | |
| if (isMarkdown && (summary.includes('**') || summary.includes('##') || summary.includes('*'))) { | |
| contentDiv.innerHTML = renderMarkdown(summary); | |
| // 为渲染后的内容添加样式 | |
| contentDiv.style.cssText += ` | |
| h1, h2, h3 { color: #2c3e50; margin: 20px 0 10px 0; } | |
| h1 { font-size: 24px; border-bottom: 2px solid #3498db; padding-bottom: 10px; } | |
| h2 { font-size: 20px; border-bottom: 1px solid #bdc3c7; padding-bottom: 5px; } | |
| h3 { font-size: 18px; } | |
| strong { color: #e74c3c; font-weight: 600; } | |
| em { color: #8e44ad; font-style: italic; } | |
| code { background: #f8f9fa; padding: 2px 6px; border-radius: 4px; font-family: 'Courier New', monospace; color: #e83e8c; } | |
| pre { background: #f8f9fa; padding: 15px; border-radius: 8px; overflow-x: auto; border-left: 4px solid #007bff; } | |
| pre code { background: none; padding: 0; color: #333; } | |
| ul, ol { margin: 10px 0; padding-left: 20px; } | |
| li { margin: 5px 0; } | |
| a { color: #007bff; text-decoration: none; } | |
| a:hover { text-decoration: underline; } | |
| p { margin: 10px 0; } | |
| `; | |
| } else { | |
| contentDiv.textContent = summary; | |
| } | |
| // 添加动画效果 | |
| setTimeout(() => { | |
| popup.style.opacity = '1'; | |
| popup.style.transform = 'translate(-50%, -50%) scale(1)'; | |
| }, 10); | |
| // 关闭函数 | |
| function closePopup() { | |
| popup.style.opacity = '0'; | |
| popup.style.transform = 'translate(-50%, -50%) scale(0.9)'; | |
| overlay.style.opacity = '0'; | |
| setTimeout(() => { | |
| popup.remove(); | |
| overlay.remove(); | |
| }, 300); | |
| } | |
| // 生成文件名 | |
| function generateFileName() { | |
| const now = new Date(); | |
| const timestamp = now.getFullYear() + | |
| String(now.getMonth() + 1).padStart(2, '0') + | |
| String(now.getDate()).padStart(2, '0') + '_' + | |
| String(now.getHours()).padStart(2, '0') + | |
| String(now.getMinutes()).padStart(2, '0') + | |
| String(now.getSeconds()).padStart(2, '0'); | |
| return `${domain}_${timestamp}.md`; | |
| } | |
| // 生成保存内容 | |
| function generateSaveContent() { | |
| const now = new Date(); | |
| const dateStr = now.toLocaleString('zh-CN'); | |
| return `# 网页总结 | |
| ## 基本信息 | |
| - **网站**: ${domain} | |
| - **URL**: ${currentUrl} | |
| - **总结时间**: ${dateStr} | |
| - **处理耗时**: ${duration}ms | |
| ## 总结内容 | |
| ${summary} | |
| ## 完整原始内容 | |
| \`\`\` | |
| ${originalContent} | |
| \`\`\` | |
| --- | |
| *由 DeepSeek 网页总结助手生成*`; | |
| } | |
| // 保存功能 | |
| document.getElementById('save-result').onclick = () => { | |
| try { | |
| const content = generateSaveContent(); | |
| const fileName = generateFileName(); | |
| // 创建下载链接 | |
| const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' }); | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = fileName; | |
| link.style.display = 'none'; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| URL.revokeObjectURL(url); | |
| // 显示成功提示 | |
| const saveBtn = document.getElementById('save-result'); | |
| const originalText = saveBtn.innerHTML; | |
| saveBtn.innerHTML = '✅ 已保存'; | |
| saveBtn.style.background = '#28a745'; | |
| setTimeout(() => { | |
| saveBtn.innerHTML = originalText; | |
| saveBtn.style.background = '#17a2b8'; | |
| }, 2000); | |
| } catch (error) { | |
| alert('保存失败:' + error.message); | |
| } | |
| }; | |
| // 复制功能 - 复制原始文本而不是HTML | |
| document.getElementById('copy-result').onclick = async () => { | |
| try { | |
| await navigator.clipboard.writeText(summary); | |
| const copyBtn = document.getElementById('copy-result'); | |
| const originalText = copyBtn.innerHTML; | |
| copyBtn.innerHTML = '✅ 已复制'; | |
| copyBtn.style.background = '#20c997'; | |
| setTimeout(() => { | |
| copyBtn.innerHTML = originalText; | |
| copyBtn.style.background = '#28a745'; | |
| }, 1500); | |
| } catch (err) { | |
| // 降级方案 | |
| const textArea = document.createElement('textarea'); | |
| textArea.value = summary; | |
| document.body.appendChild(textArea); | |
| textArea.select(); | |
| document.execCommand('copy'); | |
| document.body.removeChild(textArea); | |
| alert('📋 内容已复制到剪贴板'); | |
| } | |
| }; | |
| // 事件监听 | |
| document.getElementById('close-result').onclick = closePopup; | |
| overlay.onclick = closePopup; | |
| // ESC键关闭 | |
| const escHandler = (e) => { | |
| if (e.key === 'Escape') { | |
| closePopup(); | |
| document.removeEventListener('keydown', escHandler); | |
| } | |
| }; | |
| document.addEventListener('keydown', escHandler); | |
| // 按钮悬停效果 | |
| const buttons = [ | |
| { id: 'save-result', normalColor: '#17a2b8', hoverColor: '#138496' }, | |
| { id: 'copy-result', normalColor: '#28a745', hoverColor: '#1e7e34' }, | |
| ]; | |
| buttons.forEach(btn => { | |
| const element = document.getElementById(btn.id); | |
| element.addEventListener('mouseenter', function() { | |
| if (!this.innerHTML.includes('✅')) { | |
| this.style.background = btn.hoverColor; | |
| } | |
| }); | |
| element.addEventListener('mouseleave', function() { | |
| if (!this.innerHTML.includes('✅')) { | |
| this.style.background = btn.normalColor; | |
| } | |
| }); | |
| }); | |
| document.getElementById('close-result').addEventListener('mouseenter', function() { | |
| this.style.backgroundColor = '#f8f9fa'; | |
| }); | |
| document.getElementById('close-result').addEventListener('mouseleave', function() { | |
| this.style.backgroundColor = 'transparent'; | |
| }); | |
| } | |
| // API 调用函数 | |
| function getSummary(userText) { | |
| return new Promise((resolve, reject) => { | |
| const startTime = performance.now(); | |
| const key = GM_getValue('deepseek_api_key', ''); | |
| const promptTemplate = localStorage.getItem('deepseek_prompt_template') || "告诉我这篇文章最有价值的信息是什么:==={content}===,用中文回复"; | |
| if (!key) { | |
| resolve({ | |
| summary: "❌ 未设置 API Key,请在油猴菜单中进行配置", | |
| duration: Math.round(performance.now() - startTime), | |
| originalContent: userText | |
| }); | |
| return; | |
| } | |
| const prompt = promptTemplate.replace('{content}', userText); | |
| const body = JSON.stringify({ | |
| model: "deepseek-chat", | |
| messages: [{ role: "user", content: prompt }], | |
| temperature: 0.3, | |
| max_tokens: 4000, // 增加最大输出token数,确保总结不被截断 | |
| top_p: 0.95, // 添加top_p参数提高输出质量 | |
| frequency_penalty: 0.1, // 减少重复内容 | |
| presence_penalty: 0.1 // 鼓励多样化表达 | |
| }); | |
| GM_xmlhttpRequest({ | |
| method: "POST", | |
| url: "https://api.deepseek.com/v1/chat/completions", | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Authorization": "Bearer " + key | |
| }, | |
| data: body, | |
| onload: function (res) { | |
| const duration = Math.round(performance.now() - startTime); | |
| try { | |
| const json = JSON.parse(res.responseText); | |
| if (json.error) { | |
| resolve({ | |
| summary: "❌ API 错误:" + (json.error.message || json.error), | |
| duration: duration, | |
| originalContent: userText | |
| }); | |
| return; | |
| } | |
| const result = json.choices?.[0]?.message?.content?.trim(); | |
| // 添加调试信息 | |
| const inputLength = userText.length; | |
| const outputLength = result ? result.length : 0; | |
| const finishReason = json.choices?.[0]?.finish_reason; | |
| let debugInfo = ''; | |
| const debugMode = GM_getValue('deepseek_debug_mode', false); | |
| if (debugMode) { | |
| debugInfo = `\n\n---\n📊 调试信息:\n`; | |
| debugInfo += `• 输入长度:${inputLength} 字符\n`; | |
| debugInfo += `• 输出长度:${outputLength} 字符\n`; | |
| debugInfo += `• 完成原因:${finishReason || '未知'}\n`; | |
| debugInfo += `• 处理时间:${duration}ms`; | |
| if (finishReason === 'length') { | |
| debugInfo += `\n⚠️ 输出因长度限制被截断,建议缩短输入内容或调整max_tokens参数`; | |
| } | |
| } | |
| resolve({ | |
| summary: (result || "❌ 无法生成总结") + debugInfo, | |
| duration: duration, | |
| originalContent: userText | |
| }); | |
| } catch (e) { | |
| resolve({ | |
| summary: "❌ 响应解析失败:" + e.message, | |
| duration: duration, | |
| originalContent: userText | |
| }); | |
| } | |
| }, | |
| onerror: function (err) { | |
| const duration = Math.round(performance.now() - startTime); | |
| resolve({ | |
| summary: "❌ 请求失败,请检查网络连接", | |
| duration: duration, | |
| originalContent: userText | |
| }); | |
| } | |
| }); | |
| }); | |
| } | |
| // 创建自动总结按钮 | |
| const autoSummaryButton = document.createElement('button'); | |
| autoSummaryButton.innerHTML = "🤖 智能总结"; | |
| autoSummaryButton.style.cssText = ` | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%); | |
| color: white; | |
| border: none; | |
| padding: 12px 20px; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| font-weight: 500; | |
| z-index: 9996; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |
| transition: all 0.2s ease; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| user-select: none; | |
| display: ${buttonsVisible ? 'flex' : 'none'}; | |
| align-items: center; | |
| justify-content: center; | |
| `; | |
| autoSummaryButton.title = "自动检测并总结网页正文"; | |
| document.body.appendChild(autoSummaryButton); | |
| // 自动总结按钮悬停效果 | |
| autoSummaryButton.addEventListener('mouseenter', () => { | |
| autoSummaryButton.style.transform = 'translateY(-2px)'; | |
| autoSummaryButton.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)'; | |
| }); | |
| autoSummaryButton.addEventListener('mouseleave', () => { | |
| autoSummaryButton.style.transform = 'translateY(0)'; | |
| autoSummaryButton.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; | |
| }); | |
| // 自动总结按钮点击事件 | |
| autoSummaryButton.addEventListener('click', async () => { | |
| // 检查是否已配置API Key | |
| const apiKey = GM_getValue('deepseek_api_key', ''); | |
| if (!apiKey) { | |
| // 如果没有配置API Key,先弹出设置页面 | |
| createSettingsPanel(); | |
| return; | |
| } | |
| // 如果有缓存的结果,直接显示 | |
| if (lastSummaryResult) { | |
| showResultPopup( | |
| lastSummaryResult.summary, | |
| lastSummaryResult.duration, | |
| true, | |
| lastSummaryResult.originalContent | |
| ); | |
| return; | |
| } | |
| autoSummaryButton.innerHTML = "🔍 检测中..."; | |
| autoSummaryButton.style.cursor = "wait"; | |
| try { | |
| const mainContent = extractMainContent(); | |
| if (!mainContent) { | |
| showResultPopup("❌ 未能检测到网页正文内容,请尝试手动选择文本进行总结", 0, false, ''); | |
| return; | |
| } | |
| if (mainContent.length < 50) { | |
| showResultPopup("❌ 检测到的内容过短,请尝试手动选择文本进行总结", 0, false, mainContent); | |
| return; | |
| } | |
| autoSummaryButton.innerHTML = "⏳ 总结中..."; | |
| const result = await getSummary(mainContent); | |
| // 缓存成功的结果 | |
| if (!result.summary.startsWith('❌')) { | |
| lastSummaryResult = result; | |
| } | |
| showResultPopup(result.summary, result.duration, true, result.originalContent); | |
| } catch (error) { | |
| showResultPopup("❌ 自动总结时发生错误:" + error.message, 0, false, ''); | |
| } finally { | |
| autoSummaryButton.innerHTML = "🤖 智能总结"; | |
| autoSummaryButton.style.cursor = "pointer"; | |
| } | |
| }); | |
| // 添加全局样式,防止与网站样式冲突 | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| #deepseek-settings-panel *, #deepseek-result-popup * { | |
| box-sizing: border-box !important; | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment