ChatGPT Auto Read Aloud

GPT回复完成后自动点击朗读按钮 - 支持设置持久化存储

// ==UserScript==
// @name         ChatGPT Auto Read Aloud
// @namespace    http://tampermonkey.net/
// @version      1.9
// @description  GPT回复完成后自动点击朗读按钮 - 支持设置持久化存储
// @author       schweigen
// @license      MIT
// @match        https://chatgpt.com/*
// @grant        GM_registerMenuCommand
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 默认配置
    const DEFAULT_CONFIG = {
        enabled: true,
        delay: 500,
        maxWaitTime: 30000,
        debug: true
    };

    // 从LocalStorage加载配置
    function loadConfig() {
        try {
            const savedConfig = localStorage.getItem('chatgpt_auto_read_config');
            if (savedConfig) {
                const parsed = JSON.parse(savedConfig);
                return { ...DEFAULT_CONFIG, ...parsed };
            }
        } catch (e) {
            console.warn('[ChatGPT Auto Read] 加载配置失败:', e);
        }
        return { ...DEFAULT_CONFIG };
    }

    // 保存配置到LocalStorage
    function saveConfig() {
        try {
            const configToSave = {
                enabled: CONFIG.enabled,
                delay: CONFIG.delay,
                maxWaitTime: CONFIG.maxWaitTime,
                debug: CONFIG.debug
            };
            localStorage.setItem('chatgpt_auto_read_config', JSON.stringify(configToSave));
            debugLog('配置已保存到LocalStorage');
        } catch (e) {
            console.error('[ChatGPT Auto Read] 保存配置失败:', e);
        }
    }

    // 初始化配置
    const CONFIG = loadConfig();

    let lastProcessedMessageId = null;
    let observer = null;
    let checkTimer = null;
    let isDragging = false;
    let dragStartX, dragStartY;
    let ballStartX, ballStartY;
    let hasMoved = false;
    let isExpanded = false;

    function debugLog(message) {
        if (CONFIG.debug) {
            console.log('[ChatGPT Auto Read]', new Date().toLocaleTimeString(), message);
        }
    }

    // 保存和读取位置(使用LocalStorage)
    function savePosition(x, y) {
        try {
            localStorage.setItem('chatgpt_auto_read_position', JSON.stringify({x, y}));
            debugLog(`位置已保存: ${x}, ${y}`);
        } catch (e) {
            console.error('[ChatGPT Auto Read] 保存位置失败:', e);
        }
    }

    function loadPosition() {
        try {
            const saved = localStorage.getItem('chatgpt_auto_read_position');
            return saved ? JSON.parse(saved) : null;
        } catch (e) {
            console.warn('[ChatGPT Auto Read] 加载位置失败:', e);
            return null;
        }
    }

    // 重置位置
    function resetPosition() {
        const ball = document.getElementById('autoReadBall');
        if (ball) {
            ball.style.left = '30px';
            ball.style.top = '50%';
            ball.style.transform = 'translateY(-50%)';
            ball.style.right = 'auto';
            ball.style.bottom = 'auto';
            savePosition(30, window.innerHeight / 2);
            showNotification('🏠 位置已重置');
        }
    }

    // 切换启用状态的函数
    function toggleEnabled() {
        CONFIG.enabled = !CONFIG.enabled;
        saveConfig(); // 保存配置

        const toggle = document.getElementById('autoReadToggle');
        const status = document.getElementById('autoReadStatus');
        const ball = document.getElementById('autoReadBall');

        if (toggle) {
            toggle.checked = CONFIG.enabled;
        }

        if (status) {
            status.textContent = CONFIG.enabled ? '启用' : '禁用';
            status.style.color = CONFIG.enabled ? '#10a37f' : '#dc2626';
        }

        if (ball) {
            ball.style.background = CONFIG.enabled ? 'rgba(16, 163, 127, 0.8)' : 'rgba(128, 128, 128, 0.8)';
            ball.style.borderColor = CONFIG.enabled ? 'rgba(16, 163, 127, 1)' : 'rgba(128, 128, 128, 1)';
        }

        showNotification(CONFIG.enabled ? '✅ 已启用自动朗读' : '❌ 已禁用自动朗读');
        debugLog(`自动朗读已${CONFIG.enabled ? '启用' : '禁用'}`);
    }

    // 更新延迟时间的函数
    function updateDelay(newDelay) {
        CONFIG.delay = parseInt(newDelay);
        saveConfig(); // 保存配置
        
        const delayValue = document.getElementById('delayValue');
        if (delayValue) {
            delayValue.textContent = CONFIG.delay;
        }
        
        debugLog(`延迟时间设置为: ${CONFIG.delay}ms`);
    }

    // 注册油猴菜单
    GM_registerMenuCommand('重置圆球位置', resetPosition);
    GM_registerMenuCommand('切换启用状态', toggleEnabled);

    // 创建拖拽圆球控制器
    function createFloatingBall() {
        // 获取保存的位置
        const savedPos = loadPosition();
        const defaultLeft = '30px';
        const defaultTop = '50%';

        // 主圆球 - 根据启用状态设置颜色
        const ball = document.createElement('div');
        ball.id = 'autoReadBall';
        ball.style.cssText = `
            position: fixed;
            left: ${savedPos ? savedPos.x + 'px' : defaultLeft};
            top: ${savedPos ? savedPos.y + 'px' : defaultTop};
            ${!savedPos ? 'transform: translateY(-50%);' : ''}
            width: 28px;
            height: 28px;
            background: ${CONFIG.enabled ? 'rgba(16, 163, 127, 0.8)' : 'rgba(128, 128, 128, 0.8)'};
            border: 1.5px solid ${CONFIG.enabled ? 'rgba(16, 163, 127, 1)' : 'rgba(128, 128, 128, 1)'};
            border-radius: 50%;
            color: white;
            font-size: 15px;
            font-weight: bold;
            font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: grab;
            z-index: 10000;
            box-shadow: 0 2px 8px rgba(0,0,0,0.3);
            transition: all 0.2s ease;
            user-select: none;
            backdrop-filter: blur(5px);
            line-height: 1;
        `;
        ball.textContent = '读';

        // 设置面板
        const panel = document.createElement('div');
        panel.id = 'autoReadPanel';
        panel.style.cssText = `
            position: fixed;
            left: 70px;
            top: 50%;
            transform: translateY(-50%);
            width: 240px;
            background: rgba(45, 45, 45, 0.95);
            color: white;
            padding: 15px;
            border-radius: 12px;
            border: 1px solid rgba(85, 85, 85, 0.8);
            z-index: 9999;
            font-family: -apple-system, BlinkMacSystemFont, sans-serif;
            font-size: 12px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.4);
            backdrop-filter: blur(10px);
            transform: translateY(-50%) scale(0) translateX(-20px);
            opacity: 0;
            transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
            transform-origin: left center;
            pointer-events: none;
        `;

        panel.innerHTML = `
            <div style="margin-bottom: 12px; font-weight: bold; color: #10a37f; border-bottom: 1px solid rgba(85,85,85,0.5); padding-bottom: 6px;">
                🔊 自动朗读设置
            </div>

            <label style="display: flex; align-items: center; cursor: pointer; margin-bottom: 12px; padding: 6px; border-radius: 6px; transition: background 0.2s;" onmouseover="this.style.background='rgba(255,255,255,0.1)'" onmouseout="this.style.background='transparent'">
                <input type="checkbox" id="autoReadToggle" ${CONFIG.enabled ? 'checked' : ''}
                       style="margin-right: 8px; transform: scale(1.1);">
                <span style="font-size: 13px;">启用自动朗读</span>
            </label>

            <div style="margin-bottom: 12px; padding: 8px; background: rgba(255,255,255,0.05); border-radius: 6px;">
                <label style="display: block; margin-bottom: 6px; font-size: 11px; color: #a0a0a0;">
                    延迟时间: <span id="delayValue" style="color: #10a37f; font-weight: bold;">${CONFIG.delay}</span>ms
                </label>
                <input type="range" id="delaySlider" min="200" max="3000" step="100" value="${CONFIG.delay}"
                       style="width: 100%; height: 4px; background: #555; border-radius: 2px; outline: none; -webkit-appearance: none;">
                <div style="display: flex; justify-content: space-between; font-size: 9px; color: #888; margin-top: 2px;">
                    <span>200ms</span>
                    <span>3000ms</span>
                </div>
            </div>

            <button id="testReadButton" style="width: 100%; padding: 8px; background: linear-gradient(135deg, #10a37f, #0d8764); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; margin-bottom: 8px; transition: all 0.2s; box-shadow: 0 2px 4px rgba(0,0,0,0.2);" onmouseover="this.style.transform='translateY(-1px)'; this.style.boxShadow='0 4px 8px rgba(0,0,0,0.3)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 4px rgba(0,0,0,0.2)'">
                🎯 测试朗读
            </button>

            <button id="resetPosButton" style="width: 100%; padding: 6px; background: rgba(128, 128, 128, 0.3); color: white; border: 1px solid rgba(128, 128, 128, 0.5); border-radius: 6px; cursor: pointer; font-size: 11px; margin-bottom: 12px; transition: all 0.2s;" onmouseover="this.style.background='rgba(128, 128, 128, 0.5)'" onmouseout="this.style.background='rgba(128, 128, 128, 0.3)'">
                🏠 重置位置
            </button>

            <button id="clearConfigButton" style="width: 100%; padding: 6px; background: rgba(220, 38, 38, 0.3); color: white; border: 1px solid rgba(220, 38, 38, 0.5); border-radius: 6px; cursor: pointer; font-size: 11px; margin-bottom: 12px; transition: all 0.2s;" onmouseover="this.style.background='rgba(220, 38, 38, 0.5)'" onmouseout="this.style.background='rgba(220, 38, 38, 0.3)'">
                🗑️ 清除所有设置
            </button>

            <div style="font-size: 10px; color: #888; border-top: 1px solid rgba(85,85,85,0.3); padding-top: 8px; line-height: 1.4;">
                <div style="margin-bottom: 4px;">
                    状态: <span id="autoReadStatus" style="color: ${CONFIG.enabled ? '#10a37f' : '#dc2626'}">${CONFIG.enabled ? '启用' : '禁用'}</span>
                </div>
                <div style="margin-bottom: 4px;">
                    存储: <span style="color: #10a37f">LocalStorage</span>
                </div>
                <div style="margin-bottom: 4px;">
                    最后处理: <span id="lastProcessed" style="color: #a0a0a0;">无</span>
                </div>
                <div style="font-size: 9px; color: #666;">
                    💡 点击圆球展开/收起面板
                </div>
            </div>
        `;

        document.body.appendChild(ball);
        document.body.appendChild(panel);

        // 添加CSS样式
        const style = document.createElement('style');
        style.textContent = `
            #autoReadBall:hover {
                background: ${CONFIG.enabled ? 'rgba(16, 163, 127, 1)' : 'rgba(128, 128, 128, 1)'} !important;
                transform: scale(1.15) ${!savedPos ? 'translateY(-50%)' : ''};
                box-shadow: 0 3px 12px ${CONFIG.enabled ? 'rgba(16, 163, 127, 0.4)' : 'rgba(128, 128, 128, 0.4)'};
            }

            #autoReadBall.dragging {
                cursor: grabbing !important;
                transform: scale(0.9);
                box-shadow: 0 4px 15px rgba(0,0,0,0.5);
                z-index: 10001;
            }

            #delaySlider::-webkit-slider-thumb {
                -webkit-appearance: none;
                width: 16px;
                height: 16px;
                background: #10a37f;
                border-radius: 50%;
                cursor: pointer;
                box-shadow: 0 2px 4px rgba(0,0,0,0.3);
            }

            #delaySlider::-moz-range-thumb {
                width: 16px;
                height: 16px;
                background: #10a37f;
                border-radius: 50%;
                cursor: pointer;
                border: none;
                box-shadow: 0 2px 4px rgba(0,0,0,0.3);
            }
        `;
        document.head.appendChild(style);

        // 绑定事件
        setupBallEvents(ball, panel);
        setupPanelEvents();
    }

    // 设置圆球交互事件
    function setupBallEvents(ball, panel) {
        let mouseDownTime = 0;
        let dragTimer = null;

        // 鼠标按下事件
        ball.addEventListener('mousedown', function(e) {
            if (e.button !== 0) return; // 只响应左键

            e.preventDefault();
            e.stopPropagation();

            mouseDownTime = Date.now();
            isDragging = false;
            hasMoved = false;

            // 记录开始位置
            dragStartX = e.clientX;
            dragStartY = e.clientY;

            const rect = ball.getBoundingClientRect();
            ballStartX = rect.left;
            ballStartY = rect.top;

            // 延迟150ms才开始拖拽模式,避免误触
            dragTimer = setTimeout(() => {
                if (!hasMoved) {
                    isDragging = true;
                    ball.classList.add('dragging');

                    // 添加全局事件监听
                    document.addEventListener('mousemove', handleMouseMove, true);
                    document.addEventListener('mouseup', handleMouseUp, true);

                    // 禁用页面选择
                    document.body.style.userSelect = 'none';

                    debugLog('开始拖拽模式');
                }
            }, 150);
        });

        // 鼠标移动处理
        function handleMouseMove(e) {
            if (!isDragging) {
                // 还没进入拖拽模式,检查移动距离
                const deltaX = e.clientX - dragStartX;
                const deltaY = e.clientY - dragStartY;

                // 提高移动阈值到8px,减少误触
                if (Math.abs(deltaX) > 8 || Math.abs(deltaY) > 8) {
                    hasMoved = true;
                    debugLog('检测到移动,标记为已移动');
                }
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            const deltaX = e.clientX - dragStartX;
            const deltaY = e.clientY - dragStartY;

            // 计算新位置
            let newX = ballStartX + deltaX;
            let newY = ballStartY + deltaY;

            // 边界限制
            const maxX = window.innerWidth - ball.offsetWidth;
            const maxY = window.innerHeight - ball.offsetHeight;

            newX = Math.max(0, Math.min(newX, maxX));
            newY = Math.max(0, Math.min(newY, maxY));

            // 应用位置
            ball.style.left = newX + 'px';
            ball.style.top = newY + 'px';
            ball.style.right = 'auto';
            ball.style.bottom = 'auto';
            ball.style.transform = 'none';
        }

        // 鼠标释放处理
        function handleMouseUp(e) {
            const mouseUpTime = Date.now();
            const clickDuration = mouseUpTime - mouseDownTime;

            // 清除拖拽定时器
            if (dragTimer) {
                clearTimeout(dragTimer);
                dragTimer = null;
            }

            if (isDragging) {
                e.preventDefault();
                e.stopPropagation();

                isDragging = false;
                ball.classList.remove('dragging');

                // 移除全局事件监听
                document.removeEventListener('mousemove', handleMouseMove, true);
                document.removeEventListener('mouseup', handleMouseUp, true);

                // 恢复页面选择
                document.body.style.userSelect = '';

                // 保存位置
                const rect = ball.getBoundingClientRect();
                savePosition(rect.left, rect.top);

                debugLog('拖拽结束,位置已保存');
            } else {
                // 没有进入拖拽模式,处理点击事件
                if (!hasMoved && clickDuration < 500) {
                    debugLog('单击 - 切换面板');
                    togglePanel();
                }
            }
        }

        // 显示/隐藏面板
        function togglePanel() {
            isExpanded = !isExpanded;

            if (isExpanded) {
                showPanel();
            } else {
                hidePanel();
            }
        }

        function showPanel() {
            panel.style.pointerEvents = 'auto';
            panel.style.opacity = '1';

            // 根据球的位置调整面板位置
            const ballRect = ball.getBoundingClientRect();
            const panelWidth = 240;
            const panelHeight = 320;

            let panelLeft, panelTop;

            // 水平位置判断
            if (ballRect.left + ballRect.width + panelWidth + 20 <= window.innerWidth) {
                // 球的右侧有足够空间
                panelLeft = ballRect.right + 10;
                panel.style.transformOrigin = 'left center';
                panel.style.transform = 'translateY(-50%) scale(1) translateX(0)';
            } else {
                // 球的左侧显示
                panelLeft = ballRect.left - panelWidth - 10;
                panel.style.transformOrigin = 'right center';
                panel.style.transform = 'translateY(-50%) scale(1) translateX(0)';
            }

            // 垂直位置调整
            panelTop = ballRect.top + ballRect.height / 2;

            // 确保面板不超出屏幕
            if (panelTop + panelHeight / 2 > window.innerHeight) {
                panelTop = window.innerHeight - panelHeight / 2 - 10;
            }
            if (panelTop - panelHeight / 2 < 0) {
                panelTop = panelHeight / 2 + 10;
            }

            panel.style.left = Math.max(10, panelLeft) + 'px';
            panel.style.top = panelTop + 'px';
            panel.style.right = 'auto';
        }

        function hidePanel() {
            panel.style.pointerEvents = 'none';
            panel.style.opacity = '0';
            panel.style.transform = 'translateY(-50%) scale(0) translateX(-20px)';
            isExpanded = false;
        }

        // 全局鼠标释放事件(备用)
        document.addEventListener('mouseup', handleMouseUp);
    }

    // 设置面板事件
    function setupPanelEvents() {
        const toggle = document.getElementById('autoReadToggle');
        const testButton = document.getElementById('testReadButton');
        const resetPosButton = document.getElementById('resetPosButton');
        const clearConfigButton = document.getElementById('clearConfigButton');
        const delaySlider = document.getElementById('delaySlider');

        // 启用/禁用切换
        toggle.addEventListener('change', function() {
            CONFIG.enabled = this.checked;
            saveConfig(); // 保存配置

            const status = document.getElementById('autoReadStatus');
            status.textContent = CONFIG.enabled ? '启用' : '禁用';
            status.style.color = CONFIG.enabled ? '#10a37f' : '#dc2626';

            const ball = document.getElementById('autoReadBall');
            ball.style.background = CONFIG.enabled ? 'rgba(16, 163, 127, 0.8)' : 'rgba(128, 128, 128, 0.8)';
            ball.style.borderColor = CONFIG.enabled ? 'rgba(16, 163, 127, 1)' : 'rgba(128, 128, 128, 1)';

            // 更新CSS样式
            const hoverStyle = document.querySelector('style').textContent;
            const newHoverStyle = hoverStyle.replace(
                /background: rgba\([\d, ]+\) !important;/,
                `background: ${CONFIG.enabled ? 'rgba(16, 163, 127, 1)' : 'rgba(128, 128, 128, 1)'} !important;`
            ).replace(
                /box-shadow: 0 3px 12px rgba\([\d, ]+\);/,
                `box-shadow: 0 3px 12px ${CONFIG.enabled ? 'rgba(16, 163, 127, 0.4)' : 'rgba(128, 128, 128, 0.4)'};`
            );
            document.querySelector('style').textContent = newHoverStyle;

            debugLog(`自动朗读已${CONFIG.enabled ? '启用' : '禁用'}`);
            showNotification(CONFIG.enabled ? '✅ 已启用自动朗读' : '❌ 已禁用自动朗读');
        });

        // 延迟时间滑块
        delaySlider.addEventListener('input', function() {
            updateDelay(this.value);
        });

        // 测试朗读按钮
        testButton.addEventListener('click', function() {
            debugLog('手动测试朗读');
            const lastMessage = findLatestAssistantMessage();
            if (lastMessage) {
                clickReadAloudButton(lastMessage, true);
            } else {
                showNotification('❌ 未找到assistant消息');
            }
        });

        // 重置位置按钮
        resetPosButton.addEventListener('click', function() {
            resetPosition();
        });

        // 清除所有设置按钮
        clearConfigButton.addEventListener('click', function() {
            if (confirm('确定要清除所有设置吗?这将重置为默认配置。')) {
                // 清除LocalStorage
                localStorage.removeItem('chatgpt_auto_read_config');
                localStorage.removeItem('chatgpt_auto_read_position');
                
                showNotification('🗑️ 所有设置已清除,请刷新页面');
                debugLog('所有设置已清除');
                
                // 延迟刷新页面
                setTimeout(() => {
                    location.reload();
                }, 1500);
            }
        });

        // 点击面板外部收起
        document.addEventListener('click', function(e) {
            const panel = document.getElementById('autoReadPanel');
            const ball = document.getElementById('autoReadBall');

            if (!panel.contains(e.target) && !ball.contains(e.target) && !isDragging && isExpanded) {
                panel.style.pointerEvents = 'none';
                panel.style.opacity = '0';
                panel.style.transform = 'translateY(-50%) scale(0) translateX(-20px)';
                isExpanded = false;
            }
        });
    }

    // 查找最新的assistant消息
    function findLatestAssistantMessage() {
        const assistantMessages = document.querySelectorAll('[data-message-author-role="assistant"]');

        if (assistantMessages.length > 0) {
            const latestMessage = assistantMessages[assistantMessages.length - 1];
            debugLog(`找到 ${assistantMessages.length} 个assistant消息,最新ID: ${latestMessage.getAttribute('data-message-id')}`);
            return latestMessage;
        }

        debugLog('未找到assistant消息');
        return null;
    }

    // 简化的消息完成判断
    function isMessageComplete(messageElement) {
        const hasContent = messageElement.querySelector('.markdown, .prose, p, div[data-start]');
        if (!hasContent) {
            debugLog('消息无内容');
            return false;
        }

        const loadingSelectors = [
            '.animate-pulse',
            '.loading',
            '.spinner',
            '[class*="animate-spin"]',
            '[class*="animate-bounce"]',
            '.cursor-blink'
        ];

        for (let selector of loadingSelectors) {
            if (messageElement.querySelector(selector)) {
                debugLog(`发现加载指示器: ${selector}`);
                return false;
            }
        }

        const generatingIndicators = [
            '[data-testid*="stop"]',
            'button[aria-label*="Stop"]',
            '.text-generating',
            '[aria-label*="generating"]'
        ];

        for (let selector of generatingIndicators) {
            if (document.querySelector(selector)) {
                debugLog(`发现全局生成指示器: ${selector}`);
                return false;
            }
        }

        const inputBox = document.querySelector('textarea[placeholder*="Message"], textarea[data-testid*="prompt"]');
        if (inputBox && inputBox.disabled) {
            debugLog('输入框被禁用,可能正在生成');
            return false;
        }

        const sendButton = document.querySelector('[data-testid="send-button"], button[aria-label*="Send"]');
        if (sendButton && sendButton.disabled) {
            debugLog('发送按钮被禁用,可能正在生成');
            return false;
        }

        debugLog('消息判断为已完成');
        return true;
    }

    // 查找并点击朗读按钮
    function clickReadAloudButton(messageElement, isManualTest = false) {
        if (!CONFIG.enabled && !isManualTest) {
            debugLog('自动朗读已禁用,跳过');
            return false;
        }

        const articleElement = messageElement.closest('article');
        if (!articleElement) {
            debugLog('未找到article容器');
            showNotification('❌ 未找到消息容器');
            return false;
        }

        const buttonContainer = articleElement.querySelector('.group\\/turn-messages, [class*="turn-action"], .flex.min-h-\\[46px\\]');
        if (buttonContainer) {
            buttonContainer.style.pointerEvents = 'auto';
            buttonContainer.style.opacity = '1';
            buttonContainer.style.maskPosition = '0% 0%';
            buttonContainer.style.maskImage = 'none';
            buttonContainer.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
        }

        setTimeout(() => {
            let readButton = null;

            readButton = articleElement.querySelector('[data-testid="voice-play-turn-action-button"]');

            if (!readButton) {
                readButton = articleElement.querySelector('[aria-label="Read aloud"]');
            }

            if (!readButton) {
                const buttons = articleElement.querySelectorAll('button');
                for (let button of buttons) {
                    const svg = button.querySelector('svg path[d*="11 4.9099"], svg path[d*="10.1621 4.54132"]');
                    if (svg) {
                        readButton = button;
                        break;
                    }
                }
            }

            if (!readButton) {
                const actionButtons = articleElement.querySelectorAll('button[aria-label]');
                if (actionButtons.length >= 4) {
                    const fourthButton = actionButtons[3];
                    if (fourthButton.querySelector('svg path[fill-rule="evenodd"]')) {
                        readButton = fourthButton;
                    }
                }
            }

            if (!readButton) {
                debugLog('未找到朗读按钮');
                showNotification('❌ 未找到朗读按钮');
                return false;
            }

            debugLog(`找到朗读按钮: ${readButton.getAttribute('aria-label') || '未知'}`);

            try {
                readButton.click();
                debugLog('已点击朗读按钮');

                const lastProcessedSpan = document.getElementById('lastProcessed');
                if (lastProcessedSpan) {
                    lastProcessedSpan.textContent = new Date().toLocaleTimeString();
                }

                // 圆球闪烁效果
                const ball = document.getElementById('autoReadBall');
                const originalBg = ball.style.background;
                ball.style.background = CONFIG.enabled ? 'rgba(16, 163, 127, 1)' : 'rgba(128, 128, 128, 1)';
                ball.style.boxShadow = `0 0 15px ${CONFIG.enabled ? 'rgba(16, 163, 127, 0.8)' : 'rgba(128, 128, 128, 0.8)'}`;
                setTimeout(() => {
                    ball.style.background = originalBg;
                    ball.style.boxShadow = '0 2px 8px rgba(0,0,0,0.3)';
                }, 500);

                showNotification(isManualTest ? '🔊 手动测试朗读' : '🔊 自动朗读已启动');
                return true;
            } catch (e) {
                debugLog('点击失败: ' + e.message);
                showNotification('❌ 点击朗读按钮失败');
                return false;
            }

        }, 200);
    }

    // 显示通知
    function showNotification(message) {
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: ${message.includes('❌') ? '#dc2626' : message.includes('⚠️') ? '#d97706' : '#10a37f'};
            color: white;
            padding: 8px 16px;
            border-radius: 18px;
            z-index: 10001;
            font-size: 12px;
            font-family: -apple-system, BlinkMacSystemFont, sans-serif;
            opacity: 0;
            transform: translateX(-50%) translateY(-20px);
            transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
            box-shadow: 0 4px 20px rgba(0,0,0,0.3);
            backdrop-filter: blur(10px);
            max-width: 250px;
            text-align: center;
        `;

        notification.textContent = message;
        document.body.appendChild(notification);

        setTimeout(() => {
            notification.style.opacity = '1';
            notification.style.transform = 'translateX(-50%) translateY(0)';
        }, 100);

        setTimeout(() => {
            notification.style.opacity = '0';
            notification.style.transform = 'translateX(-50%) translateY(-20px)';
            setTimeout(() => {
                if (document.body.contains(notification)) {
                    document.body.removeChild(notification);
                }
            }, 300);
        }, 2500);
    }

    // 处理新消息
    function handleNewMessage() {
        const latestMessage = findLatestAssistantMessage();

        if (!latestMessage) {
            return;
        }

        const messageId = latestMessage.getAttribute('data-message-id');

        if (!messageId || messageId === lastProcessedMessageId) {
            return;
        }

        debugLog(`检测到新的assistant消息: ${messageId}`);

        if (checkTimer) {
            clearTimeout(checkTimer);
        }

        let checkCount = 0;
        const maxChecks = CONFIG.maxWaitTime / 500;

        const checkComplete = () => {
            checkCount++;

            if (checkCount > maxChecks) {
                debugLog('超时,强制执行朗读');
                lastProcessedMessageId = messageId;
                clickReadAloudButton(latestMessage);
                return;
            }

            if (isMessageComplete(latestMessage)) {
                debugLog(`消息生成完成(检查了${checkCount}次),准备自动朗读`);
                lastProcessedMessageId = messageId;

                setTimeout(() => {
                    clickReadAloudButton(latestMessage);
                }, CONFIG.delay);
            } else {
                debugLog(`消息仍在生成中(第${checkCount}次检查),继续等待...`);
                checkTimer = setTimeout(checkComplete, 500);
            }
        };

        checkComplete();
    }

    // 初始化DOM监听
    function initObserver() {
        if (observer) {
            observer.disconnect();
        }

        observer = new MutationObserver((mutations) => {
            let shouldCheck = false;

            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    for (let node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.querySelector && node.querySelector('[data-message-author-role="assistant"]')) {
                                shouldCheck = true;
                                break;
                            }
                        }
                    }
                }

                if (mutation.type === 'attributes' &&
                    mutation.target.closest &&
                    mutation.target.closest('[data-message-author-role="assistant"]')) {
                    shouldCheck = true;
                }
            });

            if (shouldCheck) {
                setTimeout(handleNewMessage, 300);
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['data-is-last-node', 'data-message-id', 'data-state', 'class']
        });

        debugLog('DOM监听器已启动');
    }

    // 初始化脚本
    function init() {
        debugLog('ChatGPT自动朗读脚本已启动 v1.9 - 作者: schweigen');
        debugLog(`当前配置: 启用=${CONFIG.enabled}, 延迟=${CONFIG.delay}ms`);

        const checkInterface = () => {
            if (document.querySelector('article, [data-testid*="conversation"]')) {
                debugLog('ChatGPT界面已加载');
                createFloatingBall();
                initObserver();
                setTimeout(handleNewMessage, 2000);
            } else {
                debugLog('等待ChatGPT界面加载...');
                setTimeout(checkInterface, 1000);
            }
        };

        checkInterface();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();