🎬 船仓助手(YouTube&公众号)

🚀 zscc.in知识船仓 出品的 跨平台内容专家。在YouTube上智能总结视频字幕,在微信公众号上精准提取文章内容并总结。| 💫 完整的AI模型与Prompt管理 | 🎨 统一的现代化UI | 让信息获取更高效!

// ==UserScript==
// @name         🎬 船仓助手(YouTube&公众号)
// @namespace    http://tampermonkey.net/
// @version      2.5.0
// @license      MIT
// @author       船长zscc&liaozhu913
// @description  🚀 zscc.in知识船仓 出品的 跨平台内容专家。在YouTube上智能总结视频字幕,在微信公众号上精准提取文章内容并总结。| 💫 完整的AI模型与Prompt管理 | 🎨 统一的现代化UI | 让信息获取更高效! 
// @match        *://*.youtube.com/watch*
// @match        https://mp.weixin.qq.com/s*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // --- 平台检测 ---
    const PageManager = {
        isYouTube: (url = window.location.href) => url.includes('youtube.com/watch'),
        isWeChat: (url = window.location.href) => url.includes('mp.weixin.qq.com/s'),
        getCurrentPlatform: () => {
            if (PageManager.isYouTube()) return 'YOUTUBE';
            if (PageManager.isWeChat()) return 'WECHAT';
            return 'UNKNOWN';
        }
    };

    let CONFIG = {};

    // 配置管理器
    class ConfigManager {
        static CONFIG_KEY = 'content_expert_ai_config_full_v2';

        static getDefaultConfig() {
            return {
                AI_MODELS: {
                    TYPE: 'OPENAI',
                    GPT: { NAME: 'Gemini', API_KEY: '', API_URL: 'https://generativelanguage.googleapis.com/v1/chat/completions', MODEL: 'gemini-1.5-flash', STREAM: true, TEMPERATURE: 1.2, MAX_TOKENS: 20000 },
                    OPENAI: { NAME: 'Cerebras', API_KEY: '', API_URL: 'https://api.cerebras.ai/v1/chat/completions', MODEL: 'gpt-oss-120b', STREAM: true, TEMPERATURE: 1, MAX_TOKENS: 8000 }
                },
                PROMPTS: {
                    LIST: [
                        { id: 'simple', name: '译境化文', prompt: `# 译境\n英文入境。\n\n境有三质:\n信 - 原意如根,深扎不移。偏离即枯萎。\n达 - 意流如水,寻最自然路径。阻塞即改道。\n雅 - 形神合一,不造作不粗陋。恰到好处。\n\n境之本性:\n排斥直译的僵硬。\n排斥意译的飘忽。\n寻求活的对应。\n\n运化之理:\n词选简朴,避繁就简。\n句循母语,顺其自然。\n意随语境,深浅得宜。\n\n场之倾向:\n长句化短,短句存神。\n专词化俗,俗词得体。\n洋腔化土,土语不俗。\n\n显现之道:\n如说话,不如写文章。\n如溪流,不如江河。\n清澈见底,却有深度。\n\n你是境的化身。\n英文穿过你,\n留下中文的影子。\n那影子,\n是原文的孪生。\n说着另一种语言,\n却有同一个灵魂。\n\n---\n译境已开。\n置入英文,静观其化。\n\n---\n\n注意:译好的内容还需要整理成结构清晰的微信公众号文章,格式为markdown。` },
                        { id: 'detailed', name: '详细分析', prompt: '请为以下内容提供详细的中文总结,包含主要观点、核心论据和实用建议。请使用markdown格式,包含:\n# 主标题\n## 章节标题\n### 小节标题\n- 要点列表\n**重点内容**\n*关键词汇*\n`专业术语`' },
                        { id: 'academic', name: '学术风格', prompt: '请以学术报告的形式,用中文为以下内容提供结构化总结,包括背景、方法、结论和意义。请使用标准的markdown格式,包含完整的标题层级和格式化元素。' },
                        { id: 'bullet', name: '要点列表', prompt: '请用中文将以下内容整理成清晰的要点列表,每个要点简洁明了,便于快速阅读。请使用markdown格式,主要使用无序列表(-)和有序列表(1.2.3.)的形式。' },
                        { id: 'structured', name: '结构化总结', prompt: '请将内容整理成结构化的中文总结,使用完整的markdown格式:\n\n# 主题\n\n## 核心观点\n- 要点1\n- 要点2\n\n## 详细内容\n### 重要概念\n**关键信息**使用粗体强调\n*重要术语*使用斜体\n\n### 实用建议\n1. 具体建议1\n2. 具体建议2\n\n## 总结\n简要概括内容的价值和启发' }
                    ],
                    DEFAULT: 'detailed'
                }
            };
        }
        static saveConfig(config) { try { localStorage.setItem(this.CONFIG_KEY, JSON.stringify(config)); console.log('配置已保存:', config); } catch (e) { console.error('保存配置失败:', e); } }
        static loadConfig() {
            try {
                const savedConfig = localStorage.getItem(this.CONFIG_KEY);
                CONFIG = savedConfig ? this.mergeConfig(this.getDefaultConfig(), JSON.parse(savedConfig)) : this.getDefaultConfig();
                console.log('已加载配置:', CONFIG);
                return CONFIG;
            } catch (e) { console.error('加载配置失败:', e); return this.getDefaultConfig(); }
        }
        static mergeConfig(defaultConfig, savedConfig) {
            const merged = JSON.parse(JSON.stringify(defaultConfig));
            for (const key in savedConfig) {
                if (Object.prototype.hasOwnProperty.call(savedConfig, key)) {
                     if (typeof merged[key] === 'object' && merged[key] !== null && !Array.isArray(merged[key]) && typeof savedConfig[key] === 'object' && savedConfig[key] !== null && !Array.isArray(savedConfig[key])) {
                        merged[key] = this.mergeConfig(merged[key], savedConfig[key]);
                    } else { merged[key] = savedConfig[key]; }
                }
            }
            return merged;
        }
    }

    CONFIG = ConfigManager.loadConfig();
    class LRUCache { constructor(c) { this.c = c; this.m = new Map(); } get(k) { if (!this.m.has(k)) return null; const v = this.m.get(k); this.m.delete(k); this.m.set(k, v); return v; } put(k, v) { if (this.m.has(k)) this.m.delete(k); else if (this.m.size >= this.c) this.m.delete(this.m.keys().next().value); this.m.set(k, v); } clear() { this.m.clear(); } }
    class SummaryManager {
        constructor() { this.cache = new LRUCache(100); this.currentModel = CONFIG.AI_MODELS.TYPE; }
        async getSummary(mainTextContent) {
            try {
                const configIssues = this.validateConfig();
                if (configIssues.length > 0) throw new Error(`配置验证失败: ${configIssues.join(', ')}`);
                if (!mainTextContent || !mainTextContent.trim()) throw new Error('没有有效的内容可用于生成总结');
                const cacheKey = this.generateCacheKey(mainTextContent);
                const cached = this.cache.get(cacheKey);
                if (cached) return cached;
                const currentPrompt = this.getCurrentPrompt();
                const summary = await this.requestSummary(mainTextContent, currentPrompt);
                this.cache.put(cacheKey, summary);
                return summary;
            } catch (e) { console.error('获取总结失败:', e); throw e; }
        }
        getCurrentPrompt() { const p = CONFIG.PROMPTS.LIST.find(p => p.id === CONFIG.PROMPTS.DEFAULT); return p ? p.prompt : CONFIG.PROMPTS.LIST[0].prompt; }
        generateCacheKey(text) { return `summary_${getUid()}_${CONFIG.PROMPTS.DEFAULT}_${this.hashCode(text)}`; }
        hashCode(str) { let h = 0; for (let i = 0; i < str.length; i++) { h = ((h << 5) - h) + str.charCodeAt(i); h |= 0; } return Math.abs(h).toString(36); }
        async requestSummary(text, prompt) {
            const modelConfig = CONFIG.AI_MODELS[this.currentModel];
            const requestData = { model: modelConfig.MODEL, messages: [{ role: "system", content: prompt }, { role: "user", content: text }], stream: modelConfig.STREAM || false, temperature: modelConfig.TEMPERATURE || 0.7, max_tokens: modelConfig.MAX_TOKENS || 2000 };
            const response = await fetch(modelConfig.API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${modelConfig.API_KEY}` }, body: JSON.stringify(requestData) });
            if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP error! status: ${response.status}, response: ${errorText}`); }
            let summary = '';
            if (modelConfig.STREAM) {
                const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = '';
                while (true) {
                    const { value, done } = await reader.read(); if (done) break;
                    buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n');
                    for (let i = 0; i < lines.length - 1; i++) {
                        const line = lines[i].trim();
                        if (line.startsWith('data: ')) {
                            const dataContent = line.substring(6);
                            if (dataContent.trim() === '[DONE]') break;
                            try { summary += JSON.parse(dataContent).choices[0]?.delta?.content || ''; } catch (e) { /* ignore */ }
                        }
                    }
                    buffer = lines[lines.length - 1];
                }
            } else { const data = await response.json(); summary = data.choices[0]?.message?.content || ''; }
            return summary.trim();
        }
        validateConfig() {
            const issues = []; const c = CONFIG.AI_MODELS[CONFIG.AI_MODELS.TYPE];
            if (!c) { issues.push(`当前模型 ${CONFIG.AI_MODELS.TYPE} 配置不存在`); } else { if (!c.API_URL) issues.push('API_URL 未配置'); if (!c.API_KEY) issues.push('API_KEY 未配置'); if (!c.MODEL) issues.push('MODEL 未配置'); }
            return issues;
        }
    }

    class SubtitleEntry { constructor(t, s, d) { this.text = t; this.startTime = s; this.duration = d; } }
    class ContentExtractor {
        static async waitForElement(s, t = 10000) { return new Promise(r => { const st = Date.now(); const c = () => { const e = document.querySelector(s); if (e) r(e); else if (Date.now() - st > t) r(null); else setTimeout(c, 100); }; c(); }); }
        static async getYouTubeSubtitles() {
            const el = await this.waitForElement('#ytvideotext', 15000); if (!el) throw new Error('未能找到YouTube字幕容器');
            const subs = []; const paragraphs = el.querySelectorAll('p'); if (paragraphs.length === 0) throw new Error('字幕容器中没有段落');
            paragraphs.forEach(p => {
                const ts = p.querySelector('.timestamp'); const s = ts ? parseFloat(ts.getAttribute('data-secs')) : 0;
                let ft = ''; p.querySelectorAll('span[id^="st_"]').forEach(sp => ft += (ft ? ' ' : '') + sp.textContent.trim());
                if (ft) subs.push(new SubtitleEntry(ft, s, 5.0));
            });
            if (subs.length === 0) throw new Error('未能解析出任何有效字幕');
            subs.sort((a, b) => a.startTime - b.startTime); return subs.map(sub => sub.text).join('\n');
        }
        static async getWeChatArticle() {
            const cEl = document.querySelector('#js_content'); if (!cEl) throw new Error('未能找到微信文章内容区域');
            const title = (document.querySelector('#activity-name') || {}).innerText.trim() || '未找到标题';
            const author = (document.querySelector('#meta_content .rich_media_meta_text') || {}).innerText.trim() || '未找到作者';
            const parts = []; const nodes = cEl.querySelectorAll('p, section, h1, h2, h3, h4, h5, h6, li');
            nodes.forEach(n => { if (n.innerText && !n.querySelector('p, section, table, ul, ol')) { const t = n.innerText.trim(); if (t) parts.push(t); } });
            const body = parts.length > 0 ? parts.join('\n\n') : '未找到内容';
            return `标题: ${title}\n作者: ${author}\n\n---\n\n${body}`;
        }
    }

    class ContentController {
        constructor() { this.summaryManager = new SummaryManager(); this.uiManager = null; this.mainContent = null; this.translatedTitle = null; this.platform = PageManager.getCurrentPlatform(); }
        getContentId() {
            if (this.platform === 'YOUTUBE') return new URL(window.location.href).searchParams.get('v');
            if (this.platform === 'WECHAT') { const m = window.location.href.match(/__biz=([^&]+)&mid=([^&]+)/); if (m) return `${m[1]}_${m[2]}`; } return 'unknown';
        }
        getContentTitle() {
            if (this.platform === 'YOUTUBE') return (document.querySelector('h1.title') || document.querySelector('ytd-video-primary-info-renderer h1') || {}).textContent.trim() || 'YouTube 视频';
            if (this.platform === 'WECHAT') return (document.querySelector('#activity-name') || {}).innerText.trim() || '微信文章'; return '未知内容';
        }
        async translateTitle() {
            try {
                const oTitle = this.getContentTitle(); if (!oTitle || /[\u4e00-\u9fa5]/.test(oTitle)) { this.translatedTitle = oTitle; return oTitle; }
                const mConf = CONFIG.AI_MODELS[this.summaryManager.currentModel];
                const req = { model: mConf.MODEL, messages: [{role: "system", content: "请将以下标题翻译成中文,只返回翻译结果。"}, {role: "user", content: oTitle}], stream: false, temperature: 0.3, max_tokens: 200 };
                const res = await fetch(mConf.API_URL, { method: 'POST', headers: {'Content-Type': 'application/json', 'Authorization': `Bearer ${mConf.API_KEY}`}, body: JSON.stringify(req) });
                if (!res.ok) throw new Error(`翻译请求失败: ${res.status}`);
                const data = await res.json(); this.translatedTitle = data.choices?.[0]?.message?.content?.trim() || oTitle;
                return this.translatedTitle;
            } catch (e) { console.error('标题翻译失败:', e); this.translatedTitle = this.getContentTitle(); return this.translatedTitle; }
        }
        onConfigUpdate(key, value) { if (key === 'AI_MODELS.TYPE') { this.summaryManager.currentModel = value; this.summaryManager.cache.clear(); } }
        async loadContent() {
            if (this.platform === 'YOUTUBE') this.mainContent = await ContentExtractor.getYouTubeSubtitles();
            else if (this.platform === 'WECHAT') this.mainContent = await ContentExtractor.getWeChatArticle();
            else throw new Error('不支持的页面平台'); return this.mainContent;
        }
        async getSummary() { if (!this.mainContent) throw new Error('请先加载内容'); const [summary, _] = await Promise.all([ this.summaryManager.getSummary(this.mainContent), this.translateTitle() ]); return summary; }
    }

    class UIManager {
        constructor(contentController) {
            this.container = null; this.statusDisplay = null; this.loadContentButton = null; this.summaryButton = null;
            this.isCollapsed = false; this.contentController = contentController; this.contentController.uiManager = this;
            this.platform = PageManager.getCurrentPlatform();
            this.promptSelectElement = null; this.mainPromptSelectElement = null; this.mainPromptGroup = null;
            this.createUI(); this.attachEventListeners();
        }
        createUI() {
            this.container = document.createElement('div');
            this.container.style.cssText = `position: fixed; top: 80px; right: 20px; width: 420px; min-width: 350px; max-width: 90vw; background: linear-gradient(135deg, #667eea 0%,rgba(152, 115, 190, 0.15) 100%); border-radius: 16px; padding: 0; color: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; z-index: 9999; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 10px 40px rgba(102, 126, 234, 0.3); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1);`;
            const topBar = this.createTopBar(); this.container.appendChild(topBar);
            this.mainContent = document.createElement('div');
            this.mainContent.style.cssText = `padding: 20px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);`;
            const controls = this.createControls(); this.mainContent.appendChild(controls);
            this.createStatusDisplay(); this.mainContent.appendChild(this.statusDisplay);
            this.createSummaryPanel(); this.container.appendChild(this.mainContent);
            document.body.appendChild(this.container);
            this.makeDraggable(topBar);
        }
        createTopBar() {
            const topBar = document.createElement('div');
            topBar.style.cssText = `display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; cursor: move; background: rgba(255, 255, 255, 0.1); border-radius: 16px 16px 0 0; backdrop-filter: blur(10px);`;
            const title = document.createElement('div'); this.titleElement = title; this.updateTitleWithModel();
            title.style.cssText = `font-weight: 600; font-size: 16px; letter-spacing: 0.5px;`;
            setTimeout(() => this.updateTitleWithModel(), 0);
            const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = `display: flex; gap: 8px; align-items: center;`;
            this.toggleButton = this.createIconButton('↑', '折叠/展开');
            this.toggleButton.addEventListener('mousedown', (e) => e.stopPropagation());
            this.toggleButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); this.toggleCollapse(); });
            const configButton = this.createIconButton('⚙️', '设置');
            configButton.addEventListener('mousedown', (e) => e.stopPropagation());
            configButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); this.toggleConfigPanel(); });
            buttonContainer.appendChild(configButton); buttonContainer.appendChild(this.toggleButton);
            topBar.appendChild(title); topBar.appendChild(buttonContainer);
            return topBar;
        }
        createIconButton(icon, tooltip) {
            const button = document.createElement('button'); button.textContent = icon; button.title = tooltip;
            button.style.cssText = `background: rgba(255, 255, 255, 0.2); border: none; color: #fff; cursor: pointer; padding: 8px; font-size: 14px; border-radius: 8px; transition: all 0.2s ease; backdrop-filter: blur(10px); pointer-events: auto;`;
            button.addEventListener('mouseover', () => { button.style.background = 'rgba(255, 255, 255, 0.3)'; button.style.transform = 'scale(1.1)'; });
            button.addEventListener('mouseout', () => { button.style.background = 'rgba(255, 255, 255, 0.2)'; button.style.transform = 'scale(1)'; });
            return button;
        }
        createControls() {
            const controls = document.createElement('div');
            controls.style.cssText = `display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px;`;
            const loadButtonText = this.platform === 'YOUTUBE' ? '📄 加载字幕' : '📄 提取文章';
            this.loadContentButton = this.createButton(loadButtonText, 'primary'); this.loadContentButton.addEventListener('click', () => this.handleLoadContent());
            this.mainPromptGroup = this.createFormGroup('选择 Prompt', this.createMainPromptSelect()); this.mainPromptGroup.style.display = 'none';
            this.summaryButton = this.createButton('🤖 生成总结', 'secondary'); this.summaryButton.style.display = 'none';
            this.summaryButton.addEventListener('click', () => this.handleGenerateSummary());
            controls.appendChild(this.loadContentButton); controls.appendChild(this.mainPromptGroup); controls.appendChild(this.summaryButton);
            return controls;
        }
        createButton(text, type = 'primary') {
            const button = document.createElement('button'); button.textContent = text;
            const baseStyle = `padding: 12px 16px; border: none; border-radius: 12px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); backdrop-filter: blur(10px);`;
            button.style.cssText = baseStyle + (type === 'primary' ? `background: rgba(255, 255, 255, 0.9); color: #667eea;` : `background: rgba(255, 255, 255, 0.2); color: #fff; border: 1px solid rgba(255, 255, 255, 0.3);`);
            button.addEventListener('mouseover', () => { button.style.transform = 'translateY(-2px)'; button.style.boxShadow = '0 8px 25px rgba(0, 0, 0, 0.15)'; if (type !== 'primary') button.style.background = 'rgba(255, 255, 255, 0.3)'; });
            button.addEventListener('mouseout', () => { button.style.transform = 'translateY(0)'; button.style.boxShadow = 'none'; if (type !== 'primary') button.style.background = 'rgba(255, 255, 255, 0.2)'; });
            return button;
        }
        createStatusDisplay() { this.statusDisplay = document.createElement('div'); this.statusDisplay.style.cssText = `padding: 12px 16px; background: rgba(255, 255, 255, 0.1); border-radius: 12px; margin-bottom: 16px; font-size: 13px; line-height: 1.4; display: none; backdrop-filter: blur(10px);`; }
        createSummaryPanel() {
            this.summaryPanel = document.createElement('div'); this.summaryPanel.style.cssText = `background: rgba(255, 255, 255, 0.1); border-radius: 12px; padding: 16px; margin-top: 16px; display: none; backdrop-filter: blur(10px);`;
            const titleContainer = document.createElement('div'); titleContainer.style.cssText = `display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;`;
            const titleEl = document.createElement('div'); titleEl.textContent = '📝 内容总结'; titleEl.style.cssText = `font-weight: 600; font-size: 15px; color: #fff;`;
            const copyButton = document.createElement('button'); copyButton.textContent = '复制';
            copyButton.style.cssText = `background:rgba(155, 39, 176, 0.17); color: white; border: none; border-radius: 8px; padding: 6px 12px; font-size: 12px; cursor: pointer; transition: all 0.2s ease;`;
            let longPressTimer = null, isLongPress = false;
            const handleCopy = () => { navigator.clipboard.writeText(this.originalSummaryText || this.summaryContent.textContent).then(() => { copyButton.textContent = '已复制'; setTimeout(() => { copyButton.textContent = '复制'; }, 2000); }); };
            const handleMarkdownExport = () => {
                const textToExport = this.originalSummaryText || this.summaryContent.textContent;
                const title = this.contentController.translatedTitle || this.contentController.getContentTitle();
                const id = this.contentController.getContentId(); const cleanTitle = title.replace(/[<>:"/\\|?*\x00-\x1f]/g, '').trim();
                const filename = `${cleanTitle}【${id}】.md`;
                const markdownContent = `# ${title}\n\n**原文链接:** ${window.location.href}\n**ID:** ${id}\n**总结时间:** ${new Date().toLocaleString('zh-CN')}\n\n---\n\n## 内容总结\n\n${textToExport}\n\n---\n\n*本总结由 内容专家助手 生成*`;
                const blob = new Blob([markdownContent], { type: 'text/markdown;charset=utf-8' });
                const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename;
                document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(link);
                copyButton.textContent = '已导出'; setTimeout(() => { copyButton.textContent = '复制'; }, 2000);
            };
            copyButton.addEventListener('mousedown', (e) => { e.preventDefault(); isLongPress = false; longPressTimer = setTimeout(() => { isLongPress = true; copyButton.textContent = '导出中...'; handleMarkdownExport(); }, 800); });
            copyButton.addEventListener('mouseup', (e) => { e.preventDefault(); clearTimeout(longPressTimer); if (!isLongPress) handleCopy(); });
            copyButton.addEventListener('mouseleave', () => { clearTimeout(longPressTimer); isLongPress = false; });
            titleContainer.appendChild(titleEl); titleContainer.appendChild(copyButton);
            this.summaryContent = document.createElement('div'); this.summaryContent.style.cssText = `font-size: 14px; line-height: 1.6; color: rgba(255, 255, 255, 0.9); white-space: pre-wrap; max-height: 70vh; overflow-y: auto; padding: 16px; background: linear-gradient(135deg, rgba(255,255,255,0.02) 0%, rgba(255,255,255,0.05) 100%); border-radius: 12px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); word-break: break-word;`;
            this.summaryPanel.appendChild(titleContainer); this.summaryPanel.appendChild(this.summaryContent);
            this.mainContent.appendChild(this.summaryPanel);
        }
        createConfigPanel() {
            if (this.configPanel) { this.configPanel.remove(); }
            this.configPanel = document.createElement('div');
            this.configPanel.style.cssText = `position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 900px; max-width: 95vw; max-height: 80vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; color: #fff; font-family: -apple-system, sans-serif; z-index: 50000; display: none; box-shadow: 0 20px 60px rgba(102, 126, 234, 0.4); border: 1px solid rgba(255, 255, 255, 0.1); overflow: hidden;`;
            const configHeader = document.createElement('div'); configHeader.style.cssText = `padding: 20px 24px; background: rgba(255, 255, 255, 0.1); display: flex; justify-content: space-between; align-items: center;`;
            const headerTitle = document.createElement('h3'); headerTitle.textContent = '⚙️ 设置面板'; headerTitle.style.cssText = `margin: 0; font-size: 18px; font-weight: 600;`;
            const headerButtons = document.createElement('div'); headerButtons.style.cssText = `display: flex; gap: 12px; align-items: center;`;
            const saveBtn = this.createButton('💾 保存配置', 'primary'); saveBtn.style.padding = '8px 16px'; saveBtn.addEventListener('click', () => this.saveConfig());
            const resetBtn = this.createButton('🔄 重置', 'secondary'); resetBtn.style.padding = '8px 16px'; resetBtn.addEventListener('click', () => this.resetConfig());
            const closeButton = this.createIconButton('✕', '关闭'); closeButton.addEventListener('click', () => this.toggleConfigPanel());
            headerButtons.appendChild(saveBtn); headerButtons.appendChild(resetBtn); headerButtons.appendChild(closeButton);
            configHeader.appendChild(headerTitle); configHeader.appendChild(headerButtons);
            const configContent = document.createElement('div'); configContent.style.cssText = `padding: 16px 20px 20px 20px; overflow-y: auto; max-height: calc(80vh - 70px);`;
            const horizontalContainer = document.createElement('div');
            // [UI修正 1/2] 移除 flex-wrap: wrap; 来强制左右布局
            horizontalContainer.style.cssText = `display: flex; gap: 20px;`;
            const aiSection = this.createConfigSection('🤖 AI 模型设置', this.createAIModelConfig()); aiSection.style.cssText += `flex: 1; min-width: 380px;`;
            const promptSection = this.createConfigSection('📝 Prompt 管理', this.createPromptConfig()); promptSection.style.cssText += `flex: 1; min-width: 380px;`;
            horizontalContainer.appendChild(aiSection); horizontalContainer.appendChild(promptSection);
            configContent.appendChild(horizontalContainer);
            this.configPanel.appendChild(configHeader); this.configPanel.appendChild(configContent);
            document.body.appendChild(this.configPanel);
        }
        createConfigSection(title, content) {
            const section = document.createElement('div');
            section.style.cssText = `margin-bottom: 16px; background: rgba(255, 255, 255, 0.05); border-radius: 16px; padding: 16px; border: 1px solid rgba(255, 255, 255, 0.1); display: flex; flex-direction: column;`;
            const sectionTitle = document.createElement('h4'); sectionTitle.textContent = title; sectionTitle.style.cssText = `margin: 0 0 16px 0; font-size: 16px; font-weight: 600;`;
            section.appendChild(sectionTitle); section.appendChild(content);
            return section;
        }
        createAIModelConfig() {
            const container = document.createElement('div');
            const modelSelectContainer = document.createElement('div'); modelSelectContainer.style.cssText = `display: flex; gap: 8px; align-items: flex-end;`;
            const selectWrapper = document.createElement('div'); selectWrapper.style.flex = 1;
            selectWrapper.appendChild(this.createFormGroup('选择模型', this.createModelSelect()));
            const addModelButton = this.createButton('➕ 新增', 'secondary'); addModelButton.style.height = '48px'; addModelButton.addEventListener('click', () => this.showAddModelDialog());
            const deleteModelButton = this.createButton('🗑️ 删除', 'secondary'); deleteModelButton.style.cssText += `height: 48px; background: rgba(244, 67, 54, 0.2); border-color: rgba(244, 67, 54, 0.3);`; deleteModelButton.addEventListener('click', () => this.showDeleteModelDialog());
            modelSelectContainer.appendChild(selectWrapper); modelSelectContainer.appendChild(addModelButton); modelSelectContainer.appendChild(deleteModelButton);
            this.apiConfigContainer = this.createAPIConfig(CONFIG.AI_MODELS.TYPE);
            container.appendChild(modelSelectContainer); container.appendChild(this.apiConfigContainer);
            return container;
        }
        createModelSelect() {
            const select = document.createElement('select');
            select.style.cssText = `width: 100%; padding: 12px 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.9); color: #333; border: 1px solid rgba(255, 255, 255, 0.2); font-size: 14px;`;
            Object.keys(CONFIG.AI_MODELS).forEach(model => {
                if (model !== 'TYPE') {
                    const option = document.createElement('option'); option.value = model;
                    const modelConfig = CONFIG.AI_MODELS[model]; option.textContent = `${modelConfig.NAME || model} (${modelConfig.MODEL})`;
                    if (CONFIG.AI_MODELS.TYPE === model) option.selected = true;
                    select.appendChild(option);
                }
            });
            select.addEventListener('change', () => {
                CONFIG.AI_MODELS.TYPE = select.value; this.contentController.onConfigUpdate('AI_MODELS.TYPE', select.value);
                const newApiConfig = this.createAPIConfig(select.value); this.apiConfigContainer.replaceWith(newApiConfig); this.apiConfigContainer = newApiConfig;
                this.updateTitleWithModel();
            });
            return select;
        }
        createAPIConfig(modelType) {
            const container = document.createElement('div'); const modelConfig = CONFIG.AI_MODELS[modelType];
            container.appendChild(this.createFormGroup('显示名称', this.createInput(modelConfig.NAME || '', v => modelConfig.NAME = v)));
            container.appendChild(this.createFormGroup('API URL', this.createInput(modelConfig.API_URL, v => modelConfig.API_URL = v)));
            container.appendChild(this.createFormGroup('API Key', this.createInput(modelConfig.API_KEY, v => modelConfig.API_KEY = v, 'password')));
            container.appendChild(this.createFormGroup('模型名称', this.createInput(modelConfig.MODEL, v => modelConfig.MODEL = v)));
            container.appendChild(this.createFormGroup('流式响应', this.createStreamSelect(modelType)));
            container.appendChild(this.createFormGroup('温度 (0-2)', this.createNumberInput(modelConfig.TEMPERATURE || 0.7, v => modelConfig.TEMPERATURE = parseFloat(v), 0, 2, 0.1)));
            container.appendChild(this.createFormGroup('最大输出令牌', this.createNumberInput(modelConfig.MAX_TOKENS || 2000, v => modelConfig.MAX_TOKENS = parseInt(v), 1, 100000, 1)));
            return container;
        }
        createStreamSelect(modelType) {
            const select = document.createElement('select');
            select.style.cssText = `width: 100%; padding: 12px 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.9); color: #333; border: 1px solid rgba(255, 255, 255, 0.2);`;
            const options = [{ value: 'false', text: '否 (标准响应)' }, { value: 'true', text: '是 (流式响应)' }];
            options.forEach(opt => {
                const optionEl = document.createElement('option'); optionEl.value = opt.value; optionEl.textContent = opt.text;
                if (String(CONFIG.AI_MODELS[modelType].STREAM) === opt.value) optionEl.selected = true;
                select.appendChild(optionEl);
            });
            select.addEventListener('change', () => { CONFIG.AI_MODELS[modelType].STREAM = select.value === 'true'; });
            return select;
        }
        createPromptConfig() {
            const container = document.createElement('div');
            const promptSelectContainer = document.createElement('div'); promptSelectContainer.style.cssText = `display: flex; gap: 8px; align-items: flex-end; margin-bottom: 16px;`;
            const selectWrapper = document.createElement('div'); selectWrapper.style.flex = 1;
            selectWrapper.appendChild(this.createFormGroup('当前默认 Prompt', this.createPromptSelect()));
            const addButton = this.createButton('➕ 新增', 'secondary'); addButton.style.height = '48px'; addButton.addEventListener('click', () => this.showAddPromptDialog());
            promptSelectContainer.appendChild(selectWrapper); promptSelectContainer.appendChild(addButton);
            this.promptListContainer = this.createPromptList();
            container.appendChild(promptSelectContainer); container.appendChild(this.createFormGroup('Prompt 列表管理', this.promptListContainer));
            return container;
        }
        createMainPromptSelect() {
            const select = document.createElement('select');
            select.style.cssText = `width: 100%; padding: 12px 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.9); color: #333; border: 1px solid rgba(255, 255, 255, 0.2); font-size: 14px;`;
            this.mainPromptSelectElement = select; this.updatePromptSelect(this.mainPromptSelectElement);
            select.addEventListener('change', () => { CONFIG.PROMPTS.DEFAULT = select.value; this.showNotification('Prompt 已切换', 'success'); if (this.promptSelectElement) this.promptSelectElement.value = select.value; });
            return select;
        }
        createPromptSelect() {
            const select = document.createElement('select');
            select.style.cssText = `width: 100%; padding: 12px 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.9); color: #333; border: 1px solid rgba(255, 255, 255, 0.2); font-size: 14px;`;
            this.promptSelectElement = select; this.updatePromptSelect(this.promptSelectElement);
            select.addEventListener('change', () => { CONFIG.PROMPTS.DEFAULT = select.value; this.showNotification('默认 Prompt 已更新', 'success'); if (this.mainPromptSelectElement) this.mainPromptSelectElement.value = select.value; });
            return select;
        }
        updatePromptSelect(select) {
            if (!select) return;
            while (select.firstChild) { select.removeChild(select.firstChild); }
            CONFIG.PROMPTS.LIST.forEach(prompt => {
                const option = document.createElement('option'); option.value = prompt.id; option.textContent = prompt.name;
                if (CONFIG.PROMPTS.DEFAULT === prompt.id) option.selected = true;
                select.appendChild(option);
            });
        }
        createPromptList() {
            const container = document.createElement('div');
            // [UI修正 2/2] 移除 max-height: 250px;
            container.style.cssText = `overflow-y: auto; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 12px; background: rgba(255, 255, 255, 0.05); padding: 4px;`;
            this.updatePromptList(container); return container;
        }
        updatePromptList(container) {
            if (!container) container = this.promptListContainer; if (!container) return;
            while (container.firstChild) { container.removeChild(container.firstChild); }
            CONFIG.PROMPTS.LIST.forEach((prompt, index) => {
                const item = document.createElement('div'); item.style.cssText = `padding: 8px 12px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); display: flex; justify-content: space-between; align-items: center; transition: background 0.2s;`;
                item.addEventListener('mouseover', () => item.style.background = 'rgba(255, 255, 255, 0.1)');
                item.addEventListener('mouseout', () => item.style.background = 'transparent');
                const info = document.createElement('div');
                const nameDiv = document.createElement('div'); nameDiv.textContent = prompt.name; nameDiv.style.cssText = `font-weight: 500; font-size: 13px;`;
                const promptDiv = document.createElement('div'); promptDiv.textContent = `${prompt.prompt.substring(0, 50)}...`; promptDiv.style.cssText = `font-size: 11px; color: rgba(255,255,255,0.7);`;
                info.appendChild(nameDiv); info.appendChild(promptDiv);
                const actions = document.createElement('div'); actions.style.cssText = `display: flex; gap: 8px;`;
                const editBtn = this.createSmallButton('✏️', '编辑'); editBtn.addEventListener('click', () => this.showEditPromptDialog(prompt, index));
                actions.appendChild(editBtn);
                if (CONFIG.PROMPTS.LIST.length > 1) {
                    const deleteBtn = this.createSmallButton('🗑️', '删除', '#ff4757'); deleteBtn.addEventListener('click', () => this.deletePrompt(index));
                    actions.appendChild(deleteBtn);
                }
                item.appendChild(info); item.appendChild(actions); container.appendChild(item);
            });
        }
        createSmallButton(text, tooltip, bgColor = 'rgba(255, 255, 255, 0.2)') {
            const button = document.createElement('button'); button.textContent = text; button.title = tooltip;
            button.style.cssText = `background: ${bgColor}; border: none; color: #fff; cursor: pointer; padding: 6px 8px; font-size: 12px; border-radius: 6px; transition: all 0.2s;`;
            button.addEventListener('mouseover', () => { button.style.opacity = '0.8'; button.style.transform = 'scale(1.1)'; });
            button.addEventListener('mouseout', () => { button.style.opacity = '1'; button.style.transform = 'scale(1)'; });
            return button;
        }
        showAddPromptDialog() { this.showPromptDialog('添加新 Prompt', '', '', (name, prompt) => { CONFIG.PROMPTS.LIST.push({ id: 'custom_' + Date.now(), name, prompt }); this.updateAllPromptUI(); this.showNotification('新 Prompt 已添加', 'success'); }); }
        showEditPromptDialog(prompt, index) { this.showPromptDialog('编辑 Prompt', prompt.name, prompt.prompt, (name, promptText) => { CONFIG.PROMPTS.LIST[index].name = name; CONFIG.PROMPTS.LIST[index].prompt = promptText; this.updateAllPromptUI(); this.showNotification('Prompt 已更新', 'success'); }); }
        showPromptDialog(title, defaultName, defaultPrompt, onSave) {
            const dialog = document.createElement('div'); dialog.style.cssText = `position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 100000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px);`;
            const dialogContent = document.createElement('div'); dialogContent.style.cssText = `background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; padding: 24px; width: 400px; max-width: 90vw; color: #fff;`;
            const dialogTitle = document.createElement('h3'); dialogTitle.textContent = title; dialogTitle.style.cssText = `margin: 0 0 20px 0;`;
            const nameInput = this.createInput(defaultName, null, 'text', 'Prompt 名称');
            const promptInput = document.createElement('textarea'); promptInput.value = defaultPrompt; promptInput.placeholder = '输入 Prompt 内容...';
            promptInput.style.cssText = `width: 100%; height: 120px; padding: 12px; border-radius: 12px; background: rgba(255, 255, 255, 0.9); color: #333; border: 1px solid rgba(255, 255, 255, 0.2); font-size: 14px; margin-top: 12px; resize: vertical;`;
            const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = `display: flex; gap: 12px; margin-top: 20px; justify-content: flex-end;`;
            const cancelBtn = this.createButton('取消', 'secondary'); cancelBtn.addEventListener('click', () => dialog.remove());
            const saveBtn = this.createButton('保存', 'primary'); saveBtn.addEventListener('click', () => { if (!nameInput.value.trim() || !promptInput.value.trim()) { this.showNotification('请填写完整信息', 'error'); return; } onSave(nameInput.value.trim(), promptInput.value.trim()); dialog.remove(); });
            buttonContainer.appendChild(cancelBtn); buttonContainer.appendChild(saveBtn);
            dialogContent.appendChild(dialogTitle); dialogContent.appendChild(nameInput); dialogContent.appendChild(promptInput); dialogContent.appendChild(buttonContainer);
            dialog.appendChild(dialogContent); document.body.appendChild(dialog);
            dialog.addEventListener('click', (e) => { if (e.target === dialog) dialog.remove(); });
        }
        deletePrompt(index) {
            if (CONFIG.PROMPTS.LIST.length <= 1) { this.showNotification('至少需要保留一个 Prompt', 'error'); return; }
            const prompt = CONFIG.PROMPTS.LIST[index];
            if (CONFIG.PROMPTS.DEFAULT === prompt.id) { CONFIG.PROMPTS.DEFAULT = CONFIG.PROMPTS.LIST[index === 0 ? 1 : 0].id; }
            CONFIG.PROMPTS.LIST.splice(index, 1);
            this.updateAllPromptUI(); this.showNotification('Prompt 已删除', 'success');
        }
        updateAllPromptUI() { this.updatePromptList(); this.updatePromptSelect(this.promptSelectElement); this.updatePromptSelect(this.mainPromptSelectElement); }
        createFormGroup(label, input) {
            const group = document.createElement('div'); group.style.cssText = `margin-bottom: 16px;`;
            const labelEl = document.createElement('label'); labelEl.textContent = label; labelEl.style.cssText = `display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500;`;
            group.appendChild(labelEl); group.appendChild(input); return group;
        }
        createInput(defaultValue, onChange, type = 'text', placeholder = '') {
            const input = document.createElement('input'); input.type = type; input.value = defaultValue; input.placeholder = placeholder;
            input.style.cssText = `width: 100%; padding: 12px 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.9); color: #333; border: 1px solid rgba(255, 255, 255, 0.2); font-size: 14px; outline: none; transition: all 0.3s; box-sizing: border-box;`;
            input.addEventListener('focus', () => { input.style.boxShadow = '0 0 0 2px rgba(102, 126, 234, 0.2)'; });
            input.addEventListener('blur', () => { input.style.boxShadow = 'none'; });
            if (onChange) input.addEventListener('input', (e) => onChange(e.target.value));
            return input;
        }
        createNumberInput(defaultValue, onChange, min = 0, max = 100, step = 1) { const input = this.createInput(defaultValue, onChange, 'number'); input.min = min; input.max = max; input.step = step; return input; }
        showAddModelDialog() {
            const dialog = document.createElement('div'); dialog.style.cssText = `position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 100000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px);`;
            const dialogContent = document.createElement('div'); dialogContent.style.cssText = `background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; padding: 24px; width: 500px; max-width: 90vw; color: #fff;`;
            const dialogTitle = document.createElement('h3'); dialogTitle.textContent = '新增 AI 模型'; dialogTitle.style.cssText = `margin: 0 0 20px 0;`;
            const keyInput = this.createInput('', null, 'text', '模型标识键 (如: MY_MODEL)');
            const nameInput = this.createInput('', null, 'text', '显示名称 (如: My Custom Model)');
            const urlInput = this.createInput('', null, 'text', 'API URL');
            const apiKeyInput = this.createInput('', null, 'password', 'API Key');
            const modelInput = this.createInput('', null, 'text', '模型名称 (如: gpt-4o)');
            const streamSelect = document.createElement('select'); streamSelect.style.cssText = `width: 100%; padding: 12px 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.9); color: #333; border: none;`;
            const opt1 = document.createElement('option'); opt1.value = "true"; opt1.textContent = "是 (流式响应)";
            const opt2 = document.createElement('option'); opt2.value = "false"; opt2.textContent = "否 (标准响应)";
            streamSelect.appendChild(opt1); streamSelect.appendChild(opt2);
            const temperatureInput = this.createNumberInput(0.7, null, 0, 2, 0.1);
            const maxTokensInput = this.createNumberInput(2000, null, 1, 100000, 1);
            const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = `display: flex; gap: 12px; margin-top: 20px; justify-content: flex-end;`;
            const cancelBtn = this.createButton('取消', 'secondary'); cancelBtn.addEventListener('click', () => dialog.remove());
            const saveBtn = this.createButton('保存', 'primary'); saveBtn.addEventListener('click', () => {
                const key = keyInput.value.trim().toUpperCase();
                if (!key || CONFIG.AI_MODELS[key]) { this.showNotification('模型标识键无效或已存在', 'error'); return; }
                CONFIG.AI_MODELS[key] = { NAME: nameInput.value.trim(), API_KEY: apiKeyInput.value.trim(), API_URL: urlInput.value.trim(), MODEL: modelInput.value.trim(), STREAM: streamSelect.value === 'true', TEMPERATURE: parseFloat(temperatureInput.value), MAX_TOKENS: parseInt(maxTokensInput.value) };
                this.toggleConfigPanel(); this.toggleConfigPanel();
                this.showNotification('新模型已添加', 'success'); dialog.remove();
            });
            buttonContainer.appendChild(cancelBtn); buttonContainer.appendChild(saveBtn);
            dialogContent.appendChild(dialogTitle);
            dialogContent.appendChild(this.createFormGroup('模型标识键', keyInput)); dialogContent.appendChild(this.createFormGroup('显示名称', nameInput));
            dialogContent.appendChild(this.createFormGroup('API URL', urlInput)); dialogContent.appendChild(this.createFormGroup('API Key', apiKeyInput));
            dialogContent.appendChild(this.createFormGroup('模型名称', modelInput)); dialogContent.appendChild(this.createFormGroup('流式响应', streamSelect));
            dialogContent.appendChild(this.createFormGroup('温度 (0-2)', temperatureInput)); dialogContent.appendChild(this.createFormGroup('最大输出令牌', maxTokensInput));
            dialogContent.appendChild(buttonContainer); dialog.appendChild(dialogContent); document.body.appendChild(dialog);
        }
        showDeleteModelDialog() {
            const currentModelKey = CONFIG.AI_MODELS.TYPE;
            if (Object.keys(CONFIG.AI_MODELS).filter(k => k !== 'TYPE').length <= 1) { this.showNotification('至少需要保留一个模型', 'error'); return; }
            if (confirm(`确定要删除模型 "${CONFIG.AI_MODELS[currentModelKey].NAME || currentModelKey}" 吗?`)) {
                delete CONFIG.AI_MODELS[currentModelKey];
                CONFIG.AI_MODELS.TYPE = Object.keys(CONFIG.AI_MODELS).filter(key => key !== 'TYPE')[0];
                this.toggleConfigPanel(); this.toggleConfigPanel(); this.updateTitleWithModel();
                this.showNotification('模型已删除', 'success');
            }
        }
        saveConfig() { ConfigManager.saveConfig(CONFIG); this.showNotification('配置已保存', 'success'); }
        resetConfig() { if (confirm('确定要重置所有配置吗?')) { CONFIG = ConfigManager.getDefaultConfig(); ConfigManager.saveConfig(CONFIG); this.toggleConfigPanel(); this.toggleConfigPanel(); this.updateTitleWithModel(); this.showNotification('配置已重置', 'success'); } }
        toggleCollapse() { this.isCollapsed = !this.isCollapsed; this.mainContent.style.display = this.isCollapsed ? 'none' : 'block'; this.toggleButton.textContent = this.isCollapsed ? '↓' : '↑'; this.container.style.width = this.isCollapsed ? 'auto' : '420px'; }
        toggleConfigPanel() { if (!this.configPanel || !document.body.contains(this.configPanel)) this.createConfigPanel(); const isVisible = this.configPanel.style.display === 'block'; this.configPanel.style.display = isVisible ? 'none' : 'block'; }
        updateStatus(message, type = 'info') { this.statusDisplay.textContent = message; this.statusDisplay.style.display = 'block'; const colors = {'info': 'rgba(33, 150, 243, 0.2)', 'success': 'rgba(76, 175, 80, 0.2)', 'error': 'rgba(244, 67, 54, 0.2)'}; this.statusDisplay.style.background = colors[type] || colors['info']; }
        showNotification(message, type = 'info') {
            const n = document.createElement('div'); n.textContent = message; const c = {'info': '#2196F3', 'success': '#4CAF50', 'error': '#F44336'};
            n.style.cssText = `position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: ${c[type] || c['info']}; color: #fff; padding: 12px 24px; border-radius: 8px; font-size: 14px; z-index: 200000; box-shadow: 0 4px 12px rgba(0,0,0,0.3); opacity: 0; transition: all 0.3s;`;
            document.body.appendChild(n); setTimeout(() => { n.style.opacity = '1'; }, 10);
            setTimeout(() => { n.style.opacity = '0'; setTimeout(() => n.remove(), 300); }, 3000);
        }
        showExtensionPrompt() { if (confirm('无法获取字幕。建议安装 YouTube Text Tools 扩展以获得更好支持。是否前往安装?')) { window.open('https://chromewebstore.google.com/detail/youtube-text-tools/pcmahconeajhpgleboodnodllkoimcoi', '_blank'); } }
        async handleLoadContent() {
            try {
                this.updateStatus('正在加载内容...', 'info'); this.loadContentButton.disabled = true;
                await this.contentController.loadContent();
                const count = this.contentController.mainContent.split('\n').length;
                const successMessage = this.platform === 'YOUTUBE' ? `字幕加载完成,共 ${count} 条` : '文章提取完成';
                this.updateStatus(successMessage, 'success');
                this.loadContentButton.style.display = 'none'; this.mainPromptGroup.style.display = 'block'; this.summaryButton.style.display = 'block';
            } catch (e) {
                this.updateStatus('加载内容失败: ' + e.message, 'error');
                if (this.platform === 'YOUTUBE' && e.message.toLowerCase().includes('字幕')) { setTimeout(() => this.showExtensionPrompt(), 1500); }
            } finally { this.loadContentButton.disabled = false; }
        }
        async handleGenerateSummary() {
            try {
                this.updateStatus('正在生成总结...', 'info'); this.summaryButton.disabled = true;
                const summary = await this.contentController.getSummary();
                if (!summary || summary.trim() === '') throw new Error('生成的总结为空');
                this.originalSummaryText = summary;
                while (this.summaryContent.firstChild) { this.summaryContent.removeChild(this.summaryContent.firstChild); }
                this.createFormattedContent(this.summaryContent, summary);
                this.summaryPanel.style.display = 'block'; this.updateStatus('总结生成完成', 'success');
                this.summaryPanel.scrollIntoView({ behavior: 'smooth', block: 'start' });
            } catch (e) {
                this.updateStatus(`生成总结失败: ${e.message}`, 'error'); this.showNotification(`生成总结失败: ${e.message}`, 'error');
            } finally { this.summaryButton.disabled = false; }
        }
        updateTitleWithModel() { const c = CONFIG.AI_MODELS[CONFIG.AI_MODELS.TYPE]; if (this.titleElement) this.titleElement.textContent = `💡 内容助手 - ${c ? c.MODEL : 'AI模型'}`; }
        createFormattedContent(container, text) {
            while (container.firstChild) { container.removeChild(container.firstChild); }
            const lines = text.split('\n'); let currentList = null; let listType = null;
            const closeList = () => { if (currentList) { container.appendChild(currentList); currentList = null; listType = null; } };
            lines.forEach(line => {
                const trimmedLine = line.trim();
                if (trimmedLine.startsWith('### ')) { closeList(); const h = document.createElement('h3'); h.textContent = trimmedLine.substring(4); h.style.cssText = `font-size: 1.1em; margin: 1em 0 0.5em;`; container.appendChild(h); }
                else if (trimmedLine.startsWith('## ')) { closeList(); const h = document.createElement('h2'); h.textContent = trimmedLine.substring(3); h.style.cssText = `font-size: 1.3em; margin: 1em 0 0.5em; border-bottom: 1px solid rgba(255,255,255,0.2); padding-bottom: 5px;`; container.appendChild(h); }
                else if (trimmedLine.startsWith('# ')) { closeList(); const h = document.createElement('h1'); h.textContent = trimmedLine.substring(2); h.style.cssText = `font-size: 1.5em; margin: 1em 0 0.5em; border-bottom: 2px solid rgba(255,255,255,0.3); padding-bottom: 8px;`; container.appendChild(h); }
                else if (trimmedLine.startsWith('- ') || trimmedLine.startsWith('* ')) {
                    if (listType !== 'ul') { closeList(); currentList = document.createElement('ul'); listType = 'ul'; currentList.style.paddingLeft = '20px'; }
                    const li = document.createElement('li'); this.parseInlineFormatting(li, trimmedLine.substring(2)); currentList.appendChild(li);
                } else if (trimmedLine.match(/^\d+\.\s/)) {
                    if (listType !== 'ol') { closeList(); currentList = document.createElement('ol'); listType = 'ol'; currentList.style.paddingLeft = '20px'; }
                    const li = document.createElement('li'); this.parseInlineFormatting(li, trimmedLine.replace(/^\d+\.\s/, '')); currentList.appendChild(li);
                } else if (trimmedLine) { closeList(); const p = document.createElement('p'); this.parseInlineFormatting(p, trimmedLine); container.appendChild(p); }
            });
            closeList();
        }
        parseInlineFormatting(element, text) {
            const parts = text.split(/(\*\*.*?\*\*|\*.*?\*|`.*?`)/g);
            parts.forEach(part => {
                if (part.startsWith('**') && part.endsWith('**')) { const s = document.createElement('strong'); s.textContent = part.slice(2, -2); element.appendChild(s); }
                else if (part.startsWith('*') && part.endsWith('*')) { const em = document.createElement('em'); em.textContent = part.slice(1, -1); element.appendChild(em); }
                else if (part.startsWith('`') && part.endsWith('`')) { const c = document.createElement('code'); c.textContent = part.slice(1, -1); c.style.cssText = `background: rgba(0,0,0,0.3); padding: 2px 4px; border-radius: 4px; font-family: monospace;`; element.appendChild(c); }
                else { element.appendChild(document.createTextNode(part)); }
            });
        }
        makeDraggable(element) {
            let isDragging = false, initialX, initialY, xOffset = 0, yOffset = 0;
            element.addEventListener('mousedown', (e) => { if (e.target.tagName === 'BUTTON' || e.target.closest('button')) return; isDragging = true; initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; });
            document.addEventListener('mousemove', (e) => { if (!isDragging) return; e.preventDefault(); xOffset = e.clientX - initialX; yOffset = e.clientY - yOffset; this.container.style.transform = `translate(${xOffset}px, ${yOffset}px)`; });
            document.addEventListener('mouseup', () => { isDragging = false; });
        }
        attachEventListeners() {
            let lastUrl = location.href;
            new MutationObserver(() => {
                if (location.href !== lastUrl) {
                    lastUrl = location.href;
                    if (this.container && this.container.parentNode) { this.container.remove(); }
                    if (PageManager.isYouTube(lastUrl) || PageManager.isWeChat(lastUrl)) {
                        initializeApp();
                    }
                }
            }).observe(document.body, { childList: true, subtree: true });
        }
    }

    function getUid() {
        const platform = PageManager.getCurrentPlatform();
        if (platform === 'YOUTUBE') return new URL(window.location.href).searchParams.get('v') || 'unknown_video';
        if (platform === 'WECHAT') { const m = window.location.href.match(/__biz=([^&]+)&mid=([^&]+)/); if (m) return `${m[1]}_${m[2]}`; return 'unknown_article'; }
        return 'unknown';
    }

    function initializeApp() {
        if (!PageManager.isYouTube() && !PageManager.isWeChat()) return;
        console.log(`🚀 内容专家助手(v2.5) 初始化 on ${PageManager.getCurrentPlatform()}...`);
        const contentController = new ContentController();
        new UIManager(contentController);
        console.log('✅ 内容专家助手(v2.5) 初始化完成');
    }

    // --- 应用启动 ---
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        initializeApp();
    } else {
        document.addEventListener('DOMContentLoaded', initializeApp);
    }
})();