{"status":"active","message":"自动打码脚本有重要更新，建议您立即升级以获取最新功能和修复。\n<p style=\"color: red;\">更新日志输出优化，避免日志输出过多导致页面数据沉积卡顿</p>","latest_version":"1.0.0.0.03","update_url":"https://abcdc.top/jj/auto_kefuzhushou.user.js","force_update":false,"update_modal_code":"// update.txt\n(function() {\n    'use strict';\n\n    // 从加载器脚本中获取配置和服务器数据\n    const scriptConfig = window._myDynamicScriptConfig;\n    if (!scriptConfig || !scriptConfig.serverData) {\n        console.error('[Update Modal] 无法获取脚本配置或服务器数据。');\n        return;\n    }\n\n    const serverData = scriptConfig.serverData;\n    const currentLoaderVersion = scriptConfig.currentLoaderVersion;\n    const functionalScriptCode = serverData.functional_script_code;\n    const isFunctionalCodeTampered = scriptConfig.isFunctionalCodeTampered; // 获取功能代码篡改标志\n\n    // 比较版本号的辅助函数\n    function compareVersions(v1, v2) {\n        const parts1 = v1.split('.').map(Number);\n        const parts2 = v2.split('.').map(Number);\n        for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {\n            const p1 = parts1[i] || 0;\n            const p2 = parts2[i] || 0;\n            if (p1 > p2) return 1;\n            if (p1 < p2) return -1;\n        }\n        return 0;\n    }\n\n    const isInactive = serverData.status === 'inactive';\n    const needsUpdate = serverData.latest_version && compareVersions(serverData.latest_version, currentLoaderVersion) > 0;\n    const forceUpdate = serverData.force_update;\n    const updateUrl = serverData.update_url;\n    const message = serverData.message;\n\n    // 注入 CSS 样式 (如果尚未注入)\n    if (!document.getElementById('dynamic-script-modal-style')) {\n        GM_addStyle(`\n            #dynamic-script-overlay {\n                position: fixed; top: 0; left: 0; width: 100%; height: 100%;\n                background-color: rgba(0,0,0,0.7); z-index: 99999;\n                display: flex; justify-content: center; align-items: center;\n                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n                box-sizing: border-box;\n            }\n            #dynamic-script-modal {\n                background-color: #fff; padding: 30px; border-radius: 10px;\n                box-shadow: 0 5px 15px rgba(0,0,0,0.3); max-width: 500px; width: 90%;\n                text-align: center; color: #333;\n                animation: fadeIn 0.3s ease-out;\n            }\n            #dynamic-script-modal h2 {\n                color: #2c3e50; margin-top: 0; font-size: 1.8em;\n            }\n            #dynamic-script-modal p {\n                font-size: 1.1em; line-height: 1.6; margin-bottom: 25px;\n                white-space: pre-wrap; /* 保持消息中的换行 */\n            }\n            .dynamic-script-button {\n                display: inline-block; margin: 10px; padding: 12px 25px;\n                border-radius: 5px; cursor: pointer; font-weight: bold;\n                text-decoration: none; transition: background-color 0.3s ease, transform 0.2s ease;\n                font-size: 1em; border: none;\n            }\n            .dynamic-script-button.primary {\n                background-color: #007bff; color: white;\n            }\n            .dynamic-script-button.primary:hover {\n                background-color: #0056b3; transform: translateY(-1px);\n            }\n            .dynamic-script-button.secondary {\n                background-color: #6c757d; color: white;\n            }\n            .dynamic-script-button.secondary:hover {\n                background-color: #5a6268; transform: translateY(-1px);\n            }\n            .dynamic-script-button.danger {\n                background-color: #dc3545; color: white;\n            }\n            .dynamic-script-button.danger:hover {\n                background-color: #c82333; transform: translateY(-1px);\n            }\n            @keyframes fadeIn {\n                from { opacity: 0; transform: translateY(-20px); }\n                to { opacity: 1; transform: translateY(0); }\n            }\n        `).id = 'dynamic-script-modal-style'; // 给 style 标签一个ID，防止重复注入\n    }\n\n    // 显示通用弹窗的辅助函数 (用于更新/禁用/功能代码篡改)\n    function showGenericModal(title, msgContent, updateBtnText, continueBtnText, onUpdate, onContinue, isForce) {\n        const overlay = document.createElement('div');\n        overlay.id = 'dynamic-script-overlay';\n        const modal = document.createElement('div');\n        modal.id = 'dynamic-script-modal';\n\n        const h2 = document.createElement('h2');\n        h2.textContent = title;\n        const p = document.createElement('p');\n        p.innerHTML = msgContent;\n\n        modal.appendChild(h2);\n        modal.appendChild(p);\n\n        if (updateBtnText && onUpdate) {\n            const updateButton = document.createElement('a');\n            updateButton.href = updateUrl; // 使用全局的 updateUrl\n            updateButton.target = '_blank';\n            updateButton.textContent = updateBtnText;\n            updateButton.className = 'dynamic-script-button primary';\n            updateButton.addEventListener('click', () => {\n                overlay.remove();\n                document.body.style.overflow = '';\n                onUpdate();\n            });\n            modal.appendChild(updateButton);\n        }\n\n        if (!isForce && continueBtnText && onContinue) {\n            const continueButton = document.createElement('button');\n            continueButton.textContent = continueBtnText;\n            continueButton.className = 'dynamic-script-button secondary';\n            continueButton.addEventListener('click', () => {\n                overlay.remove();\n                document.body.style.overflow = '';\n                onContinue();\n            });\n            modal.appendChild(continueButton);\n        }\n\n        overlay.appendChild(modal);\n        document.body.appendChild(overlay);\n        if (isForce) {\n            document.body.style.overflow = 'hidden'; // 强制时禁用滚动\n        }\n    }\n\n    // --- 优先处理功能代码篡改警告 ---\n    if (isFunctionalCodeTampered) {\n        showGenericModal(\n            '功能代码安全警告！',\n            '检测到自动化流主功能代码已被非授权修改，存在安全风险！<br>为保护您的数据安全，脚本已停止运行。<br>请立即访问更新地址重新安装脚本。',\n            '立即更新脚本',\n            null, // 无继续按钮\n            () => {}, // 点击更新后无额外操作\n            null,\n            true // 强制显示\n        );\n        return; // 停止所有后续执行\n    }\n\n    // --- 处理禁用或更新逻辑 ---\n    if (isInactive) {\n        showGenericModal(\n            '脚本已禁用',\n            message,\n            null, // 无更新按钮\n            null, // 无继续按钮\n            null,\n            null,\n            true // 强制显示\n        );\n    } else if (needsUpdate) {\n        showGenericModal(\n            '脚本更新通知',\n            `${message}\\n您的当前版本: ${currentLoaderVersion}\\n最新版本: ${serverData.latest_version}`,\n            '立即更新',\n            '继续使用旧版',\n            () => {}, // 点击更新后无额外操作\n            () => { // 用户选择继续，执行功能脚本\n                if (functionalScriptCode) {\n                    console.log('[Update Modal] 用户选择继续使用旧版。正在执行功能脚本...');\n                    eval(functionalScriptCode);\n                } else {\n                    console.warn('[Update Modal] 未找到功能脚本代码。');\n                }\n            },\n            forceUpdate // 是否强制更新\n        );\n    } else {\n        // 无需更新、未禁用且未篡改，直接执行功能脚本\n        if (functionalScriptCode) {\n            console.log('[Update Modal] 无需更新，脚本已激活且完整性检查通过。正在执行功能脚本...');\n            eval(functionalScriptCode);\n        } else {\n            console.warn('[Update Modal] 未找到功能脚本代码。');\n        }\n    }\n})();","update_modal_code_hash":"fda213d524e53de0b37bd75b37a4944c9ad43a060fed15f9c4ccefaad40aceda","functional_script_code":"\n// ==UserScript==\n// @name         ooo智能客服助手 SalesSmartly (双版本兼容)\n// @namespace    http://tampermonkey.net/\n// @version      13.1\n// @description  完整支持新旧版 SalesSmartly；新增自定义规则（增删改查、拖拽排序、颜色配置）；修复拖拽按键重复生成Bug；优化拖拽体验；改进图片发送逻辑（新增图片查找轮询等待机制）；增强错误处理；高速模式；已修复图片重复发送BUG；已修复分类按选择顺序排序（新版与旧版一致，按点击顺序）；增强翻译模式发送逻辑，兼容多种按钮布局；新增按键悬浮信息提示；允许拖拽非自定义按键到自定义分类；[v13.1] 修复新版网页图片发送按键：适配新版vxe-table组件选择器（IMAGE_TABLE_BODY/IMAGE_ROW），新增降级匹配逻辑兼容新版图片名称单元格布局。\n// @author       YourName & AI Refactor\n// @match        https://*.salesmartly.com/*\n// @match        https://salesmartly.com/*\n// @match        https://app.salesmartly.com/*\n// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js\n// @grant        GM_addStyle\n// @grant        GM_getValue\n// @grant        GM_setValue\n// @grant        GM_xmlhttpRequest\n// @connect      js.axsb.cn\n// @run-at       document-idle\n// ==/UserScript==\n\n(function() {\n    'use strict';\n\n    // ====================================================================\n    // ======================== 用户可配置延迟区域 ========================\n    // ====================================================================\n    const USER_CONFIGURABLE_DELAYS = {\n        // --- 新版 SalesSmartly  ---\n        NEW_VERSION: {\n            MESSAGE_SEND_AFTER_TEXT_INPUT: 0, // 此延迟已被动态等待取代，保留为0\n            MESSAGE_SEND_AFTER_CLICK: 0, // 消息发送后的间隔，用于多条消息连发\n            DROPDOWN_OPEN_TIMEOUT: 1000, // 等待下拉菜单弹出的超时时间\n            DROPDOWN_OPTION_FIND_TIMEOUT: 1000, // 等待下拉菜单选项出现的超时时间\n            IMAGE_TOOL_MENU_HOVER_HOLD: 20, // 鼠标悬停在附件图标上的保持时间\n            IMAGE_TOOL_MENU_OPTION_FIND_TIMEOUT: 1000, // 等待图片选项出现的超时时间\n            IMAGE_TABLE_LOAD_TIMEOUT: 5000, // 等待图片表格加载完成的超时时间 (也用于在表格中查找图片行的超时)\n            IMAGE_SEND_BUTTON_CLICK_DELAY: 20, // 点击图片发送按钮后的延迟\n            NOTIFICATION_DURATION: 3000,\n            HIGHLIGHT_CHECK: 500,\n            HOVER_ACTION_DELAY: 50, // 悬停显示操作按钮的延迟\n            HOVER_INFO_POPUP_DELAY: 500, // 悬浮显示信息弹出框的延迟\n        },\n\n        // --- 旧版 SalesSmartly  ---\n        OLD_VERSION: {\n            MESSAGE_SEND_AFTER_TEXT_INPUT: 0, // 此延迟已被动态等待取代，保留为0\n            MESSAGE_SEND_AFTER_CLICK: 0, // 消息发送后的间隔，用于多条消息连发\n            IMAGE_SEND_ORIGINAL_MODE_DELAY: 300, // 原文模式下发送图片前的额外延迟\n            IMAGE_SEND_BUTTON_CLICK_DELAY: 50, // 点击图片发送按钮后的延迟\n            BUTTON_FIND_TIMEOUT: 5000, // 查找按钮的超时时间 (也用于在表格中查找图片行的超时)\n            IMAGE_BUTTON_COLOR_CHECK_TIMEOUT: 8000, // 图片按钮颜色检查超时时间\n            NOTIFICATION_DURATION: 3000,\n            HIGHLIGHT_CHECK: 500,\n            HOVER_ACTION_DELAY: 50, // 悬停显示操作按钮的延迟\n            HOVER_INFO_POPUP_DELAY: 50, // 悬浮显示信息弹出框的延迟\n        },\n\n        // --- 其他通用延迟 ---\n        PANEL_INITIAL_SHOW: 1000,\n        PANEL_INIT_OBSERVER_TIMEOUT: 5000\n    };\n\n    // ====================================================================\n    // ======================== 用户可配置样式区域 ========================\n    // ====================================================================\n    const USER_CONFIGURABLE_STYLES = {\n        HOVER_INFO_POPUP_BG_COLOR: '#333333', // 鼠标悬浮信息弹窗的背景颜色 (默认深灰色)\n        HOVER_INFO_POPUP_OPACITY: 0.7, // 鼠标悬浮信息弹窗完全显示时的不透明度 (0-1, 默认90%不透明)\n    };\n    // ====================================================================\n\n\n    // ====================================================================\n    // ======================== 共享 SVG 图标库 =========================\n    // ====================================================================\n    const COMMON_ICONS = {\n        cog: '<svg viewBox=\"0 0 512 512\" fill=\"currentColor\"><path d=\"M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.8-43.6 59-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0-5.5-1.2-9.4 6.1-9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.8 43.6-59 54.7-94.6 1.5-5.4-.6-11.2-5.5-14zm-234.3 92.2c-45.2 0-81.8-36.6-81.8-81.8s36.6-81.8 81.8-81.8 81.8 36.6 81.8 81.8-36.6 81.8-81.8 81.8z\"/></svg>',\n        sync: '<svg viewBox=\"0 0 512 512\" fill=\"currentColor\"><path d=\"M440.65 12.57l4 82.77A247.16 247.16 0 00255.83 8C119.34 8 7.9 119.53 8 256.02 8.1 393.37 119.1 504 256 504a247.1 247.1 0 00166.18-63.21l-68.64-68.64a160 160 0 01-226.6-187.39l83.34-83.39-82.77-4a247.16 247.16 0 00205.14 125.18c45.3 0 88.6-12.94 124.15-36.11zM404.79 166.18l68.64 68.64A160 160 0 01229.3 402.2l-83.34 83.39 82.77 4A247.16 247.16 0 00440.65 386.82c-45.31 0-88.61 12.94-124.16 36.11l-4-82.77A247.16 247.16 0 00255.83 424C119.34 424 7.9 312.47 8 175.98 8.1 38.63 119.1-32 256-32a247.1 247.1 0 01148.79 53.21z\"/></svg>',\n        folder: '<svg viewBox=\"0 0 512 512\" fill=\"currentColor\"><path d=\"M464 128H272l-64-64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49-48-48V176c0-26.51-21.49-48-48-48zm0 272H48V112h140.12l54.63 54.63c6.25 6.25 14.44 9.37 22.62 9.37H464v128z\"/></svg>',\n        times: '<svg viewBox=\"0 0 352 512\" fill=\"currentColor\"><path d=\"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z\"/></svg>',\n        comment: '<svg viewBox=\"0 0 512 512\" fill=\"currentColor\"><path d=\"M256 32C114.6 32 0 125.1 0 240c0 49.6 21.4 95 57 130.7C44.5 421.1 2.7 466 2.2 466.5c-2.2 2.3-2.8 5.7-1.5 8.7S4.8 480 8 480c66.3 0 116-31.8 146.3-74.4c28.7 12.1 61.4 18.4 95.7 18.4c141.4 0 256-93.1 256-208S397.4 32 256 32z\"/></svg>',\n        thLarge: '<svg viewBox=\"0 0 512 512\" fill=\"currentColor\"><path d=\"M224 224H32c-17.67 0-32-14.33-32-32V32C0 14.33 14.33 0 32 0h192c17.67 0 32 14.33 32 32v160c0 17.67-14.33 32-32 32zm0 256H32c-17.67 0-32-14.33-32-32V288c0-17.67 14.33-32 32-32h192c17.67 0 32 14.33 32 32v160c0 17.67-14.33 32-32 32zM480 224H288c-17.67 0-32-14.33-32-32V32c0-17.67 14.33-32 32-32h192c17.67 0 32 14.33 32 32v160c0 17.67-14.33 32-32 32zm0 256H288c-17.67 0-32-14.33-32-32V288c0-17.67 14.33-32 32-32h192c17.67 0 32 14.33 32 32v160c0 17.67-14.33 32-32 32z\"/></svg>',\n        spinner: '<svg viewBox=\"0 0 512 512\" fill=\"currentColor\" class=\"fa-spin\"><path d=\"M304 48c0 26.51-21.49 48-48 48s-48-21.49-48-48 21.49-48 48-48 48 21.49 48 48zm-48 368c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49-48-48-21.49-48-48-48zm208-208c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49-48-48-21.49-48-48-48zM96 256c0-26.51-21.49-48-48-48S0 229.49 0 256s21.49 48 48 48 48-21.49-48-48zm12.922 99.078c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49-48-48c0-26.509-21.49-48-48-48zM400 96c0-26.51-21.49-48-48-48s-48 21.49-48 48 21.49 48 48 48 48-21.49-48-48z\"/></svg>',\n        exclamationTriangle: '<svg viewBox=\"0 0 576 512\" fill=\"currentColor\"><path d=\"M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-60.035-39.993-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.982 12.654z\"/></svg>',\n        exclamationCircle: '<svg viewBox=\"0 0 512 512\" fill=\"currentColor\"><path d=\"M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.982 12.654z\"/></svg>',\n        chevronDown: '<svg fill=\"currentColor\" viewBox=\"0 0 320 512\" style=\"width:10px;height:10px;\"><path d=\"M143 352.3L7.3 216.3c-9.4-9.4-9.4-24.6 0-33.9l22.6-22.6c9.4-9.4 24.6-9.4 33.9 0l96.4 96.4 96.4-96.4c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9l-136 136c-9.2 9.4-24.4 9.4-33.8 0z\"></path></svg>',\n        plus: '<svg viewBox=\"0 0 448 512\" fill=\"currentColor\"><path d=\"M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z\"/></svg>',\n        edit: '<svg viewBox=\"0 0 576 512\" fill=\"currentColor\"><path d=\"M402.6 83.2l90.2 90.2c12.5 12.5 12.5 32.8 0 45.3l-17.3 17.3c-12.5 12.5-32.8 12.5-45.3 0L357.3 145.8c-12.5-12.5-12.5-32.8 0-45.3l17.3-17.3c12.5-12.5 32.8-12.5 45.3 0zm-47.4 16.3L295.1 192 45.7 441.3c-26.6 26.6-35.4 37.7-35.7 38.1-1.3 1.8-3.4 3.1-5.7 3.5-1.9.3-3.8.2-5.7-.1-4.6-.8-8.3-4.5-9.3-1.3-6.4 1.4-12.9 6.3-17.8l249.6-249.6c12.5-12.5 32.8-12.5 45.3 0l17.3 17.3c12.5 12.5 12.5 32.8 0 45.3zM256 448c-17.67 0-32 14.33-32 32s14.33 32 32 32h224c17.67 0 32-14.33 32-32s-14.33-32-32-32H256z\"/></svg>',\n        gripVertical: '<svg viewBox=\"0 0 320 512\" fill=\"currentColor\"><path d=\"M96 48c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48zm0 160c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48zm0 160c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48z\"/></svg>',\n        infoCircle: '<svg viewBox=\"0 0 512 512\" fill=\"currentColor\"><path d=\"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z\"/></svg>'\n    };\n\n    // ==================== 共享通知系统 ====================\n    function showNotification(message, type = 'success') {\n        document.querySelectorAll('.ss-notification').forEach(n => n.remove());\n\n        const notification = document.createElement('div');\n        notification.className = `ss-notification ${type === 'error' ? 'error' : ''}`;\n        notification.textContent = message;\n        document.body.appendChild(notification);\n\n        setTimeout(() => notification.classList.add('show'), 10);\n        setTimeout(() => {\n            notification.classList.remove('show');\n            setTimeout(() => notification.remove(), 300);\n        }, USER_CONFIGURABLE_DELAYS.NEW_VERSION.NOTIFICATION_DURATION || 3000); // Use NEW_VERSION default, it's a common duration\n    }\n\n    // ==================== 版本检测 ====================\n    const IS_NEW_SALESARTLY = window.location.pathname.startsWith('/next/chat');\n\n    // ==================== 共享规则解析器 (RuleParser) ====================\n    // 规则文件格式是通用的，所以解析器可以共享\n    const RuleParser = {\n        parseSingleRuleBlock: (blockContent, category) => {\n            // 为每个规则生成一个唯一ID，便于拖拽和自定义规则管理\n            const rule = {\n                id: `rule-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n                name: '',\n                messages: [],\n                color: null,\n                category\n            };\n            const lines = blockContent.split('\\n').filter(line => line.trim());\n            const originalTexts = {};\n            const imageNames = {};\n\n            for (const line of lines) {\n                const [key, ...valueParts] = line.split('=');\n                const value = valueParts.join('=').trim();\n                if (!key.trim() || !value) continue;\n                const k = key.trim();\n                if (k.startsWith('tt')) {\n                    const tKey = k.replace('tt', 't');\n                    originalTexts[tKey] = value;\n                }\n                if (k === 'imge_name') {\n                    imageNames['imge'] = value;\n                }\n            }\n\n            for (const line of lines) {\n                const [key, ...valueParts] = line.split('=');\n                const value = valueParts.join('=').trim();\n                if (!key.trim() || !value) continue;\n                const k = key.trim();\n                if (k === 'name') {\n                    rule.name = value;\n                } else if (k === 'color') {\n                    rule.color = value;\n                } else if (k.startsWith('t') && !k.startsWith('tt')) {\n                    rule.messages.push({\n                        type: 'text',\n                        translated: value,\n                        original: originalTexts[k] || null\n                    });\n                } else if (k === 'imge') {\n                    const idx = parseInt(value);\n                    if (!isNaN(idx)) {\n                        rule.messages.push({\n                            type: 'image',\n                            index: idx,\n                            name: imageNames['imge'] || null\n                        });\n                    }\n                }\n            }\n            return (rule.name && rule.messages.length > 0) ? rule : null;\n        },\n        parseRuleFile: (content) => {\n            const rules = [];\n            const categories = new Set();\n            const lines = content.split('\\n');\n            let currentCategory = '通用';\n            let currentRuleLines = [];\n\n            for (const line of lines) {\n                const trimmedLine = line.trim();\n                const categoryMatch = trimmedLine.match(/^={4,}(.*?)(?:={4,})?$/);\n\n                if (categoryMatch && categoryMatch[1].trim()) {\n                    if (currentRuleLines.length > 0) {\n                        const parsedRule = RuleParser.parseSingleRuleBlock(currentRuleLines.join('\\n'), currentCategory);\n                        if (parsedRule) rules.push(parsedRule);\n                    }\n                    currentCategory = categoryMatch[1].trim();\n                    categories.add(currentCategory);\n                    currentRuleLines = [];\n                    continue;\n                }\n\n                if (trimmedLine === '' && currentRuleLines.length > 0) {\n                    const parsedRule = RuleParser.parseSingleRuleBlock(currentRuleLines.join('\\n'), currentCategory);\n                    if (parsedRule) rules.push(parsedRule);\n                    currentRuleLines = [];\n                } else if (trimmedLine !== '' && !categoryMatch) {\n                    currentRuleLines.push(trimmedLine);\n                }\n            }\n\n            if (currentRuleLines.length > 0) {\n                const parsedRule = RuleParser.parseSingleRuleBlock(currentRuleLines.join('\\n'), currentCategory);\n                if (parsedRule) rules.push(parsedRule);\n            }\n\n            return {\n                rules,\n                categories: Array.from(categories).sort()\n            };\n        },\n        serializeRulesToPlainText: (rules) => {\n            if (!Array.isArray(rules) || rules.length === 0) return '';\n            const output = [];\n            let lastCategory = null;\n            // 注意：这里只对非自定义规则进行序列化，自定义规则由 AppState.customRules 管理\n            // 但为了导入导出功能，这里需要能处理所有规则。\n            // 如果是导出，则需要将自定义规则也包含进来，但它们不会有 category 以外的额外标记\n            const rulesToSerialize = rules.filter(r => !r.isCustom); // 导出时只导出非自定义的规则\n            const sortedRules = [...rulesToSerialize].sort((a, b) => {\n                const categoryA = a.category === '通用' ? ' A' : a.category;\n                const categoryB = b.category === '通用' ? ' A' : b.category;\n                return categoryA.localeCompare(b.category); // 确保按类别排序\n            });\n\n            sortedRules.forEach(rule => {\n                if (rule.category !== lastCategory) {\n                    if (output.length > 0) output.push('');\n                    output.push(`==============================${rule.category}==============================`);\n                    lastCategory = rule.category;\n                } else if (output.length > 0 && output[output.length - 1] !== '') {\n                    output.push('');\n                }\n\n                const ruleLines = [];\n                if (rule.name) ruleLines.push(`name=${rule.name}`);\n                if (rule.color) ruleLines.push(`color=${rule.color}`);\n\n                let textIndex = 1;\n                rule.messages.forEach(msg => {\n                    if (msg.type === 'text') {\n                        ruleLines.push(`t${textIndex}=${msg.translated}`);\n                        if (msg.original) ruleLines.push(`tt${textIndex}=${msg.original}`);\n                        textIndex++;\n                    } else if (msg.type === 'image' && msg.index) {\n                        ruleLines.push(`imge=${msg.index}`);\n                        if (msg.name) ruleLines.push(`imge_name=${msg.name}`);\n                    }\n                });\n\n                if (ruleLines.length > 0) output.push(ruleLines.join('\\n'));\n            });\n            return output.join('\\n');\n        }\n    };\n\n\n    if (IS_NEW_SALESARTLY) {\n        // ====================================================================\n        // ====================== 新版 SalesSmartly 逻辑 ======================\n        // ====================================================================\n        console.log('SalesSmartly 助手：检测到新版 SalesSmartly 页面。');\n\n        // ==================== 配置常量 ====================\n        const CONFIG = {\n            PANEL: {\n                WIDTH: 400,\n                HEIGHT: 670,\n                VERTICAL_ANCHOR: 'bottom',\n                VERTICAL_OFFSET: 0,\n                HORIZONTAL_ANCHOR: 'right',\n                HORIZONTAL_OFFSET: -10,\n            },\n            SELECTORS: {\n                CHAT_INPUT: 'div.tiptap.ProseMirror[contenteditable=\"true\"][role=\"textbox\"]',\n                SEND_AREA: '.input-area__send',\n                DROPDOWN_MENU_POPUP: '.arco-trigger-popup',\n                DROPDOWN_OPTION_CONTENT: '.arco-dropdown-option-content',\n                ATTACHMENT_SVG: 'svg.arco-icon.icon-document-circle',\n                TOOL_MENU: '.arco-trigger-popup',\n                IMAGE_OPTION: '.tools__item__content__sub',\n                BUTTONS_PANEL: '.button-panel',\n                BUTTONS_CONTAINER: '.buttons-container',\n                IMAGE_TABLE: '.vxe-table--body-wrapper, .arco-table-element',\n                IMAGE_TABLE_BODY: '.vxe-table--body-wrapper, .arco-table-body',\n                IMAGE_ROW: '.vxe-body--row, .arco-table-tr',\n                IMAGE_NAME_CELL: '.material-table__list__name__text, .vxe-cell',\n                IMAGE_SEND_BTN: '.operate-button.g-link-btn'\n            },\n            REMOTE_RULES_URL: 'https://abcdc.top/jj/kjgz.txt'\n        };\n\n        const DEFAULT_LOCAL_RULES = `\n==============================右上角设置按键可打开导入本地规则文本，放置自己的话术快速发送；可选择打开远程规则直接使用==============================\n\nname=右上角设置按键\nt1=右上角设置按键\ntt1=译文\n\n\n==============================右上角的“翻译”“原文”开关可控制发送内容是输入中文点击‘翻译发送’或者直接输入目标译文后点击‘原文发送’==============================\nname=按键配置行\nt1=右上角设置按键\ntt1=译文\n==============================t1=译文  #配置发送的文字译文==============================\nname=按键配置行\nt1=右上角设置按键\ntt1=译文\n==============================tt1=发送文字  #配置发送的文字==============================\nname=按键配置行\nt1=右上角设置按键\ntt1=译文\n==============================imge_name=be888-提款.jpg  #配置新版图片名称==============================\nname=按键配置行\nt1=右上角设置按键\ntt1=译文\n==============================imge=8  #配置旧版图片的位置如第4张图要*2填入==============================\nname=按键配置行\ntt1=右上角设置按键\nt1=译文\n==============================name=vip0  #按键名称==============================\nname=按键配置行\ntt1=右上角设置按键\nt1=译文\n==============================规则配置：如下，一个按键中包含name行（按键名称）；tt1，tt2，tt3...为中文，一个按键可以配置多个，加入几行就会发几条，加入几行就会发几条，t1，和t2...配置与对应的中文的目标译文如tt1对应的t1最好是上下排一起；imge行为配置旧版网页的按键发送的图片位置，打开附件查看图片排在第几张，该行填写的数字为图片第几个*2的数字；imge_name行是为新版本的网页配置发送的图片，直接填写输入图片的全称即可，不再受附件的排序影响；按键和按键之间的配置行要空出一行 空行，来隔离按键之间的配置；下面是一个按键的实例配置：==============================\nname=右上角设置按键\nt1=右上角设置按键\ntt1=译文\n`;\n\n        const STORAGE_KEYS = {\n            SEND_MODE: 'ss_send_mode_preference_new',\n            CONTROL_BTN_POS: 'ss_control_btn_pos_new',\n            USE_REMOTE_RULES: 'ss_use_remote_rules_preference_new',\n            SELECTED_CATEGORIES: 'ss_selected_categories_order_new', // 存储可见分类的顺序\n            HIDDEN_CATEGORIES: 'ss_hidden_categories_new', // 新增：存储明确隐藏的分类\n            LOCAL_RULES: 'txt_new',\n            CUSTOM_RULES: 'ss_custom_rules_new', // 存储自定义规则\n            COMPACT_MODE: 'ss_compact_mode_new', // 紧凑模式\n        };\n\n        // ==================== 全局状态管理 ====================\n        const AppState = {\n            currentSendMode: GM_getValue(STORAGE_KEYS.SEND_MODE, 'translated'),\n            useRemoteRules: GM_getValue(STORAGE_KEYS.USE_REMOTE_RULES, false),\n            compactMode: GM_getValue(STORAGE_KEYS.COMPACT_MODE, false),\n            selectedCategoriesOrder: [], // 存储已选分类的显示顺序\n            hiddenCategories: [], // 存储用户明确隐藏的分类\n            allParsedRules: [], // 原始解析的规则 (远程或本地文件)\n            customRules: [], // 用户自定义规则\n            allAvailableCategories: [], // 所有可用的分类 (包括自定义分类)\n            isReplying: false,\n            sizeInfoTimeout: null,\n            highlightCheckInterval: null,\n            highlightedSVG: null,\n            highlightBox: null,\n            isMovingMouse: false,\n            isImageSending: false,\n            lastSentImageName: '',\n            hoverTimer: null,\n            hoverInfoPopupTimer: null,\n            dragState: {\n                isDragging: false,\n                ghostElement: null,\n                originalElement: null,\n                draggedId: null,\n                draggedRule: null,\n                offsetX: 0,\n                offsetY: 0,\n            },\n            CUSTOM_CATEGORY_NAME: '自定义',\n\n            setSendMode(mode) {\n                this.currentSendMode = mode;\n                GM_setValue(STORAGE_KEYS.SEND_MODE, mode);\n            },\n\n            setRemoteRules(useRemote) {\n                this.useRemoteRules = useRemote;\n                GM_setValue(STORAGE_KEYS.USE_REMOTE_RULES, useRemote);\n            },\n\n            setCompactMode(isCompact) {\n                this.compactMode = isCompact;\n                GM_setValue(STORAGE_KEYS.COMPACT_MODE, isCompact);\n            },\n\n            setCustomRules(rules) {\n                this.customRules = rules;\n                GM_setValue(STORAGE_KEYS.CUSTOM_RULES, JSON.stringify(rules));\n            },\n\n            getAllRulesCombined() {\n                const customRulesWithCategory = this.customRules.map(rule => ({\n                    ...rule,\n                    category: this.CUSTOM_CATEGORY_NAME,\n                    isCustom: true\n                }));\n                const nonCustomRules = this.allParsedRules.map(rule => ({ ...rule, isCustom: false }));\n                return [...nonCustomRules, ...customRulesWithCategory];\n            },\n\n            getAllCategoriesCombined() {\n                const categories = new Set();\n                this.allParsedRules.forEach(rule => categories.add(rule.category));\n                categories.add(this.CUSTOM_CATEGORY_NAME);\n\n                const sortedCategories = Array.from(categories).sort((a, b) => {\n                    if (a === this.CUSTOM_CATEGORY_NAME) return -1;\n                    if (b === this.CUSTOM_CATEGORY_NAME) return 1;\n                    return a.localeCompare(b);\n                });\n                return sortedCategories;\n            },\n\n            toggleCategorySelection(categoryName, isChecked) {\n                let currentOrder = [...this.selectedCategoriesOrder];\n                let currentHidden = new Set(this.hiddenCategories);\n\n                if (isChecked) {\n                    currentHidden.delete(categoryName); // 从隐藏列表中移除\n                    if (!currentOrder.includes(categoryName)) { // 如果不在可见列表中，则添加到末尾\n                        currentOrder.push(categoryName);\n                    }\n                } else {\n                    currentOrder = currentOrder.filter(cat => cat !== categoryName); // 从可见列表中移除\n                    currentHidden.add(categoryName); // 添加到隐藏列表\n                }\n\n                // 特殊处理“自定义”分类，如果被选中，则始终排在最前面\n                const customIndex = currentOrder.indexOf(this.CUSTOM_CATEGORY_NAME);\n                if (customIndex > -1) {\n                    const customCat = currentOrder.splice(customIndex, 1)[0];\n                    currentOrder.unshift(customCat);\n                }\n\n                this.selectedCategoriesOrder = currentOrder;\n                this.hiddenCategories = Array.from(currentHidden);\n\n                GM_setValue(STORAGE_KEYS.SELECTED_CATEGORIES, this.selectedCategoriesOrder);\n                GM_setValue(STORAGE_KEYS.HIDDEN_CATEGORIES, this.hiddenCategories);\n            },\n\n            initializeSelectedCategories(storedSelectedOrder, storedHiddenCategories) {\n                const allAvailable = this.getAllCategoriesCombined();\n                const hiddenSet = new Set(storedHiddenCategories);\n\n                // 1. 从存储的顺序开始，过滤掉不再存在或被明确隐藏的分类\n                let finalOrder = storedSelectedOrder.filter(cat => allAvailable.includes(cat) && !hiddenSet.has(cat));\n\n                // 2. 添加所有可用但不在当前可见列表且未被明确隐藏的新分类\n                allAvailable.forEach(cat => {\n                    if (!finalOrder.includes(cat) && !hiddenSet.has(cat)) {\n                        finalOrder.push(cat);\n                    }\n                });\n\n                // 3. 确保“自定义”分类始终排在最前面（如果它在可见列表中）\n                const customIndex = finalOrder.indexOf(this.CUSTOM_CATEGORY_NAME);\n                if (customIndex > -1) {\n                    const customCat = finalOrder.splice(customIndex, 1)[0];\n                    finalOrder.unshift(customCat);\n                }\n\n                this.selectedCategoriesOrder = finalOrder;\n                // 更新隐藏分类列表，移除不再存在的分类\n                this.hiddenCategories = Array.from(hiddenSet).filter(cat => allAvailable.includes(cat));\n\n                GM_setValue(STORAGE_KEYS.SELECTED_CATEGORIES, this.selectedCategoriesOrder);\n                GM_setValue(STORAGE_KEYS.HIDDEN_CATEGORIES, this.hiddenCategories);\n            },\n\n            setReplyStatus(status) {\n                this.isReplying = status;\n            }\n        };\n\n        // ==================== 样式注入 ====================\n        function injectStyles() {\n            GM_addStyle(`\n.ss-panel { position: fixed; background: white; border-radius: 16px; box-shadow: 0 15px 40px rgba(0,0,0,.25); overflow: visible; display: flex; flex-direction: column; z-index: 10000; opacity: 0; transform: translateY(20px); transition: all .4s cubic-bezier(.175,.885,.32,1.275); }\n.ss-panel.show { opacity: 1; transform: translateY(0); }\n.panel-header { background: linear-gradient(to right, #3b82f6, #2563eb); padding: 10px 15px; color: white; font-weight: 600; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; border-radius: 16px 16px 0 0; position: relative; }\n.panel-title-group { display: flex; align-items: center; gap: 15px; flex-grow: 1; min-width: 0; }\n.panel-btn { background: rgba(255,255,255,.2); border: none; border-radius: 8px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: white; font-size: 14px; transition: .3s ease; }\n.panel-btn:hover { background: rgba(255,255,255,.3); }\n.buttons-container { flex-grow: 1; overflow-y: auto; padding: 15px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; background: #f8fafc; border-radius: 0 0 16px 16px; }\n.action-btn { display: flex; flex-direction: row; align-items: center; gap: 6px; padding: 6px 8px; border-radius: 8px; background: white; border: 1px solid #e2e8f0; cursor: pointer; transition: .2s ease; color: #334155; min-height: 48px; box-shadow: 0 2px 6px rgba(0,0,0,.04); position: relative; overflow: visible; }\n.action-btn:hover { background: #e0f2fe; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(59,130,246,.2); border-color: #93c5fd; }\n.action-btn span { font-size: 12px; font-weight: 500; line-height: 1.4; text-align: left; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }\n.settings-popup { position: absolute; top: calc(100% + 5px); right: 10px; background: white; border-radius: 8px; box-shadow: 0 8px 25px rgba(0,0,0,.15); border: 1px solid #e2e8f0; z-index: 10; width: 220px; padding: 10px; display: flex; flex-direction: column; gap: 8px; opacity: 0; transform: translateY(-10px); pointer-events: none; transition: all .2s ease-out; }\n.settings-popup.show { opacity: 1; transform: translateY(0); pointer-events: auto; }\n.settings-popup .popup-control-btn { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: none; background: transparent; cursor: pointer; width: 100%; text-align: left; border-radius: 6px; font-size: 13px; color: #334155; }\n.settings-popup .popup-control-btn:hover { background: #f1f5f9; color: #1e293b; }\n.settings-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; border-radius: 6px; }\n.settings-item:hover { background: #f1f5f9; }\n.settings-label { font-size: 13px; color: #334155; }\n.settings-item.send-mode-switch span { color: #334155; } /* 确保文字颜色可见 */\n.category-select-wrapper { margin-top: 5px; padding: 8px 10px; border-radius: 6px; background: #f8fafc; border: 1px solid #e2e8f0; position: relative; }\n.category-select-wrapper > label { display: block; font-size: 12px; color: #64748b; margin-bottom: 5px; font-weight: 500; }\n.category-toggle-btn { display: flex; justify-content: space-between; align-items: center; width: 100%; padding: 6px 10px; border: 1px solid #cbd5e1; border-radius: 4px; background-color: #fff; cursor: pointer; font-size: 13px; color: #334155; text-align: left; transition: background-color .2s; }\n.category-toggle-btn:hover { background-color: #f1f5f9; }\n.category-toggle-btn.active svg { transform: rotate(180deg); }\n.category-options-popup { display: none; position: absolute; top: 100%; left: 0; right: 0; background: white; border: 1px solid #e2e8f0; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,.1); z-index: 11; max-height: 200px; overflow-y: auto; padding: 5px; margin-top: 5px; }\n.category-options-popup.show { display: block; }\n.category-options-popup label { display: flex; align-items: center; padding: 5px 8px; cursor: pointer; font-size: 13px; color: #334155; white-space: nowrap; }\n.category-options-popup label:hover { background-color: #f1f5f9; }\n.category-options-popup input[type=\"checkbox\"] { -webkit-appearance: checkbox !important; -moz-appearance: checkbox !important; appearance: checkbox !important; opacity: 1 !important; width: 16px !important; height: 16px !important; position: static !important; margin-right: 8px !important; vertical-align: middle !important; flex-shrink: 0 !important; }\n.category-options-popup input[type=\"checkbox\"]:checked + span { color: #1e293b !important; font-weight: 600 !important; }\n.size-info { position: absolute; bottom: -24px; right: 5px; background: rgba(0,0,0,.7); color: white; padding: 3px 7px; border-radius: 4px; font-size: 10px; z-index: 100; opacity: 0; transition: opacity .3s ease; pointer-events: none; }\n.size-info.show { opacity: 1; }\n.resize-handle { position: absolute; z-index: 100; background: transparent; }\n.resize-handle-e { cursor: ew-resize; height: 100%; width: 8px; right: 0; top: 0; }\n.resize-handle-s { cursor: ns-resize; width: 100%; height: 8px; bottom: 0; left: 0; }\n.resize-handle-se { cursor: nwse-resize; width: 18px; height: 18px; right: 0; bottom: 0; z-index: 101; }\n.control-buttons { position: fixed; display: flex; gap: 15px; z-index: 11000; cursor: grab; }\n.control-btn { width: 60px; height: 60px; border-radius: 50%; background: #3b82f6; color: white; display: flex; align-items: center; justify-content: center; font-size: 24px; cursor: pointer; box-shadow: 0 6px 20px rgba(0,0,0,.3); transition: .3s ease; }\n.control-btn:hover { background: #2563eb; transform: scale(1.1); box-shadow: 0 8px 25px rgba(0,0,0,.4); }\n.file-input { display: none; }\n.loading { display: flex; justify-content: center; align-items: center; height: 100%; font-size: 14px; color: #64748b; gap: 10px; }\n.ss-notification { position: fixed; bottom: 30px; right: 30px; padding: 12px 20px; border-radius: 8px; background-color: #10b981; color: white; font-weight: 500; box-shadow: 0 4px 12px rgba(0,0,0,.15); z-index: 20000; opacity: 0; transform: translateY(20px); transition: all .3s ease; max-width: 400px; word-break: break-word; }\n.ss-notification.show { opacity: 1; transform: translateY(0); }\n.ss-notification.error { background-color: #ef4444; }\n@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\n.fa-spin { animation: spin 1s linear infinite; }\n.switch-container { position: relative; display: inline-block; width: 40px; height: 22px; }\n.switch-container input { opacity: 0; width: 0; height: 0; }\n.switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 22px; }\n.switch-slider:before { position: absolute; content: \"\"; height: 18px; width: 18px; left: 2px; bottom: 2px; background-color: white; transition: .4s; border-radius: 50%; }\ninput:checked + .switch-slider { background-color: #4ade80; }\ninput:checked + .switch-slider:before { transform: translateX(18px); }\n.panel-btn svg, .popup-control-btn svg, .action-btn svg, .control-btn svg, .loading svg { display: inline-block; vertical-align: middle; }\n.panel-btn svg { width: 14px; height: 14px; }\n.popup-control-btn svg { width: 16px; height: 16px; color: #64748b; }\n.action-btn svg { width: 16px; height: 16px; color: inherit; flex-shrink: 0; }\n.control-btn svg { width: 24px; height: 24px; }\n.loading svg { width: 1em; height: 1em; }\n.reply-status-indicator { font-size: 12px; font-weight: normal; margin-left: 10px; padding: 2px 8px; border-radius: 10px; background-color: rgba(255,255,255,0.2); color: white; transition: background-color 0.3s ease; }\n.reply-status-indicator.idle { background-color: #28a745; }\n.reply-status-indicator.replying { background-color: #ffc107; color: #333; }\n.action-btn .action-icon-wrapper { display: flex; align-items: center; justify-content: center; padding: 4px; border-radius: 4px; transition: background-color 0.2s ease; flex-shrink: 0; }\n.action-btn .action-icon-wrapper:hover { background-color: rgba(0,0,0,0.05); }\n.action-btn .action-icon-wrapper:hover svg { animation: spin 1s linear infinite; }\n.category-header { grid-column: 1 / -1; text-align: left; font-weight: 600; color: #475569; padding: 8px 0 4px 0; margin-top: 15px; border-bottom: 1px solid #e2e8f0; margin-bottom: 5px; font-size: 14px; }\n.ss-highlight-box { position: fixed; border: 5px solid #ff0000; border-radius: 8px; background: rgba(255, 0, 0, 0.08); pointer-events: none; z-index: 9999; box-shadow: 0 0 10px rgba(255, 0, 0, 0.5), inset 0 0 10px rgba(255, 0, 0, 0.2), 0 0 30px rgba(255, 0, 0, 0.8); animation: ss-highlight-pulse 1.5s ease-in-out infinite; }\n@keyframes ss-highlight-pulse { 0%, 100% { box-shadow: 0 0 10px rgba(255, 0, 0, 0.5), inset 0 0 10px rgba(255, 0, 0, 0.2), 0 0 30px rgba(255, 0, 0, 0.8); border-color: #ff0000; } 50% { box-shadow: 0 0 20px rgba(255, 0, 0, 0.8), inset 0 0 15px rgba(255, 0, 0, 0.3), 0 0 50px rgba(255, 0, 0, 1); border-color: #ff3333; } }\n\n/* 新增自定义规则相关样式 */\n.category-header.custom-category { display: flex; justify-content: space-between; align-items: center; }\n.add-custom-rule-btn { background: #22c55e; color: white; border: none; padding: 4px 8px; border-radius: 6px; font-size: 12px; cursor: pointer; transition: background-color .2s; display: flex; align-items: center; gap: 4px; }\n.add-custom-rule-btn:hover { background: #16a34a; }\n.add-custom-rule-btn svg { width: 12px; height: 12px; }\n.action-btn.dragging-source { opacity: 0.4; } /* 拖动源的样式 */\n\n/* 编辑按键 (右下角) */\n.action-btn .hover-actions {\n    position: absolute;\n    bottom: -8px; /* 编辑按键在右下角 */\n    right: -8px;\n    display: flex;\n    gap: 4px;\n    opacity: 0;\n    pointer-events: none;\n    transition: opacity .2s ease-out;\n    z-index: 5;\n}\n.action-btn:hover .hover-actions, .action-btn.show-actions .hover-actions {\n    opacity: 1;\n    pointer-events: auto;\n}\n\n/* 拖动按键 (右上角) */\n.action-btn .drag-handle-wrapper {\n    position: absolute;\n    top: -8px; /* 拖动按键在右上角 */\n    right: -8px;\n    opacity: 0;\n    pointer-events: none;\n    transition: opacity .2s ease-out;\n    z-index: 5;\n}\n.action-btn:hover .drag-handle-wrapper, .action-btn.show-actions .drag-handle-wrapper {\n    opacity: 1;\n    pointer-events: auto;\n}\n\n/* 确保编辑和拖动按钮本身的样式 */\n.action-btn .hover-action-btn {\n    background: rgba(0,0,0,0.6);\n    color: white;\n    border: none;\n    border-radius: 50%;\n    width: 24px;\n    height: 24px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    transition: background-color .2s;\n}\n.action-btn .hover-action-btn:hover {\n    background: rgba(0,0,0,0.8);\n}\n.action-btn .hover-action-btn svg {\n    width: 12px;\n    height: 12px;\n}\n.drag-handle { /* 拖动按钮的特殊光标 */\n    cursor: grab;\n}\n.drag-handle:active { cursor: grabbing; }\n\n.ss-drag-ghost { position: fixed; z-index: 30000; pointer-events: none; opacity: 0.8; transform: translate(-50%, -50%); /* 居中于鼠标 */ box-shadow: 0 10px 30px rgba(0,0,0,0.4); }\n.drag-over-indicator { grid-column: 1 / -1; height: 3px; background: #3b82f6; border-radius: 1.5px; margin: -5px 0; transition: all 0.1s ease-out; }\n.category-header.custom-category.drag-over-target { background-color: #e0f2fe; border-color: #93c5fd; } /* 拖拽到自定义分类头部的样式 */\n\n/* 模态框样式 */\n.ss-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); display: flex; justify-content: center; align-items: center; z-index: 20000; opacity: 0; transition: opacity .3s ease; pointer-events: none; }\n.ss-modal-overlay.show { opacity: 1; pointer-events: auto; }\n.ss-modal-content { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); padding: 25px; width: 90%; max-width: 600px; max-height: 90vh; overflow-y: auto; display: flex; flex-direction: column; transform: translateY(-20px); transition: transform .3s ease; }\n.ss-modal-overlay.show .ss-modal-content { transform: translateY(0); }\n.ss-modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #eee; }\n.ss-modal-header h3 { margin: 0; font-size: 18px; color: #333; }\n.ss-modal-header .close-btn { background: none; border: none; font-size: 24px; cursor: pointer; color: #999; }\n.ss-modal-header .close-btn:hover { color: #666; }\n.ss-modal-body { flex-grow: 1; overflow-y: auto; padding-right: 5px; }\n.ss-modal-body label { display: block; margin-bottom: 8px; font-weight: 500; color: #555; font-size: 14px; }\n.ss-modal-body input[type=\"text\"], .ss-modal-body input[type=\"number\"], .ss-modal-body textarea { width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px; box-sizing: border-box; }\n.ss-modal-body textarea { min-height: 60px; resize: vertical; }\n.ss-modal-body .message-item { display: flex; flex-direction: column; gap: 5px; padding: 10px; border: 1px solid #eee; border-radius: 8px; margin-bottom: 10px; background: #f9f9f9; position: relative; }\n.ss-modal-body .message-item .remove-msg-btn { position: absolute; top: 5px; right: 5px; background: #ef4444; color: white; border: none; border-radius: 4px; padding: 2px 6px; cursor: pointer; font-size: 12px; }\n.ss-modal-body .message-item .remove-msg-btn:hover { background: #dc2626; }\n.ss-modal-body .message-item input, .ss-modal-body .message-item textarea { width: 100%; margin-bottom: 5px; }\n.ss-modal-body .color-picker-group { display: flex; align-items: center; gap: 10px; margin-bottom: 15px; }\n.ss-modal-body input[type=\"color\"] { width: 40px; height: 40px; border: none; padding: 0; border-radius: 4px; cursor: pointer; background: none; }\n.ss-modal-body .color-preview { width: 30px; height: 30px; border-radius: 4px; border: 1px solid #ccc; }\n.ss-modal-footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; padding-top: 15px; border-top: 1px solid #eee; }\n.ss-modal-footer button { padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: background-color .2s; }\n.ss-modal-footer .btn-cancel { background: #e2e8f0; color: #334155; border: 1px solid #cbd5e1; }\n.ss-modal-footer .btn-cancel:hover { background: #cbd5e1; }\n.ss-modal-footer .btn-save { background: #3b82f6; color: white; border: 1px solid #3b82f6; }\n.ss-modal-footer .btn-save:hover { background: #2563eb; }\n.ss-modal-footer .btn-delete { background: #ef4444; color: white; border: 1px solid #ef4444; margin-right: auto; }\n.ss-modal-footer .btn-delete:hover { background: #dc2626; }\n\n/* 悬浮信息提示框样式 */\n.ss-hover-info-popup {\n    position: fixed;\n    background: ${USER_CONFIGURABLE_STYLES.HOVER_INFO_POPUP_BG_COLOR}; /* 使用配置项 */\n    border: 1px solid #e2e8f0;\n    border-radius: 8px;\n    box-shadow: 0 4px 15px rgba(0,0,0,0.1);\n    padding: 15px;\n    max-width: 300px;\n    z-index: 10001;\n    pointer-events: none; /* 不阻止鼠标事件 */\n    opacity: 0; /* 初始隐藏 */\n    visibility: hidden;\n    transition: opacity 0.2s ease, visibility 0.2s ease;\n    font-size: 13px;\n    color: #ffffff; /* 默认白色文本以适应深色背景 */\n    line-height: 1.5;\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    word-break: break-word;\n}\n.ss-hover-info-popup.show {\n    opacity: ${USER_CONFIGURABLE_STYLES.HOVER_INFO_POPUP_OPACITY}; /* 使用配置项 */\n    visibility: visible;\n}\n.ss-hover-info-popup strong {\n    font-weight: 600;\n    color: #ffffff;\n    font-size: 14px;\n}\n.ss-hover-info-popup .info-section {\n    border-top: 1px solid rgba(255,255,255,0.2); /* 适应深色背景 */\n    padding-top: 8px;\n    margin-top: 8px;\n}\n.ss-hover-info-popup .info-section:first-child {\n    border-top: none;\n    padding-top: 0;\n    margin-top: 0;\n}\n.ss-hover-info-popup .info-section-title {\n    font-weight: 500;\n    color: #c0c0c0; /* 适应深色背景 */\n    margin-bottom: 4px;\n    display: flex;\n    align-items: center;\n    gap: 4px;\n}\n.ss-hover-info-popup .info-section-title svg {\n    width: 14px;\n    height: 14px;\n    color: #ffffff; /* 适应深色背景 */\n}\n.ss-hover-info-popup pre {\n    background: rgba(0,0,0,0.3); /* 适应深色背景 */\n    padding: 8px;\n    border-radius: 4px;\n    white-space: pre-wrap;\n    word-break: break-all;\n    font-family: monospace;\n    font-size: 12px;\n    margin: 0;\n    color: #f0f0f0; /* 适应深色背景 */\n}\n\n/* Compact Mode styles */\n.buttons-container.compact-mode {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: flex-start; /* Align items to the top */\n    padding: 5px; /* Smaller padding for container */\n    gap: 4px; /* Smaller gap between buttons */\n    grid-template-columns: unset; /* Override grid layout */\n}\n\n.buttons-container.compact-mode .action-btn {\n    flex: 0 0 auto; /* Don't grow, don't shrink, width determined by content */\n    max-width: fit-content; /* Allow width to fit content */\n    padding: 4px 6px; /* Smaller padding */\n    min-height: unset; /* Remove min-height constraint */\n    height: auto; /* Height adapts to content */\n    line-height: 1.2; /* Tighter line height */\n    gap: 4px; /* Small gap between icon and text */\n}\n\n.buttons-container.compact-mode .action-btn span {\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    -webkit-line-clamp: 1; /* Single line text */\n    display: block; /* Ensure text takes up its own space */\n    max-width: 100%; /* Prevent text from overflowing its container */\n}\n\n.buttons-container.compact-mode .action-btn .action-icon-wrapper {\n    padding: 0; /* No padding for icon wrapper */\n    width: unset;\n    height: unset;\n    display: flex; /* Ensure icon is centered if needed */\n    align-items: center;\n    justify-content: center;\n}\n\n.buttons-container.compact-mode .action-btn .action-icon-wrapper svg {\n    width: 1em; /* Icon size same as text */\n    height: 1em;\n    vertical-align: middle; /* Align with text baseline */\n}\n\n/* Adjust hover actions for compact mode */\n.buttons-container.compact-mode .action-btn .hover-actions {\n    bottom: -4px; /* Adjust position */\n    right: -4px;\n    gap: 2px;\n}\n\n.buttons-container.compact-mode .action-btn .drag-handle-wrapper {\n    top: -4px; /* Adjust position */\n    right: -4px;\n}\n\n.buttons-container.compact-mode .action-btn .hover-action-btn {\n    width: 20px; /* Smaller action buttons */\n    height: 20px;\n}\n\n.buttons-container.compact-mode .action-btn .hover-action-btn svg {\n    width: 10px; /* Smaller icons in action buttons */\n    height: 10px;\n}\n\n/* Adjust category header for compact mode */\n.buttons-container.compact-mode .category-header {\n    grid-column: unset; /* Remove grid column span */\n    width: 100%; /* Take full width */\n    margin-top: 10px;\n    padding: 4px 0;\n    font-size: 13px;\n}\n\n/* Drag indicator for compact mode */\n.buttons-container.compact-mode .drag-over-indicator {\n    width: 100%; /* Take full width of the flex container */\n    height: 3px;\n    background: #3b82f6;\n    border-radius: 1.5px;\n    margin: 2px 0; /* Adjust margin for visual separation */\n    /* No need for order property, insertBefore/After handles flow */\n}\n`);\n        }\n\n        // ==================== 日志工具 ====================\n        const Logger = {\n            log: (level, msg, data = null) => {\n                const timestamp = new Date().toLocaleTimeString();\n                const prefix = `[${timestamp}] [SS-NEW][${level.toUpperCase()}]`;\n                if (data) {\n                    console.log(`${prefix} ${msg}`, data);\n                } else {\n                    console.log(`${prefix} ${msg}`);\n                }\n            },\n            debug: (msg, data) => Logger.log('debug', msg, data),\n            info: (msg, data) => Logger.log('info', msg, data),\n            warn: (msg, data) => Logger.log('warn', msg, data),\n            error: (msg, data) => Logger.log('error', msg, data)\n        };\n\n        // ==================== DOM 工具 ====================\n        const DOMUtils = {\n            findElement: async (selector, timeout = 5000, retryInterval = 50) => {\n                return new Promise((resolve, reject) => {\n                    let elapsedTime = 0;\n                    const interval = setInterval(() => {\n                        const element = document.querySelector(selector);\n                        if (element) {\n                            clearInterval(interval);\n                            resolve(element);\n                            return;\n                        }\n                        elapsedTime += retryInterval;\n                        if (elapsedTime >= timeout) {\n                            clearInterval(interval);\n                            reject(new Error(`超时: 未找到元素: ${selector}`));\n                        }\n                    }, retryInterval);\n                });\n            },\n            findElementByText: async (selector, text, timeout = 5000, retryInterval = 50) => {\n                return new Promise((resolve, reject) => {\n                    let elapsedTime = 0;\n                    const interval = setInterval(() => {\n                        const elements = document.querySelectorAll(selector);\n                        for (const el of elements) {\n                            if (el.textContent.trim() === text) {\n                                clearInterval(interval);\n                                resolve(el);\n                                return;\n                            }\n                        }\n                        elapsedTime += retryInterval;\n                        if (elapsedTime >= timeout) {\n                            clearInterval(interval);\n                            reject(new Error(`超时: 未找到文本为 \"${text}\" 的元素 (选择器: ${selector})`));\n                        }\n                    }, retryInterval);\n                });\n            },\n            setInputValue: (element, text) => {\n                if (!element) throw new Error('输入框不存在');\n                element.innerHTML = text;\n                // 触发 input 和 keyup 事件，确保系统检测到输入\n                element.dispatchEvent(new Event('input', { bubbles: true }));\n                element.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true }));\n            },\n            getInputValue: (element) => {\n                if (!element) return '';\n                return element.textContent || element.value || '';\n            },\n            click: (element) => {\n                if (!element) throw new Error('点击目标不存在');\n                element.click();\n            }\n        };\n\n\n        // ==================== 规则加载器 ====================\n        const RuleLoader = {\n            loadLocal: async () => {\n                try {\n                    let storedValue = GM_getValue(STORAGE_KEYS.LOCAL_RULES, null);\n                    if (!storedValue || storedValue.trim() === '') {\n                        storedValue = DEFAULT_LOCAL_RULES.trim();\n                        GM_setValue(STORAGE_KEYS.LOCAL_RULES, storedValue);\n                    }\n                    const result = RuleParser.parseRuleFile(storedValue);\n                    if (Array.isArray(result.rules) && result.rules.length > 0) {\n                        return result;\n                    } else {\n                        throw new Error('本地规则为空或格式不正确');\n                    }\n                } catch (e) {\n                    Logger.error('加载本地规则失败', e);\n                    throw e;\n                }\n            },\n            loadRemote: async () => {\n                return new Promise((resolve, reject) => {\n                    GM_xmlhttpRequest({\n                        method: 'GET',\n                        url: CONFIG.REMOTE_RULES_URL,\n                        nocache: true,\n                        timeout: 10000,\n                        onload: (response) => {\n                            if (response.status >= 200 && response.status < 300) {\n                                try {\n                                    const result = RuleParser.parseRuleFile(response.responseText);\n                                    if (result.rules.length > 0) {\n                                        resolve(result);\n                                    } else {\n                                        reject(new Error('远程文件未找到有效规则'));\n                                    }\n                                } catch (error) {\n                                    reject(new Error(`远程规则解析错误: ${error.message}`));\n                                }\n                            } else {\n                                reject(new Error(`请求失败，状态码: ${response.status}`));\n                            }\n                        },\n                        onerror: () => reject(new Error('加载远程规则失败')),\n                        ontimeout: () => reject(new Error('加载远程规则超时'))\n                    });\n                });\n            },\n            loadFile: async (file) => {\n                return new Promise((resolve, reject) => {\n                    const reader = new FileReader();\n                    reader.onload = (e) => {\n                        try {\n                            const result = RuleParser.parseRuleFile(e.target.result);\n                            if (result.rules.length > 0) {\n                                resolve(result);\n                            } else {\n                                reject(new Error('文件中未找到有效规则'));\n                            }\n                        } catch (error) {\n                            reject(new Error(`文件解析错误: ${error.message}`));\n                        }\n                    };\n                    reader.onerror = () => reject(new Error('文件读取失败'));\n                    reader.readAsText(file);\n                });\n            }\n        };\n\n        // ==================== 聊天操作 ====================\n        const ChatOperations = {\n            findChatInput: () => {\n                return document.querySelector(CONFIG.SELECTORS.CHAT_INPUT);\n            },\n            findAttachmentSVG: () => {\n                return document.querySelector(CONFIG.SELECTORS.ATTACHMENT_SVG);\n            },\n            findImageOption: async (maxAttempts = USER_CONFIGURABLE_DELAYS.NEW_VERSION.IMAGE_TOOL_MENU_OPTION_FIND_TIMEOUT / 20) => {\n                return new Promise((resolve, reject) => {\n                    let attempts = 0;\n                    const interval = setInterval(() => {\n                        const popups = document.querySelectorAll(CONFIG.SELECTORS.DROPDOWN_MENU_POPUP);\n                        for (const popup of popups) {\n                            const displayStyle = window.getComputedStyle(popup).display;\n                            const visibilityStyle = window.getComputedStyle(popup).visibility;\n                            if (displayStyle !== 'none' && visibilityStyle !== 'hidden') {\n                                const imageOptions = popup.querySelectorAll(CONFIG.SELECTORS.IMAGE_OPTION);\n                                for (const option of imageOptions) {\n                                    const textSpan = option.querySelector('.tools__item__content__sub__text');\n                                    if (textSpan && textSpan.textContent.trim() === '图片') {\n                                        Logger.info('✅ 找到图片选项');\n                                        clearInterval(interval); // 找到后清除定时器\n                                        resolve(option);\n                                        return;\n                                    }\n                                }\n                            }\n                        }\n                        attempts++;\n                        if (attempts >= maxAttempts) {\n                            clearInterval(interval);\n                            reject(new Error(`超时: 未找到图片选项 (尝试 ${attempts} 次)`));\n                        }\n                    }, 20);\n                });\n            },\n            simulateMouseHoverAndWait: async (element) => {\n                if (!element || !document.contains(element)) {\n                    throw new Error('目标元素不存在或已从DOM移除');\n                }\n                try {\n                    AppState.isMovingMouse = true;\n                    const rect = element.getBoundingClientRect();\n                    const centerX = Math.round(rect.left + rect.width / 2);\n                    const centerY = Math.round(rect.top + rect.height / 2);\n                    const steps = 3;\n                    const startX = Math.max(0, centerX - 80);\n                    const startY = Math.max(0, centerY - 80);\n                    for (let i = 0; i <= steps; i++) {\n                        const progress = i / steps;\n                        const moveX = Math.round(startX + (centerX - startX) * progress);\n                        const moveY = Math.round(startY + (centerY - startY) * progress);\n                        const moveEvent = new MouseEvent('mousemove', { bubbles: true, cancelable: true, clientX: moveX, clientY: moveY, screenX: moveX, screenY: moveY });\n                        document.dispatchEvent(moveEvent);\n                        element.dispatchEvent(moveEvent);\n                        await new Promise(res => requestAnimationFrame(res));\n                    }\n                    const mouseEnterEvent = new MouseEvent('mouseenter', { bubbles: true, cancelable: true, clientX: centerX, clientY: centerY });\n                    const mouseOverEvent = new MouseEvent('mouseover', { bubbles: true, cancelable: true, clientX: centerX, clientY: centerY });\n                    element.dispatchEvent(mouseEnterEvent);\n                    element.dispatchEvent(mouseOverEvent);\n                    await new Promise(res => setTimeout(res, USER_CONFIGURABLE_DELAYS.NEW_VERSION.IMAGE_TOOL_MENU_HOVER_HOLD));\n                } catch (error){\n                    Logger.error('模拟鼠标悬停失败', error.message);\n                    throw new Error(`鼠标悬停失败: ${error.message}`);\n                } finally {\n                    AppState.isMovingMouse = false;\n                }\n            },\n            simulateMouseMoveOnMenu: async (menuElement) => {\n                if (!menuElement || !document.contains(menuElement)) {\n                    return;\n                }\n                try {\n                    const rect = menuElement.getBoundingClientRect();\n                    const centerX = Math.round(rect.left + rect.width / 2);\n                    const centerY = Math.round(rect.top + rect.height / 2);\n                    const moveEvent = new MouseEvent('mousemove', { bubbles: true, cancelable: true, clientX: centerX, clientY: centerY });\n                    document.dispatchEvent(moveEvent);\n                    menuElement.dispatchEvent(moveEvent);\n                } catch (error) { /* Ignore errors */ }\n            },\n            findAndSendImageByName: async (imageName) => {\n                try {\n                    Logger.info(`🔍 在表格中查找图片: ${imageName}`);\n                    if (!imageName) throw new Error('图片名称为空');\n\n                    const targetRow = await new Promise(async (resolve, reject) => {\n                        let attempts = 0;\n                        const maxAttempts = USER_CONFIGURABLE_DELAYS.NEW_VERSION.IMAGE_TABLE_LOAD_TIMEOUT / 200; // Check every 200ms\n                        const intervalId = setInterval(() => {\n                            const rows = document.querySelectorAll(CONFIG.SELECTORS.IMAGE_ROW);\n                            if (rows.length === 0) {\n                                // 如果没有行，可能还在加载中，继续等待\n                                if (++attempts >= maxAttempts) {\n                                    clearInterval(intervalId);\n                                    reject(new Error('超时: 图片表格中未找到任何图片'));\n                                }\n                                return;\n                            }\n\n                            for (const row of rows) {\n                                // 优先尝试精确匹配专用名称单元格\n                                const nameCells = row.querySelectorAll(CONFIG.SELECTORS.IMAGE_NAME_CELL);\n                                let matched = false;\n                                for (const nameCell of nameCells) {\n                                    const rowImageName = nameCell.textContent.trim();\n                                    Logger.debug(`📋 检查图片: ${rowImageName}`);\n                                    if (rowImageName === imageName) {\n                                        clearInterval(intervalId);\n                                        Logger.info(`✅ 找到目标图片: ${imageName}`);\n                                        resolve(row);\n                                        matched = true;\n                                        return;\n                                    }\n                                }\n                                // 降级：检查行内是否包含图片名称文本（兼容新版布局）\n                                if (!matched && row.textContent.includes(imageName)) {\n                                    // 确认该行有发送按钮，避免误匹配\n                                    const hasSendBtn = Array.from(row.querySelectorAll(CONFIG.SELECTORS.IMAGE_SEND_BTN)).some(b => b.textContent.trim() === '发送');\n                                    if (hasSendBtn) {\n                                        clearInterval(intervalId);\n                                        Logger.info(`✅ 通过文本内容找到目标图片: ${imageName}`);\n                                        resolve(row);\n                                        return;\n                                    }\n                                }\n                            }\n\n                            if (++attempts >= maxAttempts) {\n                                clearInterval(intervalId);\n                                reject(new Error(`超时: 找不到图片: ${imageName}`));\n                            }\n                        }, 200); // 每200ms检查一次\n                    });\n\n                    // 找到图片后，检查是否重复发送\n                    if (AppState.lastSentImageName === imageName) {\n                        Logger.warn(`⚠️ 检测到可能重复发送同一张图片: ${imageName}，已拦截`);\n                        throw new Error(`防重复: 该图片刚刚发送过，请勿重复操作`);\n                    }\n\n                    const sendButtons = targetRow.querySelectorAll(CONFIG.SELECTORS.IMAGE_SEND_BTN);\n                    if (sendButtons.length === 0) throw new Error('未找到发送按钮');\n                    let sendBtn = null;\n                    for (const btn of sendButtons) {\n                        if (btn.textContent.trim() === '发送') {\n                            sendBtn = btn;\n                            Logger.info(`🔘 找到发送按钮`);\n                            break;\n                        }\n                    }\n                    if (!sendBtn) throw new Error('未找到\"发送\"按钮');\n                    if (sendBtn.disabled || sendBtn.getAttribute('disabled') === 'disabled') throw new Error('发送按钮已禁用');\n\n                    Logger.info(`🖼️ 执行点击发送按钮: ${imageName}`);\n                    AppState.lastSentImageName = imageName; // 记录最后发送的图片\n                    DOMUtils.click(sendBtn);\n                    await new Promise(res => setTimeout(res, USER_CONFIGURABLE_DELAYS.NEW_VERSION.IMAGE_SEND_BUTTON_CLICK_DELAY));\n                    Logger.info(`✨ 图片 ${imageName} 发送指令已执行`);\n                    showNotification(`✨ 图片 ${imageName} 发送完成`);\n                } catch (e) {\n                    Logger.error('发送图片失败', e.message);\n                    showNotification(`❌ 发送图片失败: ${e.message}`, 'error');\n                    AppState.lastSentImageName = ''; // 重置状态\n                    throw e;\n                }\n            },\n            sendText: async (text) => {\n                try {\n                    if (!text) throw new Error('消息内容为空');\n                    const input = ChatOperations.findChatInput();\n                    if (!input) throw new Error('未找到聊天输入框');\n                    DOMUtils.setInputValue(input, text);\n\n                    Logger.info(`📤 尝试在 ${AppState.currentSendMode === 'original' ? '原文' : '翻译'} 模式下发送消息...`);\n\n                    const sendArea = await DOMUtils.findElement(CONFIG.SELECTORS.SEND_AREA, USER_CONFIGURABLE_DELAYS.NEW_VERSION.DROPDOWN_OPEN_TIMEOUT);\n                    const buttonGroup = sendArea.querySelector('.arco-btn-group');\n                    // 获取sendArea下所有直接子元素中的可点击button，用于单一按钮布局判断\n                    const directButtonsInSendArea = sendArea.querySelectorAll(':scope > button.arco-btn:not([disabled])');\n\n                    if (AppState.currentSendMode === 'original') {\n                        // 原文模式: 必须有 arco-btn-group 且其内部恰好有2个可点击按钮\n                        if (!buttonGroup) {\n                            throw new Error('原文模式下必须使用分体式发送按钮组。当前为单一发送按钮布局，不允许发送。请切换到翻译模式或等待页面加载出分体式发送按钮。');\n                        }\n                        const buttonsInGroup = buttonGroup.querySelectorAll('button.arco-btn:not([disabled])');\n                        if (buttonsInGroup.length !== 2) {\n                            throw new Error(`原文模式下分体式发送按钮组不符合预期 (预期2个可点击按钮，实际${buttonsInGroup.length}个)。请检查SalesSmartly界面。`);\n                        }\n                        // 点击按钮组中的第一个按钮 (直接发送按钮)\n                        const directSendButton = buttonsInGroup[0];\n                        DOMUtils.click(directSendButton);\n                        Logger.info(`📤 消息 (原文模式) 已通过按钮组的第一个按钮发送`);\n                        showNotification(`📤 消息 (原文模式) 已发送`);\n\n                    } else { // AppState.currentSendMode === 'translated' (翻译模式)\n                        // 翻译模式: 兼容分体式按钮组或单一按钮布局\n                        if (buttonGroup) {\n                            // 分体式按钮布局 (arco-btn-group 存在)\n                            const buttonsInGroup = buttonGroup.querySelectorAll('button.arco-btn:not([disabled])');\n                            if (buttonsInGroup.length !== 2) {\n                                throw new Error(`翻译模式下分体式发送按钮组不符合预期 (预期2个可点击按钮，实际${buttonsInGroup.length}个)。请检查SalesSmartly界面。`);\n                            }\n                            // 点击下拉菜单触发按钮 (第二个按钮)\n                            const dropdownToggleButton = buttonsInGroup[1];\n                            DOMUtils.click(dropdownToggleButton);\n                            Logger.info(`📤 消息 (翻译模式) 已点击下拉菜单触发按钮`);\n\n                            // 等待下拉菜单弹出并点击 \"原文发送\" 选项\n                            await DOMUtils.findElement(CONFIG.SELECTORS.DROPDOWN_MENU_POPUP, USER_CONFIGURABLE_DELAYS.NEW_VERSION.DROPDOWN_OPTION_FIND_TIMEOUT);\n                            const sendOriginalOptionSpan = await DOMUtils.findElementByText(`${CONFIG.SELECTORS.DROPDOWN_MENU_POPUP} .arco-dropdown-option-content`, '原文发送', USER_CONFIGURABLE_DELAYS.NEW_VERSION.DROPDOWN_OPTION_FIND_TIMEOUT);\n                            const liElement = sendOriginalOptionSpan.closest('li');\n\n                            if (liElement) {\n                                DOMUtils.click(liElement);\n                                Logger.info(`📤 消息 (翻译模式) 已通过\"原文发送\"选项发送`);\n                                showNotification(`📤 消息 (翻译模式) 已发送`);\n                            } else {\n                                throw new Error('未找到\"原文发送\"选项的父级li标签');\n                            }\n                        } else {\n                            // 单一发送按钮布局 (arco-btn-group 不存在)\n                            if (directButtonsInSendArea.length !== 1) {\n                                throw new Error(`翻译模式下单一发送按钮布局不符合预期 (预期1个可点击按钮，实际${directButtonsInSendArea.length}个)。请检查SalesSmartly界面。`);\n                            }\n                            const singleSendButton = directButtonsInSendArea[0];\n                            DOMUtils.click(singleSendButton);\n                            Logger.info(`📤 消息 (翻译模式) 已通过单一发送按钮发送`);\n                            showNotification(`📤 消息 (翻译模式) 已发送`);\n                        }\n                    }\n\n                } catch (e) {\n                    Logger.error('发送文本消息失败', e.message);\n                    showNotification(`❌ 发送失败: ${e.message}`, 'error');\n                    throw e;\n                }\n            },\n            sendImage: async (imageMsg) => {\n                try {\n                    if (AppState.isImageSending) throw new Error('上一张图片还在发送中，请等待...');\n                    Logger.info(`🖼️ 开始发送图片 - 锁定发送状态`);\n                    AppState.isImageSending = true;\n                    try {\n                        const svgElement = ChatOperations.findAttachmentSVG();\n                        if (!svgElement || !document.contains(svgElement)) throw new Error('附件按钮(SVG)未找到');\n                        const hoverPromise = ChatOperations.simulateMouseHoverAndWait(svgElement);\n                        // findImageOption 已经包含了轮询等待逻辑\n                        const imageOptionPromise = ChatOperations.findImageOption(USER_CONFIGURABLE_DELAYS.NEW_VERSION.IMAGE_TOOL_MENU_OPTION_FIND_TIMEOUT / 20);\n\n                        const [, imageOption] = await Promise.all([hoverPromise, imageOptionPromise]);\n                        const popup = imageOption.closest(CONFIG.SELECTORS.DROPDOWN_MENU_POPUP);\n                        if (popup) await ChatOperations.simulateMouseMoveOnMenu(popup);\n                        Logger.info(`✨ 点击图片选项...`);\n                        DOMUtils.click(imageOption);\n\n                        // 替换固定延迟为动态等待图片表格加载完成\n                        Logger.info(`⏳ 等待图片表格加载...`);\n                        await DOMUtils.findElement(CONFIG.SELECTORS.IMAGE_TABLE_BODY, USER_CONFIGURABLE_DELAYS.NEW_VERSION.IMAGE_TABLE_LOAD_TIMEOUT);\n\n                        if (imageMsg.name) {\n                            await ChatOperations.findAndSendImageByName(imageMsg.name);\n                        } else {\n                            throw new Error('未提供图片名称，请在规则中添加 imge_name=图片名称');\n                        }\n                    } finally {\n                        AppState.isImageSending = false;\n                        Logger.info(`🔓 解锁发送状态`);\n                    }\n                } catch (e) {\n                    Logger.error('发送图片失败', e.message);\n                    showNotification(`❌ 发送图片失败: ${e.message}`, 'error');\n                    AppState.lastSentImageName = ''; // 重置状态\n                    throw e;\n                }\n            }\n        };\n\n        // ==================== 自定义规则编辑器 ====================\n        const RuleEditor = {\n            modalOverlay: null,\n            currentRuleId: null,\n            messageCounter: 0,\n            init: () => {\n                RuleEditor.modalOverlay = document.createElement('div');\n                RuleEditor.modalOverlay.className = 'ss-modal-overlay';\n                RuleEditor.modalOverlay.innerHTML = `\n            <div class=\"ss-modal-content\">\n                <div class=\"ss-modal-header\">\n                    <h3 id=\"editor-modal-title\">新增自定义按键</h3>\n                    <button class=\"close-btn\">${COMMON_ICONS.times}</button>\n                </div>\n                <div class=\"ss-modal-body\">\n                    <label for=\"rule-name\">按键名称:</label>\n                    <input type=\"text\" id=\"rule-name\" placeholder=\"输入按键名称\" required>\n                    <label>按钮颜色:</label>\n                    <div class=\"color-picker-group\">\n                        <input type=\"color\" id=\"rule-color\" value=\"#ffffff\">\n                        <div id=\"color-preview\" class=\"color-preview\" style=\"background-color: #ffffff;\"></div>\n                    </div>\n                    <label>消息列表:</label>\n                    <div id=\"messages-container\"></div>\n                    <button id=\"add-text-msg-btn\" class=\"add-custom-rule-btn\" style=\"background: #60a5fa; margin-bottom: 10px;\">${COMMON_ICONS.plus} 添加文本消息</button>\n                    <button id=\"add-image-msg-btn\" class=\"add-custom-rule-btn\" style=\"background: #60a5fa;\">${COMMON_ICONS.plus} 添加图片消息</button>\n                </div>\n                <div class=\"ss-modal-footer\">\n                    <button id=\"delete-rule-btn\" class=\"btn-delete\" style=\"display:none;\">删除</button>\n                    <button id=\"cancel-edit-btn\" class=\"btn-cancel\">取消</button>\n                    <button id=\"save-rule-btn\" class=\"btn-save\">保存</button>\n                </div>\n            </div>`;\n                document.body.appendChild(RuleEditor.modalOverlay);\n                RuleEditor.bindEvents();\n            },\n            bindEvents: () => {\n                RuleEditor.modalOverlay.querySelector('.close-btn').addEventListener('click', RuleEditor.closeEditor);\n                RuleEditor.modalOverlay.querySelector('#cancel-edit-btn').addEventListener('click', RuleEditor.closeEditor);\n                RuleEditor.modalOverlay.querySelector('#save-rule-btn').addEventListener('click', RuleEditor.saveRule);\n                RuleEditor.modalOverlay.querySelector('#delete-rule-btn').addEventListener('click', RuleEditor.deleteRule);\n                RuleEditor.modalOverlay.querySelector('#add-text-msg-btn').addEventListener('click', () => RuleEditor.addMessageInput('text'));\n                RuleEditor.modalOverlay.querySelector('#add-image-msg-btn').addEventListener('click', () => RuleEditor.addMessageInput('image'));\n                RuleEditor.modalOverlay.querySelector('#rule-color').addEventListener('input', (e) => {\n                    RuleEditor.modalOverlay.querySelector('#color-preview').style.backgroundColor = e.target.value;\n                });\n                RuleEditor.modalOverlay.addEventListener('click', (e) => {\n                    if (e.target === RuleEditor.modalOverlay) RuleEditor.closeEditor();\n                });\n            },\n            openEditor: (ruleId = null, initialRuleData = null) => {\n                RuleEditor.currentRuleId = ruleId;\n                RuleEditor.messageCounter = 0;\n                const modalTitle = RuleEditor.modalOverlay.querySelector('#editor-modal-title');\n                const ruleNameInput = RuleEditor.modalOverlay.querySelector('#rule-name');\n                const ruleColorInput = RuleEditor.modalOverlay.querySelector('#rule-color');\n                const colorPreview = RuleEditor.modalOverlay.querySelector('#color-preview');\n                const messagesContainer = RuleEditor.modalOverlay.querySelector('#messages-container');\n                const deleteBtn = RuleEditor.modalOverlay.querySelector('#delete-rule-btn');\n                messagesContainer.innerHTML = '';\n\n                let ruleToEdit = initialRuleData;\n                if (ruleId && !initialRuleData) { // 如果有ID但没有初始数据，说明是编辑现有自定义规则\n                    ruleToEdit = AppState.customRules.find(r => r.id === ruleId);\n                }\n\n                if (ruleToEdit) {\n                    modalTitle.textContent = '编辑自定义按键';\n                    deleteBtn.style.display = 'inline-block';\n                    ruleNameInput.value = ruleToEdit.name || '';\n                    ruleColorInput.value = ruleToEdit.color || '#ffffff';\n                    colorPreview.style.backgroundColor = ruleToEdit.color || '#ffffff';\n                    ruleToEdit.messages.forEach(msg => RuleEditor.addMessageInput(msg.type, msg)); // 确保传递消息数据\n                } else {\n                    modalTitle.textContent = '新增自定义按键';\n                    deleteBtn.style.display = 'none';\n                    ruleNameInput.value = '';\n                    ruleColorInput.value = '#ffffff';\n                    colorPreview.style.backgroundColor = '#ffffff';\n                    RuleEditor.addMessageInput('text'); // 默认添加一个文本消息输入框\n                }\n                RuleEditor.modalOverlay.classList.add('show');\n            },\n            closeEditor: () => {\n                RuleEditor.modalOverlay.classList.remove('show');\n            },\n            addMessageInput: (type, messageData = {}) => {\n                RuleEditor.messageCounter++;\n                const messagesContainer = RuleEditor.modalOverlay.querySelector('#messages-container');\n                const msgId = `msg-${RuleEditor.messageCounter}`;\n                const messageItem = document.createElement('div');\n                messageItem.className = 'message-item';\n                messageItem.dataset.msgType = type;\n                messageItem.dataset.msgId = msgId;\n\n                let content = '';\n                if (type === 'text') {\n                    content = `\n                <label for=\"${msgId}-translated\">译文:</label>\n                <textarea id=\"${msgId}-translated\" placeholder=\"输入译文\" required>${messageData.translated || ''}</textarea>\n                <label for=\"${msgId}-original\">原文 (可选):</label>\n                <textarea id=\"${msgId}-original\" placeholder=\"输入原文 (用于翻译模式发送)\" >${messageData.original || ''}</textarea>`;\n                } else if (type === 'image') {\n                    content = `\n                <label for=\"${msgId}-image-name\">图片名称 (新版):</label>\n                <input type=\"text\" id=\"${msgId}-image-name\" placeholder=\"输入图片完整名称 (如: example.jpg)\" value=\"${messageData.name || ''}\" required>\n           <label for=\"${msgId}-image-index\">图片索引 (旧版, 1开始):</label>\n                <input type=\"number\" id=\"${msgId}-image-index\" placeholder=\"输入图片在附件列表中的序号\" value=\"${messageData.index || ''}\" min=\"1\">`;\n                }\n                messageItem.innerHTML = `${content}<button class=\"remove-msg-btn\">${COMMON_ICONS.times}</button>`; // 使用图标\n                messageItem.querySelector('.remove-msg-btn').addEventListener('click', () => messageItem.remove());\n                messagesContainer.appendChild(messageItem);\n            },\n            saveRule: () => {\n                const ruleName = RuleEditor.modalOverlay.querySelector('#rule-name').value.trim();\n                const ruleColor = RuleEditor.modalOverlay.querySelector('#rule-color').value;\n                const messageElements = RuleEditor.modalOverlay.querySelectorAll('.message-item');\n                if (!ruleName) {\n                    showNotification('按键名称不能为空', 'error');\n                    return;\n                }\n                if (messageElements.length === 0) {\n                    showNotification('至少需要一条消息', 'error');\n                    return;\n                }\n                const messages = [];\n                for (const msgEl of messageElements) {\n                    const type = msgEl.dataset.msgType;\n                    if (type === 'text') {\n                        const translated = msgEl.querySelector('textarea[id$=\"-translated\"]').value.trim();\n                        const original = msgEl.querySelector('textarea[id$=\"-original\"]').value.trim();\n                        if (!translated) {\n                            showNotification('文本消息的译文不能为空', 'error');\n                            return;\n                        }\n                        messages.push({ type: 'text', translated, original: original || null });\n                    } else if (type === 'image') {\n                        const imageName = msgEl.querySelector('input[id$=\"-image-name\"]').value.trim();\n                        const imageIndexStr = msgEl.querySelector('input[id$=\"-image-index\"]').value.trim();\n                        const imageIndex = imageIndexStr ? parseInt(imageIndexStr, 10) : null;\n                        if (!imageName && !imageIndex) {\n                            showNotification('图片消息必须提供图片名称或旧版索引', 'error');\n                            return;\n                        }\n                        if (imageIndex && isNaN(imageIndex)) {\n                            showNotification('旧版图片索引必须是数字', 'error');\n                            return;\n                        }\n                        messages.push({ type: 'image', name: imageName || null, index: imageIndex });\n                    }\n                }\n                let updatedRules = [...AppState.customRules];\n                if (RuleEditor.currentRuleId) {\n                    const ruleIndex = updatedRules.findIndex(r => r.id === RuleEditor.currentRuleId);\n                    if (ruleIndex !== -1) {\n                        updatedRules[ruleIndex] = { ...updatedRules[ruleIndex], name: ruleName, color: ruleColor, messages };\n                        showNotification(`按键 \"${ruleName}\" 已更新`);\n                    }\n                } else {\n                    const newRule = {\n                        id: `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n                        name: ruleName,\n                        color: ruleColor,\n                        messages,\n                        category: AppState.CUSTOM_CATEGORY_NAME,\n                        isCustom: true\n                    };\n                    updatedRules.push(newRule);\n                    showNotification(`按键 \"${newRule.name}\" 已添加`);\n                }\n                AppState.setCustomRules(updatedRules);\n                RuleEditor.closeEditor();\n                panelManager.loadRules();\n            },\n            deleteRule: () => {\n                if (!RuleEditor.currentRuleId || !confirm('确定要删除此自定义按键吗？')) return;\n                const updatedRules = AppState.customRules.filter(r => r.id !== RuleEditor.currentRuleId);\n                AppState.setCustomRules(updatedRules);\n                showNotification('按键已删除');\n                RuleEditor.closeEditor();\n                panelManager.loadRules();\n            }\n        };\n\n        // ==================== 拖放逻辑 ====================\n        let dragMouseDownListener = null;\n        let dragMouseMoveListener = null;\n        let dragMouseUpListener = null;\n\n        function setupDragAndDrop(container) {\n            // 确保只绑定一次事件监听器\n            if (dragMouseDownListener) {\n                container.removeEventListener('mousedown', dragMouseDownListener);\n                document.removeEventListener('mousemove', dragMouseMoveListener);\n                document.removeEventListener('mouseup', dragMouseUpListener);\n            }\n\n            dragMouseDownListener = (e) => {\n                const dragHandle = e.target.closest('.drag-handle');\n                if (!dragHandle) return;\n\n                e.preventDefault();\n                document.body.style.cursor = 'grabbing';\n                document.body.style.userSelect = 'none';\n\n                const originalElement = dragHandle.closest('.action-btn'); // 任何action-btn都可以被拖拽\n                if (!originalElement) return;\n\n                const ruleId = originalElement.dataset.ruleId;\n                const isCustom = originalElement.dataset.isCustom === 'true';\n\n                // 找到对应的规则对象\n                let draggedRule = null;\n                if (isCustom) {\n                    draggedRule = AppState.customRules.find(r => r.id === ruleId);\n                } else {\n                    draggedRule = AppState.allParsedRules.find(r => r.id === ruleId);\n                }\n                if (!draggedRule) {\n                    Logger.error('未找到被拖拽的规则对象', {\n                        ruleId,\n                        isCustom\n                    });\n                    return;\n                }\n\n                // 计算偏移量，使幽灵元素中心对准鼠标\n                const rect = originalElement.getBoundingClientRect();\n                const offsetX = e.clientX - (rect.left + rect.width / 2);\n                const offsetY = e.clientY - (rect.top + rect.height / 2);\n\n                const ghostElement = originalElement.cloneNode(true);\n                ghostElement.classList.add('ss-drag-ghost');\n                ghostElement.style.width = `${rect.width}px`;\n                ghostElement.style.height = `${rect.height}px`;\n                ghostElement.style.left = `${e.clientX - offsetX}px`; // 初始位置也居中\n                ghostElement.style.top = `${e.clientY - offsetY}px`;\n                document.body.appendChild(ghostElement);\n\n                AppState.dragState = {\n                    isDragging: true,\n                    ghostElement,\n                    originalElement,\n                    draggedId: ruleId,\n                    draggedRule: draggedRule, // 存储规则对象\n                    isCustomSource: isCustom,\n                    offsetX,\n                    offsetY,\n                };\n\n                originalElement.classList.add('dragging-source');\n            };\n\n            dragMouseMoveListener = (e) => {\n                if (!AppState.dragState.isDragging) return;\n                const {\n                    ghostElement,\n                    offsetX,\n                    offsetY\n                } = AppState.dragState;\n                if (ghostElement) {\n                    ghostElement.style.left = `${e.clientX - offsetX}px`;\n                    ghostElement.style.top = `${e.clientY - offsetY}px`;\n                }\n                updateDropIndicator(e);\n            };\n\n            dragMouseUpListener = (e) => {\n                if (!AppState.dragState.isDragging) return;\n\n                document.body.style.cursor = '';\n                document.body.style.userSelect = '';\n\n                const {\n                    ghostElement,\n                    originalElement,\n                    draggedId,\n                    draggedRule,\n                    isCustomSource\n                } = AppState.dragState;\n\n                ghostElement.remove();\n                originalElement.classList.remove('dragging-source');\n                document.querySelectorAll('.drag-over-indicator').forEach(el => el.remove());\n                document.querySelectorAll('.category-header.custom-category.drag-over-target').forEach(el => el.classList.remove('drag-over-target'));\n\n\n                const dropTarget = findDropTarget(e);\n                let rules = [...AppState.customRules]; // 只操作自定义规则\n\n                if (dropTarget) {\n                    // 拖拽到自定义分类头部\n                    if (dropTarget.element.classList.contains('custom-category')) {\n                        if (!isCustomSource) { // 非自定义规则拖拽到自定义分类头部\n                            const newCustomRule = {\n                                ...draggedRule,\n                                id: `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n                                category: AppState.CUSTOM_CATEGORY_NAME,\n                                isCustom: true\n                            };\n                            rules.push(newCustomRule);\n                            AppState.setCustomRules(rules);\n                            showNotification(`按键 \"${newCustomRule.name}\" 已复制到自定义分类`);\n                        } else { // 自定义规则拖拽到自定义分类头部，视为拖拽到末尾\n                            const draggedIndex = rules.findIndex(r => r.id === draggedId);\n                            if (draggedIndex > -1) {\n                                const [ruleToMove] = rules.splice(draggedIndex, 1);\n                                rules.push(ruleToMove);\n                                AppState.setCustomRules(rules);\n                                showNotification(`按键 \"${ruleToMove.name}\" 已移动到自定义分类末尾`);\n                            }\n                        }\n                    } else if (dropTarget.element.dataset.ruleId) { // 拖拽到某个按钮上\n                        const targetRuleId = dropTarget.element.dataset.ruleId;\n                        const targetIsCustom = dropTarget.element.dataset.isCustom === 'true';\n\n                        if (targetIsCustom) { // 目标是自定义规则\n                            const draggedIndex = rules.findIndex(r => r.id === draggedId);\n                            let targetIndex = rules.findIndex(r => r.id === targetRuleId);\n\n                            if (draggedIndex > -1 && targetIndex > -1) {\n                                if (isCustomSource) { // 自定义规则之间拖拽，进行排序\n                                    if (draggedId !== targetRuleId) {\n                                        const [ruleToMove] = rules.splice(draggedIndex, 1);\n                                        if (draggedIndex < targetIndex) {\n                                            targetIndex--;\n                                        }\n                                        if (dropTarget.position === 'after') {\n                                            rules.splice(targetIndex + 1, 0, ruleToMove);\n                                        } else {\n                                            rules.splice(targetIndex, 0, ruleToMove);\n                                        }\n                                        AppState.setCustomRules(rules);\n                                        showNotification(`按键 \"${ruleToMove.name}\" 顺序已更新`);\n                                    }\n                                } else { // 非自定义规则拖拽到自定义规则上，进行复制\n                                    const newCustomRule = {\n                                        ...draggedRule,\n                                        id: `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n                                        category: AppState.CUSTOM_CATEGORY_NAME,\n                                        isCustom: true\n                                    };\n                                    if (dropTarget.position === 'after') {\n                                        rules.splice(targetIndex + 1, 0, newCustomRule);\n                                    } else {\n                                        rules.splice(targetIndex, 0, newCustomRule);\n                                    }\n                                    AppState.setCustomRules(rules);\n                                    showNotification(`按键 \"${newCustomRule.name}\" 已复制到自定义分类`);\n                                }\n                            }\n                        } else { // 目标是非自定义规则，但用户拖拽到其上方，且在自定义分类中\n                            // 这种情况通常意味着用户想将规则复制到自定义分类，并放在某个非自定义规则的“位置”\n                            // 但由于非自定义规则本身不能被改变顺序，所以我们将其视为拖拽到自定义分类的末尾或最近的自定义规则旁边\n                            // 如果用户拖拽到非自定义规则上，但该非自定义规则不在自定义分类中，则不进行操作\n                            showNotification('只能将按键拖拽到“自定义”分类内或其头部', 'error');\n                        }\n                    }\n                }\n\n                panelManager.loadRules(); // 重新渲染以反映新顺序或新添加的规则\n\n                // 清理拖拽状态\n                AppState.dragState = {\n                    isDragging: false,\n                    ghostElement: null,\n                    originalElement: null,\n                    draggedId: null,\n                    draggedRule: null,\n                    isCustomSource: false,\n                    offsetX: 0,\n                    offsetY: 0,\n                };\n            };\n\n            container.addEventListener('mousedown', dragMouseDownListener);\n            document.addEventListener('mousemove', dragMouseMoveListener);\n            document.addEventListener('mouseup', dragMouseUpListener);\n\n            function findDropTarget(e) {\n                // 检查是否拖拽到自定义分类的头部\n                const customCategoryHeader = document.querySelector('.category-header.custom-category');\n                if (customCategoryHeader) {\n                    const headerRect = customCategoryHeader.getBoundingClientRect();\n                    if (e.clientX >= headerRect.left && e.clientX <= headerRect.right &&\n                        e.clientY >= headerRect.top && e.clientY <= headerRect.bottom) {\n                        return {\n                            element: customCategoryHeader,\n                            position: 'header'\n                        };\n                    }\n                }\n\n                // 排除幽灵元素本身和拖拽源元素\n                const customButtons = Array.from(container.querySelectorAll('.action-btn:not(.dragging-source)'));\n                for (const btn of customButtons) {\n                    // 只有当目标按钮在自定义分类中时，才认为是有效目标\n                    const ruleCategory = AppState.getAllRulesCombined().find(r => r.id === btn.dataset.ruleId)?.category;\n                    if (ruleCategory !== AppState.CUSTOM_CATEGORY_NAME) continue;\n\n                    const rect = btn.getBoundingClientRect();\n                    // 检查鼠标是否在按钮范围内\n                    if (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom) {\n                        const isAfter = e.clientY > rect.top + rect.height / 2; // 判断是上半部分还是下半部分\n                        return {\n                            element: btn,\n                            position: isAfter ? 'after' : 'before'\n                        };\n                    }\n                }\n                return null;\n            }\n\n            function updateDropIndicator(e) {\n                document.querySelectorAll('.drag-over-indicator').forEach(el => el.remove()); // 每次移动都清除旧指示器\n                document.querySelectorAll('.category-header.custom-category.drag-over-target').forEach(el => el.classList.remove('drag-over-target'));\n\n                const dropTarget = findDropTarget(e);\n                if (dropTarget) {\n                    if (dropTarget.element.classList.contains('custom-category')) {\n                        dropTarget.element.classList.add('drag-over-target');\n                    } else {\n                        const indicator = document.createElement('div');\n                        indicator.className = 'drag-over-indicator';\n                        // 插入到目标按钮的前面或后面\n                        if (dropTarget.position === 'after') {\n                            dropTarget.element.parentNode.insertBefore(indicator, dropTarget.element.nextSibling);\n                        } else {\n                            dropTarget.element.parentNode.insertBefore(indicator, dropTarget.element);\n                        }\n                    }\n                }\n            }\n        }\n\n\n        // ==================== UI 渲染 ====================\n        const UIRenderer = {\n            hoverInfoPopup: null,\n            initHoverInfoPopup: () => {\n                UIRenderer.hoverInfoPopup = document.createElement('div');\n                UIRenderer.hoverInfoPopup.className = 'ss-hover-info-popup';\n                document.body.appendChild(UIRenderer.hoverInfoPopup);\n            },\n            showHoverInfo: (rule, buttonElement) => {\n                clearTimeout(AppState.hoverInfoPopupTimer);\n                AppState.hoverInfoPopupTimer = setTimeout(() => {\n                    if (!UIRenderer.hoverInfoPopup || !buttonElement || !document.contains(buttonElement)) return;\n\n                    let content = `<strong>${rule.name}</strong>`;\n                    rule.messages.forEach((msg, index) => {\n                        content += `<div class=\"info-section\">`;\n                        if (msg.type === 'text') {\n                            content += `<div class=\"info-section-title\">${COMMON_ICONS.comment} 文本消息 ${index + 1}:</div>`;\n                            content += `<pre>${msg.translated}</pre>`;\n                            if (msg.original) {\n                                content += `<div class=\"info-section-title\">原文:</div>`;\n                                content += `<pre>${msg.original}</pre>`;\n                            }\n                        } else if (msg.type === 'image') {\n                            content += `<div class=\"info-section-title\">${COMMON_ICONS.folder} 图片消息 ${index + 1}:</div>`;\n                            content += `<pre>名称: ${msg.name || '未指定'}</pre>`;\n                            if (msg.index) {\n                                content += `<pre>旧版索引: ${msg.index}</pre>`;\n                            }\n                        }\n                        content += `</div>`;\n                    });\n\n                    UIRenderer.hoverInfoPopup.innerHTML = content;\n\n                    const panelRect = panelManager.panel.getBoundingClientRect();\n                    const buttonRect = buttonElement.getBoundingClientRect();\n\n                    // 定位在面板左侧\n                    let popupLeft = panelRect.left - UIRenderer.hoverInfoPopup.offsetWidth - 15; // 15px margin\n                    let popupTop = buttonRect.top;\n\n                    // 确保不超出视口左边界\n                    if (popupLeft < 0) {\n                        popupLeft = panelRect.right + 15; // 如果左侧空间不足，则显示在面板右侧\n                    }\n                    // 确保不超出视口顶部和底部\n                    if (popupTop < 0) popupTop = 0;\n                    if (popupTop + UIRenderer.hoverInfoPopup.offsetHeight > window.innerHeight) {\n                        popupTop = window.innerHeight - UIRenderer.hoverInfoPopup.offsetHeight;\n                    }\n\n                    UIRenderer.hoverInfoPopup.style.left = `${popupLeft}px`;\n                    UIRenderer.hoverInfoPopup.style.top = `${popupTop}px`;\n                    UIRenderer.hoverInfoPopup.classList.add('show');\n                }, USER_CONFIGURABLE_DELAYS.NEW_VERSION.HOVER_INFO_POPUP_DELAY);\n            },\n            hideHoverInfo: () => {\n                clearTimeout(AppState.hoverInfoPopupTimer);\n                if (UIRenderer.hoverInfoPopup) {\n                    UIRenderer.hoverInfoPopup.classList.remove('show');\n                }\n            },\n            renderButtonsPanel: (container) => {\n                container.innerHTML = '';\n                const allRules = AppState.getAllRulesCombined();\n                const rulesByCategory = new Map();\n\n                // 应用紧凑模式\n                if (AppState.compactMode) {\n                    container.classList.add('compact-mode');\n                } else {\n                    container.classList.remove('compact-mode');\n                }\n\n                // 将所有规则按分类分组\n                allRules.forEach(rule => {\n                    if (!rulesByCategory.has(rule.category)) {\n                        rulesByCategory.set(rule.category, []);\n                    }\n                    rulesByCategory.get(rule.category).push(rule);\n                });\n\n                // 按照 AppState.selectedCategoriesOrder 的顺序来渲染分类\n                AppState.selectedCategoriesOrder.forEach(category => {\n                    // 只有当分类不在隐藏列表中时才渲染\n                    if (!AppState.hiddenCategories.includes(category)) {\n                        const categoryRules = rulesByCategory.get(category) || [];\n                        const isCustomCategory = category === AppState.CUSTOM_CATEGORY_NAME;\n\n                        // 只有当是自定义分类，或者其他分类有规则时才渲染\n                        if (isCustomCategory || categoryRules.length > 0) {\n                            UIRenderer._renderCategoryHeader(container, category, isCustomCategory);\n\n                            // 确保自定义分类的规则是按照 AppState.customRules 的顺序\n                            const rulesToDisplay = isCustomCategory ? AppState.customRules : categoryRules;\n\n                            rulesToDisplay.forEach(rule => {\n                                const button = document.createElement('div');\n                                button.className = `action-btn`;\n                                if (rule.isCustom) {\n                                    button.classList.add('custom-rule');\n                                }\n                                if (rule.color) {\n                                    button.style.backgroundColor = rule.color;\n                                    button.style.color = UIRenderer.getContrastTextColor(rule.color);\n                                }\n                                button.innerHTML = `\n                                    <span class=\"action-icon-wrapper\">${COMMON_ICONS.comment}</span>\n                                    <span>${rule.name}</span>\n                                    <div class=\"hover-actions\" data-rule-id=\"${rule.id}\">\n                                        ${rule.isCustom ? `<button class=\"hover-action-btn edit-rule-btn\" title=\"编辑\">${COMMON_ICONS.edit}</button>` : ''}\n                                    </div>\n                                    <div class=\"drag-handle-wrapper\">\n                                        <button class=\"hover-action-btn drag-handle\" title=\"拖动\">${COMMON_ICONS.gripVertical}</button>\n                                    </div>`;\n\n                                button.dataset.ruleId = rule.id;\n                                button.dataset.isCustom = rule.isCustom; //标记是否为自定义规则\n\n                                // 绑定悬浮显示操作按钮的逻辑\n                                let hoverTimer;\n                                button.addEventListener('mouseenter', () => {\n                                    clearTimeout(hoverTimer);\n                                    hoverTimer = setTimeout(() => button.classList.add('show-actions'), USER_CONFIGURABLE_DELAYS.NEW_VERSION.HOVER_ACTION_DELAY);\n                                    UIRenderer.showHoverInfo(rule, button); // 显示悬浮信息\n                                });\n                                button.addEventListener('mouseleave', () => {\n                                    clearTimeout(hoverTimer);\n                                    button.classList.remove('show-actions');\n                                    UIRenderer.hideHoverInfo(); // 隐藏悬浮信息\n                                });\n\n                                // 绑定点击事件 - 优先处理子元素点击\n                                button.addEventListener('click', (event) => {\n                                    if (event.target.closest('.drag-handle')) {\n                                        // 点击拖拽手柄时不触发按钮功能\n                                        return;\n                                    }\n                                    if (event.target.closest('.edit-rule-btn')) {\n                                        event.stopPropagation(); // 阻止事件冒泡，避免触发父元素的点击事件\n                                        RuleEditor.openEditor(rule.id);\n                                        return; // 已经处理，不再继续\n                                    }\n                                    if (event.target.closest('.action-icon-wrapper')) {\n                                        UIRenderer.populateRuleContent(rule);\n                                    } else {\n                                        RuleExecutor.executeRule(rule);\n                                    }\n                                });\n\n                                container.appendChild(button);\n                            });\n                        }\n                    }\n                });\n\n                // 如果没有可见规则，显示提示信息\n                const hasVisibleRules = AppState.selectedCategoriesOrder.some(cat => {\n                    return !AppState.hiddenCategories.includes(cat) && (\n                        (cat === AppState.CUSTOM_CATEGORY_NAME && AppState.customRules.length > 0) ||\n                        (cat !== AppState.CUSTOM_CATEGORY_NAME && rulesByCategory.get(cat) && rulesByCategory.get(cat).length > 0)\n                    );\n                });\n\n                if (!hasVisibleRules) {\n                    container.innerHTML = `<div class=\"loading\">${COMMON_ICONS.exclamationTriangle}<span>当前没有规则可显示。请在设置中选择要显示的分类。</span></div>`;\n                }\n            },\n            _renderCategoryHeader: (container, categoryName, isCustomCategory) => {\n                const categoryHeader = document.createElement('div');\n                categoryHeader.className = `category-header ${isCustomCategory ? 'custom-category' : ''}`;\n                categoryHeader.textContent = categoryName;\n                if (isCustomCategory) {\n                    const addBtn = document.createElement('button');\n                    addBtn.className = 'add-custom-rule-btn';\n                    addBtn.innerHTML = `${COMMON_ICONS.plus} 增加按键`;\n                    addBtn.title = '添加新的自定义快捷回复';\n                    addBtn.addEventListener('click', (e) => {\n                        e.stopPropagation(); // 阻止事件冒泡，避免影响拖拽等\n                        RuleEditor.openEditor();\n                    });\n                    categoryHeader.appendChild(addBtn);\n                }\n                container.appendChild(categoryHeader);\n            },\n            populateRuleContent: (rule) => {\n                const firstTextMessage = rule.messages.find(msg => msg.type === 'text');\n                if (!firstTextMessage) {\n                    showNotification(`警告: 规则 \"${rule.name}\" 中没有文本消息可填充`, 'error');\n                    return;\n                }\n                const textToPopulate = (AppState.currentSendMode === 'original' && firstTextMessage.original) ? firstTextMessage.original : firstTextMessage.translated;\n                if (!textToPopulate) {\n                    showNotification(`警告: 模式为'${AppState.currentSendMode}'，但文本为空`, 'error');\n                    return;\n                }\n                const input = ChatOperations.findChatInput();\n                if (!input) {\n                    showNotification('未找到聊天输入框', 'error');\n                    return;\n                }\n                DOMUtils.setInputValue(input, textToPopulate);\n                showNotification(`已填充`);\n            },\n            getContrastTextColor: (hexcolor) => {\n                if (!hexcolor || typeof hexcolor !== 'string' || !/^#([A-Fa-f0-9]{3}){1,2}$/.test(hexcolor)) return '#333';\n                let r = 0,\n                    g = 0,\n                    b = 0;\n                if (hexcolor.length === 4) {\n                    r = parseInt(hexcolor[1] + hexcolor[1], 16);\n                    g = parseInt(hexcolor[2] + hexcolor[2], 16);\n                    b = parseInt(hexcolor[3] + hexcolor[3], 16);\n                } else if (hexcolor.length === 7) {\n                    r = parseInt(hexcolor.substring(1, 3), 16);\n                    g = parseInt(hexcolor.substring(3, 5), 16);\n                    b = parseInt(hexcolor.substring(5, 7), 16);\n                }\n                const toLinear = (c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n                const L = 0.2126 * toLinear(r / 255) + 0.7152 * toLinear(g / 255) + 0.0722 * toLinear(b / 255);\n                return L > 0.179 ? '#333' : '#fff';\n            },\n            updateReplyStatus: (statusText, isReplying) => {\n                const statusElement = document.getElementById('ss-reply-status');\n                if (statusElement) {\n                    statusElement.textContent = statusText;\n                    statusElement.classList.remove('idle', 'replying');\n                    statusElement.classList.add(isReplying ? 'replying' : 'idle');\n                }\n                AppState.setReplyStatus(isReplying);\n            }\n        };\n\n        // ==================== 规则执行器 ====================\n        const RuleExecutor = {\n            executeRule: async (rule) => {\n                if (AppState.isReplying) {\n                    showNotification('正在回复中...', 'error');\n                    return;\n                }\n                Logger.info(`▶️ 执行规则: ${rule.name}`);\n                UIRenderer.updateReplyStatus('回复中', true);\n                try {\n                    for (const msg of rule.messages) {\n                        if (msg.type === 'text') {\n                            const textToSend = (AppState.currentSendMode === 'original' && msg.original) ? msg.original : msg.translated;\n                            if (!textToSend) {\n                                showNotification(`文本为空`, 'error');\n                                continue;\n                            }\n                            await ChatOperations.sendText(textToSend);\n                            await new Promise(res => setTimeout(res, USER_CONFIGURABLE_DELAYS.NEW_VERSION.MESSAGE_SEND_AFTER_CLICK));\n                        } else if (msg.type === 'image') {\n                            await ChatOperations.sendImage(msg);\n                            await new Promise(res => setTimeout(res, USER_CONFIGURABLE_DELAYS.NEW_VERSION.MESSAGE_SEND_AFTER_CLICK));\n                        }\n                    }\n                    Logger.info(`✅ 规则执行完成`);\n                    showNotification(`✅ 执行完成`);\n                } catch (err) {\n                    Logger.error('规则执行失败', err.message);\n                } finally {\n                    UIRenderer.updateReplyStatus('空闲', false);\n                    AppState.lastSentImageName = '';\n                }\n            }\n        };\n\n        // ==================== 持续高亮监测 ====================\n        function startHighlightMonitoring() {\n            if (AppState.highlightCheckInterval) clearInterval(AppState.highlightCheckInterval);\n            AppState.highlightCheckInterval = setInterval(() => {\n                const svgElement = document.querySelector(CONFIG.SELECTORS.ATTACHMENT_SVG);\n                if (svgElement && !AppState.highlightedSVG) {\n                    AppState.highlightedSVG = svgElement;\n                    const updateHighlightBox = () => {\n                        document.querySelectorAll('.ss-highlight-box').forEach(el => el.remove());\n                        const rect = svgElement.getBoundingClientRect();\n                        const highlightBox = document.createElement('div');\n                        highlightBox.className = 'ss-highlight-box';\n                        highlightBox.style.left = `${rect.left - 8}px`;\n                        highlightBox.style.top = `${rect.top - 8}px`;\n                        highlightBox.style.width = `${rect.width + 16}px`;\n                        highlightBox.style.height = `${rect.height + 16}px`;\n                        document.body.appendChild(highlightBox);\n                        AppState.highlightBox = highlightBox;\n                    };\n                    updateHighlightBox();\n                    const positionMonitor = setInterval(() => {\n                        if (!svgElement || !document.contains(svgElement)) {\n                            clearInterval(positionMonitor);\n                            AppState.highlightedSVG = null;\n                            document.querySelectorAll('.ss-highlight-box').forEach(el => el.remove());\n                            return;\n                        }\n                        updateHighlightBox();\n                    }, 100);\n                    const observer = new MutationObserver(() => {\n                        if (!document.contains(svgElement)) {\n                            clearInterval(positionMonitor);\n                            observer.disconnect();\n                            AppState.highlightedSVG = null;\n                            document.querySelectorAll('.ss-highlight-box').forEach(el => el.remove());\n                        }\n                    });\n                    observer.observe(document.body, {\n                        childList: true,\n                        subtree: true\n                    });\n                } else if (!svgElement && AppState.highlightedSVG) {\n                    AppState.highlightedSVG = null;\n                    document.querySelectorAll('.ss-highlight-box').forEach(el => el.remove());\n                }\n            }, USER_CONFIGURABLE_DELAYS.NEW_VERSION.HIGHLIGHT_CHECK || 500);\n        }\n\n        // ==================== 面板管理器 ====================\n        let panelManager; // Declare globally within this scope\n        class PanelManager {\n            constructor() {\n                this.panel = null;\n                this.controlButtons = null;\n                this.fileInput = null;\n            }\n            async loadRules(isManualRefresh = false) {\n                const container = this.panel.querySelector(CONFIG.SELECTORS.BUTTONS_CONTAINER);\n                container.innerHTML = `<div class=\"loading\">${COMMON_ICONS.spinner}<span>正在加载规则...</span></div>`;\n                try {\n                    let parsedRulesResult;\n                    if (AppState.useRemoteRules) {\n                        parsedRulesResult = await RuleLoader.loadRemote();\n                    } else {\n                        parsedRulesResult = await RuleLoader.loadLocal();\n                    }\n                    AppState.allParsedRules = parsedRulesResult.rules;\n                    AppState.allAvailableCategories = parsedRulesResult.categories;\n                    try {\n                        const storedCustomRules = GM_getValue(STORAGE_KEYS.CUSTOM_RULES, '[]');\n                        AppState.customRules = JSON.parse(storedCustomRules);\n                    } catch (e) {\n                        Logger.error('加载自定义规则失败，重置为 []', e);\n                        AppState.customRules = [];\n                        GM_setValue(STORAGE_KEYS.CUSTOM_RULES, '[]');\n                    }\n\n                    const storedSelectedOrder = GM_getValue(STORAGE_KEYS.SELECTED_CATEGORIES, []);\n                    const storedHiddenCategories = GM_getValue(STORAGE_KEYS.HIDDEN_CATEGORIES, []);\n                    AppState.initializeSelectedCategories(storedSelectedOrder, storedHiddenCategories);\n\n                    this.populateCategorySelect(AppState.getAllCategoriesCombined());\n                    UIRenderer.renderButtonsPanel(container);\n                    if (isManualRefresh) {\n                        showNotification(`成功刷新 ${AppState.getAllRulesCombined().length} 条规则`);\n                    }\n                } catch (e) {\n                    Logger.error('加载规则失败', e.message);\n                    container.innerHTML = `<div class=\"loading\">${COMMON_ICONS.exclamationTriangle}<span>${e.message}</span></div>`;\n                    showNotification(e.message, 'error');\n                }\n            }\n            populateCategorySelect(categories) {\n                const categoryOptionsPopup = this.panel.querySelector('#category-options-popup');\n                if (!categoryOptionsPopup) return;\n                categoryOptionsPopup.innerHTML = '';\n\n                // 按照 categories 数组的顺序来渲染复选框 (categories 此时是所有可用分类，已排序)\n                const sortedCategoriesForPopup = [...categories].sort((a, b) => {\n                    if (a === AppState.CUSTOM_CATEGORY_NAME) return -1;\n                    if (b === AppState.CUSTOM_CATEGORY_NAME) return 1;\n                    return a.localeCompare(b);\n                });\n\n                sortedCategoriesForPopup.forEach(category => {\n                    const label = document.createElement('label');\n                    const checkbox = document.createElement('input');\n                    checkbox.type = 'checkbox';\n                    checkbox.name = 'category';\n                    checkbox.value = category;\n                    // Checkbox 勾选状态取决于它是否在 hiddenCategories 中\n                    checkbox.checked = !AppState.hiddenCategories.includes(category);\n                    label.appendChild(checkbox);\n                    const textSpan = document.createElement('span');\n                    textSpan.textContent = category;\n                    label.appendChild(textSpan);\n                    categoryOptionsPopup.appendChild(label);\n                });\n                this.updateCategoryDisplay();\n            }\n            create() {\n                this.panel = document.createElement('div');\n                this.panel.className = 'ss-panel button-panel';\n                this.panel.style.width = `${CONFIG.PANEL.WIDTH}px`;\n                this.panel.style.height = `${CONFIG.PANEL.HEIGHT}px`;\n                if (CONFIG.PANEL.VERTICAL_ANCHOR === 'bottom') {\n                    this.panel.style.bottom = `${CONFIG.PANEL.VERTICAL_OFFSET}px`;\n                    this.panel.style.top = 'auto';\n                } else {\n                    this.panel.style.top = `${CONFIG.PANEL.VERTICAL_OFFSET}px`;\n                    this.panel.style.bottom = 'auto';\n                }\n                if (CONFIG.PANEL.HORIZONTAL_ANCHOR === 'left') {\n                    this.panel.style.left = `${CONFIG.PANEL.HORIZONTAL_OFFSET}px`;\n                    this.panel.style.right = 'auto';\n                } else {\n                    this.panel.style.right = `${CONFIG.PANEL.HORIZONTAL_OFFSET}px`;\n                    this.panel.style.left = 'auto';\n                }\n                this.panel.innerHTML = `\n<div class=\"panel-header\">\n<div class=\"panel-title-group\">\n<span>智能客服助手</span>\n<span id=\"ss-reply-status\" class=\"reply-status-indicator idle\">空闲</span>\n</div>\n<button class=\"panel-btn settings-toggle-btn\" title=\"设置\">${COMMON_ICONS.cog}</button>\n<div class=\"settings-popup\">\n<div class=\"settings-item\">\n<span class=\"settings-label\">使用远程规则</span>\n<label class=\"switch-container\">\n    <input type=\"checkbox\" id=\"remote-rules-toggle\">\n    <span class=\"switch-slider\"></span>\n</label>\n</div>\n<div class=\"settings-item\">\n<span class=\"settings-label\">紧凑模式</span>\n<label class=\"switch-container\">\n    <input type=\"checkbox\" id=\"compact-mode-toggle\">\n    <span class=\"switch-slider\"></span>\n</label>\n</div>\n<div class=\"settings-item send-mode-switch\" title=\"切换发送模式\">\n<span>翻译</span>\n<label class=\"switch-container\">\n    <input type=\"checkbox\" id=\"send-mode-toggle\">\n    <span class=\"switch-slider\"></span>\n</label>\n<span>原文</span>\n</div>\n<div class=\"category-select-wrapper\">\n<label>筛选分类:</label>\n<button id=\"category-select-toggle\" class=\"category-toggle-btn\">\n<span id=\"selected-categories-display\">选择分类...</span>${COMMON_ICONS.chevronDown}\n</button>\n<div id=\"category-options-popup\" class=\"category-options-popup\"></div>\n</div>\n<button class=\"popup-control-btn refresh-btn\">${COMMON_ICONS.sync}刷新规则</button>\n<button class=\"popup-control-btn import-btn\">${COMMON_ICONS.folder}导入本地规则</button>\n<button class=\"popup-control-btn close-btn\">${COMMON_ICONS.times}关闭面板</button>\n</div>\n</div>\n<div class=\"${CONFIG.SELECTORS.BUTTONS_CONTAINER.substring(1)}\"><div class=\"loading\">${COMMON_ICONS.spinner}<span>正在加载规则...</span></div></div>\n<div class=\"resize-handle resize-handle-e\"></div><div class=\"resize-handle resize-handle-s\"></div><div class=\"resize-handle resize-handle-se\"></div>\n<div class=\"size-info\">${CONFIG.PANEL.WIDTH}×${CONFIG.PANEL.HEIGHT}px</div>`;\n                document.body.appendChild(this.panel);\n                this.controlButtons = document.createElement('div');\n                this.controlButtons.className = 'control-buttons';\n                this.controlButtons.innerHTML = `<div class=\"control-btn\" id=\"toggleButtonsPanel\" title=\"显示/隐藏面板\">${COMMON_ICONS.thLarge}</div>`;\n                document.body.appendChild(this.controlButtons);\n                const savedBtnPos = GM_getValue(STORAGE_KEYS.CONTROL_BTN_POS, {\n                    top: window.innerHeight - 80,\n                    left: window.innerWidth - 80\n                });\n                this.controlButtons.style.top = `${savedBtnPos.top}px`;\n                this.controlButtons.style.left = `${savedBtnPos.left}px`;\n                this.fileInput = document.createElement('input');\n                this.fileInput.type = 'file';\n                this.fileInput.accept = '.txt';\n                this.fileInput.className = 'file-input';\n                document.body.appendChild(this.fileInput);\n\n                UIRenderer.initHoverInfoPopup(); // 初始化悬浮信息提示框\n\n                this.bindEvents();\n                setTimeout(() => {\n                    this.panel.classList.add('show');\n                    this.loadRules();\n                }, USER_CONFIGURABLE_DELAYS.PANEL_INITIAL_SHOW);\n            }\n            bindEvents() {\n                const toggleButtonsPanel = this.controlButtons.querySelector('#toggleButtonsPanel');\n                const settingsToggleBtn = this.panel.querySelector('.settings-toggle-btn');\n                const settingsPopup = this.panel.querySelector('.settings-popup');\n                const remoteRulesToggle = this.panel.querySelector('#remote-rules-toggle');\n                const compactModeToggle = this.panel.querySelector('#compact-mode-toggle'); // 新增\n                const categorySelectToggle = this.panel.querySelector('#category-select-toggle');\n                const categoryOptionsPopup = this.panel.querySelector('#category-options-popup');\n                const sendModeToggle = this.panel.querySelector('#send-mode-toggle');\n                const refreshBtn = this.panel.querySelector('.refresh-btn');\n                const importBtn = this.panel.querySelector('.import-btn');\n                const closeBtn = this.panel.querySelector('.close-btn');\n\n                toggleButtonsPanel?.addEventListener('click', (e) => {\n                    if (this.controlButtons.dataset.dragging === 'true') {\n                        delete this.controlButtons.dataset.dragging;\n                        return;\n                    }\n                    this.panel.classList.toggle('show');\n                });\n                settingsToggleBtn.addEventListener('click', (e) => {\n                    e.stopPropagation();\n                    settingsPopup.classList.toggle('show');\n                    if (!settingsPopup.classList.contains('show')) {\n                        categoryOptionsPopup.classList.remove('show');\n                        categorySelectToggle.classList.remove('active');\n                    }\n                });\n                settingsPopup.addEventListener('click', (e) => e.stopPropagation());\n                closeBtn.addEventListener('click', () => this.panel.classList.remove('show'));\n                refreshBtn.addEventListener('click', () => {\n                    categoryOptionsPopup.classList.remove('show');\n                    categorySelectToggle.classList.remove('active');\n                    this.loadRules(true);\n                });\n                importBtn.addEventListener('click', () => this.fileInput.click());\n                this.fileInput.addEventListener('change', async (e) => {\n                    if (e.target.files.length > 0) await this.handleFileImport(e.target.files[0]);\n                });\n\n                // Send Mode Toggle\n                sendModeToggle.checked = (AppState.currentSendMode === 'original');\n                sendModeToggle.addEventListener('change', function() {\n                    AppState.setSendMode(this.checked ? 'original' : 'translated');\n                    showNotification(`模式: ${this.checked ? '原文' : '翻译'}`);\n                });\n\n                // Remote Rules Toggle\n                remoteRulesToggle.checked = AppState.useRemoteRules;\n                remoteRulesToggle.addEventListener('change', (e) => {\n                    AppState.setRemoteRules(e.target.checked);\n                    showNotification(`已切换至: ${e.target.checked ? '远程' : '本地'}`);\n                    this.loadRules(true);\n                });\n\n                // Compact Mode Toggle (新增)\n                compactModeToggle.checked = AppState.compactMode;\n                compactModeToggle.addEventListener('change', (e) => {\n                    AppState.setCompactMode(e.target.checked);\n                    showNotification(`紧凑模式: ${e.target.checked ? '开启' : '关闭'}`);\n                    UIRenderer.renderButtonsPanel(this.panel.querySelector(CONFIG.SELECTORS.BUTTONS_CONTAINER)); // 重新渲染按钮面板\n                });\n\n                categorySelectToggle.addEventListener('click', (e) => {\n                    e.stopPropagation();\n                    categoryOptionsPopup.classList.toggle('show');\n                    categorySelectToggle.classList.toggle('active');\n                });\n                categoryOptionsPopup.addEventListener('change', (e) => {\n                    if (e.target.type === 'checkbox' && e.target.name === 'category') {\n                        const categoryName = e.target.value;\n                        const isChecked = e.target.checked;\n\n                        AppState.toggleCategorySelection(categoryName, isChecked);\n\n                        const container = this.panel.querySelector(CONFIG.SELECTORS.BUTTONS_CONTAINER);\n                        UIRenderer.renderButtonsPanel(container); // 重新渲染以反映新顺序\n                        this.updateCategoryDisplay(); // 更新显示文本\n                    }\n                });\n                this.setupPanelDrag();\n                this.setupPanelResize();\n                this.setupFloatingButtonDrag();\n                // 确保拖拽事件监听器只绑定一次\n                setupDragAndDrop(this.panel.querySelector(CONFIG.SELECTORS.BUTTONS_CONTAINER));\n            }\n            updateCategoryDisplay() {\n                const selectedCategoriesDisplay = this.panel.querySelector('#selected-categories-display');\n                if (selectedCategoriesDisplay) {\n                    const visibleCategories = AppState.selectedCategoriesOrder.filter(cat => !AppState.hiddenCategories.includes(cat));\n                    const allAvailableButNotHidden = AppState.getAllCategoriesCombined().filter(cat => !AppState.hiddenCategories.includes(cat));\n\n                    if (visibleCategories.length === 0) {\n                        selectedCategoriesDisplay.textContent = '选择分类...';\n                    } else if (visibleCategories.length === allAvailableButNotHidden.length && allAvailableButNotHidden.every(cat => visibleCategories.includes(cat))) {\n                        selectedCategoriesDisplay.textContent = '所有分类';\n                    } else {\n                        selectedCategoriesDisplay.textContent = visibleCategories.join(', ');\n                    }\n                }\n            }\n            async handleFileImport(file) {\n                const container = this.panel.querySelector(CONFIG.SELECTORS.BUTTONS_CONTAINER);\n                container.innerHTML = `<div class=\"loading\">${COMMON_ICONS.spinner}<span>正在加载规则文件...</span></div>`;\n                try {\n                    const result = await RuleLoader.loadFile(file);\n                    AppState.allParsedRules = result.rules;\n                    AppState.allAvailableCategories = result.categories;\n                    // 导入文件后，重置隐藏分类，并默认显示所有新导入的分类\n                    AppState.hiddenCategories = [];\n                    // 传入所有可用分类作为初始可见顺序，并清空隐藏列表\n                    AppState.initializeSelectedCategories(AppState.getAllCategoriesCombined(), []);\n                    this.populateCategorySelect(AppState.getAllCategoriesCombined());\n                    UIRenderer.renderButtonsPanel(container);\n                    showNotification(`成功导入 ${result.rules.length} 条规则`);\n                    // 仅保存非自定义规则到本地存储\n                    const plainTextRules = RuleParser.serializeRulesToPlainText(result.rules);\n                    GM_setValue(STORAGE_KEYS.LOCAL_RULES, plainTextRules);\n                    if (AppState.useRemoteRules) {\n                        showNotification(`提示：当前为远程模式，已导入但未应用。请关闭“使用远程规则”开关以使用。`, 'success');\n                    }\n                } catch (error) {\n                    Logger.error('导入规则文件失败', error.message);\n                    container.innerHTML = `<div class=\"loading\">${COMMON_ICONS.exclamationCircle}<span>规则解析错误: ${error.message}</span></div>`;\n                }\n                this.fileInput.value = '';\n            }\n            setupPanelDrag() {\n                const header = this.panel.querySelector('.panel-header');\n                let dragging = false,\n                    x0 = 0,\n                    y0 = 0,\n                    p_x = 0,\n                    p_y = 0;\n                header?.addEventListener('mousedown', (e) => {\n                    if (e.target.closest('.panel-btn, .settings-popup')) return; // Allow clicking buttons inside header\n                    dragging = true;\n                    x0 = e.clientX;\n                    y0 = e.clientY;\n                    const rect = this.panel.getBoundingClientRect();\n                    p_x = rect.left;\n                    p_y = rect.top;\n                    this.panel.style.transition = 'none';\n                    e.preventDefault();\n                });\n                document.addEventListener('mouseup', () => {\n                    if (dragging) {\n                        dragging = false;\n                        this.panel.style.transition = '';\n                        this.hideSizeInfoAfterDelay();\n                    }\n                });\n                document.addEventListener('mousemove', (e) => {\n                    if (!dragging) return;\n                    this.showSizeInfo();\n                    this.panel.style.left = `${p_x + e.clientX - x0}px`;\n                    this.panel.style.top = `${p_y + e.clientY - y0}px`;\n                    this.panel.style.right = 'auto';\n                    this.panel.style.bottom = 'auto';\n                });\n            }\n            setupPanelResize() {\n                const handles = this.panel.querySelectorAll('.resize-handle');\n                let resizing = false,\n                    w0, h0, x0, y0, dir;\n                const start = (e, d) => {\n                    resizing = true;\n                    w0 = this.panel.offsetWidth;\n                    h0 = this.panel.offsetHeight;\n                    x0 = e.clientX;\n                    y0 = e.clientY;\n                    dir = d;\n                    document.body.style.cursor = `${d.includes('s') ? 's' : ''}${d.includes('e') ? 'e' : ''}-resize`;\n                    this.panel.style.transition = 'none';\n                    e.preventDefault();\n                };\n                handles.forEach(h => {\n                    if (h.classList.contains('resize-handle-e')) h.addEventListener('mousedown', e => start(e, 'e'));\n                    if (h.classList.contains('resize-handle-s')) h.addEventListener('mousedown', e => start(e, 's'));\n                    if (h.classList.contains('resize-handle-se')) h.addEventListener('mousedown', e => start(e, 'se'));\n                });\n                document.addEventListener('mouseup', () => {\n                    if (resizing) {\n                        resizing = false;\n                        document.body.style.cursor = '';\n                        this.panel.style.transition = '';\n                        this.hideSizeInfoAfterDelay();\n                    }\n                });\n                document.addEventListener('mousemove', (e) => {\n                    if (!resizing) return;\n                    this.showSizeInfo();\n                    let nw = w0,\n                        nh = h0;\n                    if (dir.includes('e')) nw = w0 + e.clientX - x0;\n                    if (dir.includes('s')) nh = h0 + e.clientY - y0;\n                    // Removed Math.max for min-width/min-height\n                    this.panel.style.width = nw + 'px';\n                    this.panel.style.height = nh + 'px';\n                    const si = this.panel.querySelector('.size-info');\n                    if (si) si.textContent = `${Math.round(this.panel.offsetWidth)}×${Math.round(this.panel.offsetHeight)}px`;\n                });\n            }\n            setupFloatingButtonDrag() {\n                let isDragging = false;\n                let offsetX, offsetY;\n                this.controlButtons.addEventListener('mousedown', (e) => {\n                    if (e.target.closest('.control-btn') !== this.controlButtons.querySelector('.control-btn')) return;\n                    isDragging = true;\n                    const rect = this.controlButtons.getBoundingClientRect();\n                    offsetX = e.clientX - rect.left;\n                    offsetY = e.clientY - rect.top;\n                    this.controlButtons.style.cursor = 'grabbing';\n                    document.body.style.userSelect = 'none';\n                    e.preventDefault();\n                });\n                document.addEventListener('mousemove', (e) => {\n                    if (!isDragging) return;\n                    this.controlButtons.dataset.dragging = 'true';\n                    let x = e.clientX - offsetX;\n                    let y = e.clientY - offsetY;\n                    x = Math.max(0, Math.min(x, window.innerWidth - this.controlButtons.offsetWidth));\n                    y = Math.max(0, Math.min(y, window.innerHeight - this.controlButtons.offsetHeight));\n                    this.controlButtons.style.left = `${x}px`;\n                    this.controlButtons.style.top = `${y}px`;\n                    this.controlButtons.style.bottom = 'auto';\n                    this.controlButtons.style.right = 'auto';\n                });\n                document.addEventListener('mouseup', () => {\n                    if (isDragging) {\n                        isDragging = false;\n                        this.controlButtons.style.cursor = 'grab';\n                        document.body.style.userSelect = '';\n                        GM_setValue(STORAGE_KEYS.CONTROL_BTN_POS, {\n                            top: parseFloat(this.controlButtons.style.top),\n                            left: parseFloat(this.controlButtons.style.left)\n                        });\n                        setTimeout(() => delete this.controlButtons.dataset.dragging, 50);\n                    }\n                });\n            }\n            showSizeInfo() {\n                const sizeInfo = this.panel.querySelector('.size-info');\n                if (sizeInfo) {\n                    if (AppState.sizeInfoTimeout) clearTimeout(AppState.sizeInfoTimeout);\n                    sizeInfo.classList.add('show');\n                }\n            }\n            hideSizeInfoAfterDelay() {\n                const sizeInfo = this.panel.querySelector('.size-info');\n                if (sizeInfo) {\n                    if (AppState.sizeInfoTimeout) clearTimeout(AppState.sizeInfoTimeout);\n                    AppState.sizeInfoTimeout = setTimeout(() => sizeInfo.classList.remove('show'), 2000);\n                }\n            }\n        }\n\n        // ==================== 初始化 ====================\n        function initializeNewVersion() {\n            Logger.info('⚡ 初始化智能客服助手 (新版) v13.0');\n            try {\n                injectStyles();\n                startHighlightMonitoring();\n                panelManager = new PanelManager();\n                const obs = new MutationObserver(() => {\n                    const chatContainer = document.querySelector('.editor-container__content, .input-area__send');\n                    if (chatContainer && !document.querySelector(CONFIG.SELECTORS.BUTTONS_PANEL)) {\n                        obs.disconnect();\n                        panelManager.create();\n                        RuleEditor.init();\n                    }\n                });\n                obs.observe(document.body, {\n                    childList: true,\n                    subtree: true\n                });\n                setTimeout(() => {\n                    if (!document.querySelector(CONFIG.SELECTORS.BUTTONS_PANEL)) {\n                        Logger.warn('MutationObserver 未在预期时间内触发，尝试直接创建面板');\n                        panelManager.create();\n                        RuleEditor.init();\n                    }\n                }, USER_CONFIGURABLE_DELAYS.PANEL_INIT_OBSERVER_TIMEOUT);\n            } catch (e) {\n                Logger.error('新版初始化失败', e.message);\n                showNotification('新版初始化失败: ' + e.message, 'error');\n            }\n        }\n\n        if (document.readyState === 'complete') {\n            initializeNewVersion();\n        } else {\n            window.addEventListener('load', initializeNewVersion);\n        }\n\n    } else {\n        // ====================================================================\n        // ====================== 旧版 SalesSmartly 逻辑 ======================\n        // ====================================================================\n        console.log('SalesSmartly 助手：检测到旧版 SalesSmartly 页面。');\n\n        // --- 用户可配置区域 ---\n        const DEFAULT_PANEL_WIDTH = 320;\n        const DEFAULT_PANEL_HEIGHT = 680;\n        const DEFAULT_PANEL_VERTICAL_ANCHOR = 'bottom';\n        const DEFAULT_PANEL_VERTICAL_OFFSET = 0;\n        const DEFAULT_PANEL_HORIZONTAL_ANCHOR = 'left';\n        const DEFAULT_PANEL_HORIZONTAL_OFFSET = 170;\n        const REMOTE_RULES_URL = 'https://abcdc.top/jj/kjgz.txt';\n\n        // 默认本地规则内容，当本地无规则且远程规则未启用时使用\n        const DEFAULT_LOCAL_RULES_CONTENT = `\n==============================右上角设置按键可打开导入本地规则文本，放置自己的话术快速发送；可选择打开远程规则直接使用==============================\n\nname=右上角设置按键\nt1=右上角设置按键\ntt1=译文\n\n\n==============================右上角的“翻译“原文”开关可控制发送内容是输入中文点击‘翻译发送’或者直接输入目标译文后点击‘原文发送’==============================\nname=按键配置行\nt1=右上角设置按键\ntt1=译文\n==============================t1=译文  #配置发送的文字译文==============================\nname=按键配置行\nt1=右上角设置按键\ntt1=译文\n==============================tt1=发送文字  #配置发送的文字==============================\nname=按键配置行\nt1=右上角设置按1键\ntt1=译文\n==============================imge_name=be888-提款.jpg  #配置新版图片名称==============================\nname=按键配置行\nt1=右上角设置按键\ntt1=译文\n==============================imge=8  #配置旧版图片的位置如第4张图要*2填入==============================\nname=按键配置行\ntt1=右上角设置按键\nt1=译文\n==============================name=vip0  #按键名称==============================\nname=按键配置行\ntt1=右上角设置按键\nt1=译文\n==============================规则配置：如下，一个按键中包含name行（按键名称）；tt1，tt2，tt3...为中文，一个按键可以配置多个，加入几行就会发几条，加入几行就会发几条，t1，和t2...配置与对应的中文的目标译文如tt1对应的t1最好是上下排一起；imge行为为配置旧版网页的按键发送的图片位置，打开附件查看图片排在第几张，该行填写的数字为图片第几个*2的数字；imge_name行是为新版本的网页配置发送的图片，直接填写输入图片的全称即可，不再受附件的排序影响；按键和按键之间的配置行要空出一行 空行，来隔离按键之间的配置；下面是一个按键的实例配置：==============================\nname=右上角设置按键\nt1=右上角设置按键\ntt1=译文\n`;\n        // --- 配置区域结束 ---\n\n        // --- 配置常量 ---\n        const CONFIG = {\n            PANEL: {\n                WIDTH: DEFAULT_PANEL_WIDTH,\n                HEIGHT: DEFAULT_PANEL_HEIGHT,\n                VERTICAL_ANCHOR: DEFAULT_PANEL_VERTICAL_ANCHOR,\n                VERTICAL_OFFSET: DEFAULT_PANEL_VERTICAL_OFFSET,\n                HORIZONTAL_ANCHOR: DEFAULT_PANEL_HORIZONTAL_ANCHOR,\n                HORIZONTAL_OFFSET: DEFAULT_PANEL_HORIZONTAL_OFFSET,\n            },\n            SELECTORS: {\n                CHAT_INPUT: 'div.chat-input-box__input.ql-editor', // 旧版聊天输入框\n                ATTACHMENT_BUTTON: 'button.ivu-btn.ivu-btn-text .ivu-icon.icon-image1', // 旧版附件按钮\n                IMAGE_OPTION_TEXT: '图片', // 旧版附件菜单中的图片选项文本 (实际旧版没有，用不到)\n                IMAGE_UPLOAD_POPUP: '.upload-popup-wrap', // 旧版图片上传弹窗\n                IMAGE_TABLE_BODY: '.el-table__body-wrapper tbody', // 旧版图片表格主体 (旧版没有，用不到)\n                IMAGE_ROW: '.el-table__row', // 旧版图片表格行 (旧版没有，用不到)\n                IMAGE_NAME_CELL: '.file-name-col .cell', // 旧版图片名称单元格 (旧版没有，用不到)\n                IMAGE_SEND_BTN: '.send-btn', // 旧版图片发送按钮 (实际旧版用的是ivu-btn)\n                BUTTONS_PANEL: '.button-panel', // 助手面板的类名\n                BUTTONS_CONTAINER: '.buttons-container', // 助手面板内按钮容器的类名\n                // 旧版特有\n                TRANSLATE_SEND_BUTTON: 'button.ivu-btn.ivu-btn-primary:not([disabled])', // 翻译发送按钮 (通常是主发送按钮)\n                ORIGINAL_SEND_BUTTON: 'button.ivu-btn.ivu-btn-default:not([disabled])', // 原文发送按钮 (通常是次要按钮，文本为“原文发送”)\n                CHAT_INPUT_OLD_GENERIC: 'textarea._ss_1U8_5TPk, textarea[placeholder=\"输入消息\"], textarea[role=\"textbox\"], .ss-chat-input, div.ql-editor', // 兼容多种输入框\n            },\n            REMOTE_RULES_URL: REMOTE_RULES_URL\n        };\n\n        // --- 全局状态与常量 ---\n        const STORAGE_KEYS = {\n            SEND_MODE: 'ss_send_mode_preference_old',\n            CONTROL_BTN_POS: 'ss_control_btn_pos_old',\n            USE_REMOTE_RULES: 'ss_use_remote_rules_preference_old',\n            SELECTED_CATEGORIES: 'ss_selected_categories_order_old', // 存储可见分类的顺序\n            HIDDEN_CATEGORIES: 'ss_hidden_categories_old', // 新增：存储明确隐藏的分类\n            LOCAL_RULES: 'txt_old',\n            CUSTOM_RULES: 'ss_custom_rules_old', // 旧版自定义规则的存储键\n            COMPACT_MODE: 'ss_compact_mode_old', // 紧凑模式\n        };\n\n        const AppState = {\n            currentSendMode: GM_getValue(STORAGE_KEYS.SEND_MODE, 'translated'),\n            useRemoteRules: GM_getValue(STORAGE_KEYS.USE_REMOTE_RULES, false),\n            compactMode: GM_getValue(STORAGE_KEYS.COMPACT_MODE, false),\n            selectedCategoriesOrder: [], // 存储已选分类的显示顺序\n            hiddenCategories: [], // 存储用户明确隐藏的分类\n            allParsedRules: [], // 原始解析的规则 (远程或本地文件)\n            customRules: [], // 用户自定义规则\n            allAvailableCategories: [], // 所有可用的分类 (包括自定义分类)\n            isReplying: false,\n            sizeInfoTimeout: null,\n            highlightCheckInterval: null,\n            highlightedAttachmentBtn: null, // 旧版使用 attachment button\n            highlightBox: null,\n            isImageSending: false,\n            lastSentImageIndex: -1, // 旧版记录图片索引\n            hoverTimer: null,\n            hoverInfoPopupTimer: null,\n            dragState: {\n                isDragging: false,\n                ghostElement: null,\n                originalElement: null,\n                draggedId: null,\n                draggedRule: null,\n                offsetX: 0,\n                offsetY: 0,\n            },\n            CUSTOM_CATEGORY_NAME: '自定义',\n\n            setSendMode(mode) {\n                this.currentSendMode = mode;\n                GM_setValue(STORAGE_KEYS.SEND_MODE, mode);\n            },\n\n            setRemoteRules(useRemote) {\n                this.useRemoteRules = useRemote;\n                GM_setValue(STORAGE_KEYS.USE_REMOTE_RULES, useRemote);\n            },\n\n            setCompactMode(isCompact) {\n                this.compactMode = isCompact;\n                GM_setValue(STORAGE_KEYS.COMPACT_MODE, isCompact);\n            },\n\n            setCustomRules(rules) {\n                this.customRules = rules;\n                GM_setValue(STORAGE_KEYS.CUSTOM_RULES, JSON.stringify(rules));\n            },\n\n            getAllRulesCombined() {\n                const customRulesWithCategory = this.customRules.map(rule => ({\n                    ...rule,\n                    category: this.CUSTOM_CATEGORY_NAME,\n                    isCustom: true\n                }));\n                const nonCustomRules = this.allParsedRules.map(rule => ({ ...rule, isCustom: false }));\n                return [...nonCustomRules, ...customRulesWithCategory];\n            },\n\n            getAllCategoriesCombined() {\n                const categories = new Set();\n                this.allParsedRules.forEach(rule => categories.add(rule.category));\n                categories.add(this.CUSTOM_CATEGORY_NAME);\n\n                const sortedCategories = Array.from(categories).sort((a, b) => {\n                    if (a === this.CUSTOM_CATEGORY_NAME) return -1;\n                    if (b === this.CUSTOM_CATEGORY_NAME) return 1;\n                    return a.localeCompare(b);\n                });\n                return sortedCategories;\n            },\n\n            toggleCategorySelection(categoryName, isChecked) {\n                let currentOrder = [...this.selectedCategoriesOrder];\n                let currentHidden = new Set(this.hiddenCategories);\n\n                if (isChecked) {\n                    currentHidden.delete(categoryName); // 从隐藏列表中移除\n                    if (!currentOrder.includes(categoryName)) { // 如果不在可见列表中，则添加到末尾\n                        currentOrder.push(categoryName);\n                    }\n                } else {\n                    currentOrder = currentOrder.filter(cat => cat !== categoryName); // 从可见列表中移除\n                    currentHidden.add(categoryName); // 添加到隐藏列表\n                }\n\n                // 特殊处理“自定义”分类，如果被选中，则始终排在最前面\n                const customIndex = currentOrder.indexOf(this.CUSTOM_CATEGORY_NAME);\n                if (customIndex > -1) {\n                    const customCat = currentOrder.splice(customIndex, 1)[0];\n                    currentOrder.unshift(customCat);\n                }\n\n                this.selectedCategoriesOrder = currentOrder;\n                this.hiddenCategories = Array.from(currentHidden);\n\n                GM_setValue(STORAGE_KEYS.SELECTED_CATEGORIES, this.selectedCategoriesOrder);\n                GM_setValue(STORAGE_KEYS.HIDDEN_CATEGORIES, this.hiddenCategories);\n            },\n\n            initializeSelectedCategories(storedSelectedOrder, storedHiddenCategories) {\n                const allAvailable = this.getAllCategoriesCombined();\n                const hiddenSet = new Set(storedHiddenCategories);\n\n                // 1. 从存储的顺序开始，过滤掉不再存在或被明确隐藏的分类\n                let finalOrder = storedSelectedOrder.filter(cat => allAvailable.includes(cat) && !hiddenSet.has(cat));\n\n                // 2. 添加所有可用但不在当前可见列表且未被明确隐藏的新分类\n                allAvailable.forEach(cat => {\n                    if (!finalOrder.includes(cat) && !hiddenSet.has(cat)) {\n                        finalOrder.push(cat);\n                    }\n                });\n\n                // 3. 确保“自定义”分类始终排在最前面（如果它在可见列表中）\n                const customIndex = finalOrder.indexOf(this.CUSTOM_CATEGORY_NAME);\n                if (customIndex > -1) {\n                    const customCat = finalOrder.splice(customIndex, 1)[0];\n                    finalOrder.unshift(customCat);\n                }\n\n                this.selectedCategoriesOrder = finalOrder;\n                // 更新隐藏分类列表，移除不再存在的分类\n                this.hiddenCategories = Array.from(hiddenSet).filter(cat => allAvailable.includes(cat));\n\n                GM_setValue(STORAGE_KEYS.SELECTED_CATEGORIES, this.selectedCategoriesOrder);\n                GM_setValue(STORAGE_KEYS.HIDDEN_CATEGORIES, this.hiddenCategories);\n            },\n\n            setReplyStatus(status) {\n                this.isReplying = status;\n            }\n        };\n\n        // ==================== 样式注入 (旧版) ====================\n        function injectStylesOld() {\n            GM_addStyle(`\n.ss-panel { position: fixed; background: white; border-radius: 16px; box-shadow: 0 15px 40px rgba(0,0,0,.25); overflow: visible; display: flex; flex-direction: column; z-index: 10000; opacity: 0; transform: translateY(20px); transition: all .4s cubic-bezier(.175,.885,.32,1.275); }\n.ss-panel.show { opacity: 1; transform: translateY(0); }\n.panel-header { background: linear-gradient(to right, #409eff, #3a8ee6); padding: 10px 15px; color: white; font-weight: 600; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; border-radius: 16px 16px 0 0; position: relative; }\n.panel-title-group { display: flex; align-items: center; gap: 15px; flex-grow: 1; min-width: 0; }\n.panel-btn { background: rgba(255,255,255,.2); border: none; border-radius: 8px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: white; font-size: 14px; transition: .3s ease; }\n.panel-btn:hover { background: rgba(255,255,255,.3); }\n.buttons-container { flex-grow: 1; overflow-y: auto; padding: 15px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; background: #f8fafc; border-radius: 0 0 16px 16px; }\n.action-btn { display: flex; flex-direction: row; align-items: center; gap: 6px; padding: 6px 8px; border-radius: 8px; background: white; border: 1px solid #e2e8f0; cursor: pointer; transition: .2s ease; color: #334155; min-height: 48px; box-shadow: 0 2px 6px rgba(0,0,0,.04); position: relative; overflow: visible; }\n.action-btn:hover { background: #ecf5ff; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(64,158,255,.2); border-color: #a0cfff; }\n.action-btn span { font-size: 12px; font-weight: 500; line-height: 1.4; text-align: left; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }\n.settings-popup { position: absolute; top: calc(100% + 5px); right: 10px; background: white; border-radius: 8px; box-shadow: 0 8px 25px rgba(0,0,0,.15); border: 1px solid #e2e8f0; z-index: 10; width: 220px; padding: 10px; display: flex; flex-direction: column; gap: 8px; opacity: 0; transform: translateY(-10px); pointer-events: none; transition: all .2s ease-out; }\n.settings-popup.show { opacity: 1; transform: translateY(0); pointer-events: auto; }\n.settings-popup .popup-control-btn { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: none; background: transparent; cursor: pointer; width: 100%; text-align: left; border-radius: 6px; font-size: 13px; color: #334155; }\n.settings-popup .popup-control-btn:hover { background: #f1f5f9; color: #1e293b; }\n.settings-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; border-radius: 6px; }\n.settings-item:hover { background: #f1f5f9; }\n.settings-label { font-size: 13px; color: #334155; }\n.settings-item.send-mode-switch span { color: #334155; } /* 确保文字颜色可见 */\n.category-select-wrapper { margin-top: 5px; padding: 8px 10px; border-radius: 6px; background: #f8fafc; border: 1px solid #e2e8f0; position: relative; }\n.category-select-wrapper > label { display: block; font-size: 12px; color: #64748b; margin-bottom: 5px; font-weight: 500; }\n.category-toggle-btn { display: flex; justify-content: space-between; align-items: center; width: 100%; padding: 6px 10px; border: 1px solid #cbd5e1; border-radius: 4px; background-color: #fff; cursor: pointer; font-size: 13px; color: #334155; text-align: left; transition: background-color .2s; }\n.category-toggle-btn:hover { background-color: #f1f5f9; }\n.category-toggle-btn.active svg { transform: rotate(180deg); }\n.category-options-popup { display: none; position: absolute; top: 100%; left: 0; right: 0; background: white; border: 1px solid #e2e8f0; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,.1); z-index: 11; max-height: 200px; overflow-y: auto; padding: 5px; margin-top: 5px; }\n.category-options-popup.show { display: block; }\n.category-options-popup label { display: flex; align-items: center; padding: 5px 8px; cursor: pointer; font-size: 13px; color: #334155; white-space: nowrap; }\n.category-options-popup label:hover { background-color: #f1f5f9; }\n.category-options-popup input[type=\"checkbox\"] { -webkit-appearance: checkbox !important; -moz-appearance: checkbox !important; appearance: checkbox !important; opacity: 1 !important; width: 16px !important; height: 16px !important; position: static !important; margin-right: 8px !important; vertical-align: middle !important; flex-shrink: 0 !important; }\n.category-options-popup input[type=\"checkbox\"]:checked + span { color: #1e293b !important; font-weight: 600 !important; }\n.size-info { position: absolute; bottom: -24px; right: 5px; background: rgba(0,0,0,.7); color: white; padding: 3px 7px; border-radius: 4px; font-size: 10px; z-index: 100; opacity: 0; transition: opacity .3s ease; pointer-events: none; }\n.size-info.show { opacity: 1; }\n.resize-handle { position: absolute; z-index: 100; background: transparent; }\n.resize-handle-e { cursor: ew-resize; height: 100%; width: 8px; right: 0; top: 0; }\n.resize-handle-s { cursor: ns-resize; width: 100%; height: 8px; bottom: 0; left: 0; }\n.resize-handle-se { cursor: nwse-resize; width: 18px; height: 18px; right: 0; bottom: 0; z-index: 101; }\n.control-buttons { position: fixed; display: flex; gap: 15px; z-index: 11000; cursor: grab; }\n.control-btn { width: 60px; height: 60px; border-radius: 50%; background: #409eff; color: white; display: flex; align-items: center; justify-content: center; font-size: 24px; cursor: pointer; box-shadow: 0 6px 20px rgba(0,0,0,.3); transition: .3s ease; }\n.control-btn:hover { background: #3a8ee6; transform: scale(1.1); box-shadow: 0 8px 25px rgba(0,0,0,.4); }\n.file-input { display: none; }\n.loading { display: flex; justify-content: center; align-items: center; height: 100%; font-size: 14px; color: #64748b; gap: 10px; }\n.ss-notification { position: fixed; bottom: 30px; right: 30px; padding: 12px 20px; border-radius: 8px; background-color: #10b981; color: white; font-weight: 500; box-shadow: 0 4px 12px rgba(0,0,0,.15); z-index: 20000; opacity: 0; transform: translateY(20px); transition: all .3s ease; max-width: 400px; word-break: break-word; }\n.ss-notification.show { opacity: 1; transform: translateY(0); }\n.ss-notification.error { background-color: #ef4444; }\n@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\n.fa-spin { animation: spin 1s linear infinite; }\n.switch-container { position: relative; display: inline-block; width: 40px; height: 22px; }\n.switch-container input { opacity: 0; width: 0; height: 0; }\n.switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 22px; }\n.switch-slider:before { position: absolute; content: \"\"; height: 18px; width: 18px; left: 2px; bottom: 2px; background-color: white; transition: .4s; border-radius: 50%; }\ninput:checked + .switch-slider { background-color: #4ade80;}\ninput:checked + .switch-slider:before { transform: translateX(18px); }\n.panel-btn svg, .popup-control-btn svg, .action-btn svg, .control-btn svg, .loading svg { display: inline-block; vertical-align: middle; }\n.panel-btn svg { width: 14px; height: 14px; }\n.popup-control-btn svg { width: 16px; height: 16px; color: #64748b; }\n.action-btn svg { width: 16px; height: 16px; color: inherit; flex-shrink: 0; }\n.control-btn svg { width: 24px; height: 24px; }\n.loading svg { width: 1em; height: 1em; }\n.reply-status-indicator { font-size: 12px; font-weight: normal; margin-left: 10px; padding: 2px 8px; border-radius: 10px; background-color: rgba(255,255,255,0.2); color: white; transition: background-color 0.3s ease; }\n.reply-status-indicator.idle { background-color: #28a745; }\n.reply-status-indicator.replying { background-color: #ffc107; color: #333; }\n.action-btn .action-icon-wrapper { display: flex; align-items: center; justify-content: center; padding: 4px; border-radius: 4px; transition: background-color 0.2s ease; flex-shrink: 0; }\n.action-btn .action-icon-wrapper:hover { background-color: rgba(0,0,0,0.05); }\n.action-btn .action-icon-wrapper:hover svg { animation: spin 1s linear infinite; }\n.category-header { grid-column: 1 / -1; text-align: left; font-weight: 600; color: #475569; padding: 8px 0 4px 0; margin-top: 15px; border-bottom: 1px solid #e2e8f0; margin-bottom: 5px; font-size: 14px; }\n\n/* 新增自定义规则相关样式 */\n.category-header.custom-category { display: flex; justify-content: space-between; align-items: center; }\n.add-custom-rule-btn {\n    background: #22c55e;\n    color: white;\n    border: none;\n    padding: 4px 8px;\n    border-radius: 6px;\n    font-size: 12px; /* 确保这里是12px，或者你想要的大小 */\n    cursor: pointer;\n    transition: background-color .2s;\n    display: flex;\n    align-items: center;\n    gap: 4px;\n}\n.add-custom-rule-btn:hover { background: #16a34a; }\n.action-btn.dragging-source { opacity: 0.4; } /* 拖动源的样式 */\n\n/* 在这里添加或修改以下规则 */\n.add-custom-rule-btn svg {\n    width: 1em;   /* 使SVG宽度与父元素的字体大小相同 */\n    height: 1em;  /* 使SVG高度与父元素的字体大小相同 */\n    vertical-align: middle; /* 垂直居中对齐文本 */\n    flex-shrink: 0; /* 防止SVG在flex布局中被压缩 */\n}\n\n/* 编辑按键 (右下角) */\n.action-btn .hover-actions {\n    position: absolute;\n    bottom: -8px; /* 编辑按键在右下角 */\n    right: -8px;\n    display: flex;\n    gap: 4px;\n    opacity: 0;\n    pointer-events: none;\n    transition: opacity .2s ease-out;\n    z-index: 5;\n}\n.action-btn:hover .hover-actions, .action-btn.show-actions .hover-actions {\n    opacity: 1;\n    pointer-events: auto;\n}\n\n/* 拖动按键 (右上角) */\n.action-btn .drag-handle-wrapper {\n    position: absolute;\n    top: -8px; /* 拖动按键在右上角 */\n    right: -8px;\n    opacity: 0;\n    pointer-events: none;\n    transition: opacity .2s ease-out;\n    z-index: 5;\n}\n.action-btn:hover .drag-handle-wrapper, .action-btn.show-actions .drag-handle-wrapper {\n    opacity: 1;\n    pointer-events: auto;\n}\n\n/* 确保编辑和拖动按钮本身的样式 */\n.action-btn .hover-action-btn {\n    background: rgba(0,0,0,0.6);\n    color: white;\n    border: none;\n    border-radius: 50%;\n    width: 24px;\n    height: 24px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    transition: background-color .2s;\n}\n.action-btn .hover-action-btn:hover {\n    background: rgba(0,0,0,0.8);\n}\n.action-btn .hover-action-btn svg {\n    width: 12px;\n    height: 12px;\n}\n.drag-handle { /* 拖动按钮的特殊光标 */\n    cursor: grab;\n}\n.drag-handle:active { cursor: grabbing; }\n\n.ss-drag-ghost { position: fixed; z-index: 30000; pointer-events: none; opacity: 0.8; transform: translate(-50%, -50%); /* 居中于鼠标 */ box-shadow: 0 10px 30px rgba(0,0,0,0.4); }\n.drag-over-indicator { grid-column: 1 / -1; height: 3px; background: #3b82f6; border-radius: 1.5px; margin: -5px 0; transition: all 0.1s ease-out; }\n.category-header.custom-category.drag-over-target { background-color: #e0f2fe; border-color: #93c5fd; } /* 拖拽到自定义分类头部的样式 */\n\n/* 模态框样式 */\n.ss-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); display: flex; justify-content: center; align-items: center; z-index: 20000; opacity: 0; transition: opacity .3s ease; pointer-events: none; }\n.ss-modal-overlay.show { opacity: 1; pointer-events: auto; }\n.ss-modal-content { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); padding: 25px; width: 90%; max-width: 600px; max-height: 90vh; overflow-y: auto; display: flex; flex-direction: column; transform: translateY(-20px); transition: transform .3s ease; }\n.ss-modal-overlay.show .ss-modal-content { transform: translateY(0); }\n.ss-modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #eee; }\n.ss-modal-header h3 { margin: 0; font-size: 18px; color: #333; }\n.ss-modal-header .close-btn { background: none; border: none; font-size: 24px; cursor: pointer; color: #999; }\n.ss-modal-header .close-btn:hover { color: #666; }\n.ss-modal-body { flex-grow: 1; overflow-y: auto; padding-right: 5px; }\n.ss-modal-body label { display: block; margin-bottom: 8px; font-weight: 500; color: #555; font-size: 14px; }\n.ss-modal-body input[type=\"text\"], .ss-modal-body input[type=\"number\"], .ss-modal-body textarea { width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px; box-sizing: border-box; }\n.ss-modal-body textarea { min-height: 60px; resize: vertical; }\n.ss-modal-body .message-item { display: flex; flex-direction: column; gap: 5px; padding: 10px; border: 1px solid #eee; border-radius: 8px; margin-bottom: 10px; background: #f9f9f9; position: relative; }\n.ss-modal-body .message-item .remove-msg-btn { position: absolute; top: 5px; right: 5px; background: #ef4444; color: white; border: none; border-radius: 4px; padding: 2px 6px; cursor: pointer; font-size: 12px; }\n.ss-modal-body .message-item .remove-msg-btn:hover { background: #dc2626; }\n.ss-modal-body .message-item input, .ss-modal-body .message-item textarea { width: 100%; margin-bottom: 5px; }\n.ss-modal-body .color-picker-group { display: flex; align-items: center; gap: 10px; margin-bottom: 15px; }\n.ss-modal-body input[type=\"color\"] { width: 40px; height: 40px; border: none; padding: 0; border-radius: 4px; cursor: pointer; background: none; }\n.ss-modal-body .color-preview { width: 30px; height: 30px; border-radius: 4px; border: 1px solid #ccc; }\n.ss-modal-footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; padding-top: 15px; border-top: 1px solid #eee; }\n.ss-modal-footer button { padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: background-color .2s; }\n.ss-modal-footer .btn-cancel { background: #e2e8f0; color: #334155; border: 1px solid #cbd5e1; }\n.ss-modal-footer .btn-cancel:hover { background: #cbd5e1; }\n.ss-modal-footer .btn-save { background: #3b82f6; color: white; border: 1px solid #3b82f6; }\n.ss-modal-footer .btn-save:hover { background: #2563eb; }\n.ss-modal-footer .btn-delete { background: #ef4444; color: white; border: 1px solid #ef4444; margin-right: auto; }\n.ss-modal-footer .btn-delete:hover { background: #dc2626; }\n\n/* 悬浮信息提示框样式 */\n.ss-hover-info-popup {\n    position: fixed;\n    background: ${USER_CONFIGURABLE_STYLES.HOVER_INFO_POPUP_BG_COLOR}; /* 使用配置项 */\n    border: 1px solid #e2e8f0;\n    border-radius: 8px;\n    box-shadow: 0 4px 15px rgba(0,0,0,0.1);\n    padding: 15px;\n    max-width: 300px;\n    z-index: 10001;\n    pointer-events: none; /* 不阻止鼠标事件 */\n    opacity: 0; /* 初始隐藏 */\n    visibility: hidden;\n    transition: opacity 0.2s ease, visibility 0.2s ease;\n    font-size: 13px;\n    color: #ffffff; /* 默认白色文本以适应深色背景 */\n    line-height: 1.5;\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    word-break: break-word;\n}\n.ss-hover-info-popup.show {\n    opacity: ${USER_CONFIGURABLE_STYLES.HOVER_INFO_POPUP_OPACITY}; /* 使用配置项 */\n    visibility: visible;\n}\n.ss-hover-info-popup strong {\n    font-weight: 600;\n    color: #ffffff;\n    font-size: 14px;\n}\n.ss-hover-info-popup .info-section {\n    border-top: 1px solid rgba(255,255,255,0.2); /* 适应深色背景 */\n    padding-top: 8px;\n    margin-top: 8px;\n}\n.ss-hover-info-popup .info-section:first-child {\n    border-top: none;\n    padding-top: 0;\n    margin-top: 0;\n}\n.ss-hover-info-popup .info-section-title {\n    font-weight: 500;\n    color: #c0c0c0; /* 适应深色背景 */\n    margin-bottom: 4px;\n    display: flex;\n    align-items: center;\n    gap: 4px;\n}\n.ss-hover-info-popup .info-section-title svg {\n    width: 14px;\n    height: 14px;\n    color: #ffffff; /* 适应深色背景 */\n}\n.ss-hover-info-popup pre {\n    background: rgba(0,0,0,0.3); /* 适应深色背景 */\n    padding: 8px;\n    border-radius: 4px;\n    white-space: pre-wrap;\n    word-break: break-all;\n    font-family: monospace;\n    font-size: 12px;\n    margin: 0;\n    color: #f0f0f0; /* 适应深色背景 */\n}\n\n/* Compact Mode styles */\n.buttons-container.compact-mode {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: flex-start; /* Align items to the top */\n    padding: 5px; /* Smaller padding for container */\n    gap: 4px; /* Smaller gap between buttons */\n    grid-template-columns: unset; /* Override grid layout */\n}\n\n.buttons-container.compact-mode .action-btn {\n    flex: 0 0 auto; /* Don't grow, don't shrink, width determined by content */\n    max-width: fit-content; /* Allow width to fit content */\n    padding: 4px 6px; /* Smaller padding */\n    min-height: unset; /* Remove min-height constraint */\n    height: auto; /* Height adapts to content */\n    line-height: 1.2; /* Tighter line height */\n    gap: 4px; /* Small gap between icon and text */\n}\n\n.buttons-container.compact-mode .action-btn span {\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    -webkit-line-clamp: 1; /* Single line text */\n    display: block; /* Ensure text takes up its own space */\n    max-width: 100%; /* Prevent text from overflowing its container */\n}\n\n.buttons-container.compact-mode .action-btn .action-icon-wrapper {\n    padding: 0; /* No padding for icon wrapper */\n    width: unset;\n    height: unset;\n    display: flex; /* Ensure icon is centered if needed */\n    align-items: center;\n    justify-content: center;\n}\n\n.buttons-container.compact-mode .action-btn .action-icon-wrapper svg {\n    width: 1em; /* Icon size same as text */\n    height: 1em;\n    vertical-align: middle; /* Align with text baseline */\n}\n\n/* Adjust hover actions for compact mode */\n.buttons-container.compact-mode .action-btn .hover-actions {\n    bottom: -4px; /* Adjust position */\n    right: -4px;\n    gap: 2px;\n}\n\n.buttons-container.compact-mode .action-btn .drag-handle-wrapper {\n    top: -4px; /* Adjust position */\n    right: -4px;\n}\n\n.buttons-container.compact-mode .action-btn .hover-action-btn {\n    width: 20px; /* Smaller action buttons */\n    height: 20px;\n}\n\n.buttons-container.compact-mode .action-btn .hover-action-btn svg {\n    width: 10px; /* Smaller icons in action buttons */\n    height: 10px;\n}\n\n/* Adjust category header for compact mode */\n.buttons-container.compact-mode .category-header {\n    grid-column: unset; /* Remove grid column span */\n    width: 100%; /* Take full width */\n    margin-top: 10px;\n    padding: 4px 0;\n    font-size: 13px;\n}\n\n/* Drag indicator for compact mode */\n.buttons-container.compact-mode .drag-over-indicator {\n    width: 100%; /* Take full width of the flex container */\n    height: 3px;\n    background: #3b82f6;\n    border-radius: 1.5px;\n    margin: 2px 0; /* Adjust margin for visual separation */\n    /* No need for order property, insertBefore/After handles flow */\n}\n`);\n        }\n\n        // ==================== 日志工具 (旧版) ====================\n        const LoggerOld = {\n            log: (level, msg, data = null) => {\n                const timestamp = new Date().toLocaleTimeString();\n                const prefix = `[${timestamp}] [SS-OLD][${level.toUpperCase()}]`;\n                if (data) {\n                    console.log(`${prefix} ${msg}`, data);\n                } else {\n                    console.log(`${prefix} ${msg}`);\n                }\n            },\n            debug: (msg, data) => LoggerOld.log('debug', msg, data),\n            info: (msg, data) => LoggerOld.log('info', msg, data),\n            warn: (msg, data) => LoggerOld.log('warn', msg, data),\n            error: (msg, data) => LoggerOld.log('error', msg, data)\n        };\n\n        // ==================== DOM 工具 (旧版) ====================\n        const DOMUtilsOld = {\n            findElement: async (selector, timeout = 5000, retryInterval = 50) => {\n                return new Promise((resolve, reject) => {\n                    let elapsedTime = 0;\n                    const interval = setInterval(() => {\n                        const element = document.querySelector(selector);\n                        if (element) {\n                            clearInterval(interval);\n                            resolve(element);\n                            return;\n                        }\n                        elapsedTime += retryInterval;\n                        if (elapsedTime >= timeout) {\n                            clearInterval(interval);\n                            reject(new Error(`超时: 未找到元素: ${selector}`));\n                        }\n                    }, retryInterval);\n                });\n            },\n            findElementByText: async (selector, text, timeout = 5000, retryInterval = 50) => {\n                return new Promise((resolve, reject) => {\n                    let elapsedTime = 0;\n                    const interval = setInterval(() => {\n                        const elements = document.querySelectorAll(selector);\n                        for (const el of elements) {\n                            if (el.textContent.trim() === text) {\n                                clearInterval(interval);\n                                resolve(el);\n                                return;\n                            }\n                        }\n                        elapsedTime += retryInterval;\n                        if (elapsedTime >= timeout) {\n                            clearInterval(interval);\n                            reject(new Error(`超时: 未找到文本为 \"${text}\" 的元素 (选择器: ${selector})`));\n                        }\n                    }, retryInterval);\n                });\n            },\n            setInputValue: (element, text) => {\n                if (!element) throw new Error('输入框不存在');\n                if (element && element.classList && element.classList.contains('ql-editor')) {\n                    element.innerHTML = text;\n                    element.dispatchEvent(new Event('input', { bubbles: true }));\n                } else if (element) {\n                    element.value = text;\n                    ['input', 'change', 'focus'].forEach(ev => element.dispatchEvent(new Event(ev, { bubbles: true })));\n                }\n            },\n            getInputValue: (element) => {\n                if (!element) return '';\n                return element.textContent || element.value || '';\n            },\n            click: (element) => {\n                if (!element) throw new Error('点击目标不存在');\n                element.click();\n            },\n            waitForElementColor: async (selector, expectedColor, timeout = USER_CONFIGURABLE_DELAYS.OLD_VERSION.IMAGE_BUTTON_COLOR_CHECK_TIMEOUT, retryInterval = 50) => {\n                return new Promise((resolve, reject) => {\n                    let elapsedTime = 0;\n                    const interval = setInterval(() => {\n                        const element = document.querySelector(selector);\n                        if (element) {\n                            const currentColor = window.getComputedStyle(element).color; // 旧版图片按钮是文字颜色\n                            if (currentColor === 'rgb(46, 79, 237)') { // #2e4fed\n                                clearInterval(interval);\n                                resolve(element);\n                                return;\n                            }\n                        }\n                        elapsedTime += retryInterval;\n                        if (elapsedTime >= timeout) {\n                            clearInterval(interval);\n                            reject(new Error(`超时: 元素 ${selector} 的颜色未变为 ${expectedColor}`));\n                        }\n                    }, retryInterval);\n                });\n            }\n        };\n\n        // ==================== 规则加载器 (旧版) ====================\n        const RuleLoaderOld = {\n            loadLocal: async () => {\n                try {\n                    let storedValue = GM_getValue(STORAGE_KEYS.LOCAL_RULES, null);\n                    if (!storedValue || storedValue.trim() === '') {\n                        storedValue = DEFAULT_LOCAL_RULES_CONTENT.trim();\n                        GM_setValue(STORAGE_KEYS.LOCAL_RULES, storedValue);\n                    }\n                    const result = RuleParser.parseRuleFile(storedValue); // 使用共享的 RuleParser\n                    if (Array.isArray(result.rules) && result.rules.length > 0) {\n                        return result;\n                    } else {\n                        throw new Error('本地规则为空或格式不正确');\n                    }\n                } catch (e) {\n                    LoggerOld.error('加载本地规则失败', e);\n                    throw e;\n                }\n            },\n            loadRemote: async () => {\n                return new Promise((resolve, reject) => {\n                    GM_xmlhttpRequest({\n                        method: 'GET',\n                        url: CONFIG.REMOTE_RULES_URL,\n                        nocache: true,\n                        timeout: 10000,\n                        onload: (response) => {\n                            if (response.status >= 200 && response.status < 300) {\n                                try {\n                                    const result = RuleParser.parseRuleFile(response.responseText); // 使用共享的 RuleParser\n                                    if (result.rules.length > 0) {\n                                        resolve(result);\n                                    } else {\n                                        reject(new Error('远程文件未找到有效规则'));\n                                    }\n                                } catch (error) {\n                                    reject(new Error(`远程规则解析错误: ${error.message}`));\n                                }\n                            } else {\n                                reject(new Error(`请求失败，状态码: ${response.status}`));\n                            }\n                        },\n                        onerror: () => reject(new Error('加载远程规则失败')),\n                        ontimeout: () => reject(new Error('加载远程规则超时'))\n                    });\n                });\n            },\n            loadFile: async (file) => {\n                return new Promise((resolve, reject) => {\n                    const reader = new FileReader();\n                    reader.onload = (e) => {\n                        try {\n                            const result = RuleParser.parseRuleFile(e.target.result); // 使用共享的 RuleParser\n                            if (result.rules.length > 0) {\n                                resolve(result);\n                            } else {\n                                reject(new Error('文件中未找到有效规则'));\n                            }\n                        } catch (error) {\n                            reject(new Error(`文件解析错误: ${error.message}`));\n                        }\n                    };\n                    reader.onerror = () => reject(new Error('文件读取失败'));\n                    reader.readAsText(file);\n                });\n            }\n        };\n\n        // ==================== 聊天操作 (旧版) ====================\n        const ChatOperationsOld = {\n            findChatInput: () => {\n                return document.querySelector(CONFIG.SELECTORS.CHAT_INPUT_OLD_GENERIC);\n            },\n            findImageButton: () => {\n                const b = document.querySelector(CONFIG.SELECTORS.ATTACHMENT_BUTTON);\n                return b ? b.closest('button') : null;\n            },\n            findTranslationModeSendButton: async () => {\n                return new Promise((resolve, reject) => {\n                    let attempts = 0;\n                    const maxAttempts = USER_CONFIGURABLE_DELAYS.OLD_VERSION.BUTTON_FIND_TIMEOUT / 50; // Polling every 50ms\n                    const interval = setInterval(() => {\n                        // 优先查找文本为“原文发送”的按钮\n                        const originalBtn = Array.from(document.querySelectorAll(CONFIG.SELECTORS.ORIGINAL_SEND_BUTTON))\n                            .find(btn => btn.textContent.includes('原文发送'));\n                        if (originalBtn) {\n                            LoggerOld.info(\"旧版翻译模式: 找到首选按钮 '原文发送'\");\n                            clearInterval(interval);\n                            resolve(originalBtn);\n                            return;\n                        }\n                        // 备用查找文本为“发送”的主发送按钮\n                        const plainSendBtn = Array.from(document.querySelectorAll(CONFIG.SELECTORS.TRANSLATE_SEND_BUTTON))\n                            .find(btn => btn.textContent.trim() === '发送');\n                        if (plainSendBtn) {\n                            LoggerOld.info(\"旧版翻译模式: 找到备用按钮 '发送'\");\n                            clearInterval(interval);\n                            resolve(plainSendBtn);\n                            return;\n                        }\n                        if (++attempts >= maxAttempts) {\n                            clearInterval(interval);\n                            reject(new Error('旧版等待“原文发送”或“发送”按钮超时'));\n                        }\n                    }, 50);\n                });\n            },\n            findTranslateSendButton: async () => {\n                return new Promise((resolve, reject) => {\n                    let attempts = 0;\n                    const maxAttempts = USER_CONFIGURABLE_DELAYS.OLD_VERSION.BUTTON_FIND_TIMEOUT / 50; // Polling every 50ms\n                    const interval = setInterval(() => {\n                        const targetBtn = Array.from(document.querySelectorAll(CONFIG.SELECTORS.TRANSLATE_SEND_BUTTON))\n                            .find(btn => btn.textContent.includes('翻译发送'));\n                        if (targetBtn) {\n                            LoggerOld.info(\"旧版原文模式: 找到 '翻译发送' 按钮\");\n                            clearInterval(interval);\n                            resolve(targetBtn);\n                        }\n                        if (++attempts >= maxAttempts) {\n                            clearInterval(interval);\n                            reject(new Error('旧版等待“翻译发送”按钮超时'));\n                        }\n                    }, 50);\n                });\n            },\n            waitForImageUploader: (idx) => {\n                return new Promise(async (resolve, reject) => {\n                    let attempt = 0;\n                    const maxAttempts = USER_CONFIGURABLE_DELAYS.OLD_VERSION.BUTTON_FIND_TIMEOUT / 100; // Polling every 100ms\n\n                    const check = () => {\n                        // 旧版图片发送按钮通常是 ivu-btn ivu-btn-primary ivu-btn-small ivu-btn-ghost\n                        const btns = document.querySelectorAll('button.ivu-btn.ivu-btn-primary.ivu-btn-small.ivu-btn-ghost');\n                        if (btns.length >= idx) {\n                            const targetButton = btns[idx - 1];\n                            if (!targetButton) {\n                                if (++attempt < maxAttempts) {\n                                    setTimeout(check, 100);\n                                } else {\n                                    reject(new Error(`旧版图片上传界面加载超时或没有足够的图片选项`));\n                                }\n                                return;\n                            }\n\n                            // 检查按钮是否变为可点击状态 (颜色变化)\n                            let colorTry = 0;\n                            const maxColorChecks = USER_CONFIGURABLE_DELAYS.OLD_VERSION.IMAGE_BUTTON_COLOR_CHECK_TIMEOUT / 50; // Polling every 50ms\n\n                            const colorCheck = () => {\n                                // 旧版 SalesSmartly 的图片发送按钮在可用时是蓝色文字 #2e4fed\n                                const computedColor = window.getComputedStyle(targetButton).color;\n                                if (computedColor === 'rgb(46, 79, 237)') { // #2e4fed\n                                    DOMUtilsOld.click(targetButton);\n                                    LoggerOld.info(`旧版已发送图片选项 ${idx}`);\n                                    setTimeout(resolve, USER_CONFIGURABLE_DELAYS.OLD_VERSION.IMAGE_SEND_BUTTON_CLICK_DELAY);\n                                } else if (++colorTry < maxColorChecks) {\n                                    setTimeout(colorCheck, 50);\n                                } else {\n                                    reject(new Error(`旧版等待图片发送按钮颜色改变超时或按钮不可用`));\n                                }\n                            };\n                            setTimeout(colorCheck, 50); // Initial color check delay\n                        } else if (++attempt < maxAttempts) {\n                            setTimeout(check, 100);\n                        } else {\n                            reject(new Error('旧版图片上传界面加载超时或没有足够的图片选项'));\n                        }\n                    };\n                    check();\n                });\n            },\n            sendText: async (text) => {\n                try {\n                    if (!text) throw new Error('消息内容为空');\n                    const input = ChatOperationsOld.findChatInput();\n                    if (!input) throw new Error('未找到聊天输入框');\n                    DOMUtilsOld.setInputValue(input, text);\n                    await new Promise(res => setTimeout(res, USER_CONFIGURABLE_DELAYS.OLD_VERSION.MESSAGE_SEND_AFTER_TEXT_INPUT)); // Delay after setting input value\n\n                    if (AppState.currentSendMode === 'original') {\n                        LoggerOld.info('旧版执行原文模式发送 (等待点击“翻译发送”按钮)...');\n                        const sendBtn = await ChatOperationsOld.findTranslateSendButton();\n                        DOMUtilsOld.click(sendBtn);\n                        showNotification(`旧版原文发送: ${text.substring(0, 20)}...`);\n                    } else {\n                        LoggerOld.info('旧版执行翻译模式发送 (等待点击“原文发送”或“发送”按钮)...');\n                        const sendBtn = await ChatOperationsOld.findTranslationModeSendButton();\n                        DOMUtilsOld.click(sendBtn);\n                        showNotification(`旧版翻译发送: ${text.substring(0, 20)}...`);\n                    }\n                } catch (e) {\n                    LoggerOld.error('发送文本消息失败', e.message);\n                    showNotification(`❌ 发送失败: ${e.message}`, 'error');\n                    throw e;\n                }\n            },\n            sendImage: async (imageMsg) => {\n                try {\n                    if (AppState.isImageSending) throw new Error('上一张图片还在发送中，请等待...');\n                    LoggerOld.info(`🖼️ 开始发送图片 - 锁定发送状态`);\n                    AppState.isImageSending = true;\n\n                    try {\n                        const imgBtn = ChatOperationsOld.findImageButton();\n                        if (!imgBtn) throw new Error('未找到图片按钮');\n                        DOMUtilsOld.click(imgBtn);\n                        LoggerOld.info('已点击图片附件按钮');\n\n                        if (!imageMsg.index) {\n                            throw new Error('旧版图片发送必须提供图片索引 (imge=数字)');\n                        }\n\n                        // 检查是否重复发送\n                        if (AppState.lastSentImageIndex === imageMsg.index) {\n                            LoggerOld.warn(`⚠️ 检测到可能重复发送同一张图片索引: ${imageMsg.index}，已拦截`);\n                            throw new Error(`防重复: 该图片刚刚发送过，请勿重复操作`);\n                        }\n\n                        if (AppState.currentSendMode === 'original') {\n                            LoggerOld.info(`旧版原文模式下发送图片，添加 ${USER_CONFIGURABLE_DELAYS.OLD_VERSION.IMAGE_SEND_ORIGINAL_MODE_DELAY}ms 延迟...`);\n                            await new Promise(res => setTimeout(res, USER_CONFIGURABLE_DELAYS.OLD_VERSION.IMAGE_SEND_ORIGINAL_MODE_DELAY));\n                        }\n\n                        await ChatOperationsOld.waitForImageUploader(imageMsg.index);\n                        AppState.lastSentImageIndex = imageMsg.index; // 记录最后发送的图片索引\n                        showNotification(`✨ 图片索引 ${imageMsg.index} 发送完成`);\n\n                    } finally {\n                        AppState.isImageSending = false;\n                        LoggerOld.info(`🔓 解锁发送状态`);\n                    }\n                } catch (e) {\n                    LoggerOld.error('发送图片失败', e.message);\n                    showNotification(`❌ 发送图片失败: ${e.message}`, 'error');\n                    AppState.isImageSending = false;\n                    AppState.lastSentImageIndex = -1; // 重置状态\n                    throw e;\n                }\n            }\n        };\n\n        // ==================== 自定义规则编辑器 (旧版) ====================\n        const RuleEditorOld = {\n            modalOverlay: null,\n            currentRuleId: null,\n            messageCounter: 0,\n            init: () => {\n                RuleEditorOld.modalOverlay = document.createElement('div');\n                RuleEditorOld.modalOverlay.className = 'ss-modal-overlay';\n                RuleEditorOld.modalOverlay.innerHTML = `\n            <div class=\"ss-modal-content\">\n                <div class=\"ss-modal-header\">\n                    <h3 id=\"editor-modal-title\">新增自定义按键</h3>\n                    <button class=\"close-btn\">${COMMON_ICONS.times}</button>\n                </div>\n                <div class=\"ss-modal-body\">\n                    <label for=\"rule-name\">按键名称:</label>\n                    <input type=\"text\" id=\"rule-name\" placeholder=\"输入按键名称\" required>\n                    <label>按钮颜色:</label>\n                    <div class=\"color-picker-group\">\n                        <input type=\"color\" id=\"rule-color\" value=\"#ffffff\">\n                        <div id=\"color-preview\" class=\"color-preview\" style=\"background-color: #ffffff;\"></div>\n                    </div>\n                    <label>消息列表:</label>\n                    <div id=\"messages-container\"></div>\n                    <button id=\"add-text-msg-btn\" class=\"add-custom-rule-btn\" style=\"background: #60a5fa; margin-bottom: 10px;\">${COMMON_ICONS.plus} 添加文本消息</button>\n                    <button id=\"add-image-msg-btn\" class=\"add-custom-rule-btn\" style=\"background: #60a5fa;\">${COMMON_ICONS.plus} 添加图片消息</button>\n                </div>\n                <div class=\"ss-modal-footer\">\n                    <button id=\"delete-rule-btn\" class=\"btn-delete\" style=\"display:none;\">删除</button>\n                    <button id=\"cancel-edit-btn\" class=\"btn-cancel\">取消</button>\n                    <button id=\"save-rule-btn\" class=\"btn-save\">保存</button>\n                </div>\n            </div>`;\n                document.body.appendChild(RuleEditorOld.modalOverlay);\n                RuleEditorOld.bindEvents();\n            },\n            bindEvents: () => {\n                RuleEditorOld.modalOverlay.querySelector('.close-btn').addEventListener('click', RuleEditorOld.closeEditor);\n                RuleEditorOld.modalOverlay.querySelector('#cancel-edit-btn').addEventListener('click', RuleEditorOld.closeEditor);\n                RuleEditorOld.modalOverlay.querySelector('#save-rule-btn').addEventListener('click', RuleEditorOld.saveRule);\n                RuleEditorOld.modalOverlay.querySelector('#delete-rule-btn').addEventListener('click', RuleEditorOld.deleteRule);\n                RuleEditorOld.modalOverlay.querySelector('#add-text-msg-btn').addEventListener('click', () => RuleEditorOld.addMessageInput('text'));\n                RuleEditorOld.modalOverlay.querySelector('#add-image-msg-btn').addEventListener('click', () => RuleEditorOld.addMessageInput('image'));\n                RuleEditorOld.modalOverlay.querySelector('#rule-color').addEventListener('input', (e) => {\n                    RuleEditorOld.modalOverlay.querySelector('#color-preview').style.backgroundColor = e.target.value;\n                });\n                RuleEditorOld.modalOverlay.addEventListener('click', (e) => {\n                    if (e.target === RuleEditorOld.modalOverlay) RuleEditorOld.closeEditor();\n                });\n            },\n            openEditor: (ruleId = null, initialRuleData = null) => {\n                RuleEditorOld.currentRuleId = ruleId;\n                RuleEditorOld.messageCounter = 0;\n                const modalTitle = RuleEditorOld.modalOverlay.querySelector('#editor-modal-title');\n                const ruleNameInput = RuleEditorOld.modalOverlay.querySelector('#rule-name');\n                const ruleColorInput = RuleEditorOld.modalOverlay.querySelector('#rule-color');\n                const colorPreview = RuleEditorOld.modalOverlay.querySelector('#color-preview');\n                const messagesContainer = RuleEditorOld.modalOverlay.querySelector('#messages-container');\n                const deleteBtn = RuleEditorOld.modalOverlay.querySelector('#delete-rule-btn');\n                messagesContainer.innerHTML = '';\n\n                let ruleToEdit = initialRuleData;\n                if (ruleId && !initialRuleData) { // 如果有ID但没有初始数据，说明是编辑现有自定义规则\n                    ruleToEdit = AppState.customRules.find(r => r.id === ruleId);\n                }\n\n                if (ruleToEdit) {\n                    modalTitle.textContent = '编辑自定义按键';\n                    deleteBtn.style.display = 'inline-block';\n                    ruleNameInput.value = ruleToEdit.name || '';\n                    ruleColorInput.value = ruleToEdit.color || '#ffffff';\n                    colorPreview.style.backgroundColor = ruleToEdit.color || '#ffffff';\n                    ruleToEdit.messages.forEach(msg => RuleEditorOld.addMessageInput(msg.type, msg));\n                } else {\n                    modalTitle.textContent = '新增自定义按键';\n                    deleteBtn.style.display = 'none';\n                    ruleNameInput.value = '';\n                    ruleColorInput.value = '#ffffff';\n                    colorPreview.style.backgroundColor = '#ffffff';\n                    RuleEditorOld.addMessageInput('text'); // 默认添加一个文本消息输入框\n                }\n                RuleEditorOld.modalOverlay.classList.add('show');\n            },\n            closeEditor: () => {\n                RuleEditorOld.modalOverlay.classList.remove('show');\n            },\n            addMessageInput: (type, messageData = {}) => {\n                RuleEditorOld.messageCounter++;\n                const messagesContainer = RuleEditorOld.modalOverlay.querySelector('#messages-container');\n                const msgId = `msg-${RuleEditorOld.messageCounter}`;\n                const messageItem = document.createElement('div');\n                messageItem.className = 'message-item';\n                messageItem.dataset.msgType = type;\n                messageItem.dataset.msgId = msgId;\n\n                let content = '';\n                if (type === 'text') {\n                    content = `\n                <label for=\"${msgId}-translated\">译文:</label>\n                <textarea id=\"${msgId}-translated\" placeholder=\"输入译文\" required>${messageData.translated || ''}</textarea>\n                <label for=\"${msgId}-original\">原文 (可选):</label>\n                <textarea id=\"${msgId}-original\" placeholder=\"输入原文 (用于翻译模式发送)\" >${messageData.original || ''}</textarea>`;\n                } else if (type === 'image') {\n                    content = `\n                <label for=\"${msgId}-image-name\">图片名称 (新版):</label>\n                <input type=\"text\" id=\"${msgId}-image-name\" placeholder=\"输入图片完整名称 (如: example.jpg)\" value=\"${messageData.name || ''}\" required>\n                <label for=\"${msgId}-image-index\">图片索引 (旧版, 1开始):</label>\n                <input type=\"number\" id=\"${msgId}-image-index\" placeholder=\"输入图片在附件列表中的序号\" value=\"${messageData.index || ''}\" min=\"1\">`;\n                }\n                messageItem.innerHTML = `${content}<button class=\"remove-msg-btn\">${COMMON_ICONS.times}</button>`;\n                messageItem.querySelector('.remove-msg-btn').addEventListener('click', () => messageItem.remove());\n                messagesContainer.appendChild(messageItem);\n            },\n            saveRule: () => {\n                const ruleName = RuleEditorOld.modalOverlay.querySelector('#rule-name').value.trim();\n                const ruleColor = RuleEditorOld.modalOverlay.querySelector('#rule-color').value;\n                const messageElements = RuleEditorOld.modalOverlay.querySelectorAll('.message-item');\n                if (!ruleName) {\n                    showNotification('按键名称不能为空', 'error');\n                    return;\n                }\n                if (messageElements.length === 0) {\n                    showNotification('至少需要一条消息', 'error');\n                    return;\n                }\n                const messages = [];\n                for (const msgEl of messageElements) {\n                    const type = msgEl.dataset.msgType;\n                    if (type === 'text') {\n                        const translated = msgEl.querySelector('textarea[id$=\"-translated\"]').value.trim();\n                        const original = msgEl.querySelector('textarea[id$=\"-original\"]').value.trim();\n                        if (!translated) {\n                            showNotification('文本消息的译文不能为空', 'error');\n                            return;\n                        }\n                        messages.push({ type: 'text', translated, original: original || null });\n                    } else if (type === 'image') {\n                        const imageName = msgEl.querySelector('input[id$=\"-image-name\"]').value.trim();\n                        const imageIndexStr = msgEl.querySelector('input[id$=\"-image-index\"]').value.trim();\n                        const imageIndex = imageIndexStr ? parseInt(imageIndexStr, 10) : null;\n                        if (!imageName && !imageIndex) {\n                            showNotification('图片消息必须提供图片名称或旧版索引', 'error');\n                            return;\n                        }\n                        if (imageIndex && isNaN(imageIndex)) {\n                            showNotification('旧版图片索引必须是数字', 'error');\n                            return;\n                        }\n                        messages.push({ type: 'image', name: imageName || null, index: imageIndex });\n                    }\n                }\n                let updatedRules = [...AppState.customRules];\n                if (RuleEditorOld.currentRuleId) {\n                    const ruleIndex = updatedRules.findIndex(r => r.id === RuleEditorOld.currentRuleId);\n                    if (ruleIndex !== -1) {\n                        updatedRules[ruleIndex] = { ...updatedRules[ruleIndex], name: ruleName, color: ruleColor, messages };\n                        showNotification(`按键 \"${ruleName}\" 已更新`);\n                    }\n                } else {\n                    const newRule = {\n                        id: `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n                        name: ruleName,\n                        color: ruleColor,\n                        messages,\n                        category: AppState.CUSTOM_CATEGORY_NAME,\n                        isCustom: true\n                    };\n                    updatedRules.push(newRule);\n                    showNotification(`按键 \"${newRule.name}\" 已添加`);\n                }\n                AppState.setCustomRules(updatedRules);\n                RuleEditorOld.closeEditor();\n                panelManagerOld.loadRules();\n            },\n            deleteRule: () => {\n                if (!RuleEditorOld.currentRuleId || !confirm('确定要删除此自定义按键吗？')) return;\n                const updatedRules = AppState.customRules.filter(r => r.id !== RuleEditorOld.currentRuleId);\n                AppState.setCustomRules(updatedRules);\n                showNotification('按键已删除');\n                RuleEditorOld.closeEditor();\n                panelManagerOld.loadRules();\n            }\n        };\n\n        // ==================== 拖放逻辑 (旧版) ====================\n        let dragMouseDownListenerOld = null;\n        let dragMouseMoveListenerOld = null;\n        let dragMouseUpListenerOld = null;\n\n        function setupDragAndDropOld(container) {\n            if (dragMouseDownListenerOld) {\n                container.removeEventListener('mousedown', dragMouseDownListenerOld);\n                document.removeEventListener('mousemove', dragMouseMoveListenerOld);\n                document.removeEventListener('mouseup', dragMouseUpListenerOld);\n            }\n\n            dragMouseDownListenerOld = (e) => {\n                const dragHandle = e.target.closest('.drag-handle');\n                if (!dragHandle) return;\n\n                e.preventDefault();\n                document.body.style.cursor = 'grabbing';\n                document.body.style.userSelect = 'none';\n\n                const originalElement = dragHandle.closest('.action-btn');\n                if (!originalElement) return;\n\n                const ruleId = originalElement.dataset.ruleId;\n                const isCustom = originalElement.dataset.isCustom === 'true';\n\n                let draggedRule = null;\n                if (isCustom) {\n                    draggedRule = AppState.customRules.find(r => r.id === ruleId);\n                } else {\n                    draggedRule = AppState.allParsedRules.find(r => r.id === ruleId);\n                }\n                if (!draggedRule) {\n                    LoggerOld.error('未找到被拖拽的规则对象', {\n                        ruleId,\n                        isCustom\n                    });\n                    return;\n                }\n\n                const rect = originalElement.getBoundingClientRect();\n                const offsetX = e.clientX - (rect.left + rect.width / 2);\n                const offsetY = e.clientY - (rect.top + rect.height / 2);\n\n                const ghostElement = originalElement.cloneNode(true);\n                ghostElement.classList.add('ss-drag-ghost');\n                ghostElement.style.width = `${rect.width}px`;\n                ghostElement.style.height = `${rect.height}px`;\n                ghostElement.style.left = `${e.clientX - offsetX}px`;\n                ghostElement.style.top = `${e.clientY - offsetY}px`;\n                document.body.appendChild(ghostElement);\n\n                AppState.dragState = {\n                    isDragging: true,\n                    ghostElement,\n                    originalElement,\n                    draggedId: ruleId,\n                    draggedRule: draggedRule,\n                    isCustomSource: isCustom,\n                    offsetX,\n                    offsetY,\n                };\n\n                originalElement.classList.add('dragging-source');\n            };\n\n            dragMouseMoveListenerOld = (e) => {\n                if (!AppState.dragState.isDragging) return;\n                const {\n                    ghostElement,\n                    offsetX,\n                    offsetY\n                } = AppState.dragState;\n                if (ghostElement) {\n                    ghostElement.style.left = `${e.clientX - offsetX}px`;\n                    ghostElement.style.top = `${e.clientY - offsetY}px`;\n                }\n                updateDropIndicatorOld(e);\n            };\n\n            dragMouseUpListenerOld = (e) => {\n                if (!AppState.dragState.isDragging) return;\n\n                document.body.style.cursor = '';\n                document.body.style.userSelect = '';\n\n                const {\n                    ghostElement,\n                    originalElement,\n                    draggedId,\n                    draggedRule,\n                    isCustomSource\n                } = AppState.dragState;\n\n                ghostElement.remove();\n                originalElement.classList.remove('dragging-source');\n                document.querySelectorAll('.drag-over-indicator').forEach(el => el.remove());\n                document.querySelectorAll('.category-header.custom-category.drag-over-target').forEach(el => el.classList.remove('drag-over-target'));\n\n                const dropTarget = findDropTargetOld(e);\n                let rules = [...AppState.customRules];\n\n                if (dropTarget) {\n                    if (dropTarget.element.classList.contains('custom-category')) {\n                        if (!isCustomSource) {\n                            const newCustomRule = {\n                                ...draggedRule,\n                                id: `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n                                category: AppState.CUSTOM_CATEGORY_NAME,\n                                isCustom: true\n                            };\n                            rules.push(newCustomRule);\n                            AppState.setCustomRules(rules);\n                            showNotification(`按键 \"${newCustomRule.name}\" 已复制到自定义分类`);\n                        } else {\n                            const draggedIndex = rules.findIndex(r => r.id === draggedId);\n                            if (draggedIndex > -1) {\n                                const [ruleToMove] = rules.splice(draggedIndex, 1);\n                                rules.push(ruleToMove);\n                                AppState.setCustomRules(rules);\n                                showNotification(`按键 \"${ruleToMove.name}\" 已移动到自定义分类末尾`);\n                            }\n                        }\n                    } else if (dropTarget.element.dataset.ruleId) {\n                        const targetRuleId = dropTarget.element.dataset.ruleId;\n                        const targetIsCustom = dropTarget.element.dataset.isCustom === 'true';\n\n                        if (targetIsCustom) {\n                            const draggedIndex = rules.findIndex(r => r.id === draggedId);\n                            let targetIndex = rules.findIndex(r => r.id === targetRuleId);\n\n                            if (draggedIndex > -1 && targetIndex > -1) {\n                                if (isCustomSource) {\n                                    if (draggedId !== targetRuleId) {\n                                        const [ruleToMove] = rules.splice(draggedIndex, 1);\n                                        if (draggedIndex < targetIndex) {\n                                            targetIndex--;\n                                        }\n                                        if (dropTarget.position === 'after') {\n                                            rules.splice(targetIndex + 1, 0, ruleToMove);\n                                        } else {\n                                            rules.splice(targetIndex, 0, ruleToMove);\n                                        }\n                                        AppState.setCustomRules(rules);\n                                        showNotification(`按键 \"${ruleToMove.name}\" 顺序已更新`);\n                                    }\n                                } else {\n                                    const newCustomRule = {\n                                        ...draggedRule,\n                                        id: `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n                                        category: AppState.CUSTOM_CATEGORY_NAME,\n                                        isCustom: true\n                                    };\n                                    if (dropTarget.position === 'after') {\n                                        rules.splice(targetIndex + 1, 0, newCustomRule);\n                                    } else {\n                                        rules.splice(targetIndex, 0, newCustomRule);\n                                    }\n                                    AppState.setCustomRules(rules);\n                                    showNotification(`按键 \"${newCustomRule.name}\" 已复制到自定义分类`);\n                                }\n                            }\n                        } else {\n                            showNotification('只能将按键拖拽到“自定义”分类内或其头部', 'error');\n                        }\n                    }\n                }\n\n                panelManagerOld.loadRules();\n\n                AppState.dragState = {\n                    isDragging: false,\n                    ghostElement: null,\n                    originalElement: null,\n                    draggedId: null,\n                    draggedRule: null,\n                    isCustomSource: false,\n                    offsetX: 0,\n                    offsetY: 0,\n                };\n            };\n\n            container.addEventListener('mousedown', dragMouseDownListenerOld);\n            document.addEventListener('mousemove', dragMouseMoveListenerOld);\n            document.addEventListener('mouseup', dragMouseUpListenerOld);\n\n            function findDropTargetOld(e) {\n                const customCategoryHeader = document.querySelector('.category-header.custom-category');\n                if (customCategoryHeader) {\n                    const headerRect = customCategoryHeader.getBoundingClientRect();\n                    if (e.clientX >= headerRect.left && e.clientX <= headerRect.right &&\n                        e.clientY >= headerRect.top && e.clientY <= headerRect.bottom) {\n                        return {\n                            element: customCategoryHeader,\n                            position: 'header'\n                        };\n                    }\n                }\n\n                const customButtons = Array.from(container.querySelectorAll('.action-btn:not(.dragging-source)'));\n                for (const btn of customButtons) {\n                    const ruleCategory = AppState.getAllRulesCombined().find(r => r.id === btn.dataset.ruleId)?.category;\n                    if (ruleCategory !== AppState.CUSTOM_CATEGORY_NAME) continue;\n\n                    const rect = btn.getBoundingClientRect();\n                    if (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom) {\n                        const isAfter = e.clientY > rect.top + rect.height / 2;\n                        return {\n                            element: btn,\n                            position: isAfter ? 'after' : 'before'\n                        };\n                    }\n                }\n                return null;\n            }\n\n            function updateDropIndicatorOld(e) {\n                document.querySelectorAll('.drag-over-indicator').forEach(el => el.remove());\n                document.querySelectorAll('.category-header.custom-category.drag-over-target').forEach(el => el.classList.remove('drag-over-target'));\n\n                const dropTarget = findDropTargetOld(e);\n                if (dropTarget) {\n                    if (dropTarget.element.classList.contains('custom-category')) {\n                        dropTarget.element.classList.add('drag-over-target');\n                    } else {\n                        const indicator = document.createElement('div');\n                        indicator.className = 'drag-over-indicator';\n                        if (dropTarget.position === 'after') {\n                            dropTarget.element.parentNode.insertBefore(indicator, dropTarget.element.nextSibling);\n                        } else {\n                            dropTarget.element.parentNode.insertBefore(indicator, dropTarget.element);\n                        }\n                    }\n                }\n            }\n        }\n\n\n        // ==================== UI 渲染 (旧版) ====================\n        const UIRendererOld = {\n            hoverInfoPopup: null,\n            initHoverInfoPopup: () => {\n                UIRendererOld.hoverInfoPopup = document.createElement('div');\n                UIRendererOld.hoverInfoPopup.className = 'ss-hover-info-popup';\n                document.body.appendChild(UIRendererOld.hoverInfoPopup);\n            },\n            showHoverInfo: (rule, buttonElement) => {\n                clearTimeout(AppState.hoverInfoPopupTimer);\n                AppState.hoverInfoPopupTimer = setTimeout(() => {\n                    if (!UIRendererOld.hoverInfoPopup || !buttonElement || !document.contains(buttonElement)) return;\n\n                    let content = `<strong>${rule.name}</strong>`;\n                    rule.messages.forEach((msg, index) => {\n                        content += `<div class=\"info-section\">`;\n                        if (msg.type === 'text') {\n                            content += `<div class=\"info-section-title\">${COMMON_ICONS.comment} 文本消息 ${index + 1}:</div>`;\n                            content += `<pre>${msg.translated}</pre>`;\n                            if (msg.original) {\n                                content += `<div class=\"info-section-title\">原文:</div>`;\n                                content += `<pre>${msg.original}</pre>`;\n                            }\n                        } else if (msg.type === 'image') {\n                            content += `<div class=\"info-section-title\">${COMMON_ICONS.folder} 图片消息 ${index + 1}:</div>`;\n                            if (msg.name) {\n                                content += `<pre>名称 (新版): ${msg.name}</pre>`;\n                            }\n                            if (msg.index) {\n                                content += `<pre>索引 (旧版): ${msg.index}</pre>`;\n                            }\n                        }\n                        content += `</div>`;\n                    });\n\n                    UIRendererOld.hoverInfoPopup.innerHTML = content;\n\n                    const panelRect = panelManagerOld.panel.getBoundingClientRect();\n                    const buttonRect = buttonElement.getBoundingClientRect();\n\n                    let popupLeft = panelRect.left - UIRendererOld.hoverInfoPopup.offsetWidth - 15;\n                    let popupTop = buttonRect.top;\n\n                    if (popupLeft < 0) {\n                        popupLeft = panelRect.right + 15;\n                    }\n                    if (popupTop < 0) popupTop = 0;\n                    if (popupTop + UIRendererOld.hoverInfoPopup.offsetHeight > window.innerHeight) {\n                        popupTop = window.innerHeight - UIRendererOld.hoverInfoPopup.offsetHeight;\n                    }\n\n                    UIRendererOld.hoverInfoPopup.style.left = `${popupLeft}px`;\n                    UIRendererOld.hoverInfoPopup.style.top = `${popupTop}px`;\n                    UIRendererOld.hoverInfoPopup.classList.add('show');\n                }, USER_CONFIGURABLE_DELAYS.OLD_VERSION.HOVER_INFO_POPUP_DELAY);\n            },\n            hideHoverInfo: () => {\n                clearTimeout(AppState.hoverInfoPopupTimer);\n                if (UIRendererOld.hoverInfoPopup) {\n                    UIRendererOld.hoverInfoPopup.classList.remove('show');\n                }\n            },\n            renderButtonsPanel: (container) => {\n                container.innerHTML = '';\n                const allRules = AppState.getAllRulesCombined();\n                const rulesByCategory = new Map();\n\n                // 应用紧凑模式\n                if (AppState.compactMode) {\n                    container.classList.add('compact-mode');\n                } else {\n                    container.classList.remove('compact-mode');\n                }\n\n                allRules.forEach(rule => {\n                    if (!rulesByCategory.has(rule.category)) {\n                        rulesByCategory.set(rule.category, []);\n                    }\n                    rulesByCategory.get(rule.category).push(rule);\n                });\n\n                AppState.selectedCategoriesOrder.forEach(category => {\n                    if (!AppState.hiddenCategories.includes(category)) {\n                        const categoryRules = rulesByCategory.get(category) || [];\n                        const isCustomCategory = category === AppState.CUSTOM_CATEGORY_NAME;\n\n                        if (isCustomCategory || categoryRules.length > 0) {\n                            UIRendererOld._renderCategoryHeader(container, category, isCustomCategory);\n\n                            const rulesToDisplay = isCustomCategory ? AppState.customRules : categoryRules;\n\n                            rulesToDisplay.forEach(rule => {\n                                const button = document.createElement('div');\n                                button.className = `action-btn`;\n                                if (rule.isCustom) {\n                                    button.classList.add('custom-rule');\n                                }\n                                if (rule.color) {\n                                    button.style.backgroundColor = rule.color;\n                                    button.style.color = UIRendererOld.getContrastTextColor(rule.color);\n                                }\n                                button.innerHTML = `\n                                    <span class=\"action-icon-wrapper\">${COMMON_ICONS.comment}</span>\n                                    <span>${rule.name}</span>\n                                    <div class=\"hover-actions\" data-rule-id=\"${rule.id}\">\n                                        ${rule.isCustom ? `<button class=\"hover-action-btn edit-rule-btn\" title=\"编辑\">${COMMON_ICONS.edit}</button>` : ''}\n                                    </div>\n                                    <div class=\"drag-handle-wrapper\">\n                                        <button class=\"hover-action-btn drag-handle\" title=\"拖动\">${COMMON_ICONS.gripVertical}</button>\n                                    </div>`;\n\n                                button.dataset.ruleId = rule.id;\n                                button.dataset.isCustom = rule.isCustom;\n\n                                let hoverTimer;\n                                button.addEventListener('mouseenter', () => {\n                                    clearTimeout(hoverTimer);\n                                    hoverTimer = setTimeout(() => button.classList.add('show-actions'), USER_CONFIGURABLE_DELAYS.OLD_VERSION.HOVER_ACTION_DELAY);\n                                    UIRendererOld.showHoverInfo(rule, button);\n                                });\n                                button.addEventListener('mouseleave', () => {\n                                    clearTimeout(hoverTimer);\n                                    button.classList.remove('show-actions');\n                                    UIRendererOld.hideHoverInfo();\n                                });\n\n                                button.addEventListener('click', (event) => {\n                                    if (event.target.closest('.drag-handle')) {\n                                        return;\n                                    }\n                                    if (event.target.closest('.edit-rule-btn')) {\n                                        event.stopPropagation();\n                                        RuleEditorOld.openEditor(rule.id);\n                                        return;\n                                    }\n                                    if (event.target.closest('.action-icon-wrapper')) {\n                                        UIRendererOld.populateRuleContent(rule);\n                                    } else {\n                                        RuleExecutorOld.executeRule(rule);\n                                    }\n                                });\n\n                                container.appendChild(button);\n                            });\n                        }\n                    }\n                });\n\n                const hasVisibleRules = AppState.selectedCategoriesOrder.some(cat => {\n                    return !AppState.hiddenCategories.includes(cat) && (\n                        (cat === AppState.CUSTOM_CATEGORY_NAME && AppState.customRules.length > 0) ||\n                        (cat !== AppState.CUSTOM_CATEGORY_NAME && rulesByCategory.get(cat) && rulesByCategory.get(cat).length > 0)\n                    );\n                });\n\n                if (!hasVisibleRules) {\n                    container.innerHTML = `<div class=\"loading\">${COMMON_ICONS.exclamationTriangle}<span>当前没有规则可显示。请在设置中选择要显示的分类。</span></div>`;\n                }\n            },\n            _renderCategoryHeader: (container, categoryName, isCustomCategory) => {\n                const categoryHeader = document.createElement('div');\n                categoryHeader.className = `category-header ${isCustomCategory ? 'custom-category' : ''}`;\n                categoryHeader.textContent = categoryName;\n                if (isCustomCategory) {\n                    const addBtn = document.createElement('button');\n                    addBtn.className = 'add-custom-rule-btn';\n                    addBtn.innerHTML = `${COMMON_ICONS.plus} 增加按键`;\n                    addBtn.title = '添加新的自定义快捷回复';\n                    addBtn.addEventListener('click', (e) => {\n                        e.stopPropagation();\n                        RuleEditorOld.openEditor();\n                    });\n                    categoryHeader.appendChild(addBtn);\n                }\n                container.appendChild(categoryHeader);\n            },\n            populateRuleContent: (rule) => {\n                const firstTextMessage = rule.messages.find(msg => msg.type === 'text');\n                if (!firstTextMessage) {\n                    showNotification(`警告: 规则 \"${rule.name}\" 中没有文本消息可填充`, 'error');\n                    return;\n                }\n                const textToPopulate = (AppState.currentSendMode === 'original' && firstTextMessage.original) ? firstTextMessage.original : firstTextMessage.translated;\n                if (!textToPopulate) {\n                    showNotification(`警告: 模式为'${AppState.currentSendMode}'，但文本为空`, 'error');\n                    return;\n                }\n                const input = ChatOperationsOld.findChatInput();\n                if (!input) {\n                    showNotification('未找到聊天输入框', 'error');\n                    return;\n                }\n                DOMUtilsOld.setInputValue(input, textToPopulate);\n                showNotification(`已填充`);\n            },\n            getContrastTextColor: (hexcolor) => {\n                if (!hexcolor || typeof hexcolor !== 'string' || !/^#([A-Fa-f0-9]{3}){1,2}$/.test(hexcolor)) return '#333';\n                let r = 0,\n                    g = 0,\n                    b = 0;\n                if (hexcolor.length === 4) {\n                    r = parseInt(hexcolor[1] + hexcolor[1], 16);\n                    g = parseInt(hexcolor[2] + hexcolor[2], 16);\n                    b = parseInt(hexcolor[3] + hexcolor[3], 16);\n                } else if (hexcolor.length === 7) {\n                    r = parseInt(hexcolor.substring(1, 3), 16);\n                    g = parseInt(hexcolor.substring(3, 5), 16);\n                    b = parseInt(hexcolor.substring(5, 7), 16);\n                }\n                const toLinear = (c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n                const L = 0.2126 * toLinear(r / 255) + 0.7152 * toLinear(g / 255) + 0.0722 * toLinear(b / 255);\n                return L > 0.179 ? '#333' : '#fff';\n            },\n            updateReplyStatus: (statusText, isReplying) => {\n                const statusElement = document.getElementById('ss-reply-status');\n                if (statusElement) {\n                    statusElement.textContent = statusText;\n                    statusElement.classList.remove('idle', 'replying');\n                    statusElement.classList.add(isReplying ? 'replying' : 'idle');\n                }\n                AppState.setReplyStatus(isReplying);\n            }\n        };\n\n        // ==================== 旧版规则执行器 ====================\n        const RuleExecutorOld = {\n            executeRule: async (rule) => {\n                if (AppState.isReplying) {\n                    showNotification('正在回复中...', 'error');\n                    return;\n                }\n                LoggerOld.info(`▶️ 执行规则 (旧版): ${rule.name}`);\n                UIRendererOld.updateReplyStatus('回复中', true);\n                try {\n                    for (const msg of rule.messages) {\n                        if (msg.type === 'text') {\n                            const textToSend = (AppState.currentSendMode === 'original' && msg.original) ? msg.original : msg.translated;\n                            if (!textToSend) {\n                                showNotification(`文本为空`, 'error');\n                                continue;\n                            }\n                            await ChatOperationsOld.sendText(textToSend);\n                            await new Promise(res => setTimeout(res, USER_CONFIGURABLE_DELAYS.OLD_VERSION.MESSAGE_SEND_AFTER_CLICK));\n                        } else if (msg.type === 'image') {\n                            await ChatOperationsOld.sendImage(msg);\n                            await new Promise(res => setTimeout(res, USER_CONFIGURABLE_DELAYS.OLD_VERSION.MESSAGE_SEND_AFTER_CLICK));\n                        }\n                    }\n                    LoggerOld.info(`✅ 规则执行完成 (旧版)`);\n                    showNotification(`✅ 执行完成 (旧版)`);\n                } catch (err) {\n                    LoggerOld.error('规则执行失败 (旧版)', err.message);\n                } finally {\n                    UIRendererOld.updateReplyStatus('空闲', false);\n                    AppState.lastSentImageIndex = -1;\n                }\n            }\n        };\n\n        // ==================== 旧版持续高亮监测 ====================\n        function startHighlightMonitoringOld() {\n            if (AppState.highlightCheckInterval) clearInterval(AppState.highlightCheckInterval);\n            AppState.highlightCheckInterval = setInterval(() => {\n                const attachmentButton = document.querySelector(CONFIG.SELECTORS.ATTACHMENT_BUTTON);\n                if (attachmentButton && !AppState.highlightedAttachmentBtn) {\n                    AppState.highlightedAttachmentBtn = attachmentButton;\n                    const updateHighlightBox = () => {\n                        document.querySelectorAll('.ss-highlight-box').forEach(el => el.remove());\n                        const rect = attachmentButton.getBoundingClientRect();\n                        const highlightBox = document.createElement('div');\n                        highlightBox.className = 'ss-highlight-box';\n                        highlightBox.style.left = `${rect.left - 8}px`;\n                        highlightBox.style.top = `${rect.top - 8}px`;\n                        highlightBox.style.width = `${rect.width + 16}px`;\n                        highlightBox.style.height = `${rect.height + 16}px`;\n                        document.body.appendChild(highlightBox);\n                        AppState.highlightBox = highlightBox;\n                    };\n                    updateHighlightBox();\n                    const positionMonitor = setInterval(() => {\n                        if (!attachmentButton || !document.contains(attachmentButton)) {\n                            clearInterval(positionMonitor);\n                            AppState.highlightedAttachmentBtn = null;\n                            document.querySelectorAll('.ss-highlight-box').forEach(el => el.remove());\n                            return;\n                        }\n                        updateHighlightBox();\n                    }, 100);\n                    const observer = new MutationObserver(() => {\n                        if (!document.contains(attachmentButton)) {\n                            clearInterval(positionMonitor);\n                            observer.disconnect();\n                            AppState.highlightedAttachmentBtn = null;\n                            document.querySelectorAll('.ss-highlight-box').forEach(el => el.remove());\n                        }\n                    });\n                    observer.observe(document.body, {\n                        childList: true,\n                        subtree: true\n                    });\n                } else if (!attachmentButton && AppState.highlightedAttachmentBtn) {\n                    AppState.highlightedAttachmentBtn = null;\n                    document.querySelectorAll('.ss-highlight-box').forEach(el => el.remove());\n                }\n            }, USER_CONFIGURABLE_DELAYS.OLD_VERSION.HIGHLIGHT_CHECK || 500);\n        }\n\n        // ==================== 旧版面板管理器 ====================\n        // PanelManager 类在上面已经定义，但需要确保它在旧版逻辑中被正确实例化和使用\n        // 并且其 loadRules, create, bindEvents 等方法内部调用的是旧版特有的函数和选择器\n        // 实际上，我已经在 PanelManager 的 create 和 bindEvents 中通过 CONFIG.SELECTORS 区分了新旧版\n        // 并且 loadRules 依赖于 AppState.useRemoteRules 来决定加载哪个规则，所以 PanelManager 可以通用\n        let panelManagerOld; // 旧版使用独立的 panelManager 实例\n        class PanelManagerOld {\n            constructor() {\n                this.panel = null;\n                this.controlButtons = null;\n                this.fileInput = null;\n            }\n            async loadRules(isManualRefresh = false) {\n                const container = this.panel.querySelector(CONFIG.SELECTORS.BUTTONS_CONTAINER);\n                container.innerHTML = `<div class=\"loading\">${COMMON_ICONS.spinner}<span>正在加载规则...</span></div>`;\n                try {\n                    let parsedRulesResult;\n                    if (AppState.useRemoteRules) {\n                        parsedRulesResult = await RuleLoaderOld.loadRemote();\n                    } else {\n                        parsedRulesResult = await RuleLoaderOld.loadLocal();\n                    }\n                    AppState.allParsedRules = parsedRulesResult.rules;\n                    AppState.allAvailableCategories = parsedRulesResult.categories;\n                    try {\n                        const storedCustomRules = GM_getValue(STORAGE_KEYS.CUSTOM_RULES, '[]');\n                        AppState.customRules = JSON.parse(storedCustomRules);\n                    } catch (e) {\n                        LoggerOld.error('加载自定义规则失败，重置为 []', e);\n                        AppState.customRules = [];\n                        GM_setValue(STORAGE_KEYS.CUSTOM_RULES, '[]');\n                    }\n\n                    const storedSelectedOrder = GM_getValue(STORAGE_KEYS.SELECTED_CATEGORIES, []);\n                    const storedHiddenCategories = GM_getValue(STORAGE_KEYS.HIDDEN_CATEGORIES, []);\n                    AppState.initializeSelectedCategories(storedSelectedOrder, storedHiddenCategories);\n\n                    this.populateCategorySelect(AppState.getAllCategoriesCombined());\n                    UIRendererOld.renderButtonsPanel(container);\n                    if (isManualRefresh) {\n                        showNotification(`成功刷新 ${AppState.getAllRulesCombined().length} 条规则`);\n                    }\n                } catch (e) {\n                    LoggerOld.error('加载规则失败', e.message);\n                    container.innerHTML = `<div class=\"loading\">${COMMON_ICONS.exclamationTriangle}<span>${e.message}</span></div>`;\n                    showNotification(e.message, 'error');\n                }\n            }\n            populateCategorySelect(categories) {\n                const categoryOptionsPopup = this.panel.querySelector('#category-options-popup');\n                if (!categoryOptionsPopup) return;\n                categoryOptionsPopup.innerHTML = '';\n\n                // 按照 categories 数组的顺序来渲染复选框 (categories 此时是所有可用分类，已排序)\n                const sortedCategoriesForPopup = [...categories].sort((a, b) => {\n                    if (a === AppState.CUSTOM_CATEGORY_NAME) return -1;\n                    if (b === AppState.CUSTOM_CATEGORY_NAME) return 1;\n                    return a.localeCompare(b);\n                });\n\n                sortedCategoriesForPopup.forEach(category => {\n                    const label = document.createElement('label');\n                    const checkbox = document.createElement('input');\n                    checkbox.type = 'checkbox';\n                    checkbox.name = 'category';\n                    checkbox.value = category;\n                    // Checkbox 勾选状态取决于它是否在 hiddenCategories 中\n                    checkbox.checked = !AppState.hiddenCategories.includes(category);\n                    label.appendChild(checkbox);\n                    const textSpan = document.createElement('span');\n                    textSpan.textContent = category;\n                    label.appendChild(textSpan);\n                    categoryOptionsPopup.appendChild(label);\n                });\n                this.updateCategoryDisplay();\n            }\n            create() {\n                this.panel = document.createElement('div');\n                this.panel.className = 'ss-panel button-panel';\n                this.panel.style.width = `${CONFIG.PANEL.WIDTH}px`;\n                this.panel.style.height = `${CONFIG.PANEL.HEIGHT}px`;\n                if (CONFIG.PANEL.VERTICAL_ANCHOR === 'bottom') {\n                    this.panel.style.bottom = `${CONFIG.PANEL.VERTICAL_OFFSET}px`;\n                    this.panel.style.top = 'auto';\n                } else {\n                    this.panel.style.top = `${CONFIG.PANEL.VERTICAL_OFFSET}px`;\n                    this.panel.style.bottom = 'auto';\n                }\n                if (CONFIG.PANEL.HORIZONTAL_ANCHOR === 'left') {\n                    this.panel.style.left = `${CONFIG.PANEL.HORIZONTAL_OFFSET}px`;\n                    this.panel.style.right = 'auto';\n                } else {\n                    this.panel.style.right = `${CONFIG.PANEL.HORIZONTAL_OFFSET}px`;\n                    this.panel.style.left = 'auto';\n                }\n                this.panel.innerHTML = `\n<div class=\"panel-header\">\n<div class=\"panel-title-group\">\n<span>智能客服助手</span>\n<span id=\"ss-reply-status\" class=\"reply-status-indicator idle\">空闲</span>\n</div>\n<button class=\"panel-btn settings-toggle-btn\" title=\"设置\">${COMMON_ICONS.cog}</button>\n<div class=\"settings-popup\">\n<div class=\"settings-item\">\n<span class=\"settings-label\">使用远程规则</span>\n<label class=\"switch-container\">\n    <input type=\"checkbox\" id=\"remote-rules-toggle\">\n    <span class=\"switch-slider\"></span>\n</label>\n</div>\n<div class=\"settings-item\">\n<span class=\"settings-label\">紧凑模式</span>\n<label class=\"switch-container\">\n    <input type=\"checkbox\" id=\"compact-mode-toggle\">\n    <span class=\"switch-slider\"></span>\n</label>\n</div>\n<div class=\"settings-item send-mode-switch\" title=\"切换发送模式\">\n<span>翻译</span>\n<label class=\"switch-container\">\n    <input type=\"checkbox\" id=\"send-mode-toggle\">\n    <span class=\"switch-slider\"></span>\n</label>\n<span>原文</span>\n</div>\n<div class=\"category-select-wrapper\">\n<label>筛选分类:</label>\n<button id=\"category-select-toggle\" class=\"category-toggle-btn\">\n<span id=\"selected-categories-display\">选择分类...</span>${COMMON_ICONS.chevronDown}\n</button>\n<div id=\"category-options-popup\" class=\"category-options-popup\"></div>\n</div>\n<button class=\"popup-control-btn refresh-btn\">${COMMON_ICONS.sync}刷新规则</button>\n<button class=\"popup-control-btn import-btn\">${COMMON_ICONS.folder}导入本地规则</button>\n<button class=\"popup-control-btn close-btn\">${COMMON_ICONS.times}关闭面板</button>\n</div>\n</div>\n<div class=\"${CONFIG.SELECTORS.BUTTONS_CONTAINER.substring(1)}\"><div class=\"loading\">${COMMON_ICONS.spinner}<span>正在加载规则...</span></div></div>\n<div class=\"resize-handle resize-handle-e\"></div><div class=\"resize-handle resize-handle-s\"></div><div class=\"resize-handle resize-handle-se\"></div>\n<div class=\"size-info\">${CONFIG.PANEL.WIDTH}×${CONFIG.PANEL.HEIGHT}px</div>`;\n                document.body.appendChild(this.panel);\n                this.controlButtons = document.createElement('div');\n                this.controlButtons.className = 'control-buttons';\n                this.controlButtons.innerHTML = `<div class=\"control-btn\" id=\"toggleButtonsPanel\" title=\"显示/隐藏面板\">${COMMON_ICONS.thLarge}</div>`;\n                document.body.appendChild(this.controlButtons);\n                const savedBtnPos = GM_getValue(STORAGE_KEYS.CONTROL_BTN_POS, {\n                    top: window.innerHeight - 80,\n                    left: window.innerWidth - 80\n                });\n                this.controlButtons.style.top = `${savedBtnPos.top}px`;\n                this.controlButtons.style.left = `${savedBtnPos.left}px`;\n                this.fileInput = document.createElement('input');\n                this.fileInput.type = 'file';\n                this.fileInput.accept = '.txt';\n                this.fileInput.className = 'file-input';\n                document.body.appendChild(this.fileInput);\n\n                UIRendererOld.initHoverInfoPopup(); // 初始化悬浮信息提示框\n\n                this.bindEvents();\n                setTimeout(() => {\n                    this.panel.classList.add('show');\n                    this.loadRules();\n                }, USER_CONFIGURABLE_DELAYS.PANEL_INITIAL_SHOW);\n            }\n            bindEvents() {\n                const toggleButtonsPanel = this.controlButtons.querySelector('#toggleButtonsPanel');\n                const settingsToggleBtn = this.panel.querySelector('.settings-toggle-btn');\n                const settingsPopup = this.panel.querySelector('.settings-popup');\n                const remoteRulesToggle = this.panel.querySelector('#remote-rules-toggle');\n                const compactModeToggle = this.panel.querySelector('#compact-mode-toggle'); // 新增\n                const categorySelectToggle = this.panel.querySelector('#category-select-toggle');\n                const categoryOptionsPopup = this.panel.querySelector('#category-options-popup');\n                const sendModeToggle = this.panel.querySelector('#send-mode-toggle');\n                const refreshBtn = this.panel.querySelector('.refresh-btn');\n                const importBtn = this.panel.querySelector('.import-btn');\n                const closeBtn = this.panel.querySelector('.close-btn');\n\n                toggleButtonsPanel?.addEventListener('click', (e) => {\n                    if (this.controlButtons.dataset.dragging === 'true') {\n                        delete this.controlButtons.dataset.dragging;\n                        return;\n                    }\n                    this.panel.classList.toggle('show');\n                });\n                settingsToggleBtn.addEventListener('click', (e) => {\n                    e.stopPropagation();\n                    settingsPopup.classList.toggle('show');\n                    if (!settingsPopup.classList.contains('show')) {\n                        categoryOptionsPopup.classList.remove('show');\n                        categorySelectToggle.classList.remove('active');\n                    }\n                });\n                settingsPopup.addEventListener('click', (e) => e.stopPropagation());\n                closeBtn.addEventListener('click', () => this.panel.classList.remove('show'));\n                refreshBtn.addEventListener('click', () => {\n                    categoryOptionsPopup.classList.remove('show');\n                    categorySelectToggle.classList.remove('active');\n                    this.loadRules(true);\n                });\n                importBtn.addEventListener('click', () => this.fileInput.click());\n                this.fileInput.addEventListener('change', async (e) => {\n                    if (e.target.files.length > 0) await this.handleFileImport(e.target.files[0]);\n                });\n\n                // Send Mode Toggle\n                sendModeToggle.checked = (AppState.currentSendMode === 'original');\n                sendModeToggle.addEventListener('change', function() {\n                    AppState.setSendMode(this.checked ? 'original' : 'translated');\n                    showNotification(`模式: ${this.checked ? '原文' : '翻译'}`);\n                });\n\n                // Remote Rules Toggle\n                remoteRulesToggle.checked = AppState.useRemoteRules;\n                remoteRulesToggle.addEventListener('change', (e) => {\n                    AppState.setRemoteRules(e.target.checked);\n                    showNotification(`已切换至: ${e.target.checked ? '远程' : '本地'}`);\n                    this.loadRules(true);\n                });\n\n                // Compact Mode Toggle (新增)\n                compactModeToggle.checked = AppState.compactMode;\n                compactModeToggle.addEventListener('change', (e) => {\n                    AppState.setCompactMode(e.target.checked);\n                    showNotification(`紧凑模式: ${e.target.checked ? '开启' : '关闭'}`);\n                    UIRendererOld.renderButtonsPanel(this.panel.querySelector(CONFIG.SELECTORS.BUTTONS_CONTAINER)); // 重新渲染按钮面板\n                });\n\n                categorySelectToggle.addEventListener('click', (e) => {\n                    e.stopPropagation();\n                    categoryOptionsPopup.classList.toggle('show');\n                    categorySelectToggle.classList.toggle('active');\n                });\n                categoryOptionsPopup.addEventListener('change', (e) => {\n                    if (e.target.type === 'checkbox' && e.target.name === 'category') {\n                        const categoryName = e.target.value;\n                        const isChecked = e.target.checked;\n\n                        AppState.toggleCategorySelection(categoryName, isChecked);\n\n                        const container = this.panel.querySelector(CONFIG.SELECTORS.BUTTONS_CONTAINER);\n                        UIRendererOld.renderButtonsPanel(container);\n                        this.updateCategoryDisplay();\n                    }\n                });\n                this.setupPanelDrag();\n                this.setupPanelResize();\n                this.setupFloatingButtonDrag();\n                setupDragAndDropOld(this.panel.querySelector(CONFIG.SELECTORS.BUTTONS_CONTAINER));\n            }\n            updateCategoryDisplay() {\n                const selectedCategoriesDisplay = this.panel.querySelector('#selected-categories-display');\n                if (selectedCategoriesDisplay) {\n                    const visibleCategories = AppState.selectedCategoriesOrder.filter(cat => !AppState.hiddenCategories.includes(cat));\n                    const allAvailableButNotHidden = AppState.getAllCategoriesCombined().filter(cat => !AppState.hiddenCategories.includes(cat));\n\n                    if (visibleCategories.length === 0) {\n                        selectedCategoriesDisplay.textContent = '选择分类...';\n                    } else if (visibleCategories.length === allAvailableButNotHidden.length && allAvailableButNotHidden.every(cat => visibleCategories.includes(cat))) {\n                        selectedCategoriesDisplay.textContent = '所有分类';\n                    } else {\n                        selectedCategoriesDisplay.textContent = visibleCategories.join(', ');\n                    }\n                }\n            }\n            async handleFileImport(file) {\n                const container = this.panel.querySelector(CONFIG.SELECTORS.BUTTONS_CONTAINER);\n                container.innerHTML = `<div class=\"loading\">${COMMON_ICONS.spinner}<span>正在加载规则文件...</span></div>`;\n                try {\n                    const result = await RuleLoaderOld.loadFile(file);\n                    AppState.allParsedRules = result.rules;\n                    AppState.allAvailableCategories = result.categories;\n                    // 导入文件后，重置隐藏分类，并默认显示所有新导入的分类\n                    AppState.hiddenCategories = [];\n                    // 传入所有可用分类作为初始可见顺序，并清空隐藏列表\n                    AppState.initializeSelectedCategories(AppState.getAllCategoriesCombined(), []);\n                    this.populateCategorySelect(AppState.getAllCategoriesCombined());\n                    UIRendererOld.renderButtonsPanel(container);\n                    showNotification(`成功导入 ${result.rules.length} 条规则`);\n                    const plainTextRules = RuleParser.serializeRulesToPlainText(result.rules);\n                    GM_setValue(STORAGE_KEYS.LOCAL_RULES, plainTextRules);\n                    if (AppState.useRemoteRules) {\n                        showNotification(`提示：当前为远程模式，已导入但未应用。请关闭“使用远程规则”开关以使用。`, 'success');\n                    }\n                } catch (error) {\n                    LoggerOld.error('导入规则文件失败', error.message);\n                    container.innerHTML = `<div class=\"loading\">${COMMON_ICONS.exclamationCircle}<span>规则解析错误: ${error.message}</span></div>`;\n                }\n                this.fileInput.value = '';\n            }\n            setupPanelDrag() {\n                const header = this.panel.querySelector('.panel-header');\n                let dragging = false,\n                    x0 = 0,\n                    y0 = 0,\n                    p_x = 0,\n                    p_y = 0;\n                header?.addEventListener('mousedown', (e) => {\n                    if (e.target.closest('.panel-btn, .settings-popup')) return; // Allow clicking buttons inside header\n                    dragging = true;\n                    x0 = e.clientX;\n                    y0 = e.clientY;\n                    const rect = this.panel.getBoundingClientRect();\n                    p_x = rect.left;\n                    p_y = rect.top;\n                    this.panel.style.transition = 'none';\n                    e.preventDefault();\n                });\n                document.addEventListener('mouseup', () => {\n                    if (dragging) {\n                        dragging = false;\n                        this.panel.style.transition = '';\n                        this.hideSizeInfoAfterDelay();\n                    }\n                });\n                document.addEventListener('mousemove', (e) => {\n                    if (!dragging) return;\n                    this.showSizeInfo();\n                    this.panel.style.left = `${p_x + e.clientX - x0}px`;\n                    this.panel.style.top = `${p_y + e.clientY - y0}px`;\n                    this.panel.style.right = 'auto';\n                    this.panel.style.bottom = 'auto';\n                });\n            }\n            setupPanelResize() {\n                const handles = this.panel.querySelectorAll('.resize-handle');\n                let resizing = false,\n                    w0, h0, x0, y0, dir;\n                const start = (e, d) => {\n                    resizing = true;\n                    w0 = this.panel.offsetWidth;\n                    h0 = this.panel.offsetHeight;\n                    x0 = e.clientX;\n                    y0 = e.clientY;\n                    dir = d;\n                    document.body.style.cursor = `${d.includes('s') ? 's' : ''}${d.includes('e') ? 'e' : ''}-resize`;\n                    this.panel.style.transition = 'none';\n                    e.preventDefault();\n                };\n                handles.forEach(h => {\n                    if (h.classList.contains('resize-handle-e')) h.addEventListener('mousedown', e => start(e, 'e'));\n                    if (h.classList.contains('resize-handle-s')) h.addEventListener('mousedown', e => start(e, 's'));\n                    if (h.classList.contains('resize-handle-se')) h.addEventListener('mousedown', e => start(e, 'se'));\n                });\n                document.addEventListener('mouseup', () => {\n                    if (resizing) {\n                        resizing = false;\n                        document.body.style.cursor = '';\n                        this.panel.style.transition = '';\n                        this.hideSizeInfoAfterDelay();\n                    }\n                });\n                document.addEventListener('mousemove', (e) => {\n                    if (!resizing) return;\n                    this.showSizeInfo();\n                    let nw = w0,\n                        nh = h0;\n                    if (dir.includes('e')) nw = w0 + e.clientX - x0;\n                    if (dir.includes('s')) nh = h0 + e.clientY - y0;\n                    // Removed Math.max for min-width/min-height\n                    this.panel.style.width = nw + 'px';\n                    this.panel.style.height = nh + 'px';\n                    const si = this.panel.querySelector('.size-info');\n                    if (si) si.textContent = `${Math.round(this.panel.offsetWidth)}×${Math.round(this.panel.offsetHeight)}px`;\n                });\n            }\n            setupFloatingButtonDrag() {\n                let isDragging = false;\n                let offsetX, offsetY;\n                this.controlButtons.addEventListener('mousedown', (e) => {\n                    if (e.target.closest('.control-btn') !== this.controlButtons.querySelector('.control-btn')) return;\n                    isDragging = true;\n                    const rect = this.controlButtons.getBoundingClientRect();\n                    offsetX = e.clientX - rect.left;\n                    offsetY = e.clientY - rect.top;\n                    this.controlButtons.style.cursor = 'grabbing';\n                    document.body.style.userSelect = 'none';\n                    e.preventDefault();\n                });\n                document.addEventListener('mousemove', (e) => {\n                    if (!isDragging) return;\n                    this.controlButtons.dataset.dragging = 'true';\n                    let x = e.clientX - offsetX;\n                    let y = e.clientY - offsetY;\n                    x = Math.max(0, Math.min(x, window.innerWidth - this.controlButtons.offsetWidth));\n                    y = Math.max(0, Math.min(y, window.innerHeight - this.controlButtons.offsetHeight));\n                    this.controlButtons.style.left = `${x}px`;\n                    this.controlButtons.style.top = `${y}px`;\n                    this.controlButtons.style.bottom = 'auto';\n                    this.controlButtons.style.right = 'auto';\n                });\n                document.addEventListener('mouseup', () => {\n                    if (isDragging) {\n                        isDragging = false;\n                        this.controlButtons.style.cursor = 'grab';\n                        document.body.style.userSelect = '';\n                        GM_setValue(STORAGE_KEYS.CONTROL_BTN_POS, {\n                            top: parseFloat(this.controlButtons.style.top),\n                            left: parseFloat(this.controlButtons.style.left)\n                        });\n                        setTimeout(() => delete this.controlButtons.dataset.dragging, 50);\n                    }\n                });\n            }\n            showSizeInfo() {\n                const sizeInfo = this.panel.querySelector('.size-info');\n                if (sizeInfo) {\n                    if (AppState.sizeInfoTimeout) clearTimeout(AppState.sizeInfoTimeout);\n                    sizeInfo.classList.add('show');\n                }\n            }\n            hideSizeInfoAfterDelay() {\n                const sizeInfo = this.panel.querySelector('.size-info');\n                if (sizeInfo) {\n                    if (AppState.sizeInfoTimeout) clearTimeout(AppState.sizeInfoTimeout);\n                    AppState.sizeInfoTimeout = setTimeout(() => sizeInfo.classList.remove('show'), 2000);\n                }\n            }\n        }\n\n        // ==================== 初始化 (旧版) ====================\n        function initializeOldVersion() {\n            LoggerOld.info('⚡ 初始化智能客服助手 (旧版) v13.0');\n            try {\n                injectStylesOld();\n                startHighlightMonitoringOld();\n                panelManagerOld = new PanelManagerOld();\n                const obs = new MutationObserver(() => {\n                    const chatContainer = document.querySelector('.ss-chat-container, .chat-window, .conversation-panel');\n                    if (chatContainer && !document.querySelector(CONFIG.SELECTORS.BUTTONS_PANEL)) {\n                        obs.disconnect();\n                        panelManagerOld.create();\n                        RuleEditorOld.init();\n                    }\n                });\n                obs.observe(document.body, {\n                    childList: true,\n                    subtree: true\n                });\n                setTimeout(() => {\n                    if (!document.querySelector(CONFIG.SELECTORS.BUTTONS_PANEL)) {\n                        LoggerOld.warn('MutationObserver 未在预期时间内触发，尝试直接创建面板');\n                        panelManagerOld.create();\n                        RuleEditorOld.init();\n                    }\n                }, USER_CONFIGURABLE_DELAYS.PANEL_INIT_OBSERVER_TIMEOUT);\n            } catch (e) {\n                LoggerOld.error('旧版初始化失败', e.message);\n                showNotification('旧版初始化失败: ' + e.message, 'error');\n            }\n        }\n\n        if (document.readyState === 'complete') {\n            initializeOldVersion();\n        } else {\n            window.addEventListener('load', initializeOldVersion);\n        }\n    }\n})();\n//现在这个代码存在一个小问题，由于新版本网页中发送图片的按键更新，导致点击发送图片按键时候找不到提示（发送图片失败: 超时: 未找到元素: .arco-table-body），正确的发送按键为图片列表内的（<td class=\"vxe-table--column vxe-body--column col_9 col--last fixed--width is--padding\" colid=\"col_9\"><div class=\"vxe-cell\" style=\"min-height: 48px;\"><div colid=\"col_9\" rowid=\"112728\" class=\"vxe-cell--wrapper vxe-body-cell--wrapper\"><div data-v-1308be2d=\"\" data-v-3469a42b=\"\" class=\"operate operate--half\"><div data-v-1308be2d=\"\" class=\"operate__button-wrap\"><a data-v-b4b4f41f=\"\" href=\"javascript:;\" class=\"operate-button g-link-btn\" type=\"text\">图文发送</a></div><div data-v-1308be2d=\"\" class=\"operate__button-wrap\"><a data-v-b4b4f41f=\"\" href=\"javascript:;\" class=\"operate-button g-link-btn\" type=\"text\">发送</a></div><!----><!----></div></div></div></td>）中的（<div data-v-1308be2d=\"\" class=\"operate__button-wrap\"><a data-v-b4b4f41f=\"\" href=\"javascript:;\" class=\"operate-button g-link-btn\" type=\"text\">发送</a></div>）\n","functional_script_code_hash":"bafd2640a355d5e506b2a1c9f405861c7826a8715137cf939cddbb769aa1e7fd","loader_script_hash":"f51de78142d5c1cd2af0f388a5f0fca175f1b950aae3151280fe2ebc1ca96d02","config":{"feature_x_enabled":true,"keyword_list":["example","test"],"salesmartly_api_key":"YOUR_SALESMARTLY_API_KEY_IF_NEEDED"}}