ReadSense

v6.5 双仓库推送:新增 GitHub HTML 归档推送功能(支持 moodHappy.gitHub.io-nce),保留原有 Markdown 笔记同步;优化交互逻辑。

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         ReadSense
// @namespace    http://tampermonkey.net/
// @version      6.5
// @description  v6.5 双仓库推送:新增 GitHub HTML 归档推送功能(支持 moodHappy.gitHub.io-nce),保留原有 Markdown 笔记同步;优化交互逻辑。
// @author       Hal
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_setClipboard
// @grant        GM_registerMenuCommand
// @connect      api.groq.com
// @connect      api.moonshot.cn
// @connect      open.bigmodel.cn
// @connect      api.deepseek.com
// @connect      api.openai.com
// @connect      api.github.com
// @connect      translate.googleapis.com
// @connect      api.dictionaryapi.dev
// @connect      dict.youdao.com
// @connect      libre-tts-nu.vercel.app
// @connect      *
// @require      https://cdn.jsdelivr.net/npm/@mozilla/[email protected]/Readability.min.js
// @require      https://unpkg.com/@popperjs/core@2
// @require      https://unpkg.com/tippy.js@6
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // =========================================================================
    // [0] 全局配置与默认值
    // =========================================================================
    const GLOBAL_CONFIG = {
        AI_TEMPLATES: {
            GROQ: { name: 'Groq', url: 'https://api.groq.com/openai/v1/chat/completions', model: 'llama-3.3-70b-versatile' },
            KIMI: { name: 'Kimi', url: 'https://api.moonshot.cn/v1/chat/completions', model: 'moonshot-v1-8k' },
            ZHIPU: { name: 'Zhipu', url: 'https://open.bigmodel.cn/api/paas/v4/chat/completions', model: 'GLM-4.5-Flash' },
            DEEPSEEK: { name: 'DeepSeek', url: 'https://api.deepseek.com/chat/completions', model: 'deepseek-chat' },
            OPENAI: { name: 'OpenAI', url: 'https://api.openai.com/v1/chat/completions', model: 'gpt-4o-mini' }
        },
        XIAOBEI_CONFIG: {
            id: 'zh-CN-liaoning-XiaobeiNeural',
            api: "https://libre-tts-nu.vercel.app/api/tts",
            voice: "zh-CN-liaoning-XiaobeiNeural"
        },
        VOICES: {
            CN: [
                { id: 'zh-CN-XiaoxiaoNeural', name: '晓晓 (默认/温暖)' },
                { id: 'zh-CN-liaoning-XiaobeiNeural', name: '辽宁口音- 晓北(女)' },
                { id: 'zh-CN-YunxiNeural', name: '云希 (男声/稳重)' },
                { id: 'zh-CN-YunjianNeural', name: '云健 (男声/体育)' }
            ],
            EN: [
                { id: 'en-US-JennyNeural', name: 'Jenny (默认/亲切)' },
                { id: 'en-US-EricNeural', name: 'Eric (男声/通用)' },
                { id: 'en-US-GuyNeural', name: 'Guy (男声/新闻)' },
                { id: 'en-US-AnaNeural', name: 'Ana (女声/孩童)' },
                { id: 'en-US-ChristopherNeural', name: 'Christopher (男声/大师)' },
                { id: 'en-US-MichelleNeural', name: 'Michelle (女声/专业)' }
            ]
        }
    };

    // --- 核心工具:TTS 请求构建 ---
    function buildTTSRequest(serverUrl, text, voiceName, userToken) {
        if (!serverUrl) return null;
        const cleanUrl = serverUrl.replace(/\/$/, '');
        const isLibreType = cleanUrl.includes('/api/tts') || cleanUrl.includes('libre-tts');
        
        if (isLibreType) {
            const params = new URLSearchParams({ t: text, v: voiceName });
            return { url: `${cleanUrl}?${params.toString()}`, headers: {} };
        } else {
            const token = userToken && userToken.trim() !== '' ? userToken : "";
            const params = new URLSearchParams({ text: text, voiceName: voiceName, token: token, rate: '+0%', pitch: '0Hz' });
            let headers = {};
            if (cleanUrl.includes('vercel.app')) { headers = { "Referer": "https://read-aloud-123.vercel.app/" }; }
            return { url: `${cleanUrl}?${params.toString()}`, headers: headers };
        }
    }

    // --- 样式:字典弹窗 ---
    const DICT_SHADOW_CSS = `
        :host { all: initial; display: block; }
        .dict-popup { text-align: left; font-size: 14px; line-height: 1.4; max-width: 280px; color: #333; padding: 5px; font-family: sans-serif; background: #fff; }
        .dict-header-row { border-bottom: 1px solid #eee; padding-bottom: 6px; margin-bottom: 6px; }
        .dict-word-line { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
        .dict-head-word { font-weight: bold; color: #D32F2F; font-size: 18px; }
        .dict-ipa { font-family: "Lucida Sans Unicode", "Arial Unicode MS", sans-serif; color: #666; font-size: 13px; background: #f0f0f0; padding: 0 4px; border-radius: 4px; }
        .dict-speaker-btn { display: inline-flex; cursor: pointer; color: #1976D2; padding: 2px; border-radius: 50%; transition: background 0.2s; align-items: center; justify-content: center; }
        .dict-speaker-btn:hover { background-color: rgba(25, 118, 210, 0.1); }
        .dict-speaker-btn.playing { color: #E91E63; animation: pulse 1s infinite; }
        .dict-speaker-btn svg { width: 16px; height: 16px; pointer-events: none; }
        .dict-basic-trans { font-size: 14px; color: #444; margin-top: 4px;}
        .dict-details { margin-top: 8px; }
        .dict-pos-block { margin-bottom: 4px; }
        .dict-pos-tag { font-style: italic; color: #1976D2; font-weight: bold; font-size: 12px; margin-right: 4px; }
        .dict-def-list { margin: 0; padding: 0; list-style: none; display: inline; }
        .dict-def-item { display: inline; }
        .dict-def-item::after { content: "; "; color: #999; }
        .dict-def-item:last-child::after { content: ""; }
        .dict-loading { color: #666; font-style: italic; font-size: 12px; display: flex; align-items: center; gap: 5px;}
        @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } }
    `;

    // --- 共享字典帮助类 ---
    const DictHelper = {
        async fetchTranslation(text) {
            const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=zh-CN&dt=t&dt=bd&q=${encodeURIComponent(text)}`;
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET", url: url,
                    onload: (res) => { if (res.status === 200) resolve(JSON.parse(res.responseText)); else reject(); },
                    onerror: reject
                });
            });
        },
        async fetchPhonetics(text) {
            const url = `https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(text)}`;
            return new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: "GET", url: url,
                    onload: (res) => {
                        try {
                            const data = JSON.parse(res.responseText);
                            if (data && data.length > 0) {
                                const phoneticObj = data[0].phonetics.find(p => p.text) || data[0];
                                resolve(phoneticObj.text || null);
                            } else resolve(null);
                        } catch (e) { resolve(null); }
                    },
                    onerror: () => resolve(null)
                });
            });
        },
        playLocalTTS(text, onEndCallback, langHint) {
            if (!window.speechSynthesis) return false;
            window.speechSynthesis.cancel();
            const u = new SpeechSynthesisUtterance(text);
            const isChinese = langHint === 'zh' || /[\u4e00-\u9fa5]/.test(text);
            u.lang = isChinese ? 'zh-CN' : 'en-US';
            u.rate = 1.0;
            u.onend = () => { if (onEndCallback) onEndCallback(); };
            u.onerror = () => { if (onEndCallback) onEndCallback(); };
            window.speechSynthesis.speak(u);
            return true;
        },
        playAudio(text, btnElement) {
            if (!text) return;
            if (btnElement) btnElement.classList.add('playing');
            const voice = GM_getValue('ttsVoiceEN', 'en-US-JennyNeural');
            window.TTSServiceInstance.play(text, false, () => {
                 if (btnElement) btnElement.classList.remove('playing');
            }, voice); 
        },
        formatDictData(originalWord, transData, ipaText) {
            let html = `<div class="dict-popup">`;
            const basicTrans = transData[0] && transData[0][0] ? transData[0][0][0] : '';
            html += `<div class="dict-header-row"><div class="dict-word-line"><span class="dict-head-word">${originalWord}</span>${ipaText ? `<span class="dict-ipa">${ipaText}</span>` : ''}<span class="dict-speaker-btn" data-word="${originalWord}" title="Pronounce"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg></span></div><div class="dict-basic-trans">${basicTrans}</div></div>`;
            const dictEntries = transData[1];
            if (dictEntries && dictEntries.length > 0) {
                html += `<div class="dict-details">`;
                dictEntries.forEach(entry => {
                    html += `<div class="dict-pos-block"><span class="dict-pos-tag">${entry[0]}.</span><ul class="dict-def-list">`;
                    entry[1].slice(0, 3).forEach(def => { html += `<li class="dict-def-item">${def}</li>`; });
                    html += `</ul></div>`;
                });
                html += `</div>`;
            } else { if (!basicTrans) html += `<div style="padding:5px">No definition found.</div>`; }
            html += `</div>`;
            return html;
        },
        initDictionary(container, parentModalId = null) {
            const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null, false);
            const nodesToProcess = [];
            while (walker.nextNode()) {
                const node = walker.currentNode;
                if (node.parentElement && (node.parentElement.tagName === 'CODE' || node.parentElement.tagName === 'A' || node.parentElement.tagName === 'BUTTON' || node.parentElement.className.includes('dict-'))) continue;
                if (node.nodeValue.trim().length > 0) nodesToProcess.push(node);
            }
            nodesToProcess.forEach(node => {
                const text = node.nodeValue;
                if (/[a-zA-Z]/.test(text)) {
                    const fragment = document.createDocumentFragment();
                    const parts = text.split(/([a-zA-Z][a-zA-Z'-]*)/g);
                    parts.forEach(part => {
                        if (/^[a-zA-Z][a-zA-Z'-]*$/.test(part)) {
                            const span = document.createElement('span');
                            span.className = 'anki-word notranslate';
                            span.setAttribute('translate', 'no');
                            span.textContent = part;
                            fragment.appendChild(span);
                        } else {
                            fragment.appendChild(document.createTextNode(part));
                        }
                    });
                    node.parentNode.replaceChild(fragment, node);
                }
            });

            if (window.tippy) {
                tippy(container.querySelectorAll('.anki-word'), {
                    trigger: 'click',
                    interactive: true,
                    theme: 'light-border',
                    placement: 'bottom',
                    animation: 'shift-away',
                    appendTo: parentModalId ? (document.getElementById(parentModalId) || document.body) : document.body,
                    allowHTML: false,
                    maxWidth: 300,
                    zIndex: 100000,
                    onShow(instance) {
                        const word = instance.reference.innerText.trim();
                        const host = document.createElement('div');
                        const shadow = host.attachShadow({ mode: 'open' });
                        const style = document.createElement('style'); style.textContent = DICT_SHADOW_CSS; shadow.appendChild(style);
                        const contentContainer = document.createElement('div'); contentContainer.className = 'dict-popup'; contentContainer.innerHTML = '<span class="dict-loading">🔍 Analyzing...</span>'; shadow.appendChild(contentContainer);
                        instance.setContent(host);
                        Promise.all([
                            DictHelper.fetchTranslation(word).catch(e => [[['Error']]]),
                            DictHelper.fetchPhonetics(word)
                        ]).then(([transData, ipaText]) => {
                            contentContainer.innerHTML = DictHelper.formatDictData(word, transData, ipaText);
                            const speakerBtn = shadow.querySelector('.dict-speaker-btn');
                            if (speakerBtn) { speakerBtn.addEventListener('click', (e) => { e.stopPropagation(); DictHelper.playAudio(word, speakerBtn); }); }
                        });
                    }
                });
            }
        }
    };

    // =========================================================================
    // [1] MODULE: DeepRead (全网通用功能 & 统一设置中心)
    // =========================================================================
    function initDeepRead() {
        console.log('✅ DeepRead Module Loading (v6.5-DualRepo)...');

        // --- 常量配置 ---
        const CONSTANTS = {
            CACHE_PREFIX: 'deepread_v70_',
            HISTORY_KEY: 'deepread_last_text_pointer',
            CUSTOM_STYLES_KEY: 'deepread_custom_styles_list',
            AI_SERVICES_KEY: 'deepread_ai_services_list',
            ACTIVE_AI_ID_KEY: 'deepread_active_ai_id',
            // Task Allocation Keys
            TASK_ANALYSIS_ID: 'deepread_task_analysis_id',
            TASK_SUMMARY_ID: 'deepread_task_summary_id',
            TASK_TWITTER_ID: 'deepread_task_twitter_id',
            TASK_TRANSLATE_ID: 'deepread_task_translate_id',
            
            CACHE_EXPIRY: 24 * 60 * 60 * 1000,
            DEFAULT_STYLES: {
                SIMPLE: {
                    label: '📝 简练',
                    prompt: `Please summarize the content in English using simple words (CEFR B1 level). Start with "Title: ...". Structure with bullet points.`
                }
            }
        };

        const DEFAULT_SETTINGS = {
            enableFloatBtn: true,
            twitterTranslationType: 'AI',
            enableFailover: true,
            // GitHub Config 1 (MD Note)
            githubToken: '',
            githubUser: 'moodHappy',
            githubRepo: 'HelloWorld',
            githubPath: 'Notes/B1.md',
            // GitHub Config 2 (HTML Archive)
            githubUser2: 'moodHappy',
            githubRepo2: 'moodHappy.gitHub.io-nce',
            githubPath2: 'danmu/',

            ttsVoiceCN: 'zh-CN-XiaoxiaoNeural',
            ttsVoiceEN: 'en-US-JennyNeural',
            ttsSources: [], 
            defaultSummaryStyle: 'SIMPLE',
            twitterPrompt: `你是一位精通中英文的语言专家。请分析我提供的句子:\n1. 判断难度等级 (A1-C2)。\n2. 提供准确、优美的中文翻译。\n3. 句中关键短语及例句、例句翻译。\n请使用 Markdown 格式输出。`,
            promptAnalyze: `你是一个智能助手,请用中文分析下面的内容。请根据内容类型(单词或句子)按以下要求进行分析:\n\n如果是**句子或段落**,请:\n1. 给出难度等级(A1-C2)并解释\n2. 核心语法结构分析\n3. 准确翻译\n4. 重点短语及例句和例句翻译\n\n如果是**单词**,请:\n1. 音标及发音提示\n2. 详细释义及词性\n3. 常用搭配和例句\n4. 记忆技巧(如有)\n\n用 **加粗** 标出重点内容,保持回答简洁实用。`,
            promptSummaryCN: `You are a helpful assistant. Please summarize the following webpage content in Chinese. Use Simplified Chinese. Structure it clearly with headings (use ## Title) and bullet points.`
        };

        // --- 工具函数 ---
        const Utils = {
            getSetting(key) { return GM_getValue(key, DEFAULT_SETTINGS[key]); },
            setSetting(key, value) { GM_setValue(key, value); },
            getAllStyles() {
                const defaults = CONSTANTS.DEFAULT_STYLES;
                const customs = GM_getValue(CONSTANTS.CUSTOM_STYLES_KEY, []);
                let merged = {};
                for (let key in defaults) { merged[key] = defaults[key]; }
                if(Array.isArray(customs)) {
                    customs.forEach(style => { merged[style.id] = { label: '✨ ' + style.label, prompt: style.prompt, isCustom: true }; });
                }
                return merged;
            },
            getAIServices() { return GM_getValue(CONSTANTS.AI_SERVICES_KEY, []); },
            setAIServices(list) { GM_setValue(CONSTANTS.AI_SERVICES_KEY, list); },
            getActiveAIId() { return GM_getValue(CONSTANTS.ACTIVE_AI_ID_KEY, ''); },
            setActiveAIId(id) { GM_setValue(CONSTANTS.ACTIVE_AI_ID_KEY, id); },
            
            getServiceIdForTask(taskType) {
                const map = {
                    'ANALYSIS': CONSTANTS.TASK_ANALYSIS_ID,
                    'SUMMARY': CONSTANTS.TASK_SUMMARY_ID,
                    'TWITTER': CONSTANTS.TASK_TWITTER_ID,
                    'TRANSLATE': CONSTANTS.TASK_TRANSLATE_ID
                };
                return GM_getValue(map[taskType], '') || this.getActiveAIId();
            },

            cleanMarkdownForTTS(text) {
                if (!text) return '';
                return text.replace(/\*\*(.*?)\*\*/g, '$1').replace(/\*(.*?)\*/g, '$1').replace(/[*#>`~-]/g, '').replace(/\[(.*?)\]\(.*?\)/g, '$1').replace(/\d+\.\s/g, '').replace(/\n+/g, ',').replace(/\s+/g, ' ').trim();
            },
            escapeHTML(str) {
                if (!str) return '';
                return str.replace(/[&<>"']/g, m => ({
                    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
                })[m]);
            },
            renderMarkdownToHTML(text) {
                if (!text) return '';
                let safeText = this.escapeHTML(text);
                return safeText
                    .replace(/\r\n/g, '\n')
                    .replace(/^###\s*(.*$)/gm, '<h4 style="margin:12px 0 6px; color:#444;">$1</h4>')
                    .replace(/^##\s*(.*$)/gm, '<h3 style="margin:18px 0 10px; color:#333; border-bottom:1px solid #eee; padding-bottom:5px;">$1</h3>')
                    .replace(/^#\s*(.*$)/gm, '<h2 style="font-size:1.3em; margin:15px 0;">$1</h2>')
                    .replace(/\*\*\s*(.*?)\s*\*\*/g, '<strong style="color: #d35400;">$1</strong>')
                    .replace(/\*\s*(.*?)\s*\*\*/g, '<em style="color: #2980b9;">$1</em>')
                    .replace(/^\s*[\-\*]\s+(.*$)/gm, '<div class="dr-list-row"><span class="dr-list-icon"></span><div class="dr-list-text">$1</div></div>')
                    .replace(/^\s*\d+\.\s+(.*$)/gm, '<div class="dr-list-header">$1</div>')
                    .replace(/\n/g, '<br>');
            },
            debounce(func, wait) {
                let timeout;
                return function (...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); };
            },
            getCacheKey(type, id) { return CONSTANTS.CACHE_PREFIX + type + '_' + btoa(encodeURIComponent(id)); },

            generateHTMLContent(title, url, summaryContent) {
                const date = new Date().toLocaleString();
                const htmlContent = this.renderMarkdownToHTML(summaryContent);
                return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${this.escapeHTML(title)} - ReadSense Archive</title>
    <style>
        body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; color: #333; }
        h1 { border-bottom: 2px solid #3498db; padding-bottom: 10px; color: #2c3e50; }
        .meta { font-size: 14px; color: #7f8c8d; margin-bottom: 25px; background: #f9f9f9; padding: 10px; border-radius: 6px; }
        .meta a { color: #3498db; text-decoration: none; word-break: break-all; }
        .meta a:hover { text-decoration: underline; }
        .content { background: #fff; }
        h2, h3 { color: #2c3e50; margin-top: 25px; }
        .footer { margin-top: 40px; font-size: 12px; color: #bdc3c7; text-align: center; border-top: 1px solid #eee; padding-top: 20px; }
    </style>
</head>
<body>
    <h1>${this.escapeHTML(title)}</h1>
    <div class="meta">
        <div>📅 归档时间: ${date}</div>
        <div>🔗 原文链接: <a href="${this.escapeHTML(url)}" target="_blank">${this.escapeHTML(url)}</a></div>
    </div>
    <div class="content">
        ${htmlContent}
    </div>
    <div class="footer">
        Generated by ReadSense UserScript
    </div>
</body>
</html>`;
            }
        };

        // --- 应用状态 ---
        const AppState = {
            currentAudio: null, isPlaying: false, isModalOpen: false, analysisText: '',
            lastScrollY: 0, currentSummaryLang: 'en', tempCustomStyles: [],
            tempDefaultStyle: 'SIMPLE', editingStyleId: null,
            tempAIServices: [], tempActiveAIId: '',
            tempTaskAllocations: { ANALYSIS: '', SUMMARY: '', TWITTER: '', TRANSLATE: '' }
        };

        // --- LLM API 服务 ---
        const LLMService = {
            async request(serviceConfig, messages) {
                if (!serviceConfig || !serviceConfig.key) throw new Error(`${serviceConfig.name || 'AI'} Key 未配置`);
                return new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: 'POST', url: serviceConfig.url,
                        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${serviceConfig.key}` },
                        data: JSON.stringify({ model: serviceConfig.model, messages: messages, stream: false }),
                        timeout: 60000,
                        onload: (res) => {
                            if (res.status === 200) { try { resolve(JSON.parse(res.responseText).choices[0]?.message?.content || '无内容'); } catch (e) { reject(new Error('解析失败')); } }
                            else { reject(new Error(`HTTP ${res.status}: ${res.responseText}`)); }
                        },
                        onerror: () => reject(new Error('网络错误'))
                    });
                });
            },
            async analyzeWithFailover(text, systemPromptKey = 'promptAnalyze', preferredServiceId = null) {
                const services = Utils.getAIServices();
                const globalActiveId = Utils.getActiveAIId();
                if (!services || services.length === 0) throw new Error("请先在设置中添加 AI 服务");

                const sortedServices = services.sort((a, b) => {
                    if (preferredServiceId && a.id === preferredServiceId) return -1;
                    if (preferredServiceId && b.id === preferredServiceId) return 1;
                    if (a.id === globalActiveId) return -1;
                    if (b.id === globalActiveId) return 1;
                    return 0;
                });

                let lastError = null;
                for (const svc of sortedServices) {
                    if (!Utils.getSetting('enableFailover') && svc !== sortedServices[0]) break;
                    try {
                        UIManager.updateStatus(`🤖 ${svc.name} 思考中...`);
                        const messages = [{ role: 'system', content: Utils.getSetting(systemPromptKey) }, { role: 'user', content: text }];
                        const result = await this.request(svc, messages);
                        return { content: result, service: svc.name, serviceId: svc.id };
                    } catch (e) {
                        lastError = e;
                        UIManager.updateStatus(`⚠️ ${svc.name} 失败,尝试下一个...`);
                    }
                }
                throw lastError || new Error('所有 AI 服务均请求失败');
            }
        };

        // --- TTS 语音服务 (隐式绑定逻辑) ---
        const TTSService = {
            currentBlobUrl: null, 

            async play(text, isChinese, onFinishCallback = null, overrideVoice = null) {
                if (AppState.isPlaying) { this.stop(); return; }
                const cleanText = Utils.cleanMarkdownForTTS(text);
                if (!cleanText) return alert('无朗读内容');
                
                AppState.isPlaying = true; 
                UIManager.updateTTSButton(true);

                const selectedVoice = overrideVoice || (isChinese ? Utils.getSetting('ttsVoiceCN') : Utils.getSetting('ttsVoiceEN'));
                const isXiaobeiId = selectedVoice === GLOBAL_CONFIG.XIAOBEI_CONFIG.id;

                if (isChinese && isXiaobeiId) {
                     const api = GLOBAL_CONFIG.XIAOBEI_CONFIG.api;
                     const voiceId = GLOBAL_CONFIG.XIAOBEI_CONFIG.voice;
                     const params = new URLSearchParams({ t: cleanText, v: voiceId, r: 0, p: 0 });
                     
                     const audio = new Audio(`${api}?${params.toString()}`);
                     AppState.currentAudio = audio;
                     audio.onended = () => { this.stop(); if(onFinishCallback) onFinishCallback(); };
                     audio.onerror = (e) => { this.stop(); alert("晓北通道连接失败"); };
                     audio.play().catch(e => { this.stop(); });
                     return; 
                }

                const sources = Utils.getSetting('ttsSources') || [];
                if (sources.length === 0) {
                     this.fallbackToLocal(cleanText, isChinese, onFinishCallback);
                     return;
                }

                let sourceIndex = 0;
                const self = this;

                function tryNextSource() {
                    if (sourceIndex >= sources.length) { self.fallbackToLocal(cleanText, isChinese, onFinishCallback); return; }
                    const currentSource = sources[sourceIndex];
                    const requestConfig = buildTTSRequest(currentSource.url, cleanText.substring(0, 1000), selectedVoice, currentSource.token);
                    if (!requestConfig) { sourceIndex++; tryNextSource(); return; }

                    GM_xmlhttpRequest({
                        method: "GET", url: requestConfig.url, headers: requestConfig.headers, responseType: "blob", timeout: 8000,
                        onload: function (response) {
                            if (response.status === 200 && response.response instanceof Blob && response.response.size > 0) {
                                if (self.currentBlobUrl) { URL.revokeObjectURL(self.currentBlobUrl); }
                                
                                const blobUrl = URL.createObjectURL(response.response);
                                self.currentBlobUrl = blobUrl;
                                const audio = new Audio(blobUrl);
                                AppState.currentAudio = audio;
                                audio.onended = () => { self.stop(); if(onFinishCallback) onFinishCallback(); };
                                audio.onerror = () => { sourceIndex++; tryNextSource(); };
                                audio.play().catch(e => { sourceIndex++; tryNextSource(); });
                            } else { sourceIndex++; tryNextSource(); }
                        }, 
                        onerror: function () { sourceIndex++; tryNextSource(); },
                        ontimeout: function () { sourceIndex++; tryNextSource(); }
                    });
                }
                tryNextSource();
            },
            async quickTest(url, token, btnElement) {
                if (!url) { alert('❌ URL 不能为空'); return; }
                const originalText = btnElement.innerHTML;
                btnElement.innerHTML = '⏳'; btnElement.disabled = true;
                const testConfig = buildTTSRequest(url, "Test", "en-US-JennyNeural", token);
                GM_xmlhttpRequest({
                    method: "GET", url: testConfig.url, headers: testConfig.headers, responseType: "blob", timeout: 5000,
                    onload: (response) => {
                        if (response.status === 200 && response.response instanceof Blob && response.response.size > 100) {
                            const blobUrl = URL.createObjectURL(response.response);
                            const audio = new Audio(blobUrl);
                            audio.play().then(() => {
                                btnElement.innerHTML = '✅'; 
                                setTimeout(() => { btnElement.innerHTML = originalText; btnElement.disabled = false; }, 2000);
                                URL.revokeObjectURL(blobUrl);
                            }).catch(() => {
                                btnElement.innerHTML = '⚠️';
                                alert('连接成功但播放被浏览器阻挡,请检查音频权限。');
                                btnElement.disabled = false;
                            });
                        } else {
                            btnElement.innerHTML = '❌'; btnElement.disabled = false;
                            alert(`请求失败 (Status: ${response.status})`);
                        }
                    },
                    onerror: () => { btnElement.innerHTML = '❌'; btnElement.disabled = false; alert('网络请求错误'); },
                    ontimeout: () => { btnElement.innerHTML = '❌'; btnElement.disabled = false; alert('请求超时'); }
                });
            },
            fallbackToLocal(text, isChinese, onFinishCallback) {
                const success = DictHelper.playLocalTTS(text, () => { this.stop(); if (onFinishCallback) onFinishCallback(); }, isChinese ? 'zh' : 'en');
                if (!success) { alert('TTS 服务不可用'); this.stop(); if (onFinishCallback) onFinishCallback(); }
            },
            stop() {
                if (AppState.currentAudio) { AppState.currentAudio.pause(); AppState.currentAudio = null; }
                if (window.speechSynthesis) window.speechSynthesis.cancel();
                if (this.currentBlobUrl) {
                    URL.revokeObjectURL(this.currentBlobUrl);
                    this.currentBlobUrl = null;
                }
                AppState.isPlaying = false; 
                UIManager.updateTTSButton(false);
            }
        };
        window.TTSServiceInstance = TTSService;

        // --- UI Manager ---
        GM_addStyle(`
            /* --- DeepRead UI Styles --- */
            .dr-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(4px); z-index: 99999; display: flex; justify-content: center; opacity: 0; pointer-events: none; transition: opacity 0.25s ease; align-items: center; }
            .dr-overlay.active { opacity: 1; pointer-events: auto; }
            .dr-modal { background: #fff; width: 600px; max-height: 80vh; border-radius: 16px; display: flex; flex-direction: column; box-shadow: 0 20px 60px rgba(0,0,0,0.3); transform: scale(0.98); transition: transform 0.2s ease, opacity 0.2s ease; touch-action: none; }
            .dr-overlay.active .dr-modal { transform: scale(1); }
            @media (max-width: 640px) { .dr-overlay { align-items: flex-start; } .dr-modal { width: 100%; margin-top: 0; top: 0; height: auto; max-height: 80vh; border-radius: 0 0 20px 20px; } }
            .dr-header { padding: 15px 20px; background: #fff; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; border-radius: 16px 16px 0 0; }
            .dr-title { font-size: 18px; font-weight: 700; color: #333; margin: 0; }
            .dr-select { margin-left: 12px; padding: 4px 8px; border-radius: 6px; border: 1px solid #ddd; font-size: 12px; color: #555; background: #fdfdfd; outline: none; cursor: pointer; max-width: 140px; }
            .dr-body { flex: 1; overflow-y: auto; background: #fff; position: relative; -webkit-overflow-scrolling: touch; touch-action: pan-y; }
            #dr-original-box { position: sticky; top: 0; z-index: 10; background: #fdfdfd; border-bottom: 3px solid #f0f0f0; padding: 8px 20px; display: none; }
            #dr-original-label { font-style: italic; color: #c9a24d; font-size: 12px; margin: 6px 0; }
            #dr-result-content { padding: 20px; font-size: 16px; line-height: 1.7; color: #2c3e50; }
            .dr-footer { padding: 25px 20px 15px; border-top: 1px solid #eee; background: #fff; display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap; flex-shrink: 0; border-radius: 0 0 16px 16px; touch-action: none; user-select: none; position: relative; }
            .dr-btn { padding: 10px 16px; border-radius: 8px; border: none; font-size: 14px; font-weight: 600; color: #fff; cursor: pointer; display: flex; align-items: center; gap: 5px; }
            .dr-btn-primary { background: #3498db; }
            .dr-btn-success { background: #27ae60; }
            .dr-btn-gray { background: #95a5a6; }
            .dr-btn-danger { background: #e74c3c; padding: 4px 8px; font-size:12px; }
            .dr-btn-edit { background: #f39c12; padding: 4px 8px; font-size:12px; margin-right: 5px; }
            .dr-btn-black { background: #333; }
            #dr-float-btn { position: fixed; right: 15px; top: 65%; transform: translateY(-50%); width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, #3498db, #8e44ad); color: white; border: none; font-size: 22px; box-shadow: 0 4px 15px rgba(0,0,0,0.3); z-index: 99990; display: flex; justify-content: center; align-items: center; cursor: pointer; opacity: 0; pointer-events: none; transform: translateY(-50%) scale(0.8); transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); }
            #dr-float-btn.visible { opacity: 1; pointer-events: auto; transform: translateY(-50%) scale(1); }
            .dr-input-group { margin-bottom: 12px; padding: 0 20px; }
            .dr-input-group label { display:block; margin-bottom:5px; font-weight:600; color:#555; font-size:12px; }
            .dr-input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box; }
            textarea.dr-input { font-family: monospace; line-height: 1.4; resize: vertical; min-height: 60px; overflow-y: hidden; }
            select.dr-input { background: #fff; }
            .dr-section-title { font-size: 15px; font-weight: 700; color: #333; border-left: 4px solid #3498db; padding-left: 10px; margin: 25px 20px 15px; cursor: pointer; user-select: none; }
            .ai-service-row { display: flex; gap: 8px; background: #f9f9f9; padding: 10px; border-radius: 8px; margin-bottom: 8px; border: 1px solid #eee; align-items: center; flex-wrap: wrap; }
            .ai-service-row.active { border-color: #3498db; background: #e8f4fd; }
            .ai-radio-col { flex: 0 0 30px; display: flex; justify-content: center; align-items: center; padding-top: 0; }
            .ai-radio-btn { width: 18px; height: 18px; border: 2px solid #ccc; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; }
            .ai-radio-btn.checked { border-color: #3498db; }
            .ai-radio-btn.checked::after { content: ''; width: 8px; height: 8px; background: #3498db; border-radius: 50%; display: block; }
            .ai-inputs-col { flex: 1; display: flex; gap: 8px; flex-wrap: wrap; min-width: 0; }
            .conf-api-host, .conf-api-key, .conf-model-name { flex: 1; min-width: 120px; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; }
            .conf-api-host { flex-basis: 30%; } 
            .conf-api-key { flex-basis: 30%; }
            .conf-model-name { flex-basis: 20%; }
            @media (max-width: 480px) { .conf-api-host, .conf-api-key, .conf-model-name { flex-basis: 100%; } }
            .tts-source-row { display: flex; gap: 6px; margin-bottom: 8px; align-items: center; flex-wrap: wrap; background: #fdfdfd; padding: 6px; border: 1px solid #eee; border-radius: 6px; }
            .tts-url-input { flex: 2; min-width: 140px; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; box-sizing: border-box; }
            .tts-token-input { flex: 1; min-width: 80px; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; box-sizing: border-box; }
            .tts-btn-group { display: flex; gap: 4px; }
            .tts-del-btn, .tts-lightning-btn { border: none; border-radius: 4px; width: 32px; height: 32px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; }
            .tts-del-btn { background: #ffebee; color: #c62828; }
            .tts-lightning-btn { background: #fff8e1; color: #f57f17; font-weight: bold; }
            .tts-lightning-btn:hover { background: #ffecb3; }
            details { margin-bottom: 10px; border-bottom: 1px solid #f0f0f0; padding-bottom: 10px; }
            details summary { list-style: none; outline: none; }
            details summary::-webkit-details-marker { display: none; }
            details summary .dr-section-title { margin: 10px 20px 5px; display: flex; align-items: center; justify-content: space-between; }
            details summary .dr-section-title::after { content: '▼'; font-size: 12px; color: #999; transition: transform 0.2s; }
            details[open] summary .dr-section-title::after { transform: rotate(180deg); }
            details .dr-input-group { padding-left: 20px; padding-right: 20px; }
            .dr-style-item { display:flex; justify-content:space-between; align-items:center; background:#f0f0f0; padding:8px 12px; margin-bottom:8px; border-radius:6px; font-size:13px; }
            .dr-style-item-left { display:flex; align-items:center; gap:8px; flex:1; }
            .dr-style-item-right { display:flex; align-items:center; }
            .dr-radio { accent-color: #3498db; width: 16px; height: 16px; cursor: pointer; }
            .dr-style-label { font-weight:600; color:#333; cursor: pointer; }
            .task-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
            @media (max-width: 500px) { .task-grid { grid-template-columns: 1fr; } }
            .task-item { background: #fdfdfd; padding: 8px; border: 1px solid #eee; border-radius: 6px; }
        `);

        const UIManager = {
            elements: {}, autoHideTimer: null,
            init() {
                const floatBtn = document.createElement('button');
                floatBtn.id = 'dr-float-btn'; floatBtn.innerHTML = '💡';
                document.body.appendChild(floatBtn);
                this.elements.floatBtn = floatBtn;

                const overlay = document.createElement('div');
                overlay.className = 'dr-overlay';
                overlay.id = 'dr-modal-overlay';
                overlay.innerHTML = `
                    <div class="dr-modal">
                        <div class="dr-header">
                            <div style="display:flex; align-items:center;">
                                <h3 class="dr-title">ReadSense</h3>
                                <select id="dr-model-select" class="dr-select" style="display:none; margin-left:10px; min-width:80px;"></select>
                                <select id="dr-style-select" class="dr-select" style="display:none;"></select>
                            </div>
                        </div>
                        <div class="dr-body">
                            <div id="dr-original-box"><span id="dr-original-label">SELECTED TEXT</span><div id="dr-original-content"></div></div>
                            <div id="dr-result-content"></div>
                        </div>
                        <div class="dr-footer"></div>
                    </div>`;
                document.body.appendChild(overlay);

                this.elements.overlay = overlay;
                this.elements.modal = overlay.querySelector('.dr-modal');
                this.elements.title = overlay.querySelector('.dr-title');
                this.elements.modelSelect = overlay.querySelector('#dr-model-select');
                this.elements.styleSelect = overlay.querySelector('#dr-style-select');
                this.elements.originalBox = overlay.querySelector('#dr-original-box');
                this.elements.originalContent = overlay.querySelector('#dr-original-content');
                this.elements.resultContent = overlay.querySelector('#dr-result-content');
                this.elements.footer = overlay.querySelector('.dr-footer');

                overlay.addEventListener('click', (e) => { if (e.target === overlay) this.closeModal(); });
                this.elements.styleSelect.addEventListener('change', (e) => { CoreController.startSummary(AppState.currentSummaryLang, e.target.value); });
                this.elements.modelSelect.addEventListener('change', (e) => { if(e.target.value) CoreController.switchModel(e.target.value); });

                let clickCount = 0, clickTimer = null, pressTimer = null, isLongPress = false;
                const startPress = (e) => {
                    if (e.type === 'mousedown' && e.button !== 0) return;
                    isLongPress = false;
                    pressTimer = setTimeout(() => {
                        isLongPress = true;
                        if (navigator.vibrate) navigator.vibrate(50);
                        floatBtn.style.transform = 'translateY(-50%) scale(1.2)';
                        setTimeout(() => floatBtn.style.transform = 'translateY(-50%) scale(1)', 200);
                        CoreController.startSummary('zh');
                    }, 800);
                };
                const cancelPress = () => { if (pressTimer) { clearTimeout(pressTimer); pressTimer = null; } };
                floatBtn.addEventListener('mousedown', startPress);
                floatBtn.addEventListener('touchstart', startPress, { passive: true });
                ['mouseup', 'mouseleave', 'touchend', 'touchmove'].forEach(evt => floatBtn.addEventListener(evt, cancelPress, { passive: true }));
                floatBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (isLongPress) { isLongPress = false; clickCount = 0; return; }
                    clickCount++;
                    if (clickCount === 1) {
                        clickTimer = setTimeout(() => {
                            if (AppState.analysisText) CoreController.startAnalysis(AppState.analysisText);
                            else { const lastText = GM_getValue(CONSTANTS.HISTORY_KEY, null); if (lastText) CoreController.startAnalysis(lastText); else alert('请先选择文本'); }
                            clickCount = 0;
                        }, 250);
                    } else if (clickCount === 2) {
                        clearTimeout(clickTimer); clickCount = 0;
                        floatBtn.style.transform = 'translateY(-50%) scale(0.9)';
                        setTimeout(() => floatBtn.style.transform = 'translateY(-50%) scale(1)', 150);
                        CoreController.startSummary('en');
                    }
                });
                
                let startY = 0, currentMoveY = 0;
                this.elements.footer.addEventListener('touchstart', (e) => { startY = e.touches[0].clientY; currentMoveY = 0; this.elements.modal.style.transition = 'none'; }, { passive: true });
                this.elements.footer.addEventListener('touchmove', (e) => { const diff = e.touches[0].clientY - startY; if (diff < 0) { currentMoveY = diff; this.elements.modal.style.transform = `translateY(${diff}px)`; } }, { passive: true });
                this.elements.footer.addEventListener('touchend', () => {
                    this.elements.modal.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
                    if (currentMoveY < -50) { this.elements.modal.style.transform = `translateY(-100vh)`; this.elements.modal.style.opacity = '0'; setTimeout(() => { this.closeModal(); }, 300); } else { this.elements.modal.style = ''; }
                    currentMoveY = 0;
                });
            },
            updateModelDropdown(currentId) {
                const select = this.elements.modelSelect;
                select.innerHTML = '';
                const services = Utils.getAIServices();
                if(!services || services.length === 0) return;
                services.forEach(svc => {
                    const opt = document.createElement('option');
                    opt.value = svc.id;
                    opt.textContent = svc.name;
                    select.appendChild(opt);
                });
                select.value = currentId || Utils.getActiveAIId();
            },
            openModal(title, mode = 'ANALYSIS', lang = 'en', currentStyle = 'SIMPLE') {
                this.elements.title.textContent = title;
                this.elements.overlay.classList.add('active');
                AppState.isModalOpen = true;
                document.body.style.overflow = 'hidden';
                this.elements.styleSelect.style.display = 'none';
                this.elements.modelSelect.style.display = 'none';
                
                if (mode === 'SUMMARY' && lang === 'en') {
                    this.updateStyleDropdown();
                    this.elements.styleSelect.style.display = 'block';
                    this.elements.styleSelect.value = currentStyle;
                } else if (mode === 'ANALYSIS') {
                    this.updateModelDropdown();
                    this.elements.modelSelect.style.display = 'block';
                }
            },
            updateStyleDropdown() {
                const select = this.elements.styleSelect;
                select.innerHTML = '';
                const styles = Utils.getAllStyles();
                for (let key in styles) {
                    const opt = document.createElement('option');
                    opt.value = key; opt.textContent = styles[key].label; select.appendChild(opt);
                }
            },
            closeModal() {
                this.elements.overlay.classList.remove('active');
                setTimeout(() => { this.elements.modal.style = ''; }, 300);
                AppState.isModalOpen = false;
                document.body.style.overflow = '';
                TTSService.stop();
            },
            updateStatus(text) {
                this.elements.resultContent.innerHTML = `<div style="text-align:center; padding:40px; color:#999;"><div style="font-size:24px; margin-bottom:10px;">⏳</div>${text}</div>`;
            },
            renderAnalysisUI(originalText, aiContent, serviceName, serviceId = null) {
                this.elements.originalBox.style.display = 'block';
                this.elements.originalContent.textContent = originalText;
                const html = Utils.renderMarkdownToHTML(aiContent);
                this.elements.resultContent.innerHTML = html + `<div style="margin-top:20px; font-size:12px; color:#ccc; text-align:right;">by ${serviceName}</div>`;
                
                if (serviceId && this.elements.modelSelect) {
                    this.elements.modelSelect.value = serviceId;
                }

                DictHelper.initDictionary(this.elements.resultContent, 'dr-modal-overlay');
                this.buildFooter([
                    { text: '🔊 朗读', type: 'primary', id: 'dr-tts-btn', onClick: () => TTSService.play(originalText, false) },
                    { text: '📋 复制', type: 'success', onClick: () => { GM_setClipboard(aiContent); alert('已复制'); } }
                ]);
            },
            renderSummaryUI(content, isCached, serviceName) {
                this.elements.originalBox.style.display = 'none';
                const html = Utils.renderMarkdownToHTML(content);
                const suffix = isCached ? ' (⚡️Cache)' : '';
                this.elements.resultContent.innerHTML = html + `<div style="margin-top:20px; font-size:12px; color:#ccc; text-align:right;">${serviceName} Summary${suffix}</div>`;
                DictHelper.initDictionary(this.elements.resultContent, 'dr-modal-overlay');
                
                const showPushOptions = () => {
                    this.buildFooter([
                        { text: '📄 推送 1 (笔记)', type: 'primary', onClick: () => CoreController.pushGitHub(content, 'NOTE') },
                        { text: '🌏 推送 2 (归档)', type: 'black', onClick: () => CoreController.pushGitHub(content, 'ARCHIVE') },
                        { text: '🚀 全部推送', type: 'success', onClick: () => CoreController.pushGitHub(content, 'BOTH') },
                        { text: '↩️ 取消', type: 'gray', onClick: () => restoreDefaultFooter() }
                    ]);
                };

                const restoreDefaultFooter = () => {
                    this.buildFooter([
                        { text: '🔊 朗读', type: 'primary', id: 'dr-tts-btn', onClick: () => TTSService.play(content, true) },
                        { text: '⬆️ GitHub', type: 'gray', id: 'dr-gh-btn', onClick: showPushOptions },
                        { text: '📋 复制', type: 'success', onClick: () => { GM_setClipboard(content); alert('已复制'); } }
                    ]);
                };

                restoreDefaultFooter();
            },
            renderAiServiceList(container) {
                container.innerHTML = '';
                AppState.tempAIServices.forEach((svc, index) => {
                    const row = document.createElement('div');
                    const isActive = svc.id === AppState.tempActiveAIId;
                    row.className = `ai-service-row ${isActive ? 'active' : ''}`;
                    const radioCol = document.createElement('div'); radioCol.className = 'ai-radio-col';
                    const radioBtn = document.createElement('div');
                    radioBtn.className = `ai-radio-btn ${isActive ? 'checked' : ''}`;
                    radioBtn.title = "设为全局首选 (Global Primary)";
                    radioBtn.onclick = () => { AppState.tempActiveAIId = svc.id; this.renderAiServiceList(container); };
                    radioCol.appendChild(radioBtn);
                    const inputCol = document.createElement('div'); inputCol.className = 'ai-inputs-col';
                    const nameInput = document.createElement('input'); nameInput.className = 'conf-model-name'; nameInput.placeholder = '名称'; nameInput.value = svc.name || ''; nameInput.onchange = (e) => { svc.name = e.target.value; };
                    const urlInput = document.createElement('input'); urlInput.className = 'conf-api-host'; urlInput.placeholder = 'Host URL'; urlInput.value = svc.url || ''; urlInput.onchange = (e) => { svc.url = e.target.value; };
                    const keyInput = document.createElement('input'); keyInput.className = 'conf-api-key'; keyInput.placeholder = 'API Key'; keyInput.value = svc.key || ''; keyInput.type = 'password'; keyInput.onchange = (e) => { svc.key = e.target.value; };
                    const modelInput = document.createElement('input'); modelInput.className = 'conf-model-name'; modelInput.placeholder = 'Model'; modelInput.value = svc.model || ''; modelInput.style.flexBasis = "40%"; modelInput.onchange = (e) => { svc.model = e.target.value; };
                    inputCol.append(nameInput, urlInput, keyInput, modelInput);
                    const delBtn = document.createElement('button'); delBtn.className = 'tts-del-btn'; delBtn.innerHTML = '×';
                    delBtn.style.marginLeft = 'auto';
                    delBtn.onclick = () => {
                        AppState.tempAIServices.splice(index, 1);
                        if (isActive && AppState.tempAIServices.length > 0) AppState.tempActiveAIId = AppState.tempAIServices[0].id;
                        this.renderAiServiceList(container);
                    };
                    row.append(radioCol, inputCol, delBtn);
                    container.appendChild(row);
                });
                const controlsDiv = document.createElement('div');
                controlsDiv.style.display = 'flex'; controlsDiv.style.gap = '8px'; controlsDiv.style.flexWrap = 'wrap'; controlsDiv.style.marginTop = '10px';
                const addBtn = document.createElement('button');
                addBtn.className = 'dr-btn dr-btn-primary'; addBtn.style.padding = '6px 12px'; addBtn.style.fontSize = '12px';
                addBtn.innerHTML = '+ 自定义';
                addBtn.onclick = () => {
                    const newId = 'ai_' + Date.now();
                    AppState.tempAIServices.push({ id: newId, name: 'Custom AI', url: '', key: '', model: '' });
                    if (!AppState.tempActiveAIId) AppState.tempActiveAIId = newId;
                    this.renderAiServiceList(container);
                };
                controlsDiv.appendChild(addBtn);
                Object.keys(GLOBAL_CONFIG.AI_TEMPLATES).forEach(key => {
                    const tpl = GLOBAL_CONFIG.AI_TEMPLATES[key];
                    const tplBtn = document.createElement('button');
                    tplBtn.className = 'dr-btn dr-btn-gray'; tplBtn.style.padding = '6px 10px'; tplBtn.style.fontSize = '12px';
                    tplBtn.innerHTML = `+ ${tpl.name}`;
                    tplBtn.onclick = () => {
                        const newId = 'ai_' + Date.now();
                        AppState.tempAIServices.push({ id: newId, name: tpl.name, url: tpl.url, key: '', model: tpl.model });
                        if (!AppState.tempActiveAIId) AppState.tempActiveAIId = newId;
                        this.renderAiServiceList(container);
                    };
                    controlsDiv.appendChild(tplBtn);
                });
                container.appendChild(controlsDiv);
            },
            renderTaskAllocation(container) {
                container.innerHTML = '';
                const tasks = [
                    { id: 'ANALYSIS', label: '1. 深度分析 (Deep Analysis)' },
                    { id: 'SUMMARY', label: '2. 内容总结 (Summary)' },
                    { id: 'TWITTER', label: '3. 推特分析 (Twitter Analysis)' },
                    { id: 'TRANSLATE', label: '4. 智能翻译 (Twitter Translation)' }
                ];
                
                const services = AppState.tempAIServices;
                const generateOptions = (currentVal) => {
                    let html = `<option value="">🚀 使用全局首选 (Default)</option>`;
                    services.forEach(s => {
                         html += `<option value="${s.id}" ${s.id === currentVal ? 'selected' : ''}>${s.name}</option>`;
                    });
                    return html;
                };

                const grid = document.createElement('div');
                grid.className = 'task-grid';

                tasks.forEach(t => {
                    const item = document.createElement('div');
                    item.className = 'task-item';
                    const label = document.createElement('label');
                    label.textContent = t.label;
                    label.style.fontSize = '12px'; label.style.fontWeight='600'; label.style.color='#444';
                    
                    const select = document.createElement('select');
                    select.className = 'dr-input';
                    select.style.fontSize = '13px'; select.style.padding='4px';
                    select.innerHTML = generateOptions(AppState.tempTaskAllocations[t.id]);
                    select.onchange = (e) => { AppState.tempTaskAllocations[t.id] = e.target.value; };

                    item.append(label, select);
                    grid.appendChild(item);
                });
                container.appendChild(grid);
            },
            renderSourceList(container) {
                const sources = Utils.getSetting('ttsSources') || [];
                const createRow = (url = '', token = '') => {
                    const row = document.createElement('div'); row.className = 'tts-source-row';
                    const urlInput = document.createElement('input'); urlInput.className = 'tts-url-input'; urlInput.placeholder = 'TTS URL (e.g., .../api/tts)'; urlInput.value = url;
                    const tokenInput = document.createElement('input'); tokenInput.className = 'tts-token-input'; tokenInput.placeholder = 'Token (Opt)'; tokenInput.value = token;
                    const btnGroup = document.createElement('div'); btnGroup.className = 'tts-btn-group';
                    const testBtn = document.createElement('button'); testBtn.className = 'tts-lightning-btn'; testBtn.innerHTML = '⚡️'; testBtn.title = "立即测试连接";
                    testBtn.onclick = () => TTSService.quickTest(urlInput.value, tokenInput.value, testBtn);
                    const delBtn = document.createElement('button'); delBtn.className = 'tts-del-btn'; delBtn.innerHTML = '×'; 
                    delBtn.onclick = () => row.remove();
                    btnGroup.append(testBtn, delBtn);
                    row.append(urlInput, tokenInput, btnGroup);
                    return row;
                };
                sources.forEach(src => container.appendChild(createRow(src.url, src.token)));
                const addBtn = document.createElement('button'); addBtn.id = 'tts-add-btn'; addBtn.className="dr-btn dr-btn-gray"; addBtn.style.width="100%"; addBtn.style.justifyContent="center"; addBtn.style.marginTop="5px"; addBtn.textContent = '+ 添加新源'; addBtn.onclick = () => container.insertBefore(createRow(), addBtn);
                container.appendChild(addBtn);
            },
            renderSettings() {
                this.elements.originalBox.style.display = 'none';
                
                AppState.tempAIServices = Utils.getAIServices();
                AppState.tempActiveAIId = Utils.getActiveAIId();
                AppState.tempTaskAllocations = {
                    ANALYSIS: GM_getValue(CONSTANTS.TASK_ANALYSIS_ID, ''),
                    SUMMARY: GM_getValue(CONSTANTS.TASK_SUMMARY_ID, ''),
                    TWITTER: GM_getValue(CONSTANTS.TASK_TWITTER_ID, ''),
                    TRANSLATE: GM_getValue(CONSTANTS.TASK_TRANSLATE_ID, '')
                };

                if (AppState.tempAIServices.length === 0) {
                     const tpl = GLOBAL_CONFIG.AI_TEMPLATES.GROQ;
                     AppState.tempAIServices.push({ id: 'init_default', name: tpl.name, url: tpl.url, key: '', model: tpl.model });
                     AppState.tempActiveAIId = 'init_default';
                }

                AppState.tempCustomStyles = GM_getValue(CONSTANTS.CUSTOM_STYLES_KEY, []);
                AppState.tempDefaultStyle = Utils.getSetting('defaultSummaryStyle') || 'SIMPLE';

                const cnVoice = Utils.getSetting('ttsVoiceCN');
                const enVoice = Utils.getSetting('ttsVoiceEN');
                const cnOptions = GLOBAL_CONFIG.VOICES.CN.map(v => `<option value="${v.id}" ${cnVoice === v.id ? 'selected' : ''}>${v.name}</option>`).join('');
                const enOptions = GLOBAL_CONFIG.VOICES.EN.map(v => `<option value="${v.id}" ${enVoice === v.id ? 'selected' : ''}>${v.name}</option>`).join('');

                this.elements.resultContent.innerHTML = `
                    <div style="padding-top:10px;">
                        <div class="dr-section-title">✨ 功能开关</div>
                        <div class="dr-input-group" style="display:flex; align-items:center; gap:8px;">
                            <input type="checkbox" id="dr-enable-float-btn" class="dr-radio" ${Utils.getSetting('enableFloatBtn') ? 'checked' : ''}>
                            <label for="dr-enable-float-btn" style="margin:0; cursor:pointer;">显示 DeepRead 悬浮按钮</label>
                        </div>
                         <div class="dr-input-group" style="display:flex; align-items:center; gap:8px;">
                            <input type="checkbox" id="dr-enable-failover" class="dr-radio" ${Utils.getSetting('enableFailover') ? 'checked' : ''}>
                            <label for="dr-enable-failover" style="margin:0; cursor:pointer;">启用 AI 故障自动轮询 (Failover)</label>
                        </div>
                        
                        <div class="dr-input-group" style="background:#f0f8ff; padding:10px; border-radius:8px; margin-top:10px;">
                            <label style="color:#0056b3;">🐦 Twitter 翻译引擎</label>
                            <select class="dr-input" id="dr-tw-trans-type">
                                <option value="AI" ${Utils.getSetting('twitterTranslationType') === 'AI' ? 'selected' : ''}>🤖 AI 智能翻译 (使用下方配置)</option>
                                <option value="GOOGLE" ${Utils.getSetting('twitterTranslationType') === 'GOOGLE' ? 'selected' : ''}>🌏 Google 翻译 (免费/快速)</option>
                            </select>
                        </div>

                        <details open>
                            <summary><div class="dr-section-title">🧠 AI 服务配置</div></summary>
                            <div class="dr-input-group">
                                <div id="dr-ai-list-container"></div>
                                <div style="margin-top:15px; border-top:1px dashed #ddd; padding-top:10px;">
                                    <div style="font-weight:700; color:#333; margin-bottom:8px;">🤖 AI 任务分流 (Task Assignment)</div>
                                    <div style="font-size:12px; color:#666; margin-bottom:8px;">将不同功能分配给最擅长的模型。未指定则使用“全局首选”。</div>
                                    <div id="dr-task-allocation-container"></div>
                                </div>
                            </div>
                        </details>

                        <details>
                            <summary><div class="dr-section-title">🔊 TTS 语音配置</div></summary>
                            <div class="dr-input-group">
                                <label>🇨🇳 中文首选语音</label>
                                <select class="dr-input" id="sk-voice-cn">${cnOptions}</select>
                                <div style="font-size:12px; color:#666; margin-top:4px;">💡 提示:选中“晓北”将自动激活独立高速通道 (v1.4)。</div>
                            </div>
                            <div class="dr-input-group">
                                <label>🇺🇸 英文首选语音</label>
                                <select class="dr-input" id="sk-voice-en">${enOptions}</select>
                            </div>
                            <div class="dr-input-group">
                                <label style="margin-bottom:8px">TTS 源列表 (Fallback) <span style="color:#f57f17;font-weight:normal;font-size:12px">⚡️点击闪电图标测试</span></label>
                                <div id="tts-source-container"></div>
                            </div>
                        </details>

                        <details>
                            <summary><div class="dr-section-title">📝 Prompt 指令管理</div></summary>
                            <div class="dr-input-group"><label>🔍 深度分析指令</label><textarea class="dr-input" id="prompt-analyze" rows="4">${Utils.getSetting('promptAnalyze')}</textarea></div>
                            <div class="dr-input-group"><label>📑 中文总结指令</label><textarea class="dr-input" id="prompt-summary-cn" rows="3">${Utils.getSetting('promptSummaryCN')}</textarea></div>
                            <div class="dr-input-group"><label>🐦 Twitter 分析指令</label><textarea class="dr-input" id="tw-prompt" rows="3">${Utils.getSetting('twitterPrompt')}</textarea></div>
                        </details>

                        <details>
                            <summary><div class="dr-section-title">🎨 英文总结风格</div></summary>
                            <div class="dr-input-group" style="border:1px solid #eee; padding:15px; border-radius:8px;">
                                <div id="dr-styles-list" style="max-height:150px; overflow-y:auto; margin-bottom:10px;"></div>
                                <div style="background:#f9f9f9; padding:10px; border-radius:6px;">
                                    <input id="dr-new-style-name" class="dr-input" placeholder="名称" style="margin-bottom:5px;">
                                    <textarea id="dr-new-style-prompt" class="dr-input" placeholder="Prompt (English)..." style="margin-bottom:5px; min-height:50px;"></textarea>
                                    <button id="dr-add-style-btn" class="dr-btn dr-btn-primary" style="width:100%; justify-content:center; padding:6px;">➕ 添加新风格</button>
                                </div>
                            </div>
                        </details>

                        <details>
                            <summary><div class="dr-section-title">📦 GitHub 笔记同步</div></summary>
                            <div class="dr-input-group">
                                <div style="margin-bottom:10px;"><label>Personal Access Token</label><input class="dr-input" id="sk-gh" value="${Utils.getSetting('githubToken')}" placeholder="ghp_..."></div>
                                
                                <div style="background:#f9f9f9; padding:10px; border-radius:6px; margin-bottom:10px;">
                                    <label style="color:#27ae60;">📂 位置 1 (笔记覆盖 / Markdown)</label>
                                    <div style="display:flex; gap:5px; margin-bottom:5px;">
                                        <input class="dr-input" id="sk-u" value="${Utils.getSetting('githubUser')}" placeholder="User">
                                        <input class="dr-input" id="sk-r" value="${Utils.getSetting('githubRepo')}" placeholder="Repo">
                                    </div>
                                    <input class="dr-input" id="sk-p" value="${Utils.getSetting('githubPath')}" placeholder="Path (e.g., Notes/B1.md)">
                                </div>

                                <div style="background:#f9f9f9; padding:10px; border-radius:6px;">
                                    <label style="color:#2980b9;">🌏 位置 2 (网页归档 / HTML)</label>
                                    <div style="display:flex; gap:5px; margin-bottom:5px;">
                                        <input class="dr-input" id="sk-u2" value="${Utils.getSetting('githubUser2')}" placeholder="User">
                                        <input class="dr-input" id="sk-r2" value="${Utils.getSetting('githubRepo2')}" placeholder="Repo">
                                    </div>
                                    <input class="dr-input" id="sk-p2" value="${Utils.getSetting('githubPath2')}" placeholder="Path (e.g., danmu/)">
                                    <div style="font-size:11px; color:#666; margin-top:4px;">* 此模式将生成 .html 文件,文件名为当前时间戳,不会覆盖旧文件。</div>
                                </div>
                            </div>
                        </details>

                        <div class="dr-section-title">💾 备份与还原</div>
                        <div class="dr-input-group" style="display:flex; gap:10px; justify-content:center; padding-bottom:20px;">
                            <button id="dr-export-btn" class="dr-btn dr-btn-gray">📤 导出配置</button>
                            <button id="dr-import-btn" class="dr-btn dr-btn-gray">📥 导入配置</button>
                        </div>
                    </div>`;

                this.renderAiServiceList(document.getElementById('dr-ai-list-container'));
                this.renderTaskAllocation(document.getElementById('dr-task-allocation-container'));
                this.renderStylesList();
                this.renderSourceList(document.getElementById('tts-source-container'));
                
                setTimeout(() => {
                    const addBtn = document.getElementById('dr-add-style-btn');
                    addBtn.onclick = () => {
                        const nameInput = document.getElementById('dr-new-style-name'); const promptInput = document.getElementById('dr-new-style-prompt');
                        const name = nameInput.value.trim(); const prompt = promptInput.value.trim();
                        if (!name || !prompt) return alert('请填写名称和 Prompt');
                        if (AppState.editingStyleId) {
                            const idx = AppState.tempCustomStyles.findIndex(s => s.id === AppState.editingStyleId);
                            if (idx > -1) { AppState.tempCustomStyles[idx].label = name; AppState.tempCustomStyles[idx].prompt = prompt; }
                            AppState.editingStyleId = null; addBtn.textContent = '➕ 添加新风格'; addBtn.className = 'dr-btn dr-btn-primary';
                        } else { AppState.tempCustomStyles.push({ id: 'custom_' + Date.now(), label: name, prompt: prompt }); }
                        nameInput.value = ''; promptInput.value = ''; this.renderStylesList();
                    };
                    document.getElementById('dr-export-btn').onclick = () => {
                         const data = { 
                             settings: {}, 
                             aiServices: Utils.getAIServices(), 
                             activeAiId: Utils.getActiveAIId(), 
                             customStyles: GM_getValue(CONSTANTS.CUSTOM_STYLES_KEY, []),
                             taskAllocations: {
                                ANALYSIS: GM_getValue(CONSTANTS.TASK_ANALYSIS_ID, ''),
                                SUMMARY: GM_getValue(CONSTANTS.TASK_SUMMARY_ID, ''),
                                TWITTER: GM_getValue(CONSTANTS.TASK_TWITTER_ID, ''),
                                TRANSLATE: GM_getValue(CONSTANTS.TASK_TRANSLATE_ID, '')
                             }
                         };
                         for(let k in DEFAULT_SETTINGS) data.settings[k] = Utils.getSetting(k);
                         const blob = new Blob([JSON.stringify(data)], {type: "application/json"});
                         const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'ReadSense_Backup.json'; a.click();
                    };
                    document.getElementById('dr-import-btn').onclick = () => {
                        const input = document.createElement('input'); input.type = 'file'; input.accept = '.json';
                        input.onchange = (e) => {
                            const f = e.target.files[0]; if(!f)return;
                            const r = new FileReader(); r.onload=(ev)=>{ 
                                try{ 
                                    const d=JSON.parse(ev.target.result); 
                                    if(d.settings){ for(let k in d.settings)GM_setValue(k, d.settings[k]); } 
                                    if(Array.isArray(d.aiServices)) Utils.setAIServices(d.aiServices); 
                                    if(d.activeAiId) Utils.setActiveAIId(d.activeAiId); 
                                    if(Array.isArray(d.customStyles)) GM_setValue(CONSTANTS.CUSTOM_STYLES_KEY, d.customStyles);
                                    if(d.taskAllocations && typeof d.taskAllocations === 'object') {
                                        if(d.taskAllocations.ANALYSIS) GM_setValue(CONSTANTS.TASK_ANALYSIS_ID, d.taskAllocations.ANALYSIS);
                                        if(d.taskAllocations.SUMMARY) GM_setValue(CONSTANTS.TASK_SUMMARY_ID, d.taskAllocations.SUMMARY);
                                        if(d.taskAllocations.TWITTER) GM_setValue(CONSTANTS.TASK_TWITTER_ID, d.taskAllocations.TWITTER);
                                        if(d.taskAllocations.TRANSLATE) GM_setValue(CONSTANTS.TASK_TRANSLATE_ID, d.taskAllocations.TRANSLATE);
                                    }
                                    alert('✅ 导入成功'); location.reload(); 
                                }catch(err){alert('❌ 导入失败:格式错误或文件损坏');} 
                            }; r.readAsText(f);
                        }; input.click();
                    };
                }, 0);

                this.buildFooter([{
                    text: '💾 保存全部设置', type: 'success', onClick: () => {
                        Utils.setSetting('enableFloatBtn', document.getElementById('dr-enable-float-btn').checked);
                        Utils.setSetting('enableFailover', document.getElementById('dr-enable-failover').checked);
                        Utils.setSetting('twitterTranslationType', document.getElementById('dr-tw-trans-type').value);
                        
                        const validServices = AppState.tempAIServices.filter(s => s.url && s.key);
                        if (validServices.length === 0) return alert("❌ 请至少配置一个有效的 AI 服务 (需 URL 和 Key)");
                        Utils.setAIServices(validServices);
                        
                        const serviceIds = new Set(validServices.map(s => s.id));
                        
                        let safeActiveId = AppState.tempActiveAIId;
                        if (!serviceIds.has(safeActiveId)) { safeActiveId = validServices[0].id; }
                        Utils.setActiveAIId(safeActiveId);

                        GM_setValue(CONSTANTS.CUSTOM_STYLES_KEY, AppState.tempCustomStyles);
                        Utils.setSetting('defaultSummaryStyle', AppState.tempDefaultStyle);
                        
                        const validateTask = (id) => serviceIds.has(id) ? id : '';
                        
                        GM_setValue(CONSTANTS.TASK_ANALYSIS_ID, validateTask(AppState.tempTaskAllocations.ANALYSIS));
                        GM_setValue(CONSTANTS.TASK_SUMMARY_ID, validateTask(AppState.tempTaskAllocations.SUMMARY));
                        GM_setValue(CONSTANTS.TASK_TWITTER_ID, validateTask(AppState.tempTaskAllocations.TWITTER));
                        GM_setValue(CONSTANTS.TASK_TRANSLATE_ID, validateTask(AppState.tempTaskAllocations.TRANSLATE));

                        Utils.setSetting('githubToken', document.getElementById('sk-gh').value.trim());
                        // Config 1
                        Utils.setSetting('githubUser', document.getElementById('sk-u').value.trim());
                        Utils.setSetting('githubRepo', document.getElementById('sk-r').value.trim());
                        Utils.setSetting('githubPath', document.getElementById('sk-p').value.trim());
                        // Config 2
                        Utils.setSetting('githubUser2', document.getElementById('sk-u2').value.trim());
                        Utils.setSetting('githubRepo2', document.getElementById('sk-r2').value.trim());
                        Utils.setSetting('githubPath2', document.getElementById('sk-p2').value.trim());

                        Utils.setSetting('twitterPrompt', document.getElementById('tw-prompt').value.trim());
                        Utils.setSetting('promptAnalyze', document.getElementById('prompt-analyze').value.trim());
                        Utils.setSetting('promptSummaryCN', document.getElementById('prompt-summary-cn').value.trim());
                        
                        Utils.setSetting('ttsVoiceCN', document.getElementById('sk-voice-cn').value);
                        Utils.setSetting('ttsVoiceEN', document.getElementById('sk-voice-en').value);

                        const sourceContainer = document.getElementById('tts-source-container');
                        const rows = sourceContainer.querySelectorAll('.tts-source-row');
                        const newSources = [];
                        rows.forEach(row => {
                            const url = row.querySelector('.tts-url-input').value.trim();
                            const token = row.querySelector('.tts-token-input').value.trim();
                            if(url) newSources.push({ url, token });
                        });
                        Utils.setSetting('ttsSources', newSources);

                        alert(`✅ 设置已保存`);
                        this.closeModal();
                    }
                }]);
            },
            renderStylesList() {
                const container = document.getElementById('dr-styles-list');
                if (!container) return;
                container.innerHTML = '';
                const createRow = (id, label, prompt, isCustom) => {
                    const div = document.createElement('div'); div.className = 'dr-style-item';
                    const leftDiv = document.createElement('div'); leftDiv.className = 'dr-style-item-left';
                    const radio = document.createElement('input'); radio.type = 'radio'; radio.name = 'style-default-group'; radio.className = 'dr-radio'; radio.checked = (AppState.tempDefaultStyle === id); radio.onclick = () => { AppState.tempDefaultStyle = id; };
                    const span = document.createElement('span'); span.className = 'dr-style-label'; span.textContent = (isCustom ? '✨ ' : '📝 ') + label; span.onclick = () => { radio.checked = true; AppState.tempDefaultStyle = id; };
                    leftDiv.append(radio, span); div.appendChild(leftDiv);
                    if (isCustom) {
                        const rightDiv = document.createElement('div'); rightDiv.className = 'dr-style-item-right';
                        const editBtn = document.createElement('button'); editBtn.className = 'dr-btn dr-btn-edit'; editBtn.textContent = '✏️ 编辑';
                        editBtn.onclick = () => {
                            document.getElementById('dr-new-style-name').value = label; document.getElementById('dr-new-style-prompt').value = prompt;
                            AppState.editingStyleId = id; const addBtn = document.getElementById('dr-add-style-btn'); addBtn.textContent = '💾 保存修改'; addBtn.className = 'dr-btn dr-btn-success';
                        };
                        const delBtn = document.createElement('button'); delBtn.className = 'dr-btn dr-btn-danger'; delBtn.textContent = '删除';
                        delBtn.onclick = () => {
                            if (AppState.editingStyleId === id) { AppState.editingStyleId = null; document.getElementById('dr-new-style-name').value = ''; document.getElementById('dr-new-style-prompt').value = ''; const addBtn = document.getElementById('dr-add-style-btn'); addBtn.textContent = '➕ 添加新风格'; addBtn.className = 'dr-btn dr-btn-primary'; }
                            if (AppState.tempDefaultStyle === id) { AppState.tempDefaultStyle = 'SIMPLE'; }
                            const idx = AppState.tempCustomStyles.findIndex(s => s.id === id);
                            if (idx > -1) { AppState.tempCustomStyles.splice(idx, 1); this.renderStylesList(); }
                        };
                        rightDiv.append(editBtn, delBtn); div.appendChild(rightDiv);
                    }
                    return div;
                };
                container.appendChild(createRow('SIMPLE', '简练', '', false));
                AppState.tempCustomStyles.forEach(style => { container.appendChild(createRow(style.id, style.label, style.prompt, true)); });
            },
            buildFooter(btns) {
                this.elements.footer.innerHTML = '';
                btns.forEach(b => {
                    const btn = document.createElement('button'); btn.className = `dr-btn dr-btn-${b.type}`; btn.textContent = b.text;
                    if (b.id) btn.id = b.id;
                    btn.addEventListener('touchstart', (e) => e.stopPropagation(), { passive: true });
                    btn.onclick = b.onClick;
                    this.elements.footer.appendChild(btn);
                });
            },
            updateTTSButton(playing) {
                const btn = document.getElementById('dr-tts-btn');
                if (btn) { btn.textContent = playing ? '🔇 停止' : '🔊 朗读'; btn.className = playing ? 'dr-btn dr-btn-gray' : 'dr-btn dr-btn-primary'; }
            },
            setFloatBtnVisible(visible) {
                if (!Utils.getSetting('enableFloatBtn')) { this.elements.floatBtn.classList.remove('visible'); return; }
                if (this.autoHideTimer) { clearTimeout(this.autoHideTimer); this.autoHideTimer = null; }
                if (visible) { this.elements.floatBtn.classList.add('visible'); this.autoHideTimer = setTimeout(() => { this.elements.floatBtn.classList.remove('visible'); }, 6000); } else { this.elements.floatBtn.classList.remove('visible'); }
            }
        };

        // --- Core Controller ---
        const CoreController = {
            async startAnalysis(targetText) {
                const text = targetText || AppState.analysisText;
                if (!text) return;
                GM_setValue(CONSTANTS.HISTORY_KEY, text);
                
                const activeId = Utils.getServiceIdForTask('ANALYSIS');
                UIManager.openModal('深度分析', 'ANALYSIS');
                UIManager.updateStatus('AI 思考中...');
                
                const specificCacheKey = Utils.getCacheKey('ana_' + activeId, text);
                const cached = GM_getValue(specificCacheKey);
                
                if (cached && Date.now() - cached.timestamp < CONSTANTS.CACHE_EXPIRY) {
                    let cachedId = cached.serviceId;
                    if (!cachedId) {
                        const s = Utils.getAIServices().find(x => x.name === cached.service);
                        if(s) cachedId = s.id;
                    }
                    UIManager.renderAnalysisUI(text, cached.content, cached.service + '(Cache)', cachedId);
                    return;
                }
                
                try {
                    const res = await LLMService.analyzeWithFailover(text, 'promptAnalyze', activeId);
                    GM_setValue(specificCacheKey, { 
                        timestamp: Date.now(), 
                        content: res.content, 
                        service: res.service,
                        serviceId: res.serviceId 
                    });
                    UIManager.renderAnalysisUI(text, res.content, res.service, res.serviceId);
                } catch (e) {
                    UIManager.updateStatus(`❌ ${e.message}`);
                    UIManager.buildFooter([{ text: '⚙️ 配置 API', type: 'primary', onClick: () => UIManager.renderSettings() }]);
                }
            },
            async switchModel(serviceId) {
                const text = AppState.analysisText || GM_getValue(CONSTANTS.HISTORY_KEY);
                if (!text) return alert('请重新选择文本');
                
                const services = Utils.getAIServices();
                const targetService = services.find(s => s.id === serviceId);
                if (!targetService) return alert('无效的服务 ID');

                const cacheKey = Utils.getCacheKey('ana_' + serviceId, text);
                const cached = GM_getValue(cacheKey);
                
                if (cached && Date.now() - cached.timestamp < CONSTANTS.CACHE_EXPIRY) {
                    UIManager.renderAnalysisUI(text, cached.content, cached.service + '(Cache)', serviceId);
                    return;
                }
                
                UIManager.updateStatus(`🤖 切换至 ${targetService.name} 分析中...`);
                try {
                    const messages = [{ role: 'system', content: Utils.getSetting('promptAnalyze') }, { role: 'user', content: text }];
                    const result = await LLMService.request(targetService, messages);
                    
                    GM_setValue(cacheKey, { 
                        timestamp: Date.now(), 
                        content: result, 
                        service: targetService.name,
                        serviceId: serviceId
                    });
                    
                    UIManager.renderAnalysisUI(text, result, targetService.name, serviceId);
                } catch (e) {
                    UIManager.updateStatus(`❌ ${targetService.name} 失败: ${e.message}`);
                    UIManager.buildFooter([{ text: '重试', type: 'primary', onClick: () => this.switchModel(serviceId) }]);
                }
            },
            async startSummary(lang, specificStyle = null) {
                AppState.currentSummaryLang = lang;
                let content = '';
                try {
                    if (typeof Readability !== 'undefined') { const article = new Readability(document.cloneNode(true)).parse(); content = article ? article.textContent : document.body.innerText; } else { content = document.body.innerText; }
                } catch (e) { content = document.body.innerText; }
                if (!content || content.length < 50) return alert('内容过短');
                
                let prompt = '';
                let cacheStyleKey = '';
                if (lang === 'zh') {
                    prompt = Utils.getSetting('promptSummaryCN'); cacheStyleKey = 'default';
                    UIManager.openModal('中文总结', 'SUMMARY', 'zh');
                } else {
                    const savedDefault = Utils.getSetting('defaultSummaryStyle') || 'SIMPLE';
                    const styleKey = specificStyle || savedDefault;
                    const allStyles = Utils.getAllStyles();
                    let styleObj = allStyles[styleKey];
                    if (!styleObj) { styleObj = allStyles['SIMPLE']; cacheStyleKey = 'SIMPLE'; } else { cacheStyleKey = styleKey; }
                    prompt = styleObj.prompt;
                    UIManager.openModal('Summary', 'SUMMARY', 'en', cacheStyleKey);
                }

                const activeId = Utils.getServiceIdForTask('SUMMARY');

                UIManager.updateStatus(`生成中...`);
                const urlId = window.location.pathname + window.location.search;
                const cacheKey = Utils.getCacheKey(`sum_${lang}_${cacheStyleKey}_${activeId}`, urlId);
                const cached = GM_getValue(cacheKey);
                if (cached && Date.now() - cached.timestamp < CONSTANTS.CACHE_EXPIRY) {
                    UIManager.renderSummaryUI(cached.content, true, 'Cache');
                    return;
                }
                try {
                    const services = Utils.getAIServices();
                    const sortedServices = services.sort((a, b) => {
                         if (a.id === activeId) return -1;
                         if (b.id === activeId) return 1;
                         return 0;
                    });

                    let successRes = null;
                    let lastErr = null;
                    for (const svc of sortedServices) {
                        if (!Utils.getSetting('enableFailover') && svc.id !== activeId) break;
                        try {
                             UIManager.updateStatus(`🤖 ${svc.name} 总结中...`);
                             const res = await LLMService.request(svc, [{ role: 'system', content: prompt }, { role: 'user', content: content.slice(0, 15000) }]);
                             successRes = { content: res, service: svc.name };
                             break;
                        } catch(e) { lastErr = e; }
                    }

                    if (successRes) {
                        GM_setValue(cacheKey, { timestamp: Date.now(), content: successRes.content });
                        UIManager.renderSummaryUI(successRes.content, false, successRes.service);
                    } else { throw lastErr || new Error("All AI Failed"); }
                } catch (e) {
                    UIManager.updateStatus(`❌ ${e.message}`);
                    UIManager.buildFooter([{ text: '⚙️ 检查配置', type: 'primary', onClick: () => UIManager.renderSettings() }]);
                }
            },
            async pushGitHub(content, mode = 'BOTH') {
                const token = Utils.getSetting('githubToken');
                if (!token) return alert('请先设置 Token');
                
                const currentUrl = window.location.href;
                const pageTitle = document.title || 'Untitled';

                // Helper: Push Logic
                const pushFile = async (user, repo, path, fileContent, message, checkSha = false) => {
                    const apiUrl = `https://api.github.com/repos/${user}/${repo}/contents/${path}`;
                    let sha = null;
                    
                    if (checkSha) {
                        try { 
                            const r = await new Promise((ok, no) => GM_xmlhttpRequest({ method: 'GET', url: apiUrl, headers: { Authorization: `Bearer ${token}` }, onload: ok, onerror: no })); 
                            if (r.status === 200) { const d = JSON.parse(r.responseText); sha = d.sha; } 
                        } catch (e) { }
                    }

                    const b64Content = btoa(unescape(encodeURIComponent(fileContent)));
                    return new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'PUT', url: apiUrl, headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
                            data: JSON.stringify({ message: message, content: b64Content, sha }),
                            onload: (r) => { if (r.status < 300) resolve(); else reject(`Code ${r.status}`); },
                            onerror: () => reject('Network Error')
                        });
                    });
                };

                const tasks = [];

                // 1. Push Note (Markdown) - Overwrite Mode
                if (mode === 'NOTE' || mode === 'BOTH') {
                    const user1 = Utils.getSetting('githubUser');
                    const repo1 = Utils.getSetting('githubRepo');
                    const path1 = Utils.getSetting('githubPath');
                    if (user1 && repo1 && path1) {
                        const mdContent = `# ${pageTitle}\n\n> 📅 ${new Date().toLocaleString()}\n> 🔗 ${currentUrl}\n\n${content}`;
                        tasks.push(pushFile(user1, repo1, path1, mdContent, `DeepRead Note: ${pageTitle}`, true)
                            .then(() => "✅ 笔记(MD)推送成功")
                            .catch(e => `❌ 笔记推送失败: ${e}`));
                    }
                }

                // 2. Push Archive (HTML) - New File Mode
                if (mode === 'ARCHIVE' || mode === 'BOTH') {
                    const user2 = Utils.getSetting('githubUser2');
                    const repo2 = Utils.getSetting('githubRepo2');
                    const basePath2 = Utils.getSetting('githubPath2'); // e.g. "danmu/"
                    
                    if (user2 && repo2 && basePath2) {
                        // Sanitize filename: Replace non-alphanumeric chars (except Chinese) with underscore
                        const safeTitle = pageTitle.replace(/[^\w\u4e00-\u9fa5]/g, '_').substring(0, 50);
                        const fileName = `${safeTitle}_${Date.now()}.html`;
                        const fullPath2 = (basePath2.endsWith('/') ? basePath2 : basePath2 + '/') + fileName;
                        
                        const htmlContent = Utils.generateHTMLContent(pageTitle, currentUrl, content);
                        
                        tasks.push(pushFile(user2, repo2, fullPath2, htmlContent, `DeepRead Archive: ${pageTitle}`, false)
                            .then(() => "✅ 归档(HTML)推送成功")
                            .catch(e => `❌ 归档推送失败: ${e}`));
                    }
                }

                if (tasks.length === 0) return alert("❌ 未配置相关仓库信息");

                const btn = document.querySelector('.dr-footer button'); // Just to give visual feedback on one button
                const originalText = btn ? btn.innerText : '';
                if (btn) { btn.innerText = '⏳ 推送中...'; btn.disabled = true; }

                Promise.all(tasks).then(results => {
                    alert(results.join('\n'));
                }).finally(() => {
                    if (btn) { btn.innerText = originalText; btn.disabled = false; }
                    // Reset UI to default state
                    UIManager.renderSummaryUI(content, true, 'Result'); 
                });
            }
        };

        // --- 初始化 DeepRead ---
        UIManager.init();
        let selTimer;
        document.addEventListener('selectionchange', () => {
            if (AppState.isModalOpen) return;
            clearTimeout(selTimer);
            selTimer = setTimeout(() => {
                const txt = window.getSelection().toString().trim();
                if (txt.length > 1) { AppState.analysisText = txt; UIManager.setFloatBtnVisible(true); } else { AppState.analysisText = ''; }
            }, 200);
        });
        const handleScroll = Utils.debounce(() => {
            const currentScrollY = window.scrollY;
            if (AppState.analysisText) { UIManager.setFloatBtnVisible(true); }
            else { if (currentScrollY > AppState.lastScrollY && currentScrollY > 100) { UIManager.setFloatBtnVisible(false); } else { UIManager.setFloatBtnVisible(true); } }
            AppState.lastScrollY = currentScrollY;
        }, 100);
        window.addEventListener('scroll', handleScroll);
        GM_registerMenuCommand("⚙️ ReadSense 设置", () => { UIManager.openModal('⚙️ 设置', 'SETTINGS'); UIManager.renderSettings(); });
    }

    // =========================================================================
    // [2] MODULE: Twitter/X Reader (仅 Twitter 生效)
    // =========================================================================
    function initTwitterReader() {
        if (!location.hostname.match(/(twitter|x)\.com/)) return;
        console.log('✅ Twitter/X Reader Module Loading...');

        const CACHE_PREFIX = 'twitter_ai_cache_v2_';
        const LAST_ANALYSIS_KEY = 'twitter_ai_last_session_data';

        function getActiveAIConfig(taskType) {
            const services = GM_getValue('deepread_ai_services_list', []);
            if (!services.length) return null;
            
            let targetId = '';
            if (taskType === 'ANALYSIS') targetId = GM_getValue('deepread_task_twitter_id', ''); 
            else if (taskType === 'TRANSLATE') targetId = GM_getValue('deepread_task_translate_id', ''); 
            
            const activeId = targetId || GM_getValue('deepread_active_ai_id', '');
            
            const active = services.find(s => s.id === activeId) || services[0];
            return {
                apiUrl: active.url,
                apiKey: active.key,
                modelName: active.model,
                systemPrompt: GM_getValue('twitterPrompt', `你是一位精通中英文的语言专家。请分析我提供的句子:\n1. 判断难度等级 (A1-C2)。\n2. 提供准确、优美的中文翻译。\n3. 句中关键短语及例句、例句翻译。\n请使用 Markdown 格式输出。`)
            };
        }

        GM_addStyle(`
            :root { --ai-bg-color: #fff; --ai-text-color: #222; --ai-modal-bg: rgba(0,0,0,0.6); --ai-content-bg: #fff; --ai-border-color: #eee; --ai-btn-color: #8b7355; --ai-tts-btn-color: #1d9bf0; --ai-trans-btn-color: #666; --ai-accent-color: #1d9bf0; --ai-quote-bg: #f7f9f9; --ai-code-bg: #f4f4f4; }
            @media (prefers-color-scheme: dark) { :root { --ai-bg-color: #1e1e1e; --ai-text-color: #e0e0e0; --ai-content-bg: #15202b; --ai-border-color: #38444d; --ai-btn-color: #ccc; --ai-tts-btn-color: #1d9bf0; --ai-trans-btn-color: #aaa; --ai-quote-bg: #1c2732; --ai-code-bg: #22303c; } }
            body.ai-scroll-locked { overflow: hidden !important; }
            .ai-original-box { background: rgba(128,128,128, 0.1); padding: 12px 18px; margin: 0; border-left: 3px solid var(--ai-accent-color); font-style: italic; font-size: 14px; max-height: 100px; overflow-y: auto; flex-shrink: 0; display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
            .ai-original-text-content { flex: 1; }
            .ai-inline-btn { display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; width: 18px; height: 18px; cursor: pointer; vertical-align: text-bottom; font-size: 0; transition: all 0.2s; position: relative; }
            .ai-inline-btn:hover { transform: scale(1.15); }
            .ai-inline-btn svg { width: 10px; height: 10px; fill: currentColor; }
            .ai-inline-btn.loading { animation: spin 1s linear infinite; border-color: transparent !important; border-top-color: currentColor !important; }
            .ai-analyze-btn { margin: 0 0 0 5px; background-color: rgba(139, 115, 85, 0.15); border: 1px solid rgba(139, 115, 85, 0.3); color: var(--ai-btn-color); }
            .ai-analyze-btn:hover { background-color: rgba(139, 115, 85, 0.25); }
            .ai-trans-btn { margin: 0 5px 0 0; background-color: rgba(128, 128, 128, 0.15); border: 1px solid rgba(128, 128, 128, 0.3); color: var(--ai-trans-btn-color); }
            .ai-trans-btn:hover { background-color: rgba(128, 128, 128, 0.25); color: #333; }
            .ai-translation-block { display: block; margin-top: 10px; padding: 10px 14px; background-color: var(--ai-quote-bg); border-left: 3px solid #999; color: var(--ai-text-color); font-size: 15px; line-height: 1.5; border-radius: 4px; width: 100%; box-sizing: border-box; animation: fadeIn 0.3s ease; }
            @keyframes fadeIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: translateY(0); } }
            .ai-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: var(--ai-modal-bg); display: none; justify-content: center; align-items: center; z-index: 9999; backdrop-filter: blur(2px); }
            .ai-modal-content { background: var(--ai-content-bg); color: var(--ai-text-color); border-radius: 12px; width: 90%; max-width: 500px; max-height: 80vh; display: flex; flex-direction: column; box-shadow: 0 10px 25px rgba(0,0,0,0.3); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 16px; user-select: text; }
            .ai-modal-header { padding: 15px 20px; border-bottom: 1px solid var(--ai-border-color); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; }
            .ai-modal-header h3 { margin: 0; font-size: 18px; font-weight: 700; }
            .ai-modal-body { padding: 20px; overflow-y: auto; flex: 1; line-height: 1.6; word-wrap: break-word; }
            .ai-modal-body h1, .ai-modal-body h2, .ai-modal-body h3, .ai-modal-body h4 { color: var(--ai-text-color); font-weight: 700; margin-top: 1.2em; margin-bottom: 0.6em; line-height: 1.3; }
            .ai-modal-body h3, .ai-modal-body h4 { font-size: 1.1em; color: var(--ai-accent-color); border-bottom: 1px dashed var(--ai-border-color); padding-bottom: 5px; }
            .ai-list-item { position: relative; padding-left: 15px; margin-bottom: 6px; }
            .ai-list-item::before { content: "•"; position: absolute; left: 0; color: var(--ai-accent-color); font-weight: bold; }
            .ai-modal-body strong { color: var(--ai-accent-color); background: rgba(29, 155, 240, 0.1); padding: 0 4px; border-radius: 3px; font-weight: 600; }
            .ai-modal-body blockquote { margin: 10px 0; padding: 10px 15px; background: var(--ai-quote-bg); border-left: 3px solid #ccc; border-radius: 4px; color: rgba(128,128,128, 0.9); }
            .ai-modal-body code { font-family: monospace; background: var(--ai-code-bg); padding: 2px 5px; border-radius: 3px; font-size: 0.9em; }
            .ai-hr { border: 0; border-top: 1px solid var(--ai-border-color); margin: 15px 0; }
            @keyframes spin { 100% { transform: rotate(360deg); } }
        `);

        function enhancedMarkdownParse(text) {
            if (!text) return '';
            let html = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
            html = html.replace(/^\s*---\s*$/gim, '<hr class="ai-hr">');
            html = html.replace(/^######\s+(.*$)/gim, '<h6>$1</h6>').replace(/^#####\s+(.*$)/gim, '<h5>$1</h5>').replace(/^####\s+(.*$)/gim, '<h4>$1</h4>').replace(/^###\s+(.*$)/gim, '<h3>$1</h3>').replace(/^##\s+(.*$)/gim, '<h2>$1</h2>').replace(/^#\s+(.*$)/gim, '<h1>$1</h1>');
            html = html.replace(/^\s*>\s+(.*$)/gim, '<blockquote>$1</blockquote>').replace(/^\s*[\*\-\+]\s+(.*$)/gim, '<div class="ai-list-item">$1</div>').replace(/^\s*\d+\.\s+(.*$)/gim, '<div class="ai-list-item ai-ordered">$1</div>');
            html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/__(.*?)__/g, '<strong>$1</strong>').replace(/\*([^\*]+)\*/g, '<em>$1</em>').replace(/_([^_]+)_/g, '<em>$1</em>').replace(/`([^`]+)`/g, '<code>$1</code>').replace(/\n/g, '<br>');
            return html;
        }

        let savedScrollTop = 0;
        function toggleScrollLock(locked) {
            const body = document.body;
            if (locked) { savedScrollTop = window.pageYOffset || document.documentElement.scrollTop; body.classList.add('ai-scroll-locked'); body.style.position = 'fixed'; body.style.top = `-${savedScrollTop}px`; body.style.width = '100%'; }
            else { body.classList.remove('ai-scroll-locked'); body.style.position = ''; body.style.top = ''; body.style.width = ''; window.scrollTo(0, savedScrollTop); }
        }

        function createModals() {
            if (document.getElementById('ai-reader-modal')) return;
            const resultModalHtml = `<div id="ai-reader-modal" class="ai-modal"><div class="ai-modal-content"><div class="ai-modal-header"><h3>🧠 AI 深度分析</h3></div><div id="ai-original-text" class="ai-original-box"></div><div id="ai-result-body" class="ai-modal-body"></div><div style="font-size:12px; text-align:center; color:#888; padding-bottom:10px; opacity:0.6;">点击单词查词 / 双击任意区域关闭</div></div></div>`;
            const div = document.createElement('div'); div.innerHTML = resultModalHtml; document.body.appendChild(div);
            window.addEventListener('click', (e) => { if (e.target.id === 'ai-reader-modal') { e.target.style.display = 'none'; toggleScrollLock(false); } });
            document.getElementById('ai-reader-modal').addEventListener('dblclick', (e) => { if (window.getSelection().toString().length > 0) return; if (e.target.closest('.tippy-box')) return; document.getElementById('ai-reader-modal').style.display = 'none'; toggleScrollLock(false); });
        }

        function renderAnalysisModal(text, mdContent) {
            createModals();
            const modal = document.getElementById('ai-reader-modal'); const resultBody = document.getElementById('ai-result-body'); const originalTextBody = document.getElementById('ai-original-text');
            originalTextBody.innerHTML = ''; const textSpan = document.createElement('div'); textSpan.className = 'ai-original-text-content'; textSpan.innerText = text; originalTextBody.appendChild(textSpan);
            resultBody.innerHTML = enhancedMarkdownParse(mdContent);
            DictHelper.initDictionary(resultBody, 'ai-reader-modal');
            modal.style.display = 'flex'; toggleScrollLock(true);
        }

        function showLastAnalysis() {
            const lastData = localStorage.getItem(LAST_ANALYSIS_KEY);
            if (!lastData) { alert('⚠️ 暂无最近分析记录'); return; }
            try { const { text, content } = JSON.parse(lastData); if (!text || !content) throw new Error('Invalid Data'); renderAnalysisModal(text, content); } catch (e) { alert('❌ 记录已损坏,无法恢复'); }
        }
        GM_registerMenuCommand("🐦 Twitter 最近分析", showLastAnalysis);

        function generateCacheKey(text, prompt) {
            let str = text + "|" + prompt; let hash = 0;
            for (let i = 0; i < str.length; i++) hash = ((hash << 5) - hash) + str.charCodeAt(i) | 0;
            return CACHE_PREFIX + Math.abs(hash);
        }

        async function handleInlineTranslation(btn, textContainer, textToTranslate) {
            const transType = GM_getValue('twitterTranslationType', 'AI');
            let transBlock = textContainer.querySelector('.ai-translation-block');
            if (transBlock) { transBlock.style.display = transBlock.style.display === 'none' ? 'block' : 'none'; return; }
            transBlock = document.createElement('div'); transBlock.className = 'ai-translation-block'; transBlock.innerHTML = '<span style="color:#999;font-style:italic;">⏳ 正在翻译...</span>';
            textContainer.appendChild(transBlock);
            btn.classList.add('loading');

            if (transType === 'GOOGLE') {
                const cacheKey = generateCacheKey(textToTranslate, "translate_google_v1");
                const cached = localStorage.getItem(cacheKey);
                if (cached) { try { transBlock.innerHTML = JSON.parse(cached).content; btn.classList.remove('loading'); return; } catch(e){} }
                GM_xmlhttpRequest({
                    method: "GET", url: `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=zh-CN&dt=t&q=${encodeURIComponent(textToTranslate)}`,
                    onload: (res) => {
                        try {
                            const data = JSON.parse(res.responseText); let result = ""; if (data && data[0]) { data[0].forEach(item => { if (item[0]) result += item[0]; }); }
                            transBlock.innerHTML = result.replace(/\n/g, '<br>'); try { localStorage.setItem(cacheKey, JSON.stringify({ content: transBlock.innerHTML })); } catch (e) { }
                        } catch (e) { transBlock.innerHTML = `<span style="color:red">❌ Google 翻译解析失败</span>`; }
                        btn.classList.remove('loading');
                    },
                    onerror: () => { transBlock.innerHTML = `<span style="color:red">❌ 网络错误 (Google)</span>`; btn.classList.remove('loading'); }
                });
                return;
            }

            const config = getActiveAIConfig('TRANSLATE');
            if (!config || !config.apiKey) { if (confirm('Twitter翻译 (AI模式) 需要 API Key。请前往 "⚙️ 设置" 配置。')) { /* No op */ } transBlock.remove(); btn.classList.remove('loading'); return; }
            
            const translatePrompt = "You are a professional translator. Translate the following text into Chinese (Simplified) directly. Do not include any explanations, just the translated text.";
            const cacheKey = generateCacheKey(textToTranslate, "translate_only_v2_" + config.modelName);
            const cached = localStorage.getItem(cacheKey);
            if (cached) { const data = JSON.parse(cached); transBlock.innerHTML = data.content; btn.classList.remove('loading'); return; }
            
            try {
                const responseText = await new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: "POST", url: config.apiUrl,
                        headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.apiKey}` },
                        data: JSON.stringify({ model: config.modelName, messages: [{ role: "system", content: translatePrompt }, { role: "user", content: textToTranslate }], temperature: 0.3 }),
                        onload: (res) => (res.status >= 200 && res.status < 300) ? resolve(res.responseText) : reject(new Error(res.status)),
                        onerror: () => reject(new Error("Network Error"))
                    });
                });
                const data = JSON.parse(responseText); const content = data.choices[0].message.content;
                transBlock.innerHTML = content.replace(/\n/g, '<br>') + `<div style="font-size:10px;color:#ccc;margin-top:5px;text-align:right">by ${config.modelName}</div>`;
                try { localStorage.setItem(cacheKey, JSON.stringify({ content: transBlock.innerHTML })); } catch (e) { }
            } catch (error) { transBlock.innerHTML = `<span style="color:red">❌ 翻译失败</span>`; console.error(error); } finally { btn.classList.remove('loading'); }
        }

        async function handleAnalysis(btn, textToAnalyze) {
            const config = getActiveAIConfig('ANALYSIS');
            if (!config || !config.apiKey) { if (confirm('Twitter助手需要 API Key。请前往 "⚙️ 设置" 配置。')) { /* No op */ } return; }
            createModals();
            const modal = document.getElementById('ai-reader-modal'); const resultBody = document.getElementById('ai-result-body'); const originalTextBody = document.getElementById('ai-original-text');
            originalTextBody.innerHTML = ''; originalTextBody.innerText = textToAnalyze;
            resultBody.innerHTML = `<div style="text-align:center; padding:20px; color:#888;">⏳ 正在连接 AI 大脑 (${config.modelName})...</div>`;
            modal.style.display = 'flex'; toggleScrollLock(true); btn.classList.add('loading');
            
            const cacheKey = generateCacheKey(textToAnalyze, config.systemPrompt + config.modelName);
            const cached = localStorage.getItem(cacheKey);
            const onSuccess = (mdContent) => { renderAnalysisModal(textToAnalyze, mdContent); btn.classList.remove('loading'); localStorage.setItem(LAST_ANALYSIS_KEY, JSON.stringify({ text: textToAnalyze, content: mdContent })); };
            if (cached) { const data = JSON.parse(cached); if (Date.now() - data.timestamp < 86400000) { onSuccess(data.content); return; } }
            try {
                const responseText = await new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: "POST", url: config.apiUrl,
                        headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.apiKey}` },
                        data: JSON.stringify({ model: config.modelName, messages: [{ role: "system", content: config.systemPrompt }, { role: "user", content: `请分析这段文本:\n"${textToAnalyze}"` }], temperature: 0.7 }),
                        onload: (res) => (res.status >= 200 && res.status < 300) ? resolve(res.responseText) : reject(new Error(res.responseText)),
                        onerror: () => reject(new Error("Network Error"))
                    });
                });
                const data = JSON.parse(responseText); const content = data.choices[0].message.content;
                try { localStorage.setItem(cacheKey, JSON.stringify({ content: content, timestamp: Date.now() })); } catch (e) { }
                onSuccess(content);
            } catch (error) { resultBody.innerHTML = `<p style="color:red;">❌ 分析失败: ${error.message}</p>`; btn.classList.remove('loading'); }
        }

        function processTweetText(element) {
            if (element.getAttribute('data-ai-processed') === 'true') return;
            if (element.innerText.trim().length < 2) return;
            const transBtn = document.createElement('span'); transBtn.className = 'ai-inline-btn ai-trans-btn';
            transBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M12.87 15.07l-2.54-2.51.03-.03A17.52 17.52 0 0 0 14.07 6H17V4h-7V2H8v2H1v2h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"/></svg>';
            transBtn.title = "AI 翻译此句";
            transBtn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const clone = element.cloneNode(true); clone.querySelectorAll('.ai-inline-btn').forEach(b => b.remove()); handleInlineTranslation(transBtn, element, clone.innerText); });
            if (element.firstChild) { element.insertBefore(transBtn, element.firstChild); } else { element.appendChild(transBtn); }
            
            const aiBtn = document.createElement('span'); aiBtn.className = 'ai-inline-btn ai-analyze-btn';
            aiBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 15h-2v-2h2zm0-4h-2V7h2z"/></svg>';
            aiBtn.title = "AI 分析此句";
            aiBtn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const clone = element.cloneNode(true); clone.querySelectorAll('.ai-inline-btn').forEach(b => b.remove()); handleAnalysis(aiBtn, clone.innerText); });
            element.appendChild(aiBtn);
            element.setAttribute('data-ai-processed', 'true');
        }

        function debounce(func, wait) {
            let timeout;
            return function(...args) {
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), wait);
            };
        }

        function initObserver() {
            const debouncedProcess = debounce(() => {
                document.querySelectorAll('[data-testid="tweetText"]').forEach(processTweetText);
            }, 500);

            const observer = new MutationObserver((mutations) => {
                let shouldProcess = false;
                for(let m of mutations) {
                    if (m.addedNodes.length > 0) { shouldProcess = true; break; }
                }
                if (shouldProcess) debouncedProcess();
            });
            observer.observe(document.body, { childList: true, subtree: true });
            document.querySelectorAll('[data-testid="tweetText"]').forEach(processTweetText);
        }
        window.addEventListener('load', () => { createModals(); initObserver(); });
        setTimeout(initObserver, 1500);
    }

    // =========================================================================
    // [3] INITIALIZATION
    // =========================================================================
    initDeepRead();
    initTwitterReader();

})();