// ==UserScript== // @name Mobile AI Summary (MD3) // @namespace http://tampermonkey.net/ // @version 1.0 // @description 为移动端设计的AI页面总结工具,采用Material Design 3风格 // @author Justin Ye // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @downloadURL none // ==/UserScript== (function() { 'use strict'; // MD3 风格定义 const md3Colors = { primary: '#006492', onPrimary: '#ffffff', primaryContainer: '#cae6ff', onPrimaryContainer: '#001e30', surface: '#f8f9ff', onSurface: '#191c20', surfaceContainer: '#f0f4f8', // 稍微深一点的背景 outline: '#72777f', shadow: 'rgba(0, 0, 0, 0.2)' }; // 新的提示词 const newPrompt = `你是一个专业的中文内容总结器。你的任务是分析提供的网页内容,**识别内容类型(例如:新闻报道、研究报告、普通文章、市场分析等)**,并在此基础上创建一个清晰、简洁、结构良好的中文总结。**总结必须严格依据原文内容,不得进行任何推测、假设或添加原文中未包含的信息。** 请严格遵守以下指南: 1. **内容类型识别与结构确定:** * 首先识别原文的内容类型。 * 根据内容类型,确定最合适的分段和总结重点。总结的结构应该逻辑清晰,反映原文的核心信息。 * 你可以使用 **\`##\`** 作为主要分段的标题,例如:对于新闻可以使用“事件概述”、“关键进展”等,对于报告可以使用“主要发现”、“数据支持”等。不必拘泥于原文的固定分段,但要确保覆盖核心要点。 2. **输出格式:** * 使用 **\`##\`** 表示主要段落标题。 * 使用 **\`•\`** 表示段落内的关键点和细节。 * 使用 **粗体** 突出重要术语、概念或关键信息。 * 使用 **\`>\`** 表示引人注目的原文引述(如果适用)。 3. **内容要求:** * 总结必须**严格忠于原文**,不允许加入任何个人观点、推测或假设。 * **识别并保留原文中的重要数据、数字、统计信息或关键事实。** * 根据识别的内容类型,调整总结的侧重点,但**所有信息必须来源于原文**。 4. **写作风格:** * 语言清晰简洁。 * 专业且客观的语调。 * 逻辑流畅。 * 易于理解。 * 聚焦于原文的核心信息和重要细节。 5. **重要规则:** * **DO NOT show your reasoning process.** (不要显示你的思考过程或内部步骤。)`; // 默认配置 const defaultConfig = { apiUrl: 'https://api.openai.com/v1/chat/completions', apiKey: '', prompt: newPrompt, model: 'gpt-3.5-turbo' }; // GM 函数兼容层 const GM = { setValue: (key, value) => { try { if (typeof GM_setValue !== 'undefined') { GM_setValue(key, value); } else { localStorage.setItem(`mobile_ai_summary_${key}`, JSON.stringify(value)); } } catch (error) { console.error('保存配置失败:', error); } }, getValue: (key, defaultValue) => { try { if (typeof GM_getValue !== 'undefined') { return GM_getValue(key, defaultValue); } const value = localStorage.getItem(`mobile_ai_summary_${key}`); return value ? JSON.parse(value) : defaultValue; } catch (error) { console.error('获取配置失败:', error); return defaultValue; } } }; // 获取配置 let config = { apiUrl: GM.getValue('apiUrl', defaultConfig.apiUrl), apiKey: GM.getValue('apiKey', defaultConfig.apiKey), prompt: GM.getValue('prompt', defaultConfig.prompt), model: GM.getValue('model', defaultConfig.model) }; // 加载 marked.js let markedLoaded = false; const markedScript = document.createElement('script'); markedScript.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js'; markedScript.onload = () => { markedLoaded = true; marked.setOptions({ breaks: true, gfm: true }); }; document.head.appendChild(markedScript); // 创建样式 const style = document.createElement('style'); style.textContent = ` /* Material Design 3 风格样式 */ :root { --md-sys-color-primary: ${md3Colors.primary}; --md-sys-color-on-primary: ${md3Colors.onPrimary}; --md-sys-color-primary-container: ${md3Colors.primaryContainer}; --md-sys-color-on-primary-container: ${md3Colors.onPrimaryContainer}; --md-sys-color-surface: ${md3Colors.surface}; --md-sys-color-on-surface: ${md3Colors.onSurface}; --md-sys-color-surface-container: ${md3Colors.surfaceContainer}; --md-sys-color-outline: ${md3Colors.outline}; --md-sys-shadow: ${md3Colors.shadow}; } .mas-fab-container { position: fixed; right: 16px; bottom: 100px; /* 避开底部导航栏 */ z-index: 999999; display: flex; flex-direction: column; gap: 16px; pointer-events: none; /* 允许点击穿透 */ } .mas-fab { width: 56px; height: 56px; border-radius: 16px; /* MD3 Large FAB shape */ background-color: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); border: none; box-shadow: 0 4px 8px 3px var(--md-sys-shadow); display: flex; align-items: center; justify-content: center; font-size: 24px; cursor: pointer; pointer-events: auto; transition: transform 0.2s, box-shadow 0.2s; -webkit-tap-highlight-color: transparent; } .mas-fab:active { transform: scale(0.95); box-shadow: 0 2px 4px 2px var(--md-sys-shadow); } .mas-fab svg { width: 24px; height: 24px; fill: currentColor; } /* Bottom Sheet 样式 */ .mas-bottom-sheet { position: fixed; left: 0; right: 0; bottom: 0; background-color: var(--md-sys-color-surface); border-top-left-radius: 28px; border-top-right-radius: 28px; box-shadow: 0 -2px 10px rgba(0,0,0,0.1); z-index: 1000000; transform: translateY(100%); transition: transform 0.3s cubic-bezier(0.2, 0.0, 0, 1.0); max-height: 85vh; display: flex; flex-direction: column; color: var(--md-sys-color-on-surface); font-family: system-ui, -apple-system, sans-serif; } .mas-bottom-sheet.show { transform: translateY(0); } .mas-drag-handle { width: 32px; height: 4px; background-color: var(--md-sys-color-outline); opacity: 0.4; border-radius: 2px; margin: 22px auto 0; flex-shrink: 0; } .mas-sheet-content { padding: 24px; overflow-y: auto; -webkit-overflow-scrolling: touch; } .mas-sheet-header { font-size: 22px; font-weight: 400; margin-bottom: 24px; color: var(--md-sys-color-on-surface); } /* 表单样式 */ .mas-input-group { margin-bottom: 20px; } .mas-label { display: block; font-size: 12px; color: var(--md-sys-color-on-surface); margin-bottom: 8px; font-weight: 500; } .mas-input, .mas-textarea { width: 100%; padding: 12px 16px; border: 1px solid var(--md-sys-color-outline); border-radius: 4px; background: transparent; font-size: 16px; color: var(--md-sys-color-on-surface); box-sizing: border-box; transition: border-color 0.2s; } .mas-input:focus, .mas-textarea:focus { outline: none; border-color: var(--md-sys-color-primary); border-width: 2px; padding: 11px 15px; /* 补偿边框宽度 */ } .mas-textarea { min-height: 100px; resize: vertical; font-family: inherit; } /* 按钮样式 */ .mas-btn { background-color: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); border: none; border-radius: 20px; padding: 10px 24px; font-size: 14px; font-weight: 500; cursor: pointer; width: 100%; height: 40px; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; } .mas-btn:active { opacity: 0.9; } .mas-btn-text { background: transparent; color: var(--md-sys-color-primary); margin-top: 8px; } /* 结果内容样式 */ .mas-result-content { font-size: 16px; line-height: 1.6; } .mas-result-content h1, .mas-result-content h2, .mas-result-content h3 { margin-top: 1em; margin-bottom: 0.5em; color: var(--md-sys-color-on-surface); } .mas-result-content p { margin-bottom: 1em; } .mas-result-content ul { padding-left: 20px; margin-bottom: 1em; } .mas-result-content li { margin-bottom: 0.5em; } .mas-result-content strong { font-weight: 700; } .mas-result-content blockquote { border-left: 4px solid var(--md-sys-color-primary-container); margin: 1em 0; padding-left: 16px; color: #555; } /* 遮罩层 */ .mas-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.32); z-index: 999999; display: none; opacity: 0; transition: opacity 0.3s; } .mas-overlay.show { display: block; opacity: 1; } .mas-actions { display: flex; gap: 12px; margin-top: 24px; } `; document.head.appendChild(style); // 创建 UI 元素 const fabContainer = document.createElement('div'); fabContainer.className = 'mas-fab-container'; // 总结按钮 FAB const summaryFab = document.createElement('button'); summaryFab.className = 'mas-fab'; summaryFab.innerHTML = ` `; // 设置按钮 FAB (小一点) const settingsFab = document.createElement('button'); settingsFab.className = 'mas-fab'; settingsFab.style.width = '40px'; settingsFab.style.height = '40px'; settingsFab.style.borderRadius = '12px'; settingsFab.style.alignSelf = 'flex-end'; settingsFab.innerHTML = ` `; fabContainer.appendChild(settingsFab); fabContainer.appendChild(summaryFab); document.body.appendChild(fabContainer); // 遮罩层 const overlay = document.createElement('div'); overlay.className = 'mas-overlay'; document.body.appendChild(overlay); // 设置面板 Bottom Sheet const settingsSheet = document.createElement('div'); settingsSheet.className = 'mas-bottom-sheet'; settingsSheet.innerHTML = `
设置
`; document.body.appendChild(settingsSheet); // 结果面板 Bottom Sheet const resultSheet = document.createElement('div'); resultSheet.className = 'mas-bottom-sheet'; resultSheet.innerHTML = `
AI 总结
`; document.body.appendChild(resultSheet); // 交互逻辑 function showOverlay() { overlay.classList.add('show'); document.body.style.overflow = 'hidden'; // 防止背景滚动 } function hideOverlay() { overlay.classList.remove('show'); document.body.style.overflow = ''; } function closeAllSheets() { settingsSheet.classList.remove('show'); resultSheet.classList.remove('show'); hideOverlay(); } // 点击遮罩关闭 overlay.addEventListener('click', closeAllSheets); // 设置按钮点击 settingsFab.addEventListener('click', () => { settingsSheet.classList.add('show'); showOverlay(); }); // 保存配置 document.getElementById('masSaveConfig').addEventListener('click', () => { config.apiUrl = document.getElementById('masApiUrl').value; config.apiKey = document.getElementById('masApiKey').value; config.prompt = document.getElementById('masPrompt').value; config.model = document.getElementById('masModel').value; GM.setValue('apiUrl', config.apiUrl); GM.setValue('apiKey', config.apiKey); GM.setValue('prompt', config.prompt); GM.setValue('model', config.model); closeAllSheets(); // 可以添加一个简单的 Toast 提示 alert('配置已保存'); }); // 总结按钮点击 summaryFab.addEventListener('click', async () => { if (!config.apiKey) { alert('请先配置 API Key'); settingsSheet.classList.add('show'); showOverlay(); return; } const resultContent = document.getElementById('masResultContent'); resultContent.innerHTML = '

正在分析页面内容...

'; resultSheet.classList.add('show'); showOverlay(); const pageContent = document.body.innerText.substring(0, 5000); // 增加一点长度限制 try { const response = await fetch(config.apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.apiKey}` }, body: JSON.stringify({ model: config.model, messages: [ { role: 'system', content: config.prompt }, { role: 'user', content: pageContent } ] }) }); const data = await response.json(); if (data.choices && data.choices[0]) { const rawContent = data.choices[0].message.content; // 等待 marked 加载 if (!markedLoaded) { await new Promise(resolve => setTimeout(resolve, 500)); } try { resultContent.innerHTML = marked.parse(rawContent); } catch (e) { resultContent.innerText = rawContent; } // 绑定复制按钮 const copyBtn = document.getElementById('masCopyBtn'); copyBtn.onclick = () => { navigator.clipboard.writeText(rawContent).then(() => { copyBtn.innerText = '已复制'; setTimeout(() => copyBtn.innerText = '复制', 2000); }); }; } else { throw new Error('API 返回数据异常'); } } catch (error) { resultContent.innerHTML = `

出错啦: ${error.message}

`; } }); // 关闭结果面板 document.getElementById('masCloseResultBtn').addEventListener('click', closeAllSheets); // 简单的拖拽支持 (FAB) let isDragging = false; let startY = 0; let startBottom = 100; fabContainer.addEventListener('touchstart', (e) => { if (e.target.closest('.mas-fab')) { isDragging = true; startY = e.touches[0].clientY; const style = window.getComputedStyle(fabContainer); startBottom = parseInt(style.bottom); } }); document.addEventListener('touchmove', (e) => { if (!isDragging) return; e.preventDefault(); // 防止滚动 const deltaY = startY - e.touches[0].clientY; let newBottom = startBottom + deltaY; newBottom = Math.max(20, Math.min(newBottom, window.innerHeight - 100)); fabContainer.style.bottom = `${newBottom}px`; }, { passive: false }); document.addEventListener('touchend', () => { isDragging = false; }); })();