Greasy Fork

Greasy Fork is available in English.

抖音推荐影响器 (Smart Feed Assistant)

通过AI智能分析内容,优化你的信息流体验

当前为 2025-11-05 提交的版本,查看 最新版本

// ==UserScript==
// @name         抖音推荐影响器 (Smart Feed Assistant)
// @namespace    https://github.com/baianjo/Douyin-Smart-Feed-Assistant
// @version      2.1.0
// @description  通过AI智能分析内容,优化你的信息流体验
// @author       Baianjo
// @match        *://www.douyin.com/*
// @connect      api.moonshot.cn
// @connect      api.deepseek.com
// @connect      dashscope.aliyuncs.com
// @connect      dashscope-intl.aliyuncs.com
// @connect      open.bigmodel.cn
// @connect      *
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        unsafeWindow
// @run-at       document-end
// @homepageURL  https://github.com/baianjo/Douyin-Smart-Feed-Assistant
// @supportURL   mailto:[email protected]
// @license      MIT
// ==/UserScript==

/*
 * ============================================================
 * 🔧 开发者注意事项 (DEVELOPER NOTES)
 * ============================================================
 *
 * 【需要定期更新的部分】
 *
 * 1. DOM选择器 (约95-120行)
 *    - 抖音更新后最容易失效的部分
 *    - 如果无法提取标题/作者/标签,请用F12检查新的类名
 *    - 添加新选择器到数组开头作为优先方案
 *
 * 2. 快捷键映射 (约540行)
 *    - 当前:Z=点赞, R=不感兴趣, X=评论, ArrowDown=下一个
 *    - 如果抖音修改快捷键,请在pressKey函数中更新
 *
 * 【代码结构说明】
 * - CONFIG模块: 所有配置项和默认值
 * - Utils模块: 通用工具函数
 * - VideoExtractor模块: DOM操作和内容提取
 * - AIService模块: AI API调用和判断逻辑
 * - UI模块: 用户界面创建和交互
 * - Controller模块: 主控制流程
 *
 * 【常见问题排查】
 * - 无法提取视频信息 → 检查DOM选择器
 * - API调用失败 → 检查网络和API Key
 * - 操作无效 → 检查快捷键是否变化
 * - 视频不切换 → 检查滚动逻辑和判断条件
 */

(function() {
    'use strict';

    // ==================== 配置管理模块 ====================
    const CONFIG = {
        // 默认配置
        defaults: {
            // API设置
            apiKey: '',
            customEndpoint: '', // 自定义API地址(支持第三方转发)
            customModel: '', // 自定义模型名称
            apiProvider: 'deepseek',

            // 记住用户选择的模板
            selectedTemplate: '', // 空字符串表示"自定义规则"

            // 提示词
            promptLike: '我希望看到积极向上、有教育意义、展示美好事物的内容。',
            promptNeutral: '普通的娱乐内容、日常生活记录,不特别推荐也不反对。',
            promptDislike: '低俗、暴力、虚假信息、过度营销的内容应该被过滤。',

            // 行为控制
            minDelay: 1,
            maxDelay: 3,
            runDuration: 15,

            // 高级选项
            skipProbability: 8,
            watchBeforeLike: [2, 4],
            maxRetries: 3,

            // UI状态
            panelMinimized: true,
            panelPosition: { x: window.innerWidth - 80, y: 100 }
        },

        /*
         * ⚠️ 重要:DOM选择器配置
         * 这是最容易失效的部分,抖音每次更新可能都需要调整
         *
         * 调试技巧:
         * 1. 打开F12开发者工具
         * 2. 点击左上角的"选择元素"图标
         * 3. 鼠标悬停在视频标题/作者/标签上
         * 4. 查看右侧高亮的HTML结构
         * 5. 复制类名或结构特征
         * 6. 添加到下面的数组中(优先级从上到下)
         */
        selectors: {
            // 视频标题
            title: [
                'div[class*="pQBVl"] span span span', // 当前主方案 (2025-10)
                '#slidelist [data-e2e="feed-item"] div[style*="lineClamp"]',
                '.video-info-detail span',
                '[data-e2e="feed-title"]'
            ],
            // 作者名称
            author: [
                '[data-e2e="feed-author-name"]',
                '.author-name',
                'a[class*="author"]',
                '[class*="AuthorName"]'
            ],
            // 标签(话题)
            tags: [
                'a[href*="/search/"]',
                '.tag-link',
                '[class*="hashtag"]',
                'a[class*="SLdJu"]' // 当前发现的标签类名
            ],
        },


        // ⚠️ 开发者维护区域:API 提供商统一配置
        //
        // 📌 requestParams 参数说明:
        //   - 填写具体值(如 temperature: 0.3)→ 发送到 API
        //   - 注释掉或删除该行 → 不发送,使用 API 默认值
        //   - stream: false 是必填项(禁用流式输出)
        //
        // 🔧 关于 vendorSpecific(厂商特定参数):
        //   • 仅在预设厂商配置中使用(如 GLM 的 thinking 禁用)
        //   • ⚠️ 切勿在所有配置中统一添加!原因:
        //     - 多数 OpenAI 兼容 API 会严格验证参数
        //     - 遇到未知字段会返回 400/422 错误
        //     - 只有明确支持的厂商才能使用特定参数
        //   • 自定义 API 暂不应添加 vendorSpecific
        apiProviders: {
            deepseek: {
                name: 'DeepSeek(推荐:最便宜)',
                endpoint: 'https://api.deepseek.com/v1/chat/completions',
                defaultModel: 'deepseek-chat',
                models: [
                    { value: 'deepseek-chat', label: 'deepseek-chat (V3.2推荐)' }
                ],
                requestParams: {
                    temperature: 0.3,      // 可选:删除此行则使用 API 默认值
                    max_tokens: 500,       // 可选:删除此行则使用 API 默认值
                    stream: false          // 必填:禁用流式输出
                }
            },
            kimi: {
                name: 'Kimi / 月之暗面',
                endpoint: 'https://api.moonshot.cn/v1/chat/completions',
                defaultModel: 'kimi-k2-0905-preview',
                models: [
                    { value: 'kimi-k2-0905-preview', label: 'kimi-k2-0905-preview' }
                ],
                requestParams: {
                    temperature: 0.3,
                    max_tokens: 500,
                    stream: false
                }
            },
            qwen: {
                name: 'Qwen / 通义千问',
                endpoint: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
                defaultModel: 'qwen-flash',
                models: [
                    { value: 'qwen-max', label: 'qwen-max(最强)' },
                    { value: 'qwen-plus', label: 'qwen-plus(推荐)' },
                    { value: 'qwen-flash', label: 'qwen-flash(快速)' }
                ],
                requestParams: {
                    temperature: 0.3,
                    max_tokens: 500,
                    stream: false
                }
            },
            glm: {
                name: 'GLM / 智谱AI',
                endpoint: 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
                defaultModel: 'glm-4.6',
                models: [
                    { value: 'glm-4.6', label: 'glm-4.6' }
                ],
                requestParams: {
                    temperature: 0.3,
                    max_tokens: 500,
                    stream: false,
                    // ⚠️ GLM 专属:禁用思考模式(否则会超时)
                    vendorSpecific: {
                        thinking: { type: 'disabled' }
                    }
                }
            },
            gemini: {
                name: 'Gemini / Google AI Studio',
                endpoint: 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions',
                defaultModel: 'gemini-2.5-flash',
                models: [
                    { value: 'gemini-2.5-flash', label: 'gemini-2.5-flash' },
                    { value: 'gemini-2.5-pro', label: 'gemini-2.5-pro' },
                ],
                requestParams: {
                    stream: false,
                    vendorSpecific: {
                        "extra_body": {    // Gemini 要求的字段名
                            "google": {
                                "thinking_config": {
                                    "thinking_budget": 128,
                                    "include_thoughts": false
                                }
                            }
                        }
                    }
                }
            }
        },

        // ✅ 简化:从统一配置中获取默认模型
        getDefaultModel: (provider) => {
            return CONFIG.apiProviders[provider]?.defaultModel || '';
        },

        // 预设模板
        templates: {
            '小学内容引导': {
                like: '对小学生趣味生动的STEM科普、历史故事,启发学习兴趣与好奇心;展现中国普通劳动者的奉献,或适合小学生同情的感人的家庭、师生、同学、社会百态、家国情谊、乡土情结;分享小升初的经验、名校风光、为什么学习等合理焦虑的正面话题;培养自律、诚信、爱护家人、尊重他人的品格;展现自然风光、创意手工、小学生健康运动,培养审美与动手能力;学习良好的价值观和金钱观。',
                neutral: '不含强烈价值观输出的日常生活记录、社会新闻、萌宠、美食、旅行片段;非低俗的唱歌、乐器弹奏等才艺表演;非暴力、非上瘾的益智类或创意类游戏短视频;死板/不够通俗/不够引人入胜的知识。',
                dislike: '无意义的玩梗、降智恶搞;炫富攀比、宣扬过度消费;展现不尊重长辈、师长,恶意捉弄他人,或传播负面情绪的内容;易上瘾的长时间游戏直播/录播;包含、低俗、性暗示的内容。'
            },
            '中学内容引导': {
                like: '系统讲解科学、技术、历史、商业等领域知识,构建知识体系;专业技能(如编程、设计、摄影)的学习实践过程;对时事与社会现象有理有据的逻辑分析,提供多元视角,培养独立思考;顶尖学府生活、职业规划与个人成长经验;高质量纪录片,展现自然与文化厚重,培养人文关怀与社会责任感。',
                neutral: '不含强烈价值观输出的日常生活Vlog、美食探店、旅行记录;不含攻击性的普通新闻资讯;非专业、纯娱乐性质的才艺表演。',
                dislike: '纯粹玩梗、逻辑缺失的抽象内容;无节制宣扬消费主义、炫富;传播负面情绪、制造性别对立或社会矛盾;包含性暗示、观感不适的舞蹈、低俗笑话。'
            },
            '效率与知识': {
                like: '商业分析、科技前沿、技能学习、效率工具、深度思考类内容。有价值、有启发。',
                neutral: '新闻资讯、行业动态等信息类内容。',
                dislike: '娱乐八卦、情感鸡汤、无意义的搞笑视频、标题党。'
            },
            '新闻与时事': {
                like: '严肃新闻、社会事件、政策解读、国际局势、经济分析等客观理性的内容。',
                neutral: '地方新闻、社区故事等区域性内容。',
                dislike: '未经证实的传言、情绪化煽动、极端观点。'
            },
            '健康生活': {
                like: '健身运动、营养饮食、心理健康、医学科普、户外活动等促进身心健康的内容。',
                neutral: '美食探店、旅游vlog等生活方式内容。',
                dislike: '伪科学养生、极端减肥、危险运动、不健康的生活方式。'
            },
            '艺术审美': {
                like: '绘画、音乐、舞蹈、摄影、设计、建筑等艺术创作和欣赏内容。有美感、有深度。',
                neutral: '普通的才艺展示、手工DIY等创意内容。',
                dislike: '低俗模仿、审美庸俗、抄袭作品。'
            },
            '美女审美': {
                like: '高颜值、身材姣好的年轻女性为绝对主角的视频。tag可能是舞蹈、御姐、黑丝、cos、女友、擦边、泳装、穿搭等。',
                neutral: '女性的展示内容,或无法分辨是什么视频类型。视频未完全满足like标准中的成品质量和视觉聚焦要求,但只要可能和女性相关即可,即使需要猜测。tag可能是表情管理、瑜伽、美颜等。这类视频标题往往是无意义的话甚至几乎无标题,如「心很贵 一定要装最美的东西/你想我了吗」',
                dislike: '严格排除所有非上述定义的视频。包括但不限于:纯风景、新闻、时政、科普、教育、影视剪辑、动漫、游戏、美食、萌宠、Vlog、生活记录、剧情短剧、手工、绘画等。'
            },
            '帅哥审美': {
                'like': '高颜值、身材姣好的年轻男性为绝对主角的视频。tag可能是舞蹈、型男、西装、肌肉、腹肌、cos、男友、男友视角、擦边、泳裤、穿搭、男神等。',
                'neutral': '男性的展示内容,或无法分辨是什么视频类型。视频未完全满足like标准中的成品质量和视觉聚焦要求,但只要可能和男性相关即可,即使需要猜测。tag可能是表情管理、健身、运动、美颜等。这类视频标题往往是无意义的话甚至几乎无标题,如「今天的心情... / 猜我在想什么」',
                'dislike': '严格排除所有非上述定义的视频。包括但不限于:纯风景、新闻、时政、科普、教育、影视剪辑、动漫、游戏、美食、萌宠、Vlog、生活记录、剧情短剧、手工、绘画等。'
            }

        }
    };

    /**
     * 🔧 配置加载函数(带深度验证)
     *
     * 验证策略:
     * 1. 类型检查(number/string/boolean/object/array)
     * 2. 数值有效性(NaN/Infinity检查)
     * 3. 范围限制(min/max边界)
     * 4. 嵌套对象完整性(panelPosition、watchBeforeLike)
     */
    const loadConfig = () => {
        try {
            const saved = GM_getValue('config', null);

            // 情况1:无存储数据 → 直接返回默认值(深拷贝)
            if (!saved || typeof saved !== 'object') {
                console.log('[智能助手] 📋 使用默认配置');
                return JSON.parse(JSON.stringify(CONFIG.defaults));
            }

            // 情况2:有存储数据 → 合并并验证
            const merged = { ...CONFIG.defaults, ...saved };

            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            // 📌 数值字段验证(关键参数)
            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            const numberFields = [
                { key: 'minDelay', min: 1, max: 60 },
                { key: 'maxDelay', min: 1, max: 60 },
                { key: 'runDuration', min: 1, max: 180 },
                { key: 'skipProbability', min: 0, max: 100 },
                { key: 'maxRetries', min: 1, max: 10 }
            ];

            numberFields.forEach(({ key, min, max }) => {
                const val = merged[key];

                // 检查:是否为数字、是否有效、是否在范围内
                if (
                    typeof val !== 'number' ||
                    isNaN(val) ||
                    !isFinite(val) ||  // 排除Infinity
                    val < min ||
                    val > max
                ) {
                    console.warn(`[智能助手] ⚠️ 配置项 ${key} 无效 (${val}),使用默认值 (${CONFIG.defaults[key]})`);
                    merged[key] = CONFIG.defaults[key];
                }
            });

            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            // 📌 字符串字段验证
            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            const stringFields = ['apiKey', 'customEndpoint', 'customModel', 'apiProvider',
                                  'selectedTemplate', 'promptLike', 'promptNeutral', 'promptDislike'];

            stringFields.forEach(key => {
                if (typeof merged[key] !== 'string') {
                    console.warn(`[智能助手] ⚠️ 配置项 ${key} 类型错误,重置为默认值`);
                    merged[key] = CONFIG.defaults[key];
                }
            });

            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            // 📌 布尔字段验证
            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            const boolFields = ['panelMinimized'];

            boolFields.forEach(key => {
                if (typeof merged[key] !== 'boolean') {
                    console.warn(`[智能助手] ⚠️ 配置项 ${key} 类型错误,重置为默认值`);
                    merged[key] = CONFIG.defaults[key];
                }
            });

            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            // 📌 复杂对象验证:panelPosition
            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            if (
                !merged.panelPosition ||
                typeof merged.panelPosition !== 'object' ||
                typeof merged.panelPosition.x !== 'number' ||
                typeof merged.panelPosition.y !== 'number' ||
                isNaN(merged.panelPosition.x) ||
                isNaN(merged.panelPosition.y) ||
                !isFinite(merged.panelPosition.x) ||
                !isFinite(merged.panelPosition.y)
            ) {
                console.warn('[智能助手] ⚠️ panelPosition 数据无效,重置为默认值');
                merged.panelPosition = {
                    x: CONFIG.defaults.panelPosition.x,
                    y: CONFIG.defaults.panelPosition.y
                };
            } else {
                // 🆕 额外检查:位置是否在屏幕范围内
                const maxX = window.innerWidth - 60;
                const maxY = window.innerHeight - 60;

                if (merged.panelPosition.x < 0 || merged.panelPosition.x > maxX) {
                    console.warn('[智能助手] ⚠️ panelPosition.x 超出范围,自动修正');
                    merged.panelPosition.x = Math.max(0, Math.min(maxX, merged.panelPosition.x));
                }

                if (merged.panelPosition.y < 0 || merged.panelPosition.y > maxY) {
                    console.warn('[智能助手] ⚠️ panelPosition.y 超出范围,自动修正');
                    merged.panelPosition.y = Math.max(0, Math.min(maxY, merged.panelPosition.y));
                }
            }

            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            // 📌 复杂对象验证:watchBeforeLike
            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            if (
                !Array.isArray(merged.watchBeforeLike) ||
                merged.watchBeforeLike.length !== 2 ||
                typeof merged.watchBeforeLike[0] !== 'number' ||
                typeof merged.watchBeforeLike[1] !== 'number' ||
                isNaN(merged.watchBeforeLike[0]) ||
                isNaN(merged.watchBeforeLike[1]) ||
                merged.watchBeforeLike[0] < 0 ||
                merged.watchBeforeLike[1] > 30 ||
                merged.watchBeforeLike[0] > merged.watchBeforeLike[1]  // 🆕 逻辑检查:min不能大于max
            ) {
                console.warn('[智能助手] ⚠️ watchBeforeLike 数据无效,重置为默认值');
                merged.watchBeforeLike = [...CONFIG.defaults.watchBeforeLike];
            }

            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            // 📌 特殊逻辑验证
            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            // 检查:minDelay 不能大于 maxDelay
            if (merged.minDelay > merged.maxDelay) {
                console.warn('[智能助手] ⚠️ minDelay > maxDelay,自动交换');
                [merged.minDelay, merged.maxDelay] = [merged.maxDelay, merged.minDelay];
            }

            // 检查:apiProvider 是否有效
            const validProviders = [...Object.keys(CONFIG.apiProviders), 'custom'];
            if (!validProviders.includes(merged.apiProvider)) {
                console.warn(`[智能助手] ⚠️ apiProvider 无效 (${merged.apiProvider}),重置为 deepseek`);
                merged.apiProvider = 'deepseek';
            }


            console.log('[智能助手] ✅ 配置加载并验证完成');
            return merged;

        } catch (e) {
            // 🆕 错误处理:解析失败时返回默认值
            console.error('[智能助手] ❌ 配置加载失败:', e);
            alert('⚠️ 配置数据损坏,已重置为默认值\n\n如果问题持续,请清除浏览器扩展数据后重试');

            // 清除损坏的配置
            try {
                GM_deleteValue('config');
            } catch (delErr) {
                console.error('[智能助手] 无法删除损坏的配置:', delErr);
            }

            return JSON.parse(JSON.stringify(CONFIG.defaults));
        }
    };

    const saveConfig = async (config) => {
        try {
            // 🆕 保存前验证
            console.log('[智能助手] 📝 准备保存配置:', {
                位置: config.panelPosition,
                最小化: config.panelMinimized
            });

            // 🆕 验证位置数据有效性
            if (config.panelPosition) {
                if (isNaN(config.panelPosition.x) || isNaN(config.panelPosition.y)) {
                    console.error('[智能助手] ❌ 位置数据无效:', config.panelPosition);
                    alert('⚠️ 检测到无效的位置数据(NaN),已取消保存');
                    return;
                }
            }

            // 同步写入
            GM_setValue('config', config);

            // 延迟确保写入完成
            await new Promise(resolve => setTimeout(resolve, 100)); // 🆕 延长到100ms

            // 🆕 验证写入成功
            const saved = GM_getValue('config', null);
            if (saved && saved.panelPosition) {
                const match = saved.panelPosition.x === config.panelPosition.x &&
                              saved.panelPosition.y === config.panelPosition.y;
                console.log('[智能助手] ✅ 保存验证:', {
                    写入位置: config.panelPosition,
                    读取位置: saved.panelPosition,
                    匹配状态: match ? '✓ 成功' : '✗ 失败'
                });

                if (!match) {
                    console.error('[智能助手] ❌ 保存验证失败!写入的值和读取的值不一致');
                }
            } else {
                console.error('[智能助手] ❌ 保存验证失败,读取到空数据');
            }
        } catch (e) {
            console.error('[智能助手] ❌ GM_setValue 失败:', e);
            alert('⚠️ 配置保存失败!\n' + e.message);
        }
    };

    // ==================== 工具函数 ====================
    const Utils = {
        // 随机延迟(模拟人类行为)
        randomDelay: (min, max) => {
            return new Promise(resolve => {
                const delay = (Math.random() * (max - min) + min) * 1000;
                setTimeout(resolve, delay);
            });
        },

        // 查找元素(支持多套备用选择器)
        findElement: (selectors, root = document) => {
            for (const selector of selectors) {
                try {
                    const el = root.querySelector(selector);
                    if (el) return el;
                } catch (e) {
                    console.warn(`[智能助手] 选择器失败: ${selector}`, e);
                }
            }
            return null;
        },

        // 查找所有元素
        findElements: (selectors, root = document) => {
            for (const selector of selectors) {
                try {
                    const els = root.querySelectorAll(selector);
                    if (els.length > 0) return Array.from(els);
                } catch (e) {
                    console.warn(`[智能助手] 选择器失败: ${selector}`, e);
                }
            }
            return [];
        },

        /*
         * 模拟键盘快捷键
         *
         * 抖音网页版快捷键(可能随版本变化):
         * - Z: 点赞/取消点赞
         * - X: 打开/关闭评论区
         * - R: 标记"不感兴趣"
         * - ArrowDown/↓: 下一个视频
         * - ArrowUp/↑: 上一个视频
         * - Space: 播放/暂停
         *
         * 如果抖音修改了快捷键,请在这里更新
         */
        pressKey: (key) => {
            const keyMap = {
                'z': { key: 'z', code: 'KeyZ', keyCode: 90 },
                'x': { key: 'x', code: 'KeyX', keyCode: 88 },
                'r': { key: 'r', code: 'KeyR', keyCode: 82 },
                'ArrowDown': { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
                'ArrowUp': { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
                'Space': { key: ' ', code: 'Space', keyCode: 32 }
            };

            const config = keyMap[key] || { key: key, code: key, keyCode: key.charCodeAt(0) };

            const event = new KeyboardEvent('keydown', {
                key: config.key,
                code: config.code,
                keyCode: config.keyCode,
                bubbles: true,
                cancelable: true
            });

            document.dispatchEvent(event);
        },

        // 等待元素出现
        waitForElement: (selectors, timeout = 5000) => {
            return new Promise((resolve) => {
                const startTime = Date.now();
                const timer = setInterval(() => {
                    const el = Utils.findElement(selectors);
                    if (el || Date.now() - startTime > timeout) {
                        clearInterval(timer);
                        resolve(el);
                    }
                }, 100);
            });
        },

        // 提取文本
        extractText: (element) => {
            if (!element) return '';
            return element.innerText || element.textContent || '';
        },

        // 格式化时间
        formatTime: (seconds) => {
            const m = Math.floor(seconds / 60);
            const s = Math.floor(seconds % 60);
            return `${m}:${s.toString().padStart(2, '0')}`;
        }
    };

    // ==================== DOM操作模块 ====================
    const VideoExtractor = {
        // 🆕 通过视口中心定位当前视频容器
        getCurrentFeedItem: () => {
            const centerX = window.innerWidth / 2;
            const centerY = window.innerHeight / 2;
            const centerEl = document.elementFromPoint(centerX, centerY);

            if (!centerEl) {
                UI.log('⚠️ 无法定位中心元素', 'warning');
                return null;
            }

            // 向上查找 feed-item 容器
            const feedItem = centerEl.closest('[data-e2e="feed-item"]');
            if (!feedItem) {
                UI.log('⚠️ 未找到 feed-item 容器', 'warning');
            }

            return feedItem;
        },

        // 🆕 获取完整标题(改进版:不主动点击展开)
        getFullTitle: (container) => {
            if (!container) return '';

            // 提取标题(优先级从高到低)
            const titleSelectors = [
                'div[class*="pQBVl"]', // 🆕 改为选择整个容器,而不是内部 span
                'div[data-e2e="video-desc"]',
                '.video-info-detail',
                '[data-e2e="feed-title"]'
            ];

            for (const selector of titleSelectors) {
                const el = container.querySelector(selector);
                if (el) {
                    // 🆕 获取所有文本节点(包括被折叠的部分)
                    let text = el.innerText || el.textContent || '';

                    // 过滤掉标签部分(# 开头的内容)
                    const lines = text.split('\n');
                    let cleanText = '';

                    for (const line of lines) {
                        if (line.trim().startsWith('#')) break; // 遇到标签就停止
                        cleanText += line + ' ';
                    }

                    cleanText = cleanText.trim();

                    // 移除"展开"按钮文本
                    cleanText = cleanText.replace(/展开$/, '').trim();

                    if (cleanText.length > 2) {
                        return cleanText;
                    }
                }
            }

            return '';
        },

        // 获取当前视频信息
        getCurrentVideoInfo: async (config) => {
            // 🆕 增加初始等待,确保 DOM 稳定
            await new Promise(r => setTimeout(r, 500));

            // 🆕 智能重试机制(最多 4 次)
            let feedItem = null;
            const maxAttempts = 7; // ← 可配置重试次数
            const retryDelayMs = 250; // ← 可配置重试间隔(毫秒)

            for (let attempt = 0; attempt < maxAttempts; attempt++) {
                feedItem = VideoExtractor.getCurrentFeedItem();
                if (feedItem) {
                    // 额外验证:确保元素在视口内
                    const rect = feedItem.getBoundingClientRect();
                    const isInView = rect.top < window.innerHeight && rect.bottom > 0;
                    if (isInView) {
                        if (attempt > 0) {
                            UI.log(`✅ 重试成功(第 ${attempt + 1} 次)`, 'success');
                        }
                        break; // 成功找到,退出循环
                    } else {
                        UI.log(`⚠️ 找到元素但不在视口 (y: ${rect.top.toFixed(0)}),等待 ${retryDelayMs}ms 后重试`, 'warning');
                        feedItem = null;
                    }
                } else {
                    UI.log(`⚠️ 未找到 feed-item(尝试 ${attempt + 1}/${maxAttempts}),等待 ${retryDelayMs}ms 后重试`, 'warning');
                }
                // 等待后重试(最后一次不等)
                if (attempt < maxAttempts - 1) {
                    await new Promise(r => setTimeout(r, retryDelayMs));
                }
            }

            if (!feedItem) {
                return null;
            }

            const info = {
                title: '',
                author: '',
                tags: [],
                url: window.location.href,
                isLive: false
            };

            // 检测是否为直播
            info.isLive = !!(
                feedItem.querySelector('[data-e2e="feed-live"]') ||
                feedItem.querySelector('.live-icon') ||
                feedItem.querySelector('a[data-e2e="live-slider"]')
            );

            if (info.isLive) {
                UI.log('🔴 检测到直播,跳过信息提取', 'info');
                return info;
            }

            // 提取标题(可能需要展开)
            info.title = VideoExtractor.getFullTitle(feedItem);

            // 如果标题太短,等待一下再试
            if (info.title.length < 3) {
                await Utils.randomDelay(0.5, 0.5);
                info.title = VideoExtractor.getFullTitle(feedItem);
            }

            // 提取作者
            const authorSelectors = [
                '[data-e2e="feed-author-name"]',
                '.author-name',
                'a[class*="author"]',
                '[class*="AuthorName"]'
            ];

            for (const selector of authorSelectors) {
                const el = feedItem.querySelector(selector);
                if (el) {
                    info.author = Utils.extractText(el).trim();
                    break;
                }
            }

            // 提取标签(只取前3个,避免混入其他视频)
            const tagEls = feedItem.querySelectorAll('a[href*="/search/"]');
            info.tags = Array.from(tagEls)
                .slice(0, 3)
                .map(el => Utils.extractText(el).trim())
                .filter(t => t.startsWith('#'));

            UI.log(`📺 标题: ${info.title.substring(0, 40)}${info.title.length > 40 ? '...' : ''}`, 'success');
            if (info.author) UI.log(`👤 作者: ${info.author}`, 'info');
            if (info.tags.length > 0) UI.log(`🏷️ 标签: ${info.tags.join(', ')}`, 'info');



            return info;
        },

        // 构建内容档案
        buildDossier: (info) => {
            const parts = [];
            if (info.author) parts.push(`作者:${info.author}`);
            if (info.title) parts.push(`标题:${info.title}`);
            if (info.tags.length > 0) parts.push(`标签:${info.tags.join(', ')}`);
            return parts.join('。');
        },

        // 执行操作(简化版,不再需要回滚)
        executeAction: async (action, config) => {
            const [minWatch, maxWatch] = config.watchBeforeLike;
            const watchTime = Math.random() * (maxWatch - minWatch) + minWatch;

            UI.log(`⏱️ 观看 ${watchTime.toFixed(1)} 秒...`, 'info');
            await Utils.randomDelay(minWatch, maxWatch);

            switch (action) {
                case 'like':
                    UI.log('👍 执行: 点赞', 'success');
                    Utils.pressKey('z');
                    await Utils.randomDelay(1, 1.5);
                    break;
                case 'dislike':
                    UI.log('👎 执行: 不感兴趣', 'warning');
                    Utils.pressKey('r');
                    await Utils.randomDelay(0.5, 1.5);
                    return; // 不感兴趣会自动跳转,不需要手动下滚
                case 'neutral':
                    UI.log('➡️ 执行: 忽略', 'info');
                    break;
            }

            // 下滚到下一个视频
            UI.log('⬇️ 切换到下一个视频...', 'info');
            Utils.pressKey('ArrowDown');
            await Utils.randomDelay(1, 2.5);
        }
    };

    // ==================== AI交互模块 ====================
    const AIService = {
        /*
         * 调用AI API
         *
         * 支持多种API格式:
         * 1. 标准OpenAI格式(OpenAI, DeepSeek, Kimi等)
         * 2. 自定义endpoint(第三方转发服务)
         */
        callAPI: (messages, config) => {
            return new Promise((resolve, reject) => {
                let endpoint = '';

                // ✅ 修复:只有选择"自定义"时才使用 customEndpoint
                if (config.apiProvider === 'custom' && config.customEndpoint) {  // ← 加上提供商判断
                    // 用户填写的自定义地址(简单处理)
                    endpoint = config.customEndpoint.replace(/\/+$/, '');

                    // 如果用户只填了基础地址(如 https://api.example.com 或 https://api.example.com/v1)
                    if (!endpoint.includes('/chat/completions')) {
                        // 智能补全
                        if (/\/v\d+$/.test(endpoint)) {
                            // 情况 1: 已有版本号 /v1, /v4 等
                            endpoint += '/chat/completions';
                        } else {
                            // 情况 2: 无版本号或其他路径,统一加 /v1/chat/completions
                            endpoint += '/v1/chat/completions';
                        }
                    }
                } else {
                    // 使用预设厂商的完整端点
                    const provider = CONFIG.apiProviders[config.apiProvider];
                    if (!provider) {
                        reject(new Error('未知的 API 提供商'));
                        return;
                    }
                    endpoint = provider.endpoint;
                }


                // ✅ 构建请求头(OpenAI 兼容格式)
                const headers = {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${config.apiKey}`
                };

                // ✅ 构建请求体基础部分(防止提供商间模型混用)
                let modelName;
                if (config.apiProvider === 'custom') {
                    // 自定义模式:直接使用用户输入的模型名
                    modelName = config.customModel || 'gpt-3.5-turbo';
                } else {
                    // 预设模式:检查保存的模型是否在当前提供商的列表中
                    const provider = CONFIG.apiProviders[config.apiProvider];
                    const validModels = provider?.models?.map(m => m.value) || [];

                    if (config.customModel && validModels.includes(config.customModel)) {
                        modelName = config.customModel;
                    } else {
                        // 如果保存的模型不匹配,使用当前提供商的默认模型
                        modelName = CONFIG.getDefaultModel(config.apiProvider);
                    }
                }

                const baseBody = {
                    model: modelName,
                    messages: messages
                };

                // ✅ 合并厂商特定的请求参数(temperature、max_tokens、stream、vendorSpecific 等)
                const provider = CONFIG.apiProviders[config.apiProvider];
                let body;

                if (provider?.requestParams) {
                    const params = { ...provider.requestParams };

                    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                    // 🔧 vendorSpecific 自动展开机制
                    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                    //
                    // 作用:将厂商特定参数从容器中提取,放到请求体根级别
                    //
                    // 示例转换:
                    //   输入 requestParams:
                    //   {
                    //       temperature: 0.3,
                    //       vendorSpecific: {
                    //           thinking: { type: 'disabled' },
                    //           custom_param: true
                    //       }
                    //   }
                    //
                    //   输出 HTTP 请求体:
                    //   {
                    //       "model": "glm-4.6",
                    //       "messages": [...],
                    //       "temperature": 0.3,              ← 标准参数保留
                    //       "thinking": { "type": "disabled" },  ← 从 vendorSpecific 展开
                    //       "custom_param": true             ← 从 vendorSpecific 展开
                    //   }
                    //
                    // 为什么这样设计?
                    //   • 避免配置文件混乱(清晰区分标准参数和特殊参数)
                    //   • 防止参数冲突(不同厂商的特殊参数互不干扰)
                    //
                    // ⚠️ 注意事项:
                    //   • vendorSpecific 中的参数会覆盖同名的外层参数
                    //   • 仅在预设厂商配置中使用,自定义 API 不支持
                    //   • 如果参数未生效,检查日志中的"完整请求体 JSON"
                    //
                    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

                    if (params.vendorSpecific && typeof params.vendorSpecific === 'object') {
                        // 提取特殊参数
                        const vendorFields = params.vendorSpecific;

                        // 从 params 中删除容器(避免发送 vendorSpecific 字段本身)
                        delete params.vendorSpecific;

                        // 合并:基础内容 + 标准参数 + 厂商特殊参数
                        body = {
                            ...baseBody,      // model, messages
                            ...params,        // temperature, stream 等
                            ...vendorFields   // thinking, custom_param 等
                        };

                        // 🆕 更详细的调试日志
                        UI.log(`🔧 检测到 vendorSpecific 参数`, 'info', 'debug');
                        UI.log(`📦 容器内容: ${JSON.stringify(vendorFields)}`, 'info', 'debug');
                        UI.log(`✅ 已自动展开到请求体根级别`, 'success', 'debug');
                    } else {
                        // 没有特殊参数,直接合并
                        body = { ...baseBody, ...params };
                    }
                } else {
                    // 🆕 自定义 API:使用最小化请求体(第 818 行开始的逻辑)
                    body = {
                        ...baseBody,
                        temperature: 0.3,
                        max_tokens: 500,
                        stream: false
                        // ⚠️ 不添加 vendorSpecific!
                        // 原因:不知道用户的 API 支持什么参数,保守策略
                    };

                    // 🆕 检测疑似推理模型,发出警告
                    const modelName = body.model.toLowerCase();
                    if (modelName.includes('reason') || modelName.includes('think') ||
                        modelName.includes('r1') || modelName.includes('o1')) {
                        UI.log('⚠️⚠️⚠️ 警告:检测到疑似推理模型!', 'warning');
                        UI.log(`📛 模型名称: ${body.model}`, 'warning');
                        UI.log('💡 推理模型可能导致解析失败,强烈建议切换到标准对话模型', 'warning');
                        UI.log('✅ 推荐模型: deepseek-chat, gpt-4o-mini, claude-3.5-sonnet 等', 'info');
                    }
                }

                UI.log(`📡 请求地址: ${endpoint}`, 'info', 'debug');
                UI.log(`🤖 使用模型: ${body.model}`, 'info', 'debug');
                UI.log(`⚙️ 参数: temperature=${body.temperature}, max_tokens=${body.max_tokens}, stream=${body.stream}`, 'info', 'debug');
                UI.log('──────── 📡 请求详情 ────────', 'info', 'debug');
                UI.log(`🌐 完整 URL: ${endpoint}`, 'info', 'debug');
                UI.log(`🔑 Authorization: Bearer ${config.apiKey.substring(0, 15)}...`, 'info', 'debug');
                UI.log(`📦 请求体关键字段:`, 'info', 'debug');
                UI.log(`  • model: ${body.model}`, 'info', 'debug');
                UI.log(`  • temperature: ${body.temperature}`, 'info', 'debug');
                UI.log(`  • max_tokens: ${body.max_tokens}`, 'info', 'debug');
                UI.log(`  • stream: ${body.stream}`, 'info', 'debug');
                if (body.thinking) {
                    UI.log(`  • thinking: ${JSON.stringify(body.thinking)}`, 'warning', 'debug');
                }
                UI.log(`📄 完整请求体 JSON (前 800 字符):`, 'info', 'debug');
                UI.log(JSON.stringify(body, null, 2).substring(0, 800), 'info', 'debug');
                UI.log('────────────────────────────', 'info', 'debug');

                // 🆕 添加等待提示
                UI.log('⏳ 正在发送请求...', 'info', 'debug');
                // 🆕 等待动画(每2秒输出一次)
                let waitCount = 0;
                const waitTimer = setInterval(() => {
                    waitCount++;
                    UI.log(`⏳ 等待服务器响应... (${waitCount * 2}秒)`, 'info', 'debug');
                }, 2000);

                GM_xmlhttpRequest({
                    method: 'POST',
                    url: endpoint,
                    headers: headers,
                    data: JSON.stringify(body),
                    timeout: 30000,
                    onload: (response) => {
                        clearInterval(waitTimer); // 🆕 清除等待动画
                        UI.log('✅ 收到响应', 'success');
                        UI.log('──────── 📥 响应详情 ────────', 'info', 'debug');
                        UI.log(`📊 状态码: ${response.status} ${response.statusText}`, 'info', 'debug');
                        UI.log(`📄 响应体前 1000 字符:`, 'info', 'debug');
                        UI.log(response.responseText.substring(0, 1000), 'info', 'debug');
                        UI.log('────────────────────────────', 'info', 'debug');
                        try {
                            if (response.status !== 200) {
                                UI.log(`❌ HTTP ${response.status}: ${response.statusText}`, 'error');
                                reject(new Error(`HTTP ${response.status}: ${response.responseText.substring(0, 200)}`));
                                return;
                            }

                            const data = JSON.parse(response.responseText);
                            let content = '';

                            // 🆕 改进:处理标准格式 + 推理模型的特殊格式
                            if (data.choices && data.choices[0] && data.choices[0].message) {
                                const msg = data.choices[0].message;
                                content = msg.content || ''; // 标准字段

                                // 🆕 检测推理模型的特殊响应
                                if (!content && msg.reasoning_content) {
                                    UI.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'error');
                                    UI.log('❌ 检测到推理模型的响应格式!', 'error');
                                    UI.log('', 'error');
                                    UI.log('📋 详细信息:', 'error');
                                    UI.log(`  • API 返回了 reasoning_content 而非 content`, 'error');
                                    UI.log(`  • 这表明你使用了带推理功能的模型`, 'error');
                                    UI.log(`  • 当前模型: ${body.model}`, 'error');
                                    UI.log('', 'error');
                                    UI.log('✅ 解决方案:', 'info');
                                    UI.log('  1. 如使用自定义API,请切换到标准对话模型', 'info');
                                    UI.log('     推荐: deepseek-chat, gpt-4o-mini, claude-3.5-sonnet', 'info');
                                    UI.log('  2. 或在"基础设置"中选择预设厂商(已优化)', 'info');
                                    UI.log('', 'error');
                                    UI.log('💡 为什么会这样?', 'info');
                                    UI.log('  推理模型(如 deepseek-reasoner)会先思考再回答,', 'info');
                                    UI.log('  其思考过程存储在 reasoning_content 中,', 'info');
                                    UI.log('  而本脚本需要直接的回答(存储在 content 中)。', 'info');
                                    UI.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'error');

                                    throw new Error(
                                        '推理模型响应格式不兼容\n\n' +
                                        '请切换到标准对话模型,或使用预设厂商配置。\n' +
                                        '详细信息请查看运行日志。'
                                    );
                                }
                            } else if (data.message && data.message.content) {
                                content = data.message.content;
                            } else {
                                UI.log(`⚠️ 未知响应格式: ${JSON.stringify(data).substring(0, 300)}`, 'error');
                                throw new Error('API 返回了不支持的格式,请检查模型是否正确');
                            }

                            if (!content) {
                                // 🆕 更详细的空内容错误提示
                                const rawSnippet = response.responseText.substring(0, 500);
                                let errorMsg = 'API 返回空内容';

                                // 二次检测(防止某些边缘情况)
                                if (rawSnippet.includes('reasoning') || rawSnippet.includes('thinking')) {
                                    errorMsg += '\n\n可能使用了推理模型,请切换到标准对话模型';
                                }

                                throw new Error(errorMsg + '\n\n原始响应片段:\n' + rawSnippet);
                            }

                            UI.log('✅ AI 响应成功', 'success');
                            resolve(content);

                        } catch (e) {
                            UI.log(`💥 解析失败: ${e.message}`, 'error');
                            reject(new Error(`${e.message}\n原始响应: ${response.responseText.substring(0, 500)}`));
                        }
                    },
                    onerror: (error) => {
                        clearInterval(waitTimer); // 🆕 清除等待动画
                        const msg = `🌐 网络错误 - ${error.statusText || error.error || '连接失败'}`;
                        UI.log(msg, 'error');
                        reject(new Error(msg));
                    },
                    ontimeout: () => {
                        clearInterval(waitTimer); // 🆕 清除等待动画
                        UI.log('⏱️ 请求超时(30秒)', 'error');
                        reject(new Error('请求超时,可能是网络问题或模型响应过慢'));
                    }
                });
            });
        },

        // 测试API连接
        testAPI: async (config) => {
            UI.log('════════════════════════════', 'info');
            UI.log('🧪 开始测试 API 连接', 'info');
            UI.log('════════════════════════════', 'info');

            // 🆕 显示当前配置快照
            UI.log(`📌 配置快照:`, 'info', 'debug');
            UI.log(`  • API 提供商: ${config.apiProvider}`, 'info', 'debug');
            UI.log(`  • API Key 前缀: ${config.apiKey.substring(0, 12)}...`, 'info', 'debug');
            UI.log(`  • 自定义端点: ${config.customEndpoint || '(空 - 使用预设)'}`, 'info');
            UI.log(`  • 自定义模型: ${config.customModel || '(空 - 使用预设)'}`, 'info');
            UI.log('', 'info');

            const testMessages = [
                { role: 'user', content: '请回复"连接成功"' }
            ];

            try {
                const response = await AIService.callAPI(testMessages, config);
                UI.log('════════════════════════════', 'success');
                UI.log('✅ API 测试成功!', 'success');
                UI.log(`📨 AI 响应内容: ${response.substring(0, 100)}`, 'success');
                UI.log('════════════════════════════', 'success');
                return { success: true, message: response };
            } catch (e) {
                UI.log('════════════════════════════', 'error');
                UI.log('❌ API 测试失败!', 'error');
                UI.log(`📛 错误消息: ${e.message}`, 'error');
                UI.log('💡 请检查上方的请求/响应详情', 'warning');
                UI.log('════════════════════════════', 'error');
                return { success: false, message: e.message };
            }
        },

        // 单次判定模式(推荐)
        judgeSingle: async (dossier, config) => {
            const prompt = `你是一个内容分类助手。现在给出三种规则:「
【点赞规则】
${config.promptLike}

【忽略规则】
${config.promptNeutral}

【不感兴趣规则】
${config.promptDislike}
」
请根据以上规则判断下述视频内容:「
【视频内容】
${dossier}
」
**重要提示**:标签可能包含干扰或对不上该视频标题的信息。
请直接回答以下JSON格式,不要有任何其他内容:
{"action": "like/neutral/dislike", "reason": "简短理由"}`;

            const messages = [{ role: 'user', content: prompt }];
            const response = await AIService.callAPI(messages, config);

            // 解析JSON
            const jsonMatch = response.match(/\{[^}]+\}/);
            if (jsonMatch) {
                return JSON.parse(jsonMatch[0]);
            }

            // 降级解析
            if (response.includes('like') || response.includes('点赞')) {
                return { action: 'like', reason: response };
            }
            if (response.includes('dislike') || response.includes('不感兴趣')) {
                return { action: 'dislike', reason: response };
            }
            return { action: 'neutral', reason: response };
        },


        // 主判定入口
        judge: async (dossier, config) => {
            return await AIService.judgeSingle(dossier, config);
        }
    };

    // ==================== UI模块 ====================
    const UI = {
        panel: null,
        floatingButton: null,

        create: () => {
            // 添加样式
            GM_addStyle(`
                /* 悬浮按钮 - 水晶风格 */
                .smart-feed-float-btn {
                    position: fixed;
                    width: 60px;
                    height: 60px;
                    border-radius: 50%;
                    background: linear-gradient(135deg, rgba(139, 162, 251, 0.85) 0%, rgba(185, 163, 251, 0.85) 100%);
                    backdrop-filter: blur(10px);
                    box-shadow: 0 8px 32px rgba(139, 162, 251, 0.3),
                                inset 0 1px 0 rgba(255, 255, 255, 0.4);
                    border: 1px solid rgba(255, 255, 255, 0.2);
                    cursor: move;
                    z-index: 999999;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    font-size: 24px;
                    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                    user-select: none;
                }

                .smart-feed-float-btn:hover {
                    transform: scale(1.05);
                    box-shadow: 0 12px 40px rgba(139, 162, 251, 0.4),
                                inset 0 1px 0 rgba(255, 255, 255, 0.5);
                }

                .smart-feed-float-btn.running {
                    background: linear-gradient(135deg, rgba(99, 230, 190, 0.85) 0%, rgba(56, 178, 172, 0.85) 100%);
                    animation: pulse-glow 2s infinite;
                }

                @keyframes pulse-glow {
                    0%, 100% {
                        box-shadow: 0 8px 32px rgba(99, 230, 190, 0.3),
                                    inset 0 1px 0 rgba(255, 255, 255, 0.4);
                    }
                    50% {
                        box-shadow: 0 12px 48px rgba(99, 230, 190, 0.6),
                                    inset 0 1px 0 rgba(255, 255, 255, 0.5);
                    }
                }

                /* 主面板 - 毛玻璃效果 */
                .smart-feed-panel {
                    position: fixed;
                    width: 420px;
                    max-height: 80vh;
                    background: rgba(255, 255, 255, 0.5);
                    backdrop-filter: blur(20px) saturate(180%);
                    -webkit-backdrop-filter: blur(20px) saturate(180%);
                    border-radius: 20px;
                    box-shadow: 0 20px 60px rgba(100, 100, 150, 0.15),
                                0 0 0 1px rgba(255, 255, 255, 0.3);
                    z-index: 999998;
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
                    color: #1f2937;
                    overflow: hidden;
                    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                }

                /* 顶部标题栏 - 水晶风格 + 集成开始按钮 */
                .smart-feed-header {
                    padding: 16px 20px;
                    background: linear-gradient(135deg, rgba(139, 162, 251, 0.65) 0%, rgba(185, 163, 251, 0.65) 100%);
                    backdrop-filter: blur(10px);
                    border-bottom: 1px solid rgba(255, 255, 255, 0.2);
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    cursor: move;
                    user-select: none;
                }

                .smart-feed-title {
                    font-size: 16px;
                    font-weight: 700;
                    color: rgba(255, 255, 255, 0.95);
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
                }

                /* 顶部按钮组 */
                .smart-feed-header-actions {
                    display: flex;
                    gap: 8px;
                    align-items: center;
                }

                /* 开始运行按钮(在顶部) */
                .smart-feed-start-btn {
                    padding: 8px 16px;
                    border-radius: 10px;
                    border: none;
                    background: rgba(255, 255, 255, 0.9);
                    color: #10b981;
                    font-size: 14px;
                    font-weight: 600;
                    cursor: pointer;
                    transition: all 0.2s;
                    box-shadow: 0 2px 8px rgba(16, 185, 129, 0.2);
                }

                .smart-feed-start-btn:hover {
                    background: rgba(255, 255, 255, 1);
                    transform: translateY(-1px);
                    box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
                }

                .smart-feed-start-btn.running {
                    background: rgba(239, 68, 68, 0.9);
                    color: white;
                }

                .smart-feed-start-btn.running:hover {
                    background: rgba(239, 68, 68, 1);
                }

                .smart-feed-close {
                    width: 32px;
                    height: 32px;
                    border-radius: 50%;
                    background: rgba(255, 255, 255, 0.25);
                    backdrop-filter: blur(10px);
                    border: 1px solid rgba(255, 255, 255, 0.3);
                    color: rgba(255, 255, 255, 0.95);
                    font-size: 20px;
                    cursor: pointer;
                    transition: all 0.2s;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                }

                .smart-feed-close:hover {
                    background: rgba(255, 255, 255, 0.35);
                    transform: rotate(90deg);
                }

                .smart-feed-body {
                    max-height: calc(80vh - 70px);
                    overflow-y: auto;
                    padding: 20px;
                    background: rgba(255, 255, 255, 0.5);
                }

                .smart-feed-body::-webkit-scrollbar {
                    width: 6px;
                }

                .smart-feed-body::-webkit-scrollbar-thumb {
                    background: rgba(139, 162, 251, 0.3);
                    border-radius: 3px;
                }

                .smart-feed-body::-webkit-scrollbar-thumb:hover {
                    background: rgba(139, 162, 251, 0.5);
                }

                /* 标签页 */
                .smart-feed-tabs {
                    display: flex;
                    gap: 8px;
                    margin-bottom: 20px;
                    background: rgba(241, 245, 249, 0.6);
                    backdrop-filter: blur(10px);
                    padding: 4px;
                    border-radius: 12px;
                }

                .smart-feed-tab {
                    flex: 1;
                    padding: 10px;
                    border: none;
                    background: transparent;
                    color: #64748b;
                    border-radius: 8px;
                    cursor: pointer;
                    font-size: 14px;
                    font-weight: 500;
                    transition: all 0.2s;
                }

                .smart-feed-tab:hover {
                    color: #475569;
                    background: rgba(255, 255, 255, 0.5);
                }

                .smart-feed-tab.active {
                    background: rgba(255, 255, 255, 0.9);
                    color: rgba(139, 162, 251, 1);
                    box-shadow: 0 2px 8px rgba(139, 162, 251, 0.15);
                }

                /* 表单元素 */
                .smart-feed-section {
                    margin-bottom: 20px;
                }

                .smart-feed-label {
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    margin-bottom: 10px;
                    font-size: 14px;
                    font-weight: 600;
                    color: #374151;
                }

                .smart-feed-help {
                    cursor: help;
                    width: 18px;
                    height: 18px;
                    border-radius: 50%;
                    background: rgba(139, 162, 251, 0.2);
                    color: rgba(139, 162, 251, 1);
                    display: inline-flex;
                    align-items: center;
                    justify-content: center;
                    font-size: 12px;
                    font-weight: bold;
                    transition: all 0.2s;
                }

                .smart-feed-help:hover {
                    background: rgba(139, 162, 251, 0.9);
                    color: white;
                    transform: scale(1.1);
                }

                .smart-feed-input, .smart-feed-textarea, .smart-feed-select {
                    width: 100%;
                    padding: 12px;
                    border: 2px solid rgba(229, 231, 235, 0.8);
                    border-radius: 10px;
                    background: rgba(255, 255, 255, 0.8);
                    backdrop-filter: blur(10px);
                    color: #1f2937;
                    font-size: 14px;
                    transition: all 0.2s;
                    box-sizing: border-box;
                }

                .smart-feed-input:focus, .smart-feed-textarea:focus, .smart-feed-select:focus {
                    outline: none;
                    border-color: rgba(139, 162, 251, 0.8);
                    background: rgba(255, 255, 255, 0.95);
                    box-shadow: 0 0 0 3px rgba(139, 162, 251, 0.1);
                }

                .smart-feed-textarea {
                    min-height: 80px;
                    resize: vertical;
                    font-family: inherit;
                }

                .smart-feed-button {
                    width: 100%;
                    padding: 14px;
                    border: none;
                    border-radius: 12px;
                    font-size: 15px;
                    font-weight: 600;
                    cursor: pointer;
                    transition: all 0.2s;
                    margin-top: 10px;
                }

                .smart-feed-button-primary {
                    background: linear-gradient(135deg, rgba(16, 185, 129, 0.9) 0%, rgba(5, 150, 105, 0.9) 100%);
                    color: white;
                    box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
                }

                .smart-feed-button-primary:hover {
                    transform: translateY(-2px);
                    box-shadow: 0 6px 20px rgba(16, 185, 129, 0.3);
                }

                .smart-feed-button-stop {
                    background: linear-gradient(135deg, rgba(239, 68, 68, 0.9) 0%, rgba(220, 38, 38, 0.9) 100%);
                    color: white;
                }

                .smart-feed-button-secondary {
                    background: rgba(243, 244, 246, 0.8);
                    backdrop-filter: blur(10px);
                    color: #374151;
                }

                .smart-feed-button-secondary:hover {
                    background: rgba(229, 231, 235, 0.9);
                }

                /* 日志 */
                .smart-feed-log {
                    background: rgba(248, 250, 252, 0.8);
                    backdrop-filter: blur(10px);
                    border: 1px solid rgba(226, 232, 240, 0.8);
                    border-radius: 10px;
                    padding: 15px;
                    max-height: 300px;
                    overflow-y: auto;
                    font-size: 12px;
                    font-family: 'Courier New', monospace;
                }

                /* 🆕 在这里添加以下新样式(约第352行) */

                /* 可折叠日志容器 */
                .collapsible-log {
                    position: relative;
                    display: inline-block;
                    width: 100%;
                }

                /* 预览文本(默认显示) */
                .collapsible-log .log-preview {
                    display: inline;
                    color: inherit;
                }

                /* 完整文本(默认隐藏) */
                .collapsible-log .log-full {
                    display: none;
                    margin-top: 8px;
                    padding: 10px;
                    background: rgba(241, 245, 249, 0.9);
                    border-radius: 6px;
                    border: 1px solid rgba(226, 232, 240, 0.6);
                    font-size: 11px;
                    line-height: 1.6;
                    overflow-x: auto;
                    white-space: pre-wrap;
                    word-break: break-all;
                }

                /* 展开按钮 */
                .collapsible-log .expand-btn {
                    margin-left: 8px;
                    padding: 2px 8px;
                    border: none;
                    background: rgba(139, 162, 251, 0.15);
                    color: rgba(139, 162, 251, 1);
                    border-radius: 4px;
                    cursor: pointer;
                    font-size: 11px;
                    font-weight: 600;
                    transition: all 0.2s;
                    vertical-align: middle;
                }

                .collapsible-log .expand-btn:hover {
                    background: rgba(139, 162, 251, 0.25);
                    transform: translateY(-1px);
                }

                /* 展开状态 */
                .collapsible-log.expanded .log-preview {
                    display: none;
                }

                .collapsible-log.expanded .log-full {
                    display: block;
                }

                .collapsible-log.expanded .expand-btn {
                    background: rgba(239, 68, 68, 0.15);
                    color: rgba(239, 68, 68, 1);
                }

                .collapsible-log.expanded .expand-btn::before {
                    content: '收起 ';
                }

                .collapsible-log:not(.expanded) .expand-btn::before {
                    content: '展开 ';
                }

                .smart-feed-log-item {
                    margin-bottom: 8px;
                    padding: 6px 0;
                    border-bottom: 1px solid rgba(226, 232, 240, 0.5);
                    display: flex;
                    gap: 10px;
                }

                .smart-feed-log-time {
                    color: #94a3b8;
                    flex-shrink: 0;
                }

                .smart-feed-log-text {
                    flex: 1;
                }

                /* 其他 */
                .smart-feed-range-group {
                    display: flex;
                    gap: 10px;
                    align-items: center;
                }

                .smart-feed-range-input {
                    flex: 1;
                }

                .smart-feed-checkbox-group {
                    display: flex;
                    align-items: center;
                    gap: 10px;
                    padding: 12px;
                    background: rgba(248, 250, 252, 0.8);
                    backdrop-filter: blur(10px);
                    border-radius: 10px;
                }

                .smart-feed-checkbox {
                    width: 20px;
                    height: 20px;
                    cursor: pointer;
                }

                .smart-feed-info-box {
                    background: rgba(254, 243, 199, 0.8);
                    backdrop-filter: blur(10px);
                    border-left: 4px solid rgba(245, 158, 11, 0.8);
                    padding: 12px;
                    border-radius: 8px;
                    font-size: 13px;
                    color: #92400e;
                    margin-bottom: 15px;
                }

                .smart-feed-link {
                    color: rgba(139, 162, 251, 1);
                    text-decoration: none;
                    font-weight: 600;
                }

                .smart-feed-link:hover {
                    text-decoration: underline;
                }

                /* 统计卡片 */
                .smart-feed-stats {
                    display: grid;
                    grid-template-columns: repeat(2, 1fr);
                    gap: 10px;
                    margin-bottom: 20px;
                }

                .smart-feed-stat-card {
                    background: linear-gradient(135deg, rgba(240, 249, 255, 0.8) 0%, rgba(224, 242, 254, 0.8) 100%);
                    backdrop-filter: blur(10px);
                    padding: 15px;
                    border-radius: 12px;
                    text-align: center;
                    border: 1px solid rgba(186, 230, 253, 0.3);
                }

                .smart-feed-stat-value {
                    font-size: 24px;
                    font-weight: 700;
                    color: #0284c7;
                }

                .smart-feed-stat-label {
                    font-size: 12px;
                    color: #64748b;
                    margin-top: 5px;
                }

                /* 性能优化:启用 GPU 加速 */
                .smart-feed-panel,
                .smart-feed-float-btn,
                .smart-feed-button {
                    will-change: transform;
                    transform: translateZ(0);
                }

                /* 可折叠帮助框 */
                .collapsible-help-box .help-header {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    cursor: pointer;
                    user-select: none;
                }

                .collapsible-help-box .help-toggle-btn {
                    padding: 4px 12px;
                    border: none;
                    background: rgba(139, 162, 251, 0.2);
                    color: rgba(139, 162, 251, 1);
                    border-radius: 6px;
                    cursor: pointer;
                    font-size: 12px;
                    font-weight: 600;
                    transition: all 0.2s;
                }

                .collapsible-help-box .help-toggle-btn:hover {
                    background: rgba(139, 162, 251, 0.3);
                    transform: translateY(-1px);
                }

                .collapsible-help-box .help-content {
                    display: none;
                    margin-top: 12px;
                    padding-top: 12px;
                }

                .collapsible-help-box.expanded .help-content {
                    display: block;
                }

                .collapsible-help-box.expanded .help-toggle-btn {
                    background: rgba(239, 68, 68, 0.2);
                    color: rgba(239, 68, 68, 1);
                }
            `);


            const config = loadConfig();

            // 🆕 详细调试日志
            console.log('[智能助手] 🔧 初始化 - 完整配置:', config);
            console.log('[智能助手] 📍 panelPosition 原始值:', config.panelPosition);
            console.log('[智能助手] 📍 panelPosition 类型检查:', {
                是对象: typeof config.panelPosition === 'object',
                x类型: typeof config.panelPosition?.x,
                y类型: typeof config.panelPosition?.y,
                x值: config.panelPosition?.x,
                y值: config.panelPosition?.y
            });

            // 创建悬浮按钮
            UI.floatingButton = document.createElement('div');
            UI.floatingButton.className = 'smart-feed-float-btn';
            UI.floatingButton.innerHTML = '🤖';

            // 🆕 更严格的位置解析
            let savedX, savedY, useDefault = false;

            if (config.panelPosition &&
                typeof config.panelPosition.x === 'number' &&
                typeof config.panelPosition.y === 'number' &&
                !isNaN(config.panelPosition.x) &&
                !isNaN(config.panelPosition.y)) {
                savedX = config.panelPosition.x;
                savedY = config.panelPosition.y;
                console.log('[智能助手] ✅ 使用保存的位置:', savedX, savedY);
            } else {
                savedX = window.innerWidth - 80;
                savedY = 100;
                useDefault = true;
                console.log('[智能助手] ⚠️ 使用默认位置(原因: panelPosition无效):', savedX, savedY);
                console.log('[智能助手] 💡 判断依据:', {
                    存在性: !!config.panelPosition,
                    x是数字: typeof config.panelPosition?.x === 'number',
                    y是数字: typeof config.panelPosition?.y === 'number',
                    x非NaN: !isNaN(config.panelPosition?.x),
                    y非NaN: !isNaN(config.panelPosition?.y)
                });
            }

            // 🆕 确保值在合理范围内
            savedX = Math.max(0, Math.min(window.innerWidth - 60, savedX));
            savedY = Math.max(0, Math.min(window.innerHeight - 60, savedY));

            // 🆕 显式设置style(确保没有transform干扰)
            UI.floatingButton.style.left = savedX + 'px';
            UI.floatingButton.style.top = savedY + 'px';
            UI.floatingButton.style.transform = 'none'; // 🆕 强制移除transform
            UI.floatingButton.title = '点击打开智能助手';

            console.log('[智能助手] 🎯 按钮最终位置:', {
                left: UI.floatingButton.style.left,
                top: UI.floatingButton.style.top,
                使用默认值: useDefault
            });

            // 创建主面板(默认隐藏)
            UI.panel = document.createElement('div');
            UI.panel.className = 'smart-feed-panel';
            UI.panel.style.display = config.panelMinimized ? 'none' : 'block';

            // 🆕 面板位置跟随按钮
            const panelLeft = Math.max(10, savedX - 360);
            const panelTop = Math.max(10, savedY);
            UI.panel.style.left = panelLeft + 'px';
            UI.panel.style.top = panelTop + 'px';

            console.log('[智能助手] 面板初始位置:', panelLeft, panelTop); // 🆕 调试日志


            UI.panel.innerHTML = `
                <div class="smart-feed-header">
                    <div class="smart-feed-title">
                        🤖 智能助手
                    </div>
                    <div class="smart-feed-header-actions">
                        <button class="smart-feed-start-btn" id="startBtnTop">▶ 开始</button>
                        <button class="smart-feed-close">×</button>
                    </div>
                </div>
                <div class="smart-feed-body">
                    <div class="smart-feed-tabs">
                        <button class="smart-feed-tab active" data-tab="basic">基础设置</button>
                        <button class="smart-feed-tab" data-tab="advanced">高级选项</button>
                        <button class="smart-feed-tab" data-tab="log">运行日志</button>
                        <button class="smart-feed-tab" data-tab="about">关于</button>
                    </div>

                    <!-- 基础设置 -->
                    <div class="smart-feed-tab-content" data-content="basic">
                        <div class="smart-feed-info-box">
                            ⚠️ 本工具可能因抖音更新而失效,遇到问题请及时反馈!
                        </div>

                        <div class="smart-feed-section">
                            <div class="smart-feed-label">
                                🔌 API 提供商
                                <span class="smart-feed-help" title="点击“关于”标签查看详细教程">?</span>
                            </div>
                            <select class="smart-feed-select" id="apiProvider">
                                ${Object.entries(CONFIG.apiProviders).map(([key, provider]) =>
                                    `<option value="${key}">${provider.name}</option>`
                                ).join('')}
                                <option value="custom">自定义 OpenAI 兼容 API</option>
                            </select>
                        </div>

                        <!-- 🆕 重要提示框(可折叠) -->
                        <div class="smart-feed-info-box collapsible-help-box" style="margin-top: 10px; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-left: 4px solid #f59e0b;">
                            <div class="help-header">
                                <strong>🎯 新手 5 分钟上手指南</strong>
                                <button class="help-toggle-btn">展开 ▼</button>
                            </div>
                            <div class="help-content">
                                <!-- 第一部分:准备工作 -->
                                <div style="background: rgba(220, 38, 38, 0.1); border-left: 3px solid #dc2626; padding: 12px; border-radius: 6px; margin-bottom: 15px;">
                                    <strong style="color: #dc2626;">📋 开始前的准备(必做!)</strong><br>
                                    <div style="margin-top: 8px; line-height: 1.8;">
                                        ☑️ 打开抖音网页版:<a href="https://www.douyin.com/" target="_blank" style="color: #2563eb;">www.douyin.com</a><br>
                                        ☑️ 点击左侧菜单"<strong>推荐</strong>"(不是"精选")<br>
                                        ☑️ 关闭视频右下角的"<strong>自动连播</strong>"(必须变成灰色)<br>
                                        ☑️ 确保浏览器没有开启无痕模式(否则配置无法保存)
                                    </div>
                                </div>
                        
                                <!-- 第二部分:配置流程 -->
                                <strong>⚙️ 三步完成配置</strong><br>
                                <div style="background: rgba(255,255,255,0.7); padding: 12px; border-radius: 8px; margin: 10px 0;">
                                    <table style="width: 100%; font-size: 13px; line-height: 1.8;">
                                        <tr>
                                            <td style="width: 60px; vertical-align: top; font-weight: bold; color: #7c3aed;">步骤 1</td>
                                            <td>
                                                <strong>获取 API Key</strong>(注册即可)<br>
                                                <span style="color: #64748b;">
                                                • 推荐新手选 <a href="https://platform.deepseek.com/api_keys" target="_blank" style="color: #2563eb;">DeepSeek</a><br>
                                                • 注册后在控制台点"创建 API Key",复制那串英文<br>
                                                • 或选 <a href="https://open.bigmodel.cn/usercenter/apikeys" target="_blank" style="color: #2563eb;">智谱GLM</a>(有长期免费模型,但其控制台稍显复杂)
                                                </span>
                                            </td>
                                        </tr>
                                        <tr><td colspan="2" style="padding: 8px 0;"></td></tr>
                                        <tr>
                                            <td style="vertical-align: top; font-weight: bold; color: #7c3aed;">步骤 2</td>
                                            <td>
                                                <strong>填写配置</strong><br>
                                                <span style="color: #64748b;">
                                                • 在下方"<strong>API 提供商</strong>"选你刚注册的平台<br>
                                                • 把复制的 Key 粘贴到"<strong>API Key</strong>"输入框<br>
                                                • 点击"<strong>🧪 测试连接</strong>"按钮(看到绿色成功提示就对了)
                                                </span>
                                            </td>
                                        </tr>
                                        <tr><td colspan="2" style="padding: 8px 0;"></td></tr>
                                        <tr>
                                            <td style="vertical-align: top; font-weight: bold; color: #7c3aed;">步骤 3</td>
                                            <td>
                                                <strong>设置偏好</strong><br>
                                                <span style="color: #64748b;">
                                                • 新手直接选"<strong>预设模板</strong>"(如"青少年内容引导")<br>
                                                • 或者在三个规则框里描述你想看/不想看什么<br>
                                                • <strong style="color: #dc2626;">滚动到底部点"💾 保存当前配置"</strong>
                                                </span>
                                            </td>
                                        </tr>
                                    </table>
                                </div>
                        
                                <!-- 第三部分:开始使用 -->
                                <div style="background: rgba(16, 185, 129, 0.1); border-left: 3px solid #10b981; padding: 12px; border-radius: 6px; margin: 15px 0;">
                                    <strong style="color: #059669;">✅ 配置完成后</strong><br>
                                    <div style="margin-top: 8px; line-height: 1.8;">
                                        1️⃣ 点击面板右上角"<strong>▶ 开始</strong>"按钮<br>
                                        2️⃣ 切换到"<strong>运行日志</strong>"标签页,看实时处理进度<br>
                                        3️⃣ <strong style="color: #dc2626;">保持抖音标签页可见</strong><br>
                                        4️⃣ 建议首次运行 10-15 分钟,观察效果后再调整
                                    </div>
                                </div>
                        
                                <!-- 第四部分:常见错误 -->
                                <details style="margin-top: 15px;">
                                    <summary style="cursor: pointer; color: #dc2626; font-weight: bold;">❌ 遇到问题?点击查看常见错误</summary>
                                    <div style="margin-top: 10px; padding-left: 15px; font-size: 12px; line-height: 1.8; color: #64748b;">
                                        <strong>Q: 点"测试连接"失败?</strong><br>
                                        A: ① 检查 Key 前后有没有多余空格 ② 确认选对了提供商 ③ 检查网络能否访问对应网站<br><br>
                        
                                        <strong>Q: 脚本一直显示"无法定位视频"?</strong><br>
                                        A: ① 确认在"推荐"页面 ② 关闭了自动连播 ③ 刷新页面重试<br><br>
                        
                                        <strong>其他问题?</strong><br>
                                        发邮件到 <a href="mailto:[email protected]" style="color: #2563eb;">[email protected]</a>,记得附上"运行日志"截图
                                    </div>
                                </details>
                        
                                <hr style="border: none; border-top: 1px dashed #cbd5e1; margin: 15px 0;">
                        
                                <!-- 第五部分:进阶说明(折叠) -->
                                <details style="margin-top: 10px;">
                                    <summary style="cursor: pointer; color: #7c3aed; font-weight: bold;">🔧 进阶:自定义 API 怎么用?</summary>
                                    <div style="margin-top: 10px; padding-left: 15px; font-size: 12px; line-height: 1.8; color: #64748b;">
                                        如果你用的是第三方转发服务(如 OpenAI 中转):<br><br>
                        
                                        1️⃣ 在"<strong>API 提供商</strong>"选"<strong>自定义 OpenAI 兼容 API</strong>"<br>
                                        2️⃣ 填写 API 地址(只需填到域名或 /v1,脚本会自动补全):<br>
                                        <code style="background: #f1f5f9; padding: 2px 6px; border-radius: 3px;">https://api.example.com/v1</code><br>
                                        3️⃣ 手动输入模型名称(如 <code>gpt-4o-mini</code>)<br><br>
                        
                                        <strong style="color: #dc2626;">⚠️ 自定义 API 禁止使用推理模型</strong><br>
                                        如 <code>deepseek-reasoner</code>、<code>o1</code> 等会导致解析失败
                                    </div>
                                </details>
                        
                                <div style="margin-top: 15px; padding: 10px; background: rgba(139, 92, 246, 0.1); border-radius: 6px; font-size: 12px; text-align: center; color: #7c3aed;">
                                    💡 <strong>小贴士</strong>:第一次使用建议从预设模板开始,熟悉后再自定义规则
                                </div>
                            </div>
                        </div>

                        <div class="smart-feed-section">
                            <div class="smart-feed-label">🔑 API Key</div>
                            <input type="text" class="smart-feed-input" id="apiKey" placeholder="输入你的 API Key(长串英文)">
                            <small style="color: #64748b; display: block; margin-top: 5px;">
                                💡 在各平台的控制台/设置页面创建后,粘贴到这里
                            </small>
                        </div>

                        <!-- 🆕 模型选择(动态生成) -->
                        <div class="smart-feed-section" id="modelSection">
                            <div class="smart-feed-label">
                                🤖 模型选择
                                <span class="smart-feed-help" title="不同模型的能力和价格不同">?</span>
                            </div>
                            <select class="smart-feed-select" id="modelSelect">
                                <!-- 由 JavaScript 动态生成 -->
                            </select>
                            <small style="color: #94a3b8; display: block; margin-top: 5px; font-size: 12px;">
                                ⚙️ 开发者提示:新增模型请修改 <code>CONFIG.apiProviders[厂商].models</code> 数组
                            </small>
                        </div>

                        <!-- 🆕 自定义 API 地址(仅在选择"自定义"时显示) -->
                        <div class="smart-feed-section" id="customEndpointSection" style="display: none;">
                            <div class="smart-feed-label">
                                🌐 API 地址
                                <span class="smart-feed-help" title="仅在使用自定义 API 时填写">?</span>
                            </div>
                            <input type="text" class="smart-feed-input" id="customEndpoint" placeholder="留空则使用官方地址">
                            <small style="color: #64748b; display: block; margin-top: 5px;">
                                💡 <strong>填写方式(任选其一)</strong>:<br>
                                • 只填域名:<code>https://api.example.com</code><br>
                                • 填到版本号:<code>https://api.example.com/v1</code><br>
                                • 填完整路径:<code>https://api.example.com/v1/chat/completions</code><br>
                                <strong>✅ 脚本会智能补全缺失部分,你填哪种都行</strong>
                            </small>

                            <!-- 🆕 新增警告框 -->
                            <div style="background: rgba(254, 226, 226, 0.9); border-left: 4px solid #dc2626; padding: 12px; border-radius: 8px; margin-top: 10px; font-size: 13px; color: #991b1b;">
                                <strong>⚠️ 重要限制</strong><br>
                                自定义API时,<strong>请勿使用</strong>带推理/思考模式的模型,例如:<br>
                                • ❌ <code>deepseek-reasoner</code>(DeepSeek R1)<br>
                                • ❌ 其他带 <code>reasoning</code> 功能的模型<br><br>
                                <strong>原因</strong>:这类模型会返回推理过程而非直接内容,导致脚本无法正确解析。<br>
                                <strong>建议</strong>:使用标准对话模型,如 <code>deepseek-chat</code>、<code>gpt-4o-mini</code> 等。
                            </div>
                        </div>

                        <button class="smart-feed-button smart-feed-button-secondary" id="testApiBtn" style="margin-top: 10px;">
                            🧪 测试连接
                        </button>

                        <div class="smart-feed-section">
                            <div class="smart-feed-label">预设模板</div>
                            <select class="smart-feed-select" id="template">
                                <option value="">自定义规则</option>
                                ${Object.keys(CONFIG.templates).map(t => `<option value="${t}">${t}</option>`).join('')}
                            </select>
                        </div>

                        <div class="smart-feed-section">
                            <div class="smart-feed-label">点赞收藏规则</div>
                            <textarea class="smart-feed-textarea" id="promptLike" placeholder="描述你希望看到什么内容...">${config.promptLike}</textarea>
                        </div>

                        <div class="smart-feed-section">
                            <div class="smart-feed-label">忽略路过规则</div>
                            <textarea class="smart-feed-textarea" id="promptNeutral" placeholder="描述普通内容的标准...">${config.promptNeutral}</textarea>
                        </div>

                        <div class="smart-feed-section">
                            <div class="smart-feed-label">不感兴趣规则</div>
                            <textarea class="smart-feed-textarea" id="promptDislike" placeholder="描述你想过滤什么内容...">${config.promptDislike}</textarea>
                        </div>

                        <div class="smart-feed-section">
                            <div class="smart-feed-label">操作间隔(秒)</div>
                            <div class="smart-feed-range-group">
                                <input type="number" class="smart-feed-input smart-feed-range-input" id="minDelay" value="${config.minDelay}" min="1" max="60">
                                <span>到</span>
                                <input type="number" class="smart-feed-input smart-feed-range-input" id="maxDelay" value="${config.maxDelay}" min="1" max="60">
                            </div>
                        </div>

                        <div class="smart-feed-section">
                            <div class="smart-feed-label">运行时长(分钟)</div>
                            <input type="number" class="smart-feed-input" id="runDuration" value="${config.runDuration}" min="1" max="180">
                        </div>

                    </div>

                    <!-- 高级选项 -->
                    <div class="smart-feed-tab-content" data-content="advanced" style="display: none;">
                        <div class="smart-feed-info-box">
                            ℹ️ 这些设置影响工具的行为模式,建议保持默认值
                        </div>


                        <div class="smart-feed-section">
                            <div class="smart-feed-label">操作前观看时长(秒)</div>
                            <div class="smart-feed-range-group">
                                <input type="number" class="smart-feed-input smart-feed-range-input" id="watchMin" value="${config.watchBeforeLike[0]}" min="0" max="30">
                                <span>到</span>
                                <input type="number" class="smart-feed-input smart-feed-range-input" id="watchMax" value="${config.watchBeforeLike[1]}" min="0" max="30">
                            </div>
                            <small style="color: #64748b;">模拟真人观看一段时间后再操作</small>
                        </div>

                        <div class="smart-feed-section">
                            <div class="smart-feed-label">内容跳过概率(%)</div>
                            <input type="number" class="smart-feed-input" id="skipProbability" value="${config.skipProbability}" min="0" max="50">
                            <small style="color: #64748b;">随机跳过部分视频,避免每个都操作</small>
                        </div>

                        <div class="smart-feed-section">
                            <div class="smart-feed-label">API失败重试次数</div>
                            <input type="number" class="smart-feed-input" id="maxRetries" value="${config.maxRetries}" min="1" max="10">
                        </div>
                    </div>

                    <!-- 运行日志 -->
                    <div class="smart-feed-tab-content" data-content="log" style="display: none;">
                        <div class="smart-feed-stats" id="statsContainer">
                            <div class="smart-feed-stat-card">
                                <div class="smart-feed-stat-value" id="statTotal">0</div>
                                <div class="smart-feed-stat-label">已处理</div>
                            </div>
                            <div class="smart-feed-stat-card">
                                <div class="smart-feed-stat-value" id="statLiked">0</div>
                                <div class="smart-feed-stat-label">点赞</div>
                            </div>
                            <div class="smart-feed-stat-card">
                                <div class="smart-feed-stat-value" id="statNeutral">0</div>
                                <div class="smart-feed-stat-label">忽略</div>
                            </div>
                            <div class="smart-feed-stat-card">
                                <div class="smart-feed-stat-value" id="statDisliked">0</div>
                                <div class="smart-feed-stat-label">不感兴趣</div>
                            </div>
                        </div>

                        <!-- 🆕 新增:日志控制栏 -->
                        <div style="display: flex; gap: 10px; margin-bottom: 10px; align-items: center; justify-content: space-between;">
                            <label style="display: flex; align-items: center; gap: 6px; font-size: 13px; color: #64748b; cursor: pointer; user-select: none;">
                                <input type="checkbox" id="verboseLog" style="width: 16px; height: 16px; cursor: pointer;">
                                <span>显示详细调试信息</span>
                            </label>
                            <button class="smart-feed-button smart-feed-button-secondary" id="clearLog"
                                    style="margin: 0; padding: 8px 16px; width: auto; font-size: 13px;">
                                🗑️ 清空日志
                            </button>
                        </div>

                        <div class="smart-feed-log" id="logContainer">
                            <div class="smart-feed-log-item">
                                <span class="smart-feed-log-time">${new Date().toLocaleTimeString()}</span>
                                <span class="smart-feed-log-text">等待开始运行...</span>
                            </div>
                        </div>
                    </div>

                    <!-- 关于 -->
                    <div class="smart-feed-tab-content" data-content="about" style="display: none;">
                        <div class="smart-feed-section">
                            <h3 style="margin: 0 0 15px 0; color: #1f2937;">📖 使用说明</h3>
                            <div style="background: #f8fafc; padding: 15px; border-radius: 10px; font-size: 13px; line-height: 1.8; color: #475569;">
                                <p><strong>⚠️ 后台挂机说明:</strong></p>
                                <p>• 本脚本<strong>需要保持抖音标签页可见</strong>(不能切换到其他标签页)</p>
                                <p>• 可以最小化浏览器窗口,但抖音页面必须在当前激活的标签</p>
                                <p>• 原因:快捷键操作和DOM监听需要页面处于活跃状态</p>
                                <p>• 建议:使用独立浏览器窗口运行,不影响其他工作</p>

                                <hr style="border: none; border-top: 1px solid #e2e8f0; margin: 15px 0;">

                                <p><strong>❓ 常见问题</strong></p>

                                <p><strong>Q: 价格大概多少?</strong></p>
                                <p>A: 取决于你所选择的API供应商,部分供应商完全可以做到免费,如新人注册送大量限时额度。Deepseek参考价格:1元约可以判断1000次视频。</p>

                                <p><strong>Q: 为什么不能使用 deepseek 深度思考(如R1)?</strong></p>
                                <p>A: 推理模型会返回思考过程而非直接回答(content)。关键在于:1.这样会拖慢判断速度,让抖音误以为您长时间停留在看该视频;2.是为了您的钱包着想,这样不省钱。请使用 如deepseek-chat (类似曾经的DeepSeek-V3)等的标准对话模型。</p>

                                <p><strong>Q: 出现 400/422 错误怎么办?</strong></p>
                                <p>A: 检查 API 地址是否正确,或尝试切换到预设厂商配置。</p>

                                <p><strong>Q: 自定义 API 支持哪些参数?</strong></p>

                                <p><strong>🎯 如何获取 API Key:</strong></p>
                                <p>• <a href="https://platform.deepseek.com/api_keys" target="_blank" class="smart-feed-link">DeepSeek 官网</a> - 价格最便宜(推荐)</p>
                                <p>• <a href="https://platform.moonshot.cn/console/api-keys" target="_blank" class="smart-feed-link">Kimi 官网</a> - 国内服务,有免费额度</p>
                                <p>• <a href="https://dashscope.console.aliyun.com/apiKey" target="_blank" class="smart-feed-link">Qwen 官网</a> - 阿里云通义千问</p>
                                <p>• <a href="https://open.bigmodel.cn/usercenter/apikeys" target="_blank" class="smart-feed-link">GLM 官网</a> - 智谱 AI</p>
                                <p>• 第三方转发:如果你有其他兼容 OpenAI 格式的 API,选择"自定义"</p>

                                <hr style="border: none; border-top: 1px solid #e2e8f0; margin: 15px 0;">

                                <p><strong>📝 填写示例:</strong></p>
                                <p><strong>DeepSeek:</strong></p>
                                <p>• API Key: <code>sk-xxxxxx</code></p>
                                <p>• 模型: <code>deepseek-chat</code></p>
                                <p>• API 地址: 留空(自动使用 <code>https://api.deepseek.com/v1/chat/completions</code>)</p>

                                <p><strong>自定义 API(如第三方转发):</strong></p>
                                <p>• API Key: <code>你的Key</code></p>
                                <p>• API 地址: <code>https://your-api.com/v1</code>(只需填到 /v1,脚本会自动补全)</p>
                                <p>• 模型: 手动输入模型名称</p>

                                <hr style="border: none; border-top: 1px solid #e2e8f0; margin: 15px 0;">

                                <p><strong>🔧 开发者维护说明</strong></p>
                                <p>• <strong>统一配置位置</strong>:所有厂商配置集中在 <code>CONFIG.apiProviders</code>(第 145 行)</p>
                                <p>• <strong>新增厂商</strong>:在 <code>apiProviders</code> 中添加一个对象,包含 name、endpoint、defaultModel、models、requestParams</p>
                                <p>• <strong>新增模型</strong>:在对应厂商的 <code>models</code> 数组中添加 <code>{ value: 'model-id', label: '显示名称' }</code></p>
                                <p>• <strong>调整请求参数</strong>:修改 <code>requestParams</code>(支持 temperature、max_tokens、stream、extra_body 等)</p>
                                <p>• <strong>特殊参数示例</strong>:GLM 的 <code>extra_body.thinking</code> 禁用,DeepSeek 的温度调整等</p>
                                <p>• <strong>无需分散修改</strong>:模型、端点、参数全部在一个配置对象中</p>

                                <p><strong>💡 使用技巧:</strong></p>
                                <p>• 首次使用建议先测试连接,确保API可用</p>
                                <p>• 运行时长设置10-20分钟即可,避免长时间挂机</p>
                            </div>
                        </div>

                        <div class="smart-feed-section">
                            <h3 style="margin: 0 0 15px 0; color: #1f2937;">🐛 反馈与支持</h3>
                            <div style="background: #fef3c7; padding: 15px; border-radius: 10px; font-size: 13px; line-height: 1.8; color: #92400e;">
                                <p><strong>本工具可能因抖音更新而失效!</strong></p>
                                <p>遇到问题请及时反馈,帮助我们改进:</p>
                                <p>• 📧 邮件反馈:<a href="mailto:[email protected]" class="smart-feed-link">[email protected]</a></p>
                                <p>• 🌟 GitHub项目:<a href="https://github.com/baianjo/Douyin-Smart-Feed-Assistant" target="_blank" class="smart-feed-link">点击访问</a></p>
                                <p>• 如果觉得有用,请给项目点个⭐Star支持一下!</p>
                                <p style="margin-top: 10px; font-size: 12px; color: #78716c;">反馈时请附上错误截图和日志,方便快速定位问题</p>
                            </div>
                        </div>

                        <div class="smart-feed-section">
                            <h3 style="margin: 0 0 15px 0; color: #1f2937;">⚖️ 免责声明</h3>
                            <div style="background: #fee2e2; padding: 15px; border-radius: 10px; font-size: 12px; line-height: 1.8; color: #991b1b;">
                                <p>• 本工具仅供学习和个人研究使用</p>
                                <p>• 使用本工具可能违反抖音服务条款</p>
                                <p>• 因使用本工具导致的账号问题,作者不承担任何责任</p>
                                <p>• 请遵守相关法律法规,理性使用AI技术</p>
                                <p>• API Key仅存储在本地浏览器,不会上传到任何服务器</p>
                            </div>
                        </div>
                    </div>
                </div>
            `;

            document.body.appendChild(UI.floatingButton);
            document.body.appendChild(UI.panel);

            UI.bindEvents();
        },

        bindEvents: () => {
            // ========== 1️⃣ 变量声明区(必须在最前面)==========
            let isDraggingBtn = false;
            let isDraggingPanel = false;
            let btnStartX = 0, btnStartY = 0, btnStartLeft = 0, btnStartTop = 0;
            let panelStartX = 0, panelStartY = 0, panelStartLeft = 0, panelStartTop = 0;
            let wasDragging = false;

            const config = loadConfig();

            // ========== 2️⃣ 工具函数定义区(提前定义,避免调用顺序问题)==========

            // 🆕 显示保存成功提示
            function showSaveNotice() {
                const notice = document.createElement('div');
                notice.style.cssText = `
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    background: #10b981;
                    color: white;
                    padding: 12px 20px;
                    border-radius: 8px;
                    font-size: 14px;
                    z-index: 9999999;
                    box-shadow: 0 4px 12px rgba(16,185,129,0.3);
                    animation: slideIn 0.3s ease;
                `;
                notice.textContent = '✓ 配置已保存';
                document.body.appendChild(notice);

                setTimeout(() => {
                    notice.style.animation = 'slideOut 0.3s ease';
                    setTimeout(() => notice.remove(), 300);
                }, 2000);
            }

            // 🆕 动态更新模型选项
            // ✅ 动态更新模型选项(从统一配置读取)
            function updateModelOptions(provider) {
                const modelSelect = document.getElementById('modelSelect');
                const modelSection = document.getElementById('modelSection');

                // 🔧 安全检查:如果元素不存在,直接返回
                if (!modelSelect || !modelSection) {
                    console.warn('[智能助手] ⚠️ 模型选择元素未找到,跳过初始化');
                    return;
                }

                // ✅ 从统一配置中读取模型列表
                const providerConfig = CONFIG.apiProviders[provider];
                const options = providerConfig?.models || [];

                if (provider === 'custom' || options.length === 0) {
                    // 自定义 API:替换为输入框
                    modelSelect.outerHTML = '<input type="text" class="smart-feed-input" id="modelSelect" placeholder="输入模型名称(如 gpt-4o-mini)">';
                    const smallEl = modelSection.querySelector('small');
                    if (smallEl) smallEl.style.display = 'none';
                } else {
                    // 预设 API:显示下拉选择
                    if (modelSelect.tagName !== 'SELECT') {
                        modelSelect.outerHTML = '<select class="smart-feed-select" id="modelSelect"></select>';
                    }

                    const newSelect = document.getElementById('modelSelect');
                    if (!newSelect) return;

                    // 清空现有选项
                    newSelect.innerHTML = '';

                    // 添加新选项
                    options.forEach(opt => {
                        const option = document.createElement('option');
                        option.value = opt.value;
                        option.textContent = opt.label;
                        newSelect.appendChild(option);
                    });

                    const smallEl = modelSection.querySelector('small');
                    if (smallEl) smallEl.style.display = 'block';

                    // 恢复之前保存的模型
                    const savedConfig = loadConfig();
                    if (savedConfig.customModel && options.find(o => o.value === savedConfig.customModel)) {
                        newSelect.value = savedConfig.customModel;
                    }

                    // 🆕 绑定保存事件
                    newSelect.addEventListener('change', async (e) => {
                        const cfg = loadConfig();
                        cfg.customModel = e.target.value;
                        await saveConfig(cfg);
                        showSaveNotice();
                    });
                }
            }

            // 🆕 防抖保存配置(用于手动保存按钮)
            const saveConfigDebounced = (() => {
                let timer = null;
                return (showNotice = false) => {
                    clearTimeout(timer);
                    timer = setTimeout(async () => {
                        const cfg = loadConfig();

                        // 读取所有配置项
                        const apiKeyEl = document.getElementById('apiKey');
                        const customEndpointEl = document.getElementById('customEndpoint');
                        const modelSelectEl = document.getElementById('modelSelect');
                        const apiProviderEl = document.getElementById('apiProvider');

                        if (apiKeyEl) cfg.apiKey = apiKeyEl.value;
                        if (customEndpointEl) cfg.customEndpoint = customEndpointEl.value;
                        if (modelSelectEl) cfg.customModel = modelSelectEl.value;
                        if (apiProviderEl) cfg.apiProvider = apiProviderEl.value;

                        cfg.promptLike = document.getElementById('promptLike')?.value || cfg.promptLike;
                        cfg.promptNeutral = document.getElementById('promptNeutral')?.value || cfg.promptNeutral;
                        cfg.promptDislike = document.getElementById('promptDislike')?.value || cfg.promptDislike;
                        cfg.minDelay = parseInt(document.getElementById('minDelay')?.value || cfg.minDelay);
                        cfg.maxDelay = parseInt(document.getElementById('maxDelay')?.value || cfg.maxDelay);
                        cfg.runDuration = parseInt(document.getElementById('runDuration')?.value || cfg.runDuration);
                        cfg.enableComments = document.getElementById('enableComments')?.checked || false;
                        cfg.skipProbability = parseInt(document.getElementById('skipProbability')?.value || cfg.skipProbability);
                        cfg.maxRetries = parseInt(document.getElementById('maxRetries')?.value || cfg.maxRetries);
                        cfg.watchBeforeLike = [
                            parseInt(document.getElementById('watchMin')?.value || 2),
                            parseInt(document.getElementById('watchMax')?.value || 8)
                        ];

                        await saveConfig(cfg);

                        if (showNotice) {
                            showSaveNotice();
                        }
                    }, 300);
                };
            })();

            // ========== 3️⃣ 恢复上次的配置 ==========
            document.getElementById('apiProvider').value = config.apiProvider || 'deepseek';
            document.getElementById('apiKey').value = config.apiKey || '';
            document.getElementById('customEndpoint').value = config.customEndpoint || '';

            if (config.selectedTemplate) {
                document.getElementById('template').value = config.selectedTemplate;
            }

            document.getElementById('minDelay').value = config.minDelay || 2;
            document.getElementById('maxDelay').value = config.maxDelay || 8;
            document.getElementById('runDuration').value = config.runDuration || 20;
            document.getElementById('watchMin').value = config.watchBeforeLike?.[0] || 2;
            document.getElementById('watchMax').value = config.watchBeforeLike?.[1] || 8;
            document.getElementById('skipProbability').value = config.skipProbability || 8;
            document.getElementById('maxRetries').value = config.maxRetries || 3;

            // ========== 4️⃣ 事件监听器绑定区 ==========

            // 悬浮按钮点击 - 展开/收起面板
            UI.floatingButton.addEventListener('click', async () => {
                if (wasDragging) {
                    console.log('[智能助手] ℹ️ 检测到拖动残留,忽略点击事件');
                    return;
                }

                const isHidden = UI.panel.style.display === 'none';
                UI.panel.style.display = isHidden ? 'block' : 'none';

                if (!isHidden) {
                    const cfg = loadConfig();
                    cfg.panelMinimized = true;
                    await saveConfig(cfg);
                    console.log('[智能助手] 💾 面板关闭,已保存状态');
                }
            });

            // 关闭按钮
            UI.panel.querySelector('.smart-feed-close').addEventListener('click', async () => {
                UI.panel.style.display = 'none';

                const cfg = loadConfig();
                cfg.panelMinimized = true;
                await saveConfig(cfg);
                console.log('[智能助手] 💾 面板关闭(X按钮),已保存状态');
            });

            // 拖动功能 - 悬浮按钮
            UI.floatingButton.addEventListener('mousedown', (e) => {
                if (e.button === 0) {
                    isDraggingBtn = true;
                    btnStartX = e.clientX;
                    btnStartY = e.clientY;
                    btnStartLeft = UI.floatingButton.offsetLeft;
                    btnStartTop = UI.floatingButton.offsetTop;

                    UI.floatingButton.style.transition = 'none';
                    if (UI.panel.style.display !== 'none') {
                        UI.panel.style.transition = 'none';
                    }

                    e.preventDefault();
                }
            });

            // 拖动功能 - 面板
            const header = UI.panel.querySelector('.smart-feed-header');
            header.addEventListener('mousedown', (e) => {
                if (e.target.tagName !== 'BUTTON') {
                    isDraggingPanel = true;
                    panelStartX = e.clientX;
                    panelStartY = e.clientY;
                    panelStartLeft = UI.panel.offsetLeft;
                    panelStartTop = UI.panel.offsetTop;

                    UI.panel.style.transition = 'none';
                }
            });

            // 拖动功能 - 移动监听
            document.addEventListener('mousemove', (e) => {
                if (isDraggingBtn) {
                    const dx = e.clientX - btnStartX;
                    const dy = e.clientY - btnStartY;
                    const newLeft = Math.max(0, Math.min(window.innerWidth - 60, btnStartLeft + dx));
                    const newTop = Math.max(0, Math.min(window.innerHeight - 60, btnStartTop + dy));

                    UI.floatingButton.style.left = newLeft + 'px';
                    UI.floatingButton.style.top = newTop + 'px';

                    if (UI.panel.style.display !== 'none') {
                        const panelLeft = Math.max(10, newLeft - 360);
                        const panelTop = Math.max(10, newTop);
                        UI.panel.style.left = panelLeft + 'px';
                        UI.panel.style.top = panelTop + 'px';
                    }
                }

                if (isDraggingPanel) {
                    const dx = e.clientX - panelStartX;
                    const dy = e.clientY - panelStartY;
                    const newLeft = Math.max(10, Math.min(window.innerWidth - 420, panelStartLeft + dx));
                    const newTop = Math.max(10, Math.min(window.innerHeight - 100, panelStartTop + dy));

                    UI.panel.style.left = newLeft + 'px';
                    UI.panel.style.top = newTop + 'px';
                }
            });

            // 拖动功能 - 释放监听
            document.addEventListener('mouseup', async () => {
                if (isDraggingBtn || isDraggingPanel) {
                    UI.floatingButton.style.transition = '';
                    UI.panel.style.transition = '';

                    const leftStr = UI.floatingButton.style.left;
                    const topStr = UI.floatingButton.style.top;
                    const currentX = parseInt(leftStr.replace('px', ''));
                    const currentY = parseInt(topStr.replace('px', ''));

                    const moveDistance = Math.sqrt(
                        Math.pow(currentX - btnStartLeft, 2) +
                        Math.pow(currentY - btnStartTop, 2)
                    );

                    if (moveDistance > 5) {
                        wasDragging = true;

                        if (!isNaN(currentX) && !isNaN(currentY)) {
                            const cfg = loadConfig();
                            cfg.panelPosition = { x: currentX, y: currentY };
                            cfg.panelMinimized = UI.panel.style.display === 'none';
                            await saveConfig(cfg);
                        }

                        setTimeout(() => {
                            wasDragging = false;
                        }, 300);
                    } else {
                        wasDragging = false;
                    }
                }

                isDraggingBtn = false;
                isDraggingPanel = false;
            });

            // 标签切换
            UI.panel.querySelectorAll('.smart-feed-tab').forEach(tab => {
                tab.addEventListener('click', () => {
                    const tabName = tab.dataset.tab;
                    UI.panel.querySelectorAll('.smart-feed-tab').forEach(t => t.classList.remove('active'));
                    tab.classList.add('active');
                    UI.panel.querySelectorAll('.smart-feed-tab-content').forEach(content => {
                        content.style.display = content.dataset.content === tabName ? 'block' : 'none';
                    });
                });
            });

            // API提供商切换
            document.getElementById('apiProvider').addEventListener('change', async (e) => {
                const provider = e.target.value;
                const cfg = loadConfig();
                cfg.apiProvider = provider;
                await saveConfig(cfg);
                showSaveNotice();

                updateModelOptions(provider);

                document.getElementById('customEndpointSection').style.display =
                    provider === 'custom' ? 'block' : 'none';
            });

            // 🔧 初始化:生成模型列表(添加延迟确保 DOM 完全准备好)
            setTimeout(() => {
                updateModelOptions(config.apiProvider);
                document.getElementById('customEndpointSection').style.display =
                    config.apiProvider === 'custom' ? 'block' : 'none';
            }, 100);

            // 帮助按钮
            UI.panel.querySelectorAll('.smart-feed-help').forEach(help => {
                help.addEventListener('click', () => {
                    const tab = UI.panel.querySelector('.smart-feed-tab[data-tab="about"]');
                    tab.click();
                });
            });

            // 测试API按钮
            document.getElementById('testApiBtn').addEventListener('click', async () => {
                const btn = document.getElementById('testApiBtn');
                const originalText = btn.textContent;

                // 🆕 自动切换到日志标签页
                const logTab = UI.panel.querySelector('.smart-feed-tab[data-tab="log"]');
                if (logTab) {
                    logTab.click();
                    // 清空旧日志
                    document.getElementById('logContainer').innerHTML = '';
                }

                btn.textContent = '测试中...';
                btn.disabled = true;

                // 🆕 实时读取当前表单值(不依赖 loadConfig)
                const testConfig = {
                    apiKey: document.getElementById('apiKey').value.trim(),
                    apiProvider: document.getElementById('apiProvider').value,
                    customEndpoint: document.getElementById('customEndpoint').value.trim(),
                    customModel: document.getElementById('modelSelect').value.trim()
                };

                // 🆕 详细的前置检查
                UI.log('🔍 执行前置检查...', 'info', 'debug');

                if (!testConfig.apiKey) {
                    UI.log('❌ 检测到空的 API Key!', 'error');
                    UI.log('💡 请在"基础设置"中填写 API Key 后再测试', 'warning');
                    btn.textContent = originalText;
                    btn.disabled = false;
                    return;
                }

                if (testConfig.apiProvider === 'custom' && !testConfig.customEndpoint) {
                    UI.log('⚠️ 选择了"自定义 API"但未填写 API 地址', 'warning');
                    UI.log('💡 请填写自定义 API 地址,或切换到预设提供商', 'warning');
                }

                UI.log('✅ 前置检查通过,开始测试...', 'success');
                UI.log('', 'info');

                const result = await AIService.testAPI(testConfig);

                // 🆕 移除自动弹窗,改为日志提示
                if (result.success) {
                    UI.log('', 'success');
                    UI.log('🎉 测试成功!可以开始使用了', 'success');
                    UI.log('💡 如需修改配置,请在"基础设置"标签页调整', 'info');
                } else {
                    UI.log('', 'error');
                    UI.log('💊 故障排查建议:', 'warning');
                    UI.log('  1. 检查 API Key 是否正确(注意前后空格)', 'warning');
                    UI.log('  2. 确认选择的提供商和实际 Key 匹配', 'warning');
                    UI.log('  3. 检查网络是否能访问对应 API 地址', 'warning');
                    UI.log('  4. 查看上方响应体中的具体错误信息', 'warning');
                }

                btn.textContent = originalText;
                btn.disabled = false;
            });

            // 模板切换
            document.getElementById('template').addEventListener('change', async (e) => {
                const templateName = e.target.value;
                const cfg = loadConfig();

                cfg.selectedTemplate = templateName;

                if (templateName && CONFIG.templates[templateName]) {
                    const tpl = CONFIG.templates[templateName];
                    document.getElementById('promptLike').value = tpl.like;
                    document.getElementById('promptNeutral').value = tpl.neutral;
                    document.getElementById('promptDislike').value = tpl.dislike;

                    cfg.promptLike = tpl.like;
                    cfg.promptNeutral = tpl.neutral;
                    cfg.promptDislike = tpl.dislike;
                }

                await saveConfig(cfg);
                showSaveNotice();
            });

            // 为所有输入框添加失焦自动保存
            const inputs = ['apiKey', 'customEndpoint',
                           'promptLike', 'promptNeutral', 'promptDislike',
                           'minDelay', 'maxDelay', 'runDuration',
                           'watchMin', 'watchMax', 'skipProbability', 'maxRetries'];

            inputs.forEach(id => {
                const el = document.getElementById(id);
                if (el) {
                    el.addEventListener('blur', async () => {
                        const cfg = loadConfig();

                        if (el.type === 'checkbox') {
                            cfg[id] = el.checked;
                        } else if (id === 'watchMin' || id === 'watchMax') {
                            cfg.watchBeforeLike = [
                                parseInt(document.getElementById('watchMin').value),
                                parseInt(document.getElementById('watchMax').value)
                            ];
                        } else {
                            cfg[id] = el.type === 'number' ? parseInt(el.value) : el.value;
                        }

                        await saveConfig(cfg);
                        showSaveNotice();
                    });
                }
            });

            // 添加手动保存按钮
            const saveBtn = document.createElement('button');
            saveBtn.className = 'smart-feed-button smart-feed-button-secondary';
            saveBtn.textContent = '💾 保存当前配置';
            saveBtn.style.marginTop = '10px';
            saveBtn.onclick = () => saveConfigDebounced(true);

            const basicContent = document.querySelector('[data-content="basic"]');
            if (basicContent) {
                basicContent.appendChild(saveBtn);
            }

            // 开始/停止按钮
            document.getElementById('startBtnTop').addEventListener('click', () => {
                if (Controller.isRunning) {
                    Controller.stop();
                } else {
                    Controller.start();
                }
            });

            // 清空日志
            document.getElementById('clearLog').addEventListener('click', () => {
                document.getElementById('logContainer').innerHTML = '';
                UI.log('日志已清空', 'info');
            });

            // 🆕 折叠帮助框功能
            const helpBox = document.querySelector('.collapsible-help-box');
            if (helpBox) {
                const header = helpBox.querySelector('.help-header');
                const btn = helpBox.querySelector('.help-toggle-btn');

                header.addEventListener('click', () => {
                    helpBox.classList.toggle('expanded');
                    btn.textContent = helpBox.classList.contains('expanded') ? '收起 ▲' : '展开 ▼';
                });
            }
        },

        log: (message, type = 'info', level = 'normal') => {
            const logContainer = document.getElementById('logContainer');
            if (!logContainer) return;

            // 🆕 检查详细日志开关(保持原有的防御性编程风格)
            const verboseCheckbox = document.getElementById('verboseLog');
            const isVerboseMode = verboseCheckbox?.checked || false;

            // 🆕 如果是调试信息且未开启详细模式,则跳过
            if (level === 'debug' && !isVerboseMode) {
                return;
            }

            const item = document.createElement('div');
            item.className = 'smart-feed-log-item';

            const colors = {
                info: '#64748b',
                success: '#10b981',
                warning: '#f59e0b',
                error: '#ef4444'
            };

            // 🆕 检测是否为可折叠的长文本
            let displayText = message;
            const isLongText = message.length > 300;
            const isStructuredData = message.includes('{') || message.includes('JSON') ||
                                     message.includes('请求体') || message.includes('响应体');

            // ✅ 使用 DOM API 而非 innerHTML,彻底避免 XSS
            const timeSpan = document.createElement('span');
            timeSpan.className = 'smart-feed-log-time';
            timeSpan.textContent = new Date().toLocaleTimeString();

            const textSpan = document.createElement('span');
            textSpan.className = 'smart-feed-log-text';
            textSpan.style.color = colors[type];

            // 如果是长文本且包含结构化数据,创建可折叠组件
            if (isLongText && isStructuredData) {
                const wrapper = document.createElement('span');
                wrapper.className = 'collapsible-log';

                // 预览部分
                const preview = document.createElement('span');
                preview.className = 'log-preview';
                preview.textContent = message.substring(0, 120).replace(/\n/g, ' ') + '...'; // textContent 自动转义

                // 展开按钮
                const expandBtn = document.createElement('button');
                expandBtn.className = 'expand-btn';
                expandBtn.addEventListener('click', function() {
                    this.parentElement.classList.toggle('expanded');
                });

                // 完整内容
                const fullDiv = document.createElement('div');
                fullDiv.className = 'log-full';
                fullDiv.textContent = message; // textContent 自动转义

                // 组装
                wrapper.appendChild(preview);
                wrapper.appendChild(expandBtn);
                wrapper.appendChild(fullDiv);
                textSpan.appendChild(wrapper);
            } else {
                // 普通文本直接设置
                textSpan.textContent = displayText;
            }

            // 组装日志项
            item.appendChild(timeSpan);
            item.appendChild(textSpan);

            logContainer.appendChild(item);
            logContainer.scrollTop = logContainer.scrollHeight;  // 🔧 保留原有的自动滚动

            // 🔧 保留原有的内存管理逻辑
            while (logContainer.children.length > 400) {
                logContainer.removeChild(logContainer.firstChild);
            }
        },

        updateStats: (stats) => {
            document.getElementById('statTotal').textContent = stats.total;
            document.getElementById('statLiked').textContent = stats.liked;
            document.getElementById('statNeutral').textContent = stats.neutral;
            document.getElementById('statDisliked').textContent = stats.disliked;
        }
    };

    // ==================== 主控制器 ====================
    const Controller = {
        isRunning: false,
        startTime: null,
        consecutiveErrors: 0,
        stats: {
            total: 0,
            liked: 0,
            neutral: 0,
            disliked: 0,
            skipped: 0,
            errors: 0
        },

        cleanup: async () => {
            try {
                UI.log('🧹 正在清理运行状态...', 'info');



                // 确保视频处于播放状态(避免卡在暂停)
                const video = document.querySelector('video');
                if (video && video.paused) {
                    video.play().catch(e => {
                        console.warn('[智能助手] 视频恢复播放失败:', e);
                    });
                }

                UI.log('✅ 清理完成', 'success');
            } catch (e) {
                console.warn('[智能助手] 清理过程出错:', e);
                UI.log('⚠️ 清理时出现异常(可忽略)', 'warning');
            }
        },

        start: async () => {
            const config = loadConfig();

            // 验证配置
            if (!config.apiKey) {
                alert('❌ 请先配置API Key!\n\n点击右上角"关于"标签查看获取教程');
                return;
            }

            // 🆕 防止重复启动
            if (Controller.isRunning) {
                UI.log('⚠️ 脚本已在运行中', 'warning');
                return;
            }

            Controller.isRunning = true;
            Controller.startTime = Date.now();
            Controller.consecutiveErrors = 0;
            Controller.stats = { total: 0, liked: 0, neutral: 0, disliked: 0, skipped: 0, errors: 0 };

            // 更新UI
            const btn = document.getElementById('startBtnTop');
            btn.textContent = '⏸ 停止';
            btn.className = 'smart-feed-start-btn running';
            UI.floatingButton.classList.add('running');
            UI.floatingButton.title = '运行中...点击查看详情';

            UI.log('========================================', 'info');
            UI.log('🚀 智能助手启动成功', 'success');
            UI.log(`📋 运行配置: ${config.judgeMode === 'single' ? '单次调用' : '双重判定'} | 间隔${config.minDelay}-${config.maxDelay}秒 | 时长${config.runDuration}分钟`, 'info');
            UI.log('========================================', 'info');

            // 主循环
            while (Controller.isRunning) {
                try {
                    // 🆕 每次循环开始立即检查
                    if (!Controller.isRunning) {
                        UI.log('⏹️ 检测到停止信号,退出循环', 'info');
                        break;
                    }

                    // 检查运行时长
                    const elapsed = (Date.now() - Controller.startTime) / 1000 / 60;
                    if (elapsed >= config.runDuration) {
                        UI.log('⏰ 已达到设定运行时长,自动停止', 'warning');
                        break;
                    }

                    Controller.stats.total++;
                    UI.updateStats(Controller.stats);

                    UI.log(`\n━━━━━━━━ 视频 #${Controller.stats.total} ━━━━━━━━`, 'info');

                    // 随机跳过判断
                    if (Math.random() * 100 < config.skipProbability) {
                        Controller.stats.skipped++;
                        UI.log('⏭️ 随机跳过此视频', 'info');
                        Utils.pressKey('ArrowDown');
                        await Utils.randomDelay(config.minDelay, config.maxDelay);
                        continue; // 🆕 直接 continue,循环开头会再次检查 isRunning
                    }

                    // 获取当前视频信息
                    UI.log('📥 正在分析当前视频...', 'info');
                    const videoInfo = await VideoExtractor.getCurrentVideoInfo(config);

                    // 🆕 异步操作后立即检查
                    if (!Controller.isRunning) {
                        UI.log('⏹️ 检测到停止信号,退出循环', 'info');
                        break;
                    }

                    if (!videoInfo) {
                        UI.log('⚠️ 无法定位当前视频,尝试恢复...', 'warning');
                        Controller.stats.errors++;           // 总错误数(用于统计)
                        Controller.consecutiveErrors++;      // 🆕 累加连续错误

                        UI.log('🔄 执行恢复操作...', 'info');
                        Utils.pressKey('ArrowDown');
                        await Utils.randomDelay(2, 2.5);

                        if (!Controller.isRunning) break;

                        Utils.pressKey('ArrowDown');
                        await Utils.randomDelay(2, 2.5);

                        // 🆕 改为检查连续错误
                        if (Controller.consecutiveErrors >= 5) {
                            UI.log('❌ 连续失败5次,脚本可能已失效', 'error');
                            UI.log('💡 最常见原因:抖音更新了页面结构,导致DOM选择器失效', 'warning');
                            UI.log('📧 请将此问题反馈给作者:[email protected]', 'warning');
                            UI.log('🌟 或访问GitHub提交Issue(点击面板"关于"标签查看链接)', 'info');

                            alert('⚠️ 脚本可能已失效\n\n' +
                                  '【最可能的原因】\n' +
                                  '✗ 抖音更新了页面结构(DOM选择器失效)\n\n' +
                                  '【其他可能原因】\n' +
                                  '• 页面长时间运行导致DOM混乱\n' +
                                  '• 网络不稳定\n\n' +
                                  '【建议操作】\n' +
                                  '1. 先刷新页面后重试\n' +
                                  '2. 如果问题持续,请反馈给作者\n\n' +
                                  '📧 反馈邮箱:[email protected]\n' +
                                  '🌟 GitHub:查看面板"关于"标签');

                            Controller.stop();
                            break;
                        }

                        continue;
                    }

                    // 直播直接跳过
                    if (videoInfo.isLive) {
                        UI.log('🔴 检测到直播,直接跳过', 'warning');
                        Utils.pressKey('ArrowDown');
                        await Utils.randomDelay(2, 3);

                        if (!Controller.isRunning) break;

                        Controller.stats.skipped++;
                        UI.updateStats(Controller.stats);
                        continue;
                    }


                    // 验证标题有效性
                    if (!videoInfo.title || videoInfo.title.length < 3) {
                        UI.log('⚠️ 标题信息不足,跳过', 'warning');
                        Controller.stats.errors++;
                        Controller.consecutiveErrors++; // 🆕 标题提取失败也算连续错误

                        // 🆕 如果标题、作者、标签都为空,高度怀疑DOM选择器失效
                        if (!videoInfo.title && !videoInfo.author && videoInfo.tags.length === 0) {
                            UI.log('⚠️ 完全无法提取视频信息(可能是DOM选择器失效)', 'warning');

                            // 🆕 连续3次完全提取失败,立即判定为失效
                            if (Controller.consecutiveErrors >= 3) {
                                UI.log('❌ 连续3次完全无法提取信息,判定脚本已失效', 'error');
                                UI.log('💡 抖音很可能更新了页面HTML结构', 'warning');
                                UI.log('📧 请反馈此问题:[email protected]', 'warning');
                                UI.log('💊 反馈时请说明发现时间和浏览器版本', 'info');

                                alert('⚠️ 检测到DOM选择器失效\n\n' +
                                      '脚本连续3次无法识别视频信息,\n' +
                                      '这通常意味着抖音更新了页面HTML结构。\n\n' +
                                      '请将此问题反馈给作者:\n' +
                                      '📧 [email protected]\n\n' +
                                      '【反馈时请提供】\n' +
                                      '• 发现时间(如 2025-01-15)\n' +
                                      '• 浏览器版本(按F12查看Console)\n' +
                                      '• 视频是否能正常播放');

                                Controller.stop();
                                break;
                            }
                        }

                        Utils.pressKey('ArrowDown');
                        await Utils.randomDelay(2, 3);

                        if (!Controller.isRunning) break;

                        continue;
                    }
                    // 🆕 成功提取有效视频信息 → 重置连续错误计数
                    Controller.consecutiveErrors = 0;

                    const dossier = VideoExtractor.buildDossier(videoInfo);

                    // AI判断(带重试机制)
                    let retries = 0;
                    let result = null;

                    while (retries < config.maxRetries && !result && Controller.isRunning) {
                        try {
                            UI.log(`🤖 AI分析中${retries > 0 ? ` (重试 ${retries}/${config.maxRetries})` : ''}...`, 'info');
                            result = await AIService.judge(dossier, config);

                            // 🆕 成功后也检查
                            if (!Controller.isRunning) {
                                UI.log('⏹️ AI分析完成,但检测到停止信号', 'info');
                                break;
                            }
                        } catch (e) {
                            // 🆕 失败后立即检查
                            if (!Controller.isRunning) {
                                UI.log('⏹️ 检测到停止信号,中止重试', 'info');
                                break;
                            }

                            retries++;
                            UI.log(`❌ AI调用失败 (${retries}/${config.maxRetries}): ${e.message}`, 'error');

                            if (retries < config.maxRetries) {
                                const waitTime = Math.pow(2, retries);
                                UI.log(`⏳ 等待 ${waitTime} 秒后重试...`, 'warning');
                                await Utils.randomDelay(waitTime, waitTime + 2);

                                // 🆕 等待后再检查
                                if (!Controller.isRunning) {
                                    UI.log('⏹️ 等待期间检测到停止信号', 'info');
                                    break;
                                }
                            }
                        }
                    }

                    // 🆕 退出重试循环后检查
                    if (!Controller.isRunning) {
                        UI.log('⏹️ 退出重试循环,检测到停止信号', 'info');
                        break;
                    }

                    if (!result) {
                        Controller.stats.errors++;
                        UI.log('💀 多次重试失败,跳过该视频', 'error');
                        Utils.pressKey('ArrowDown');
                        await Utils.randomDelay(2, 3);

                        if (!Controller.isRunning) break;

                        continue;
                    }

                    // 统计并执行操作
                    const actionMap = { like: '点赞 👍', neutral: '忽略 ➡️', dislike: '不感兴趣 👎' };
                    Controller.stats[result.action === 'like' ? 'liked' : result.action === 'dislike' ? 'disliked' : 'neutral']++;

                    UI.log(`✨ AI判断: ${actionMap[result.action]}`, 'success');
                    UI.log(`💭 理由: ${result.reason}`, 'info');

                    await VideoExtractor.executeAction(result.action, config);

                    // 🆕 操作后检查
                    if (!Controller.isRunning) {
                        UI.log('⏹️ 操作完成,但检测到停止信号', 'info');
                        break;
                    }

                    UI.updateStats(Controller.stats);

                    // 随机延迟后进入下一轮
                    const delay = Math.random() * (config.maxDelay - config.minDelay) + config.minDelay;
                    UI.log(`⏱️ 等待 ${delay.toFixed(1)} 秒后继续...`, 'info');
                    await Utils.randomDelay(config.minDelay, config.maxDelay);

                } catch (e) {
                    // 🆕 异常处理中也检查
                    if (!Controller.isRunning) {
                        UI.log('⏹️ 异常处理中检测到停止信号', 'info');
                        break;
                    }

                    Controller.stats.errors++;
                    Controller.consecutiveErrors++; // 🆕 异常也算连续失败
                    UI.log(`💥 发生未预期错误: ${e.message}`, 'error');
                    console.error('[智能助手]', e);

                    UI.log('🔄 尝试自动恢复...', 'warning');
                    Utils.pressKey('ArrowDown');
                    await Utils.randomDelay(3, 5);
                }
            }

            // 🆕 确保循环退出后调用 stop
            Controller.stop();
        },

        stop: async () => { // ⚠️ 注意这里改成了 async
            if (!Controller.isRunning) return;

            Controller.isRunning = false;

            // ✅ 执行清理
            await Controller.cleanup();

            // 更新UI
            const btn = document.getElementById('startBtnTop');
            if (btn) {
                btn.textContent = '▶ 开始';
                btn.className = 'smart-feed-start-btn';
            }
            UI.floatingButton.classList.remove('running');
            UI.floatingButton.title = '点击打开智能助手';

            // 显示统计
            const stats = Controller.stats;
            UI.log('\n========================================', 'info');
            UI.log('🏁 运行结束', 'success');
            UI.log(`📊 统计数据:`, 'info');
            UI.log(`   总计: ${stats.total} 个视频`, 'info');
            UI.log(`   点赞 👍: ${stats.liked} | 忽略 ➡️: ${stats.neutral} | 不感兴趣 👎: ${stats.disliked}`, 'info');
            UI.log(`   跳过 ⏭️: ${stats.skipped} | 错误 ❌: ${stats.errors}`, 'info');

            const runTime = Controller.startTime ? (Date.now() - Controller.startTime) / 1000 / 60 : 0;
            UI.log(`⏱️ 运行时长: ${runTime.toFixed(1)} 分钟`, 'info');
            UI.log('========================================', 'info');
        },
    };

    // ==================== 初始化 ====================
    const init = () => {
        // 检查是否在抖音网页版
        if (!window.location.hostname.includes('douyin.com')) {
            return;
        }

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

        // 延迟创建UI,确保页面完全加载
        setTimeout(() => {
            try {
                UI.create();
                console.log('[智能助手] 已加载成功');
                console.log('[智能助手] 开发者:请查看代码开头的注释了解维护说明');
            } catch (e) {
                console.error('[智能助手] 初始化失败:', e);
            }
        }, 2000);
    };

    init();
})();