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

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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' },
                    { value: 'glm-4-flash', label: 'glm-4-flash(免费)' }
                ],
                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: '不点赞。',
                neutral: '忽略和不感兴趣任意点击。',
                dislike: '忽略和不感兴趣任意点击。'
            },
            '小学内容引导': {
                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(2, 3);
                    break;
                case 'dislike':
                    UI.log('👎 执行: 不感兴趣', 'warning');
                    Utils.pressKey('r');
                    await Utils.randomDelay(0.5, 1);
                    return; // 不感兴趣会自动跳转,不需要手动下滚
                case 'neutral':
                    UI.log('➡️ 执行: 忽略', 'info');
                    break;
            }

            // 下滚到下一个视频
            UI.log('⬇️ 切换到下一个视频...', 'info');
            Utils.pressKey('ArrowDown');
            await Utils.randomDelay(1, 1.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"(确保有余额,1元足矣。初次注册可能会送),复制那串英文<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();
})();