Greasy Fork

来自缓存

Greasy Fork is available in English.

Telegram 输入框翻译并发送

修复版:支持发送按钮点击翻译,支持图片/文件发送弹窗中的中文翻译,检测到中文时自动触发翻译而非报错,增强防快发保护

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Telegram 输入框翻译并发送 
// @namespace    http://tampermonkey.net/
// @version      4.4
// @description  修复版:支持发送按钮点击翻译,支持图片/文件发送弹窗中的中文翻译,检测到中文时自动触发翻译而非报错,增强防快发保护
// @author       Your Name / AI Assistant
// @match        https://web.telegram.org/k/*
// @match        https://web.telegram.org/a/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      api.lhfcb.com
// ==/UserScript==

(function () {
    'use strict';

    console.log('=== Telegram 翻译脚本 v3.3.2 修复版加载 ===');

    // --- 常量定义 ---
    const SCRIPT_VERSION = '3.3.2';
    const OHMYGPT_API_KEY = "sk-1zm9YotucF60cHkzLgf9fDHHU9qAGAqzwGv4N7cLkJVfl0rU";
    const OHMYGPT_API_ENDPOINT = "https://api.lhfcb.com/v1/chat/completions";
    const INPUT_TRANSLATE_MODEL = "gemini-2.5-flash-nothinking";

    const STORAGE_KEY_PREFIX = 'tg_translate_v3_';
    const STORAGE_KEY_API_ENDPOINT = STORAGE_KEY_PREFIX + 'api_endpoint';
    const STORAGE_KEY_API_KEY = STORAGE_KEY_PREFIX + 'api_key';
    const STORAGE_KEY_MODEL = STORAGE_KEY_PREFIX + 'model';
    const STORAGE_KEY_MODE = STORAGE_KEY_PREFIX + 'mode';
    const STORAGE_KEY_AUTOSEND = STORAGE_KEY_PREFIX + 'autosend';

    const MODE_ABBREVIATED = 'abbr';
    const MODE_STANDARD = 'std';

    // 选择器定义
    const INPUT_AREA_CONTAINER_SELECTOR = '.chat-input-main'; // 确保只匹配聊天输入区域
    const INPUT_SELECTOR = '.input-message-input';
    const SEND_BUTTON_SELECTOR = '.btn-send';
    
    // 图片/文件发送弹窗选择器
    const POPUP_CONTAINER_SELECTOR = '.popup-send-photo, .popup-new-media';
    const POPUP_INPUT_SELECTOR = '.popup-send-photo .input-message-input, .popup-new-media .input-message-input';
    const POPUP_SEND_BUTTON_SELECTOR = '.popup-send-photo .btn-primary, .popup-new-media .btn-primary, .popup-send-photo .btn-send, .popup-new-media .btn-send';
    
    // 辅助函数:判断输入框是否在弹窗中
    function isInPopup(element) {
        return element && element.closest(POPUP_CONTAINER_SELECTOR);
    }
    
    // 辅助函数:获取对应的发送按钮
    function getSendButton(inputElement) {
        if (isInPopup(inputElement)) {
            // 弹窗中的发送按钮
            const popup = inputElement.closest(POPUP_CONTAINER_SELECTOR);
            return popup ? popup.querySelector('.btn-primary, .btn-send, button.btn-primary') : null;
        }
        return document.querySelector(SEND_BUTTON_SELECTOR);
    }

    // UI ID 定义
    const FLOATING_BUTTON_ID = 'tg-trans-float-btn';
    const SETTINGS_PANEL_ID = 'tg-trans-settings-panel';
    const STATUS_BAR_ID = 'tg-trans-status-bar';
    const RETRY_BUTTON_ID = 'tg-trans-retry-btn';
    const RETRY_PROCESSING_BUTTON_ID = 'tg-trans-retry-proc-btn';
    const MODE_BUTTON_ABBR_ID = 'tg-trans-mode-abbr';
    const MODE_BUTTON_STD_ID = 'tg-trans-mode-std';
    const AUTO_SEND_TOGGLE_ID = 'tg-trans-autosend-toggle';
    const CONTROLS_CONTAINER_ID = 'tg-trans-controls';

    // 正则表达式
    const BURMESE_REGEX = /[\u1000-\u109F]/;
    const CHINESE_REGEX = /[\u4e00-\u9fa5]/;

    // --- << MODIFIED v4.1 >> 翻译提示词 (Parser-Safe Version) ---
    // 基础翻译提示词 (用于API调用,缩写模式在此基础上进行脚本后处理)
    const TRANSLATION_PROMPT = `Role: Expert Linguist & Cultural Consultant
Task: Translate the following Chinese text into high-fidelity, natural-sounding American English, adhering to a strict set of principles and rules.

// --- Core Principles for High-Quality Translation ---
// Before executing, internalize these guiding principles for handling Chinese.

1.  Context-First, Anti-Literalism: Chinese is a high-context language. First, understand the complete intent, subtext, and logic of the source text. Your primary goal is to translate this *meaning*, not just the individual words.

2.  Master Sentence Restructuring: Actively transform Chinese sentence structures into idiomatic English.
*   Topic-Comment -> SVO: Convert phrases like 'zhe jian yifu wo xihuan' (literally "this clothing, I like") into "I like this piece of clothing."
*   Add Implied Subjects: Chinese often omits subjects. Infer and add them. For example, 'zhi dao le' becomes "I understand" or "Got it."
*   Bridge Logical Gaps: Chinese uses implicit logic (known as 'yihe'). Add English conjunctions (because, so, while, which) to make the relationship between clauses explicit and clear.

3.  Translate Cultural Nuance, Not Just Words:
*   Idioms ('chengyu'): Translate the underlying meaning. The idiom 'hua she tian zu' should become "to gild the lily" or "to do something superfluous," not "draw a snake and add feet."
*   Polite Phrases ('ketaohua'): Translate the social function. 'Xin ku le' is not "you've worked hard"; it's "Thank you for your hard work" or "I appreciate your efforts." 'Mafan ni le' is "Thank you for your help" or "Sorry to bother you."
*   Formality: Distinguish between the formal 'you' (pinyin: nin) and the casual 'you' (pinyin: ni) by adjusting the English tone (e.g., using "sir/ma'am," more structured sentences vs. a relaxed style).

// --- Strict Execution Rules ---
// After considering the principles above, apply these rules without deviation.

1.  Language Detection: Prioritize translating Chinese text. If mixed with untranslatable content (code, IDs, numbers, proper nouns), translate the Chinese parts and integrate the rest seamlessly into the English sentence.
2.  Formality: Maintain the original text's level of formality or informality as determined by the principles above.
3.  Untranslatable Content: If parts of the input are untranslatable, keep them as they are within the translated sentence structure.
4.  Fully Untranslatable Input: If the entire input is untranslatable (e.g., just numbers, code, already valid English, emojis only), return the original text unmodified.
5.  Punctuation: DO NOT add a period (.) at the end of the translated sentence. However, if the original text ends with a different punctuation mark like a question mark (?) or an exclamation mark (!), you MUST preserve it. Ensure correct spacing.
6.  Output: Return ONLY the final translated text. NO explanations, NO notes, NO apologies, NO introductory phrases like "Here is the translation:". Just the resulting text.

Input Text:
{text_to_translate}`;

    const MAX_CACHE_SIZE = 100;
    const translationCache = new Map();

    // --- 状态变量 ---
    let isPanelOpen = false;
    let autoSendEnabled = localStorage.getItem(STORAGE_KEY_AUTOSEND) === 'true';
    let currentTranslationMode = localStorage.getItem(STORAGE_KEY_MODE) || MODE_ABBREVIATED;
    let lastOriginalText = '';
    let isTranslatingAndSending = false;
    let allowNextSend = false; // 新增:标记是否允许下一次发送
    let justTranslated = false;
    let currentInputApiXhr = null;
    let floatingButtonElement = null;
    let settingsPanelElement = null;
    let statusBarElement = null;

    // --- API 配置状态 ---
    let currentApiEndpoint = localStorage.getItem(STORAGE_KEY_API_ENDPOINT) || OHMYGPT_API_ENDPOINT;
    let currentApiKey = localStorage.getItem(STORAGE_KEY_API_KEY) || OHMYGPT_API_KEY;
    let currentApiModel = localStorage.getItem(STORAGE_KEY_MODEL) || INPUT_TRANSLATE_MODEL;

    console.log(`[输入翻译 v${SCRIPT_VERSION}] 加载配置: Model=${currentApiModel}, Endpoint=${currentApiEndpoint}`);

    // --- CSS 样式 ---
    GM_addStyle(`
    ${INPUT_AREA_CONTAINER_SELECTOR} { position: relative !important; overflow: visible !important; }
    
    /* 悬浮设置按钮 */
    #${FLOATING_BUTTON_ID} {
        position: fixed; bottom: 80px; right: 20px; width: 48px; height: 48px;
        border-radius: 50%; background: linear-gradient(135deg, #5e72e4, #825ee4);
        color: #fff; border: none; cursor: move; box-shadow: 0 4px 12px rgba(94, 114, 228, 0.4);
        z-index: 9999; display: flex; align-items: center; justify-content: center;
        font-size: 20px; font-weight: bold; transition: box-shadow 0.3s ease;
        user-select: none; touch-action: none;
    }
    #${FLOATING_BUTTON_ID}:hover { box-shadow: 0 6px 16px rgba(94, 114, 228, 0.5); }
    #${FLOATING_BUTTON_ID}.dragging { transition: none; box-shadow: 0 8px 20px rgba(94, 114, 228, 0.6); }
    #${FLOATING_BUTTON_ID}.panel-open { background: linear-gradient(135deg, #ff6b6b, #ee5a52); }
    
    /* 设置面板 */
    #${SETTINGS_PANEL_ID} {
        position: fixed; width: 280px;
        background: linear-gradient(135deg, rgba(30, 30, 35, 0.98), rgba(40, 40, 45, 0.98));
        backdrop-filter: blur(12px); border: 1px solid rgba(255, 255, 255, 0.15);
        border-radius: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
        z-index: 9998; padding: 16px; display: none; opacity: 0;
        transform: scale(0.95); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    }
    #${SETTINGS_PANEL_ID}.visible { display: block; opacity: 1; transform: scale(1); }
    #${SETTINGS_PANEL_ID} .panel-title { font-size: 16px; font-weight: 700; color: #e8eaed; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
    #${SETTINGS_PANEL_ID} .setting-group { margin-bottom: 16px; }
    #${SETTINGS_PANEL_ID} .setting-label { font-size: 13px; font-weight: 600; color: #b8bdc4; margin-bottom: 8px; display: block; }
    #${SETTINGS_PANEL_ID} .button-group { display: flex; gap: 8px; }
    #${SETTINGS_PANEL_ID} .setting-button {
        flex: 1; padding: 8px 12px; font-size: 13px; font-weight: 600; color: #b8bdc4;
        background: rgba(60, 60, 70, 0.6); border: 1px solid rgba(255, 255, 255, 0.1);
        border-radius: 8px; cursor: pointer; transition: all 0.2s ease; text-align: center;
    }
    #${SETTINGS_PANEL_ID} .setting-button:hover { background: rgba(80, 80, 90, 0.8); border-color: rgba(255, 255, 255, 0.2); }
    #${SETTINGS_PANEL_ID} .setting-button.active { background: linear-gradient(135deg, #5e72e4, #825ee4); color: #fff; border-color: transparent; box-shadow: 0 2px 8px rgba(94, 114, 228, 0.3); }
    #${SETTINGS_PANEL_ID} .toggle-button {
        width: 100%; padding: 10px 16px; font-size: 14px; font-weight: 600;
        border-radius: 8px; cursor: pointer; transition: all 0.2s ease; text-align: center;
        border: 1px solid rgba(255, 255, 255, 0.1);
    }
    #${SETTINGS_PANEL_ID} .toggle-button.off { background: rgba(60, 60, 70, 0.6); color: #b8bdc4; }
    #${SETTINGS_PANEL_ID} .toggle-button.on { background: linear-gradient(135deg, #51cf66, #37b24d); color: #fff; border-color: transparent; box-shadow: 0 2px 8px rgba(81, 207, 102, 0.3); }
    #${SETTINGS_PANEL_ID} .toggle-button:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); }
    
    /* 状态栏 - 移到输入框上方 */
    #${STATUS_BAR_ID} { 
        position: absolute; top: -40px; left: 8px; right: 8px; display: none; 
        padding: 6px 12px; font-size: 12px; color: #e8eaed; 
        background: linear-gradient(135deg, rgba(30, 30, 35, 0.95), rgba(40, 40, 45, 0.95)); 
        backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.15); 
        border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); 
        z-index: 149; line-height: 1.4; text-align: left; 
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 
        opacity: 0; pointer-events: none; transform: translateY(-5px);
    }
    #${STATUS_BAR_ID}.visible { display: flex; justify-content: space-between; align-items: center; opacity: 1; pointer-events: auto; transform: translateY(0); }
    #${STATUS_BAR_ID} .status-text { flex-grow: 1; margin-right: 10px; font-weight: 500; }
    #${STATUS_BAR_ID} .status-buttons { display: flex; gap: 6px; flex-shrink: 0; }
    #${STATUS_BAR_ID} .status { font-style: italic; color: #b8bdc4; }
    #${STATUS_BAR_ID} .info { font-style: italic; color: #64b5f6; font-weight: 600; }
    #${STATUS_BAR_ID} .error { font-weight: 600; color: #ff6b6b; }
    #${STATUS_BAR_ID} .success { font-weight: 600; color: #51cf66; }
    
    /* 重试按钮 */
    #${RETRY_BUTTON_ID}, #${RETRY_PROCESSING_BUTTON_ID} { 
        padding: 4px 10px; font-size: 11px; font-weight: 600; color: #e8eaed; 
        background: linear-gradient(135deg, rgba(70, 70, 80, 0.9), rgba(60, 60, 70, 0.9)); 
        border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 5px; cursor: pointer; 
        flex-shrink: 0; transition: all 0.2s ease; white-space: nowrap; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }
    #${RETRY_BUTTON_ID}:hover, #${RETRY_PROCESSING_BUTTON_ID}:hover { background: linear-gradient(135deg, rgba(90, 90, 100, 0.95), rgba(80, 80, 90, 0.95)); transform: translateY(-1px); box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3); }
    #${RETRY_BUTTON_ID}:active, #${RETRY_PROCESSING_BUTTON_ID}:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); }
    `);

    // --- 辅助函数 ---
    function detectLanguage(text) {
        if (!text || text.trim().length === 0) return null;
        const trimmedText = text.trim();
        if (BURMESE_REGEX.test(trimmedText)) return 'Burmese';
        if (CHINESE_REGEX.test(trimmedText)) return 'Chinese';
        return 'Other';
    }
    function setCursorToEnd(element) { try { const range = document.createRange(); const sel = window.getSelection(); range.selectNodeContents(element); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); element.focus(); } catch (e) { console.warn(`[输入翻译 v${SCRIPT_VERSION}] 设置光标时出错:`, e); } }

    // 标记是否是脚本触发的发送
    let isScriptTriggeredSend = false;

    // 安全发送消息 - 使用 Telegram 的内部 API
    // isForceOriginal: 是否强制发送原文(用于 Ctrl+Enter)
    function safeSendMessage(inputElement, isForceOriginal = false) {
        if (!inputElement) return false;

        // 最后一道防线:检查是否包含中文/缅甸文
        const currentText = inputElement.textContent?.trim() || '';
        const detectedLang = detectLanguage(currentText);
        
        // 检查是否在弹窗中
        const inPopup = isInPopup(inputElement);
        
        // 如果不是强制发送原文,且不是通过allowNextSend标记允许的发送,且包含中文/缅甸文
        if (!isForceOriginal && !allowNextSend && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) {
            console.warn(`[输入翻译 v${SCRIPT_VERSION}] 检测到中文/缅甸文,自动触发翻译: "${currentText}"${inPopup ? ' (弹窗)' : ''}`);
            // 不显示错误,而是自动触发翻译
            const sendButton = getSendButton(inputElement);
            if (sendButton && currentText) {
                translateAndSend(currentText, inputElement, sendButton);
            }
            return false;
        }

        try {
            isScriptTriggeredSend = true;
            allowNextSend = false; // 重置标记
            console.log(`[输入翻译 v${SCRIPT_VERSION}] 尝试发送消息${isForceOriginal ? ' (强制发送原文)' : ''}${inPopup ? ' (弹窗)' : ''}`);

            // 1. 确保文本已更新并触发 input 事件
            inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));

            // 2. 模拟回车键发送
            const sendKeyEvent = (type) => {
                const event = new KeyboardEvent(type, {
                    key: 'Enter', code: 'Enter', keyCode: 13, which: 13,
                    bubbles: true, cancelable: true, composed: true, isTrusted: false
                });
                Object.defineProperty(event, '_scriptTriggered', { value: true });
                return event;
            };

            setTimeout(() => {
                inputElement.dispatchEvent(sendKeyEvent('keydown'));
                inputElement.dispatchEvent(sendKeyEvent('keypress'));
                inputElement.dispatchEvent(sendKeyEvent('keyup'));
                console.log(`[输入翻译 v${SCRIPT_VERSION}] 已触发键盘事件序列`);
                setTimeout(() => { isScriptTriggeredSend = false; }, 300);
            }, 100); // 延迟 100ms 确保 input 事件被处理

            return true;
        } catch (e) {
            console.error(`[输入翻译 v${SCRIPT_VERSION}] 发送消息失败:`, e);
            isScriptTriggeredSend = false;
            allowNextSend = false;
            return false;
        }
    }

    // 拖动相关变量
    let isDragging = false;
    let dragStartX = 0;
    let dragStartY = 0;
    let buttonStartX = 0;
    let buttonStartY = 0;

    function ensureControlsExist() {
        const inputMainContainer = document.querySelector(INPUT_AREA_CONTAINER_SELECTOR);
        if (!inputMainContainer) return;
        if (window.getComputedStyle(inputMainContainer).position !== 'relative') { inputMainContainer.style.position = 'relative'; }

        // 创建悬浮按钮
        if (!floatingButtonElement || !document.body.contains(floatingButtonElement)) {
            floatingButtonElement = document.createElement('button');
            floatingButtonElement.id = FLOATING_BUTTON_ID;
            floatingButtonElement.type = 'button';
            floatingButtonElement.innerHTML = '⚙️';
            floatingButtonElement.title = '翻译设置 (可拖动)';
            floatingButtonElement.addEventListener('mousedown', startDrag);
            floatingButtonElement.addEventListener('touchstart', startDrag, { passive: false });
            document.body.appendChild(floatingButtonElement);
        }

        // 创建设置面板
        if (!settingsPanelElement || !document.body.contains(settingsPanelElement)) {
            settingsPanelElement = document.createElement('div');
            settingsPanelElement.id = SETTINGS_PANEL_ID;
            settingsPanelElement.innerHTML = `
                <div class="panel-title">翻译设置</div>
                <div class="setting-group">
                    <label class="setting-label">API 设置</label>
                    <input type="text" id="custom-api-endpoint" class="setting-input" placeholder="API Endpoint" value="${currentApiEndpoint}">
                    <!-- 修改:使用 type="text" 并添加 autocomplete="off" 防止密码管理器自动填充 -->
                    <input type="text" id="custom-api-key" class="setting-input" placeholder="API Key" value="${currentApiKey}" autocomplete="off" data-lpignore="true">
                    <input type="text" id="custom-api-model" class="setting-input" placeholder="Model Name" value="${currentApiModel}">
                </div>
                <div class="setting-group">
                    <label class="setting-label">翻译模式</label>
                    <div class="button-group">
                        <button class="setting-button" id="${MODE_BUTTON_ABBR_ID}" data-mode="${MODE_ABBREVIATED}">缩写</button>
                        <button class="setting-button" id="${MODE_BUTTON_STD_ID}" data-mode="${MODE_STANDARD}">标准</button>
                    </div>
                </div>
                <div class="setting-group">
                    <label class="setting-label">自动发送</label>
                    <button class="toggle-button" id="${AUTO_SEND_TOGGLE_ID}">自动发送: 开启</button>
                </div>
                <style>
                    .setting-input {
                        width: 100%; padding: 8px; margin-bottom: 8px;
                        background: rgba(60, 60, 70, 0.6); border: 1px solid rgba(255, 255, 255, 0.1);
                        border-radius: 6px; color: #e8eaed; font-size: 12px;
                        box-sizing: border-box;
                    }
                    .setting-input:focus { border-color: #5e72e4; outline: none; }
                </style>
            `;
            document.body.appendChild(settingsPanelElement);

            const abbrBtn = settingsPanelElement.querySelector(`#${MODE_BUTTON_ABBR_ID}`);
            const stdBtn = settingsPanelElement.querySelector(`#${MODE_BUTTON_STD_ID}`);
            const autoSendBtn = settingsPanelElement.querySelector(`#${AUTO_SEND_TOGGLE_ID}`);

            const endpointInput = settingsPanelElement.querySelector('#custom-api-endpoint');
            const keyInput = settingsPanelElement.querySelector('#custom-api-key');
            const modelInput = settingsPanelElement.querySelector('#custom-api-model');

            if (abbrBtn) abbrBtn.addEventListener('click', () => switchMode(MODE_ABBREVIATED));
            if (stdBtn) stdBtn.addEventListener('click', () => switchMode(MODE_STANDARD));
            if (autoSendBtn) autoSendBtn.addEventListener('click', toggleAutoSend);

            if (endpointInput) endpointInput.addEventListener('change', (e) => {
                currentApiEndpoint = e.target.value.trim();
                localStorage.setItem(STORAGE_KEY_API_ENDPOINT, currentApiEndpoint);
                updateStatusDisplay('API Endpoint 已保存', 'success', 2000);
            });
            if (keyInput) keyInput.addEventListener('change', (e) => {
                currentApiKey = e.target.value.trim();
                localStorage.setItem(STORAGE_KEY_API_KEY, currentApiKey);
                updateStatusDisplay('API Key 已保存', 'success', 2000);
            });
            if (modelInput) modelInput.addEventListener('change', (e) => {
                currentApiModel = e.target.value.trim();
                localStorage.setItem(STORAGE_KEY_MODEL, currentApiModel);
                updateStatusDisplay('Model Name 已保存', 'success', 2000);
            });

            updateModeButtonVisuals();
            updateAutoSendButtonVisual();
        }

        // 创建状态栏
        if (!statusBarElement || !inputMainContainer.contains(statusBarElement)) {
            statusBarElement = document.createElement('div');
            statusBarElement.id = STATUS_BAR_ID;
            inputMainContainer.appendChild(statusBarElement);
        }
    }

    // 拖动功能
    function startDrag(e) {
        e.preventDefault();
        isDragging = true;
        const touch = e.touches ? e.touches[0] : e;
        dragStartX = touch.clientX;
        dragStartY = touch.clientY;
        const rect = floatingButtonElement.getBoundingClientRect();
        buttonStartX = rect.left;
        buttonStartY = rect.top;
        floatingButtonElement.classList.add('dragging');
        document.addEventListener('mousemove', onDrag);
        document.addEventListener('touchmove', onDrag, { passive: false });
        document.addEventListener('mouseup', stopDrag);
        document.addEventListener('touchend', stopDrag);
    }

    function onDrag(e) {
        if (!isDragging) return;
        e.preventDefault();
        const touch = e.touches ? e.touches[0] : e;
        const deltaX = touch.clientX - dragStartX;
        const deltaY = touch.clientY - dragStartY;
        const newX = buttonStartX + deltaX;
        const newY = buttonStartY + deltaY;
        floatingButtonElement.style.left = newX + 'px';
        floatingButtonElement.style.top = newY + 'px';
        floatingButtonElement.style.right = 'auto';
        floatingButtonElement.style.bottom = 'auto';
        updatePanelPosition();
    }

    function stopDrag(e) {
        if (!isDragging) return;
        isDragging = false;
        floatingButtonElement.classList.remove('dragging');
        document.removeEventListener('mousemove', onDrag);
        document.removeEventListener('touchmove', onDrag);
        document.removeEventListener('mouseup', stopDrag);
        document.removeEventListener('touchend', stopDrag);
        const touch = e.changedTouches ? e.changedTouches[0] : e;
        const deltaX = Math.abs(touch.clientX - dragStartX);
        const deltaY = Math.abs(touch.clientY - dragStartY);
        if (deltaX < 5 && deltaY < 5) { toggleSettingsPanel(); }
    }

    function updatePanelPosition() {
        if (!settingsPanelElement || !floatingButtonElement) return;
        const buttonRect = floatingButtonElement.getBoundingClientRect();
        const panelWidth = 280;
        const panelHeight = settingsPanelElement.offsetHeight || 200;
        const margin = 10;
        let panelLeft = buttonRect.left - panelWidth - margin;
        let panelTop = buttonRect.top;
        if (panelLeft < margin) { panelLeft = buttonRect.right + margin; }
        if (panelLeft + panelWidth > window.innerWidth - margin) {
            panelLeft = buttonRect.left;
            panelTop = buttonRect.top - panelHeight - margin;
        }
        panelLeft = Math.max(margin, Math.min(panelLeft, window.innerWidth - panelWidth - margin));
        panelTop = Math.max(margin, Math.min(panelTop, window.innerHeight - panelHeight - margin));
        settingsPanelElement.style.left = panelLeft + 'px';
        settingsPanelElement.style.top = panelTop + 'px';
    }

    function toggleSettingsPanel() {
        isPanelOpen = !isPanelOpen;
        if (settingsPanelElement) {
            if (isPanelOpen) {
                updatePanelPosition();
                settingsPanelElement.classList.add('visible');
            } else {
                settingsPanelElement.classList.remove('visible');
            }
        }
        if (floatingButtonElement) {
            if (isPanelOpen) {
                floatingButtonElement.classList.add('panel-open');
                floatingButtonElement.innerHTML = '✕';
            } else {
                floatingButtonElement.classList.remove('panel-open');
                floatingButtonElement.innerHTML = '⚙️';
            }
        }
    }

    document.addEventListener('click', (e) => {
        if (isPanelOpen && settingsPanelElement && floatingButtonElement) {
            if (!settingsPanelElement.contains(e.target) && !floatingButtonElement.contains(e.target)) {
                toggleSettingsPanel();
            }
        }
    }, true);

    function updateStatusDisplay(content, type = 'status', duration = 0, showRetryButton = false, showRetryProcessingButton = false) {
        ensureControlsExist();
        if (!statusBarElement) return;
        let buttonsHtml = '';
        if (showRetryButton && lastOriginalText) { buttonsHtml += `<button id="${RETRY_BUTTON_ID}" type="button">重试原文</button>`; }
        if (showRetryProcessingButton) { buttonsHtml += `<button id="${RETRY_PROCESSING_BUTTON_ID}" type="button">重试处理</button>`; }
        statusBarElement.innerHTML = `<span class="status-text ${type}">${content}</span>${buttonsHtml ? `<div class="status-buttons">${buttonsHtml}</div>` : ''}`;
        statusBarElement.classList.add('visible');
        if (showRetryButton && lastOriginalText) { const retryBtn = statusBarElement.querySelector(`#${RETRY_BUTTON_ID}`); if (retryBtn) retryBtn.addEventListener('click', handleRetryOriginalClick); }
        if (showRetryProcessingButton) { const retryProcBtn = statusBarElement.querySelector(`#${RETRY_PROCESSING_BUTTON_ID}`); if (retryProcBtn) retryProcBtn.addEventListener('click', handleRetryProcessingClick); }
        if (statusBarElement.hideTimeout) clearTimeout(statusBarElement.hideTimeout);
        statusBarElement.hideTimeout = duration > 0 ? setTimeout(hideStatusDisplay, duration) : null;
    }

    function hideStatusDisplay() {
        if (statusBarElement) { if (statusBarElement.hideTimeout) clearTimeout(statusBarElement.hideTimeout); statusBarElement.hideTimeout = null; statusBarElement.classList.remove('visible'); setTimeout(() => { if (statusBarElement && !statusBarElement.classList.contains('visible')) { statusBarElement.innerHTML = ''; } }, 250); }
    }

    function replaceContentAndSend(inputElement, newText, autoSend, originalText) {
        // 使用 execCommand 替换内容,这是最可靠的方法来触发 React/框架的更新
        inputElement.focus();
        document.execCommand('selectAll', false, null);
        document.execCommand('insertText', false, newText);

        // 兜底:如果 execCommand 失败,使用 textContent 并触发事件
        if (inputElement.textContent !== newText) {
            console.log(`[输入翻译 v${SCRIPT_VERSION}] execCommand 可能失败,尝试手动设置 textContent`);
            inputElement.textContent = newText;
            inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
        }

        setCursorToEnd(inputElement);

        if (autoSend) {
            // 增加延迟,确保 DOM 更新和 React 状态同步
            setTimeout(() => {
                if (!isTranslatingAndSending) return;

                // --- 关键修复:发送前严格校验 ---
                // 检查当前输入框内容是否仍然是原文,或者包含中文/缅甸文字符
                const currentText = inputElement.textContent?.trim();
                const currentLang = detectLanguage(currentText);
                const originalLang = detectLanguage(originalText);
                
                // 如果当前内容仍然是原文,或者原文是中文/缅甸文但当前内容也是中文/缅甸文,说明替换失败
                const replacementFailed = currentText && originalText && (
                    currentText === originalText || 
                    ((originalLang === 'Chinese' || originalLang === 'Burmese') && 
                     (currentLang === 'Chinese' || currentLang === 'Burmese'))
                );
                
                if (replacementFailed) {
                    console.warn(`[输入翻译 v${SCRIPT_VERSION}] 检测到内容未更新,阻止发送并重试替换`);
                    updateStatusDisplay("检测到内容未更新,正在重试...", 'error');

                    // 重试替换
                    inputElement.focus();
                    document.execCommand('selectAll', false, null);
                    document.execCommand('insertText', false, newText);

                    setTimeout(() => {
                        const retryText = inputElement.textContent?.trim();
                        const retryLang = detectLanguage(retryText);
                        
                        // 再次严格检查:确保不是原文,且不包含中文/缅甸文
                        if (retryText === newText && retryLang !== 'Chinese' && retryLang !== 'Burmese') {
                            finishSend();
                        } else {
                            console.error(`[输入翻译 v${SCRIPT_VERSION}] 文本替换失败,阻止发送原文`);
                            updateStatusDisplay("文本替换失败,请手动发送翻译后的文本", 'error', 5000, true);
                            isTranslatingAndSending = false;
                            allowNextSend = false; // 确保不允许发送
                        }
                    }, 200);
                    return;
                }

                // 最终检查:确保当前内容不包含中文/缅甸文
                if (currentLang === 'Chinese' || currentLang === 'Burmese') {
                    console.error(`[输入翻译 v${SCRIPT_VERSION}] 最终检查失败:内容仍包含中文/缅甸文,阻止发送`);
                    updateStatusDisplay("翻译失败,内容仍包含中文,已阻止发送", 'error', 5000, true);
                    isTranslatingAndSending = false;
                    allowNextSend = false;
                    return;
                }

                finishSend();
            }, 300); // 延迟 300ms
        } else {
            updateStatusDisplay(`处理完成 ✓ (请手动发送)`, 'success', 0, true, true);
            isTranslatingAndSending = false;
            lastTranslationEndTime = Date.now();
            justTranslated = true;
        }

        function finishSend() {
            isTranslatingAndSending = false;
            lastTranslationEndTime = Date.now();
            justTranslated = false;
            allowNextSend = true; // 允许这次发送
            updateStatusDisplay("翻译完成,正在发送...", 'success', 1500); // 显示成功提示
            safeSendMessage(inputElement);
            // 发送后自动隐藏提示框
            setTimeout(() => {
                hideStatusDisplay();
            }, 1500);
        }
    }


    function performAbbreviationPreservingEmojis(inputElement, textToProcess) {
        const emojiMap = new Map();
        let placeholderIndex = 0;
        const tempDiv = document.createElement('div');
        tempDiv.innerHTML = inputElement.innerHTML;
        const emojiNodes = [];
        const walker = document.createTreeWalker(tempDiv, NodeFilter.SHOW_ELEMENT);
        let elementNode;
        while (elementNode = walker.nextNode()) {
            if (elementNode.tagName === 'IMG' && elementNode.classList.contains('emoji')) {
                emojiNodes.push(elementNode);
            }
        }
        for (const emojiNode of emojiNodes) {
            const placeholder = `__EMOJI_PLACEHOLDER_${placeholderIndex++}__`;
            emojiMap.set(placeholder, emojiNode.outerHTML);
            const textNode = document.createTextNode(placeholder);
            if (emojiNode.parentNode) {
                emojiNode.parentNode.replaceChild(textNode, emojiNode);
            }
        }
        const textWithPlaceholders = tempDiv.textContent || "";
        let abbreviatedText = applyLetterAbbreviations(fixNumberAbbreviations(textWithPlaceholders));
        const resultFragment = document.createDocumentFragment();
        let lastIndex = 0;
        const placeholderRegex = /__EMOJI_PLACEHOLDER_\d+__/g;
        let match;
        while ((match = placeholderRegex.exec(abbreviatedText)) !== null) {
            if (match.index > lastIndex) {
                resultFragment.appendChild(document.createTextNode(abbreviatedText.substring(lastIndex, match.index)));
            }
            const placeholder = match[0];
            if (emojiMap.has(placeholder)) {
                const emojiHtml = emojiMap.get(placeholder);
                const tempContainer = document.createElement('span');
                tempContainer.innerHTML = emojiHtml;
                resultFragment.appendChild(tempContainer.firstChild);
            }
            lastIndex = match.index + placeholder.length;
        }
        if (lastIndex < abbreviatedText.length) {
            resultFragment.appendChild(document.createTextNode(abbreviatedText.substring(lastIndex)));
        }

        // 对于包含 emoji 的复杂 HTML,execCommand 可能处理不好,这里保持原样但增加事件触发
        inputElement.innerHTML = '';
        inputElement.appendChild(resultFragment);
        setCursorToEnd(inputElement);
        inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));

        updateStatusDisplay("替换缩写成功", 'success', 2000);
        showInlineStatus(inputElement, '替换缩写成功');

        setTimeout(() => {
            justTranslated = true;
            safeSendMessage(inputElement);
            isTranslatingAndSending = false;
            lastTranslationEndTime = Date.now();
            hideStatusDisplay();
        }, 200);
    }

    function fixNumberAbbreviations(text) {
        if (!text) return text; let originalText = text;
        text = text.replace(/\b2\b/gi, "to"); text = text.replace(/\b4\b/gi, "for"); text = text.replace(/\b(be?|b)4\b/gi, "before"); text = text.replace(/\b2day\b/gi, "today"); text = text.replace(/\b2nite\b/gi, "tonight"); text = text.replace(/\b2night\b/gi, "tonight"); text = text.replace(/\b2mrw\b/gi, "tomorrow"); text = text.replace(/\b2moro\b/gi, "tomorrow"); text = text.replace(/\bgr8\b/gi, "great"); text = text.replace(/\bl8r\b/gi, "later"); text = text.replace(/\bw8\b/gi, "wait"); text = text.replace(/\bh8\b/gi, "hate"); text = text.replace(/\bsk8\b/gi, "skate"); text = text.replace(/\bm8\b/gi, "mate");
        if (text !== originalText) { console.log(`[输入翻译 v${SCRIPT_VERSION}] 数字缩写修正: "${originalText}" -> "${text}"`); } return text;
    }

    function applyLetterAbbreviations(text) {
        if (!text) return text;
        text = text.replace(/\b[Tt]hank you\b/g, m => m.charAt(0) === 'T' ? 'Thx u' : 'thx u');
        let originalText = text; let modifiedText = text; let initialCapitalizationApplied = false; let changesMade = false;
        const abbrMap = { "you": "u", "your": "ur", "yours": "urs", "yourself": "urself", "are": "r", "thanks": "thx", "thank": "thx", "and": "&", "before": "bfr", "first": "frst", "tomorrow": "tmrw", "next": "nxt" };
        const capitalizeAtStart = ["u", "ur", "urs", "r", "thx", "bfr", "frst", "tmrw", "nxt", "urself"];
        let firstWordIndex = -1; let firstWord = ""; let leadingChars = ""; const match = modifiedText.match(/^(\s*[^a-zA-Z\s]*)?([a-zA-Z]+)/);
        if (match) {
            leadingChars = match[1] || ""; firstWord = match[2]; firstWordIndex = leadingChars.length; const lowerFirstWord = firstWord.toLowerCase();
            if (abbrMap.hasOwnProperty(lowerFirstWord)) {
                const abbreviation = abbrMap[lowerFirstWord]; let replacementMade = false;
                if (capitalizeAtStart.includes(abbreviation)) { const capitalizedAbbr = abbreviation.charAt(0).toUpperCase() + abbreviation.slice(1); modifiedText = leadingChars + capitalizedAbbr + modifiedText.substring(firstWordIndex + firstWord.length); initialCapitalizationApplied = true; replacementMade = true; }
                else if (abbreviation === '&') { modifiedText = leadingChars + abbreviation + modifiedText.substring(firstWordIndex + firstWord.length); initialCapitalizationApplied = true; replacementMade = true; }
                if (replacementMade) changesMade = true;
            }
        }
        const replaceRemaining = (fullWord, abbr) => { const regexLower = new RegExp(`\\b${fullWord}\\b`, 'g'); const regexUpper = new RegExp(`\\b${fullWord.charAt(0).toUpperCase() + fullWord.slice(1)}\\b`, 'g'); let startIndex = 0; if (initialCapitalizationApplied && firstWord.toLowerCase() === fullWord) { startIndex = firstWordIndex + (abbrMap[fullWord] ? abbrMap[fullWord].length : firstWord.length); } let targetStringPart = modifiedText.substring(startIndex); let prefix = modifiedText.substring(0, startIndex); let replacedPart = targetStringPart; let currentChangesMade = false; const originalLength = replacedPart.length; if (abbr === '&') { replacedPart = replacedPart.replace(/\b[Aa]nd\b/g, '&'); } else { replacedPart = replacedPart.replace(regexLower, abbr); replacedPart = replacedPart.replace(regexUpper, abbr); } if (replacedPart.length !== originalLength || replacedPart !== targetStringPart) currentChangesMade = true; modifiedText = prefix + replacedPart; if (currentChangesMade) changesMade = true; };
        for (const word in abbrMap) { replaceRemaining(word, abbrMap[word]); }
        if (/^\s*&/.test(modifiedText)) { modifiedText = modifiedText.replace(/^(\s*)&/, '$1And'); }
        if (changesMade) { console.log(`[输入翻译 v${SCRIPT_VERSION}] 字母缩写: "${originalText}" -> "${modifiedText}"`); } return modifiedText;
    }

    function handleRetryOriginalClick(event) {
        event.preventDefault(); event.stopPropagation();
        if (isTranslatingAndSending) return;
        if (!lastOriginalText) return;
        const inputElement = document.querySelector(INPUT_SELECTOR);
        const sendButton = document.querySelector(SEND_BUTTON_SELECTOR);
        if (!inputElement || !sendButton) return;
        translateAndSend(lastOriginalText, inputElement, sendButton, true);
    }

    function handleRetryProcessingClick(event) {
        event.preventDefault(); event.stopPropagation();
        if (isTranslatingAndSending) return;
        const inputElement = document.querySelector(INPUT_SELECTOR);
        const sendButton = document.querySelector(SEND_BUTTON_SELECTOR);
        if (!inputElement || !sendButton) return;
        const currentText = inputElement.textContent?.trim();
        if (!currentText) return;
        translateAndSend(currentText, inputElement, sendButton, true);
    }

    function updateAutoSendButtonVisual() {
        const autoSendBtn = document.getElementById(AUTO_SEND_TOGGLE_ID);
        if (!autoSendBtn) return;
        autoSendBtn.textContent = autoSendEnabled ? "自动发送: 开启" : "自动发送: 关闭";
        autoSendBtn.className = autoSendEnabled ? 'toggle-button on' : 'toggle-button off';
    }

    function toggleAutoSend() {
        autoSendEnabled = !autoSendEnabled;
        updateAutoSendButtonVisual();
        updateStatusDisplay(`自动发送已${autoSendEnabled ? '开启' : '关闭'}`, 'status', 2000);
        localStorage.setItem(STORAGE_KEY_AUTOSEND, autoSendEnabled.toString());
    }

    function updateModeButtonVisuals() {
        const abbrButton = document.getElementById(MODE_BUTTON_ABBR_ID);
        const stdButton = document.getElementById(MODE_BUTTON_STD_ID);
        if (!abbrButton || !stdButton) return;
        if (currentTranslationMode === MODE_ABBREVIATED) {
            abbrButton.classList.add('active');
            stdButton.classList.remove('active');
        } else {
            abbrButton.classList.remove('active');
            stdButton.classList.add('active');
        }
    }

    function switchMode(newMode) {
        if (newMode === currentTranslationMode) return;
        currentTranslationMode = newMode;
        updateModeButtonVisuals();
        updateStatusDisplay(`模式切换为: ${currentTranslationMode === MODE_ABBREVIATED ? '缩写' : '标准'}`, 'status', 2000);
        localStorage.setItem(STORAGE_KEY_MODE, currentTranslationMode);
    }

    function translateAndSend(textToProcess, inputElement, sendButton, forceApi = false) {
        if (isTranslatingAndSending) return;
        if (!inputElement || !sendButton) { updateStatusDisplay("错误: 无法找到输入框或发送按钮", 'error', 4000, true, true); return; }
        isTranslatingAndSending = true;
        hideStatusDisplay();
        const detectedLang = detectLanguage(textToProcess);
        const currentModeText = currentTranslationMode === MODE_ABBREVIATED ? '缩写' : '标准';

        if (currentTranslationMode === MODE_ABBREVIATED && detectedLang !== 'Chinese' && detectedLang !== 'Burmese') {
            performAbbreviationPreservingEmojis(inputElement, textToProcess);
            return;
        }
        if (detectedLang === 'Chinese' || detectedLang === 'Burmese') { lastOriginalText = textToProcess; }

        const useCache = !forceApi && (detectedLang === 'Chinese' || detectedLang === 'Burmese');
        const cacheKey = `${currentTranslationMode}::${textToProcess}`;

        if (useCache && translationCache.has(cacheKey)) {
            const finalText = translationCache.get(cacheKey);
            console.log(`[输入翻译 v${SCRIPT_VERSION}][缓存命中]`);
            updateStatusDisplay(`[${currentModeText}] 已从缓存加载 ✓`, 'info', 3000, false, !autoSendEnabled);
            replaceContentAndSend(inputElement, finalText, autoSendEnabled, textToProcess);
            return;
        }

        const finalPrompt = TRANSLATION_PROMPT.replace('{text_to_translate}', textToProcess);
        updateStatusDisplay(`[${currentModeText}] 翻译处理中...`, 'status');

        const requestBody = { model: currentApiModel, messages: [{ "role": "user", "content": finalPrompt }], temperature: 0.6 };
        if (currentInputApiXhr && typeof currentInputApiXhr.abort === 'function') { currentInputApiXhr.abort(); }

        currentInputApiXhr = GM_xmlhttpRequest({
            method: "POST", url: currentApiEndpoint,
            headers: { "Content-Type": "application/json", "Authorization": `Bearer ${currentApiKey}` },
            data: JSON.stringify(requestBody),
            onload: function (response) {
                currentInputApiXhr = null;
                try {
                    if (response.status >= 200 && response.status < 300) {
                        const data = JSON.parse(response.responseText);
                        const rawTranslation = data.choices?.[0]?.message?.content?.trim();
                        if (rawTranslation) {
                            let finalProcessedText;
                            if (currentTranslationMode === MODE_ABBREVIATED) {
                                finalProcessedText = applyLetterAbbreviations(fixNumberAbbreviations(rawTranslation));
                            } else {
                                finalProcessedText = rawTranslation.trim();
                            }

                            if (!forceApi && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) {
                                if (translationCache.size >= MAX_CACHE_SIZE) { const oldestKey = translationCache.keys().next().value; translationCache.delete(oldestKey); }
                                translationCache.set(cacheKey, finalProcessedText);
                            }

                            replaceContentAndSend(inputElement, finalProcessedText, autoSendEnabled, textToProcess);
                        } else { throw new Error(`API 返回空内容`); }
                    } else { throw new Error(`HTTP ${response.status}`); }
                } catch (e) {
                    console.error(`[输入翻译 v${SCRIPT_VERSION}] API Error:`, e);
                    updateStatusDisplay(`处理失败: ${e.message}`, 'error', 5000, true, true);
                    isTranslatingAndSending = false;
                }
            },
            onerror: function (response) { currentInputApiXhr = null; updateStatusDisplay(`网络错误`, 'error', 5000, true, true); isTranslatingAndSending = false; },
            ontimeout: function () { currentInputApiXhr = null; updateStatusDisplay("请求超时", 'error', 5000, true, true); isTranslatingAndSending = false; },
            timeout: 45000
        });
    }

    function handleInputKeyDown(event) {
        const inputElement = event.target;
        if (!inputElement) return;

        // 第一层检查:URL 包含 search
        if (window.location.hash.includes('search')) {
            console.log(`[输入翻译] URL包含search,跳过处理`);
            return;
        }

        // 第二层检查:元素在搜索相关容器内
        if (inputElement.closest('.search-group') ||
            inputElement.closest('.sidebar-search') ||
            inputElement.closest('#search-container') ||
            inputElement.closest('[class*="search"]') ||
            inputElement.closest('.input-search')) {
            console.log(`[输入翻译] 元素在搜索容器内,跳过处理`);
            return;
        }

        // 第三层检查:元素本身的属性
        const elementClasses = inputElement.className || '';
        const elementId = inputElement.id || '';
        if (elementClasses.toLowerCase().includes('search') ||
            elementId.toLowerCase().includes('search')) {
            console.log(`[输入翻译] 元素包含search属性,跳过处理`);
            return;
        }

        // 检查是否在弹窗中(图片/文件发送弹窗)
        const inPopup = isInPopup(inputElement);
        
        // 第四层检查:必须在聊天输入容器内或弹窗内
        const chatInputContainer = inputElement.closest(INPUT_AREA_CONTAINER_SELECTOR);
        if (!chatInputContainer && !inPopup) {
            console.log(`[输入翻译] 不在聊天输入容器或弹窗内,跳过处理`);
            return;
        }

        // 第五层检查:必须是正确的输入框类型
        if (!inputElement.matches('div.input-message-input[contenteditable="true"]')) {
            console.log(`[输入翻译] 不是聊天输入框,跳过处理`);
            return;
        }
        
        if (inPopup) {
            console.log(`[输入翻译] 检测到弹窗输入框`);
        }

        if (isScriptTriggeredSend || event._scriptTriggered) return;

        // 新增功能:Ctrl+Enter 或 Alt+Enter 发送原文(强制发送,跳过翻译)
        if (event.key === 'Enter' && (event.ctrlKey || event.altKey)) {
            event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation();
            justTranslated = false;
            safeSendMessage(inputElement, true); // 传入 true 表示强制发送原文
            return;
        }

        if (event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.ctrlKey && justTranslated) {
            event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation();
            
            // 翻译完成后的发送,需要再次检查内容
            const currentText = inputElement.textContent?.trim() || '';
            const currentLang = detectLanguage(currentText);
            
            // 如果内容包含中文/缅甸文,阻止发送
            if (currentLang === 'Chinese' || currentLang === 'Burmese') {
                console.warn(`[输入翻译 v${SCRIPT_VERSION}] 阻止发送:内容仍包含中文/缅甸文`);
                updateStatusDisplay("内容仍包含中文,无法发送!", 'error', 3000);
                justTranslated = false;
                return;
            }
            
            allowNextSend = true; // 翻译完成后允许发送
            safeSendMessage(inputElement);
            justTranslated = false;
            hideStatusDisplay();
            return;
        }

        if (event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.ctrlKey) {
            event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation();
            if (isTranslatingAndSending) return;

            let text = inputElement.textContent?.trim() || inputElement.innerText?.trim() || "";
            const detectedLang = detectLanguage(text);
            // 使用辅助函数获取对应的发送按钮
            const sendButton = getSendButton(inputElement);

            if (!sendButton || sendButton.disabled) {
                updateStatusDisplay("错误: 发送按钮不可用!", 'error', 5000, true, true);
                return;
            }

            const needsProcessing = text && text.length > 0 && (
                (detectedLang === 'Chinese' || detectedLang === 'Burmese') ||
                (currentTranslationMode === MODE_ABBREVIATED && detectedLang !== 'Chinese' && detectedLang !== 'Burmese')
            );

            if (needsProcessing) {
                translateAndSend(text, inputElement, sendButton);
            } else {
                // 不需要处理的情况,直接发送(但仍需检查是否包含中文/缅甸文)
                const currentLang = detectLanguage(text);
                if (currentLang === 'Chinese' || currentLang === 'Burmese') {
                    console.warn(`[输入翻译 v${SCRIPT_VERSION}] 检测到中文/缅甸文但未进入处理流程,强制处理`);
                    translateAndSend(text, inputElement, sendButton);
                } else {
                    justTranslated = false;
                    allowNextSend = true;
                    safeSendMessage(inputElement);
                }
            }
        }
        else if (isTranslatingAndSending && !['Shift', 'Control', 'Alt', 'Meta', 'Enter', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Escape', 'Tab'].includes(event.key)) {
            hideStatusDisplay();
            if (currentInputApiXhr && typeof currentInputApiXhr.abort === 'function') { currentInputApiXhr.abort(); }
            isTranslatingAndSending = false;
        } else if (!isTranslatingAndSending) {
            if (statusBarElement && statusBarElement.classList.contains('visible')) {
                const statusSpan = statusBarElement.querySelector('span.status');
                if (statusSpan) hideStatusDisplay();
            }
        }
    }

    let lastTranslationEndTime = 0;
    const VOICE_BLOCK_DURATION = 500;

    function preventVoiceRecording(event) {
        const target = event.target;
        const isVoiceButton = target.closest('.btn-icon.rp') || target.closest('button[title*="录音"]') || target.closest('button[title*="Record"]') || target.closest('.record-control');
        const shouldBlock = isVoiceButton && (isTranslatingAndSending || (Date.now() - lastTranslationEndTime < VOICE_BLOCK_DURATION));
        if (shouldBlock) {
            event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation();
            return false;
        }
    }

    function globalEventInterceptor(event) {
        if (!isTranslatingAndSending) return;
        const target = event.target;
        const isInputArea = target.closest(INPUT_AREA_CONTAINER_SELECTOR);
        if (isInputArea && (event.type === 'mousedown' || event.type === 'touchstart')) {
            const isControlButton = target.closest(`#${CONTROLS_CONTAINER_ID}`);
            const isSendButton = target.closest(SEND_BUTTON_SELECTOR);
            if (!isControlButton && !isSendButton) {
                event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation();
            }
        }
    }
    
    // 拦截发送按钮点击,检查是否需要翻译
    function handleSendButtonClick(event) {
        const target = event.target;
        
        // 检查是否点击了发送按钮(主输入框或弹窗)
        const sendButton = target.closest('.btn-send, .btn-primary');
        if (!sendButton) return;
        
        // 检查是否在弹窗中
        const popup = sendButton.closest(POPUP_CONTAINER_SELECTOR);
        const inMainChat = sendButton.closest(INPUT_AREA_CONTAINER_SELECTOR);
        
        let inputElement = null;
        
        if (popup) {
            // 弹窗中的发送按钮
            inputElement = popup.querySelector('div.input-message-input[contenteditable="true"]');
        } else if (inMainChat) {
            // 主聊天区域的发送按钮
            inputElement = inMainChat.querySelector('div.input-message-input[contenteditable="true"]');
        }
        
        if (!inputElement) return;
        
        const text = inputElement.textContent?.trim() || '';
        const detectedLang = detectLanguage(text);
        
        // 如果包含中文或缅甸文,拦截点击并触发翻译
        if (text && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) {
            event.preventDefault();
            event.stopPropagation();
            event.stopImmediatePropagation();
            
            console.log(`[输入翻译 v${SCRIPT_VERSION}] 拦截发送按钮点击,检测到 ${detectedLang},触发翻译`);
            
            if (!isTranslatingAndSending) {
                translateAndSend(text, inputElement, sendButton);
            }
            return false;
        }
    }
    
    // 为弹窗添加发送按钮监听器
    function attachPopupSendButtonListener(popupNode) {
        if (popupNode.dataset.sendButtonListenerAttached) return;
        
        const sendButtons = popupNode.querySelectorAll('.btn-primary, .btn-send');
        sendButtons.forEach(btn => {
            btn.addEventListener('click', handleSendButtonClick, { capture: true });
            btn.addEventListener('mousedown', handleSendButtonClick, { capture: true });
        });
        
        popupNode.dataset.sendButtonListenerAttached = 'true';
        console.log(`[输入翻译] 已为弹窗发送按钮添加监听器`);
    }

    function initialize() {
        console.log(`[输入翻译 v${SCRIPT_VERSION}] 初始化...`);

        document.body.addEventListener('mousedown', preventVoiceRecording, { capture: true, passive: false });
        document.body.addEventListener('touchstart', preventVoiceRecording, { capture: true, passive: false });
        document.body.addEventListener('click', preventVoiceRecording, { capture: true, passive: false });
        document.body.addEventListener('mousedown', globalEventInterceptor, { capture: true, passive: false });
        document.body.addEventListener('touchstart', globalEventInterceptor, { capture: true, passive: false });
        
        // 全局监听发送按钮点击,拦截中文/缅甸文发送
        document.body.addEventListener('click', handleSendButtonClick, { capture: true });
        document.body.addEventListener('mousedown', handleSendButtonClick, { capture: true });

        const observer = new MutationObserver(mutations => {
            let controlsNeedCheck = false;
            mutations.forEach(mutation => {
                if (mutation.addedNodes) {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType !== 1) return;

                        // 跳过搜索区域的变化
                        if (node.closest && (node.closest('.search-group') ||
                            node.closest('.sidebar-search') ||
                            node.closest('#search-container') ||
                            node.closest('[class*="search"]'))) {
                            return;
                        }

                        // 检查聊天输入容器
                        const containerNode = node.matches && node.matches(INPUT_AREA_CONTAINER_SELECTOR) ? node : node.querySelector && node.querySelector(INPUT_AREA_CONTAINER_SELECTOR);
                        if (containerNode) controlsNeedCheck = true;
                        
                        // 检查弹窗容器(图片/文件发送弹窗)
                        const popupNode = node.matches && node.matches(POPUP_CONTAINER_SELECTOR) ? node : node.querySelector && node.querySelector(POPUP_CONTAINER_SELECTOR);
                        if (popupNode) {
                            console.log(`[输入翻译] 检测到弹窗出现`);
                            // 查找弹窗中的输入框
                            const popupInputs = popupNode.querySelectorAll('div.input-message-input[contenteditable="true"]');
                            popupInputs.forEach(popupInput => {
                                if (!popupInput.dataset.customInputTranslateListener) {
                                    attachInputListeners(popupInput);
                                }
                            });
                        }

                        // 只在聊天输入容器内或弹窗内查找输入框
                        const inPopupContainer = node.closest && node.closest(POPUP_CONTAINER_SELECTOR);
                        if (containerNode || (node.closest && node.closest(INPUT_AREA_CONTAINER_SELECTOR)) || inPopupContainer) {
                            const inputElementNode = node.matches && node.matches('div.input-message-input[contenteditable="true"]') ? node : node.querySelector && node.querySelector('div.input-message-input[contenteditable="true"]');
                            if (inputElementNode && !inputElementNode.dataset.customInputTranslateListener) {
                                attachInputListeners(inputElementNode);
                                if (!inPopupContainer) controlsNeedCheck = true;
                            }
                        }
                    });
                }
            });
            if (controlsNeedCheck) setTimeout(ensureControlsExist, 50);
        });

        observer.observe(document.body, { childList: true, subtree: true });
        setTimeout(() => {
            const initialContainer = document.querySelector(INPUT_AREA_CONTAINER_SELECTOR);
            if (initialContainer) ensureControlsExist();
            const initialInputElement = document.querySelector(INPUT_SELECTOR);
            if (initialInputElement) attachInputListeners(initialInputElement);
        }, 1000);
    }

    function attachInputListeners(inputElement) {
        if (inputElement.dataset.customInputTranslateListener) return;

        // URL 检查
        if (window.location.hash.includes('search')) {
            console.log(`[输入翻译] URL包含search,不附加监听器`);
            return;
        }

        // 容器检查
        if (inputElement.closest('.search-group') ||
            inputElement.closest('.sidebar-search') ||
            inputElement.closest('#search-container') ||
            inputElement.closest('[class*="search"]') ||
            inputElement.closest('.input-search')) {
            console.log(`[输入翻译] 跳过搜索框元素`);
            return;
        }

        // 元素属性检查
        const elementClasses = inputElement.className || '';
        const elementId = inputElement.id || '';
        if (elementClasses.toLowerCase().includes('search') ||
            elementId.toLowerCase().includes('search')) {
            console.log(`[输入翻译] 元素包含search属性,跳过`);
            return;
        }

        // 检查是否在弹窗中
        const inPopup = isInPopup(inputElement);
        
        // 必须在聊天输入容器内或弹窗内
        const chatInputContainer = inputElement.closest(INPUT_AREA_CONTAINER_SELECTOR);
        if (!chatInputContainer && !inPopup) {
            console.log(`[输入翻译] 跳过非聊天输入框`);
            return;
        }

        if (inPopup) {
            console.log(`[输入翻译] 附加监听器到弹窗输入框(图片/文件发送)`);
        } else {
            console.log(`[输入翻译] 附加监听器到聊天输入框`);
        }
        inputElement.addEventListener('keydown', handleInputKeyDown, { capture: true, passive: false });
        inputElement.dataset.customInputTranslateListener = 'true';
        
        // 只有主聊天输入框才需要创建控件
        if (!inPopup) {
            console.log(`[输入翻译] 准备调用 ensureControlsExist`);
            ensureControlsExist();
            console.log(`[输入翻译] ensureControlsExist 调用完成`);
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }

    function showInlineStatus(el, msg, duration = 2000) {
        const parent = el.parentElement;
        if (!parent) return;
        parent.style.position = parent.style.position || 'relative';
        const tip = document.createElement('div');
        tip.textContent = msg;
        tip.style.cssText = 'position:absolute;top:-24px;right:0;padding:4px 8px;background:rgba(0,0,0,0.7);color:#fff;border-radius:4px;font-size:12px;z-index:500;';
        parent.appendChild(tip);
        setTimeout(() => { if (tip && tip.parentElement) tip.parentElement.removeChild(tip); }, duration);
    }

})();