Greasy Fork

Greasy Fork is available in English.

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

🚀 zscc.in 知识船仓·公益社区 出品的 跨平台内容专家。在YouTube、B站上智能总结视频字幕,在微信公众号上精准提取文章内容并总结。| 💫 完整的AI模型与Prompt管理 | 🎨 统一的现代化UI | 让信息获取更高效!安装短链:dub.sh/iytb 。建议同时安装 [YouTube Text Tools](https://dub.sh/ytbcc) 字幕插件,获得更快更好的YouTube字幕提取效果。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         🎬 船仓AI助手(YouTube&公众号&B站)
// @version      5.6
// @license      MIT
// @author       船长zscc&liaozhu913
// @description  🚀 zscc.in 知识船仓·公益社区 出品的 跨平台内容专家。在YouTube、B站上智能总结视频字幕,在微信公众号上精准提取文章内容并总结。| 💫 完整的AI模型与Prompt管理 | 🎨 统一的现代化UI | 让信息获取更高效!安装短链:dub.sh/iytb 。建议同时安装 [YouTube Text Tools](https://dub.sh/ytbcc) 字幕插件,获得更快更好的YouTube字幕提取效果。
// @match        *://*.youtube.com/watch*
// @match        https://mp.weixin.qq.com/s*
// @match        *://*.bilibili.com/video/*
// @match        *://*.bilibili.com/cheese/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      *
// @connect      api.bilibili.com
// @connect      *.hdslb.com
// @connect      aisubtitle.hdslb.com
// @connect      api.cerebras.ai
// @connect      api.siliconflow.cn
// @connect      generativelanguage.googleapis.com
// @connect      api.zscc.in
// @connect      publishmarkdown.com
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.min.js
// @namespace http://tampermonkey.net/
// ==/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'),
        isBilibili: (url = window.location.href) => url.includes('bilibili.com/video'),
        getCurrentPlatform: () => {
            if (PageManager.isYouTube()) return 'YOUTUBE';
            if (PageManager.isWeChat()) return 'WECHAT';
            if (PageManager.isBilibili()) return 'BILIBILI';
            return 'UNKNOWN';
        }
    };

    let CONFIG = {};

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

        static getDefaultConfig() {
            return {
                AI_MODELS: {
                    TYPE: 'CHUANCANG',
                    CHUANCANG: {
                        NAME: '船仓API',
                        API_TYPE: 'openai',
                        API_KEY: '',
                        API_URL: 'https://api.zscc.in/v1/chat/completions',
                        MODEL: '',
                        STREAM: true,
                        TEMPERATURE: 1,
                        MAX_TOKENS: 20000,
                        REASONING_EFFORT: 'none',
                        AVAILABLE_MODELS: []
                    },
                    GEMINI: {
                        NAME: 'Gemini',
                        API_TYPE: 'gemini',
                        API_KEY: '',
                        API_URL: 'https://generativelanguage.googleapis.com/v1beta/models/',
                        MODEL: '',
                        STREAM: false,
                        TEMPERATURE: 0.7,
                        MAX_TOKENS: 8192,
                        REASONING_EFFORT: 'none',
                        AVAILABLE_MODELS: []
                    },
                    ANTHROPIC: {
                        NAME: 'Anthropic',
                        API_TYPE: 'anthropic',
                        API_KEY: '',
                        API_URL: 'https://api.anthropic.com/v1/messages',
                        MODEL: '',
                        STREAM: false,
                        TEMPERATURE: 0.7,
                        MAX_TOKENS: 4096,
                        REASONING_EFFORT: 'none',
                        AVAILABLE_MODELS: []
                    },
                    CEREBRAS: {
                        NAME: 'Cerebras',
                        API_TYPE: 'openai',
                        API_KEY: '',
                        API_URL: 'https://api.cerebras.ai/v1/chat/completions',
                        MODEL: '',
                        STREAM: false,
                        TEMPERATURE: 0.7,
                        MAX_TOKENS: 20000,
                        REASONING_EFFORT: 'none',
                        AVAILABLE_MODELS: []
                    }
                },
                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'
                },
                PUBLISH_MARKDOWN: {
                    API_KEY: '',
                    ENABLED: false
                },
                HISTORY: {
                    MAX_ITEMS: 50
                },
                APPEARANCE: {
                    THEME: 'default'
                }
            };
        }
        // 使用 GM_setValue/GM_getValue 实现跨平台全局配置共享
        static saveConfig(config) { try { GM_setValue(this.CONFIG_KEY, config); console.log('配置已保存:', config); } catch (e) { console.error('保存配置失败:', e); } }
        static loadConfig() {
            try {
                const savedConfig = GM_getValue(this.CONFIG_KEY, null);
                // GM_getValue 直接返回对象,无需 JSON.parse
                // 兼容旧的字符串格式(如果用户之前用 localStorage 存过)
                let configToMerge = savedConfig;
                if (typeof savedConfig === 'string') {
                    try { configToMerge = JSON.parse(savedConfig); } catch (e) { configToMerge = null; }
                }

                const defaultConfig = this.getDefaultConfig();
                CONFIG = configToMerge ? this.mergeConfig(defaultConfig, configToMerge) : defaultConfig;

                // --- 关键修复:处理用户已删除的模型 ---
                // mergeConfig 会把 default 有但 saved 没有的 key 加回来。
                // 如果用户明确删除了某个默认模型,我们需要再次把它移除。
                if (configToMerge && configToMerge.AI_MODELS) {
                    const savedModels = configToMerge.AI_MODELS;
                    const mergedModels = CONFIG.AI_MODELS;

                    // 遍历 mergedModels (它现在包含了 defaults 的所有 key)
                    Object.keys(mergedModels).forEach(key => {
                        if (key === 'TYPE') return; // TYPE 字段保留

                        // 如果这个 key 在 default 中存在(说明是预置模型)
                        // 但是在 savedConfig 中不存在(说明用户把它删了)
                        // 那么我们应该把它从最终配置中移除
                        // 注意:我们只处理 default 中有的 key。如果是用户自定义的新增 key,mergeConfig 已经正确保留了。
                        if (defaultConfig.AI_MODELS[key] && !savedModels[key]) {
                            console.log(`[ConfigManager] Detect deleted default model: ${key}, removing from config.`);
                            delete mergedModels[key];
                        }
                    });
                }

                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;
        }
    }

    // --- 主题定义 ---
    const THEMES = {
        'default': {
            name: '船仓红韵',
            styles: {
                h1: {
                    first: `font-size: 1.4em; margin: 0 -16px 1em -16px; padding: 16px 20px; font-weight: 700; color: #fff; background: linear-gradient(135deg, #c83232 0%, #e04545 100%); border-radius: 8px; box-shadow: 0 4px 15px rgba(200, 50, 50, 0.25);`,
                    normal: `font-size: 1.4em; margin: 1.2em 0 0.7em; font-weight: 700; color: #111; border-bottom: 3px solid rgba(200, 50, 50, 0.4); padding-bottom: 12px;`
                },
                h2: `font-size: 1.3em; margin: 1.5em 0 0.8em; font-weight: 700; color: #222; border-bottom: 2px solid rgba(200, 50, 50, 0.25); padding-bottom: 10px;`,
                h3: `font-size: 1.1em; margin: 1.4em 0 0.7em; font-weight: 600; color: #333;`,
                h4: `font-size: 1em; margin: 1.2em 0 0.6em; font-weight: 600; color: #3a3a3a;`,
                h5: `font-size: 0.9em; margin: 1em 0 0.5em; font-weight: 600; color: #444;`,
                h6: `font-size: 0.85em; margin: 1em 0 0.5em; font-weight: 600; color: #555;`,
                blockquote: `margin: 1.2em 0; padding: 14px 18px; border-left: 4px solid #c83232; background: rgba(200, 50, 50, 0.06); border-radius: 0 10px 10px 0; color: #555; font-style: italic; line-height: 1.8;`,
                strong: `font-weight: 700; color: #b22222;`,
                em: `font-style: italic; color: #c83232;`,
                code: `background: rgba(200, 50, 50, 0.1); color: #b22222; padding: 2px 6px; border-radius: 4px; font-family: 'SF Mono', Monaco, monospace; font-size: 0.9em;`,
                link: `color: #c83232; text-decoration: underline; text-underline-offset: 2px;`,
                del: `text-decoration: line-through; color: #888;`,
                hr: `border: none; height: 2px; background: linear-gradient(to right, transparent, rgba(200, 50, 50, 0.3), transparent); margin: 1.8em 0;`,
                th: `padding: 10px 12px; background: rgba(200, 50, 50, 0.1); border: 1px solid rgba(200, 50, 50, 0.2); font-weight: 600; text-align: left; vertical-align: top;`,
                td: `padding: 8px 12px; border: 1px solid rgba(0, 0, 0, 0.1); text-align: left; vertical-align: top;`,
                pre: `margin: 1em 0; padding: 16px; background: #1e1e1e; border-radius: 8px; overflow-x: auto; border: 1px solid rgba(200, 50, 50, 0.2);`,
                code_block: `font-family: 'SF Mono', Monaco, 'Courier New', monospace; font-size: 13px; color: #d4d4d4; line-height: 1.5; white-space: pre;`,
                checkbox_checked: `color: #4caf50; font-size: 1.1em;`,
                checkbox_unchecked: `color: #888; font-size: 1.1em;`,
                p: `margin: 1em 0; line-height: 1.85; color: #333; text-align: justify;`,
                li: `margin: 0.5em 0; line-height: 1.75; color: #444;`,
                ul: `padding-left: 24px; margin: 1em 0; list-style-type: disc;`,
                ol: `padding-left: 24px; margin: 1em 0;`
            }
        },
        'spring': {
            name: '春日物语',
            styles: {
                h1: {
                    first: `font-size: 1.4em; margin: 0 -16px 1em -16px; padding: 16px 20px; font-weight: 600; color: #2c3e50; background: linear-gradient(to bottom, #effaf6, #d7f0e5); border-bottom: 2px solid #42b983; border-radius: 8px 8px 0 0;`,
                    normal: `font-size: 1.5em; margin: 1.2em 0 0.7em; font-weight: 600; color: #2c3e50; padding-bottom: 0.3em; border-bottom: 2px solid #42b983;`
                },
                h2: `font-size: 1.3em; margin: 1.5em 0 0.8em; font-weight: 600; color: #2c3e50; border-bottom: 1px dashed #42b983; padding-bottom: 8px;`,
                h3: `font-size: 1.1em; margin: 1.4em 0 0.7em; font-weight: 600; color: #2c3e50; padding-left: 8px; border-left: 4px solid #42b983; line-height: 1.2em;`,
                h4: `font-size: 1em; margin: 1.2em 0 0.6em; font-weight: 600; color: #42b983;`,
                h5: `font-size: 0.9em; margin: 1em 0 0.5em; font-weight: 600; color: #555;`,
                h6: `font-size: 0.85em; margin: 1em 0 0.5em; font-weight: 600; color: #777;`,
                blockquote: `margin: 1.2em 0; padding: 14px 18px; border-left: 4px solid #42b983; background: #f8fdfa; border-radius: 4px; color: #555; line-height: 1.8;`,
                strong: `font-weight: 700; color: #42b983; margin: 0 2px;`,
                em: `font-style: italic; color: #42b983;`,
                code: `background: #f3fcf8; color: #2c3e50; padding: 2px 6px; border-radius: 4px; font-family: 'SF Mono', Monaco, monospace; font-size: 0.9em; border: 1px solid #e0f2ea;`,
                link: `color: #42b983; text-decoration: none; border-bottom: 1px solid #42b983; transition: all 0.2s;`,
                del: `text-decoration: line-through; color: #aaa;`,
                hr: `border: none; height: 2px; background: #e0f2ea; margin: 2em 0;`,
                th: `padding: 10px 12px; background: #42b983; color: white; border: 1px solid #3aa876; font-weight: 600; text-align: left; vertical-align: top;`,
                td: `padding: 8px 12px; border: 1px solid rgba(0, 0, 0, 0.05); text-align: left; vertical-align: top;`,
                pre: `margin: 1em 0; padding: 16px; background: #f8fdfa; border-left: 4px solid #42b983; border-radius: 4px; overflow-x: auto; border: 1px solid #e0f2ea;`,
                code_block: `font-family: 'SF Mono', Monaco, 'Courier New', monospace; font-size: 13px; color: #2c3e50; line-height: 1.5; white-space: pre;`,
                checkbox_checked: `color: #42b983; font-size: 1.1em;`,
                checkbox_unchecked: `color: #ccc; font-size: 1.1em;`,
                p: `margin: 1em 0; line-height: 1.8; color: #2c3e50; text-align: justify;`,
                li: `margin: 0.5em 0; line-height: 1.75; color: #34495e;`,
                ul: `padding-left: 24px; margin: 1em 0; list-style-type: disc; color: #42b983;`,
                ol: `padding-left: 24px; margin: 1em 0; color: #42b983;`
            }
        }
    };

    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;
            this.keyIndex = 0; // 用于多 Key 轮询
        }
        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); }
        // ++ 多 Key 轮询支持的请求函数 ++
        async requestSummary(text, prompt) {
            const modelConfig = CONFIG.AI_MODELS[this.currentModel];

            // === 解析多个 API Key(支持逗号分隔) ===
            const apiKeyString = modelConfig.API_KEY || '';
            const apiKeys = apiKeyString.includes(',')
                ? apiKeyString.split(',').map(k => k.trim()).filter(k => k.length > 0)
                : [apiKeyString.trim()];

            if (apiKeys.length === 0) {
                throw new Error('API Key 未配置');
            }

            // 使用 API_TYPE 字段判断请求格式,兼容旧配置
            const apiType = modelConfig.API_TYPE || (modelConfig.API_URL.includes('generativelanguage') ? 'gemini' : 'openai');

            // === 轮询逻辑:选择当前使用的 Key ===
            let currentKey = apiKeys[this.keyIndex % apiKeys.length];
            console.log(`[API Key 轮询] 总共 ${apiKeys.length} 个 Key,当前使用索引: ${this.keyIndex % apiKeys.length}`);

            // 请求成功后递增索引(轮询到下一个 Key)
            const incrementKeyIndex = () => {
                this.keyIndex = (this.keyIndex + 1) % apiKeys.length;
            };

            // === 最多重试 3 次,失败时切换 Key ===
            const maxRetries = 3;
            const retryDelay = 2000; // 每次重试间隔 2 秒

            for (let attempt = 1; attempt <= maxRetries; attempt++) {
                try {
                    // 每次重试时选择当前的 Key
                    currentKey = apiKeys[(this.keyIndex + attempt - 1) % apiKeys.length];
                    console.log(`[尝试 ${attempt}/${maxRetries}] 使用 Key 索引: ${(this.keyIndex + attempt - 1) % apiKeys.length}`);

                    let requestUrl, requestBody, requestHeaders = { 'Content-Type': 'application/json' };

                    // 根据 API 类型构建请求
                    if (apiType === 'gemini') {
                        requestUrl = `${modelConfig.API_URL}${modelConfig.MODEL}:generateContent?key=${currentKey}`;
                        requestBody = { contents: [{ parts: [{ text: `${prompt}\n\n---\n\n${text}` }] }] };
                    } else if (apiType === 'anthropic') {
                        requestUrl = modelConfig.API_URL;
                        if (!requestUrl.endsWith('/messages')) {
                            requestUrl = requestUrl.replace(/\/$/, '') + '/messages';
                        }
                        requestHeaders['x-api-key'] = currentKey;
                        requestHeaders['anthropic-version'] = '2023-06-01';
                        requestHeaders['content-type'] = 'application/json';
                        delete requestHeaders['Authorization'];
                        requestBody = {
                            model: modelConfig.MODEL,
                            messages: [{ role: 'user', content: `${prompt}\n\n---\n\n${text}` }],
                            max_tokens: modelConfig.MAX_TOKENS || 20000,
                            temperature: modelConfig.TEMPERATURE || 0.7,
                            stream: false
                        };
                    } else {
                        // OpenAI Compatible
                        requestUrl = modelConfig.API_URL;
                        if (apiType === 'openai' && !requestUrl.includes('/chat/completions')) {
                            if (requestUrl.endsWith('/v1') || requestUrl.endsWith('/v1/')) {
                                requestUrl = requestUrl.replace(/\/$/, '') + '/chat/completions';
                            } else if (!requestUrl.includes('/v1/')) {
                                requestUrl = requestUrl.replace(/\/$/, '') + '/v1/chat/completions';
                            }
                        }
                        requestHeaders['Authorization'] = `Bearer ${currentKey}`;
                        requestBody = {
                            model: modelConfig.MODEL,
                            messages: [{ role: "system", content: prompt }, { role: "user", content: text }],
                            stream: false,
                            temperature: modelConfig.TEMPERATURE || 0.7,
                            max_tokens: modelConfig.MAX_TOKENS || 2000
                        };
                        if (modelConfig.REASONING_EFFORT && modelConfig.REASONING_EFFORT !== 'none') {
                            requestBody.reasoning_effort = modelConfig.REASONING_EFFORT;
                        }
                    }

                    // 发起请求
                    const result = await new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'POST',
                            url: requestUrl,
                            headers: requestHeaders,
                            data: JSON.stringify(requestBody),
                            timeout: 60000, // 60 秒超时
                            onload: function (response) {
                                if (response.status >= 200 && response.status < 300) {
                                    try {
                                        const data = JSON.parse(response.responseText);
                                        let summary = '';
                                        if (apiType === 'gemini') {
                                            summary = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
                                        } else if (apiType === 'anthropic') {
                                            summary = data.content?.[0]?.text || '';
                                        } else {
                                            summary = data.choices[0]?.message?.content || '';
                                        }
                                        resolve(summary.trim());
                                    } catch (e) {
                                        reject(new Error('解析响应JSON失败: ' + e.message));
                                    }
                                } else {
                                    // HTTP 错误,触发重试
                                    reject(new Error(`HTTP ${response.status}: ${response.responseText}`));
                                }
                            },
                            onerror: function (response) {
                                // 网络错误,触发重试
                                reject(new Error('网络请求失败: ' + (response.statusText || '未知错误')));
                            },
                            ontimeout: function () {
                                // 超时错误,触发重试
                                reject(new Error('请求超时'));
                            }
                        });
                    });

                    // === 请求成功,递增索引并返回结果 ===
                    console.log('[API Key 轮询] 请求成功');
                    incrementKeyIndex();
                    return result;

                } catch (error) {
                    console.warn(`[尝试 ${attempt}/${maxRetries}] 失败: ${error.message}`);

                    // 如果还有重试机会,等待后继续
                    if (attempt < maxRetries) {
                        console.log(`[API Key 轮询] 将在 ${retryDelay}ms 后切换到下一个 Key 重试...`);
                        await new Promise(res => setTimeout(res, retryDelay));
                    } else {
                        // 所有重试都失败了
                        throw new Error(`API 请求失败(已重试 ${maxRetries} 次): ${error.message}`);
                    }
                }
            }
        }
        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;
        }
    }

    const BilibiliSubtitleFetcher = {
        async _request(url) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET", url: url.startsWith('//') ? 'https:' + url : url,
                    onload: r => { try { resolve(JSON.parse(r.responseText)); } catch (e) { reject(e); } },
                    onerror: e => reject(e)
                });
            });
        },
        // ++ 这是新的代码,请用它来替换上面的旧代码 ++
        async getVideoInfo() {
            // 优先从 URL 中解析 bvid,这是最可靠的方式
            const bvidMatch = window.location.href.match(/video\/(BV[a-zA-Z0-9]+)/);
            const bvid = bvidMatch ? bvidMatch[1] : (window.bvid || window.__INITIAL_STATE__?.bvid);

            if (!bvid) {
                // 如果无法找到 bvid,则无法继续
                throw new Error('未能从URL或页面中找到视频的BVID');
            }

            // 使用 bvid 调用官方API来获取包含 cid 和 aid 的详细信息
            const apiUrl = `https://api.bilibili.com/x/web-interface/view?bvid=${bvid}`;

            try {
                const data = await this._request(apiUrl);
                if (data && data.code === 0) {
                    const pMatch = window.location.href.match(/[?&]p=(\d+)/);
                    const pageNumber = pMatch ? parseInt(pMatch[1], 10) : 1;

                    let cid;
                    // 检查是否为多P视频并正确获取对应P的cid
                    if (data.data.pages && data.data.pages.length >= pageNumber) {
                        cid = data.data.pages[pageNumber - 1].cid;
                    } else {
                        // 如果是单P视频或p参数无效,则直接获取
                        cid = data.data.cid;
                    }

                    const aid = data.data.aid;

                    if (!cid) {
                        throw new Error('从API响应中未能找到有效的CID');
                    }

                    return { aid, bvid, cid };
                } else {
                    throw new Error(`B站API请求失败: ${data.message || '未知错误'}`);
                }
            } catch (e) {
                console.error('调用B站视频信息API时发生错误:', e);
                throw new Error('通过API获取视频信息时网络请求失败');
            }
        },
        async getSubtitleConfig(info) {
            const apis = [`//api.bilibili.com/x/player/v2?cid=${info.cid}&bvid=${info.bvid}`, `//api.bilibili.com/x/v2/dm/view?aid=${info.aid}&oid=${info.cid}&type=1`];
            for (const api of apis) {
                try {
                    const data = await this._request(api);
                    if (data.code === 0 && data.data?.subtitle?.subtitles?.length > 0) return data.data.subtitle;
                } catch (e) { /* ignore */ }
            }
            return null;
        },
        async getSubtitleContent(url) { try { const data = await this._request(url); return data.body || []; } catch (e) { return []; } }
    };

    class ContentExtractor {
        static async waitForElement(s, t = 15000) { return new Promise(r => { const c = () => { const e = document.querySelector(s); if (e) r(e); else setTimeout(c, 200); }; c(); }); }
        static async getYouTubeSubtitles() {
            const el = await this.waitForElement('#ytvideotext', 15000); if (!el) throw new Error('未能找到YouTube字幕容器 (建议安装 https://dub.sh/ytbcc 插件)');
            const subs = []; el.querySelectorAll('p').forEach(p => { let ft = ''; p.querySelectorAll('span[id^="st_"]').forEach(sp => ft += (ft ? ' ' : '') + sp.textContent.trim()); if (ft) subs.push(ft); });
            if (subs.length === 0) throw new Error('未能解析出任何有效字幕');
            return subs.join('\n');
        }
        static async getBilibiliSubtitles() {
            const info = await BilibiliSubtitleFetcher.getVideoInfo(); if (!info.cid) throw new Error("无法获取B站视频信息 (CID)");
            const config = await BilibiliSubtitleFetcher.getSubtitleConfig(info); if (!config) throw new Error("该视频没有找到CC字幕");
            const subtitles = await BilibiliSubtitleFetcher.getSubtitleContent(config.subtitles[0].subtitle_url); if (subtitles.length === 0) throw new Error("获取字幕内容失败");
            return subtitles.map(sub => sub.content).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 = []; cEl.querySelectorAll('p, section, h1, h2, h3, h4, h5, h6, li').forEach(n => { if (n.innerText && !n.querySelector('p, section, table, ul, ol')) { const t = n.innerText.trim(); if (t) parts.push(t); } });
            return `标题: ${title}\n作者: ${author}\n\n---\n\n${parts.join('\n\n') || '未找到内容'}`;
        }
    }
    // 历史记录管理器 - 使用 GM_setValue/GM_getValue 实现跨域存储
    const HISTORY_STORAGE_KEY = 'content_expert_ai_history_v2'; // 新key避免旧数据干扰
    const HISTORY_MAX_ITEMS = 50;

    const HistoryManager = {
        getHistory() {
            try {
                // GM_getValue 可以直接存取对象,不需要JSON转换
                const data = GM_getValue(HISTORY_STORAGE_KEY, []);
                console.log('[HistoryManager] getHistory - got:', data, 'type:', typeof data, 'isArray:', Array.isArray(data));
                if (Array.isArray(data)) {
                    return data;
                }
                // 兼容旧的字符串格式
                if (typeof data === 'string' && data.length > 0) {
                    try {
                        const parsed = JSON.parse(data);
                        return Array.isArray(parsed) ? parsed : [];
                    } catch (e) {
                        return [];
                    }
                }
                return [];
            } catch (e) {
                console.error('[HistoryManager] Failed to load history:', e);
                return [];
            }
        },

        saveHistory(history) {
            try {
                console.log('[HistoryManager] saveHistory - attempting to save:', history.length, 'items');
                console.log('[HistoryManager] saveHistory - first item:', history[0]);
                // 直接保存数组对象
                GM_setValue(HISTORY_STORAGE_KEY, history);
                console.log('[HistoryManager] saveHistory - GM_setValue completed');

                // 立即验证
                const verify = GM_getValue(HISTORY_STORAGE_KEY, []);
                console.log('[HistoryManager] Verification - read back:', verify.length, 'items, isArray:', Array.isArray(verify));
                if (!Array.isArray(verify) || verify.length !== history.length) {
                    console.error('[HistoryManager] VERIFICATION FAILED! Saved:', history.length, 'Read:', verify.length);
                    alert('[HistoryManager] 验证失败! 保存:' + history.length + ' 读取:' + verify.length);
                } else {
                    console.log('[HistoryManager] saveHistory - verification passed');
                }
            } catch (e) {
                console.error('[HistoryManager] Failed to save history:', e);
                alert('[HistoryManager] 保存失败: ' + e.message);
            }
        },

        addRecord(record) {
            console.log('[HistoryManager] addRecord called with:', record);
            if (!record || !record.id) {
                console.error('[HistoryManager] Invalid record:', record);
                return;
            }
            const history = this.getHistory();
            console.log('[HistoryManager] Current history count:', history.length);

            // 移除同ID的旧记录(如果有),确保最新生成的在最前面
            const newHistory = history.filter(item => item.id !== record.id);

            newHistory.unshift({
                ...record,
                timestamp: Date.now()
            });

            // 使用配置的限制,支持动态修改
            const maxItems = CONFIG.HISTORY ? parseInt(CONFIG.HISTORY.MAX_ITEMS) : 50;
            // 如果设置为 -1 或 0 (虽然UI没这选项,但作为防御编程) 视为不限制? 或者就按标准来。
            // 假设 'Infinite' 在 UI 上可能对应一个超大数,但为了逻辑严谨,这里还是要做截断。
            // 用户说"Infinite scroll"是指前端显示。存储一般还是有个上限好,但用户如果选无限,就设个很大的数
            // 根据之前的分析,我们不希望它真的无限增长。
            // 更新逻辑:始终截断到 maxItems
            if (newHistory.length > maxItems) {
                newHistory.length = maxItems;
            }

            this.saveHistory(newHistory);
            console.log('[HistoryManager] addRecord completed, new count:', newHistory.length);
        },

        deleteRecord(id) {
            const history = this.getHistory();
            const newHistory = history.filter(item => item.id !== id);
            this.saveHistory(newHistory);
        },

        updateRecord(id, updates) {
            const history = this.getHistory();
            const index = history.findIndex(item => item.id === id);
            if (index !== -1) {
                history[index] = { ...history[index], ...updates };
                this.saveHistory(history);
                console.log('[HistoryManager] updateRecord completed for id:', id, 'updates:', updates);
            }
        },

        clearHistory() {
            GM_setValue(HISTORY_STORAGE_KEY, []);
            console.log('[HistoryManager] clearHistory completed');
        }
    };


    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]}`; }
            if (this.platform === 'BILIBILI') { const match = window.location.href.match(/video\/(BV[a-zA-Z0-9]+)/); return match ? match[1] : 'unknown_bilibili_video'; }
            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() || '微信文章';
            if (this.platform === 'BILIBILI') return (document.querySelector('h1.video-title') || {}).textContent.trim() || 'Bilibili 视频';
            return '未知内容';
        }
        getChannelName() {
            if (this.platform === 'YOUTUBE') {
                const channelEl = document.querySelector('#channel-name a, ytd-channel-name a, #owner #text a');
                return channelEl ? channelEl.textContent.trim() : '未知频道';
            }
            if (this.platform === 'WECHAT') {
                return (document.querySelector('#js_name') || {}).innerText?.trim() || '未知公众号';
            }
            if (this.platform === 'BILIBILI') {
                const upEl = document.querySelector('.up-name, .up-info-container .name');
                return upEl ? upEl.textContent.trim() : '未知UP主';
            }
            return '未知作者';
        }
        async translateTitle() {
            try {
                const oTitle = this.getContentTitle();
                if (!oTitle || /[\u4e00-\u9fa5]/.test(oTitle)) { this.translatedTitle = oTitle; return oTitle; }
                const summary = await this.summaryManager.requestSummary(oTitle, "Translate the following title to Chinese. Respond with only the translated text, without any explanations or quotes.");
                this.translatedTitle = summary || 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 if (this.platform === 'BILIBILI') this.mainContent = await ContentExtractor.getBilibiliSubtitles();
            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.toggleCollapse(); // 默认收起UI
            this.attachEventListeners();
        }
        createUI() {
            // 创建 Shadow Host
            this.shadowHost = document.createElement('div');
            this.shadowHost.id = 'ai-assistant-shadow-host';
            this.shadowHost.style.cssText = 'position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483647;';
            document.body.appendChild(this.shadowHost);

            // 创建 Shadow Root
            this.shadowRoot = this.shadowHost.attachShadow({ mode: 'open' });

            // 添加全局样式(滚动条等)到 Shadow Root
            const style = document.createElement('style');
            style.textContent = `
                /* 方案:全局磨砂质感滚动条 (Frosted Glass) - 强制生效 */
                *::-webkit-scrollbar {
                    width: 6px !important;
                    height: 6px !important;
                    background: transparent !important;
                }
                *::-webkit-scrollbar-track {
                    background: transparent !important;
                }
                *::-webkit-scrollbar-track-piece {
                    background: transparent !important;
                }
                *::-webkit-scrollbar-thumb {
                    background: rgba(0, 0, 0, 0.1) !important; 
                    backdrop-filter: blur(4px) !important;
                    -webkit-backdrop-filter: blur(4px) !important;
                    border-radius: 10px !important;
                    border: 1px solid rgba(255, 255, 255, 0.1) !important;
                    box-shadow: inset 0 0 4px rgba(0,0,0,0.05) !important;
                    transition: all 0.2s ease !important;
                }
                /* 鼠标悬停时略微加深 */
                *::-webkit-scrollbar-thumb:hover {
                    background: rgba(0, 0, 0, 0.2) !important; 
                    border: 1px solid rgba(255, 255, 255, 0.3) !important;
                }
                *::-webkit-scrollbar-corner {
                    background: transparent !important;
                }
                
                /* 基础重置,确保 Shadow DOM 内元素不受外部 CSS 污染或奇怪影响 */
                * {
                    box-sizing: border-box;
                }
            `;
            this.shadowRoot.appendChild(style);

            this.container = document.createElement('div');
            const defaultWidth = this.platform === 'BILIBILI' ? '453px' : '420px';
            this.container.style.cssText = `position: fixed; top: 80px; right: 20px; width: ${defaultWidth}; min-width: 280px; max-width: 90vw; background: rgba(255, 255, 255, 0.75); border-radius: 16px; padding: 0; color: #1f2937; 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(0, 0, 0, 0.1); backdrop-filter: blur(20px) saturate(180%); border: 1px solid rgba(0, 0, 0, 0.1);`;

            // Append container to Shadow Root instead of body
            this.shadowRoot.appendChild(this.container);

            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);

            // Remove the old document.body.appendChild call
            // document.body.appendChild(this.container); 

            this.makeDraggable(topBar);
            this.makeResizable(); // 添加可调整宽度功能
        }
        // 创建左侧可拖动调整宽度的手柄
        makeResizable() {
            // 左侧调整手柄
            const leftHandle = document.createElement('div');
            leftHandle.style.cssText = `position: absolute; left: 0; top: 0; width: 6px; height: 100%; cursor: ew-resize; background: transparent; z-index: 10;`;
            leftHandle.addEventListener('mouseenter', () => leftHandle.style.background = 'rgba(59, 130, 246, 0.3)');
            leftHandle.addEventListener('mouseleave', () => { if (!this.isResizing) leftHandle.style.background = 'transparent'; });

            this.container.appendChild(leftHandle);
            this.leftResizeHandle = leftHandle; // 保存引用,用于在收起时隐藏

            this.isResizing = false;

            // 左侧拖动逻辑
            leftHandle.addEventListener('mousedown', (e) => {
                e.preventDefault();
                e.stopPropagation();
                if (this.isCollapsed) return; // 收起时不允许调整
                this.isResizing = true;
                this.container.style.transition = 'none'; // 拖动时禁用过渡动画
                const startX = e.clientX;
                const startWidth = this.container.offsetWidth;

                const onMouseMove = (e) => {
                    const deltaX = startX - e.clientX;
                    const newWidth = Math.min(Math.max(startWidth + deltaX, 280), window.innerWidth * 0.9);
                    this.container.style.width = `${newWidth}px`;
                    // 调整宽度后更新高度
                    this.updateSummaryContentHeight();
                };

                const onMouseUp = () => {
                    this.isResizing = false;
                    this.container.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
                    leftHandle.style.background = 'transparent';
                    document.removeEventListener('mousemove', onMouseMove);
                    document.removeEventListener('mouseup', onMouseUp);
                };

                document.addEventListener('mousemove', onMouseMove);
                document.addEventListener('mouseup', onMouseUp);
            });
        }
        createTopBar() {
            const topBar = document.createElement('div');
            this.topBar = topBar;
            topBar.style.cssText = `display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; cursor: move; background: rgba(255, 255, 255, 0.5); 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('⚙️', '设置');
            this.configButton = configButton;
            configButton.addEventListener('mousedown', (e) => e.stopPropagation());
            configButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); this.toggleConfigPanel(); });
            this.historyButton = this.createIconButton('🕒', '历史记录');
            this.historyButton.addEventListener('mousedown', (e) => e.stopPropagation());
            this.historyButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); this.toggleHistoryPanel(); });
            buttonContainer.appendChild(this.historyButton); 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(59, 130, 246, 0.1); border: none; color: #3b82f6; 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', () => {
                if (!this.isCollapsed) { // 仅在展开时应用背景色悬停效果
                    button.style.background = 'rgba(59, 130, 246, 0.2)';
                }
                button.style.transform = 'scale(1.1)';
            });
            button.addEventListener('mouseout', () => {
                if (!this.isCollapsed) { // 仅在展开时应用背景色悬停效果
                    button.style.background = 'rgba(59, 130, 246, 0.1)';
                }
                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;`;

            let loadButtonText = '📄 提取内容';
            if (this.platform === 'YOUTUBE') loadButtonText = '📄 加载字幕';
            else if (this.platform === 'WECHAT') loadButtonText = '📄 提取文章';
            else if (this.platform === 'BILIBILI') loadButtonText = '📄 加载字幕';

            this.loadContentButton = this.createButton(loadButtonText, 'primary');
            this.loadContentButton.addEventListener('click', () => this.handleLoadContent());
            controls.appendChild(this.loadContentButton);

            // Row for Prompt Select and Generate Button (Option A Modified)
            const actionRow = document.createElement('div');
            actionRow.style.cssText = `display: none; gap: 8px; align-items: stretch;`; // Normally hidden until content loads

            // 1. Prompt Select (No Label, Flex Grow)
            // Direct select creation without form group wrapper
            this.mainPromptSelectElement = this.createMainPromptSelect();
            // Wrap in a div to handle flex growth cleanly if needed, or apply flex directly
            this.mainPromptSelectElement.style.width = '100%';
            this.mainPromptSelectElement.style.height = '100%'; // Match button height

            const promptWrapper = document.createElement('div');
            promptWrapper.style.cssText = `flex: 1; min-width: 0;`; // Text overflow handling
            promptWrapper.appendChild(this.mainPromptSelectElement);

            // 2. Summary Button (Fixed Width or Auto)
            this.summaryButton = this.createButton('🤖 生成总结', 'secondary');
            this.summaryButton.style.display = 'block'; // Reset from default createButton if needed, but here it's fine
            this.summaryButton.style.whiteSpace = 'nowrap';
            this.summaryButton.style.height = '100%'; // Ensure full height consistency
            this.summaryButton.addEventListener('click', () => this.handleGenerateSummary());

            actionRow.appendChild(promptWrapper);
            actionRow.appendChild(this.summaryButton);

            this.actionRow = actionRow; // Save reference to toggle visibility
            controls.appendChild(actionRow);

            return controls;
        }
        createButton(text, type = 'primary') {
            const button = document.createElement('button'); button.textContent = text;
            // Removed createButton's hardcoded display logic if it interferes, but standard is block/inline-block
            const baseStyle = `padding: 10px 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); display: flex; align-items: center; justify-content: center;`;
            button.style.cssText = baseStyle + (type === 'primary' ? `background: #3b82f6; color: #fff;` : `background: rgba(59, 130, 246, 0.1); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.3);`);
            button.dataset.originalText = text; // Store original text for restoration
            button.addEventListener('mouseover', () => { if (!button.disabled) { button.style.transform = 'translateY(-2px)'; button.style.boxShadow = '0 8px 25px rgba(59, 130, 246, 0.25)'; if (type !== 'primary') button.style.background = 'rgba(59, 130, 246, 0.15)'; } });
            button.addEventListener('mouseout', () => { if (!button.disabled) { button.style.transform = 'translateY(0)'; button.style.boxShadow = 'none'; if (type !== 'primary') button.style.background = 'rgba(59, 130, 246, 0.1)'; } });
            return button;
        }
        createStatusDisplay() {
            // Status area removed in favor of in-button status
            this.statusDisplay = document.createElement('div');
            this.statusDisplay.style.display = 'none'; // Keep element but hidden to avoid null refs if any
        }
        createSummaryPanel() {
            this.summaryPanel = document.createElement('div'); this.summaryPanel.style.cssText = `background: rgba(255, 255, 255, 0.5); border-radius: 12px; padding: 16px; margin-top: 16px; display: none; backdrop-filter: blur(10px); border: 1px solid rgba(0, 0, 0, 0.05);`;
            const titleContainer = document.createElement('div'); titleContainer.style.cssText = `display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;`;

            // Left side: Title + Theme Toggle
            const leftContainer = document.createElement('div'); leftContainer.style.cssText = `display: flex; align-items: center; gap: 12px;`;
            const titleEl = document.createElement('div'); titleEl.textContent = '📝 内容总结'; titleEl.style.cssText = `font-weight: 600; font-size: 15px; color: #1f2937;`;

            // Theme Toggle (iOS Style)
            const toggleWrapper = document.createElement('div');
            toggleWrapper.style.cssText = `display: flex; align-items: center; gap: 6px;`;
            const toggleLabel = document.createElement('span'); toggleLabel.textContent = '主题'; toggleLabel.style.cssText = `font-size: 11px; color: #666;`;

            const labelSwitch = document.createElement('label');
            labelSwitch.style.cssText = `position: relative; display: inline-block; width: 32px; height: 18px;`;
            const inputSwitch = document.createElement('input');
            inputSwitch.type = 'checkbox';
            inputSwitch.checked = (CONFIG.APPEARANCE.THEME === 'spring');
            inputSwitch.style.cssText = `opacity: 0; width: 0; height: 0;`;

            const slider = document.createElement('span');
            slider.style.cssText = `position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 18px;`;
            // Slider knob styled via pseudo-element simulation (since we can't easily use CSS classes with inline styles for pseudo-elements, we use a child span)
            const knob = document.createElement('span');
            knob.style.cssText = `position: absolute; content: ""; height: 14px; width: 14px; left: 2px; bottom: 2px; background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,0.3); transform: ${inputSwitch.checked ? 'translateX(14px)' : 'translateX(0)'};`;
            if (inputSwitch.checked) slider.style.backgroundColor = '#42b983';

            labelSwitch.appendChild(inputSwitch);
            labelSwitch.appendChild(slider);
            slider.appendChild(knob);

            inputSwitch.addEventListener('change', () => {
                const isSpring = inputSwitch.checked;
                CONFIG.APPEARANCE.THEME = isSpring ? 'spring' : 'default';
                ConfigManager.saveConfig(CONFIG);

                // Animate
                slider.style.backgroundColor = isSpring ? '#42b983' : '#ccc';
                knob.style.transform = isSpring ? 'translateX(14px)' : 'translateX(0)';

                // Re-render
                if (this.summaryContent && this.originalSummaryText) {
                    this.createFormattedContent(this.summaryContent, this.originalSummaryText);
                    this.showNotification(`已切换为${isSpring ? '船仓沐春' : '船仓红韵'}主题`, 'success');
                }
            });

            toggleWrapper.appendChild(toggleLabel);
            toggleWrapper.appendChild(labelSwitch);

            leftContainer.appendChild(titleEl);
            leftContainer.appendChild(toggleWrapper);

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

            // 复制按钮
            const copyButton = document.createElement('button'); copyButton.textContent = '复制';
            copyButton.style.cssText = `background: #3b82f6; 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; });
            buttonsContainer.appendChild(copyButton);

            // PublishMarkdown 发布按钮
            this.publishButton = document.createElement('button'); this.publishButton.textContent = '📤 发布';
            this.publishButton.style.cssText = `background: #c83232; color: white; border: none; border-radius: 8px; padding: 6px 12px; font-size: 12px; cursor: pointer; transition: all 0.2s ease; display: ${CONFIG.PUBLISH_MARKDOWN?.ENABLED ? 'block' : 'none'};`;
            this.publishButton.addEventListener('click', () => this.handlePublishMarkdown());
            buttonsContainer.appendChild(this.publishButton);

            titleContainer.appendChild(leftContainer); titleContainer.appendChild(buttonsContainer);

            // 已发布URL显示区域
            this.publishedUrlContainer = document.createElement('div');
            this.publishedUrlContainer.style.cssText = `display: none; margin-bottom: 12px; padding: 10px 14px; background: rgba(76, 175, 80, 0.1); border-radius: 8px; border: 1px solid rgba(76, 175, 80, 0.2);`;
            const urlRow = document.createElement('div'); urlRow.style.cssText = `display: flex; align-items: center; gap: 8px; flex-wrap: wrap;`;
            const urlLabel = document.createElement('span'); urlLabel.textContent = '🔗 已发布:'; urlLabel.style.cssText = `font-size: 12px; color: #666;`;
            this.publishedUrlLink = document.createElement('a'); this.publishedUrlLink.style.cssText = `font-size: 12px; color: #2e7d32; text-decoration: none; word-break: break-all;`; this.publishedUrlLink.target = '_blank';
            this.editIdentifierButton = document.createElement('button'); this.editIdentifierButton.textContent = '✏️';
            this.editIdentifierButton.style.cssText = `background: rgba(200, 50, 50, 0.1); color: #c83232; border: none; border-radius: 6px; padding: 4px 8px; font-size: 12px; cursor: pointer; transition: all 0.2s ease;`;
            this.editIdentifierButton.title = '编辑自定义URL';
            this.editIdentifierButton.addEventListener('click', () => this.showEditIdentifierDialog());
            urlRow.appendChild(urlLabel); urlRow.appendChild(this.publishedUrlLink); urlRow.appendChild(this.editIdentifierButton);
            this.publishedUrlContainer.appendChild(urlRow);

            this.summaryContent = document.createElement('div'); this.summaryContent.style.cssText = `font-size: 14px; line-height: 1.6; color: #374151; white-space: pre-wrap; overflow-y: auto; padding: 16px; background: rgba(255, 255, 255, 0.6); border-radius: 12px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); word-break: break-word;`;
            this.summaryPanel.appendChild(titleContainer); this.summaryPanel.appendChild(this.publishedUrlContainer); this.summaryPanel.appendChild(this.summaryContent);
            this.mainContent.appendChild(this.summaryPanel);

            // 记录当前发布的identifier
            this.currentPublishedIdentifier = null;

            // 添加窗口resize事件监听器,动态调整内容面板高度
            window.addEventListener('resize', () => this.updateSummaryContentHeight());
        }
        // 动态计算并设置内容面板高度,使其延伸到浏览器窗口底部边缘
        updateSummaryContentHeight() {
            if (!this.summaryContent || !this.container || this.summaryPanel.style.display === 'none') return;
            const windowHeight = window.innerHeight;
            const containerRect = this.container.getBoundingClientRect();
            const bottomPadding = 20; // 距离窗口底部的边距

            // 计算容器顶部到窗口底部的可用空间
            const containerTopOffset = containerRect.top;
            const totalAvailableHeight = windowHeight - containerTopOffset - bottomPadding;

            // 先设置容器的最大高度
            this.container.style.maxHeight = `${totalAvailableHeight}px`;
            this.container.style.overflow = 'hidden';

            // 计算 summaryContent 的可用高度
            // 需要减去 topBar、controls、status、summaryPanel 的标题等其他元素的高度
            const summaryContentRect = this.summaryContent.getBoundingClientRect();
            const summaryContentTop = summaryContentRect.top;
            const summaryPanelPadding = 36; // summaryPanel的padding + 额外边距
            const availableHeight = windowHeight - summaryContentTop - bottomPadding - summaryPanelPadding;

            // 设置最小高度为100px,最大高度为可用空间
            const maxHeight = Math.max(100, availableHeight);
            this.summaryContent.style.maxHeight = `${maxHeight}px`;

            // 为 mainContent 添加滚动支持
            this.mainContent.style.maxHeight = `${totalAvailableHeight - 60}px`; // 减去 topBar 高度
            this.mainContent.style.overflowY = 'auto';
        }
        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: rgba(255, 255, 255, 0.85); border-radius: 20px; color: #1f2937; font-family: -apple-system, sans-serif; z-index: 50000; display: none; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15); border: 1px solid rgba(0, 0, 0, 0.1); overflow: hidden; backdrop-filter: blur(20px) saturate(180%);`;
            const configHeader = document.createElement('div'); configHeader.style.cssText = `padding: 20px 24px; background: rgba(59, 130, 246, 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');
            // ++ 在这里添加下面的新代码 ++
            const importBtn = this.createButton('📥 导入', 'secondary'); importBtn.style.padding = '8px 16px'; importBtn.addEventListener('click', () => this.handleImport());
            const exportBtn = this.createButton('📤 导出', 'secondary'); exportBtn.style.padding = '8px 16px'; exportBtn.addEventListener('click', () => this.handleExport());
            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(importBtn);
            headerButtons.appendChild(exportBtn);
            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');
            // Two-column layout container
            horizontalContainer.style.cssText = `display: flex; gap: 20px; align-items: stretch;`;

            // Left Column: AI Settings
            const aiSection = this.createConfigSection('🤖 AI 模型设置', this.createAIModelConfig());
            aiSection.style.cssText += `flex: 1; min-width: 380px; display: flex; flex-direction: column;`;

            // Right Column: Prompt + Publish
            const rightColumn = document.createElement('div');
            rightColumn.style.cssText = `flex: 1; min-width: 380px; display: flex; flex-direction: column; gap: 16px;`;

            const promptSection = this.createConfigSection('📝 Prompt 管理', this.createPromptConfig());
            // promptSection.style.cssText += `flex: 1;`; // Remove flex: 1 to let it shrink to content height

            const publishSection = this.createPublishSectionWithToggle();
            // Remove margin-top: auto so it follows the prompt section directly with the gap defined in rightColumn
            // publishSection.style.cssText += `margin-top: auto;`; 

            rightColumn.appendChild(promptSection);
            // Appearance section removed as requested
            rightColumn.appendChild(publishSection);

            horizontalContainer.appendChild(aiSection);
            horizontalContainer.appendChild(rightColumn);

            configContent.appendChild(horizontalContainer);
            this.configPanel.appendChild(configHeader); this.configPanel.appendChild(configContent);
            this.shadowRoot.appendChild(this.configPanel);
        }
        createConfigSection(title, content) {
            const section = document.createElement('div');
            section.style.cssText = `margin-bottom: 16px; background: rgba(59, 130, 246, 0.05); border-radius: 16px; padding: 16px; border: 1px solid rgba(59, 130, 246, 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');
            // 选择平台行:使用与其他字段相同的 createFormGroup 样式来保持一致
            const selectRow = document.createElement('div'); selectRow.style.cssText = `display: flex; align-items: center; gap: 12px; margin-bottom: 12px;`;
            const selectLabel = document.createElement('label'); selectLabel.textContent = '选择平台'; selectLabel.style.cssText = `flex-shrink: 0; min-width: 60px; font-size: 13px; font-weight: 500; color: #374151; text-align: right;`;
            const selectAndButtons = document.createElement('div'); selectAndButtons.style.cssText = `flex: 1; display: flex; gap: 8px; align-items: center;`;
            const modelSelect = this.createModelSelect(); modelSelect.style.flex = '1';
            const addModelButton = this.createButton('+ 新增', 'secondary'); addModelButton.style.cssText += `height: 38px; padding: 6px 12px; font-size: 13px;`; addModelButton.addEventListener('click', () => this.showAddModelDialog());
            const deleteModelButton = this.createButton('删除', 'secondary'); deleteModelButton.style.cssText += `height: 38px; padding: 6px 12px; font-size: 13px; background: rgba(244, 67, 54, 0.1); color: #ef4444; border-color: rgba(244, 67, 54, 0.3);`; deleteModelButton.addEventListener('click', () => this.showDeleteModelDialog());
            selectAndButtons.appendChild(modelSelect); selectAndButtons.appendChild(addModelButton); selectAndButtons.appendChild(deleteModelButton);
            selectRow.appendChild(selectLabel); selectRow.appendChild(selectAndButtons);
            this.apiConfigContainer = this.createAPIConfig(CONFIG.AI_MODELS.TYPE);
            container.appendChild(selectRow); 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];

            // Core Settings
            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', '支持多个Key,用英文逗号分隔')));

            // Model ID with Fetch Feature
            container.appendChild(this.createFormGroup('模型ID', this.createModelSelectionControl(modelConfig)));

            // Advanced Settings (Collapsible)
            const details = document.createElement('details');
            details.style.cssText = `border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 12px; padding: 8px; background: rgba(59, 130, 246, 0.05); margin-top: 8px;`;

            const summary = document.createElement('summary');
            summary.textContent = '🛠️ 高级设置';
            summary.style.cssText = `cursor: pointer; font-size: 14px; font-weight: 500; color: #3b82f6; outline: none; padding: 4px; user-select: none;`;
            details.appendChild(summary);

            const content = document.createElement('div');
            content.style.cssText = `padding-top: 12px; display: flex; flex-direction: column; gap: 4px;`;

            content.appendChild(this.createFormGroup('API 类型', this.createAPITypeSelect(modelType)));
            content.appendChild(this.createFormGroup('最大输出', this.createNumberInput(modelConfig.MAX_TOKENS || 2000, v => modelConfig.MAX_TOKENS = parseInt(v), 1, 100000, 1)));
            content.appendChild(this.createFormGroup('流式响应', this.createStreamSelect(modelType)));
            content.appendChild(this.createFormGroup('温度 (0-2)', this.createNumberInput(modelConfig.TEMPERATURE || 0.7, v => modelConfig.TEMPERATURE = parseFloat(v), 0, 2, 0.1)));
            content.appendChild(this.createFormGroup('推理(适配Cerebras,其他渠道不要开启)', this.createReasoningEffortSelect(modelType)));

            details.appendChild(content);
            container.appendChild(details);

            // Configuration Guide (Collapsible)
            const guideDetails = document.createElement('details');
            guideDetails.style.cssText = `border: 1px dashed rgba(59, 130, 246, 0.2); border-radius: 12px; padding: 8px; background: rgba(59, 130, 246, 0.02); margin-top: 8px;`;

            const guideSummary = document.createElement('summary');
            guideSummary.textContent = 'ℹ️ 配置说明';
            guideSummary.style.cssText = `cursor: pointer; font-size: 13px; font-weight: 500; color: #6b7280; outline: none; padding: 4px; user-select: none;`;
            guideDetails.appendChild(guideSummary);

            const guideContent = document.createElement('div');
            guideContent.style.cssText = `padding: 8px 4px 4px 18px; display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: #4b5563; line-height: 1.5;`;

            const guideItems = [
                { title: '多Key轮询', text: '用,分隔多个Key,失败自动切换。' },
                { title: '获取模型', text: '配置好URL/Key可一键获取模型列表,勾选主用模型。' },
                { title: '智能适配', text: '自动识别 API 格式与路径补全。' },
                { title: '推理功能', text: '适配Cerebras推理模型,其他渠道请关闭。' }
            ];

            guideItems.forEach(item => {
                const p = document.createElement('div');

                const titleSpan = document.createElement('span');
                titleSpan.textContent = `• ${item.title}`;
                titleSpan.style.cssText = `font-weight: 600; color: #374151;`;

                p.appendChild(titleSpan);
                p.appendChild(document.createTextNode(`:${item.text}`));

                guideContent.appendChild(p);
            });

            guideDetails.appendChild(guideContent);
            container.appendChild(guideDetails);

            return container;
        }
        createAPITypeSelect(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: 'gemini', text: 'Gemini 格式' }, { value: 'openai', text: 'OpenAI 兼容格式' }, { value: 'anthropic', text: 'Anthropic 格式' }];
            const currentType = CONFIG.AI_MODELS[modelType].API_TYPE || 'openai';
            options.forEach(opt => {
                const optionEl = document.createElement('option'); optionEl.value = opt.value; optionEl.textContent = opt.text;
                if (currentType === opt.value) optionEl.selected = true;
                select.appendChild(optionEl);
            });
            select.addEventListener('change', () => { CONFIG.AI_MODELS[modelType].API_TYPE = select.value; });
            return select;
        }
        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: 'true', text: '是 (流式响应)' }, { value: 'false', 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;
        }
        createReasoningEffortSelect(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: 'none', text: '关闭 (不启用推理)' },
                { value: 'low', text: '低 (快速响应)' },
                { value: 'medium', text: '中 (默认推理)' },
                { value: 'high', text: '高 (深度分析)' }
            ];
            const currentEffort = CONFIG.AI_MODELS[modelType].REASONING_EFFORT || 'none';
            options.forEach(opt => {
                const optionEl = document.createElement('option'); optionEl.value = opt.value; optionEl.textContent = opt.text;
                if (currentEffort === opt.value) optionEl.selected = true;
                select.appendChild(optionEl);
            });
            select.addEventListener('change', () => { CONFIG.AI_MODELS[modelType].REASONING_EFFORT = select.value; });
            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;
            const promptFormGroup = this.createFormGroup('当前 Prompt', this.createPromptSelect());
            promptFormGroup.style.marginBottom = '0'; // 移除底部边距以对齐按钮
            selectWrapper.appendChild(promptFormGroup);
            const addButton = this.createButton('➕ 新增', 'secondary'); addButton.style.height = '44px'; addButton.addEventListener('click', () => this.showAddPromptDialog());
            promptSelectContainer.appendChild(selectWrapper); promptSelectContainer.appendChild(addButton);
            this.promptListContainer = this.createPromptList();
            container.appendChild(promptSelectContainer); container.appendChild(this.promptListContainer);
            return container;
        }
        // createAppearanceConfig removed

        createPublishSectionWithToggle() {
            const section = document.createElement('div');
            section.style.cssText = `margin-bottom: 16px; background: rgba(59, 130, 246, 0.05); border-radius: 16px; padding: 16px; border: 1px solid rgba(59, 130, 246, 0.1); display: flex; flex-direction: column;`;

            // 标题行:包含标题和 iOS 风格开关
            const titleRow = document.createElement('div');
            titleRow.style.cssText = `display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;`;
            const sectionTitle = document.createElement('h4');
            sectionTitle.textContent = '📤 PublishMarkdown 发布';
            sectionTitle.style.cssText = `margin: 0; font-size: 16px; font-weight: 600;`;

            // iOS 风格开关
            const toggleWrapper = document.createElement('div');
            toggleWrapper.style.cssText = `display: flex; align-items: center; gap: 8px;`;
            const toggleLabel = document.createElement('span');
            toggleLabel.textContent = '启用';
            toggleLabel.style.cssText = `font-size: 13px; color: #666;`;

            const labelSwitch = document.createElement('label');
            labelSwitch.style.cssText = `position: relative; display: inline-block; width: 36px; height: 20px;`;
            const inputSwitch = document.createElement('input');
            inputSwitch.type = 'checkbox';
            inputSwitch.checked = CONFIG.PUBLISH_MARKDOWN?.ENABLED || false;
            inputSwitch.style.cssText = `opacity: 0; width: 0; height: 0;`;

            const slider = document.createElement('span');
            slider.style.cssText = `position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: ${inputSwitch.checked ? '#3b82f6' : '#ccc'}; transition: .3s; border-radius: 20px;`;
            const knob = document.createElement('span');
            knob.style.cssText = `position: absolute; content: ""; height: 16px; width: 16px; left: 2px; bottom: 2px; background-color: white; transition: .3s; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,0.3); transform: ${inputSwitch.checked ? 'translateX(16px)' : 'translateX(0)'};`;

            labelSwitch.appendChild(inputSwitch);
            labelSwitch.appendChild(slider);
            slider.appendChild(knob);

            toggleWrapper.appendChild(toggleLabel);
            toggleWrapper.appendChild(labelSwitch);
            titleRow.appendChild(sectionTitle);
            titleRow.appendChild(toggleWrapper);

            // 内容容器
            const contentContainer = document.createElement('div');
            contentContainer.style.cssText = `display: flex; flex-direction: column; gap: 12px;`;

            // API Key 输入
            const apiKeyInput = this.createInput(CONFIG.PUBLISH_MARKDOWN?.API_KEY || '', (value) => {
                CONFIG.PUBLISH_MARKDOWN = CONFIG.PUBLISH_MARKDOWN || {};
                CONFIG.PUBLISH_MARKDOWN.API_KEY = value;
            }, 'password', 'PublishMarkdown API Key');
            apiKeyInput.disabled = !CONFIG.PUBLISH_MARKDOWN?.ENABLED;

            // 开关事件
            inputSwitch.addEventListener('change', () => {
                const isEnabled = inputSwitch.checked;
                CONFIG.PUBLISH_MARKDOWN = CONFIG.PUBLISH_MARKDOWN || {};
                CONFIG.PUBLISH_MARKDOWN.ENABLED = isEnabled;
                slider.style.backgroundColor = isEnabled ? '#3b82f6' : '#ccc';
                knob.style.transform = isEnabled ? 'translateX(16px)' : 'translateX(0)';
                apiKeyInput.disabled = !isEnabled;
            });

            // 说明文字
            const helpText = document.createElement('div');
            helpText.style.cssText = `font-size: 12px; color: #6b7280;`;
            const helpLink = document.createElement('a');
            helpLink.href = 'https://publishmarkdown.com/docs';
            helpLink.target = '_blank';
            helpLink.textContent = '获取 API Key →';
            helpLink.style.cssText = `color: #3b82f6; text-decoration: none; margin-right: 8px;`;
            helpText.appendChild(helpLink);
            helpText.appendChild(document.createTextNode('启用后可一键发布总结的内容生成网址'));

            contentContainer.appendChild(this.createFormGroup('API Key', apiKeyInput));
            contentContainer.appendChild(helpText);

            section.appendChild(titleRow);
            section.appendChild(contentContainer);
            return section;
        }
        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');
            // 固定高度显示约 6 条记录 (每条约 66px * 6 = 396px)
            container.style.cssText = `height: 400px; 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(0, 0, 0, 0.05); display: flex; justify-content: space-between; align-items: flex-start; transition: background 0.2s; min-height: 50px; gap: 10px;`;

                item.addEventListener('mouseover', () => item.style.background = 'rgba(0, 0, 0, 0.02)');
                item.addEventListener('mouseout', () => item.style.background = 'transparent');

                const info = document.createElement('div');
                info.style.cssText = `flex: 1; overflow: hidden; display: flex; flex-direction: column; gap: 4px;`;

                const nameDiv = document.createElement('div');
                nameDiv.textContent = prompt.name;
                nameDiv.title = prompt.name; // Tooltip显示完整标题
                nameDiv.style.cssText = `font-weight: 600; font-size: 13px; color: #374151; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.4;`;

                const promptDiv = document.createElement('div');
                promptDiv.textContent = prompt.prompt;
                promptDiv.title = prompt.prompt; // Tooltip显示完整内容
                promptDiv.style.cssText = `font-size: 11px; color: #6b7280; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.5; height: 3.0em; text-align: justify; word-break: break-all;`;

                info.appendChild(nameDiv); info.appendChild(promptDiv);

                const actions = document.createElement('div');
                actions.style.cssText = `display: flex; gap: 6px; flex-shrink: 0; padding-top: 2px;`;

                const editBtn = this.createSmallButton('✏️', '编辑');
                editBtn.addEventListener('click', (e) => { e.stopPropagation(); this.showEditPromptDialog(prompt, index); });

                actions.appendChild(editBtn);
                if (CONFIG.PROMPTS.LIST.length > 1) {
                    const deleteBtn = this.createSmallButton('🗑️', '删除', 'rgba(244, 67, 54, 0.1)');
                    deleteBtn.style.color = '#ef4444';
                    deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); 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: #374151; 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.unshift({ 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: rgba(255, 255, 255, 0.92); border-radius: 16px; padding: 24px; width: 450px; max-width: 90vw; color: #1f2937; backdrop-filter: blur(20px) saturate(180%); border: 1px solid rgba(0, 0, 0, 0.1); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);`;
            const dialogTitle = document.createElement('h3'); dialogTitle.textContent = title; dialogTitle.style.cssText = `margin: 0 0 20px 0; color: #1f2937;`;
            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: 150px; padding: 12px 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.9); color: #333; border: 1px solid rgba(0, 0, 0, 0.1); font-size: 14px; margin-top: 16px; resize: vertical; box-sizing: border-box; font-family: inherit;`;
            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); this.shadowRoot.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 = `display: flex; align-items: center; gap: 12px; margin-bottom: 12px;`;
            const labelEl = document.createElement('label'); labelEl.textContent = label; labelEl.style.cssText = `flex-shrink: 0; min-width: 60px; font-size: 13px; font-weight: 500; color: #374151; text-align: right;`;
            input.style.flex = '1';
            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: rgba(255, 255, 255, 0.92); border-radius: 16px; padding: 24px; width: 500px; max-width: 90vw; max-height: 85vh; overflow-y: auto; color: #1f2937; backdrop-filter: blur(20px) saturate(180%); border: 1px solid rgba(0, 0, 0, 0.1); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);`;
            const dialogTitle = document.createElement('h3'); dialogTitle.textContent = '新增 AI 平台'; dialogTitle.style.cssText = `margin: 0 0 20px 0; color: #1f2937;`;

            const nameInput = this.createInput('', null, 'text', '平台名称 (如: 硅基流动)');
            // API 类型选择
            const apiTypeSelect = document.createElement('select'); apiTypeSelect.style.cssText = `width: 100%; padding: 12px 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.9); color: #333; border: none;`;
            const apiTypeOpt1 = document.createElement('option'); apiTypeOpt1.value = 'openai'; apiTypeOpt1.textContent = 'OpenAI 兼容格式(一般用这个)';
            const apiTypeOpt2 = document.createElement('option'); apiTypeOpt2.value = 'gemini'; apiTypeOpt2.textContent = 'Gemini 格式';
            const apiTypeOpt3 = document.createElement('option'); apiTypeOpt3.value = 'anthropic'; apiTypeOpt3.textContent = 'Anthropic 格式';
            apiTypeSelect.appendChild(apiTypeOpt1); apiTypeSelect.appendChild(apiTypeOpt2); apiTypeSelect.appendChild(apiTypeOpt3);

            const urlInput = this.createInput('', null, 'text', '如:https://api.openai.com');
            const apiKeyInput = this.createInput('', null, 'password', '支持多个Key用英文逗号,分隔');

            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 name = nameInput.value.trim();
                if (!name) { this.showNotification('平台名称不能为空', 'error'); return; }

                // Auto-generate key from name + timestamp
                let key = name.toUpperCase().replace(/[^A-Z0-9]/g, '_');
                if (!key) key = 'CUSTOM';
                key = key + '_' + Date.now().toString().slice(-4);

                if (CONFIG.AI_MODELS[key]) { this.showNotification('生成模型ID冲突,请重试', 'error'); return; }

                CONFIG.AI_MODELS[key] = { NAME: name, API_TYPE: apiTypeSelect.value, API_KEY: apiKeyInput.value.trim(), API_URL: urlInput.value.trim(), MODEL: '', STREAM: true, TEMPERATURE: 1, MAX_TOKENS: 20000, REASONING_EFFORT: 'none', AVAILABLE_MODELS: [] };

                if (this.configPanel) {
                    this.configPanel.remove();
                    this.configPanel = null;
                }
                this.toggleConfigPanel();
                this.showNotification('新平台已添加', 'success');
                dialog.remove();
            });
            buttonContainer.appendChild(cancelBtn); buttonContainer.appendChild(saveBtn);
            dialogContent.appendChild(dialogTitle);
            dialogContent.appendChild(this.createFormGroup('平台名称', nameInput));
            dialogContent.appendChild(this.createFormGroup('API 类型', apiTypeSelect));
            dialogContent.appendChild(this.createFormGroup('API URL', urlInput));
            dialogContent.appendChild(this.createFormGroup('API Key', apiKeyInput));
            dialogContent.appendChild(buttonContainer); dialog.appendChild(dialogContent); this.shadowRoot.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];
                if (this.configPanel) {
                    this.configPanel.remove();
                    this.configPanel = null;
                }
                this.toggleConfigPanel();
                this.updateTitleWithModel();
                this.showNotification('模型已删除', 'success');
            }
        }
        saveConfig() { ConfigManager.saveConfig(CONFIG); this.showNotification('配置已保存', 'success'); }
        // ++ 在 saveConfig() 函数结束后,粘贴下面所有代码 ++
        handleExport() {
            try {
                const configString = JSON.stringify(CONFIG, null, 2); // 格式化JSON,方便阅读
                const blob = new Blob([configString], { type: 'application/json;charset=utf-8' });
                const link = document.createElement('a');
                link.href = URL.createObjectURL(blob);
                link.download = `船仓AI助手-配置备份-${new Date().toISOString().slice(0, 10)}.json`;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                URL.revokeObjectURL(link.href);
                this.showNotification('配置已导出', 'success');
            } catch (e) {
                this.showNotification(`导出失败: ${e.message}`, 'error');
                console.error('导出配置失败:', e);
            }
        }
        handleImport() {
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = '.json,application/json';
            input.onchange = e => {
                const file = e.target.files[0];
                if (!file) return;

                const reader = new FileReader();
                reader.onload = (event) => {
                    try {
                        const importedConfig = JSON.parse(event.target.result);
                        if (importedConfig && importedConfig.AI_MODELS && importedConfig.PROMPTS) {
                            CONFIG = importedConfig;
                            ConfigManager.saveConfig(CONFIG);

                            // 强制刷新UI
                            if (this.configPanel) {
                                this.configPanel.remove();
                                this.configPanel = null;
                            }
                            this.toggleConfigPanel();
                            this.updateTitleWithModel();
                            this.showNotification('配置导入成功!', 'success');
                        } else {
                            throw new Error('文件格式不正确');
                        }
                    } catch (err) {
                        this.showNotification(`导入失败: ${err.message}`, 'error');
                        console.error('导入配置失败:', err);
                    }
                };
                reader.readAsText(file);
            };
            input.click();
        }
        resetConfig() { if (confirm('确定要重置所有配置吗?')) { CONFIG = ConfigManager.getDefaultConfig(); ConfigManager.saveConfig(CONFIG); this.toggleConfigPanel(); this.toggleConfigPanel(); this.updateTitleWithModel(); this.showNotification('配置已重置', 'success'); } }

        toggleHistoryPanel() {
            if (!this.historyPanel || !this.shadowRoot.contains(this.historyPanel)) this.createHistoryPanel();
            const isVisible = this.historyPanel.style.display === 'block';
            this.historyPanel.style.display = isVisible ? 'none' : 'block';
            if (!isVisible) this.renderHistoryList();
        }

        createHistoryPanel() {
            if (this.historyPanel) this.historyPanel.remove();
            this.historyPanel = document.createElement('div');
            this.historyPanel.style.cssText = `position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 600px; max-width: 90vw; max-height: 80vh; background: rgba(255, 255, 255, 0.9); border-radius: 20px; color: #1f2937; font-family: -apple-system, sans-serif; z-index: 50000; display: none; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15); border: 1px solid rgba(0, 0, 0, 0.1); overflow: hidden; backdrop-filter: blur(20px) saturate(180%);`;

            const header = document.createElement('div');
            header.style.cssText = `padding: 20px 24px; background: rgba(59, 130, 246, 0.1); display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(0,0,0,0.05);`;

            // 左侧容器:标题 + 数量选择
            const leftContainer = document.createElement('div');
            leftContainer.style.cssText = `display: flex; align-items: center; gap: 12px;`;

            const title = document.createElement('h3');
            title.textContent = '🕒 历史记录';
            title.style.cssText = `margin: 0; font-size: 18px; font-weight: 600;`;

            // 数量限制选择器
            const limitSelect = document.createElement('select');
            limitSelect.title = '最大保存记录数';
            limitSelect.style.cssText = `padding: 4px 8px; border-radius: 6px; border: 1px solid rgba(0,0,0,0.1); background: rgba(255,255,255,0.5); font-size: 12px; color: #666; cursor: pointer; outline: none;`;
            const limitOptions = [
                { val: 50, text: '保留50条' },
                { val: 100, text: '保留100条' },
                { val: 200, text: '保留200条' },
                { val: 500, text: '保留500条' }
            ];
            const currentMax = CONFIG.HISTORY ? CONFIG.HISTORY.MAX_ITEMS : 50;
            limitOptions.forEach(opt => {
                const o = document.createElement('option');
                o.value = opt.val;
                o.textContent = opt.text;
                if (parseInt(currentMax) === opt.val) o.selected = true;
                limitSelect.appendChild(o);
            });
            limitSelect.addEventListener('change', () => {
                if (!CONFIG.HISTORY) CONFIG.HISTORY = {};
                CONFIG.HISTORY.MAX_ITEMS = parseInt(limitSelect.value);
                ConfigManager.saveConfig(CONFIG);
                this.showNotification(`已设置最大保留 ${CONFIG.HISTORY.MAX_ITEMS} 条记录`, 'success');
            });

            leftContainer.appendChild(title);
            leftContainer.appendChild(limitSelect);

            const controls = document.createElement('div'); controls.style.cssText = `display: flex; gap: 10px; align-items: center;`;

            // 全选复选框
            this.selectAllCheckbox = document.createElement('input');
            this.selectAllCheckbox.type = 'checkbox';
            this.selectAllCheckbox.title = '全选';
            this.selectAllCheckbox.style.cssText = `width: 16px; height: 16px; cursor: pointer; accent-color: #3b82f6;`;
            this.selectAllCheckbox.addEventListener('change', () => this.toggleSelectAllHistory());
            const selectLabel = document.createElement('span');
            selectLabel.textContent = '全选';
            selectLabel.style.cssText = `font-size: 12px; color: #666; cursor: pointer;`;
            selectLabel.addEventListener('click', () => { this.selectAllCheckbox.click(); });

            // 导出按钮
            const exportBtn = this.createSmallButton('📥 导出', '导出选中记录', 'rgba(59, 130, 246, 0.1)');
            exportBtn.style.color = '#3b82f6';
            exportBtn.addEventListener('click', () => this.handleExportHistory());

            // 清空按钮
            const clearBtn = this.createSmallButton('🗑️ 清空', '清空所有记录', 'rgba(244, 67, 54, 0.1)');
            clearBtn.style.color = '#ef4444';
            clearBtn.addEventListener('click', () => {
                if (confirm('确定要清空所有历史记录吗?')) {
                    HistoryManager.clearHistory();
                    this.selectedHistoryIds = new Set();
                    this.visibleHistoryCount = 20; // 重置显示计数
                    this.renderHistoryList();
                    this.showNotification('历史记录已清空', 'success');
                }
            });
            const closeBtn = this.createIconButton('✕', '关闭');
            closeBtn.addEventListener('click', () => this.toggleHistoryPanel());

            controls.appendChild(this.selectAllCheckbox); controls.appendChild(selectLabel);
            controls.appendChild(exportBtn); controls.appendChild(clearBtn); controls.appendChild(closeBtn);

            header.appendChild(leftContainer); // 左侧放标题+选择器
            header.appendChild(controls);

            this.historyListContainer = document.createElement('div');
            this.historyListContainer.style.cssText = `padding: 10px; overflow-y: auto; max-height: calc(80vh - 70px);`;

            // 无限滚动监听
            this.historyListContainer.addEventListener('scroll', () => {
                const { scrollTop, scrollHeight, clientHeight } = this.historyListContainer;
                // 距离底部 50px 时加载更多
                if (scrollTop + clientHeight >= scrollHeight - 50) {
                    this.loadMoreHistory();
                }
            });

            this.historyPanel.appendChild(header); this.historyPanel.appendChild(this.historyListContainer);
            this.shadowRoot.appendChild(this.historyPanel);

            // 初始化显示参数
            this.visibleHistoryCount = 20;
            this.renderedCount = 0;
            this.renderHistoryList(); // 初次渲染
        }

        loadMoreHistory() {
            const history = HistoryManager.getHistory();
            // 如果当前显示的已经 >= 总数,就不渲染了
            if (this.visibleHistoryCount >= history.length) return;

            // 每次多加载 20 条
            this.visibleHistoryCount += 20;
            // append = true 模式
            this.renderHistoryList(true);
        }

        createModelSelectionControl(modelConfig) {
            const container = document.createElement('div');
            container.style.cssText = `display: flex; gap: 8px; align-items: center; width: 100%;`;

            const inputContainer = document.createElement('div');
            inputContainer.style.flex = '1';

            const renderInput = () => {
                inputContainer.textContent = '';
                const hasModels = (modelConfig.SELECTED_MODELS && modelConfig.SELECTED_MODELS.length > 0) ||
                    (modelConfig.AVAILABLE_MODELS && modelConfig.AVAILABLE_MODELS.length > 0);

                if (hasModels) {
                    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; outline: none; transition: all 0.3s; box-sizing: border-box; appearance: none; -webkit-appearance: none;`;

                    // Use SELECTED_MODELS if available, otherwise fallback to AVAILABLE_MODELS
                    let displayModels = modelConfig.SELECTED_MODELS && modelConfig.SELECTED_MODELS.length > 0
                        ? modelConfig.SELECTED_MODELS
                        : modelConfig.AVAILABLE_MODELS;

                    // Add current model if not in list (to preserve value)
                    const currentModel = modelConfig.MODEL;
                    if (currentModel && !displayModels.includes(currentModel)) {
                        displayModels = [currentModel, ...displayModels];
                    }

                    displayModels.forEach(m => {
                        const option = document.createElement('option');
                        option.value = m;
                        option.textContent = m;
                        if (m === currentModel) option.selected = true;
                        select.appendChild(option);
                    });

                    // Add Custom Icon arrow
                    select.style.backgroundImage = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M2.5 4.5L6 8L9.5 4.5' stroke='%23666' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")`;
                    select.style.backgroundRepeat = 'no-repeat';
                    select.style.backgroundPosition = 'right 16px center';

                    select.addEventListener('change', () => { modelConfig.MODEL = select.value; this.updateTitleWithModel(); });
                    inputContainer.appendChild(select);
                } else {
                    const input = document.createElement('input');
                    input.type = 'text';
                    input.value = modelConfig.MODEL || '';
                    input.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('input', (e) => { modelConfig.MODEL = e.target.value; this.updateTitleWithModel(); });
                    input.addEventListener('focus', () => { input.style.boxShadow = '0 0 0 2px rgba(102, 126, 234, 0.2)'; });
                    input.addEventListener('blur', () => { input.style.boxShadow = 'none'; });
                    inputContainer.appendChild(input);
                }
            };

            // Initial Render
            renderInput();

            // Fetch Button
            const fetchBtn = this.createButton('📋 获取模型', 'secondary');
            fetchBtn.style.padding = '8px 12px';
            fetchBtn.style.whiteSpace = 'nowrap';
            fetchBtn.addEventListener('click', async () => {
                if (!modelConfig.API_KEY) { this.showNotification('请先填写 API Key', 'error'); return; }
                if (!modelConfig.API_URL) { this.showNotification('请先填写 API URL', 'error'); return; }

                fetchBtn.disabled = true;
                fetchBtn.textContent = '获取中...';

                try {
                    // Try to deduce /v1/models endpoint
                    // Common patterns: .../v1/chat/completions -> .../v1/models
                    let baseUrl = modelConfig.API_URL;
                    const urlObj = new URL(baseUrl);

                    if (baseUrl.includes('/chat/completions')) {
                        // e.g. https://host/v1/chat/completions -> https://host/v1/models
                        baseUrl = baseUrl.replace('/chat/completions', '/models');
                    } else if (baseUrl.includes('/v1')) {
                        // e.g. https://host/v1 -> https://host/v1/models
                        // Split at /v1 to be safe and reconstruct
                        const parts = baseUrl.split('/v1');
                        baseUrl = parts[0] + '/v1/models';
                    } else {
                        // Fallback: If no /v1/ is seen, assume it's a base host like https://api.zscc.in
                        // Standard OpenAI compatible path is /v1/models
                        baseUrl = urlObj.origin + (urlObj.origin.endsWith('/') ? '' : '/') + 'v1/models';
                    }


                    console.log('[ModelFetcher] Fetching from:', baseUrl);

                    // 处理可能存在的多 Key 情况,仅使用第一个 Key 获取模型列表
                    const currentApiKey = modelConfig.API_KEY.includes(',')
                        ? modelConfig.API_KEY.split(',')[0].trim()
                        : modelConfig.API_KEY.trim();

                    const response = await fetch(baseUrl, {
                        method: 'GET',
                        headers: {
                            'Authorization': `Bearer ${currentApiKey}`,
                            'Content-Type': 'application/json'
                        }
                    });

                    if (!response.ok) throw new Error(`HTTP ${response.status}`);
                    const data = await response.json();

                    let models = [];
                    // Parse data.data or data
                    if (data && Array.isArray(data.data)) {
                        models = data.data.map(m => m.id).sort();
                    } else if (Array.isArray(data)) {
                        models = data.map(m => m.id).sort();
                    } else {
                        throw new Error('无法解析返回的模型列表格式');
                    }

                    if (models.length === 0) throw new Error('未找到任何模型');

                    // Show Selection Dialog
                    this.showModelSelectionDialog(models, modelConfig, () => {
                        renderInput();
                        this.updateTitleWithModel();
                        this.showNotification(`已更新模型列表,当前选中 ${modelConfig.SELECTED_MODELS ? modelConfig.SELECTED_MODELS.length : 0} 个模型`, 'success');
                    });

                } catch (e) {
                    console.error('Fetch Models Error:', e);
                    this.showNotification(`获取模型失败: ${e.message}`, 'error');
                } finally {
                    fetchBtn.disabled = false;
                    fetchBtn.textContent = '📋 获取模型';
                }
            });

            container.appendChild(inputContainer);
            container.appendChild(fetchBtn);
            return container;
        }

        showModelSelectionDialog(allModels, modelConfig, callback) {
            const selectedModels = modelConfig.SELECTED_MODELS || [];

            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 content = document.createElement('div');
            content.style.cssText = `background: rgba(255, 255, 255, 0.95); border-radius: 16px; padding: 24px; width: 500px; max-width: 90vw; max-height: 80vh; display: flex; flex-direction: column; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);`;

            const title = document.createElement('h3'); title.textContent = '选择可用模型'; title.style.marginBottom = '16px';

            const listContainer = document.createElement('div');
            listContainer.style.cssText = `flex: 1; overflow-y: auto; border: 1px solid #eee; border-radius: 8px; padding: 8px; margin-bottom: 16px; display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; align-content: start;`;

            const checkboxes = [];
            allModels.forEach(m => {
                const label = document.createElement('label');
                label.style.cssText = `display: flex; align-items: center; gap: 8px; padding: 6px; cursor: pointer; border-radius: 6px; hover: background: #f5f5f5; font-size: 13px;`;
                label.addEventListener('mouseover', () => label.style.background = '#f0f0f0');
                label.addEventListener('mouseout', () => label.style.background = 'transparent');

                const cb = document.createElement('input');
                cb.type = 'checkbox';
                cb.value = m;
                if (selectedModels.includes(m)) cb.checked = true;

                const span = document.createElement('span');
                span.textContent = m;
                span.style.wordBreak = 'break-all';

                label.appendChild(cb);
                label.appendChild(span);
                listContainer.appendChild(label);
                checkboxes.push(cb);
            });

            const footer = document.createElement('div');
            footer.style.cssText = `display: flex; justify-content: space-between; align-items: center;`;

            const leftActions = document.createElement('div');
            const selectAll = document.createElement('button'); selectAll.textContent = '全选';
            selectAll.style.cssText = `border: none; background: none; color: #3b82f6; cursor: pointer; font-size: 13px; margin-right: 12px;`;
            selectAll.onclick = () => checkboxes.forEach(c => c.checked = true);

            const selectNone = document.createElement('button'); selectNone.textContent = '清空';
            selectNone.style.cssText = `border: none; background: none; color: #ef4444; cursor: pointer; font-size: 13px;`;
            selectNone.onclick = () => checkboxes.forEach(c => c.checked = false);

            leftActions.appendChild(selectAll); leftActions.appendChild(selectNone);

            const rightActions = document.createElement('div'); rightActions.style.gap = '12px'; rightActions.style.display = 'flex';
            const cancelBtn = this.createButton('取消', 'secondary'); cancelBtn.onclick = () => dialog.remove();
            const saveBtn = this.createButton('确认', 'primary');
            saveBtn.onclick = () => {
                const checked = checkboxes.filter(c => c.checked).map(c => c.value);
                modelConfig.SELECTED_MODELS = checked; // Update selected models

                // If current MODEL is not in selected list, update it to the first selected one
                if (checked.length > 0 && (!modelConfig.MODEL || !checked.includes(modelConfig.MODEL))) {
                    modelConfig.MODEL = checked[0];
                }

                if (callback) callback();
                dialog.remove();
            };

            rightActions.appendChild(cancelBtn); rightActions.appendChild(saveBtn);
            footer.appendChild(leftActions); footer.appendChild(rightActions);

            content.appendChild(title); content.appendChild(listContainer); content.appendChild(footer);
            dialog.appendChild(content); this.shadowRoot.appendChild(dialog);
            dialog.addEventListener('click', (e) => { if (e.target === dialog) dialog.remove(); });
        }

        renderHistoryList(append = false) {
            // 初始化选中记录集合
            if (!this.selectedHistoryIds) this.selectedHistoryIds = new Set();
            // 确保显示计数已初始化
            if (!this.visibleHistoryCount) this.visibleHistoryCount = 20;

            // 如果不是追加模式,清空容器和计数
            if (!append) {
                while (this.historyListContainer.firstChild) {
                    this.historyListContainer.removeChild(this.historyListContainer.firstChild);
                }
                this.renderedCount = 0;
            }

            const history = HistoryManager.getHistory();
            console.log('[renderHistoryList] Total items:', history.length, 'Visible items limit:', this.visibleHistoryCount, 'Mode:', append ? 'Append' : 'Reset');

            if (history.length === 0) {
                const emptyMsg = document.createElement('div');
                emptyMsg.textContent = '暂无历史记录';
                emptyMsg.style.cssText = 'text-align:center; padding: 40px; color: #888;';
                this.historyListContainer.appendChild(emptyMsg);
                if (this.selectAllCheckbox) this.selectAllCheckbox.checked = false;
                return;
            }

            // 计算需要渲染的范围
            // 如果是 append,从 renderedCount 开始,到 limit 结束
            // 如果是 reset,从 0 开始,到 limit 结束
            const startIdx = append ? this.renderedCount : 0;
            const endIdx = Math.min(this.visibleHistoryCount, history.length);

            const itemsToRender = history.slice(startIdx, endIdx);

            itemsToRender.forEach(item => {
                const el = document.createElement('div');
                el.style.cssText = `display: flex; align-items: center; padding: 12px 16px; background: rgba(255,255,255,0.5); border-radius: 12px; margin-bottom: 8px; border: 1px solid rgba(0,0,0,0.05); transition: all 0.2s;`;
                el.addEventListener('mouseover', () => el.style.background = 'rgba(255,255,255,0.8)');
                el.addEventListener('mouseout', () => el.style.background = 'rgba(255,255,255,0.5)');

                // 复选框
                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.checked = this.selectedHistoryIds.has(item.id);
                checkbox.style.cssText = `width: 16px; height: 16px; cursor: pointer; margin-right: 12px; accent-color: #3b82f6; flex-shrink: 0;`;
                checkbox.addEventListener('change', (e) => {
                    e.stopPropagation();
                    if (checkbox.checked) {
                        this.selectedHistoryIds.add(item.id);
                    } else {
                        this.selectedHistoryIds.delete(item.id);
                    }
                    this.updateSelectAllCheckbox();
                });
                checkbox.addEventListener('click', (e) => e.stopPropagation());

                const info = document.createElement('div');
                info.style.cssText = `flex: 1; cursor: pointer; padding-right: 10px; overflow: hidden;`;
                info.addEventListener('click', () => this.loadHistoryItem(item));

                const titleText = document.createElement('div');
                titleText.textContent = item.title;
                titleText.style.cssText = `font-weight: 500; font-size: 14px; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;`;

                // 元数据行:平台链接 + 频道名称 + 时间
                const metaText = document.createElement('div');
                metaText.style.cssText = `font-size: 11px; color: #666; display: flex; align-items: center; gap: 4px; flex-wrap: wrap;`;

                // 平台图标和名称(点击跳转到原视频)
                const platformLink = document.createElement('a');
                platformLink.href = item.url || '#';
                platformLink.target = '_blank';
                platformLink.textContent = `${this.getPlatformIcon(item.platform)} ${item.platform}`;
                platformLink.style.cssText = `color: #3b82f6; text-decoration: none; cursor: pointer; transition: color 0.2s;`;
                platformLink.addEventListener('mouseover', () => platformLink.style.color = '#2563eb');
                platformLink.addEventListener('mouseout', () => platformLink.style.color = '#3b82f6');
                platformLink.addEventListener('click', (e) => e.stopPropagation());

                // 分隔符
                const sep1 = document.createElement('span');
                sep1.textContent = '·';
                sep1.style.color = '#999';

                // 频道名称
                const channelSpan = document.createElement('span');
                channelSpan.textContent = item.channelName || '未知频道';
                channelSpan.style.cssText = `color: #888; max-width: 150px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;`;
                channelSpan.title = item.channelName || '未知频道';

                // 分隔符
                const sep2 = document.createElement('span');
                sep2.textContent = '·';
                sep2.style.color = '#999';

                // 时间
                const timeStr = new Date(item.timestamp).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
                const timeSpan = document.createElement('span');
                timeSpan.textContent = timeStr;

                metaText.appendChild(platformLink);
                metaText.appendChild(sep1);
                metaText.appendChild(channelSpan);
                metaText.appendChild(sep2);
                metaText.appendChild(timeSpan);

                info.appendChild(titleText); info.appendChild(metaText);

                // 按钮容器
                const btnContainer = document.createElement('div');
                btnContainer.style.cssText = `display: flex; align-items: center; gap: 4px; flex-shrink: 0;`;

                // 发布按钮 - 开启PublishMarkdown时显示
                if (CONFIG.PUBLISH_MARKDOWN?.ENABLED) {
                    const publishBtn = document.createElement('button');
                    publishBtn.textContent = '发布';
                    publishBtn.style.cssText = `border: none; background: transparent; color: #3b82f6; cursor: pointer; padding: 4px 8px; font-size: 12px; border-radius: 4px; transition: all 0.2s;`;
                    publishBtn.addEventListener('mouseover', () => { publishBtn.style.background = 'rgba(59, 130, 246, 0.1)'; });
                    publishBtn.addEventListener('mouseout', () => { publishBtn.style.background = 'transparent'; });
                    publishBtn.addEventListener('click', (e) => {
                        e.stopPropagation();
                        this.publishFromHistory(item);
                    });
                    btnContainer.appendChild(publishBtn);
                }

                // 链接按钮 - 仅当有发布链接时显示
                if (item.publishedUrl) {
                    const linkBtn = document.createElement('button');
                    linkBtn.textContent = '链接';
                    linkBtn.style.cssText = `border: none; background: transparent; color: #10b981; cursor: pointer; padding: 4px 8px; font-size: 12px; border-radius: 4px; transition: all 0.2s;`;
                    linkBtn.addEventListener('mouseover', () => { linkBtn.style.background = 'rgba(16, 185, 129, 0.1)'; });
                    linkBtn.addEventListener('mouseout', () => { linkBtn.style.background = 'transparent'; });
                    linkBtn.addEventListener('click', (e) => {
                        e.stopPropagation();
                        window.open(item.publishedUrl, '_blank');
                    });
                    btnContainer.appendChild(linkBtn);
                }

                const delBtn = document.createElement('button');
                delBtn.textContent = '✕';
                delBtn.style.cssText = `border: none; background: transparent; color: #999; cursor: pointer; padding: 4px 8px; font-size: 14px; border-radius: 4px; transition: all 0.2s; flex-shrink: 0;`;
                delBtn.addEventListener('mouseover', () => { delBtn.style.background = 'rgba(244, 67, 54, 0.1)'; delBtn.style.color = '#ef4444'; });
                delBtn.addEventListener('mouseout', () => { delBtn.style.background = 'transparent'; delBtn.style.color = '#999'; });
                delBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (confirm('确定删除这条记录吗?')) {
                        HistoryManager.deleteRecord(item.id);
                        this.selectedHistoryIds.delete(item.id);
                        this.renderHistoryList();
                    }
                });

                el.appendChild(checkbox); el.appendChild(info); el.appendChild(btnContainer); el.appendChild(delBtn);
                this.historyListContainer.appendChild(el);
            });

            // 更新已渲染数量,供下次 append 使用
            this.renderedCount = endIdx;

            // 更新全选复选框状态
            this.updateSelectAllCheckbox();
        }


        getPlatformIcon(platform) {
            if (platform === 'YOUTUBE') return '📺';
            if (platform === 'WECHAT') return '📰';
            if (platform === 'BILIBILI') return '🅱️';
            return '📄';
        }

        // 全选/取消全选历史记录
        toggleSelectAllHistory() {
            const history = HistoryManager.getHistory();
            if (!this.selectedHistoryIds) this.selectedHistoryIds = new Set();

            if (this.selectAllCheckbox.checked) {
                // 全选 - 清空后重新添加确保类型一致
                this.selectedHistoryIds.clear();
                history.forEach(item => this.selectedHistoryIds.add(item.id));
                console.log('[toggleSelectAllHistory] 全选:', this.selectedHistoryIds.size, '条记录', Array.from(this.selectedHistoryIds));
            } else {
                // 取消全选
                this.selectedHistoryIds.clear();
                console.log('[toggleSelectAllHistory] 取消全选');
            }
            this.renderHistoryList();
        }

        // 更新全选复选框状态
        updateSelectAllCheckbox() {
            if (!this.selectAllCheckbox) return;
            const history = HistoryManager.getHistory();
            if (history.length === 0) {
                this.selectAllCheckbox.checked = false;
                this.selectAllCheckbox.indeterminate = false;
            } else if (this.selectedHistoryIds.size === 0) {
                this.selectAllCheckbox.checked = false;
                this.selectAllCheckbox.indeterminate = false;
            } else if (this.selectedHistoryIds.size === history.length) {
                this.selectAllCheckbox.checked = true;
                this.selectAllCheckbox.indeterminate = false;
            } else {
                this.selectAllCheckbox.checked = false;
                this.selectAllCheckbox.indeterminate = true;
            }
        }

        // 从历史记录中发布
        async publishFromHistory(item) {
            if (!CONFIG.PUBLISH_MARKDOWN?.ENABLED || !CONFIG.PUBLISH_MARKDOWN?.API_KEY) {
                this.showNotification('请先在设置中启用并配置 PublishMarkdown API Key', 'error');
                return;
            }
            if (!item.summary || item.summary.trim() === '') {
                this.showNotification('该记录没有可发布的内容', 'error');
                return;
            }

            const markdownContent = `# ${item.title}\n\n**原文链接:** ${item.url}\n**总结时间:** ${new Date(item.timestamp).toLocaleString('zh-CN')}\n\n---\n\n## 内容总结\n\n${item.summary}\n\n---\n\n*本总结由 船仓AI助手 生成,脚本:dub.sh/iytb*`;

            this.showNotification('正在发布...', 'info');

            try {
                const isUpdate = !!item.publishedIdentifier;
                const apiUrl = isUpdate
                    ? `https://publishmarkdown.com/v1/api/markdown/${item.publishedIdentifier}`
                    : 'https://publishmarkdown.com/v1/api/markdown';
                const method = isUpdate ? 'PUT' : 'POST';

                const response = await new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: method,
                        url: apiUrl,
                        headers: {
                            'Content-Type': 'application/json',
                            'api-key': CONFIG.PUBLISH_MARKDOWN.API_KEY
                        },
                        data: JSON.stringify({ content: markdownContent }),
                        onload: function (res) {
                            try {
                                const data = JSON.parse(res.responseText);
                                if (res.status >= 200 && res.status < 300 && data.status === 'success') {
                                    resolve(data);
                                } else {
                                    reject(new Error(data.message || `HTTP ${res.status}`));
                                }
                            } catch (e) {
                                reject(new Error('解析响应失败'));
                            }
                        },
                        onerror: () => reject(new Error('网络请求失败')),
                        ontimeout: () => reject(new Error('请求超时'))
                    });
                });

                // 成功发布,更新历史记录
                HistoryManager.updateRecord(item.id, {
                    publishedUrl: response.data.url,
                    publishedIdentifier: response.data.identifier
                });
                this.renderHistoryList(); // 刷新列表以显示链接按钮
                this.showNotification(isUpdate ? '已更新发布' : '发布成功!', 'success');

            } catch (e) {
                this.showNotification(`发布失败: ${e.message}`, 'error');
            }
        }

        // 处理导出历史记录
        async handleExportHistory() {
            console.log('[handleExportHistory] selectedHistoryIds:', this.selectedHistoryIds ? Array.from(this.selectedHistoryIds) : 'undefined');

            if (!this.selectedHistoryIds || this.selectedHistoryIds.size === 0) {
                this.showNotification('请先选择要导出的记录', 'error');
                return;
            }

            const history = HistoryManager.getHistory();
            console.log('[handleExportHistory] history ids:', history.map(item => item.id));

            const selectedRecords = history.filter(item => this.selectedHistoryIds.has(item.id));
            console.log('[handleExportHistory] selectedRecords:', selectedRecords.length);

            if (selectedRecords.length === 0) {
                this.showNotification('未找到选中的记录', 'error');
                return;
            }

            try {
                if (selectedRecords.length === 1) {
                    // 单条记录导出为MD文件
                    this.exportSingleRecord(selectedRecords[0]);
                } else {
                    // 多条记录打包为ZIP
                    console.log('[handleExportHistory] 准备导出', selectedRecords.length, '条记录为ZIP');
                    await this.exportMultipleRecords(selectedRecords);
                }
            } catch (e) {
                console.error('导出失败:', e);
                this.showNotification(`导出失败: ${e.message}`, 'error');
            }
        }

        // 生成单条记录的Markdown内容
        generateRecordMarkdown(record) {
            const platformName = {
                'YOUTUBE': 'YouTube',
                'WECHAT': '微信公众号',
                'BILIBILI': 'B站'
            }[record.platform] || record.platform;

            const timeStr = new Date(record.timestamp).toLocaleString('zh-CN');

            return `# ${record.title}

**平台:** ${platformName}
**作者:** ${record.channelName || '未知'}
**原文链接:** ${record.url || '无'}
**总结时间:** ${timeStr}

---

${record.summary}

---

*本内容由 船仓AI助手 生成,脚本:dub.sh/iytb*
`;
        }

        // 导出单条记录为MD文件
        exportSingleRecord(record) {
            const content = this.generateRecordMarkdown(record);
            const cleanTitle = record.title.replace(/[<>:"/\\|?*\x00-\x1f]/g, '').trim().substring(0, 50);
            const filename = `${cleanTitle}.md`;

            const blob = new Blob([content], { 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.href);

            this.showNotification('已导出为MD文件', 'success');
        }

        // 批量导出多条记录为ZIP
        exportMultipleRecords(records) {
            console.log('[exportMultipleRecords] Starting export for', records.length, 'records');
            const self = this;

            if (typeof JSZip === 'undefined') {
                console.error('[exportMultipleRecords] JSZip is undefined');
                this.showNotification('ZIP库未加载,请刷新页面重试', 'error');
                return;
            }
            console.log('[exportMultipleRecords] JSZip is available, version:', JSZip.version);

            try {
                const zip = new JSZip();
                console.log('[exportMultipleRecords] JSZip instance created');
                const usedNames = new Set();

                records.forEach((record, index) => {
                    const content = this.generateRecordMarkdown(record);
                    let baseName = record.title.replace(/[<>:"/\\|?*\x00-\x1f]/g, '').trim().substring(0, 50);
                    if (!baseName) baseName = `记录_${index + 1}`;

                    // 确保文件名唯一
                    let filename = `${baseName}.md`;
                    let counter = 1;
                    while (usedNames.has(filename)) {
                        filename = `${baseName}_${counter}.md`;
                        counter++;
                    }
                    usedNames.add(filename);

                    zip.file(filename, content);
                    console.log('[exportMultipleRecords] Added file:', filename);
                });

                const dateStr = new Date().toISOString().slice(0, 10);
                const zipFilename = `船仓AI助手-历史记录-${dateStr}.zip`;
                console.log('[exportMultipleRecords] Generating ZIP:', zipFilename);

                // 使用 .then()/.catch() 而非 await
                const generatePromise = zip.generateAsync({ type: 'blob' });
                console.log('[exportMultipleRecords] generateAsync called, promise:', generatePromise);

                generatePromise.then(function (zipBlob) {
                    console.log('[exportMultipleRecords] ZIP blob created, size:', zipBlob.size);

                    const link = document.createElement('a');
                    link.href = URL.createObjectURL(zipBlob);
                    link.download = zipFilename;
                    document.body.appendChild(link);
                    console.log('[exportMultipleRecords] Triggering download');
                    link.click();
                    document.body.removeChild(link);
                    URL.revokeObjectURL(link.href);

                    self.showNotification(`已导出 ${records.length} 条记录为ZIP`, 'success');
                    console.log('[exportMultipleRecords] Export completed successfully');
                }).catch(function (e) {
                    console.error('[exportMultipleRecords] generateAsync error:', e);
                    self.showNotification(`ZIP生成失败: ${e.message}`, 'error');
                });

            } catch (e) {
                console.error('[exportMultipleRecords] Sync error:', e);
                this.showNotification(`导出失败: ${e.message}`, 'error');
            }
        }


        loadHistoryItem(item) {
            this.originalSummaryText = item.summary;
            this.currentHistoryItemId = item.id; // 保存当前加载的历史记录ID
            while (this.summaryContent.firstChild) { this.summaryContent.removeChild(this.summaryContent.firstChild); }
            this.createFormattedContent(this.summaryContent, item.summary);
            this.summaryPanel.style.display = 'block';
            setTimeout(() => this.updateSummaryContentHeight(), 50); // 动态调整高度
            this.updateStatus('已加载历史记录', 'success');
            // 更新标题
            if (this.contentController) {
                this.contentController.translatedTitle = item.title;
            }
            // 关闭历史面板
            this.toggleHistoryPanel();
            // 不自动滚动,或者滚动到顶部
            this.summaryPanel.scrollIntoView({ behavior: 'smooth', block: 'start' });

            // 恢复发布状态:如果记录已发布,显示发布链接
            if (item.publishedUrl && item.publishedIdentifier) {
                this.currentPublishedIdentifier = item.publishedIdentifier;
                if (this.publishedUrlLink) {
                    this.publishedUrlLink.href = item.publishedUrl;
                    this.publishedUrlLink.textContent = item.publishedUrl;
                }
                if (this.publishedUrlContainer) this.publishedUrlContainer.style.display = 'block';
            } else {
                // 重置发布状态
                this.currentPublishedIdentifier = null;
                if (this.publishedUrlContainer) this.publishedUrlContainer.style.display = 'none';
            }

            // 更新发布按钮可见性(确保在所有平台都能正确显示)
            if (this.publishButton) {
                this.publishButton.style.display = CONFIG.PUBLISH_MARKDOWN?.ENABLED ? 'block' : 'none';
            }
        }
        // ++ 请用下面这整块新代码,替换掉原来的 toggleCollapse() 函数 ++
        toggleCollapse() {
            this.isCollapsed = !this.isCollapsed;

            if (this.isCollapsed) {
                // --- 执行收起操作 ---
                this.mainContent.style.display = 'none';
                this.toggleButton.textContent = '↓';

                // 隐藏标题和设置按钮
                this.titleElement.style.display = 'none';
                this.configButton.style.display = 'none';
                if (this.historyButton) this.historyButton.style.display = 'none';
                if (this.leftResizeHandle) this.leftResizeHandle.style.display = 'none'; // 隐藏左侧调整手柄

                // 将主容器和顶部栏变得透明且无边框
                this.container.style.background = 'transparent';
                this.container.style.boxShadow = 'none';
                this.container.style.backdropFilter = 'none';
                this.container.style.border = 'none';
                this.container.style.padding = '0';
                this.container.style.width = 'auto'; // 宽度自适应
                this.container.style.minWidth = '0';

                this.topBar.style.padding = '0';
                this.topBar.style.background = 'transparent';
                this.topBar.style.justifyContent = 'flex-end'; // 让按钮靠右

                // 将收起按钮美化成一个独立的浅色磨砂质感按钮
                this.toggleButton.style.background = 'rgba(255, 255, 255, 0.85)';
                this.toggleButton.style.backdropFilter = 'blur(20px) saturate(180%)';
                this.toggleButton.style.border = '1px solid rgba(0, 0, 0, 0.1)';
                this.toggleButton.style.boxShadow = '0 8px 32px 0 rgba(0, 0, 0, 0.1)';
                this.toggleButton.style.borderRadius = '50%';
                this.toggleButton.style.width = '40px';
                this.toggleButton.style.height = '40px';
                this.toggleButton.style.padding = '0';
                this.toggleButton.style.fontSize = '22px';
                this.toggleButton.style.color = '#3b82f6';
                this.toggleButton.style.cursor = 'grab';
                this.toggleButton.style.animation = 'pulse 2s ease-in-out infinite';

                // 添加呼吸动画样式
                if (!document.getElementById('cchelper-animations')) {
                    const style = document.createElement('style');
                    style.id = 'cchelper-animations';
                    style.textContent = `
                        @keyframes pulse {
                            0%, 100% { transform: scale(1); box-shadow: 0 8px 32px 0 rgba(59, 130, 246, 0.2); }
                            50% { transform: scale(1.05); box-shadow: 0 8px 40px 0 rgba(59, 130, 246, 0.35); }
                        }
                    `;
                    document.head.appendChild(style);
                }

            } else {
                // --- 执行展开操作 ---
                // 如果处于边缘隐藏模式,先退出
                if (this.isEdgeHidden) {
                    this.exitEdgeHiddenMode();
                }

                this.mainContent.style.display = 'block';

                // 显示标题和设置按钮
                this.titleElement.style.display = 'flex';
                this.configButton.style.display = 'block';
                if (this.historyButton) this.historyButton.style.display = 'block';
                if (this.leftResizeHandle) this.leftResizeHandle.style.display = 'block'; // 显示左侧调整手柄

                // 恢复主容器的原始样式
                const defaultWidth = this.platform === 'BILIBILI' ? '453px' : '420px';
                this.container.style.cssText = `position: fixed; top: 80px; right: 20px; width: ${defaultWidth}; min-width: 350px; max-width: 90vw; background: rgba(255, 255, 255, 0.75); border-radius: 16px; padding: 0; color: #1f2937; 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(0, 0, 0, 0.1); backdrop-filter: blur(20px) saturate(180%); border: 1px solid rgba(0, 0, 0, 0.1);`;

                // 恢复顶部栏的原始样式
                this.topBar.style.cssText = `display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; cursor: move; background: rgba(255, 255, 255, 0.5); border-radius: 16px 16px 0 0; backdrop-filter: blur(10px);`;

                // 恢复展开/收起按钮的原始样式
                this.toggleButton.style.cssText = `background: rgba(59, 130, 246, 0.1); border: none; color: #3b82f6; cursor: pointer; padding: 8px; font-size: 14px; border-radius: 8px; transition: all 0.2s ease; backdrop-filter: blur(10px); pointer-events: auto;`;
                this.toggleButton.textContent = '↑'; // cssText会覆盖内容, 所以需要重新设置
            }
        }
        toggleConfigPanel() {
            if (!this.configPanel || !this.shadowRoot.contains(this.configPanel)) this.createConfigPanel();
            const isVisible = this.configPanel.style.display === 'block';
            this.configPanel.style.display = isVisible ? 'none' : 'block';
        }
        updateStatus(message, type = 'info') {
            // Updated to use the Summary Button for status feedback
            if (!this.summaryButton) return;

            this.summaryButton.textContent = message;

            // Optional: Change button style based on state
            if (type === 'info') {
                // Loading state is usually handled by 'disabled' in the caller, but we can add an icon or pulse here if we wanted
            } else if (type === 'success') {
                setTimeout(() => {
                    this.summaryButton.textContent = this.summaryButton.dataset.originalText || '🤖 生成总结';
                    this.summaryButton.disabled = false;
                }, 3000);
            } else if (type === 'error') {
                // Keep error message longer or until click
                setTimeout(() => {
                    this.summaryButton.textContent = this.summaryButton.dataset.originalText || '🤖 生成总结';
                    this.summaryButton.disabled = false;
                }, 5000);
            }
        }
        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;`;
            this.shadowRoot.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';
                // Toggle the new flex row instead of individual elements
                if (this.actionRow) {
                    this.actionRow.style.display = 'flex';
                }
                // Fallback for safety if old props are accessed elsewhere (though we removed them)
                if (this.mainPromptGroup) this.mainPromptGroup.style.display = 'block';
                if (this.summaryButton) 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');
                setTimeout(() => this.updateSummaryContentHeight(), 50); // 动态调整高度
                this.summaryPanel.scrollIntoView({ behavior: 'smooth', block: 'start' });
                // 重置发布状态
                this.currentPublishedIdentifier = null;
                if (this.publishedUrlContainer) {
                    this.publishedUrlContainer.style.display = 'none';
                }
                // 更新发布按钮可见性
                if (this.publishButton) {
                    this.publishButton.style.display = CONFIG.PUBLISH_MARKDOWN?.ENABLED ? 'block' : 'none';
                }

                // 保存到历史记录
                const historyRecord = {
                    id: this.contentController.getContentId(),
                    title: this.contentController.translatedTitle || this.contentController.getContentTitle(),
                    url: window.location.href,
                    summary: summary,
                    platform: this.platform,
                    channelName: this.contentController.getChannelName()
                };
                console.log('[handleGenerateSummary] About to save history record:', historyRecord);
                try {
                    HistoryManager.addRecord(historyRecord);
                    this.showNotification('历史记录已保存', 'success');
                } catch (historyErr) {
                    console.error('[handleGenerateSummary] History save error:', historyErr);
                    this.showNotification('历史记录保存失败: ' + historyErr.message, 'error');
                }
            } catch (e) {
                this.updateStatus(`生成总结失败: ${e.message}`, 'error'); this.showNotification(`生成总结失败: ${e.message}`, 'error');
            } finally { this.summaryButton.disabled = false; }
        }
        async handlePublishMarkdown(customIdentifier = null) {
            if (!CONFIG.PUBLISH_MARKDOWN?.ENABLED || !CONFIG.PUBLISH_MARKDOWN?.API_KEY) {
                this.showNotification('请先在设置中启用并配置 PublishMarkdown API Key', 'error');
                return;
            }
            const textToPublish = this.originalSummaryText || this.summaryContent.textContent;
            if (!textToPublish || textToPublish.trim() === '') {
                this.showNotification('没有可发布的内容', 'error');
                return;
            }
            const title = this.contentController.translatedTitle || this.contentController.getContentTitle();
            const id = this.contentController.getContentId();
            const markdownContent = `# ${title}\n\n**原文链接:** ${window.location.href}\n**总结时间:** ${new Date().toLocaleString('zh-CN')}\n\n---\n\n## 内容总结\n\n${textToPublish}\n\n---\n\n*本总结由 船仓AI助手 生成,脚本:dub.sh/iytb*`;

            this.publishButton.disabled = true;
            this.publishButton.textContent = '发布中...';

            try {
                const isUpdate = this.currentPublishedIdentifier && customIdentifier;
                const apiUrl = isUpdate
                    ? `https://publishmarkdown.com/v1/api/markdown/${this.currentPublishedIdentifier}`
                    : 'https://publishmarkdown.com/v1/api/markdown';
                const method = isUpdate ? 'PUT' : 'POST';

                const requestBody = { content: markdownContent };
                if (customIdentifier) {
                    requestBody.identifier = customIdentifier;
                }

                const response = await new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: method,
                        url: apiUrl,
                        headers: {
                            'Content-Type': 'application/json',
                            'api-key': CONFIG.PUBLISH_MARKDOWN.API_KEY
                        },
                        data: JSON.stringify(requestBody),
                        onload: function (res) {
                            try {
                                const data = JSON.parse(res.responseText);
                                if (res.status >= 200 && res.status < 300 && data.status === 'success') {
                                    resolve(data);
                                } else {
                                    reject(new Error(data.message || `HTTP ${res.status}`));
                                }
                            } catch (e) {
                                reject(new Error('解析响应失败'));
                            }
                        },
                        onerror: function (err) {
                            reject(new Error('网络请求失败'));
                        },
                        ontimeout: function () {
                            reject(new Error('请求超时'));
                        }
                    });
                });

                // 成功发布
                const publishedUrl = response.data.url;
                this.currentPublishedIdentifier = response.data.identifier;
                this.publishedUrlLink.href = publishedUrl;
                this.publishedUrlLink.textContent = publishedUrl;
                this.publishedUrlContainer.style.display = 'block';
                this.showNotification(isUpdate ? 'URL已更新' : '发布成功!', 'success');

                // 保存发布链接到历史记录
                if (id) {
                    HistoryManager.updateRecord(id, {
                        publishedUrl: publishedUrl,
                        publishedIdentifier: response.data.identifier
                    });
                }

            } catch (e) {
                this.showNotification(`发布失败: ${e.message}`, 'error');
            } finally {
                this.publishButton.disabled = false;
                this.publishButton.textContent = '📤 发布';
            }
        }
        showEditIdentifierDialog() {
            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: rgba(255, 255, 255, 0.92); border-radius: 16px; padding: 24px; width: 400px; max-width: 90vw; color: #1f2937; backdrop-filter: blur(20px) saturate(180%); border: 1px solid rgba(0, 0, 0, 0.1); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);`;
            const dialogTitle = document.createElement('h3'); dialogTitle.textContent = '✏️ 编辑自定义URL'; dialogTitle.style.cssText = `margin: 0 0 16px 0; color: #1f2937;`;
            const helpText = document.createElement('p'); helpText.textContent = '输入新的URL标识符,发布后URL将变为:'; helpText.style.cssText = `font-size: 13px; color: #666; margin-bottom: 8px;`;
            const previewUrl = document.createElement('code'); previewUrl.style.cssText = `display: block; font-size: 12px; color: #c83232; background: rgba(200, 50, 50, 0.1); padding: 8px 12px; border-radius: 6px; margin-bottom: 16px; word-break: break-all;`;
            previewUrl.textContent = `https://publishmarkdown.com/${this.currentPublishedIdentifier || 'your-identifier'}`;
            const identifierInput = this.createInput(this.currentPublishedIdentifier || '', null, 'text', '输入自定义标识符 (如: my-article)');
            identifierInput.addEventListener('input', () => {
                previewUrl.textContent = `https://publishmarkdown.com/${identifierInput.value || 'your-identifier'}`;
            });
            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.style.background = '#c83232';
            saveBtn.addEventListener('click', async () => {
                const newIdentifier = identifierInput.value.trim();
                if (!newIdentifier) {
                    this.showNotification('请输入有效的标识符', 'error');
                    return;
                }
                dialog.remove();
                await this.handlePublishMarkdown(newIdentifier);
            });
            buttonContainer.appendChild(cancelBtn); buttonContainer.appendChild(saveBtn);
            dialogContent.appendChild(dialogTitle); dialogContent.appendChild(helpText); dialogContent.appendChild(previewUrl);
            dialogContent.appendChild(this.createFormGroup('新标识符', identifierInput)); dialogContent.appendChild(buttonContainer);
            dialog.appendChild(dialogContent); this.shadowRoot.appendChild(dialog);
            dialog.addEventListener('click', (e) => { if (e.target === dialog) dialog.remove(); });
        }
        updateTitleWithModel() {
            if (!this.titleElement) return;

            this.titleElement.textContent = ''; // Clear previous content safely
            // 强制不换行,防止被挤压
            this.titleElement.style.display = this.isCollapsed ? 'none' : 'flex';
            this.titleElement.style.alignItems = 'center';
            this.titleElement.style.whiteSpace = 'nowrap';
            this.titleElement.style.flexShrink = '0';

            const titleSpan = document.createElement('span');
            titleSpan.textContent = '💡 船仓AI助手';
            titleSpan.style.flexShrink = '0'; // 标题文字也不收缩
            this.titleElement.appendChild(titleSpan);

            // Spacer
            const spacer = document.createElement('span');
            spacer.textContent = ' ';
            spacer.style.margin = '0 4px';
            this.titleElement.appendChild(spacer);

            // 收集所有有效平台的模型(有API Key且有模型名称的)
            const validModels = [];
            Object.keys(CONFIG.AI_MODELS).forEach(platformKey => {
                if (platformKey !== 'TYPE') {
                    const platform = CONFIG.AI_MODELS[platformKey];
                    if (platform.API_KEY) {
                        // 优先使用用户勾选的 Selected Models
                        if (platform.SELECTED_MODELS && platform.SELECTED_MODELS.length > 0) {
                            platform.SELECTED_MODELS.forEach(m => {
                                validModels.push({
                                    platformKey: platformKey,
                                    platformName: platform.NAME || platformKey,
                                    model: m
                                });
                            });
                        } else if (platform.MODEL) {
                            // 兼容旧逻辑:如果没有勾选,则显示当前默认模型
                            validModels.push({
                                platformKey: platformKey,
                                platformName: platform.NAME || platformKey,
                                model: platform.MODEL
                            });
                        }
                    }
                }
            });

            if (validModels.length > 0) {
                // Create Global Model Dropdown
                const select = document.createElement('select');
                this.globalModelSelect = select; // 保存引用以便同步
                select.style.cssText = `border: none; background: transparent; font-weight: 500; font-size: 14px; color: #888; cursor: pointer; outline: none; appearance: none; -webkit-appearance: none; padding-right: 18px; width: 148px; text-overflow: ellipsis; flex-shrink: 0;`;

                // 当前选中的平台和模型
                const currentPlatformKey = CONFIG.AI_MODELS.TYPE;
                const currentModel = CONFIG.AI_MODELS[currentPlatformKey]?.MODEL;

                validModels.forEach(item => {
                    const option = document.createElement('option');
                    option.value = `${item.platformKey}|${item.model}`; // 存储平台key和模型名
                    option.textContent = `${item.model} - ${item.platformName}`;
                    if (item.platformKey === currentPlatformKey && item.model === currentModel) {
                        option.selected = true;
                    }
                    select.appendChild(option);
                });

                // Custom Arrow
                select.style.backgroundImage = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M2.5 4.5L6 8L9.5 4.5' stroke='%23333' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")`;
                select.style.backgroundRepeat = 'no-repeat';
                select.style.backgroundPosition = 'right center';

                select.addEventListener('change', (e) => {
                    const [platformKey, modelName] = e.target.value.split('|');
                    // 切换到对应平台
                    CONFIG.AI_MODELS.TYPE = platformKey;
                    // 更新对应平台的模型(如果模型名不同)
                    if (CONFIG.AI_MODELS[platformKey]) {
                        CONFIG.AI_MODELS[platformKey].MODEL = modelName;
                    }
                    ConfigManager.saveConfig(CONFIG);
                    select.blur(); // Remove focus
                    this.showNotification(`已切换到: ${modelName} (${CONFIG.AI_MODELS[platformKey]?.NAME || platformKey})`, 'success');
                });

                // Hover effect
                select.addEventListener('mouseenter', () => select.style.opacity = '0.7');
                select.addEventListener('mouseleave', () => select.style.opacity = '1');

                this.titleElement.appendChild(select);
            } else {
                // No valid models - show static text
                const modelSpan = document.createElement('span');
                modelSpan.textContent = '未配置模型';
                modelSpan.style.color = '#999';
                this.titleElement.appendChild(modelSpan);
            }
        }

        getCurrentThemeStyles() {
            const themeKey = (CONFIG.APPEARANCE && CONFIG.APPEARANCE.THEME) || 'default';
            return (THEMES[themeKey] || THEMES['default']).styles;
        }

        createFormattedContent(container, text) {
            while (container.firstChild) { container.removeChild(container.firstChild); }

            // 预处理:将 <br> 和 <br/> 转换为换行符
            text = text.replace(/<br\s*\/?>/gi, '\n');

            // 预处理代码块:将多行代码块转换为特殊标记
            const processedText = this.preprocessCodeBlocks(text);
            const lines = processedText.split('\n');
            let currentList = null;
            let listType = null;
            let isFirstH1 = true;
            let tableRows = []; // 用于收集表格行
            let inTable = false;

            const closeList = () => { if (currentList) { container.appendChild(currentList); currentList = null; listType = null; } };

            const closeTable = () => {
                if (tableRows.length > 0) {
                    const table = document.createElement('table');
                    const styles = this.getCurrentThemeStyles();
                    table.style.cssText = `width: 100%; border-collapse: collapse; margin: 1em 0; font-size: 13px;`;

                    // 检测分隔行的函数:匹配只包含 |、-、:、空格的行
                    const isSeparatorRow = (row) => /^\|[\s\-:|]+\|$/.test(row) && row.includes('-');

                    // 过滤掉分隔行,获取实际数据行
                    const dataRows = tableRows.filter(row => !isSeparatorRow(row));

                    dataRows.forEach((row, rowIndex) => {
                        const tr = document.createElement('tr');
                        const cells = row.split('|').filter((cell, i, arr) => i > 0 && i < arr.length - 1);

                        cells.forEach(cellText => {
                            const cell = document.createElement(rowIndex === 0 ? 'th' : 'td');
                            // 处理单元格内的 <br> 标签
                            const cleanedText = cellText.trim().replace(/<br\s*\/?>/gi, '\n');
                            // 使用 parseInlineFormatting 处理单元格内容,支持粗体、斜体等
                            this.parseTableCellContent(cell, cleanedText);
                            cell.style.cssText = rowIndex === 0 ? styles.th : styles.td;
                            tr.appendChild(cell);
                        });
                        table.appendChild(tr);
                    });

                    container.appendChild(table);
                    tableRows = [];
                    inTable = false;
                }
            };

            const styles = this.getCurrentThemeStyles();

            lines.forEach((line, lineIndex) => {
                const trimmedLine = line.trim();

                // 检测表格行 (以 | 开头和结尾)
                if (trimmedLine.startsWith('|') && trimmedLine.endsWith('|')) {
                    closeList();
                    inTable = true;
                    tableRows.push(trimmedLine);
                    return;
                } else if (inTable) {
                    // 表格结束
                    closeTable();
                }

                // 分隔线 - 使用渐变效果
                if (trimmedLine === '---' || trimmedLine === '***' || trimmedLine === '___') {
                    closeList();
                    const hr = document.createElement('hr');
                    hr.style.cssText = styles.hr;
                    container.appendChild(hr);
                }
                // 引用块 - 红色主题
                else if (trimmedLine.startsWith('> ')) {
                    closeList();
                    const blockquote = document.createElement('blockquote');
                    blockquote.style.cssText = styles.blockquote;
                    this.parseInlineFormatting(blockquote, trimmedLine.substring(2));
                    container.appendChild(blockquote);
                }
                // 六级标题
                else if (trimmedLine.startsWith('###### ')) {
                    closeList();
                    const h = document.createElement('h6');
                    this.parseInlineFormatting(h, trimmedLine.substring(7));
                    h.style.cssText = styles.h6;
                    container.appendChild(h);
                }
                // 五级标题
                else if (trimmedLine.startsWith('##### ')) {
                    closeList();
                    const h = document.createElement('h5');
                    this.parseInlineFormatting(h, trimmedLine.substring(6));
                    h.style.cssText = styles.h5;
                    container.appendChild(h);
                }
                // 四级标题
                else if (trimmedLine.startsWith('#### ')) {
                    closeList();
                    const h = document.createElement('h4');
                    this.parseInlineFormatting(h, trimmedLine.substring(5));
                    h.style.cssText = styles.h4;
                    container.appendChild(h);
                }
                // 三级标题
                else if (trimmedLine.startsWith('### ')) {
                    closeList();
                    const h = document.createElement('h3');
                    this.parseInlineFormatting(h, trimmedLine.substring(4));
                    h.style.cssText = styles.h3;
                    container.appendChild(h);
                }
                // 二级标题 - 带红色下划线
                else if (trimmedLine.startsWith('## ')) {
                    closeList();
                    const h = document.createElement('h2');
                    this.parseInlineFormatting(h, trimmedLine.substring(3));
                    h.style.cssText = styles.h2;
                    container.appendChild(h);
                }
                // 一级标题 - 红色顶部条+背景 (首个h1特殊样式)
                else if (trimmedLine.startsWith('# ')) {
                    closeList();
                    const h = document.createElement('h1');
                    this.parseInlineFormatting(h, trimmedLine.substring(2));
                    if (isFirstH1) {
                        h.style.cssText = styles.h1.first;
                        isFirstH1 = false;
                    } else {
                        h.style.cssText = styles.h1.normal;
                    }
                    container.appendChild(h);
                }
                // 代码块(预处理后的标记)
                else if (trimmedLine.startsWith('___CODEBLOCK___')) {
                    closeList();
                    const codeData = trimmedLine.substring(15); // 移除标记前缀
                    const langMatch = codeData.match(/^LANG:(.*?):::/);
                    const lang = langMatch ? langMatch[1] : '';
                    const codeContent = langMatch ? codeData.substring(langMatch[0].length) : codeData;

                    const pre = document.createElement('pre');
                    pre.style.cssText = styles.pre;
                    const code = document.createElement('code');
                    code.textContent = codeContent.replace(/___NEWLINE___/g, '\n');
                    code.style.cssText = styles.code_block;
                    if (lang) {
                        const langLabel = document.createElement('div');
                        langLabel.textContent = lang;
                        langLabel.style.cssText = `font-size: 11px; color: #888; margin-bottom: 8px; font-family: -apple-system, sans-serif;`;
                        pre.appendChild(langLabel);
                    }
                    pre.appendChild(code);
                    container.appendChild(pre);
                }
                // 任务列表 - 未完成
                else if (trimmedLine.startsWith('- [ ] ') || trimmedLine.startsWith('* [ ] ')) {
                    if (listType !== 'task') { closeList(); currentList = document.createElement('ul'); listType = 'task'; currentList.style.cssText = `padding-left: 0; margin: 1em 0; list-style-type: none;`; }
                    const li = document.createElement('li');
                    li.style.cssText = styles.li + ` display: flex; align-items: flex-start; gap: 8px;`;
                    const checkbox = document.createElement('span');
                    checkbox.textContent = '☐';
                    checkbox.style.cssText = styles.checkbox_unchecked;
                    li.appendChild(checkbox);
                    const textSpan = document.createElement('span');
                    this.parseInlineFormatting(textSpan, trimmedLine.substring(6));
                    li.appendChild(textSpan);
                    currentList.appendChild(li);
                }
                // 任务列表 - 已完成
                else if (trimmedLine.startsWith('- [x] ') || trimmedLine.startsWith('- [X] ') || trimmedLine.startsWith('* [x] ') || trimmedLine.startsWith('* [X] ')) {
                    if (listType !== 'task') { closeList(); currentList = document.createElement('ul'); listType = 'task'; currentList.style.cssText = `padding-left: 0; margin: 1em 0; list-style-type: none;`; }
                    const li = document.createElement('li');
                    li.style.cssText = styles.li + ` display: flex; align-items: flex-start; gap: 8px; text-decoration: line-through; opacity: 0.8;`;
                    const checkbox = document.createElement('span');
                    checkbox.textContent = '☑';
                    checkbox.style.cssText = styles.checkbox_checked;
                    li.appendChild(checkbox);
                    const textSpan = document.createElement('span');
                    this.parseInlineFormatting(textSpan, trimmedLine.substring(6));
                    li.appendChild(textSpan);
                    currentList.appendChild(li);
                }
                // 无序列表
                else if (trimmedLine.startsWith('- ') || trimmedLine.startsWith('* ')) {
                    if (listType !== 'ul') { closeList(); currentList = document.createElement('ul'); listType = 'ul'; currentList.style.cssText = styles.ul; }
                    const li = document.createElement('li');
                    li.style.cssText = styles.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.cssText = styles.ol; }
                    const li = document.createElement('li');
                    li.style.cssText = styles.li;
                    this.parseInlineFormatting(li, trimmedLine.replace(/^\d+\.\s/, ''));
                    currentList.appendChild(li);
                }
                // 普通段落
                else if (trimmedLine) {
                    closeList();
                    const p = document.createElement('p');
                    p.style.cssText = styles.p;
                    this.parseInlineFormatting(p, trimmedLine);
                    container.appendChild(p);
                }
            });
            closeList();
            closeTable(); // 确保最后的表格被渲染
        }
        parseInlineFormatting(element, text) {
            const styles = this.getCurrentThemeStyles();
            // 扩展正则匹配:粗体、斜体、行内代码、链接、删除线、HTML锚点
            const parts = text.split(/(\*\*.*?\*\*|\*.*?\*|`.*?`|\[.*?\]\(.*?\)|~~.*?~~|<a\s+name=["'].*?["']\s*><\/a>)/g);
            parts.forEach(part => {
                if (part.startsWith('**') && part.endsWith('**')) {
                    const s = document.createElement('strong');
                    s.textContent = part.slice(2, -2);
                    s.style.cssText = styles.strong;
                    element.appendChild(s);
                }
                else if (part.startsWith('~~') && part.endsWith('~~')) {
                    // 删除线
                    const s = document.createElement('span');
                    s.textContent = part.slice(2, -2);
                    s.style.cssText = styles.del;
                    element.appendChild(s);
                }
                else if (part.startsWith('*') && part.endsWith('*')) {
                    const em = document.createElement('em');
                    em.textContent = part.slice(1, -1);
                    em.style.cssText = styles.em;
                    element.appendChild(em);
                }
                else if (part.startsWith('`') && part.endsWith('`')) {
                    const c = document.createElement('code');
                    c.textContent = part.slice(1, -1);
                    c.style.cssText = styles.code; // Use inline code style
                    element.appendChild(c);
                }
                else if (part.startsWith('[') && part.includes('](') && part.endsWith(')')) {
                    // 链接处理
                    const linkMatch = part.match(/^\[(.*?)\]\((.*?)\)$/);
                    if (linkMatch) {
                        const a = document.createElement('a');
                        a.textContent = linkMatch[1];
                        a.href = linkMatch[2];
                        a.target = '_blank';
                        a.rel = 'noopener noreferrer';
                        a.style.cssText = styles.link;
                        element.appendChild(a);
                    } else {
                        element.appendChild(document.createTextNode(part));
                    }
                }
                else if (part.startsWith('<a') && part.includes('name=')) {
                    // HTML 锚点处理
                    const nameMatch = part.match(/name=["'](.*?)["']/);
                    if (nameMatch) {
                        const a = document.createElement('a');
                        a.name = nameMatch[1];
                        element.appendChild(a);
                    }
                }
                else { element.appendChild(document.createTextNode(part)); }
            });
        }
        // 预处理代码块:将 ``` 包裹的多行代码块转换为单行标记
        preprocessCodeBlocks(text) {
            const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
            return text.replace(codeBlockRegex, (match, lang, code) => {
                // 将代码内容中的换行符转换为特殊标记,便于单行处理
                const escapedCode = code.replace(/\n/g, '___NEWLINE___').trim();
                return `___CODEBLOCK___LANG:${lang}:::${escapedCode}`;
            });
        }
        // 处理表格单元格内容,支持换行和内联格式
        parseTableCellContent(cell, text) {
            // 按换行符分割
            const lines = text.split('\n');
            lines.forEach((line, index) => {
                // 对每一行应用内联格式
                this.parseInlineFormatting(cell, line);
                // 如果不是最后一行,添加换行元素
                if (index < lines.length - 1) {
                    cell.appendChild(document.createElement('br'));
                }
            });
        }
        makeDraggable(element) {
            let isDragging = false, startX, startY, currentX = 0, currentY = 0;

            // 整个面板的拖拽(展开状态时)
            element.addEventListener('mousedown', (e) => {
                if (e.target.tagName === 'BUTTON' || e.target.closest('button')) return;
                if (this.isCollapsed) return; // 收起状态时不允许通过顶部栏拖拽
                isDragging = true;
                startX = e.clientX - currentX;
                startY = e.clientY - currentY;
                this.container.style.transition = 'none';
            });

            document.addEventListener('mousemove', (e) => {
                if (!isDragging) return;
                e.preventDefault();
                currentX = e.clientX - startX;
                currentY = e.clientY - startY;
                this.container.style.transform = `translate(${currentX}px, ${currentY}px)`;
            });

            document.addEventListener('mouseup', () => {
                if (!isDragging) return;
                isDragging = false;
                this.container.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
            });

            // 设置收起状态图标的独立拖动功能
            this.setupCollapsedIconDrag();
        }

        setupCollapsedIconDrag() {
            let isDragging = false, startX, startY;
            this.iconX = window.innerWidth - 60; // 图标初始X位置
            this.iconY = 80; // 图标初始Y位置
            this.isEdgeHidden = false;

            // 图标的拖拽
            this.toggleButton.addEventListener('mousedown', (e) => {
                if (!this.isCollapsed) return; // 只在收起状态时可拖动
                e.stopPropagation();
                isDragging = true;
                startX = e.clientX - this.iconX;
                startY = e.clientY - this.iconY;
                this.toggleButton.style.transition = 'none';
                this.toggleButton.style.animation = 'none'; // 拖拽时暂停呼吸动画
            });

            document.addEventListener('mousemove', (e) => {
                if (!isDragging || !this.isCollapsed) return;
                e.preventDefault();
                this.iconX = e.clientX - startX;
                this.iconY = e.clientY - startY;

                // 限制在屏幕范围内
                this.iconX = Math.max(0, Math.min(this.iconX, window.innerWidth - 40));
                this.iconY = Math.max(0, Math.min(this.iconY, window.innerHeight - 40));

                this.container.style.cssText = `position: fixed; top: ${this.iconY}px; left: ${this.iconX}px; right: auto; width: auto; min-width: 0; background: transparent; box-shadow: none; backdrop-filter: none; border: none; padding: 0; z-index: 9999;`;
            });

            document.addEventListener('mouseup', (e) => {
                if (!isDragging || !this.isCollapsed) return;
                isDragging = false;
                this.toggleButton.style.transition = 'all 0.3s ease';

                // 检测是否拖到右边缘
                if (this.iconX > window.innerWidth - 60) {
                    this.enterEdgeHiddenMode();
                } else {
                    this.toggleButton.style.animation = 'pulse 2s ease-in-out infinite';
                }
            });

            // 创建边缘触发区域用于唤醒
            this.createEdgeTriggerZone();
        }

        createEdgeTriggerZone() {
            this.edgeTriggerZone = document.createElement('div');
            this.edgeTriggerZone.style.cssText = `position: fixed; top: 0; right: 0; width: 20px; height: 100%; z-index: 9998; background: transparent; display: none;`;
            this.shadowRoot.appendChild(this.edgeTriggerZone);

            // 鼠标进入边缘区域时唤醒图标
            this.edgeTriggerZone.addEventListener('mouseenter', () => {
                if (this.isEdgeHidden) {
                    this.showFromEdge();
                }
            });
        }

        enterEdgeHiddenMode() {
            this.isEdgeHidden = true;
            this.savedIconY = this.iconY;

            // 将图标移动到右边缘外,只露出一小部分
            this.iconX = window.innerWidth - 15;
            this.container.style.cssText = `position: fixed; top: ${this.iconY}px; left: ${this.iconX}px; right: auto; width: auto; min-width: 0; background: transparent; box-shadow: none; backdrop-filter: none; border: none; padding: 0; z-index: 9999; transition: all 0.3s ease;`;
            this.toggleButton.style.opacity = '0.5';
            this.toggleButton.style.animation = 'none';

            // 显示边缘触发区域
            this.edgeTriggerZone.style.display = 'block';
        }

        showFromEdge() {
            // 从边缘滑出显示
            this.iconX = window.innerWidth - 50;
            this.container.style.cssText = `position: fixed; top: ${this.iconY}px; left: ${this.iconX}px; right: auto; width: auto; min-width: 0; background: transparent; box-shadow: none; backdrop-filter: none; border: none; padding: 0; z-index: 9999; transition: all 0.3s ease;`;
            this.toggleButton.style.opacity = '1';
            this.toggleButton.style.animation = 'pulse 2s ease-in-out infinite';

            // 鼠标离开图标时判断是否需要重新隐藏
            const hideHandler = () => {
                if (this.isEdgeHidden && !this.container.matches(':hover')) {
                    setTimeout(() => {
                        if (this.isEdgeHidden && !this.container.matches(':hover')) {
                            this.iconX = window.innerWidth - 15;
                            this.container.style.left = `${this.iconX}px`;
                            this.toggleButton.style.opacity = '0.5';
                            this.toggleButton.style.animation = 'none';
                        }
                    }, 500);
                }
            };

            this.container.addEventListener('mouseleave', hideHandler, { once: true });
        }

        exitEdgeHiddenMode() {
            this.isEdgeHidden = false;
            this.toggleButton.style.opacity = '1';
            this.edgeTriggerZone.style.display = 'none';
        }
        // ++ 在 makeDraggable() 函数结束后,粘贴下面所有代码 ++
        handleFullscreenChange() {
            // 检查当前是否有元素处于全屏状态
            const isFullscreen = !!document.fullscreenElement;

            // 如果进入全屏,则隐藏脚本容器;如果退出全屏,则显示它
            if (isFullscreen) {
                this.container.style.display = 'none';
            } else {
                this.container.style.display = 'block';
            }
        }
        attachEventListeners() {
            let lastUrl = location.href;
            document.addEventListener('fullscreenchange', () => this.handleFullscreenChange());
            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) || PageManager.isBilibili(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() && !PageManager.isBilibili()) return;
        console.log(`🚀 船仓AI助手 初始化 on ${PageManager.getCurrentPlatform()}...`);
        const contentController = new ContentController();
        new UIManager(contentController);
        console.log('✅ 船仓AI助手 初始化完成');
    }

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