AI网页内容智能总结助手

网页内容智能总结,支持自定义API和提示词

// ==UserScript==
// @name         AI Page Summarizer Pro
// @name:zh-CN   AI网页内容智能总结助手
// @namespace    http://tampermonkey.net/
// @version      1.0.0.0
// @description  网页内容智能总结,支持自定义API和提示词
// @description:zh-CN  网页内容智能总结,支持自定义API和提示词
// @author       Your Name
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM.xmlHttpRequest
// @grant        GM.setValue
// @grant        GM.getValue
// @grant        GM.registerMenuCommand
// @grant        GM.addStyle
// @grant        window.fetch
// @grant        window.localStorage
// @connect      api.openai.com
// @connect      *
// @require      https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js
// @run-at       document-start
// @noframes
// @license      MIT
// @compatible   chrome
// @compatible   firefox
// @compatible   edge
// @compatible   opera
// @compatible   safari
// @compatible   android
// ==/UserScript==

(function() {
    'use strict';

    // 添加全局错误处理
    window.addEventListener('error', function(event) {
        console.error('脚本错误:', event.error);
        if (event.error && event.error.stack) {
            console.error('错误堆栈:', event.error.stack);
        }
    });

    window.addEventListener('unhandledrejection', function(event) {
        console.error('未处理的Promise错误:', event.reason);
    });

    // 兼容性检查
    const browserSupport = {
        hasGM: typeof GM !== 'undefined',
        hasGMFunctions: typeof GM_getValue !== 'undefined',
        hasLocalStorage: (function() {
            try {
                localStorage.setItem('test', 'test');
                localStorage.removeItem('test');
                return true;
            } catch (e) {
                return false;
            }
        })(),
        hasBackdropFilter: (function() {
            const el = document.createElement('div');
            return typeof el.style.backdropFilter !== 'undefined' || 
                   typeof el.style.webkitBackdropFilter !== 'undefined';
        })(),
        isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
        isSafari: /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
    };

    // 兼容性处理层
    const scriptHandler = {
        // 存储值
        setValue: async function(key, value) {
            try {
                if (browserSupport.hasGMFunctions) {
                    GM_setValue(key, value);
                    return true;
                } else if (browserSupport.hasGM && GM.setValue) {
                    await GM.setValue(key, value);
                    return true;
                } else if (browserSupport.hasLocalStorage) {
                    localStorage.setItem('ws_' + key, JSON.stringify(value));
                    return true;
                }
                return false;
            } catch (error) {
                console.error('存储值失败:', error);
                return false;
            }
        },
        
        // 获取值
        getValue: async function(key, defaultValue) {
            try {
                if (browserSupport.hasGMFunctions) {
                    return GM_getValue(key, defaultValue);
                } else if (browserSupport.hasGM && GM.getValue) {
                    return await GM.getValue(key, defaultValue);
                } else if (browserSupport.hasLocalStorage) {
                    const value = localStorage.getItem('ws_' + key);
                    return value ? JSON.parse(value) : defaultValue;
                }
                return defaultValue;
            } catch (error) {
                console.error('获取值失败:', error);
                return defaultValue;
            }
        },
        
        // HTTP请求
        xmlHttpRequest: function(details) {
            return new Promise((resolve, reject) => {
                const handleResponse = (response) => {
                    resolve(response);
                };

                const handleError = (error) => {
                    reject(new Error('请求错误: ' + error.message));
                };

                if (browserSupport.hasGMFunctions && typeof GM_xmlhttpRequest !== 'undefined') {
                    GM_xmlhttpRequest({
                        ...details,
                        onload: handleResponse,
                        onerror: handleError,
                        ontimeout: details.ontimeout
                    });
                } else if (browserSupport.hasGM && typeof GM !== 'undefined' && GM.xmlHttpRequest) {
                    GM.xmlHttpRequest({
                        ...details,
                        onload: handleResponse,
                        onerror: handleError,
                        ontimeout: details.ontimeout
                    });
                } else {
                    fetch(details.url, {
                        method: details.method,
                        headers: details.headers,
                        body: details.data,
                        mode: 'cors',
                        credentials: 'omit'
                    })
                    .then(async response => {
                        const text = await response.text();
                        handleResponse({
                            status: response.status,
                            responseText: text,
                            responseHeaders: [...response.headers].join('\n')
                        });
                    })
                    .catch(handleError);
                }
            }).then(response => {
                if (details.onload) {
                    details.onload(response);
                }
                return response;
            }).catch(error => {
                if (details.onerror) {
                    details.onerror(error);
                }
                throw error;
            });
        },
        
        // 注册(不可用)菜单命令
        registerMenuCommand: function(name, fn) {
            try {
                if (browserSupport.hasGMFunctions) {
                    GM_registerMenuCommand(name, fn);
                    return true;
                } else if (browserSupport.hasGM && GM.registerMenuCommand) {
                    GM.registerMenuCommand(name, fn);
                    return true;
                }
                return false;
            } catch (error) {
                console.log('注册(不可用)菜单命令失败:', error);
                return false;
            }
        },
        
        // 添加样式
        addStyle: function(css) {
            try {
                if (browserSupport.hasGMFunctions) {
                    GM_addStyle(css);
                    return true;
                } else if (browserSupport.hasGM && GM.addStyle) {
                    GM.addStyle(css);
                    return true;
                } else {
                    const style = document.createElement('style');
                    style.textContent = css;
                    document.head.appendChild(style);
                    return true;
                }
            } catch (error) {
                console.error('添加样式失败:', error);
                return false;
            }
        }
    };

    // 配置项
    let config = {
        apiUrl: 'https://api.openai.com/v1/chat/completions',
        apiKey: '',
        model: 'gpt-3.5-turbo',
        theme: 'light',
        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 ## for main sections
   - Use bullet points (•) for key points and details
   - Use bold for important terms
   - Use blockquotes for notable quotes

2. Content Structure:
   ## 核心观点
   • Key points here...

   ## 关键信息
   • Important details here...

   ## 市场情绪
   • Market sentiment here...

   ## 专家观点
   • Expert opinions here...

   ## 总结
   • Final summary here...

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
   - Make sure bullet points (•) are in the same line with text
   - Use ## for main section headers

Remember: Focus on delivering the information directly without any meta-analysis or explanation of your process.`,
        iconPosition: { y: 20 },
        shortcut: 'option+a',
        summaryWindowPositioned: false  // 添加窗口位置状态跟踪
    };

    // 初始化配置
    async function initConfig() {
        config.apiUrl = await scriptHandler.getValue('apiUrl', config.apiUrl);
        config.apiKey = await scriptHandler.getValue('apiKey', config.apiKey);
        config.model = await scriptHandler.getValue('model', config.model);
        config.prompt = await scriptHandler.getValue('prompt', config.prompt);
        config.iconPosition = await scriptHandler.getValue('iconPosition', config.iconPosition || { y: 20 });
        config.shortcut = await scriptHandler.getValue('shortcut', config.shortcut);
        config.theme = await scriptHandler.getValue('theme', config.theme);
        config.summaryWindowPositioned = await scriptHandler.getValue('summaryWindowPositioned', false);
        
        console.log('加载的图标位置配置:', config.iconPosition);
    }

    // DOM 元素引用
    const elements = {
        icon: null,
        container: null,
        settings: null,
        backdrop: null
    };

    // 全局变量用于判断是否已经监听了键盘事件
    let keyboardListenerActive = false;

    // 拖拽功能
    function makeDraggable(element) {
        let isDragging = false;
        let startX, startY, startLeft, startTop;
        
        // 添加位置信息以便拖动
        if (element.style.position !== 'fixed') {
            element.style.position = 'fixed';
        }
        
        // 确保元素有初始位置
        if (!element.style.bottom || !element.style.right) {
            element.style.bottom = '20px';
            element.style.right = '20px';
        }
        
        // 鼠标/触摸开始事件
        function handleStart(e) {
            isDragging = true;
            
            // 计算起始位置
            const rect = element.getBoundingClientRect();
            
            // 将位置从right/bottom转换为left/top以便计算
            const computedStyle = window.getComputedStyle(element);
            const right = parseInt(computedStyle.right || '0');
            const bottom = parseInt(computedStyle.bottom || '0');
            
            startLeft = window.innerWidth - right - rect.width;
            startTop = window.innerHeight - bottom - rect.height;
            
            // 记录鼠标/触摸起始位置
            if (e.type === 'touchstart') {
                startX = e.touches[0].clientX;
                startY = e.touches[0].clientY;
            } else {
                startX = e.clientX;
                startY = e.clientY;
                e.preventDefault(); // 防止选中文本
            }
            
            // 添加移动和结束事件监听
            if (e.type === 'touchstart') {
                document.addEventListener('touchmove', handleMove, { passive: false });
                document.addEventListener('touchend', handleEnd);
            } else {
                document.addEventListener('mousemove', handleMove);
                document.addEventListener('mouseup', handleEnd);
            }
            
            // 设置拖动中的样式
            element.style.transition = 'none';
            element.style.opacity = '1';
        }
        
        // 鼠标/触摸移动事件
        function handleMove(e) {
            if (!isDragging) return;
            
            let moveX, moveY;
            if (e.type === 'touchmove') {
                moveX = e.touches[0].clientX - startX;
                moveY = e.touches[0].clientY - startY;
                e.preventDefault(); // 防止页面滚动
            } else {
                moveX = e.clientX - startX;
                moveY = e.clientY - startY;
            }
            
            // 计算新位置
            let newLeft = startLeft + moveX;
            let newTop = startTop + moveY;
            
            // 边界检查
            newLeft = Math.max(0, Math.min(window.innerWidth - element.offsetWidth, newLeft));
            newTop = Math.max(0, Math.min(window.innerHeight - element.offsetHeight, newTop));
            
            // 更新位置 (使用left/top)
            element.style.left = `${newLeft}px`;
            element.style.top = `${newTop}px`;
            
            // 清除right/bottom,避免冲突
            element.style.right = '';
            element.style.bottom = '';
        }
        
        // 鼠标/触摸结束事件
        function handleEnd() {
            if (!isDragging) return;
            isDragging = false;
            
            // 移除事件监听
            document.removeEventListener('mousemove', handleMove);
            document.removeEventListener('mouseup', handleEnd);
            document.removeEventListener('touchmove', handleMove);
            document.removeEventListener('touchend', handleEnd);
            
            // 保存新位置
            const rect = element.getBoundingClientRect();
            const newX = rect.left;
            const newY = rect.top;
            
            // 转换回right/bottom定位
            const right = window.innerWidth - newX - rect.width;
            const bottom = window.innerHeight - newY - rect.height;
            
            // 设置新位置,恢复transition
            element.style.left = '';
            element.style.top = '';
            element.style.right = `${right}px`;
            element.style.bottom = `${bottom}px`;
            element.style.transition = 'opacity 0.3s ease';
            
            // 如果不是在悬停状态,恢复默认透明度
            if (!element.matches(':hover')) {
                element.style.opacity = '0.8';
            }
            
            // 保存位置到配置
            config.iconPosition = { x: newX, y: newY };
            saveConfig();
        }
        
        // 添加事件监听
        element.addEventListener('mousedown', handleStart);
        element.addEventListener('touchstart', handleStart, { passive: false });
    }

    // 显示提示消息
    function showToast(message) {
        const toast = document.createElement('div');
        toast.textContent = message;
        
        const baseStyle = `
            position: fixed;
            left: 50%;
            transform: translateX(-50%);
            background: #4CAF50;
            color: white;
            padding: ${browserSupport.isMobile ? '12px 24px' : '10px 20px'};
            border-radius: 4px;
            z-index: 1000001;
            font-size: ${browserSupport.isMobile ? '16px' : '14px'};
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            text-align: center;
            max-width: ${browserSupport.isMobile ? '90%' : '300px'};
            word-break: break-word;
        `;
        
        // 在移动设备上显示在底部,否则显示在顶部
        const position = browserSupport.isMobile ? 
            'bottom: 80px;' : 
            'top: 20px;';
        
        toast.style.cssText = baseStyle + position;
        
        document.body.appendChild(toast);
        setTimeout(() => {
            toast.style.opacity = '0';
            toast.style.transition = 'opacity 0.3s ease';
            setTimeout(() => toast.remove(), 300);
        }, 2000);
    }

    // 快捷键处理
    const keyManager = {
        setup() {
            try {
                // 移除旧的监听器
                if (keyboardListenerActive) {
                    document.removeEventListener('keydown', this._handleKeyDown);
                }

                // 添加新的监听器
                this._handleKeyDown = (e) => {
                    // 忽略输入框中的按键
                    if (e.target.tagName === 'INPUT' || 
                        e.target.tagName === 'TEXTAREA' || 
                        e.target.isContentEditable ||
                        e.target.getAttribute('role') === 'textbox') {
                        return;
                    }

                    // 解析配置的快捷键
                    const shortcutParts = config.shortcut.toLowerCase().split('+');
                    
                    // 获取主键(非修饰键)
                    const mainKey = shortcutParts.filter(part => 
                        !['alt', 'option', 'ctrl', 'control', 'shift', 'cmd', 'command', 'meta']
                        .includes(part)
                    )[0] || 'a';
                    
                    // 检查所需的修饰键
                    const needAlt = shortcutParts.some(p => p === 'alt' || p === 'option');
                    const needCtrl = shortcutParts.some(p => p === 'ctrl' || p === 'control');
                    const needShift = shortcutParts.some(p => p === 'shift');
                    const needMeta = shortcutParts.some(p => p === 'cmd' || p === 'command' || p === 'meta');
                    
                    // 检查按键是否匹配
                    const isMainKeyMatched = 
                        e.key.toLowerCase() === mainKey || 
                        e.code.toLowerCase() === 'key' + mainKey ||
                        e.keyCode === mainKey.toUpperCase().charCodeAt(0);
                        
                    // 检查修饰键是否匹配
                    const modifiersMatch = 
                        e.altKey === needAlt && 
                        e.ctrlKey === needCtrl && 
                        e.shiftKey === needShift && 
                        e.metaKey === needMeta;
                    
                    if (isMainKeyMatched && modifiersMatch) {
                        console.log('快捷键触发成功:', config.shortcut);
                        e.preventDefault();
                        e.stopPropagation();
                        showSummary();
                        return false;
                    }
                };
                
                // 使用捕获阶段来确保我们能先捕获到事件
                document.addEventListener('keydown', this._handleKeyDown, true);
                keyboardListenerActive = true;
                
                // 设置全局访问方法
                window.activateSummary = showSummary;
                
                console.log('快捷键已设置:', config.shortcut);
                return true;
            } catch (error) {
                console.error('设置快捷键失败:', error);
                return false;
            }
        }
    };

    // 等待页面加载完成
    function waitForPageLoad() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initializeScript);
        } else {
            initializeScript();
        }
    }

    // 保存配置数据
    async function saveConfig() {
        try {
            await scriptHandler.setValue('apiUrl', config.apiUrl);
            await scriptHandler.setValue('apiKey', config.apiKey);
            await scriptHandler.setValue('model', config.model);
            await scriptHandler.setValue('prompt', config.prompt);
            await scriptHandler.setValue('iconPosition', config.iconPosition);
            await scriptHandler.setValue('shortcut', config.shortcut);
            await scriptHandler.setValue('theme', config.theme);
            console.log('配置已保存');
            return true;
        } catch (error) {
            console.error('保存配置失败:', error);
            return false;
        }
    }

    // 为Safari创建专用存储对象
    function createSafariStorage() {
        // 内存缓存
        const memoryCache = {};
        
        return {
            getValue: async function(key, defaultValue) {
                try {
                    // 优先从localStorage获取
                    if (browserSupport.hasLocalStorage) {
                        const storedValue = localStorage.getItem('ws_' + key);
                        if (storedValue !== null) {
                            return JSON.parse(storedValue);
                        }
                    }
                    
                    // 返回内存缓存或默认值
                    return key in memoryCache ? memoryCache[key] : defaultValue;
                } catch (error) {
                    console.error(`Safari存储读取失败 [${key}]:`, error);
                    return defaultValue;
                }
            },
            
            setValue: async function(key, value) {
                try {
                    // 尝试写入localStorage
                    if (browserSupport.hasLocalStorage) {
                        localStorage.setItem('ws_' + key, JSON.stringify(value));
                    }
                    
                    // 同时写入内存缓存
                    memoryCache[key] = value;
                    return true;
                } catch (error) {
                    console.error(`Safari存储写入失败 [${key}]:`, error);
                    // 仅写入内存缓存
                    memoryCache[key] = value;
                    return false;
                }
            }
        };
    }

    // 修复Safari的拖拽和显示问题
    function fixSafariIssues() {
        if (!browserSupport.isSafari) return;
        
        console.log('应用Safari兼容性修复');
        
        // 为Safari添加特定CSS
        const safariCSS = `
            #website-summary-icon, 
            #website-summary-container,
            #website-summary-settings {
                -webkit-user-select: none !important;
                user-select: none !important;
                -webkit-touch-callout: none !important;
                touch-action: none !important;
            }
            
            #website-summary-content {
                -webkit-user-select: text !important;
                user-select: text !important;
                touch-action: auto !important;
            }
        `;
        
        scriptHandler.addStyle(safariCSS);
    }

    // 初始化脚本处理程序
    function initScriptHandler() {
        // 检测Safari浏览器
        if (browserSupport.isSafari) {
            console.log('检测到Safari浏览器,应用特殊兼容');
            
            // 创建Safari特定存储
            const safariStorage = createSafariStorage();
            
            // 修改scriptHandler中的存储方法
            const originalGetValue = scriptHandler.getValue;
            const originalSetValue = scriptHandler.setValue;
            
            // 覆盖getValue方法
            scriptHandler.getValue = async function(key, defaultValue) {
                try {
                    // 先尝试原有方法
                    const result = await originalGetValue.call(scriptHandler, key, defaultValue);
                    
                    // 如果获取失败或返回undefined,使用Safari存储
                    if (result === undefined || result === null) {
                        console.log(`标准存储获取失败,使用Safari存储 [${key}]`);
                        return await safariStorage.getValue(key, defaultValue);
                    }
                    
                    return result;
                } catch (error) {
                    console.error(`getValue失败 [${key}]:`, error);
                    return await safariStorage.getValue(key, defaultValue);
                }
            };
            
            // 覆盖setValue方法
            scriptHandler.setValue = async function(key, value) {
                try {
                    // 同时尝试原有方法和Safari存储
                    const originalResult = await originalSetValue.call(scriptHandler, key, value);
                    const safariResult = await safariStorage.setValue(key, value);
                    
                    // 只要有一个成功就返回成功
                    return originalResult || safariResult;
                } catch (error) {
                    console.error(`setValue失败 [${key}]:`, error);
                    // 尝试使用Safari存储作为后备
                    return await safariStorage.setValue(key, value);
                }
            };
            
            // 应用Safari特定修复
            fixSafariIssues();
        }
    }

    // 初始化脚本
    async function initializeScript() {
        try {
            // 初始化ScriptHandler
            initScriptHandler();
            // 等待marked库加载
            await waitForMarked();
            // 初始化配置
            await initConfig();
            // 添加全局样式
            addGlobalStyles();
            // 创建图标
            createIcon();
            // 设置快捷键
            keyManager.setup();
            // 注册(不可用)菜单命令
            registerMenuCommands();
            
            console.log('AI Page Summarizer Pro 初始化完成');
        } catch (error) {
            console.error('初始化失败:', error);
        }
    }

    // 等待marked库加载
    function waitForMarked() {
        return new Promise((resolve) => {
            if (window.marked) {
                window.marked.setOptions({ breaks: true, gfm: true });
                resolve();
            } else {
                const checkMarked = setInterval(() => {
                    if (window.marked) {
                        clearInterval(checkMarked);
                        window.marked.setOptions({ breaks: true, gfm: true });
                        resolve();
                    }
                }, 100);
                // 10秒后超时
                setTimeout(() => {
                    clearInterval(checkMarked);
                    console.warn('marked库加载超时,继续初始化');
                    resolve();
                }, 10000);
            }
        });
    }

    // 添加全局样式
    function addGlobalStyles() {
        const css = `
            #website-summary-icon * {
                box-sizing: border-box !important;
                margin: 0 !important;
                padding: 0 !important;
            }
            #website-summary-icon span {
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
                line-height: 1 !important;
            }
        `;
        scriptHandler.addStyle(css);
    }

    // 创建图标
    function createIcon() {
        // 检查是否已存在图标
        const existingIcon = document.getElementById('website-summary-icon');
        if (existingIcon) {
            existingIcon.remove();
        }

        // 创建图标元素
        const icon = document.createElement('div');
        icon.id = 'website-summary-icon';
        icon.innerHTML = '💡';
        
        // 从配置中获取保存的位置
        const savedPosition = config.iconPosition || {};
        const hasValidPosition = typeof savedPosition.x === 'number' && typeof savedPosition.y === 'number';
        
        // 计算位置样式
        let positionStyle = '';
        if (hasValidPosition) {
            // 使用保存的精确位置
            positionStyle = `
                top: ${savedPosition.y}px !important;
                left: ${savedPosition.x}px !important;
                right: auto !important;
                bottom: auto !important;
            `;
        } else {
            // 使用默认位置
            positionStyle = `
                bottom: 20px !important;
                right: 20px !important;
            `;
        }
        
        // 设置图标样式
        icon.style.cssText = `
            position: fixed;
            z-index: 2147483647 !important;
            ${positionStyle}
            width: auto !important;
            height: auto !important;
            padding: 8px !important;
            font-size: ${browserSupport.isMobile ? '20px' : '24px'} !important;
            line-height: 1 !important;
            cursor: pointer !important;
            user-select: none !important;
            -webkit-user-select: none !important;
            visibility: visible !important;
            opacity: 0.8;
            transition: opacity 0.3s ease !important;
            border-radius: 8px !important;
            box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1) !important;
        `;

        // 添加鼠标悬停效果
        icon.addEventListener('mouseover', () => {
            icon.style.opacity = '1';
        });

        icon.addEventListener('mouseout', () => {
            icon.style.opacity = '0.8';
        });

        // 添加点击事件
        icon.addEventListener('click', async (e) => {
            e.preventDefault();
            e.stopPropagation();
            await showSummary();
        });

        // 修改右键菜单处理方式
        icon.addEventListener('contextmenu', (e) => {
            e.preventDefault();
            e.stopPropagation();
            showSettings();
        });

        // 支持双击打开设置(为Safari增加额外的交互方式)
        let lastClickTime = 0;
        icon.addEventListener('click', (e) => {
            const currentTime = new Date().getTime();
            if (currentTime - lastClickTime < 300) { // 双击间隔300ms
                e.preventDefault();
                e.stopPropagation();
                showSettings();
            }
            lastClickTime = currentTime;
        });

        // 添加优化的拖动功能
        makeIconDraggable(icon);

        // 确保 body 存在后再添加图标
        if (document.body) {
            document.body.appendChild(icon);
        } else {
            document.addEventListener('DOMContentLoaded', () => {
                document.body.appendChild(icon);
            });
        }

        // 将图标引用存储到elements对象中
        elements.icon = icon;
    }
    
    // 专门为图标设计的拖动函数
    function makeIconDraggable(icon) {
        let isDragging = false;
        let startX, startY, startLeft, startTop;
        
        // 鼠标/触摸开始事件
        function handleStart(e) {
            isDragging = true;
            
            // 记录初始位置
            const rect = icon.getBoundingClientRect();
            startLeft = rect.left;
            startTop = rect.top;
            
            // 记录鼠标/触摸起始位置
            if (e.type === 'touchstart') {
                startX = e.touches[0].clientX;
                startY = e.touches[0].clientY;
            } else {
                startX = e.clientX;
                startY = e.clientY;
                e.preventDefault(); // 防止选中文本
            }
            
            // 设置拖动时的样式
            icon.style.transition = 'none';
            icon.style.opacity = '1';
            
            // 添加移动和结束事件监听
            if (e.type === 'touchstart') {
                document.addEventListener('touchmove', handleMove, { passive: false });
                document.addEventListener('touchend', handleEnd);
            } else {
                document.addEventListener('mousemove', handleMove);
                document.addEventListener('mouseup', handleEnd);
            }
        }
        
        // 鼠标/触摸移动事件
        function handleMove(e) {
            if (!isDragging) return;
            
            let moveX, moveY;
            if (e.type === 'touchmove') {
                moveX = e.touches[0].clientX - startX;
                moveY = e.touches[0].clientY - startY;
                e.preventDefault(); // 防止页面滚动
            } else {
                moveX = e.clientX - startX;
                moveY = e.clientY - startY;
            }
            
            // 计算新位置
            let newLeft = startLeft + moveX;
            let newTop = startTop + moveY;
            
            // 边界检查
            newLeft = Math.max(0, Math.min(window.innerWidth - icon.offsetWidth, newLeft));
            newTop = Math.max(0, Math.min(window.innerHeight - icon.offsetHeight, newTop));
            
            // 更新位置
            icon.style.left = `${newLeft}px`;
            icon.style.top = `${newTop}px`;
            icon.style.right = 'auto';
            icon.style.bottom = 'auto';
        }
        
        // 鼠标/触摸结束事件
        function handleEnd() {
            if (!isDragging) return;
            isDragging = false;
            
            // 移除事件监听
            document.removeEventListener('mousemove', handleMove);
            document.removeEventListener('mouseup', handleEnd);
            document.removeEventListener('touchmove', handleMove);
            document.removeEventListener('touchend', handleEnd);
            
            // 保存新位置
            const rect = icon.getBoundingClientRect();
            config.iconPosition = {
                x: rect.left,
                y: rect.top
            };
            
            // 持久化保存位置
            saveIconPosition();
            
            // 恢复透明度过渡效果
            icon.style.transition = 'opacity 0.3s ease';
            if (!icon.matches(':hover')) {
                icon.style.opacity = '0.8';
            }
        }
        
        // 添加事件监听
        icon.addEventListener('mousedown', handleStart);
        icon.addEventListener('touchstart', handleStart, { passive: false });
        
        // 处理窗口大小变化
        window.addEventListener('resize', () => {
            const rect = icon.getBoundingClientRect();
            
            // 如果图标超出视口范围,调整位置
            if (rect.right > window.innerWidth) {
                icon.style.left = `${window.innerWidth - icon.offsetWidth}px`;
            }
            
            if (rect.bottom > window.innerHeight) {
                icon.style.top = `${window.innerHeight - icon.offsetHeight}px`;
            }
            
            // 更新保存的位置
            config.iconPosition = {
                x: parseInt(icon.style.left),
                y: parseInt(icon.style.top)
            };
            
            // 持久化保存位置
            saveIconPosition();
        });
    }

    // 保存图标位置
    function saveIconPosition() {
        scriptHandler.setValue('iconPosition', config.iconPosition);
        console.log('图标位置已保存:', config.iconPosition);
    }

    // 显示设置界面
    function showSettings() {
        try {
            const settings = elements.settings || createSettingsUI();
            settings.style.display = 'block';
            showBackdrop();
            setTimeout(() => settings.style.opacity = '1', 10);
        } catch (error) {
            console.error('显示设置界面失败:', error);
            alert('无法显示设置界面,请检查控制台以获取详细信息');
        }
    }

    // 显示摘要
    async function showSummary() {
        const container = elements.container || createSummaryUI();
        const content = container.querySelector('#website-summary-content');
        
        // 如果容器有自定义位置,保持原位置;否则重置到屏幕中心
        const hasCustomPosition = container.hasAttribute('data-positioned');
        if (!hasCustomPosition) {
            container.style.left = '50%';
            container.style.top = '50%';
            container.style.transform = 'translate(-50%, -50%)';
        }
        
        // 显示容器和背景
        showBackdrop();
        container.style.display = 'block';
        setTimeout(() => container.style.opacity = '1', 10);
        
        // 显示加载中
        content.innerHTML = `<p style="text-align: center; color: ${config.theme === 'dark' ? '#bdc1c6' : '#666'};">正在获取总结...</p>`;
        
        try {
            // 获取页面内容
            const pageContent = getPageContent();
            if (!pageContent || pageContent.trim().length === 0) {
                throw new Error('无法获取页面内容');
            }
            
            console.log('页面内容长度:', pageContent.length);
            console.log('API配置:', {
                url: config.apiUrl,
                model: config.model,
                contentLength: pageContent.length
            });
            
            // 获取总结
            const summary = await getSummary(pageContent);
            if (!summary || summary.trim().length === 0) {
                throw new Error('API返回内容为空');
            }
            
            // 添加样式并渲染内容
            addMarkdownStyles();
            await renderContent(summary);
        } catch (error) {
            console.error('总结失败:', error);
            content.innerHTML = `
                <p style="text-align: center; color: #ff4444;">
                    获取总结失败:${error.message}<br>
                    <small style="color: ${config.theme === 'dark' ? '#bdc1c6' : '#666'};">
                        请检查控制台以获取详细错误信息
                    </small>
                </p>`;
        }
    }

    // 创建/显示背景
    function showBackdrop() {
        if (!elements.backdrop) {
            const backdrop = document.createElement('div');
            backdrop.id = 'website-summary-backdrop';
            const isDark = config.theme === 'dark';
            backdrop.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background-color: ${isDark ? 'rgba(32, 33, 36, 0.75)' : 'rgba(250, 250, 252, 0.75)'};
                backdrop-filter: blur(5px);
                z-index: 999997;
                display: none;
                opacity: 0;
                transition: opacity 0.3s ease;
            `;
            
            backdrop.addEventListener('click', (e) => {
                if (e.target === backdrop) {
                    hideUI();
                }
            });
            
            document.body.appendChild(backdrop);
            elements.backdrop = backdrop;
        } else {
            // 更新背景颜色以匹配当前主题
            const isDark = config.theme === 'dark';
            elements.backdrop.style.backgroundColor = isDark ? 'rgba(32, 33, 36, 0.75)' : 'rgba(250, 250, 252, 0.75)';
        }
        
        elements.backdrop.style.display = 'block';
        setTimeout(() => elements.backdrop.style.opacity = '1', 10);
    }

    // 隐藏UI
    function hideUI() {
        // 隐藏背景
        if (elements.backdrop) {
            elements.backdrop.style.opacity = '0';
            setTimeout(() => elements.backdrop.style.display = 'none', 300);
        }
        
        // 隐藏摘要容器
        if (elements.container) {
            elements.container.style.opacity = '0';
            setTimeout(() => elements.container.style.display = 'none', 300);
        }
    }

    // 创建摘要UI
    function createSummaryUI() {
        const container = document.createElement('div');
        container.id = 'website-summary-container';
        
        const isDark = config.theme === 'dark';
        let styles = `
            position: fixed;
            z-index: 999998;
            background: ${isDark ? darkColors.containerBg : 'rgba(255, 255, 255, 0.98)'};
            color: ${isDark ? darkColors.text : '#333'};
            border-radius: ${browserSupport.isMobile ? '8px' : '12px'};
            box-shadow: 0 8px 32px ${isDark ? 'rgba(0, 0, 0, 0.4)' : 'rgba(0, 0, 0, 0.08)'};
            padding: ${browserSupport.isMobile ? '12px' : '16px'};
            width: ${browserSupport.isMobile ? '92%' : '80%'};
            max-width: ${browserSupport.isMobile ? '100%' : '800px'};
            max-height: ${browserSupport.isMobile ? '85vh' : '80vh'};
            font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Roboto, sans-serif;
            display: none;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            overflow: hidden;
            opacity: 0;
            transition: opacity 0.3s ease;
            will-change: transform;
            -webkit-backface-visibility: hidden;
            backface-visibility: hidden;
        `;

        // 添加backdrop-filter(如果支持)
        if (browserSupport.hasBackdropFilter) {
            styles += 'backdrop-filter: blur(10px);';
            styles += '-webkit-backdrop-filter: blur(10px);';
        }

        container.style.cssText = styles;

        // 标题栏
        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 ${isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'};
            user-select: none;
            -webkit-user-select: none;
        `;

        // 标题
        const title = document.createElement('h3');
        // 获取当前页面标题并截断(如果过长)
        const pageTitle = document.title;
        const maxTitleLength = browserSupport.isMobile ? 30 : 50;
        title.textContent = pageTitle.length > maxTitleLength ? 
            pageTitle.substring(0, maxTitleLength) + '...' : 
            pageTitle;
        title.style.cssText = `
            margin: 0; 
            font-size: 16px; 
            color: ${isDark ? '#e8eaed' : '#333'};
            pointer-events: none;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            max-width: ${browserSupport.isMobile ? '160px' : '350px'};
            font-weight: 500;
        `;

        // 按钮容器
        const buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = 'display: flex; gap: 12px; align-items: center;';

        // 复制按钮 - Mac风格SVG图标
        const copyBtn = document.createElement('button');
        copyBtn.title = '复制内容';
        copyBtn.style.cssText = `
            background: transparent;
            border: none;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 5px;
            width: 28px;
            height: 28px;
            border-radius: 6px;
            transition: background-color 0.2s;
            color: ${isDark ? 'rgba(255, 255, 255, 0.8)' : 'rgba(0, 0, 0, 0.6)'};
        `;

        // Mac风格的复制图标SVG
        copyBtn.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
            </svg>
        `;

        copyBtn.addEventListener('mouseover', () => {
            copyBtn.style.backgroundColor = isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)';
        });
        
        copyBtn.addEventListener('mouseout', () => {
            copyBtn.style.backgroundColor = 'transparent';
        });

        copyBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            const content = document.getElementById('website-summary-content').innerText;
            navigator.clipboard.writeText(content).then(() => {
                // 显示复制成功状态
                copyBtn.innerHTML = `
                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M20 6L9 17l-5-5"></path>
                    </svg>
                `;
                copyBtn.style.color = isDark ? '#8ab4f8' : '#34c759';
                
                setTimeout(() => {
                    // 恢复原始图标
                    copyBtn.innerHTML = `
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                            <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                        </svg>
                    `;
                    copyBtn.style.color = isDark ? 'rgba(255, 255, 255, 0.8)' : 'rgba(0, 0, 0, 0.6)';
                }, 1500);
            });
        });

        // 关闭按钮 - Mac风格SVG图标
        const closeBtn = document.createElement('button');
        closeBtn.title = '关闭';
        closeBtn.style.cssText = `
            background: transparent;
            border: none;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 5px;
            width: 28px;
            height: 28px;
            border-radius: 6px;
            transition: background-color 0.2s, color 0.2s;
            color: ${isDark ? 'rgba(255, 255, 255, 0.8)' : 'rgba(0, 0, 0, 0.6)'};
        `;

        // Mac风格的关闭图标SVG
        closeBtn.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <line x1="18" y1="6" x2="6" y2="18"></line>
                <line x1="6" y1="6" x2="18" y2="18"></line>
            </svg>
        `;

        closeBtn.addEventListener('mouseover', () => {
            closeBtn.style.backgroundColor = isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)';
            closeBtn.style.color = isDark ? '#ff4444' : '#ff3b30';
        });
        
        closeBtn.addEventListener('mouseout', () => {
            closeBtn.style.backgroundColor = 'transparent';
            closeBtn.style.color = isDark ? 'rgba(255, 255, 255, 0.8)' : 'rgba(0, 0, 0, 0.6)';
        });

        closeBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            hideUI();
        });

        // 内容区域
        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;
            color: ${isDark ? '#e8eaed' : '#333'};
            -webkit-overflow-scrolling: touch;
            overscroll-behavior: contain;
        `;

        // 防止内容区域的滚动触发容器拖动
        content.addEventListener('mousedown', (e) => {
            e.stopPropagation();
        });
        content.addEventListener('touchstart', (e) => {
            e.stopPropagation();
        }, { passive: true });

        // 组装界面
        buttonContainer.appendChild(copyBtn);
        buttonContainer.appendChild(closeBtn);
        header.appendChild(title);
        header.appendChild(buttonContainer);
        container.appendChild(header);
        container.appendChild(content);
        document.body.appendChild(container);
        
        // 专门使用标题栏拖动
        makeDraggableByHeader(container, header);
        elements.container = container;
        return container;
    }

    // 专门用于通过标题栏拖动的函数
    function makeDraggableByHeader(element, handle) {
        let isDragging = false;
        let startX, startY, startLeft, startTop;
        
        // 鼠标/触摸开始事件
        function handleStart(e) {
            isDragging = true;
            
            // 记录初始位置
            const rect = element.getBoundingClientRect();
            
            // 如果使用了transform-translate,则切换到绝对定位
            if (element.style.transform && element.style.transform.includes('translate')) {
                element.style.transform = 'none';
                element.style.left = rect.left + 'px';
                element.style.top = rect.top + 'px';
                
                // 标记元素已被手动定位
                element.setAttribute('data-positioned', 'true');
            }
            
            startLeft = rect.left;
            startTop = rect.top;
            
            // 记录鼠标/触摸起始位置
            if (e.type === 'touchstart') {
                startX = e.touches[0].clientX;
                startY = e.touches[0].clientY;
                // 阻止默认行为只在触摸时需要
                e.preventDefault();
            } else {
                startX = e.clientX;
                startY = e.clientY;
                e.preventDefault();
            }
            
            // 移除过渡效果
            element.style.transition = 'none';
            
            // 添加移动和结束事件监听
            if (e.type === 'touchstart') {
                document.addEventListener('touchmove', handleMove, { passive: false });
                document.addEventListener('touchend', handleEnd);
                document.addEventListener('touchcancel', handleEnd);
            } else {
                document.addEventListener('mousemove', handleMove);
                document.addEventListener('mouseup', handleEnd);
            }
        }
        
        // 鼠标/触摸移动事件
        function handleMove(e) {
            if (!isDragging) return;
            
            let moveX, moveY;
            if (e.type === 'touchmove') {
                moveX = e.touches[0].clientX - startX;
                moveY = e.touches[0].clientY - startY;
                // 阻止默认滚动
                e.preventDefault();
            } else {
                moveX = e.clientX - startX;
                moveY = e.clientY - startY;
            }
            
            // 计算新位置
            const newLeft = startLeft + moveX;
            const newTop = startTop + moveY;
            
            // 边界检查
            const maxLeft = window.innerWidth - element.offsetWidth;
            const maxTop = window.innerHeight - element.offsetHeight;
            
            // 应用新位置
            element.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + 'px';
            element.style.top = Math.max(0, Math.min(newTop, maxTop)) + 'px';
            
            // 标记元素已被手动定位
            element.setAttribute('data-positioned', 'true');
        }
        
        // 鼠标/触摸结束事件
        function handleEnd() {
            if (!isDragging) return;
            isDragging = false;
            
            // 移除事件监听
            document.removeEventListener('mousemove', handleMove);
            document.removeEventListener('mouseup', handleEnd);
            document.removeEventListener('touchmove', handleMove);
            document.removeEventListener('touchend', handleEnd);
            document.removeEventListener('touchcancel', handleEnd);
            
            // 恢复过渡效果
            element.style.transition = 'opacity 0.3s ease';
            
            // 保存位置状态
            saveWindowPosition(element);
        }
        
        // 保存窗口位置
        function saveWindowPosition(element) {
            if (element.id === 'website-summary-container') {
                const rect = element.getBoundingClientRect();
                // 只保存位置标记状态,不保存具体位置
                config.summaryWindowPositioned = true;
                scriptHandler.setValue('summaryWindowPositioned', true);
            }
        }
        
        // 仅在指定的标题栏上添加事件监听
        handle.addEventListener('mousedown', handleStart);
        handle.addEventListener('touchstart', handleStart, { passive: false });
        
        // 处理窗口变化
        window.addEventListener('resize', () => {
            if (element.hasAttribute('data-positioned')) {
                const rect = element.getBoundingClientRect();
                
                // 如果窗口超出视口边界,调整位置
                if (rect.right > window.innerWidth) {
                    element.style.left = Math.max(0, window.innerWidth - element.offsetWidth) + 'px';
                }
                
                if (rect.bottom > window.innerHeight) {
                    element.style.top = Math.max(0, window.innerHeight - element.offsetHeight) + 'px';
                }
            }
        });
        
        // 如果用户离开窗口,确保释放拖动状态
        window.addEventListener('blur', () => {
            if (isDragging) {
                handleEnd();
            }
        });
        
        // 检查是否应该恢复自定义位置
        if (element.id === 'website-summary-container' && config.summaryWindowPositioned) {
            // 标记元素有自定义位置
            element.setAttribute('data-positioned', 'true');
        }
    }

    // 创建设置界面
    function createSettingsUI() {
        const settingsContainer = document.createElement('div');
        settingsContainer.id = 'website-summary-settings';
        
        // 基础样式
        const isDark = config.theme === 'dark';
        settingsContainer.style.cssText = `
            position: fixed;
            z-index: 1000000;
            background: ${isDark ? 'rgba(32, 33, 36, 0.98)' : 'rgba(255, 255, 255, 0.98)'};
            color: ${isDark ? '#e8eaed' : '#333'};
            border-radius: 12px;
            box-shadow: 0 4px 20px ${isDark ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.15)'};
            padding: 20px;
            width: 400px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            display: none;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            will-change: transform;
            -webkit-backface-visibility: hidden;
            backface-visibility: hidden;
        `;

        if (browserSupport.hasBackdropFilter) {
            settingsContainer.style.backdropFilter = 'blur(10px)';
            settingsContainer.style.webkitBackdropFilter = 'blur(10px)';
        }

        // 标题栏
        const header = document.createElement('div');
        header.style.cssText = `
            display: flex; 
            justify-content: space-between; 
            align-items: center; 
            margin-bottom: 20px; 
            cursor: move;
            user-select: none;
            -webkit-user-select: none;
        `;

        const title = document.createElement('h3');
        title.textContent = '设置';
        title.style.cssText = `
            margin: 0; 
            color: ${isDark ? '#e8eaed' : '#333'};
            pointer-events: none;
        `;

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        closeBtn.style.cssText = `
            background: none;
            border: none;
            font-size: 24px;
            cursor: pointer;
            padding: 0 8px;
            color: ${isDark ? '#e8eaed' : '#666'};
        `;
        closeBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            settingsContainer.style.display = 'none';
            if (elements.backdrop) {
                elements.backdrop.style.opacity = '0';
                setTimeout(() => elements.backdrop.style.display = 'none', 300);
            }
        });

        // 表单
        const form = document.createElement('form');
        form.style.cssText = 'display: flex; flex-direction: column; gap: 16px;';
        
        // 创建输入字段函数
        function createField(id, label, value, type = 'text', placeholder = '') {
            const container = document.createElement('div');
            container.style.cssText = 'display: flex; flex-direction: column; gap: 4px;';
            
            const labelElem = document.createElement('label');
            labelElem.textContent = label;
            labelElem.style.cssText = `font-size: 14px; color: ${isDark ? '#e8eaed' : '#333'}; font-weight: 500;`;
            
            const input = document.createElement(type === 'textarea' ? 'textarea' : 'input');
            if (type !== 'textarea') input.type = type;
            input.id = id;
            input.value = value;
            input.placeholder = placeholder;
            input.autocomplete = 'off';
            input.setAttribute('data-form-type', 'other');
            
            const baseStyle = `
                width: 100%;
                padding: 8px;
                border: 1px solid ${isDark ? '#555' : '#ddd'};
                border-radius: 6px;
                font-family: inherit;
                background: ${isDark ? '#202124' : '#fff'};
                color: ${isDark ? '#e8eaed' : '#333'};
            `;
            input.style.cssText = type === 'textarea' ? baseStyle + 'height: 100px; resize: vertical;' : baseStyle;
            
            container.appendChild(labelElem);
            container.appendChild(input);
            return { container, input };
        }

        // 创建主题切换
        function createThemeSwitch() {
            const container = document.createElement('div');
            container.style.cssText = 'display: flex; align-items: center; gap: 12px; margin-bottom: 16px;';
            
            const label = document.createElement('label');
            label.textContent = '主题模式:';
            label.style.cssText = `font-size: 14px; color: ${isDark ? '#e8eaed' : '#333'}; font-weight: 500;`;
            
            const themeSwitch = document.createElement('div');
            themeSwitch.style.cssText = 'display: flex; gap: 8px;';
            
            const createThemeButton = (themeName, text) => {
                const btn = document.createElement('button');
                btn.textContent = text;
                btn.type = 'button';
                const isActive = config.theme === themeName;
                btn.style.cssText = `
                    padding: 6px 12px;
                    border-radius: 4px;
                    border: 1px solid ${isDark ? '#555' : '#ddd'};
                    background: ${isActive ? (isDark ? '#555' : '#007AFF') : 'transparent'};
                    color: ${isActive ? '#fff' : (isDark ? '#e8eaed' : '#333')};
                    cursor: pointer;
                    transition: all 0.2s;
                `;
                btn.addEventListener('click', async () => {
                    config.theme = themeName;
                    await scriptHandler.setValue('theme', themeName);
                    // 重新创建设置界面而不是移除
                    const oldSettings = elements.settings;
                    elements.settings = null;
                    showSettings();
                    if (oldSettings) {
                        oldSettings.remove();
                    }
                });
                return btn;
            };
            
            const lightBtn = createThemeButton('light', '浅色');
            const darkBtn = createThemeButton('dark', '深色');
            
            themeSwitch.appendChild(lightBtn);
            themeSwitch.appendChild(darkBtn);
            container.appendChild(label);
            container.appendChild(themeSwitch);
            
            return container;
        }
        
        // 创建字段
        const apiUrlField = createField('apiUrl', 'API URL', config.apiUrl, 'text', '输入API URL');
        const apiKeyField = createField('apiKey', 'API Key', config.apiKey, 'text', '输入API Key');
        const modelField = createField('model', 'AI 模型', config.model, 'text', '输入AI模型名称');
        const shortcutField = createField('shortcut', '快捷键', config.shortcut, 'text', '例如: option+a, ctrl+shift+s');
        const promptField = createField('prompt', '提示词', config.prompt, 'textarea', '输入提示词');
        
        // 添加主题切换
        form.appendChild(createThemeSwitch());
        
        // 添加字段到表单
        form.appendChild(apiUrlField.container);
        form.appendChild(apiKeyField.container);
        form.appendChild(modelField.container);
        form.appendChild(shortcutField.container);
        form.appendChild(promptField.container);
        
        // 保存按钮
        const saveBtn = document.createElement('button');
        saveBtn.textContent = '保存设置';
        saveBtn.type = 'button';
        saveBtn.style.cssText = `
            background: ${isDark ? '#8ab4f8' : '#007AFF'};
            color: ${isDark ? '#202124' : '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 = isDark ? '#aecbfa' : '#0056b3';
        });
        saveBtn.addEventListener('mouseout', () => {
            saveBtn.style.backgroundColor = isDark ? '#8ab4f8' : '#007AFF';
        });
        
        // 保存逻辑
        saveBtn.addEventListener('click', async (e) => {
            e.preventDefault();
            
            // 获取并验证表单值
            const newApiUrl = apiUrlField.input.value.trim();
            const newApiKey = apiKeyField.input.value.trim();
            const newModel = modelField.input.value.trim();
            const newPrompt = promptField.input.value.trim();
            const newShortcut = shortcutField.input.value.trim();
            
            if (!newApiUrl || !newApiKey) {
                alert('请至少填写API URL和API Key');
                return;
            }

            try {
                // 使用scriptHandler保存设置
                await scriptHandler.setValue('apiUrl', newApiUrl);
                await scriptHandler.setValue('apiKey', newApiKey);
                await scriptHandler.setValue('model', newModel);
                await scriptHandler.setValue('prompt', newPrompt);
                await scriptHandler.setValue('shortcut', newShortcut);
                await scriptHandler.setValue('theme', config.theme);

                // 更新内存配置
                config.apiUrl = newApiUrl;
                config.apiKey = newApiKey;
                config.model = newModel;
                config.prompt = newPrompt;
                config.shortcut = newShortcut;
                
                // 更新快捷键
                keyManager.setup();

                // 显示成功提示
                showToast('设置已保存');
                
                // 关闭设置
                settingsContainer.style.display = 'none';
            } catch (error) {
                console.error('保存设置失败:', error);
                showToast('保存设置失败,请重试');
            }
        });

        // 组装界面
        header.appendChild(title);
        header.appendChild(closeBtn);
        form.appendChild(saveBtn);
        settingsContainer.appendChild(header);
        settingsContainer.appendChild(form);
        document.body.appendChild(settingsContainer);

        // 使用优化的拖拽功能,只允许通过标题栏拖动
        makeDraggableByHeader(settingsContainer, header);
        elements.settings = settingsContainer;
        return settingsContainer;
    }

    // 获取页面内容
    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, 5000);
        } catch (error) {
            return document.body.innerText.slice(0, 5000);
        }
    }

    // 修改深色模式颜色方案
    const darkColors = {
        background: '#242526',           // 更柔和的深色背景
        containerBg: '#2d2d30',          // 容器背景色
        text: '#e4e6eb',                 // 更柔和的文字颜色
        secondaryText: '#b0b3b8',        // 次要文字颜色
        border: '#3e4042',               // 边框颜色
        codeBackground: '#3a3b3c',       // 代码块背景
        blockquoteBorder: '#4a4b4d',     // 引用块边框
        blockquoteText: '#cacbcc',       // 引用块文字
        linkColor: '#4e89e8'             // 链接颜色
    };

    // 修改 API 调用函数
    function getSummary(content) {
        return new Promise((resolve, reject) => {
            const apiKey = config.apiKey.trim();
            
            if (!apiKey) {
                reject(new Error('请先设置API Key'));
                return;
            }

            const requestData = {
                model: config.model,
                messages: [
                    {
                        role: 'system',
                        content: '你是一个专业的网页内容总结助手,善于使用markdown格式来组织信息。'
                    },
                    {
                        role: 'user',
                        content: config.prompt + '\n\n' + content
                    }
                ],
                temperature: 0.7,
                stream: false
            };

            // 处理 URL
            let apiUrl = config.apiUrl.trim();
            if (!apiUrl.startsWith('http://') && !apiUrl.startsWith('https://')) {
                apiUrl = 'https://' + apiUrl;
            }

            // 打印请求信息用于调试
            console.log('发送请求到:', apiUrl);
            console.log('请求数据:', JSON.stringify(requestData, null, 2));

            // 发送请求
            const xhr = typeof GM_xmlhttpRequest !== 'undefined' ? GM_xmlhttpRequest : (typeof GM !== 'undefined' && GM.xmlHttpRequest);
            
            if (!xhr) {
                reject(new Error('不支持的环境:无法发送跨域请求'));
                return;
            }

            xhr({
                method: 'POST',
                url: apiUrl,
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${apiKey}`,
                    'Accept': 'application/json'
                },
                data: JSON.stringify(requestData),
                timeout: 30000,
                onload: function(response) {
                    try {
                        console.log('收到响应:', response.status);
                        console.log('响应头:', response.responseHeaders);
                        console.log('响应内容:', response.responseText);

                        if (response.status === 429) {
                            reject(new Error('API请求过于频繁,请稍后再试'));
                            return;
                        }

                        if (response.status !== 200) {
                            reject(new Error(`API请求失败: HTTP ${response.status}`));
                            return;
                        }

                        let data;
                        try {
                            data = JSON.parse(response.responseText);
                        } catch (e) {
                            console.error('JSON解析失败:', e);
                            reject(new Error('API响应格式错误'));
                            return;
                        }

                        if (data.error) {
                            reject(new Error('API错误: ' + (data.error.message || JSON.stringify(data.error))));
                            return;
                        }

                        // 提取内容
                        let content = null;
                        
                        if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) {
                            const choice = data.choices[0];
                            if (choice.message && choice.message.content) {
                                content = choice.message.content;
                            } else if (choice.text) {
                                content = choice.text;
                            }
                        }

                        if (!content && data.response) {
                            content = typeof data.response === 'string' ? data.response : JSON.stringify(data.response);
                        }

                        if (!content && data.content) {
                            content = data.content;
                        }

                        if (content) {
                            resolve(content.trim());
                        } else {
                            reject(new Error('无法从API响应中提取内容'));
                        }
                    } catch (error) {
                        console.error('处理响应时出错:', error);
                        reject(new Error('处理响应失败: ' + error.message));
                    }
                },
                onerror: function(error) {
                    console.error('请求错误:', error);
                    reject(new Error('请求失败: ' + (error.message || '网络错误')));
                },
                ontimeout: function() {
                    reject(new Error('请求超时'));
                }
            });
        });
    }

    // 配置 Marked 渲染器
    function configureMarked() {
        if (typeof marked === 'undefined') return;

        // 配置 marked 选项
        marked.setOptions({
            gfm: true,
            breaks: true,
            headerIds: true,
            mangle: false,
            smartLists: true,
            smartypants: true,
            highlight: function(code, lang) {
                return code;
            }
        });

        // 自定义渲染器
        const renderer = new marked.Renderer();

        // 自定义标题渲染 - 移除 ## 前缀
        renderer.heading = function(text, level) {
            return `<h${level}>${text}</h${level}>`;
        };

        // 自定义列表项渲染
        renderer.listitem = function(text) {
            return `<li><span class="bullet">•</span><span class="text">${text}</span></li>`;
        };

        // 自定义段落渲染
        renderer.paragraph = function(text) {
            return `<p>${text}</p>`;
        };

        // 自定义代码块渲染
        renderer.code = function(code, language) {
            return `<pre><code class="language-${language}">${code}</code></pre>`;
        };

        // 自定义引用块渲染
        renderer.blockquote = function(quote) {
            return `<blockquote>${quote}</blockquote>`;
        };

        // 设置渲染器
        marked.setOptions({ renderer });
    }

    // 修改 Markdown 样式
    function addMarkdownStyles() {
        const styleId = 'website-summary-markdown-styles';
        if (document.getElementById(styleId)) return;

        const isDark = config.theme === 'dark';
        const style = document.createElement('style');
        style.id = styleId;
        
        // 定义颜色变量
        const colors = {
            light: {
                text: '#2c3e50',
                background: '#ffffff',
                border: '#e2e8f0',
                link: '#2563eb',
                linkHover: '#1d4ed8',
                code: '#f8fafc',
                codeBorder: '#e2e8f0',
                blockquote: '#f8fafc',
                blockquoteBorder: '#3b82f6',
                heading: '#1e293b',
                hr: '#e2e8f0',
                marker: '#64748b'
            },
            dark: {
                text: '#e2e8f0',
                background: '#1e293b',
                border: '#334155',
                link: '#60a5fa',
                linkHover: '#93c5fd',
                code: '#1e293b',
                codeBorder: '#334155',
                blockquote: '#1e293b',
                blockquoteBorder: '#60a5fa',
                heading: '#f1f5f9',
                hr: '#334155',
                marker: '#94a3b8'
            }
        };

        const c = isDark ? colors.dark : colors.light;

        style.textContent = `
            #website-summary-content {
                font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
                line-height: 1.7;
                color: ${c.text};
                font-size: 15px;
                padding: 20px;
                max-width: 800px;
                margin: 0 auto;
            }

            #website-summary-content h2 {
                font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
                font-weight: 600;
                line-height: 1.3;
                margin: 1.8em 0 1em;
                color: ${c.heading};
                font-size: 1.6em;
                letter-spacing: -0.01em;
            }

            #website-summary-content h3 {
                font-size: 1.3em;
                margin: 1.5em 0 0.8em;
                color: ${c.heading};
                font-weight: 600;
                line-height: 1.4;
            }

            #website-summary-content p {
                margin: 0.8em 0;
                line-height: 1.75;
                letter-spacing: 0.01em;
            }

            #website-summary-content ul {
                margin: 0.6em 0;
                padding-left: 0.5em;
                list-style: none;
            }

            #website-summary-content ul li {
                display: flex;
                align-items: baseline;
                margin: 0.4em 0;
                line-height: 1.6;
                letter-spacing: 0.01em;
            }

            #website-summary-content ul li .bullet {
                color: ${c.marker};
                margin-right: 0.7em;
                font-weight: normal;
                flex-shrink: 0;
            }

            #website-summary-content ul li .text {
                flex: 1;
            }

            #website-summary-content blockquote {
                margin: 1.2em 0;
                padding: 0.8em 1.2em;
                background: ${c.blockquote};
                border-left: 4px solid ${c.blockquoteBorder};
                border-radius: 6px;
                color: ${isDark ? '#cbd5e1' : '#475569'};
                font-style: italic;
            }

            #website-summary-content blockquote p {
                margin: 0.4em 0;
            }

            #website-summary-content code {
                font-family: "SF Mono", Menlo, Monaco, Consolas, monospace;
                font-size: 0.9em;
                background: ${c.code};
                border: 1px solid ${c.codeBorder};
                border-radius: 4px;
                padding: 0.2em 0.4em;
            }

            #website-summary-content pre {
                background: ${c.code};
                border: 1px solid ${c.codeBorder};
                border-radius: 8px;
                padding: 1.2em;
                overflow-x: auto;
                margin: 1.2em 0;
            }

            #website-summary-content pre code {
                background: none;
                border: none;
                padding: 0;
                font-size: 0.9em;
                line-height: 1.6;
            }

            #website-summary-content strong {
                font-weight: 600;
                color: ${isDark ? '#f1f5f9' : '#1e293b'};
            }

            #website-summary-content em {
                font-style: italic;
                color: ${isDark ? '#cbd5e1' : '#475569'};
            }

            #website-summary-content hr {
                margin: 2em 0;
                border: none;
                border-top: 1px solid ${c.hr};
            }

            #website-summary-content table {
                width: 100%;
                border-collapse: collapse;
                margin: 1.2em 0;
                font-size: 0.95em;
            }

            #website-summary-content th,
            #website-summary-content td {
                padding: 0.8em;
                border: 1px solid ${c.border};
                text-align: left;
            }

            #website-summary-content th {
                background: ${c.code};
                font-weight: 600;
            }

            #website-summary-content img {
                max-width: 100%;
                height: auto;
                border-radius: 8px;
                margin: 1em 0;
            }

            @media (max-width: 768px) {
                #website-summary-content {
                    font-size: 14px;
                    padding: 16px;
                }

                #website-summary-content h2 {
                    font-size: 1.4em;
                }

                #website-summary-content h3 {
                    font-size: 1.2em;
                }
            }
        `;

        document.head.appendChild(style);
    }

    // 修复打字机效果后内容消失的问题
    async function renderContent(content) {
        const container = document.getElementById('website-summary-content');
        if (!container) return;
        
        try {
            if (!content || content.trim().length === 0) {
                throw new Error('内容为空');
            }

            // 确保 marked 已加载并配置
            if (typeof marked === 'undefined') {
                throw new Error('Markdown 渲染器未加载');
            }
            
            // 配置 marked
            configureMarked();

            // 渲染 Markdown
            const html = marked.parse(content);
            
            // 清空容器
            container.innerHTML = '';
            
            // 创建临时容器
            const temp = document.createElement('div');
            temp.innerHTML = html;
            
            // 始终启用打字机效果
            const backupContent = temp.cloneNode(true);
            
            try {
                // 真实的逐字符打字机效果
                const typeWriter = async () => {
                    // 首先添加所有元素到DOM,但设置为不可见
                    const fragments = Array.from(temp.children);
                    const allElementsWithText = [];
                    
                    // 添加所有HTML元素结构,但内容为空
                    for (let fragment of fragments) {
                        // 克隆元素,但清空文本内容
                        const emptyElement = fragment.cloneNode(true);
                        
                        // 递归查找所有文本节点并收集信息
                        const collectTextNodes = (node, parentElement) => {
                            if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
                                // 保存文本节点信息
                                allElementsWithText.push({
                                    element: parentElement,
                                    originalText: node.textContent,
                                    currentPosition: 0
                                });
                                // 清空文本
                                node.textContent = '';
                            } else if (node.nodeType === Node.ELEMENT_NODE) {
                                // 处理子元素中的文本节点
                                for (const child of Array.from(node.childNodes)) {
                                    collectTextNodes(child, node);
                                }
                            }
                        };
                        
                        collectTextNodes(fragment, emptyElement);
                        container.appendChild(emptyElement);
                    }
                    
                    // 打字速度调整 - 根据总字符数动态调整
                    const totalChars = allElementsWithText.reduce((sum, item) => sum + item.originalText.length, 0);
                    // 对于长内容,加快打字速度
                    const baseCharDelay = totalChars > 1000 ? 3 : 5; // 每个字符的基础延迟(毫秒)
                    
                    // 复制原始DOM结构,用于最终替换(避免打字过程中的可能问题)
                    const finalContent = backupContent.cloneNode(true);
                    
                    // 开始打字
                    let typedChars = 0;
                    const startTime = performance.now();
                    let lastScrollTime = 0;
                    
                    while (typedChars < totalChars) {
                        // 随机选择一个还有字符要显示的元素
                        const pendingElements = allElementsWithText.filter(item => 
                            item.currentPosition < item.originalText.length);
                        
                        if (pendingElements.length === 0) break;
                        
                        // 随机选择一个待处理元素
                        const randomIndex = Math.floor(Math.random() * pendingElements.length);
                        const selectedItem = pendingElements[randomIndex];
                        
                        // 添加下一个字符
                        const char = selectedItem.originalText[selectedItem.currentPosition];
                        selectedItem.currentPosition++;
                        typedChars++;
                        
                        // 更新DOM (查找元素中的第一个文本节点并添加字符)
                        const updateTextNode = (node) => {
                            if (node.nodeType === Node.TEXT_NODE) {
                                node.textContent += char;
                                return true;
                            } else if (node.nodeType === Node.ELEMENT_NODE) {
                                for (const child of Array.from(node.childNodes)) {
                                    if (updateTextNode(child)) {
                                        return true;
                                    }
                                }
                            }
                            return false;
                        };
                        
                        updateTextNode(selectedItem.element);
                        
                        // 智能滚动:每处理30个字符滚动一次,并加入时间限制,避免滚动过于频繁
                        const currentTime = performance.now();
                        if (typedChars % 30 === 0 && currentTime - lastScrollTime > 200) {
                            container.scrollTop = container.scrollHeight;
                            lastScrollTime = currentTime;
                        }
                        
                        // 动态调整延迟,以获得更自然的打字感觉
                        const progress = typedChars / totalChars;
                        let adjustedDelay = baseCharDelay;
                        
                        // 开始更快,中间变慢,结束再次加速
                        if (progress < 0.2) {
                            adjustedDelay = baseCharDelay * 0.5; // 开始阶段更快
                        } else if (progress > 0.8) {
                            adjustedDelay = baseCharDelay * 0.7; // 结束阶段也较快
                        }
                        
                        // 有时候添加一个随机的短暂停顿,模拟真人打字节奏(减少概率,避免过慢)
                        if (Math.random() < 0.03) {
                            adjustedDelay = baseCharDelay * 4; // 偶尔的停顿
                        }
                        
                        await new Promise(resolve => setTimeout(resolve, adjustedDelay));
                        
                        // 检查是否超时(超过6秒),如果超时就直接显示全部内容
                        if (performance.now() - startTime > 6000) {
                            console.log('打字机效果超时,直接显示全部内容');
                            break;
                        }
                    }
                    
                    // 打字完成或超时后,确保显示完整内容
                    return finalContent;
                };
                
                // 开始打字效果
                const completedContent = await typeWriter();
                
                // 使用单独的 try-catch 确保内容不丢失
                try {
                    // 确保内容完整显示 - 使用替换节点而不是直接操作innerHTML
                    if (completedContent) {
                        // 先替换内容,再移除原来的内容
                        const tempDiv = document.createElement('div');
                        while (completedContent.firstChild) {
                            tempDiv.appendChild(completedContent.firstChild);
                        }
                        
                        // 清除旧内容
                        while (container.firstChild) {
                            container.removeChild(container.firstChild);
                        }
                        
                        // 添加新内容
                        while (tempDiv.firstChild) {
                            container.appendChild(tempDiv.firstChild);
                        }
                    }
                } catch (finalError) {
                    console.error('最终内容替换失败:', finalError);
                    // 如果替换失败,确保使用备份内容显示
                    container.innerHTML = '';
                    
                    // 再次尝试添加原始备份内容
                    try {
                        Array.from(backupContent.children).forEach(child => {
                            container.appendChild(child.cloneNode(true));
                        });
                    } catch (lastError) {
                        // 最终失败,直接使用原始HTML
                        container.innerHTML = html;
                    }
                }
            } catch (typewriterError) {
                console.error('打字机效果失败:', typewriterError);
                // 确保内容显示即使打字机效果失败
                container.innerHTML = '';
                while (backupContent.firstChild) {
                    container.appendChild(backupContent.firstChild);
                }
            }
            
            // 确保内容显示后滚动到顶部
            setTimeout(() => {
                container.scrollTop = 0;
            }, 100);
        } catch (error) {
            console.error('渲染内容失败:', error);
            container.innerHTML = `
                <p style="text-align: center; color: #ff4444;">
                    渲染内容失败:${error.message}<br>
                    <small style="color: ${config.theme === 'dark' ? '#bdc1c6' : '#666'};">
                        请刷新页面重试
                    </small>
                </p>`;
        }
    }

    // 添加菜单命令
    function registerMenuCommands() {
        scriptHandler.registerMenuCommand('显示网页总结 (快捷键: ' + config.shortcut + ')', showSummary);
        scriptHandler.registerMenuCommand('打开设置', showSettings);
    }

    // 启动脚本
    waitForPageLoad();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址