智能标注-控制台脚本版(完结)

F12打开开发者模式,上方栏目选择控制台,输入需要进行的脚本。

注意备份!!!

返修助手(被制裁)

AI配置教程在同栏目中底部商品页面,请自行获取

质检相关
3.0
// ==UserScript==
// @name         极速自动领取·佑(可收缩小球+行选择)
// @namespace    https://github.com/yournamespace
// @version      6.0
// @description  紧凑卡片,运行时收缩为动画小球,自定义领取行,后台极速,防伪通知
// @author       佑
// @match        https://labelx.alibaba-inc.com/corpora/labeling/labelingTask?projectId=1023
// @match        https://labelx.alibaba-inc.com/corpora/labeling/checkTask?projectId=1023
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // 网址校验
    const TARGET_PATTERN = /^https:\/\/labelx\.alibaba-inc\.com\/corpora\/labeling\/(labelingTask|checkTask)\?projectId=1023/;
    if (!TARGET_PATTERN.test(location.href)) return;

    // ========== 配置 ==========
    const CLICK_INTERVAL_MS = 80;
    const SUCCESS_KEYWORDS = ['领取成功', '成功领取', '已领取'];
    const EXCLUDE_KEYWORDS = ['分人员', '多人', '批量', '团队'];
    // ==========================

    // 隐蔽防伪数据
    const _0x4f = [0x4f51];
    const _0x62 = ['NWwyYzZJQ0Y3N3lhNUx5Wg=='];
    const AUTHOR = String.fromCharCode(_0x4f[0]);
    const ENCRYPTED_AUTHOR = atob(_0x62[0]);

    const QUOTES = [
        '✨ 此即,智慧之殿堂!—— 纳西妲',
        '🍖 牛福永恒,吃饱喝好!',
        '⚡ 稻光,亦是永恒!—— 雷电将军',
        '🎲 愿风神忽悠你!—— 温迪',
        '🌊 牛福如水,福泽绵长。',
        '🔥 天动万象!—— 钟离',
        '🍜 牛福面馆,今日特供。',
        '🌙 月光点亮的时候,牛福会保佑你。'
    ];

    // 状态变量
    let successCount = 0;
    let consecutiveSuccess = 0;
    let attemptCount = 0;
    let isRunning = false;
    let worker = null;
    let fallbackInterval = null;
    let observer = null;
    let processedMessages = new WeakSet();
    let selectedRow = 1;                // 默认领取第1行

    // UI 元素
    let mainCard = null;
    let ballView = null;               // 收缩后的小球
    let successDisplay = null;
    let comboDisplay = null;
    let attemptDisplay = null;
    let statusDot = null;
    let statusText = null;
    let rowSelect = null;

    // ---------- 智能行识别 ----------
    function getClaimButtonsByRow() {
        const selectors = [
            'button', '[role="button"]', '.ant-v5-btn', '.ant-btn', '.el-button',
            '[class*="领取"]', '[class*="claim"]'
        ];
        const all = document.querySelectorAll(selectors.join(','));
        const validButtons = [];
        const seen = new Set();

        for (const el of all) {
            if (seen.has(el)) continue;
            seen.add(el);

            const rect = el.getBoundingClientRect();
            if (rect.width === 0 || rect.height === 0) continue;
            if (el.disabled === true) continue;
            if (el.classList.contains('disabled')) continue;
            if (getComputedStyle(el).pointerEvents === 'none') continue;

            const text = (el.innerText || el.textContent || '').trim();
            if (!text.includes('领取')) continue;
            if (EXCLUDE_KEYWORDS.some(kw => text.includes(kw))) continue;

            validButtons.push({ el, text, y: rect.y });
        }

        if (validButtons.length === 0) return [];

        // 按 Y 坐标排序
        validButtons.sort((a, b) => a.y - b.y);

        // 分行(容差 10px)
        const rows = [];
        let currentRow = [];
        let lastY = validButtons[0].y;

        for (const btn of validButtons) {
            if (Math.abs(btn.y - lastY) <= 10) {
                currentRow.push(btn);
            } else {
                if (currentRow.length) rows.push(currentRow);
                currentRow = [btn];
                lastY = btn.y;
            }
        }
        if (currentRow.length) rows.push(currentRow);

        return rows;
    }

    function clickReceive() {
        const rows = getClaimButtonsByRow();
        if (rows.length === 0) return;

        // 根据选择的行号获取按钮(索引从0开始)
        const rowIndex = Math.min(selectedRow - 1, rows.length - 1);
        const targetRow = rows[rowIndex];
        if (!targetRow || targetRow.length === 0) return;

        // 取该行第一个按钮
        const btn = targetRow[0].el;
        try {
            btn.click();
            attemptCount++;
            if (attemptDisplay) attemptDisplay.textContent = attemptCount;
        } catch (e) {}
    }

    // ---------- 轻量成功通知 ----------
    function showLightNotification() {
        const old = document.getElementById('light-claim-notify');
        if (old) old.remove();

        const quote = QUOTES[Math.floor(Math.random() * QUOTES.length)];

        const notify = document.createElement('div');
        notify.id = 'light-claim-notify';
        notify.style.cssText = `
            position: fixed; top: 20px; right: 20px; z-index: 2147483647;
            background: #1e2436; color: #f0e6d2;
            border-left: 5px solid #d4af37;
            border-radius: 10px; padding: 10px 14px;
            font-family: system-ui, sans-serif; font-size: 13px;
            box-shadow: 0 8px 20px rgba(0,0,0,0.5);
            animation: slideDown 0.3s ease-out;
            pointer-events: none; max-width: 260px;
        `;
        notify.innerHTML = `
            <div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
                <span>👤 ${AUTHOR}</span>
                <span style="font-size:11px; opacity:0.7; margin-left:auto;">防伪</span>
            </div>
            <div style="font-family:monospace; font-size:11px; opacity:0.9; word-break:break-all; margin-bottom:6px;">
                🔐 ${ENCRYPTED_AUTHOR}
            </div>
            <div style="font-style:italic; color:#ffd966;">
                “${quote}”
            </div>
        `;

        if (!document.querySelector('#notify-style')) {
            const style = document.createElement('style');
            style.id = 'notify-style';
            style.textContent = `@keyframes slideDown{from{opacity:0;transform:translateY(-30px);}to{opacity:1;transform:translateY(0);}}`;
            document.head.appendChild(style);
        }

        document.body.appendChild(notify);
        setTimeout(() => { if (notify.parentNode) notify.remove(); }, 5000);
    }

    // ---------- 成功监听 ----------
    function setupObserver() {
        const obs = new MutationObserver((mutations) => {
            for (const m of mutations) {
                for (const node of m.addedNodes) {
                    if (node.nodeType !== Node.ELEMENT_NODE) continue;
                    const msg = node.classList?.contains('ant-message-notice')
                        ? node
                        : node.querySelector?.('.ant-message-notice');
                    if (msg && !processedMessages.has(msg)) {
                        processedMessages.add(msg);
                        const txt = msg.innerText || '';
                        if (SUCCESS_KEYWORDS.some(k => txt.includes(k))) {
                            successCount++;
                            consecutiveSuccess++;
                            updateStats();
                            showLightNotification();
                            console.log(`✅ 成功 ${successCount} 连击 ${consecutiveSuccess}`);
                        }
                    }
                }
            }
        });
        obs.observe(document.body, { childList: true, subtree: true });
        return obs;
    }

    // ---------- 降级定时器 ----------
    function startFallback() {
        if (fallbackInterval) clearInterval(fallbackInterval);
        fallbackInterval = setInterval(() => {
            try { clickReceive(); } catch(e) {}
        }, CLICK_INTERVAL_MS);
        console.log('⚠️ 使用降级 setInterval 模式');
    }

    function stopFallback() {
        if (fallbackInterval) {
            clearInterval(fallbackInterval);
            fallbackInterval = null;
        }
    }

    // ---------- Worker 驱动 ----------
    function startWorker() {
        const workerCode = `
            let intervalId = null;
            self.onmessage = function(e) {
                if (e.data === 'start') {
                    if (intervalId !== null) return;
                    intervalId = setInterval(() => postMessage('click'), ${CLICK_INTERVAL_MS});
                } else if (e.data === 'stop') {
                    if (intervalId !== null) {
                        clearInterval(intervalId);
                        intervalId = null;
                    }
                }
            };
        `;
        const blob = new Blob([workerCode], { type: 'application/javascript' });
        worker = new Worker(URL.createObjectURL(blob));
        worker.onmessage = (e) => {
            if (e.data === 'click') {
                try { clickReceive(); } catch(e) {}
            }
        };
        worker.onerror = (err) => {
            console.error('❌ Worker 错误,切换降级', err);
            stopWorker();
            startFallback();
        };
        worker.postMessage('start');
        console.log('🚀 Worker 模式启动');
    }

    function stopWorker() {
        if (worker) {
            worker.postMessage('stop');
            worker.terminate();
            worker = null;
        }
    }

    // ---------- 启动/停止 ----------
    function start() {
        if (isRunning) return;
        if (!observer) observer = setupObserver();

        isRunning = true;
        consecutiveSuccess = 0;
        attemptCount = 0;
        updateStats();
        setCardCollapsed(true);  // 收缩为小球

        try {
            startWorker();
        } catch (e) {
            console.warn('⚠️ Worker 创建失败,使用降级', e);
            startFallback();
        }

        console.log(`▶️ 启动 | 行 ${selectedRow} | 间隔 ${CLICK_INTERVAL_MS}ms`);
    }

    function stop() {
        if (!isRunning) return;

        stopWorker();
        stopFallback();

        isRunning = false;
        setCardCollapsed(false); // 展开卡片
        console.log(`⏹️ 停止 | 总成功 ${successCount} | 尝试 ${attemptCount}`);
    }

    function toggle() {
        isRunning ? stop() : start();
    }

    // 小球点击时也切换停止/启动
    function ballClickHandler() {
        stop(); // 停止并展开
    }

    // 卡片展开/收缩切换
    function setCardCollapsed(collapsed) {
        if (!mainCard || !ballView) return;

        if (collapsed) {
            mainCard.style.display = 'none';
            ballView.style.display = 'flex';
            updateBallStats();
        } else {
            mainCard.style.display = 'block';
            ballView.style.display = 'none';
            updateStats();
        }
    }

    function updateStats() {
        if (successDisplay) successDisplay.textContent = successCount;
        if (comboDisplay) comboDisplay.textContent = consecutiveSuccess;
        if (attemptDisplay) attemptDisplay.textContent = attemptCount;
        // 同时更新小球内数字
        updateBallStats();
    }

    function updateBallStats() {
        if (!ballView) return;
        const ballSuccess = ballView.querySelector('#ball-success');
        const ballCombo = ballView.querySelector('#ball-combo');
        if (ballSuccess) ballSuccess.textContent = successCount;
        if (ballCombo) ballCombo.textContent = consecutiveSuccess;
    }

    // ---------- 创建 UI:紧凑卡片 + 小球 ----------
    function createUI() {
        // 主卡片(紧凑版)
        const card = document.createElement('div');
        card.id = 'claim-master-card';
        card.style.cssText = `
            position: fixed; bottom: 20px; right: 20px; z-index: 2147483646;
            width: 250px; background: #1a2332; color: #f0e6d2;
            border-radius: 20px; padding: 16px 14px;
            font-family: system-ui, 'Microsoft YaHei', sans-serif;
            box-shadow: 0 10px 25px rgba(0,0,0,0.6), 0 0 0 2px #d4af37;
            cursor: pointer; user-select: none;
            transition: all 0.25s;
            border: 1px solid rgba(212,175,55,0.5);
        `;

        card.innerHTML = `
            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px;">
                <div style="background: #d4af37; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #1a1e2a; font-weight: bold; font-size: 18px;">👤</div>
                <div style="flex:1;">
                    <div style="font-weight:700; font-size:15px;">佑·牛福</div>
                    <div style="font-size:10px; opacity:0.7; display:flex; align-items:center; gap:4px;">
                        <span>防伪</span>
                        <span style="background:#2a2f42; padding:2px 6px; border-radius:10px; font-family:monospace;">🔐 ${ENCRYPTED_AUTHOR.slice(0,8)}…</span>
                    </div>
                </div>
            </div>

            <!-- 行选择下拉 -->
            <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; background: rgba(0,0,0,0.2); border-radius: 12px; padding: 6px 10px;">
                <span style="font-size:12px; opacity:0.8;">领取行</span>
                <select id="row-selector" style="background: #2a2f42; color: #f0e6d2; border: 1px solid #d4af37; border-radius: 8px; padding: 4px 8px; font-size: 13px; cursor: pointer;">
                    <option value="1" selected>第 1 行</option>
                    <option value="2">第 2 行</option>
                    <option value="3">第 3 行</option>
                </select>
            </div>

            <!-- 统计区域 -->
            <div style="display:flex; justify-content:space-around; background:rgba(0,0,0,0.2); border-radius:14px; padding:10px 4px; margin-bottom:12px;">
                <div style="text-align:center;">
                    <div style="font-size:11px; opacity:0.7;">成功</div>
                    <div id="master-success-count" style="font-size:24px; font-weight:bold; color:#ffd966;">0</div>
                </div>
                <div style="width:1px; background:rgba(255,255,255,0.15);"></div>
                <div style="text-align:center;">
                    <div style="font-size:11px; opacity:0.7;">连击</div>
                    <div id="master-combo-count" style="font-size:24px; font-weight:bold; color:#ff6ec7;">0</div>
                </div>
                <div style="width:1px; background:rgba(255,255,255,0.15);"></div>
                <div style="text-align:center;">
                    <div style="font-size:11px; opacity:0.7;">尝试</div>
                    <div id="master-attempt-count" style="font-size:24px; font-weight:bold; color:#4ecdc4;">0</div>
                </div>
            </div>

            <!-- 状态栏 -->
            <div style="display:flex; align-items:center; justify-content:space-between;">
                <div style="display:flex; align-items:center; gap:5px;">
                    <span id="master-status-dot" style="width:10px; height:10px; border-radius:50%; background:#95a5a6;"></span>
                    <span id="master-status-text" style="font-size:13px;">待命中</span>
                </div>
                <div style="font-size:18px;">⚡</div>
            </div>
            <div style="margin-top:10px; font-size:10px; text-align:center; opacity:0.5;">
                点击卡片开始/停止
            </div>
        `;

        // 悬浮小球(运行时显示)
        const ball = document.createElement('div');
        ball.id = 'claim-floating-ball';
        ball.style.cssText = `
            position: fixed; bottom: 20px; right: 20px; z-index: 2147483647;
            width: 60px; height: 60px; background: #1a2332;
            border-radius: 50%; display: none;
            align-items: center; justify-content: center;
            box-shadow: 0 8px 20px rgba(0,0,0,0.6), 0 0 0 2px #d4af37;
            cursor: pointer; user-select: none;
            animation: pulseGlow 1.8s infinite ease-in-out;
            border: 1px solid rgba(212,175,55,0.6);
            color: #f0e6d2;
        `;
        ball.innerHTML = `
            <div style="text-align: center;">
                <div style="font-size: 18px; font-weight: bold; color: #ffd966;" id="ball-success">0</div>
                <div style="font-size: 10px; opacity: 0.7;">连击 <span id="ball-combo">0</span></div>
                <div style="font-size: 14px; margin-top: 2px;">⚡</div>
            </div>
        `;

        // 动画样式
        const style = document.createElement('style');
        style.textContent = `
            @keyframes pulseGlow {
                0% { box-shadow: 0 8px 20px rgba(0,0,0,0.6), 0 0 0 2px #d4af37; transform: scale(1); }
                50% { box-shadow: 0 8px 30px rgba(212,175,55,0.6), 0 0 0 4px rgba(212,175,55,0.3); transform: scale(1.05); }
                100% { box-shadow: 0 8px 20px rgba(0,0,0,0.6), 0 0 0 2px #d4af37; transform: scale(1); }
            }
        `;
        document.head.appendChild(style);

        document.body.appendChild(card);
        document.body.appendChild(ball);

        // 保存引用
        mainCard = card;
        ballView = ball;
        successDisplay = card.querySelector('#master-success-count');
        comboDisplay = card.querySelector('#master-combo-count');
        attemptDisplay = card.querySelector('#master-attempt-count');
        statusDot = card.querySelector('#master-status-dot');
        statusText = card.querySelector('#master-status-text');
        rowSelect = card.querySelector('#row-selector');

        // 事件绑定
        card.addEventListener('click', (e) => {
            // 如果点击的是下拉框,不触发卡片切换
            if (e.target.tagName === 'SELECT' || e.target.tagName === 'OPTION') return;
            toggle();
        });

        ball.addEventListener('click', ballClickHandler);

        rowSelect.addEventListener('change', (e) => {
            selectedRow = parseInt(e.target.value, 10);
            console.log(`📌 切换领取行:第 ${selectedRow} 行`);
        });
        rowSelect.addEventListener('click', (e) => e.stopPropagation()); // 防止触发展开

        // 悬停效果(卡片)
        card.addEventListener('mouseenter', () => {
            if (!isRunning) {
                card.style.transform = 'scale(1.02)';
                card.style.boxShadow = '0 14px 30px rgba(0,0,0,0.7), 0 0 0 2px #d4af37';
            }
        });
        card.addEventListener('mouseleave', () => {
            card.style.transform = 'scale(1)';
            card.style.boxShadow = '0 10px 25px rgba(0,0,0,0.6), 0 0 0 2px #d4af37';
        });
    }

    // 更新卡片运行状态样式(小球已处理,这里只更新卡片内文字)
    function updateCardState(running) {
        if (!statusDot || !statusText) return;
        if (running) {
            statusText.textContent = '抢领中';
            statusDot.style.background = '#2ecc71';
            statusDot.style.boxShadow = '0 0 10px #2ecc71';
        } else {
            statusText.textContent = '待命中';
            statusDot.style.background = '#95a5a6';
            statusDot.style.boxShadow = 'none';
        }
    }

    // 覆盖 setCardCollapsed,同时更新状态文字
    const originalSetCollapsed = setCardCollapsed;
    setCardCollapsed = function(collapsed) {
        originalSetCollapsed(collapsed);
        updateCardState(isRunning);
    };

    // ---------- 初始化 ----------
    function init() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                createUI();
                console.log(`✅ 脚本已加载 | 当前页:${location.pathname.includes('checkTask') ? '质检' : '标注'}`);
            });
        } else {
            createUI();
            console.log(`✅ 脚本已加载 | 当前页:${location.pathname.includes('checkTask') ? '质检' : '标注'}`);
        }
    }

    init();

    // 暴露控制接口
    window.quickClaim = { start, stop, toggle };
})();

借入篡改猴,悬新增浮框,后台自动

(function() {
    const CLICK_INTERVAL_MS = 80;
    const SUCCESS_KEYWORDS = ['领取成功', '成功领取', '已领取'];
    
    const _0x4f = [0x4f51];
    const _0x62 = ['NWwyYzZJQ0Y3N3lhNUx5Wg=='];
    const AUTHOR = String.fromCharCode(_0x4f[0]);
    const ENCRYPTED_AUTHOR = atob(_0x62[0]);
    
    const QUOTES = [
        '✨ 此即,智慧之殿堂!—— 纳西妲',
        '🍖 牛福永恒,吃饱喝好!',
        '⚡ 稻光,亦是永恒!—— 雷电将军',
        '🎲 愿风神忽悠你!—— 温迪',
        '🌊 牛福如水,福泽绵长。',
        '🔥 天动万象!—— 钟离',
        '🍜 牛福面馆,今日特供。',
        '🌙 月光点亮的时候,牛福会保佑你。'
    ];

    let successCount = 0;
    let intervalId = null;
    let isRunning = false;
    let processedMessages = new WeakSet();
    let lastModalTime = 0;
    const MODAL_COOLDOWN_MS = 2000;

    function showAntiFakeModal() {
        const now = Date.now();
        if (now - lastModalTime < MODAL_COOLDOWN_MS) return;
        lastModalTime = now;

        const oldModal = document.getElementById('anti-fake-modal');
        if (oldModal) oldModal.remove();

        const randomQuote = QUOTES[Math.floor(Math.random() * QUOTES.length)];

        const modal = document.createElement('div');
        modal.id = 'anti-fake-modal';
        modal.style.cssText = `
            position: fixed; bottom: 90px; right: 20px; z-index: 1000000;
            background: rgba(20, 25, 40, 0.9); color: #f0e6d2;
            border: 1px solid #c9aa7b; border-radius: 16px;
            padding: 16px 20px; font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
            box-shadow: 0 8px 24px rgba(0,0,0,0.5); backdrop-filter: blur(6px);
            min-width: 260px; pointer-events: none;
            animation: fadeSlideIn 0.3s ease-out;
        `;
        modal.innerHTML = `
            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
                <span style="font-size: 22px;">👤</span>
                <span style="font-weight: bold; letter-spacing: 1px;">作者:${AUTHOR}</span>
                <span style="margin-left: auto; font-size: 12px; opacity: 0.7;">防伪认证</span>
            </div>
            <div style="background: #2a2f42; border-radius: 8px; padding: 8px 12px; margin-bottom: 12px; font-family: monospace; font-size: 13px; border-left: 4px solid #d4af37;">
                🔐 ${ENCRYPTED_AUTHOR}
            </div>
            <div style="font-style: italic; color: #ffd966; text-align: right; border-top: 1px dashed #5a5f72; padding-top: 10px;">
                “${randomQuote}”
            </div>
            <div style="margin-top: 6px; font-size: 12px; text-align: right; opacity: 0.6;">
                🎉 累计成功 ${successCount} 次
            </div>
        `;

        const style = document.createElement('style');
        style.textContent = `
            @keyframes fadeSlideIn {
                from { opacity: 0; transform: translateY(20px); }
                to { opacity: 1; transform: translateY(0); }
            }
        `;
        if (!document.querySelector('#anti-fake-style')) {
            style.id = 'anti-fake-style';
            document.head.appendChild(style);
        }

        document.body.appendChild(modal);
        setTimeout(() => { if (modal.parentNode) modal.remove(); }, 6000);
    }

    function clickReceive() {
        const btns = document.querySelectorAll('button');
        for (let btn of btns) {
            const text = (btn.innerText || btn.textContent || '').trim();
            if (text === '领取' || text.includes('领取')) {
                btn.click();
                return;
            }
        }
    }

    function setupObserver() {
        const observer = new MutationObserver((mutations) => {
            for (const m of mutations) {
                for (const node of m.addedNodes) {
                    if (node.nodeType !== 1) continue;
                    const msg = node.classList?.contains('ant-message-notice')
                        ? node
                        : node.querySelector?.('.ant-message-notice');
                    if (msg && !processedMessages.has(msg)) {
                        processedMessages.add(msg);
                        const txt = msg.innerText || '';
                        if (SUCCESS_KEYWORDS.some(k => txt.includes(k))) {
                            successCount++;
                            console.log(`✅ 领取成功!累计成功: ${successCount} 次`);
                            showAntiFakeModal();
                        }
                    }
                }
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
        return observer;
    }

    let observer = null;

    function start() {
        if (intervalId) return;
        if (!observer) observer = setupObserver();
        isRunning = true;
        intervalId = setInterval(() => {
            try { clickReceive(); } catch(e) {}
        }, CLICK_INTERVAL_MS);
        console.log(`🚀 极速自动领取已启动,间隔 ${CLICK_INTERVAL_MS} ms`);
        updateButton(true);
    }

    function stop() {
        if (intervalId) {
            clearInterval(intervalId);
            intervalId = null;
        }
        isRunning = false;
        console.log(`🛑 已停止,累计成功领取 ${successCount} 次`);
        updateButton(false);
    }

    function toggle() { isRunning ? stop() : start(); }

    const btn = document.createElement('div');
    btn.id = 'quick-claim';
    btn.innerText = '▶️ 开始抢领';
    btn.style.cssText = `
        position: fixed; bottom: 20px; right: 20px; z-index: 999999;
        padding: 10px 16px; border-radius: 30px; background: #2ecc71;
        color: #fff; font-size: 14px; font-weight: bold; text-align: center;
        cursor: pointer; box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        font-family: sans-serif; border: none; user-select: none;
    `;
    btn.onclick = toggle;
    document.body.appendChild(btn);

    function updateButton(running) {
        btn.innerText = running ? '⏹️ 停止抢领' : '▶️ 开始抢领';
        btn.style.backgroundColor = running ? '#e74c3c' : '#2ecc71';
    }

    window.quickClaim = { start, stop, toggle };
    console.log('✅ 极速抢领脚本已加载 (80ms/次)');
})();

新增按钮,随启随用。

新增彩蛋。优化逻辑

(function() {
    let intervalId = setInterval(function() {
        let receiveButtons = Array.from(document.querySelectorAll('button, a, [role="button"]')).filter(btn => btn.innerText.includes('领取') && !btn.disabled);
        if (receiveButtons.length) {
            receiveButtons.forEach(btn => {
                console.log('点击领取按钮:', btn);
                btn.click();
            });
        } else {
            console.log('未找到可领取的按钮');
        }
    }, 3000);
    // 可以提供一个停止函数
    window.stopAutoClick = function() { clearInterval(intervalId); console.log('停止自动点击'); };
    console.log('已启动自动点击领取,每3秒一次,调用 window.stopAutoClick() 可停止');
})();

最初版本,最稳定

标注相关
6.0
// ==UserScript==
// @name         LabelX 智能标注助手
// @namespace    http://tampermonkey.net/
// @version      55.0
// @description  优化进度更新,支持同步后端设置
// @author       定制版
// @match        *://labelx.alibaba-inc.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @connect      111.228.5.19
// ==/UserScript==

(async function() {
    'use strict';

    // ---------- 配置 ----------
    const API = 'http://111.228.5.19:7792/api.php'; // 请修改为实际地址

    const STORAGE_TOKEN = 'anno_token';
    const STORAGE_USER = 'anno_username';
    const STORAGE_RULES = 'labelx_custom_rules';
    const STORAGE_SETTINGS = 'labelx_settings';

    // ---------- 全局状态 ----------
    let backendOk = false, loggedIn = false, user = null, isAdmin = false;
    let token = localStorage.getItem(STORAGE_TOKEN) || '';
    let savedUser = localStorage.getItem(STORAGE_USER) || '';
    let run = false, pause = false, stop = false, idx = 0, total = 0, items = [];

    let CFG = { dItem: 120, dFill: 70, dClick: 70 };
    let Settings = {
        localDelayItem: 20, localDelayFill: 15, localDelayClick: 15,
        autoSelectValid: true, autoClearOnInvalid: true, autoSubmit: false
    };

    // ---------- 默认规则 ----------
    const DEFAULT_RULES = {
        protectWords: [
            // 成语典故
            "一心一意","三心二意","七上八下","九牛一毛","一心二用","一模一样","一举一动",
            "一五一十","一朝一夕","一步登天","一步一趋","一清二楚","一传十","一代宗师","一年一度",
            "十年寒窗","七年之痒","四书五经","五十弦翻塞外声","一千零一夜","海底两万里",
            "八百里分麾下炙","十之八九","六六大顺","九九归一","天下第一关","十三朝古都",
            "一瘸一拐","一草一木","一针一线","一颦一笑","一生一世","一龙一猪","一张一弛",
            "一唱一和","一板一眼","三心二意朝三暮四","一心不能二用","一步一步","一草一木",
            // 历史地名专有名词
            "明十三陵","九寨沟","三里屯","万里长城","三国演义","七里香","五月天",
            "三只松鼠","零一万物","五八同城","文一路","二环路","三全路",
            // 诗词引用
            "两个黄鹂鸣翠柳","一行白鹭上青天","两个黄鹂鸣翠柳",
            // 口语"一/两/三"开头
            "一下","一下下","一点点","一些","一直","一定","一起","一般","一会儿","一同","一方面",
            "一块儿","一块两块","两块","两毛","两次","两边","两三","两三天","两三个",
            "三下","三四个","三五个",
            // 特定词
            "零花钱","零配件","零碎","零售","零度","零分","零基础",
            // 不一定类
            "不一定","不一定吧","不一定啊","不一定对","不一定行",
            // 序号
            "第一个","第一个是","第一","第二","第三个","第四个","第五个",
            // 叠词
            "一个个","一张张","一条条","一遍遍","一次次",
            // 虚指泛指
            "一两句","说两句","玩两把","有两下","有一点","有一点湿","有一点好",
            "有一点热","有一点大","有一点小","一男子","两个闲钱",
            "一到路上","一气之下","玩两天","过两天再说","晚一点",
            // 星期/农历
            "星期三","星期二","星期四","星期五","星期六","礼拜五","礼拜天","礼拜一","礼拜六",
            "正月初八","腊月初八","八月十五","端午","中秋","正月","腊月","初一","十五",
            // 特定量词组(半字开头)
            "半盖","半片","半拉","半边天","半天","半辈子","半路","半推半就","一人一半","半生半熟",
            // 修饰性描述
            "有一天下午","一天清晨","有一年夏天","他一个人生活","两个世界的人","两颗星",
            // 语气程度
            "百分之百支持","百分之一万没问题","放一百个心","精神上百分之百支持",
            // 历史年号/节日
            "十月革命","洪武十三年","万历十五年","七七事变","十一黄金周","五一劳动节",
            // 其他专有名词
            "英语四级","科目三考试","二次元","三位一体","三维空间","六度空间理论","第一学历",
            "四大天王","四大发明","午时三刻","一年三熟","零一万物"
        ],
        asrFix: {
            "0售": "零售",
            "三点零五": "3.05",
            "三点二五": "3.25",
            "三点零二": "3.02",
            "五点七": "5.7",
            "九点三": "9.3",
            "六点八": "6.8",
            "八点五": "8.5"
        },
        strongAIPatterns: [
            "您拨(叫|打)的(用户|电话|号码)","通话移转|呼叫转移|无法接听|暂时无法接通","请在提示音后|滴声后.*留言|录制.*留言|语音留言",
            "重录请按|重新录制|按.*重录","退订回T|回复TD退订","智能语音助手|语音助理|智能助理","挂断即可|完成后挂断|转至|尝试联系的用户",
            "请核对小票以及货品避免取错","高德地图持续|高德地图导航","小爱同学|小爱助理","天猫精灵|叮咚叮咚",
            "对不起您拨的号码不存在|号码不存在","远声场|背景.*人声|难以.*听清","多人.*重叠|同时.*说话"
        ],
        invalidKeywords: [
            "您拨叫的用户正忙","您拨打的电话正在通话中","通话移转","您拨打的电话已关机","您拨打的电话暂时无法接通",
            "您拨打的电话已停机","来电提醒","正在通话中","已转接","呼转","呼叫","受限","阅权","请勿",
            "您呼叫的用户","呼叫转移","无法接通","无法接听","请在提示音后录制留言","提示音后留言",
            "录制留言","语音留言","留言录音","提示音","还呗来电","环呗来电","安逸包来电","爱医宝来电","安逸花来电",
            "重录请按","智能助理","语音助理","机主","本机机主","机主声明",
            "小爱同学","小爱助理","天猫精灵","叮咚叮咚",
            "高德地图持续为您导航","高德地图导航开始",
            "对不起您拨的号码不存在","号码不存在","快已转至语音留言",
            "远声场难以听清","背景音乐太吵","背景人声嘈杂","多人同时说话听不清",
            "纯音乐没有人声","全是伴奏听不到词","这是首歌","这首歌是.*演唱"
        ],
        digitMap: { "零":"0","一":"1","二":"2","三":"3","四":"4","五":"5","六":"6","七":"7","八":"8","九":"9","两":"2","幺":"1" },
        unitMap: { "十":10, "百":100, "千":1000, "万":10000, "亿":100000000 },
        quantifierList: ["个","只","条","张","本","支","块","次","趟","遍","顿","件","台","辆","部","首","句","家","所","间","座","扇","页","章","节","岁","年","月","天","小时","分钟","秒钟","元","角","分","笔","单"],
        conversionPatterns: {"money_units":["块","毛","分"],"date_units":["年","月","日","号","点","分","秒"],"phone_replacements":{"杠":"-","点":"."},"pure_point":"点","range_separators":["到","至"],"percent_prefix":"百分之","fraction_separator":"分之","protected_digits":["一","二","两"],"dot_fix":"点","two_to_er":"二","exclude_digits":["一","二"],"punctuation_map":{".":"。",",":",","?":"?","!":"!",";":";",":":":","(":"(",")":")","<":"《",">":"》"}}
    };

    let RULES = JSON.parse(JSON.stringify(DEFAULT_RULES));
    RULES.strongAIPatterns = RULES.strongAIPatterns.map(p => new RegExp(p));

    // UI 元素
    let st, bar, txt, btnS, btnT;
    let curOrig = '', curTrans = '', curValid = true, curTA = null, curIdx = 0;
    let isAutoFilling = false;

    // ---------- 初始化设置和规则 ----------
    function loadSettings() {
        const saved = GM_getValue(STORAGE_SETTINGS);
        if (saved) try { Object.assign(Settings, JSON.parse(saved)); } catch(e) {}
        applyDelays();
    }
    function saveSettings() { GM_setValue(STORAGE_SETTINGS, JSON.stringify(Settings)); applyDelays(); }
    function applyDelays() {
        CFG.dItem = Settings.localDelayItem;
        CFG.dFill = Settings.localDelayFill;
        CFG.dClick = Settings.localDelayClick;
    }
    function loadRules() {
        const saved = GM_getValue(STORAGE_RULES);
        if (saved) {
            try {
                const parsed = JSON.parse(saved);
                RULES = { ...DEFAULT_RULES, ...parsed };
                RULES.strongAIPatterns = RULES.strongAIPatterns.map(p => new RegExp(p));
            } catch(e) {}
        }
    }
    function saveRulesToStorage() {
        const toStore = JSON.parse(JSON.stringify(RULES));
        toStore.strongAIPatterns = RULES.strongAIPatterns.map(p => p.source);
        GM_setValue(STORAGE_RULES, JSON.stringify(toStore));
    }

    // ---------- 辅助函数 ----------
    function escapeHtml(text) {
        if (text === null || text === undefined) return '';
        const div = document.createElement('div');
        div.textContent = String(text);
        return div.innerHTML;
    }

    const canSave = () => backendOk && loggedIn && token;
    const Q_KEY = 'anno_offline_queue';
    const getQ = () => { try { return JSON.parse(localStorage.getItem(Q_KEY)||'[]'); } catch { return []; } };
    const saveQ = (q) => { if (q.length>500) q=q.slice(-500); localStorage.setItem(Q_KEY, JSON.stringify(q)); };
    const addToQ = (d) => { const q=getQ(); q.push(d); saveQ(q); };
    async function flushQ() {
        if (!canSave()) return;
        const q=getQ(); if(!q.length) return;
        const ok=[];
        for(let i=0;i<q.length;i++) {
            try {
                await new Promise((res,rej)=>GM_xmlhttpRequest({
                    method:'POST', url:API+'?action=save', data:JSON.stringify(q[i]), headers:{'Content-Type':'application/json','X-Auth-Token':token},
                    onload: r=>{ try{ JSON.parse(r.responseText).success?res():rej(); }catch{rej();} }, onerror:rej
                }));
                ok.push(i);
            }catch{break;}
        }
        if(ok.length) saveQ(q.filter((_,i)=>!ok.includes(i)));
    }
    async function saveToDB(orig, trans, valid) {
        if(!canSave()) { addToQ({original_text:orig, transcribed_text:trans, is_valid:valid?1:0}); return; }
        GM_xmlhttpRequest({ method:'POST', url:API+'?action=save', data:JSON.stringify({original_text:orig, transcribed_text:trans, is_valid:valid?1:0}), headers:{'Content-Type':'application/json','X-Auth-Token':token} });
    }
    // 配额缓存,减少网络请求
    var cachedQuota = -1, lastQuotaCheck = 0;
    async function checkQuota() {
        if(!canSave()) return -1;
        var now = Date.now();
        // 缓存30秒内的配额结果
        if (now - lastQuotaCheck < 30000 && cachedQuota >= 0) return cachedQuota;
        return new Promise(function(resolve){
            GM_xmlhttpRequest({ method:'GET', url:API+'?action=check_quota', headers:{'X-Auth-Token':token}, onload:function(r){
                try{ var j=JSON.parse(r.responseText); cachedQuota = j.success?j.available:-1; lastQuotaCheck=Date.now(); resolve(cachedQuota); }
                catch{ resolve(-1); }
            }, onerror:function(){ resolve(-1); }});
        });
    }
    function setTextarea(tx, v) {
        if(!tx) return;
        const d=Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,'value');
        const descriptorSet = d ? d.set : null;
        if(descriptorSet) descriptorSet.call(tx,v); else tx.value=v;
        tx.dispatchEvent(new Event('input',{bubbles:true}));
        tx.dispatchEvent(new Event('change',{bubbles:true}));
        tx.dispatchEvent(new Event('blur',{bubbles:true}));
    }

    // ---------- 中文数字解析(核心)- 符合ITN标注规范 ----------
    function parseChineseNumber(s) {
        let t = s.replace(/两/g, '二');
        if (/^[一二]$/.test(t)) return null;

        // 预处理:处理 "十" 在开头或单独成数的情况 (十=10, 十三=13)
        if (t === '十') return 10;
        if (/^十[零一二三四五六七八九]$/.test(t)) {
            // 十三、十四等
            const digit = RULES.digitMap[t[1]];
            return 10 + parseInt(digit);
        }

        // 检查是否包含无效字符(非数字、非单位)
        if (!/^[零一二三四五六七八九十百千万亿]+$/.test(t)) return null;

        // 处理纯个位数字序列(如 "三七九九"),不应转换
        if (/^[零一二三四五六七八九]+$/.test(t)) return null;

        let result = 0, temp = 0, section = 0;
        let lastWasUnit = false;
        for (let i = 0; i < t.length; i++) {
            const c = t[i];
            const digit = RULES.digitMap[c];
            if (digit !== undefined) {
                const digitVal = parseInt(digit);
                // 如果前一个字符是十,当前的数字是其组成部分(如十一=11,二十一=21)
                if (lastWasUnit && t[i-1] === '十') {
                    section += digitVal;  // 十=10, 加 1 = 11
                    temp = 0;
                } else {
                    temp = digitVal;
                    if (i === t.length - 1) section += temp;
                }
                lastWasUnit = false;
            } else if (RULES.unitMap[c]) {
                const unit = RULES.unitMap[c];
                if (unit >= 10000) {
                    section = (section + (temp || 1)) * unit;
                    result += section;
                    section = 0;
                } else {
                    section += (temp || 1) * unit;
                }
                temp = 0;
                lastWasUnit = true;
            } else return null;
        }
        return result + section + temp;
    }

    // 获取带单位的数字表示(如:3亿5000万)
    function getMixedRepresentation(numStr) {
        const value = parseChineseNumber(numStr);
        if (value === null) return null;
        if (value >= 100000000) {
            const yi = Math.floor(value / 100000000);
            const remainder = value % 100000000;
            if (remainder > 0) {
                if (remainder >= 10000) {
                    const wan = Math.floor(remainder / 10000);
                    const rem = remainder % 10000;
                    if (rem > 0) return yi + '亿' + wan + '万' + rem;
                    else return yi + '亿' + wan + '万';
                } else return yi + '亿' + remainder;
            } else return yi + '亿';
        } else if (value >= 10000) {
            const wan = Math.floor(value / 10000);
            const remainder = value % 10000;
            if (remainder > 0) return wan + '万' + remainder;
            else return wan + '万';
        }
        return value.toString();
    }

    function isApproximateNumber(str) {
        // 概数不转换:几、多、来、余、相邻数字组合
        if (/[几多来余]/.test(str)) return true;
        // 相邻数字组成的概数:三两百、两三千、七八十万
        if (/^[零一二三四五六七八九][零一二三四五六七八九][百千万亿]?$/.test(str)) return true;
        // 十+几/多/来/余
        if (/^十[几多来余]?$/.test(str)) return true;
        // 初七八(农历日期)
        if (/^初[零一二三四五六七八九]$/.test(str)) return true;
        return false;
    }

    function localConvert(text) {
        if (!text) return '';
        let t = text;

        // ========== 1. 保护词占位 ==========
        const placeholders = [];
        RULES.protectWords.sort((a,b)=>b.length-a.length).forEach((w,i)=>{
            t = t.replace(new RegExp(w, 'g'), m=>{ const ph=`__P${i}__`; placeholders.push({ph, orig:m}); return ph; });
        });

        // ========== 2. ASR修正 ==========
        for (const [w,c] of Object.entries(RULES.asrFix)) t = t.replace(new RegExp(w, 'g'), c);

        // ========== 3.5 特殊编码(身份证/车牌/航班号/火车车次) ==========
        // 身份证:尾号零八五八二叉 → 尾号08582X
        t = t.replace(/(尾号)([零一二三四五六七八九十幺叉]+)/g, (m, prefix, num) => prefix + digitByDigit(num));
        // 车牌:粤B五九幺六幺 → 粤B59161
        t = t.replace(/([\u4e00-\u9fa5][A-Z])([零一二三四五六七八九十幺]+)/g, (m, plate, num) => plate + digitByDigit(num));
        // 航班号:MU五七零二 → MU5702
        t = t.replace(/(MU|CA|CZ|HU|9C|HO|BK|ZH|KN|UQ|JD|TV|GT|DG|MF|FU|KN)([零一二三四五六七八九十幺]+)/g, (m, code, num) => code + digitByDigit(num));
        // 火车车次:高铁G一百零一 → 高铁G101,K八十八次 → K88次
        t = t.replace(/(高铁?|动车?|普快?|K|T|Z|G|D|C|Y)[A-Z]?([零一二三四五六七八九十百千万]+)(次|号)?/g, (m, type, num, suffix) => {
            const v = parseChineseNumber(num);
            return type + (v !== null ? v : digitByDigit(num)) + (suffix || '');
        });
        // 二号线/幺四九路 → 2号线/149路(线路识别)
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)(号线|路|公交)/g, (m, num, suffix) => {
            const v = parseChineseNumber(num);
            return (v !== null ? v : digitByDigit(num)) + suffix;
        });

        // ========== 3. 通信号码(幺→1,叉→X,杠→-,点→.,加→+) ==========
        // 逐位转换:幺→1,叉→X(末位)
        function digitByDigit(str) {
            var out = '';
            for (var i = 0; i < str.length; i++) {
                var ch = str[i];
                if (ch === '幺') out += '1';
                else if (ch === '叉' && i === str.length - 1) out += 'X';
                else if (RULES.digitMap[ch] !== undefined) out += RULES.digitMap[ch];
                else out += ch;
            }
            return out;
        }
        // 验证码+数字 → 验证码4029
        t = t.replace(/(验证码?\s*)([零一二三四五六七八九十两幺叉]+)/g, (m, prefix, num) => {
            if (/[百千万亿]/.test(num)) return m;
            return prefix + digitByDigit(num);
        });
        // 固话区号杠号码 → 010-888转606
        t = t.replace(/([零一二三]+杠[零一二三]+杠)([零一二三四五六七八九十两幺]+转?[零一二三四五六七八九十两幺]*)/g, (m, area, rest) => {
            return digitByDigit(area) + digitByDigit(rest);
        });
        // IP地址:一九二点一六八点零点一 → 192.168.0.1
        t = t.replace(/([零一二三四五六七八九十]+点)+([零一二三四五六七八九十]+)/g, (m) => digitByDigit(m));
        // 国际区号:加八六幺三八 → +86138
        t = t.replace(/加([零一二三四五六七八九十两幺]+)/g, (m, num) => '+' + digitByDigit(num));
        // 纯电话号码(幺开头+数字序列)→ 15910036
        t = t.replace(/幺[零一二三四五六七八九幺]+/g, m => digitByDigit(m));
        // 固话区号-号码(杠连接)
        t = t.replace(/(\d+)杠(\d+)/g, '$1-$2');
        // IP地址的点
        t = t.replace(/(\d+)点(\d+)/g, '$1.$2');
        // 国际区号加号
        t = t.replace(/加(\d+)/g, '+$1');
        // 保留"转"(分机号)

        // ========== 4. 分数:分之 → / ==========
        // 三分之二 → 2/3,五分之四 → 4/5
        t = t.replace(/([零一二三四五六七八九两十百千万亿]+)分之([零一二三四五六七八九两十百千万亿]+)/g, (m, denom, numer) => {
            const d = parseChineseNumber(denom);
            const n = parseChineseNumber(numer);
            if (d !== null && n !== null && d !== 0) return n + '/' + d;
            return m;
        });

        // ========== 5. 百分数:百分之 → % ==========
        // 百分之二十 → 20%,百分之三十四点五八 → 34.58%
        t = t.replace(/百分之([零一二三四五六七八九两十百千万亿\d.]+)/g, (m, num) => {
            const v = parseChineseNumber(num);
            if (v !== null) return v + '%';
            // 如果包含小数点,尝试直接转换
            if (/\d/.test(num)) return num + '%';
            return m;
        });

        // ========== 6. 千分数:千分之 → ‰ ==========
        t = t.replace(/千分之([零一二三四五六七八九两十百千万亿\d.]+)/g, (m, num) => {
            const v = parseChineseNumber(num);
            if (v !== null) return v + '‰';
            return m;
        });

        // ========== 7. 比例:比 → : ==========
        // 一比三 → 1:3,零点五比二 → 0.5:2
        t = t.replace(/([零一二三四五六七八九两十百千万亿\d.]+)比([零一二三四五六七八九两十百千万亿\d.]+)/g, (m, a, b) => {
            const va = parseChineseNumber(a);
            const vb = parseChineseNumber(b);
            if (va !== null && vb !== null) return va + ':' + vb;
            return m;
        });

        // ========== 8. 日期转换 ==========
        // 年份:零八/年 → 08/年,二零二四年 → 2024年
        t = t.replace(/([零一二三四五六七八九]{4,})年/g, (m, y) => {
            let out = '';
            for (let c of y) out += RULES.digitMap[c] !== undefined ? RULES.digitMap[c] : c;
            return out + '年';
        });
        // 月份:十月 → 10月,十月一号 → 10月1号
        t = t.replace(/十([一二三四五六七八九])月/g, (m, d) => (10 + parseInt(RULES.digitMap[d])) + '月');
        t = t.replace(/十月/g, '10月');
        t = t.replace(/([零一二三四五六七八九])月/g, (m, d) => (RULES.digitMap[d] !== undefined ? RULES.digitMap[d] : d) + '月');
        // 日号:支持十到九十九号
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)[号日](?![号日])/g, function(m, num) {
            if (/^[十]$/.test(num)) return '10';
            var v = parseChineseNumber(num);
            return v !== null ? v : m;
        });

        // ========== 9. 时间转换 ==========
        // 半点:两点半 → 2点半
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)点半/g, (m, h) => {
            const hour = parseChineseNumber(h);
            return (hour !== null ? hour : h) + '点半';
        });
        // 整点:下午两点 → 下午2点,晚上十二点 → 晚上12点,整点钟/整点
        t = t.replace(/(?:上午?|下午?|早上?|晚上?|中午?|凌晨?|傍晚?|半夜?)(?:十)?([一二三四五六七八九])点(钟|整)?/g, (m, h, suffix) => {
            return m.replace(h + '点' + (suffix||''), (RULES.digitMap[h] !== undefined ? RULES.digitMap[h] : h) + '点' + (suffix||''));
        });
        t = t.replace(/(?:上午?|下午?|早上?|晚上?|中午?|凌晨?|傍晚?|半夜?)十点(钟|整)?/g, m => {
            return m.replace('十点' + (m.includes('点钟')?'钟':(m.includes('整')?'整':'')), '10点' + (m.includes('点钟')?'钟':(m.includes('整')?'整':'')));
        });
        t = t.replace(/(?:上午?|下午?|早上?|晚上?|中午?|凌晨?|傍晚?|半夜?)(?:二十|三十)点(钟|整)?/g, m => {
            const hour = m.includes('二十') ? '20' : '30';
            const suffix = m.includes('点钟') ? '点钟' : (m.includes('整') ? '整' : '');
            return m.replace(/[二三]十点/, hour + '点').replace(/点钟|整/g, suffix);
        });
        // 一刻:九点一刻 → 9点一刻
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)点一刻/g, (m, h) => {
            const hour = parseChineseNumber(h);
            return (hour !== null ? hour : h) + '点一刻';
        });
        // 几点几分(不带量词)
        t = t.replace(/([零一二三四五六七八九十]+)点([零一二三四五六七八九]+)分/g, (m, h, min) => {
            const hour = parseChineseNumber(h);
            const minute = parseChineseNumber(min);
            return (hour !== null ? hour : h) + '点' + (minute !== null ? String(minute).padStart(2, '0') : min) + '分';
        });
        // 带小时/分钟/秒钟的
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)(个)?小时/g, (m, num, skip) => {
            if (/^[一二]$/.test(num)) return m; // 一小时/二小时保留
            const v = parseChineseNumber(num);
            return (v !== null ? v : num) + '小时';
        });
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)(个)?分钟/g, (m, num, skip) => {
            const v = parseChineseNumber(num);
            return (v !== null ? v : num) + '分钟';
        });
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)(个)?秒钟/g, (m, num, skip) => {
            const v = parseChineseNumber(num);
            return (v !== null ? v : num) + '秒钟';
        });

        // ========== 10. 数字范围(到/至) ==========
        // 三到六个 → 3~6个(无量词时用~)
        // 三个到六个 → 3个到6个(有量词保留到)
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)到([零一二三四五六七八九十百千万亿]+)/g, (m, a, b) => {
            const va = getMixedRepresentation(a);
            const vb = getMixedRepresentation(b);
            if (va !== null && vb !== null) return va + '~' + vb;
            return m;
        });
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)至([零一二三四五六七八九十百千万亿]+)/g, (m, a, b) => {
            const va = getMixedRepresentation(a);
            const vb = getMixedRepresentation(b);
            if (va !== null && vb !== null) return va + '~' + vb;
            return m;
        });

        // ========== 11. 量词转换 ==========
        const quantClass = RULES.quantifierList.join('');
        // 一、二、两 + 量词 → 保留汉字
        t = t.replace(new RegExp(`([一二两])([${quantClass}])`, 'g'), (m, num, unit) => num + unit);
        // 概数+量词不转换(几十、几百等)
        t = t.replace(new RegExp(`([几多成上打]+)([零一二三四五六七八九十百千万亿]*)([${quantClass}])`, 'g'), (m, prefix, num, unit) => prefix + num + unit);
        // 三及以上 + 量词 → 转换为数字
        t = t.replace(new RegExp(`([零三四五六七八九十百千万亿]+)([${quantClass}])`, 'g'), (m, num, unit) => {
            // 概数不转换(几、多、成等开头的组合)
            if (/^[几多成上打]/.test(num)) return num + unit;
            // 十+量词(如:十来个)不转换
            if (/^十[零一二三四五六七八九]?$/.test(num)) return num + unit;
            // 万/亿级数字保持单位
            if (/[万亿]/.test(num)) {
                const mixed = getMixedRepresentation(num);
                if (mixed !== null) return mixed + unit;
            }
            const v = parseChineseNumber(num);
            return v !== null ? v + unit : m;
        });

        // ========== 12. 货币 ==========
        // 五块六毛 → 5块6毛,三块五 → 3块5
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)块([零一二三四五六七八九]+)毛/g, (m, y, m2) => {
            const yVal = parseChineseNumber(y);
            const mVal = parseChineseNumber(m2);
            return (yVal !== null ? yVal : y) + '块' + (mVal !== null ? mVal : m2) + '毛';
        });
        // 五块五 → 5块5,五块五毛五 → 5块5毛5
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)块零?([零一二三四五六七八九]+)/g, (m, y, f) => {
            const yVal = parseChineseNumber(y);
            const fVal = parseChineseNumber(f);
            return (yVal !== null ? yVal : y) + '块' + (fVal !== null ? fVal : f);
        });
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)块/g, (m, y) => {
            const v = parseChineseNumber(y);
            return v !== null ? v + '块' : m;
        });

        // ========== 13. 度量衡 ==========
        // 保留单位,只转换数字
        // 五千米 → 5千米,三十八度五 → 38度5
        t = t.replace(/([零一二三四五六七八九十百千万亿\d.]+)(米|千米|厘米|公里|克|千克|毫克|升|毫升|毫升|吨|摄氏度|度|焦耳|千焦|瓦|千瓦|马力|赫兹|千赫|兆赫)/g, (m, num, unit) => {
            if (/[零一二三四五六七八九十]/.test(num)) {
                const v = parseChineseNumber(num);
                return (v !== null ? v : num) + unit;
            }
            return m;
        });

        // ========== 14. 小数 ==========
        // 一点七五 → 1.75(带量词或单位时转换)
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)点([零一二三四五六七八九]+)/g, (m, int, dec) => {
            // 整数部分:直接用 digitMap 转换(不用 parseChineseNumber,因为 "一" 会返回 null)
            let intOut = '';
            for (let c of int) intOut += RULES.digitMap[c] !== undefined ? RULES.digitMap[c] : c;
            // 小数部分:逐位转换
            let decOut = '';
            for (let c of dec) decOut += RULES.digitMap[c] !== undefined ? RULES.digitMap[c] : c;
            return intOut + '.' + decOut;
        });

        // ========== 15. 万/亿级大数字 ==========
        // 三亿五千万 → 3亿5000万,一万二千 → 12000(完整转换)
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)亿([零一二三四五六七八九]+)?万?/g, (m, yi, rest) => {
            const yiVal = parseChineseNumber(yi);
            if (yiVal !== null) {
                if (rest && parseChineseNumber(rest + '万')) {
                    // 三亿五千万 → 3亿5000万
                    return yiVal + '亿' + parseChineseNumber(rest + '万');
                }
                return yiVal + '亿';
            }
            return m;
        });
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)万([零一二三四五六七八九]+)?/g, (m, wan, rest) => {
            const wanVal = parseChineseNumber(wan);
            if (wanVal !== null) {
                if (rest) {
                    return wanVal + '万' + parseChineseNumber(rest);
                }
                return wanVal + '万';
            }
            return m;
        });

        // ========== 16.5 地址转换(路名保护,门牌楼栋转换) ==========
        // 楼层:十八层 → 18层,负一层/地下层 → 负1层/地下1层
        t = t.replace(/(负|地下)([零一二三四五六七八九十百千万亿]+)层/g, (m, prefix, num) => {
            const v = parseChineseNumber(num);
            return prefix + (v !== null ? v : num) + '层';
        });
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)层/g, (m, num) => {
            if (/^[一二]$/.test(num)) return m; // 一层保留汉字
            const v = parseChineseNumber(num);
            return (v !== null ? v : num) + '层';
        });
        // 房间号:三零二室 → 302室,十八楼三零二室 → 18楼302室
        t = t.replace(/([零一二三四五六七八九十]+)室/g, (m, num) => {
            const v = parseChineseNumber(num);
            return (v !== null ? v : num) + '室';
        });
        // 楼栋号:五号楼 → 5号楼,二单元 → 2单元
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)号楼/g, (m, num) => {
            const v = parseChineseNumber(num);
            return (v !== null ? v : num) + '号楼';
        });
        t = t.replace(/([零一二三四五六七八九十百千万亿]+)单元/g, (m, num) => {
            const v = parseChineseNumber(num);
            return (v !== null ? v : num) + '单元';
        });
        // 号牌:二十二号 → 22号,甲十一号 → 甲11号
        t = t.replace(/(甲|乙|丙)?([零一二三四五六七八九十百千万亿]+)号/g, (m, prefix, num) => {
            const v = parseChineseNumber(num);
            return (prefix || '') + (v !== null ? v : num) + '号';
        });
        // 邮编:六五零零一一 → 650011
        if (/\d/.test(t) === false) { // 只有中文数字时
            t = t.replace(/([零一二三四五六七八九]{6})/g, m => digitByDigit(m));
            t = t.replace(/([零一二三四五六七八九]{4})路/g, (m, num) => digitByDigit(num) + '路');
        }
        // ========== 16. 剩余中文数字整体转换 ==========
        // 先处理概数词(几十、几百等)
        t = t.replace(/[几多成上打]+[零一二三四五六七八九十百千万亿]*/g, m => m);
        // 转换普通数字
        t = t.replace(/[零一二三四五六七八九两十百千万亿]+/g, m => {
            // 概数不转换
            if (isApproximateNumber(m)) return m;
            // 单独的一、二不转换(量词前会保留)
            if (/^[一二]$/.test(m)) return m;
            // 万/亿保持
            if (/^[万亿]+$/.test(m)) return m;
            const v = parseChineseNumber(m);
            return v !== null ? v.toString() : m;
        });

        // ========== 17. 恢复保护词 ==========
        placeholders.forEach(({ph, orig})=> t = t.replace(new RegExp(ph, 'g'), orig));

        // ========== 18. 标点 ==========
        t = t.trim().replace(/[\s\n\r]/g, '');
        if (!/[。?!]$/.test(t)) {
            if (/[吗谁什么哪怎么怎样多少几啥呢]|有没有|是不是|行不行|可不可以|能否|是否/.test(t)) t += '?';
            else t += '。';
        }
        return t;
    }

    function isValidText(text) {
        const trimmed = text.trim();
        if (/^(你好|您好|喂|嗨|在吗|哈喽)[!!。??]*$/.test(trimmed)) return false;
        for (let p of RULES.strongAIPatterns) if (p.test(text)) return false;
        for (let kw of RULES.invalidKeywords) if (trimmed.includes(kw)) {
            if (kw.length < 5) return false;
            if (trimmed.replace(new RegExp(kw, 'g'), '').trim().length <= 5) return false;
        }
        const letters = text.match(/[a-zA-Z]/g) || [];
        return letters.length / text.length <= 0.5;
    }

    // ---------- 条目处理 ----------
    function extractItemData(item, index) {
        let ta=null, rg=null;
        for(let w of item.querySelectorAll('.labelRender-item-answer-wrap')) {
            const ti=w.querySelector('.labelRender-item-answer-title');
            if(!ti) continue;
            if(ti.innerText.includes('转写文本')) ta=w.querySelector('textarea.ant-v5-input');
            else if(ti.innerText.includes('是否有效')) rg=w;
        }
        if(!ta||!rg) return null;
        const od=item.querySelector('.dt-text-container > div');
        const orig=od?od.innerText.trim():'';
        if(!orig) return null;
        return { index, element:item, textarea:ta, radioGroup:rg, originalText:orig, hasExistingData: ta.value.trim() && rg.querySelector('input[type="radio"]:checked') };
    }

    // 处理单个条目
    async function processItem(item) {
        try {
            if(stop) return false;

            const orig = item.originalText;
            const valid = isValidText(orig);
            const target = valid ? localConvert(orig) : '';

            isAutoFilling = true;
            setTextarea(item.textarea, target);
            await new Promise(r=>setTimeout(r, CFG.dFill));

            if (Settings.autoSelectValid) {
                const targetLabel = valid ? '有效' : '无效';
                const labels = item.radioGroup.querySelectorAll('label.ant-v5-radio-wrapper');
                let clicked = false;
                for (let l of labels) {
                    const sp = l.querySelector('.ant-v5-radio-label');
                    if (sp && sp.innerText.trim() === targetLabel) {
                        if (!l.classList.contains('ant-v5-radio-wrapper-checked')) l.click();
                        await new Promise(r=>setTimeout(r, CFG.dClick));
                        clicked = true;
                        break;
                    }
                }
                // 备用方案:直接设置radio
                if (!clicked) {
                    const radios = Array.from(item.radioGroup.querySelectorAll('input[type="radio"]'));
                    const radio = radios.find(r => r.value === targetLabel);
                    if (radio && !radio.checked) {
                        radio.checked = true;
                        radio.dispatchEvent(new Event('change',{bubbles:true}));
                        radio.dispatchEvent(new Event('click',{bubbles:true}));
                        await new Promise(r=>setTimeout(r, CFG.dClick));
                    }
                }
            }
            isAutoFilling = false;
            saveToDB(orig, target, valid);
            return true;
        } catch(e) {
            console.error('处理条目失败:', e);
            return true;
        }
    }

    // ---------- UI 更新 ----------
    function updUI() {
        if(!st||!bar||!txt) return;
        const pct = total ? (idx/total*100) : 0;
        bar.style.width = pct+'%';
        txt.textContent = idx + ' / ' + total;
        // 状态文字(隐藏百分比)
        const newStatus = !run ? '⚪ 待命中' : (pause ? '⏸ 已暂停' : '▶ 运行中');
        if (st.textContent !== newStatus) st.textContent = newStatus;
        const newBtnText = run ? (pause ? '▶ 继续' : '⏸ 暂停') : '▶ 开始';
        if (btnS.textContent !== newBtnText) btnS.textContent = newBtnText;
    }

    function scrollToTop() {
        // 使用 requestAnimationFrame 确保在下一帧执行
        requestAnimationFrame(function() {
            // 方法1: window 滚动
            window.scrollTo(0, 0);
            window.scrollTo({top: 0, left: 0, behavior: 'instant'});
            // 方法2: document 元素
            document.documentElement.scrollTop = 0;
            document.body.scrollTop = 0;
            document.body.scrollTo(0, 0);
            // 方法3: 滚动到第一个条目
            var firstItem = document.querySelector('.labelRender-item');
            if (firstItem) {
                firstItem.scrollIntoView({block: 'start', behavior: 'instant'});
                firstItem.scrollIntoView(true);
            }
            // 方法4: 尝试滚动容器(如果有)
            var container = document.querySelector('.dt-text-container, .scroll-container, main, [role="main"]');
            if (container && container.scrollTo) {
                container.scrollTo(0, 0);
            }
        });
    }

    async function mainLoop() {
        if(!canSave()) { alert('请先登录'); showLoginPanel(); return; }
        if(await checkQuota()===0) { alert('配额已用完'); return; }
        await flushQ();
        run=true; pause=false; stop=false;
        items=Array.from(document.querySelectorAll('.labelRender-item'));
        total=items.length; idx=0;
        injectFeedbackButtons();

        // 预提取所有数据并计算转换结果,避免在循环中重复计算
        const pending = [];
        for (let i=0; i<total; i++) {
            const d = extractItemData(items[i], i);
            if (d && !d.hasExistingData) {
                // 预计算转换结果
                d.valid = isValidText(d.originalText);
                d.target = d.valid ? localConvert(d.originalText) : '';
                pending.push(d);
            }
        }
        if (pending.length===0) { run=false; updUI(); return; }

        // 主循环
        for (let i=0; i<pending.length; i++) {
            if (stop) break;
            if (pause) { await new Promise(r=>setTimeout(r,100)); if(!pause) i--; continue; }
            try {
                await processItem(pending[i]);
            } catch(e) {
                console.error('处理失败:', e);
            }
            idx = i+1;
            updUI();
            if (CFG.dItem > 0) await new Promise(r=>setTimeout(r, CFG.dItem));
        }
        // 完成后滚动到顶部
        run=pause=stop=false; updUI();
        scrollToTop();
        await flushQ();
    }

    function injectFeedbackButtons() {
        // 优化:使用DocumentFragment批量注入,减少DOM回流
        const existingBtns = document.querySelectorAll('.fb-btn');
        const existingIdxs = new Set();
        existingBtns.forEach(function(btn){
            var idx = btn.dataset.idx;
            if(idx) existingIdxs.add(parseInt(idx));
        });
        const fragment = document.createDocumentFragment();
        document.querySelectorAll('.labelRender-item').forEach(function(item, i){
            if(existingIdxs.has(i)) return;
            item.style.position = 'relative';
            var btn = document.createElement('button');
            btn.className = 'fb-btn';
            btn.dataset.idx = i;
            btn.innerHTML = '📝';
            btn.title = '反馈';
            btn.style.cssText = 'position:absolute;top:6px;right:6px;z-index:100;padding:2px 8px;background:#1890ff;color:#fff;border:none;border-radius:12px;font-size:11px;cursor:pointer;';
            btn.onclick = function(e){ e.stopPropagation(); showFeedbackPanel(i); };
            fragment.appendChild(btn);
        });
        // 一次性添加到DOM
        if (fragment.children.length > 0) {
            const firstItem = document.querySelector('.labelRender-item');
            if (firstItem) firstItem.parentNode.appendChild(fragment);
        }
    }

    // ---------- 登录面板 ----------
    function showLoginPanel() {
        var oldPanel = document.getElementById('lb-panel');
        if(oldPanel) oldPanel.remove();
        oldPanel = document.getElementById('login-panel');
        if(oldPanel) oldPanel.remove();
        const panel=document.createElement('div'); panel.id='login-panel';
        panel.innerHTML = `<style>#login-panel{position:fixed;top:10px;right:10px;z-index:99999;background:#fff;border-radius:20px;padding:24px;width:280px;box-shadow:0 15px 30px rgba(0,0,0,0.15);}</style>
            <h3>🔐 登录 / 注册</h3><input id="login-username" placeholder="用户名" style="width:100%;padding:10px;margin-bottom:10px;border-radius:14px;border:1px solid #ccc;"><br>
            <input id="login-password" type="password" placeholder="密码" style="width:100%;padding:10px;margin-bottom:10px;border-radius:14px;border:1px solid #ccc;"><br>
            <div style="display:flex;gap:10px;"><button id="login-submit" style="flex:1;padding:10px;background:#3b82f6;color:#fff;border:none;border-radius:14px;">登录</button><button id="register-submit" style="flex:1;padding:10px;background:#fff;border:1px solid #3b82f6;color:#3b82f6;border-radius:14px;">注册</button></div>
            <div id="login-error" style="color:red;margin-top:10px;"></div>`;
        document.body.appendChild(panel);
        document.getElementById('login-submit').onclick=async ()=>{
            const u=document.getElementById('login-username').value.trim(), p=document.getElementById('login-password').value, err=document.getElementById('login-error');
            if(!u||!p) { err.textContent='请输入用户名和密码'; return; }
            GM_xmlhttpRequest({ method:'POST', url:API+'?action=login', data:JSON.stringify({username:u, password:p}), headers:{'Content-Type':'application/json'},
                onload: r=>{ try{ const j=JSON.parse(r.responseText); if(j.success){ token=j.token; user={username:u}; localStorage.setItem(STORAGE_TOKEN,token); localStorage.setItem(STORAGE_USER,u); loggedIn=true; panel.remove(); showMainPanel(); } else err.textContent=j.message; }catch{ err.textContent='响应异常'; } },
                onerror: ()=>err.textContent='网络错误' });
        };
        document.getElementById('register-submit').onclick=async ()=>{
            const u=document.getElementById('login-username').value.trim(), p=document.getElementById('login-password').value, err=document.getElementById('login-error');
            if(!u||!p) { err.textContent='请输入用户名和密码'; return; }
            if(p.length<6) { err.textContent='密码至少6位'; return; }
            GM_xmlhttpRequest({ method:'POST', url:API+'?action=register', data:JSON.stringify({username:u, password:p}), headers:{'Content-Type':'application/json'},
                onload: r=>{ try{ const j=JSON.parse(r.responseText); err.style.color=j.success?'#10b981':'#ef4444'; err.textContent=j.message; }catch{ err.textContent='响应异常'; } },
                onerror: ()=>{ err.style.color='#ef4444'; err.textContent='网络错误'; } });
        };
    }

    // ---------- 自动同步服务器规则 ----------
    async function syncRulesFromBackend() {
        if (!backendOk) return;
        try {
            var resp = await new Promise(function(res){GM_xmlhttpRequest({
                method:'GET',url:API+'?action=get_full_runtime_config',
                onload:function(r){try{res(JSON.parse(r.responseText));}catch{res({success:false});}},
                onerror:function(){res({success:false});}
            });});
            if (resp.success && resp.config) {
                var cfg = resp.config;
                RULES.protectWords = cfg.protect_words || RULES.protectWords;
                RULES.asrFix = cfg.asr_fix || RULES.asrFix;
                RULES.strongAIPatterns = (cfg.strong_ai_patterns || []).map(function(p){return new RegExp(p);});
                RULES.invalidKeywords = cfg.invalid_keywords || RULES.invalidKeywords;
                // 同步速度设置
                if (cfg.delay_item) Settings.localDelayItem = cfg.delay_item;
                if (cfg.delay_fill) Settings.localDelayFill = cfg.delay_fill;
                if (cfg.delay_click) Settings.localDelayClick = cfg.delay_click;
                applyDelays();
                saveRulesToStorage();
                saveSettings();
            }
        } catch(e) {}
    }

    // ---------- 检查管理员身份 ----------
    async function checkAdminStatus() {
        if (!backendOk || !token) return;
        try {
            var resp = await new Promise(function(res){GM_xmlhttpRequest({
                method:'GET', url:API+'?action=admin_get_all_config',
                headers:{'X-Auth-Token': token},
                onload: function(r){try{res(JSON.parse(r.responseText));}catch{res({success:false,noAdmin:true});}},
                onerror: function(){res({success:false,noAdmin:true});}
            });});
            isAdmin = resp.success && !resp.noAdmin;
        } catch(e) { isAdmin = false; }
    }

    // ---------- 保存规则到服务器(仅管理员) ----------
    async function saveRulesToServer() {
        if (!backendOk || !token || !isAdmin) {
            alert('需要管理员权限才能保存规则到服务器');
            return;
        }
        if (!confirm('确定要将当前规则保存到服务器数据库吗?')) return;
        try {
            var dataToSave = {
                protect_words: RULES.protectWords,
                asr_fix: RULES.asrFix,
                strong_ai_patterns: RULES.strongAIPatterns.map(function(p){return p.source;}),
                invalid_keywords: RULES.invalidKeywords,
                digit_map: RULES.digitMap,
                unit_map: RULES.unitMap,
                quantifier_list: RULES.quantifierList,
                conversion_patterns: RULES.conversionPatterns || {}
            };
            var resp = await new Promise(function(res){GM_xmlhttpRequest({
                method:'POST', url:API+'?action=admin_update_config',
                headers:{'Content-Type':'application/json','X-Auth-Token':token},
                data: JSON.stringify(dataToSave),
                onload: function(r){try{res(JSON.parse(r.responseText));}catch{res({success:false});}},
                onerror: function(){res({success:false});}
            });});
            if (resp.success) {
                alert('规则已成功保存到服务器数据库!');
            } else {
                alert('保存失败: ' + (resp.message || '未知错误'));
            }
        } catch(e) { alert('保存出错'); }
    }

    // ---------- 主面板 ----------
    function showMainPanel() {
        var oldPanel = document.getElementById('login-panel');
        if(oldPanel) oldPanel.remove();
        oldPanel = document.getElementById('lb-panel');
        if(oldPanel) oldPanel.remove();
        const p=document.createElement('div'); p.id='lb-panel';
        p.innerHTML = `<style>#lb-panel{position:fixed;top:10px;right:10px;z-index:99999;width:200px;background:#fff;border-radius:16px;box-shadow:0 8px 20px rgba(0,0,0,0.12);border:1px solid rgba(0,0,0,0.08);font-family:system-ui;overflow:hidden;transition:height 0.3s ease;}#lb-panel.dragging{background:#fff!important;}#lb-panel.minimized .body,#lb-panel.minimized .progress-bar,#lb-panel.minimized .buttons,#lb-panel.minimized .lb-text-row{display:none !important;}#lb-panel.minimized{height:46px;}.header{padding:12px 14px;cursor:move;display:flex;justify-content:space-between;border-bottom:1px solid rgba(0,0,0,0.05);}.title{font-weight:600;color:#1e293b;font-size:13px;}.actions span{cursor:pointer;margin-left:6px;font-size:12px;}.body{padding:14px;}.progress-bar{height:5px;background:#e2e8f0;border-radius:3px;margin:10px 0;overflow:hidden;}.progress-fill{height:100%;width:0%;background:linear-gradient(90deg,#3b82f6,#60a5fa);border-radius:3px;transition:width 0.15s ease;}.lb-text-row{display:flex;justify-content:center;gap:8px;font-size:11px;color:#64748b;}.buttons{display:flex;gap:6px;margin-top:12px;}.buttons button{flex:1;padding:7px;border:none;border-radius:10px;background:#f1f5f9;border:1px solid rgba(0,0,0,0.05);cursor:pointer;font-size:12px;}.buttons .primary{background:#3b82f6;color:#fff;}</style>
            <div class="header"><div class="title">🤖 标注助手 (${user.username})</div><div class="actions"><span id="lb-minimize" title="最小化">−</span><span id="lb-settings" title="设置">⚙️</span><span id="lb-close" title="关闭">✕</span></div></div>
            <div class="body"><div id="lb-status">⚪ 待命中</div><div class="progress-bar"><div class="progress-fill" id="lb-bar"></div></div><div class="lb-text-row"><span id="lb-text" style="text-align:center;font-size:12px;">0 / 0</span></div><div class="buttons"><button id="lb-start" class="primary">▶ 开始</button><button id="lb-stop" disabled>⏹ 停止</button></div></div>`;
        document.body.appendChild(p);
        st=document.getElementById('lb-status'); bar=document.getElementById('lb-bar'); txt=document.getElementById('lb-text');
        btnS=document.getElementById('lb-start'); btnT=document.getElementById('lb-stop');
        document.getElementById('lb-settings').onclick=showSettingsPanel;
        document.getElementById('lb-close').onclick=()=>{ p.style.display='none'; };
        document.getElementById('lb-minimize').onclick=()=>{ p.classList.toggle('minimized'); };
        btnS.onclick=()=>{ if(!run){ idx=0; mainLoop(); } else { pause=!pause; updUI(); } };
        btnT.onclick=()=>{ if(run){ stop=true; pause=false; updUI(); } };

        // 登录成功后自动从服务器同步规则和速度设置
        syncRulesFromBackend();

        let drag=false, startX,startY,startLeft,startTop;
        p.querySelector('.header').addEventListener('mousedown', e=>{
            if(e.target.closest('.actions')) return;
            drag=true; p.classList.add('dragging');
            const rect=p.getBoundingClientRect();
            startX=e.clientX; startY=e.clientY; startLeft=rect.left; startTop=rect.top;
            p.style.left=startLeft+'px'; p.style.right='auto'; p.style.top=startTop+'px';
            e.preventDefault();
        });
        document.addEventListener('mousemove', e=>{
            if(!drag) return;
            let left=startLeft+e.clientX-startX, top=startTop+e.clientY-startY;
            left=Math.max(0, Math.min(left, window.innerWidth-p.offsetWidth));
            top=Math.max(0, Math.min(top, window.innerHeight-p.offsetHeight));
            p.style.left=left+'px'; p.style.top=top+'px';
        });
        document.addEventListener('mouseup', ()=>{ drag=false; p.classList.remove('dragging'); });
        initManualClear();
        updUI();
    }

    // ---------- 设置面板(含规则管理) ----------
    function showSettingsPanel() {
        const overlay=document.createElement('div'); overlay.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,0.3);z-index:100002;display:flex;align-items:center;justify-content:center;';
        const panel=document.createElement('div'); panel.style.cssText='background:#fff;border-radius:16px;padding:24px;width:900px;max-height:85vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,0.3);';

        panel.innerHTML = `
            <style>
                .sp-tabs{display:flex;gap:8px;border-bottom:2px solid #e2e8f0;padding-bottom:12px;margin-bottom:20px;}
                .sp-tab{padding:10px 24px;border:none;border-radius:12px 12px 0 0;cursor:pointer;background:#f1f5f9;color:#64748b;font-weight:500;transition:all 0.2s;}
                .sp-tab:hover{background:#e2e8f0;}
                .sp-tab.active{background:#3b82f6;color:#fff;}
                .sp-content{display:none;}
                .sp-content.active{display:block;}
                .sp-grid{display:grid;grid-template-columns:1fr 1fr;gap:24px;}
                .sp-section{background:#f8fafc;border-radius:16px;padding:16px;}
                .sp-section h4{margin:0 0 12px 0;color:#334155;font-size:14px;font-weight:600;}
                .sp-row{display:flex;align-items:center;gap:8px;margin:8px 0;}
                .sp-row label{flex:1;color:#475569;font-size:13px;}
                .sp-row input[type="number"]{width:60px;padding:4px 8px;border:1px solid #e2e8f0;border-radius:6px;}
                .sp-sync-btn{padding:10px 20px;background:#10b981;color:#fff;border:none;border-radius:10px;cursor:pointer;font-weight:500;margin-top:12px;width:100%;}
                .sp-sync-btn:hover{background:#059669;}
                .sp-rules-list{border:1px solid #e2e8f0;border-radius:12px;padding:12px;background:#fff;max-height:180px;overflow-y:auto;min-height:100px;}
                .sp-rule{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:16px;margin:3px;font-size:12px;}
                .sp-rule.protect{background:#dbeafe;color:#1e40af;}
                .sp-rule.invalid{background:#fee2e2;color:#991b1b;}
                .sp-rule.pattern{background:#d1fae5;color:#065f46;}
                .sp-rule.asr{background:#fef3c7;color:#92400e;}
                .sp-rule .del{background:none;border:none;cursor:pointer;color:inherit;opacity:0.5;padding:0 2px;font-size:11px;}
                .sp-rule .del:hover{opacity:1;}
                .sp-add-row{display:flex;gap:6px;margin-top:10px;}
                .sp-add-row input{flex:1;padding:8px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;}
                .sp-add-row button{padding:8px 16px;border:none;border-radius:8px;cursor:pointer;font-weight:500;background:#3b82f6;color:#fff;}
                .sp-add-row button:hover{background:#2563eb;}
                .sp-asr-table{width:100%;border-collapse:collapse;font-size:12px;}
                .sp-asr-table th,.sp-asr-table td{padding:6px 8px;text-align:left;border-bottom:1px solid #e2e8f0;}
                .sp-asr-table th{background:#f1f5f9;font-weight:600;color:#64748b;}
                .sp-footer{display:flex;justify-content:space-between;align-items:center;margin-top:20px;padding-top:16px;border-top:1px solid #e2e8f0;}
                .sp-footer .info{color:#94a3b8;font-size:12px;}
                .sp-footer .btns{display:flex;gap:10px;}
                .sp-footer button{padding:10px 20px;border:none;border-radius:10px;cursor:pointer;font-weight:500;}
            </style>
            <h2>⚙️ 设置与规则</h2>
            <div class="sp-tabs">
                <button class="sp-tab active" data-tab="settings">⚡ 运行参数</button>
                <button class="sp-tab" data-tab="rules-protect">🔒 保护词</button>
                <button class="sp-tab" data-tab="rules-invalid">❌ 无效词</button>
                <button class="sp-tab" data-tab="rules-pattern">🧠 AI正则</button>
                <button class="sp-tab" data-tab="rules-asr">🎯 ASR修正</button>
            </div>

            <!-- 运行参数 -->
            <div class="sp-content active" data-content="settings">
                <div class="sp-grid">
                    <div class="sp-section">
                        <h4>⚡ 速度设置</h4>
                        <div class="sp-row"><label>条目间隔</label><input type="number" id="set-dItem" value="${Settings.localDelayItem}"> ms</div>
                        <div class="sp-row"><label>填充等待</label><input type="number" id="set-dFill" value="${Settings.localDelayFill}"> ms</div>
                        <div class="sp-row"><label>点击等待</label><input type="number" id="set-dClick" value="${Settings.localDelayClick}"> ms</div>
                        <button id="syncSpeedBtn" style="margin-top:10px;padding:6px 12px;background:#10b981;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:11px;">📥 同步后端设置</button>
                    </div>
                    <div class="sp-section">
                        <h4>🎯 自动化设置</h4>
                        <div class="sp-row"><label>自动勾选有效性</label><input type="checkbox" id="set-autoValid" ${Settings.autoSelectValid?'checked':''}></div>
                        <div class="sp-row"><label>手动无效清空</label><input type="checkbox" id="set-autoClear" ${Settings.autoClearOnInvalid?'checked':''}></div>
                        <div class="sp-row"><label>自动提交</label><input type="checkbox" id="set-autoSubmit" ${Settings.autoSubmit?'checked':''}></div>
                    </div>
                </div>
                <button id="syncServerBtn" class="sp-sync-btn">🔄 从服务器同步规则</button>
            </div>

            <!-- 保护词 -->
            <div class="sp-content" data-content="rules-protect">
                <p style="color:#64748b;margin-bottom:12px;font-size:13px;">保护词:转写时完全保留,不会进行任何转换</p>
                <div id="sp-protect-list" class="sp-rules-list"></div>
                <div class="sp-add-row">
                    <input type="text" id="sp-protect-input" placeholder="输入保护词...">
                    <button data-add="protect">添加</button>
                </div>
            </div>

            <!-- 无效关键词 -->
            <div class="sp-content" data-content="rules-invalid">
                <p style="color:#64748b;margin-bottom:12px;font-size:13px;">无效关键词:包含这些关键词的条目将被标记为无效</p>
                <div id="sp-invalid-list" class="sp-rules-list"></div>
                <div class="sp-add-row">
                    <input type="text" id="sp-invalid-input" placeholder="输入无效关键词...">
                    <button data-add="invalid">添加</button>
                </div>
            </div>

            <!-- 强AI正则 -->
            <div class="sp-content" data-content="rules-pattern">
                <p style="color:#64748b;margin-bottom:12px;font-size:13px;">强AI正则:匹配正则表达式的内容强制判定为AI生成</p>
                <div id="sp-pattern-list" class="sp-rules-list"></div>
                <div class="sp-add-row">
                    <input type="text" id="sp-pattern-input" placeholder="输入正则表达式...">
                    <button data-add="pattern">添加</button>
                </div>
            </div>

            <!-- ASR修正 -->
            <div class="sp-content" data-content="rules-asr">
                <p style="color:#64748b;margin-bottom:12px;font-size:13px;">ASR修正:将错误识别的语音文本自动修正为正确内容</p>
                <div style="max-height:200px;overflow-y:auto;">
                    <table class="sp-asr-table">
                        <thead><tr><th style="width:40%;">错误文本</th><th style="width:40%;">正确文本</th><th style="width:20%;"></th></tr></thead>
                        <tbody id="sp-asr-list"></tbody>
                    </table>
                </div>
                <div class="sp-add-row" style="margin-top:12px;">
                    <input type="text" id="sp-asr-wrong" placeholder="错误文本">
                    <span style="color:#94a3b8;">→</span>
                    <input type="text" id="sp-asr-correct" placeholder="正确文本">
                    <button data-add="asr">添加</button>
                </div>
            </div>

            <div class="sp-footer">
                <span class="info" id="sp-counts"></span>
                <div class="btns">
                    <button id="saveToServerBtn" style="background:#10b981;color:#fff;display:none;" title="仅管理员可用">☁️ 保存到服务器</button>
                    <button id="cancelSettingsBtn" style="background:#e2e8f0;color:#475569;">取消</button>
                    <button id="saveSettingsBtn" style="background:#3b82f6;color:#fff;">💾 保存</button>
                    <button id="logoutSettingsBtn" style="background:#ef4444;color:#fff;">退出登录</button>
                </div>
            </div>
        `;
        overlay.appendChild(panel);
        document.body.appendChild(overlay);
        overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });

        // 切换标签
        panel.querySelectorAll('.sp-tab').forEach(tab => {
            tab.addEventListener('click', () => {
                panel.querySelectorAll('.sp-tab').forEach(t => t.classList.remove('active'));
                panel.querySelectorAll('.sp-content').forEach(c => c.classList.remove('active'));
                tab.classList.add('active');
                panel.querySelector('[data-content="' + tab.dataset.tab + '"]').classList.add('active');
            });
        });

        // 渲染规则列表
        function renderProtect() {
            document.getElementById('sp-protect-list').innerHTML = RULES.protectWords.length ?
                RULES.protectWords.map(w => '<span class="sp-rule protect">' + escapeHtml(w) + '<button class="del" data-type="protect" data-word="' + escapeHtml(w) + '">✕</button></span>').join('') :
                '<p style="color:#94a3b8;text-align:center;padding:30px;">暂无保护词</p>';
        }
        function renderInvalid() {
            document.getElementById('sp-invalid-list').innerHTML = RULES.invalidKeywords.length ?
                RULES.invalidKeywords.map(w => '<span class="sp-rule invalid">' + escapeHtml(w) + '<button class="del" data-type="invalid" data-word="' + escapeHtml(w) + '">✕</button></span>').join('') :
                '<p style="color:#94a3b8;text-align:center;padding:30px;">暂无无效关键词</p>';
        }
        function renderPattern() {
            var list = document.getElementById('sp-pattern-list');
            if (!RULES.strongAIPatterns.length) {
                list.innerHTML = '<p style="color:#94a3b8;text-align:center;padding:30px;">暂无强AI正则</p>';
                return;
            }
            list.innerHTML = RULES.strongAIPatterns.map(function(p, i) {
                return '<span class="sp-rule pattern">' + escapeHtml(p.source) + '<button class="del" data-type="pattern" data-idx="' + i + '">✕</button></span>';
            }).join('');
        }
        function renderAsr() {
            const entries = Object.entries(RULES.asrFix);
            document.getElementById('sp-asr-list').innerHTML = entries.length ?
                entries.map(function(e){var w=e[0],c=e[1];return '<tr><td>'+escapeHtml(w)+'</td><td>'+escapeHtml(c)+'</td><td><button class="del" data-type="asr" data-wrong="'+escapeHtml(w)+'" style="color:#ef4444;background:none;border:none;cursor:pointer;padding:2px 6px;">删除</button></td></tr>';}).join('') :
                '<tr><td colspan="3" style="text-align:center;color:#94a3b8;padding:30px;">暂无ASR修正</td></tr>';
        }
        function updateCounts() {
            document.getElementById('sp-counts').textContent = '🔒' + RULES.protectWords.length + ' ❌' + RULES.invalidKeywords.length + ' 🧠' + RULES.strongAIPatterns.length + ' 🎯' + Object.keys(RULES.asrFix).length;
        }
        function renderAll() { renderProtect(); renderInvalid(); renderPattern(); renderAsr(); updateCounts(); }
        renderAll();

        // 添加规则
        panel.querySelectorAll('[data-add]').forEach(function(btn){
            btn.addEventListener('click', function(){
                var type = this.dataset.add;
                if (type === 'protect') {
                    var val = document.getElementById('sp-protect-input').value.trim();
                    if (val && RULES.protectWords.indexOf(val) === -1) { RULES.protectWords.push(val); renderProtect(); updateCounts(); document.getElementById('sp-protect-input').value = ''; }
                } else if (type === 'invalid') {
                    var val = document.getElementById('sp-invalid-input').value.trim();
                    if (val && RULES.invalidKeywords.indexOf(val) === -1) { RULES.invalidKeywords.push(val); renderInvalid(); updateCounts(); document.getElementById('sp-invalid-input').value = ''; }
                } else if (type === 'pattern') {
                    var val = document.getElementById('sp-pattern-input').value.trim();
                    if (val && !RULES.strongAIPatterns.some(function(p){return p.source===val;})) { RULES.strongAIPatterns.push(new RegExp(val)); renderPattern(); updateCounts(); document.getElementById('sp-pattern-input').value = ''; }
                } else if (type === 'asr') {
                    var wrong = document.getElementById('sp-asr-wrong').value.trim();
                    var correct = document.getElementById('sp-asr-correct').value.trim();
                    if (wrong && correct) { RULES.asrFix[wrong] = correct; renderAsr(); updateCounts(); document.getElementById('sp-asr-wrong').value = ''; document.getElementById('sp-asr-correct').value = ''; }
                }
            });
        });


        // 删除规则
        panel.addEventListener('click', function(e){
            var btn = e.target.closest('.del');
            if (!btn) return;
            var type = btn.dataset.type;
            if (type === 'protect') RULES.protectWords = RULES.protectWords.filter(function(w){return w !== btn.dataset.word;});
            else if (type === 'invalid') RULES.invalidKeywords = RULES.invalidKeywords.filter(function(w){return w !== btn.dataset.word;});
            else if (type === 'pattern') {
                var idx = parseInt(btn.dataset.idx, 10);
                if (!isNaN(idx)) RULES.strongAIPatterns.splice(idx, 1);
            }
            else if (type === 'asr') delete RULES.asrFix[btn.dataset.wrong];
            renderAll();
        });

        // 从服务器同步规则和速度设置
        document.getElementById('syncServerBtn').addEventListener('click', async function(){
            if (!backendOk) { alert('后端未连接'); return; }
            try {
                var resp = await new Promise(function(res){GM_xmlhttpRequest({method:'GET',url:API+'?action=get_full_runtime_config',onload:function(r){try{res(JSON.parse(r.responseText));}catch{res({success:false});}},onerror:function(){res({success:false});}});});
                if (!resp.success || !resp.config) { alert('同步失败'); return; }
                var cfg = resp.config;
                RULES.protectWords = cfg.protect_words || [];
                RULES.asrFix = cfg.asr_fix || {};
                RULES.strongAIPatterns = (cfg.strong_ai_patterns || []).map(function(p){return new RegExp(p);});
                RULES.invalidKeywords = cfg.invalid_keywords || [];
                // 同步速度设置
                if (cfg.delay_item) { Settings.localDelayItem = cfg.delay_item; document.getElementById('set-dItem').value = cfg.delay_item; }
                if (cfg.delay_fill) { Settings.localDelayFill = cfg.delay_fill; document.getElementById('set-dFill').value = cfg.delay_fill; }
                if (cfg.delay_click) { Settings.localDelayClick = cfg.delay_click; document.getElementById('set-dClick').value = cfg.delay_click; }
                renderAll();
                alert('已同步服务器规则与速度设置');
            } catch(e) { alert('同步出错'); }
        });

        // 单独同步速度设置
        document.getElementById('syncSpeedBtn').addEventListener('click', async function(){
            if (!backendOk) { alert('后端未连接'); return; }
            try {
                var resp = await new Promise(function(res){GM_xmlhttpRequest({method:'GET',url:API+'?action=get_speed_config',onload:function(r){try{res(JSON.parse(r.responseText));}catch{res({success:false});}},onerror:function(){res({success:false});}});});
                if (!resp.success || !resp.config) { alert('同步速度设置失败'); return; }
                Settings.localDelayItem = resp.config.delay_item || Settings.localDelayItem;
                Settings.localDelayFill = resp.config.delay_fill || Settings.localDelayFill;
                Settings.localDelayClick = resp.config.delay_click || Settings.localDelayClick;
                document.getElementById('set-dItem').value = Settings.localDelayItem;
                document.getElementById('set-dFill').value = Settings.localDelayFill;
                document.getElementById('set-dClick').value = Settings.localDelayClick;
                alert('速度设置已同步');
            } catch(e) { alert('同步出错'); }
        });

        // 保存
        document.getElementById('cancelSettingsBtn').addEventListener('click', function(){overlay.remove();});

        const dItemInput = document.getElementById('set-dItem');
        const dFillInput = document.getElementById('set-dFill');
        const dClickInput = document.getElementById('set-dClick');

        document.getElementById('saveSettingsBtn').addEventListener('click', function(){
            Settings.localDelayItem = +dItemInput.value;
            Settings.localDelayFill = +dFillInput.value;
            Settings.localDelayClick = +dClickInput.value;
            Settings.autoSelectValid = document.getElementById('set-autoValid').checked;
            Settings.autoClearOnInvalid = document.getElementById('set-autoClear').checked;
            Settings.autoSubmit = document.getElementById('set-autoSubmit').checked;
            saveSettings();
            saveRulesToStorage();
            overlay.remove();
            alert('设置与规则已保存');
        });

        // 管理员保存到服务器按钮
        if (isAdmin) {
            document.getElementById('saveToServerBtn').style.display = 'block';
        }
        document.getElementById('saveToServerBtn').addEventListener('click', async function(){
            await saveRulesToServer();
        });
        document.getElementById('logoutSettingsBtn').addEventListener('click', function(){localStorage.clear();location.reload();});
    }

    // ---------- 反馈面板 ----------
    function showFeedbackPanel(itemIdx) {
        const item = items[itemIdx];
        const data = extractItemData(item, itemIdx);
        if (!data) return;
        const overlay=document.createElement('div'); overlay.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,0.4);z-index:100004;display:flex;align-items:center;justify-content:center;';
        const panel=document.createElement('div'); panel.style.cssText='background:#fff;border-radius:16px;padding:24px;width:580px;max-height:80vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,0.3);';
        panel.innerHTML = `
            <h2>💬 提交反馈</h2>
            <p><strong>原始文本:</strong> ${escapeHtml(data.originalText)}</p>
            <p><strong>当前转写:</strong> ${escapeHtml(data.textarea.value)}</p>
            <label>正确文本(选填):</label>
            <textarea id="fbCorrect" rows="2" style="width:100%; padding:8px; border-radius:8px; border:1px solid #ccc; margin-bottom:10px;"></textarea>
            <label>错误类型:</label>
            <select id="fbErrorType" style="width:100%; padding:8px; border-radius:8px; border:1px solid #ccc; margin-bottom:10px;">
                <option value="数字转换错误">数字转换错误</option><option value="日期转换错误">日期转换错误</option><option value="AI误判">AI误判</option><option value="标点错误">标点错误</option><option value="其他">其他</option>
            </select>
            <label>补充说明(选填):</label>
            <textarea id="fbNote" rows="2" style="width:100%; padding:8px; border-radius:8px; border:1px solid #ccc; margin-bottom:15px;"></textarea>
            <button id="fbSubmit" style="padding:10px 20px; background:#3b82f6; color:#fff; border:none; border-radius:8px; cursor:pointer;">提交反馈</button>
            <hr style="margin:20px 0;">
            <h3>📜 近期反馈</h3>
            <div id="fbHistory" style="max-height:200px; overflow-y:auto;">加载中...</div>
        `;
        overlay.appendChild(panel);
        document.body.appendChild(overlay);
        overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
        GM_xmlhttpRequest({
            method: 'GET', url: API + '?action=get_my_feedback&limit=5', headers: {'X-Auth-Token': token},
            onload: r => {
                try {
                    const j = JSON.parse(r.responseText);
                    const historyDiv = document.getElementById('fbHistory');
                    if (j.success && j.feedback.length) {
                        historyDiv.innerHTML = j.feedback.map(f=>`<div style="border-bottom:1px solid #eee;padding:8px 0;"><b>${f.error_type}</b> ${f.created_at}<br>${f.original_text}<br>${f.status==='已修正'?'✅ 已修正':(f.admin_reply||'⏳ 待处理')}</div>`).join('');
                    } else historyDiv.innerHTML = '<div style="color:#999;">暂无反馈记录</div>';
                } catch(e) {}
            }
        });
        document.getElementById('fbSubmit').onclick = () => {
            const correct = document.getElementById('fbCorrect').value.trim();
            const errorType = document.getElementById('fbErrorType').value;
            const note = document.getElementById('fbNote').value.trim();
            const submitBtn = document.getElementById('fbSubmit');
            submitBtn.disabled = true;
            submitBtn.textContent = '提交中...';
            GM_xmlhttpRequest({
                method: 'POST', url: API + '?action=submit_feedback',
                data: JSON.stringify({
                    original_text: data.originalText,
                    transcribed_text: data.textarea.value,
                    correct_text: correct,
                    error_type: errorType,
                    user_is_valid: curValid ? 1 : 0,
                    system_is_valid: curValid ? 1 : 0,
                    note: note
                }),
                headers: {'Content-Type':'application/json','X-Auth-Token':token},
                onload: r => {
                    try {
                        const resp = JSON.parse(r.responseText);
                        if (resp.success) {
                            alert('反馈已提交,感谢您的反馈!');
                        } else {
                            alert('提交失败: ' + (resp.message || '未知错误'));
                            submitBtn.disabled = false;
                            submitBtn.textContent = '提交反馈';
                            return;
                        }
                    } catch(e) {
                        alert('提交失败: 响应解析错误');
                        submitBtn.disabled = false;
                        submitBtn.textContent = '提交反馈';
                        return;
                    }
                    overlay.remove();
                },
                onerror: () => {
                    alert('提交失败: 网络错误');
                    submitBtn.disabled = false;
                    submitBtn.textContent = '提交反馈';
                }
            });
        };
    }

    function initManualClear() {
        document.body.addEventListener('change', e => {
            if(!e.isTrusted||e.target.type!=='radio'||!Settings.autoClearOnInvalid) return;
            const label=e.target.closest('label.ant-v5-radio-wrapper');
            if(!label) return;
            const span=label.querySelector('.ant-v5-radio-label');
            if(!span||span.innerText.trim()!=='无效') return;
            const item=e.target.closest('.labelRender-item');
            if(!item) return;
            let ta=null;
            for(let w of item.querySelectorAll('.labelRender-item-answer-wrap')) {
                const ti=w.querySelector('.labelRender-item-answer-title');
                if(ti&&ti.innerText.includes('转写文本')) { ta=w.querySelector('textarea.ant-v5-input'); break; }
            }
            if(ta) setTextarea(ta,'');
        });
    }

    // ---------- 初始化(修复登录持久化) ----------
    async function init() {
        loadSettings();
        loadRules();
        try {
            const resp=await new Promise((res,rej)=>GM_xmlhttpRequest({ method:'GET', url:API+'?action=ping', onload:res, onerror:rej }));
            backendOk=JSON.parse(resp.responseText).success===true;
        } catch(e) { backendOk=false; }
        if (backendOk && token) {
            // 验证 token 有效性(调用一个轻量接口)
            try {
                const validResp = await new Promise(res=> GM_xmlhttpRequest({
                    method:'GET', url:API+'?action=check_quota', headers:{'X-Auth-Token':token},
                    onload: r=>{ try{ res(JSON.parse(r.responseText)); }catch{ res({success:false}); } },
                    onerror: ()=> res({success:false})
                }));
                if (validResp.success) {
                    loggedIn = true;
                    user = { username: savedUser };
                    // 检查是否为管理员
                    checkAdminStatus();
                    showMainPanel();
                    return;
                }
            } catch(e) {}
            // token 无效,清除
            localStorage.removeItem(STORAGE_TOKEN);
            localStorage.removeItem(STORAGE_USER);
            token = '';
        }
        showLoginPanel();
    }
    init();
})();

增加登陆系统,接入数据库,新增后台控制关键词减少前端代码

优化启动逻辑,优化前端面板

📋 规则总览与示例

规则类别具体规则示例输入输出/判定
AI无效过滤长关键词(≥5字)您拨打的电话已关机❌ 无效
短关键词(<5字)重录秦案2、空号❌ 无效(直接命中)
纯问候语(≤5字)你好、在吗❌ 无效
强AI模板(正则)您拨叫的用户正忙、请在滴声后留言❌ 无效
引导指令评分按1转接,按井号重录❌ 无效(得分≥5)
数字转换整数金额 → 纯数字十四万八千148000
金额带角分 → 补零保留单位十四万八千块零六毛六148000块06毛06分
日期时间二零二五年十月三号十一点二十五分2025年10月3号11点25分
小数(前后数字)三点一四3.14
范围三到五个3~5个
电话/编码(逐位)幺五九杠八八八159-888
百分数百分之二十20%
分数三分之二2/3
大数(≥1万)五万一千51000(整数)或 5万1000(如带单位)
绝对保护一/二/两 + 量词一个、两条、两款原样保留
常见成语/专名一心一意、三国演义原样保留
标点/空格合并字母间空格a p papp
英文标点转中文hello.hello。
疑问句自动加问号是吗是吗?
隐藏内容,输入密码后查看
4399

© 版权声明
THE END
喜欢就支持一下吧
点赞1 分享
评论 共2条

请登录后发表评论