大模型中文翻译助手

选中文本后调用 OpenAI Compatible API 将其翻译为中文,支持历史记录、收藏夹及整页翻译

目前为 2025-03-18 提交的版本。查看 最新版本

// ==UserScript==
// @name         大模型中文翻译助手
// @name:en      LLM powered WebPage Translator to Chinese
// @namespace    http://tampermonkey.net/
// @version      1.3.14
// @description  选中文本后调用 OpenAI Compatible API 将其翻译为中文,支持历史记录、收藏夹及整页翻译
// @description:en Select text and call OpenAI Compatible API to translate it to Chinese, supports history, favorites and full page translation
// @author       tzh
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // 可配置的设置
    const defaultSettings = {
        apiEndpoint: 'https://api.deepseek.com/v1/chat/completions', // 默认 OpenAI API 端点
        apiKey: '', // 设置您的 API 密钥
        model: 'deepseek-chat', // 可以根据需要更改为其他模型
        systemPrompt: '你是一个翻译助手。我会为你提供待翻译的文本,以及之前已经翻译过的上下文(如果有)。请参考这些上下文,将文本准确地翻译成中文,保持原文的意思、风格和格式。在充分保留原文意思的情况下使用符合中文习惯的表达。只返回翻译结果,不需要解释。',
        useStreaming: false, // 默认启用流式响应
        temperature: 0.3, // 控制翻译结果的随机性,值越低越准确,值越高越有创意
        maxHistoryItems: 50, // 最大历史记录数
        maxFavoritesItems: 100, // 最大收藏数
        showSourceLanguage: false, // 是否显示源语言
        autoDetectLanguage: true, // 是否自动检测语言
        detectArticleContent: true, // 是否自动识别文章主体内容
        contextSize: 3, // 翻译时使用的上下文数量
        useTranslationContext: true, // 是否启用翻译上下文
        fullPageTranslationSelector: 'body', // 整页翻译时的选择器
        fullPageMaxSegmentLength: 2000, // 整页翻译时的最大分段长度
        excludeSelectors: 'script, style, noscript, iframe, img, svg, canvas', // 翻译时排除的元素
        apiConfigs: [ // 多API配置
            {
                name: 'DeepSeek',
                apiEndpoint: 'https://api.deepseek.com/v1/chat/completions',
                apiKey: '',
                model: 'deepseek-chat',
            }
        ],
        currentApiIndex: 0, // 当前使用的API索引
        currentTab: 'general', // 默认显示普通设置标签
    };

    // 初始化设置
    let settings = GM_getValue('translatorSettings', defaultSettings);

    // 全文翻译相关状态
    let isTranslatingFullPage = false; // 是否正在进行全文翻译
    let isTranslationPaused = false; // 是否已暂停翻译
    let translationSegments = []; // 存储页面分段内容
    let lastTranslatedIndex = -1; // 最后翻译的段落索引
    let originalTexts = []; // 存储原始文本

    // 确保设置中包含apiConfigs字段
    if (!settings.apiConfigs) {
        settings.apiConfigs = [
            {
                name: '默认API',
                apiEndpoint: settings.apiEndpoint,
                apiKey: settings.apiKey,
                model: settings.model,
            }
        ];
        settings.currentApiIndex = 0;
        GM_setValue('translatorSettings', settings);
    }

    // 从当前选择的API配置中同步主要设置
    function syncApiSettings() {
        if (settings.apiConfigs && settings.apiConfigs.length > 0 && settings.currentApiIndex >= 0 && settings.currentApiIndex < settings.apiConfigs.length) {
            const currentApi = settings.apiConfigs[settings.currentApiIndex];
            const oldEndpoint = settings.apiEndpoint;
            const oldModel = settings.model;
            
            settings.apiEndpoint = currentApi.apiEndpoint;
            settings.apiKey = currentApi.apiKey;
            settings.model = currentApi.model;
            
            // 检查是否有变化
            if (oldEndpoint !== settings.apiEndpoint || oldModel !== settings.model) {
                console.log(`API设置已同步 - 索引: ${settings.currentApiIndex}, 端点: ${settings.apiEndpoint}, 模型: ${settings.model} (变更: ${oldEndpoint} -> ${settings.apiEndpoint}, ${oldModel} -> ${settings.model})`);
            } else {
                console.log(`API设置已同步 - 索引: ${settings.currentApiIndex}, 端点: ${settings.apiEndpoint}, 模型: ${settings.model} (无变更)`);
            }
            
            GM_setValue('translatorSettings', settings);
        } else {
            console.warn('无法同步API设置 - 无效的配置或索引', {
                hasConfigs: Boolean(settings.apiConfigs),
                configsLength: settings.apiConfigs ? settings.apiConfigs.length : 0,
                currentIndex: settings.currentApiIndex
            });
        }
    }

    // 初始同步
    syncApiSettings();

    let translationHistory = GM_getValue('translationHistory', []);
    let translationFavorites = GM_getValue('translationFavorites', []);

    // 跟踪当前活跃的翻译按钮
    let activeTranslateButton = null;
    let lastSelectedText = '';
    let lastSelectionRect = null;

    // 监听整个文档的mousedown事件,防止点击翻译按钮时丢失选择
    document.addEventListener('mousedown', function (e) {
        // 检查点击是否发生在翻译按钮上
        if (e.target.classList.contains('translate-button') || e.target.closest('.translate-button')) {
            e.stopPropagation();
            e.preventDefault();
        }
    }, true);  // 使用捕获阶段,确保在其他处理程序之前执行

    // 语言检测函数
    function detectLanguage(text) {
        return new Promise((resolve) => {
        // 简单的语言检测逻辑
        const chineseRegex = /[\u4e00-\u9fa5]/;
        const englishRegex = /[a-zA-Z]/;
        const japaneseRegex = /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/;
        const koreanRegex = /[\uAC00-\uD7AF\u1100-\u11FF]/;

            if (chineseRegex.test(text)) resolve('中文');
            else if (englishRegex.test(text)) resolve('英语');
            else if (japaneseRegex.test(text)) resolve('日语');
            else if (koreanRegex.test(text)) resolve('韩语');
            else resolve('未知');
        });
    }

    // 创建设置面板
    function createSettingsPanel() {
        const settingsPanel = document.createElement('div');
        settingsPanel.id = 'translator-settings-panel';
        settingsPanel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 500px;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
            z-index: 10000;
            height: 90vh;
            display: none;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            font-size: 14px;
            line-height: 1.5;
            color: #333;
        `;

        // 创建Tab栏
        const tabsHtml = `
            <div class="settings-header" style="padding: 0; flex: 0 0 auto;">
                <div class="settings-tabs" style="border-bottom: 1px solid #eee;">
                    <div class="tab-buttons" style="display: flex;">
                        <button id="general-tab-btn" class="tab-btn ${settings.currentTab === 'general' ? 'active' : ''}" 
                            style="flex: 1; padding: 12px 15px; background: none; border: none; border-bottom: 2px solid ${settings.currentTab === 'general' ? '#4285f4' : 'transparent'}; 
                            cursor: pointer; color: ${settings.currentTab === 'general' ? '#4285f4' : '#666'}; font-size: 14px; font-weight: 500; outline: none;">
                            翻译设置
                        </button>
                        <button id="api-tab-btn" class="tab-btn ${settings.currentTab === 'api' ? 'active' : ''}" 
                            style="flex: 1; padding: 12px 15px; background: none; border: none; border-bottom: 2px solid ${settings.currentTab === 'api' ? '#4285f4' : 'transparent'}; 
                            cursor: pointer; color: ${settings.currentTab === 'api' ? '#4285f4' : '#666'}; font-size: 14px; font-weight: 500; outline: none;">
                            API 管理
                        </button>
                    </div>
                </div>
            </div>
        `;

        // 创建内容区域的容器
        const contentContainerHtml = `
            <div class="settings-content" style="flex: 1 1 auto; overflow-y: auto; padding: 20px;">
                <div id="general-tab" class="tab-content" style="display: ${settings.currentTab === 'general' ? 'block' : 'none'};">
                    <h2 style="margin: 0 0 20px 0; font-size: 18px; font-weight: 500; color: #333;">翻译设置</h2>
                    <div style="margin-bottom: 20px;">
                        <label for="systemPrompt" style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">系统提示词:</label>
                        <textarea id="systemPrompt" style="width: 100%; height: 100px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; line-height: 1.5; resize: vertical; font-family: inherit;">${settings.systemPrompt}</textarea>
                    </div>
                    <div style="margin-bottom: 20px;">
                        <label for="temperature" style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">随机性(Temperature)</label>
                        <div style="display: flex; align-items: center;">
                            <input type="range" id="temperature" min="0" max="1" step="0.1" value="${settings.temperature}" 
                                style="flex-grow: 1; height: 4px; background: #ddd; border-radius: 2px; outline: none; -webkit-appearance: none;">
                            <span id="temperature-value" style="margin-left: 15px; min-width: 30px; color: #666;">${settings.temperature}</span>
                        </div>
                        <div style="font-size: 12px; color: #666; margin-top: 8px;">
                            值越低翻译越准确,值越高结果越具有创意性。翻译任务建议使用较低的值。
                        </div>
                    </div>
                    <div style="margin-bottom: 15px;">
                        <label style="display: flex; align-items: center; color: #333;">
                            <input type="checkbox" id="showSourceLanguage" ${settings.showSourceLanguage ? 'checked' : ''} 
                                style="margin: 0 8px 0 0; width: 16px; height: 16px;">
                            <span style="font-weight: 500;">显示原文</span>
                        </label>
                        <div style="font-size: 12px; color: #666; margin-top: 8px; margin-left: 24px;">
                            启用后将在翻译结果上方显示原文
                        </div>
                    </div>
                    <div style="margin-bottom: 15px;">
                        <label style="display: flex; align-items: center; color: #333;">
                            <input type="checkbox" id="useStreaming" ${settings.useStreaming ? 'checked' : ''} 
                                style="margin: 0 8px 0 0; width: 16px; height: 16px;">
                            <span style="font-weight: 500;">启用流式响应(实时显示翻译结果)</span>
                        </label>
                        <div style="font-size: 12px; color: #666; margin-top: 8px; margin-left: 24px;">
                            如果遇到翻译失败问题,可以尝试关闭此选项
                        </div>
                    </div>
                    <div style="margin-bottom: 15px;">
                        <label style="display: flex; align-items: center; color: #333;">
                            <input type="checkbox" id="useTranslationContext" ${settings.useTranslationContext ? 'checked' : ''} 
                                style="margin: 0 8px 0 0; width: 16px; height: 16px;">
                            <span style="font-weight: 500;">启用翻译上下文</span>
                        </label>
                        <div style="font-size: 12px; color: #666; margin-top: 8px; margin-left: 24px;">
                            启用后将使用之前翻译过的内容作为上下文,提高翻译连贯性
                        </div>
                    </div>
                    <div style="margin-bottom: 20px;">
                        <label for="contextSize" style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">上下文数量:</label>
                        <div style="display: flex; align-items: center;">
                            <input type="number" id="contextSize" min="1" max="10" value="${settings.contextSize}" 
                                style="width: 60px; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
                            <span style="margin-left: 10px; color: #666;">段落</span>
                        </div>
                        <div style="font-size: 12px; color: #666; margin-top: 8px;">
                            使用前面已翻译段落作为上下文提升翻译连贯性,建议设置1-5之间,设置较大值会降低翻译速度,消耗更多API配额
                        </div>
                    </div>
                    <div style="margin-bottom: 15px;">
                        <label style="display: flex; align-items: center; color: #333;">
                            <input type="checkbox" id="detectArticleContent" ${settings.detectArticleContent ? 'checked' : ''} 
                                style="margin: 0 8px 0 0; width: 16px; height: 16px;">
                            <span style="font-weight: 500;">智能识别文章主体内容</span>
                        </label>
                        <div style="font-size: 12px; color: #666; margin-top: 8px; margin-left: 24px;">
                            启用后将自动识别文章主要内容区域,避免翻译无关内容
                        </div>
                    </div>
                    <div style="margin-bottom: 20px;">
                        <label for="fullPageTranslationSelector" style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">整页翻译选择器:</label>
                        <input type="text" id="fullPageTranslationSelector" value="${settings.fullPageTranslationSelector}" 
                            style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
                        <div style="font-size: 12px; color: #666; margin-top: 8px;">
                            CSS选择器,用于指定翻译哪些区域的内容
                        </div>
                    </div>
                    <div style="margin-bottom: 20px;">
                        <label for="excludeSelectors" style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">排除翻译的元素:</label>
                        <input type="text" id="excludeSelectors" value="${settings.excludeSelectors}" 
                            style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
                        <div style="font-size: 12px; color: #666; margin-top: 8px;">
                            CSS选择器,指定要排除翻译的元素
                        </div>
                    </div>
                </div>
                <div id="api-tab" class="tab-content" style="display: ${settings.currentTab === 'api' ? 'block' : 'none'};">
                    <h2 style="margin: 0 0 20px 0; font-size: 18px; font-weight: 500; color: #333;">API 管理</h2>
                    <button id="add-api-btn" style="width: 100%; padding: 12px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; margin-bottom: 20px;">
                        + 添加新 API
                    </button>
                    <div id="api-list">
                        ${settings.apiConfigs.map((api, index) => `
                            <div class="api-item" style="margin-bottom: 15px; padding: 15px; border: 1px solid #ddd; border-radius: 4px; position: relative; ${index === settings.currentApiIndex ? 'background-color: #f0f8ff; border-color: #4285f4;' : ''}">
                                <div style="position: absolute; top: 15px; right: 15px;">
                                    ${index === settings.currentApiIndex ?
                                        '<span style="color: #4CAF50; font-weight: 500;">✓ 当前使用</span>' :
                                        `<button class="use-api-btn" data-index="${index}" style="padding: 4px 12px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 8px; font-size: 12px;">使用</button>`
                                    }
                                    <button class="edit-api-btn" data-index="${index}" style="padding: 4px 12px; background-color: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 8px; font-size: 12px;">编辑</button>
                                    ${settings.apiConfigs.length > 1 ?
                                        `<button class="delete-api-btn" data-index="${index}" style="padding: 4px 12px; background-color: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">删除</button>` : ''
                                    }
                                </div>
                                <div style="margin-bottom: 8px;"><strong style="color: #333;">名称:</strong> <span style="color: #666;">${api.name}</span></div>
                                <div style="margin-bottom: 8px;"><strong style="color: #333;">端点:</strong> <span style="color: #666;">${api.apiEndpoint}</span></div>
                                <div style="margin-bottom: 8px;"><strong style="color: #333;">密钥:</strong> <span style="color: #666;">${api.apiKey ? '******' + api.apiKey.substring(api.apiKey.length - 4) : '未设置'}</span></div>
                                <div><strong style="color: #333;">模型:</strong> <span style="color: #666;">${api.model}</span></div>
                            </div>
                        `).join('')}
                    </div>
                </div>
            </div>
        `;

        // 创建API编辑表单
        const apiFormHtml = `
            <div id="api-form" style="display: none; padding: 20px; overflow-y: auto; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: white;">
                <h2 id="api-form-title" style="margin: 0 0 20px 0; font-size: 18px; font-weight: 500; color: #333;">添加 API</h2>
                <input type="hidden" id="api-form-index" value="-1">
                <div style="margin-bottom: 20px;">
                    <label for="api-name" style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">API 名称:</label>
                    <input type="text" id="api-name" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;" placeholder="例如:OpenAI、Azure、DeepSeek">
                </div>
                <div style="margin-bottom: 20px;">
                    <label for="api-endpoint" style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">API 端点:</label>
                    <input type="text" id="api-endpoint" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;" placeholder="例如:https://api.openai.com/v1/chat/completions">
                </div>
                <div style="margin-bottom: 20px;">
                    <label for="api-key" style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">API 密钥:</label>
                    <input type="password" id="api-key" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;" placeholder="输入您的API密钥">
                    <div style="font-size: 12px; color: #666; margin-top: 8px;">
                        编辑现有API时,如不需要修改密钥请留空
                    </div>
                </div>
                <div style="margin-bottom: 20px;">
                    <label for="api-model" style="display: block; margin-bottom: 8px; color: #333; font-weight: 500;">模型名称:</label>
                    <input type="text" id="api-model" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;" placeholder="例如:gpt-3.5-turbo">
                </div>
                <div style="text-align: right;">
                    <button id="cancel-api-form" style="padding: 8px 20px; background: #f5f5f5; color: #333; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-right: 10px; cursor: pointer;">取消</button>
                    <button id="save-api-form" style="padding: 8px 20px; background-color: #4285f4; color: white; border: none; border-radius: 4px; font-size: 14px; cursor: pointer;">保存</button>
                </div>
            </div>
        `;

        // 创建底部按钮区域
        const footerHtml = `
            <div class="settings-footer" style="padding: 15px 20px; border-top: 1px solid #eee; text-align: right; background: white; border-radius: 0 0 8px 8px; flex: 0 0 auto;">
                <button id="cancel-settings" style="padding: 8px 20px; background: #f5f5f5; color: #333; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-right: 10px; cursor: pointer;">取消</button>
                <button id="save-settings" style="padding: 8px 20px; background-color: #4285f4; color: white; border: none; border-radius: 4px; font-size: 14px; cursor: pointer;">保存</button>
            </div>
        `;

        settingsPanel.innerHTML = tabsHtml + contentContainerHtml + apiFormHtml + footerHtml;
        document.body.appendChild(settingsPanel);

        // 添加温度滑块值变化的事件监听器
        document.getElementById('temperature').addEventListener('input', function() {
            document.getElementById('temperature-value').textContent = this.value;
        });

        // 工具函数:更新设置面板视图
        function updateSettingsPanelView() {
            // 更新标签页状态
            document.getElementById('general-tab').style.display = settings.currentTab === 'general' ? 'block' : 'none';
            document.getElementById('api-tab').style.display = settings.currentTab === 'api' ? 'block' : 'none';
            document.getElementById('api-form').style.display = 'none';
            
            // 更新标签按钮样式
            const generalTabBtn = document.getElementById('general-tab-btn');
            const apiTabBtn = document.getElementById('api-tab-btn');
            
            if (settings.currentTab === 'general') {
                generalTabBtn.style.borderBottom = '2px solid #4285f4';
                generalTabBtn.style.color = '#4285f4';
                apiTabBtn.style.borderBottom = '2px solid transparent';
                apiTabBtn.style.color = '#666';
            } else {
                apiTabBtn.style.borderBottom = '2px solid #4285f4';
                apiTabBtn.style.color = '#4285f4';
                generalTabBtn.style.borderBottom = '2px solid transparent';
                generalTabBtn.style.color = '#666';
            }
            
            // 更新表单值
            document.getElementById('systemPrompt').value = settings.systemPrompt;
            document.getElementById('temperature').value = settings.temperature;
            document.getElementById('temperature-value').textContent = settings.temperature;
            document.getElementById('showSourceLanguage').checked = settings.showSourceLanguage;
            document.getElementById('useStreaming').checked = settings.useStreaming;
            document.getElementById('useTranslationContext').checked = settings.useTranslationContext;
            document.getElementById('contextSize').value = settings.contextSize;
            document.getElementById('detectArticleContent').checked = settings.detectArticleContent;
            document.getElementById('fullPageTranslationSelector').value = settings.fullPageTranslationSelector;
            document.getElementById('excludeSelectors').value = settings.excludeSelectors;
            
            // 更新API列表
            updateApiList();
        }

        // 事件处理程序
        document.getElementById('general-tab-btn').addEventListener('click', function() {
            settings.currentTab = 'general';
            updateSettingsPanelView();
        });

        document.getElementById('api-tab-btn').addEventListener('click', function() {
            settings.currentTab = 'api';
            updateSettingsPanelView();
        });

        // 添加API按钮事件
        document.getElementById('add-api-btn').addEventListener('click', function() {
            document.getElementById('api-tab').style.display = 'none';
            document.getElementById('api-form').style.display = 'block';
            document.getElementById('api-form-title').textContent = '添加 API';
            document.getElementById('api-form-index').value = '-1';
            document.getElementById('api-name').value = '';
            document.getElementById('api-endpoint').value = '';
            document.getElementById('api-key').value = '';
            document.getElementById('api-model').value = '';
        });

        // API表单取消按钮事件
        document.getElementById('cancel-api-form').addEventListener('click', function() {
            document.getElementById('api-form').style.display = 'none';
            document.getElementById('api-tab').style.display = 'block';
        });

        // API表单保存按钮事件
        document.getElementById('save-api-form').addEventListener('click', function() {
            const index = parseInt(document.getElementById('api-form-index').value);
            const apiConfig = {
                name: document.getElementById('api-name').value,
                apiEndpoint: document.getElementById('api-endpoint').value,
                model: document.getElementById('api-model').value
            };

            const apiKey = document.getElementById('api-key').value;
            if (apiKey) {
                apiConfig.apiKey = apiKey;
            } else if (index !== -1) {
                apiConfig.apiKey = settings.apiConfigs[index].apiKey;
            }

            if (index === -1) {
                settings.apiConfigs.push(apiConfig);
            } else {
                settings.apiConfigs[index] = apiConfig;
            }

            document.getElementById('api-form').style.display = 'none';
            document.getElementById('api-tab').style.display = 'block';
            updateApiList();
        });

        // 设置面板的取消和保存按钮事件
        document.getElementById('cancel-settings').addEventListener('click', function() {
            settingsPanel.style.display = 'none';
        });

        document.getElementById('save-settings').addEventListener('click', function() {
            settings = {
                ...settings,
                systemPrompt: document.getElementById('systemPrompt').value,
                useStreaming: document.getElementById('useStreaming').checked,
                showSourceLanguage: document.getElementById('showSourceLanguage').checked,
                detectArticleContent: document.getElementById('detectArticleContent').checked,
                temperature: parseFloat(document.getElementById('temperature').value),
                fullPageTranslationSelector: document.getElementById('fullPageTranslationSelector').value,
                excludeSelectors: document.getElementById('excludeSelectors').value,
                useTranslationContext: document.getElementById('useTranslationContext').checked,
                contextSize: parseInt(document.getElementById('contextSize').value) || 3,
                currentTab: settings.currentTab
            };

            GM_setValue('translatorSettings', settings);
            settingsPanel.style.display = 'none';
        });

        // 绑定API列表项的事件处理程序
        function bindApiListEvents() {
            document.querySelectorAll('.use-api-btn').forEach(button => {
                button.addEventListener('click', function() {
                    const index = parseInt(this.dataset.index);
                    settings.currentApiIndex = index;
                    updateApiList();
                });
            });

            document.querySelectorAll('.edit-api-btn').forEach(button => {
                button.addEventListener('click', function() {
                    const index = parseInt(this.dataset.index);
                    const api = settings.apiConfigs[index];
                    document.getElementById('api-form-title').textContent = '编辑 API';
                    document.getElementById('api-form-index').value = index;
                    document.getElementById('api-name').value = api.name;
                    document.getElementById('api-endpoint').value = api.apiEndpoint;
                    document.getElementById('api-key').value = '';
                    document.getElementById('api-model').value = api.model;
                    
                    // 在内容区域显示API表单,而不是隐藏内容区域
                    document.getElementById('api-form').style.display = 'block';
                    document.getElementById('api-tab').style.display = 'none';
                });
            });

            document.querySelectorAll('.delete-api-btn').forEach(button => {
                button.addEventListener('click', function() {
                    const index = parseInt(this.dataset.index);
                    if (confirm('确定要删除这个API配置吗?')) {
                        settings.apiConfigs.splice(index, 1);
                        if (settings.currentApiIndex === index) {
                            settings.currentApiIndex = 0;
                        } else if (settings.currentApiIndex > index) {
                            settings.currentApiIndex--;
                        }
                        updateApiList();
                    }
                });
            });
        }

        // 更新API列表的函数
        function updateApiList() {
            const apiListHtml = settings.apiConfigs.map((api, index) => `
                <div class="api-item" style="margin-bottom: 15px; padding: 15px; border: 1px solid #ddd; border-radius: 4px; position: relative; ${index === settings.currentApiIndex ? 'background-color: #f0f8ff; border-color: #4285f4;' : ''}">
                    <div style="position: absolute; top: 15px; right: 15px;">
                        ${index === settings.currentApiIndex ?
                            '<span style="color: #4CAF50; font-weight: 500;">✓ 当前使用</span>' :
                            `<button class="use-api-btn" data-index="${index}" style="padding: 4px 12px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 8px; font-size: 12px;">使用</button>`
                        }
                        <button class="edit-api-btn" data-index="${index}" style="padding: 4px 12px; background-color: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 8px; font-size: 12px;">编辑</button>
                        ${settings.apiConfigs.length > 1 ?
                            `<button class="delete-api-btn" data-index="${index}" style="padding: 4px 12px; background-color: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">删除</button>` : ''
                        }
                    </div>
                    <div style="margin-bottom: 8px;"><strong style="color: #333;">名称:</strong> <span style="color: #666;">${api.name}</span></div>
                    <div style="margin-bottom: 8px;"><strong style="color: #333;">端点:</strong> <span style="color: #666;">${api.apiEndpoint}</span></div>
                    <div style="margin-bottom: 8px;"><strong style="color: #333;">密钥:</strong> <span style="color: #666;">${api.apiKey ? '******' + api.apiKey.substring(api.apiKey.length - 4) : '未设置'}</span></div>
                    <div><strong style="color: #333;">模型:</strong> <span style="color: #666;">${api.model}</span></div>
                </div>
            `).join('');

            document.getElementById('api-list').innerHTML = apiListHtml;
            bindApiListEvents();
        }

        // 显示和隐藏面板的方法
        const show = () => {
            settingsPanel.style.display = 'flex';
            settingsPanel.style.flexDirection = 'column';
            updateSettingsPanelView();
        };

        const hide = () => {
            settingsPanel.style.display = 'none';
        };

        return {
            panel: settingsPanel,
            show: show,
            hide: hide,
            updateView: updateSettingsPanelView
        };
    }

    function createSettingsButton() {
        const settingsButton = document.createElement('div');
        settingsButton.id = 'translator-settings-button';
        settingsButton.innerHTML = '⚙️';
        settingsButton.title = '翻译设置';
        settingsButton.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 40px;
            height: 40px;
            background-color: rgba(66, 133, 244, 0.8);
            color: white;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 20px;
            cursor: pointer;
            z-index: 9999;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        `;

        document.body.appendChild(settingsButton);

        const settingsPanel = createSettingsPanel();

        settingsButton.addEventListener('click', function() {
            // 显示面板并设置flex布局
            settingsPanel.panel.style.display = 'flex';
            settingsPanel.panel.style.flexDirection = 'column';
            
            // 强制确保有一个有效的当前标签
            if (!settings.currentTab || (settings.currentTab !== 'general' && settings.currentTab !== 'api')) {
                settings.currentTab = 'general';
            }
            
            // 由于没有updateSettingsPanelView函数,直接更新当前标签的显示
            const generalTab = document.getElementById('general-tab');
            const apiTab = document.getElementById('api-tab');
            
            if (generalTab && apiTab) {
                generalTab.style.display = settings.currentTab === 'general' ? 'block' : 'none';
                apiTab.style.display = settings.currentTab === 'api' ? 'block' : 'none';
            }
        });

        return settingsButton;
    }

    // 创建翻译整页按钮
    function createTranslatePageButton() {
        const button = document.createElement('div');
        button.id = 'translate-page-button';
        button.innerHTML = '翻译整页';
        button.title = '翻译当前页面内容';
        button.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 70px;
            padding: 8px 12px;
            background-color: rgba(66, 133, 244, 0.8);
            color: white;
            border-radius: 20px;
            font-size: 14px;
            cursor: pointer;
            z-index: 9999;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
            display: flex;
            align-items: center;
            justify-content: center;
        `;

        document.body.appendChild(button);

        button.addEventListener('click', function () {
            if (isTranslatingFullPage) {
                alert('正在翻译中,请稍候...');
                return;
            }
            translateFullPage();
        });

        return button;
    }

    // 创建翻译进度条
    function createProgressBar() {
        const progressContainer = document.createElement('div');
        progressContainer.id = 'translation-progress-container';
        progressContainer.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 4px;
            background-color: #f3f3f3;
            z-index: 10001;
            display: none;
        `;

        const progressBar = document.createElement('div');
        progressBar.id = 'translation-progress-bar';
        progressBar.style.cssText = `
            height: 100%;
            width: 0%;
            background-color: #4285f4;
            transition: width 0.3s;
        `;

        progressContainer.appendChild(progressBar);
        document.body.appendChild(progressContainer);

        return progressContainer;
    }

    // 深度优先遍历DOM树
    function extractTextNodesFromElement(element, textSegments, depth = 0, excludeElements = null, isInReferencesSection = false, referencesSectionElement = null) {
        // 如果没有传入排除元素,使用全局的
        if (excludeElements === null) {
            excludeElements = settings.excludeSelectors ?
                Array.from(document.querySelectorAll(settings.excludeSelectors)) : [];
        }
        
        // 检查是否进入了参考文献区域
        if (element === referencesSectionElement) {
            isInReferencesSection = true;
        }

        // 如果这个元素在排除列表中,跳过
        if (excludeElements.includes(element)) {
            return;
        }

        try {
            // 只对元素节点检查样式
            if (element.nodeType === Node.ELEMENT_NODE) {
                // 检查元素是否隐藏
                const style = window.getComputedStyle(element);
                if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
                    return;
                }

                // 特殊处理参考文献条目
                // 参考文献条目通常以数字加方括号开头,如 [1], [2] 等
                if (isInReferencesSection && /^\s*\[\d+\]/.test(element.textContent)) {
                    console.log("检测到参考文献条目:", element.textContent.substring(0, 50));
                    textSegments.push({
                        text: element.textContent,
                        element: element,
                        htmlContent: element.innerHTML,
                        isReferenceItem: true,
                        isInReferencesSection: true,
                        original: element.textContent
                    });
                    return; // 参考文献条目作为整体处理,不再递归子节点
                }

                // 对于块级元素,我们在处理完所有子节点后添加换行标记
                const isBlockElement = style.display === 'block' || 
                                     style.display === 'flex' || 
                                     style.display === 'grid' || 
                                     element.tagName === 'DIV' || 
                                     element.tagName === 'P' || 
                                     element.tagName === 'SECTION' || 
                                     element.tagName === 'ARTICLE' ||
                                     element.tagName === 'LI';
                
                // 递归处理子节点
                for (let i = 0; i < element.childNodes.length; i++) {
                    extractTextNodesFromElement(element.childNodes[i], textSegments, depth + 1, excludeElements, isInReferencesSection, referencesSectionElement);
                }

                // 添加换行标记
                if (isBlockElement && element.textContent.trim() && element.childNodes.length > 0) {
                    textSegments.push({ isNewLine: true });
                }
            } else if (element.nodeType === Node.TEXT_NODE) {
                // 文本节点
                const text = element.textContent.trim();
                if (text) {
                    textSegments.push({
                        text: text,
                        node: element,
                        original: element.textContent,
                        parentElement: element.parentElement,
                        isInReferencesSection: isInReferencesSection
                    });
                }
            }
        } catch (error) {
            console.warn("处理元素时出错:", error);
        }
    }
    
    // 合并文本段落,特殊处理参考文献条目
    function mergeTextSegments(textSegments) {
        // 合并相邻文本段落,特殊处理参考文献条目
        const mergedSegments = [];
        let currentSegment = { text: '', nodes: [], isReferenceItem: false };
        let referenceItems = [];

        for (const segment of textSegments) {
            // 如果是参考文献条目,单独处理
            if (segment.isReferenceItem) {
                // 如果当前段落有内容,先保存
                if (currentSegment.text.trim()) {
                    mergedSegments.push({ ...currentSegment });
                    currentSegment = { text: '', nodes: [], isReferenceItem: false };
                }

                // 添加参考文献条目
                referenceItems.push(segment);
                mergedSegments.push({
                    text: segment.text,
                    element: segment.element,
                    htmlContent: segment.htmlContent,
                    isReferenceItem: true,
                    nodes: [{ node: segment.element, original: segment.original }]
                });

                continue;
            }

            // 如果是参考文献区域但非条目,可能需要特殊处理
            if (segment.isInReferencesSection && !segment.isNewLine) {
                // 可能属于参考文献区域的普通文本,保持段落结构
                if (currentSegment.isInReferencesSection !== true) {
                    // 如果当前段落不是参考文献区域,创建新段落
                    if (currentSegment.text.trim()) {
                        mergedSegments.push({ ...currentSegment });
                    }
                    currentSegment = {
                        text: segment.text,
                        nodes: segment.node ? [{ node: segment.node, original: segment.original }] : [],
                        isInReferencesSection: true
                    };
                } else {
                    // 属于同一参考文献区域段落,合并文本
                    currentSegment.text += (currentSegment.text ? ' ' : '') + segment.text;
                    if (segment.node) {
                        currentSegment.nodes.push({
                            node: segment.node,
                            original: segment.original,
                            parentElement: segment.parentElement
                        });
                    }
                }
                continue;
            }

            if (segment.isNewLine) {
                if (currentSegment.text.trim()) {
                    mergedSegments.push({ ...currentSegment });
                    currentSegment = { text: '', nodes: [], isReferenceItem: false };
                }
                continue;
            }

            currentSegment.text += (currentSegment.text ? ' ' : '') + segment.text;
            if (segment.node) {
                currentSegment.nodes.push({
                    node: segment.node,
                    original: segment.original,
                    parentElement: segment.parentElement
                });
            }

            // 如果当前段落已经足够长,创建新段落
            if (currentSegment.text.length >= settings.fullPageMaxSegmentLength) {
                mergedSegments.push({ ...currentSegment });
                currentSegment = { text: '', nodes: [], isReferenceItem: false };
            }
        }

        // 添加最后一个段落
        if (currentSegment.text.trim()) {
            mergedSegments.push(currentSegment);
        }

        console.log(`提取到${mergedSegments.length}个文本段落,其中参考文献条目${referenceItems.length}个`);
        return mergedSegments;
    }
    
    // 修改extractPageContent函数,使用合并函数
    function extractPageContent() {
        console.log("开始提取页面内容");
        
        // 如果启用了自动识别文章主体,尝试识别
        if (settings.detectArticleContent) {
            const rawSegments = detectMainContent();
            if (rawSegments) {
                console.log("成功识别到文章主体内容");
                return mergeTextSegments(rawSegments);
            } else {
                console.log("未能识别出文章主体,回退到常规翻译模式");
            }
        }
        
        const contentSelector = settings.fullPageTranslationSelector || 'body';
        const contentElement = document.querySelector(contentSelector);

        if (!contentElement) {
            console.error(`未找到匹配选择器的元素: ${contentSelector}`);
            return [];
        }

        // 获取所有需要排除的元素
        const excludeElements = settings.excludeSelectors ?
            Array.from(document.querySelectorAll(settings.excludeSelectors)) : [];

        // 检测参考文献区域
        const referencesElements = Array.from(document.querySelectorAll('h2, h3, h4')).filter(el =>
            el.textContent.toLowerCase().includes('reference') ||
            el.textContent.toLowerCase().includes('bibliography') ||
            el.textContent.includes('参考文献')
        );

        let isInReferencesSection = false;
        let referencesSectionElement = null;

        if (referencesElements.length > 0) {
            referencesSectionElement = referencesElements[0];
            console.log("检测到参考文献区域:", referencesSectionElement.textContent);
        }
        
        // 存储提取的文本段落
        const textSegments = [];
        
        // 深度优先遍历DOM树
        extractTextNodesFromElement(contentElement, textSegments);
        
        // 合并相邻文本段落
        return mergeTextSegments(textSegments);
    }
    
    // 识别网页中的文章主体内容
    function detectMainContent() {
        // 可能的文章主体容器选择器
        const possibleArticleSelectors = [
            'article',
            '.article', 
            '.post', 
            '.content', 
            '.post-content',
            '.article-content', 
            '.entry-content',
            '.main-content',
            'main',
            '#main',
            '#content',
            '.story',
            '[itemprop="articleBody"]',
            '[role="main"]',
            // 添加Divi主题常用的内容容器选择器
            '.et_pb_post_content',
            '.et_pb_module.et_pb_post_content',
            '.et_pb_text_inner',
            '.et_pb_post_content_0_tb_body',
            '.et_builder_inner_content',
            // 添加更多通用选择器
            '.single-content',
            '.single-post-content',
            '.page-content',
            '.post-text'
        ];
        
        // 尝试这些选择器,寻找包含最多内容的元素
        let bestElement = null;
        let maxTextLength = 0;
        
        for (const selector of possibleArticleSelectors) {
            const elements = document.querySelectorAll(selector);
            
            for (const element of elements) {
                // 计算文本内容长度
                const textLength = element.textContent.trim().length;
                
                // 如果比之前找到的更长,更新最佳元素
                if (textLength > maxTextLength) {
                    maxTextLength = textLength;
                    bestElement = element;
                }
            }
        }
        
        // 如果找到了合适的元素
        if (bestElement && maxTextLength > 300) { // 降低阈值,从500改为300个字符,更容易识别较短的文章
            console.log(`检测到文章主体: ${bestElement.tagName}${bestElement.id ? '#'+bestElement.id : ''}${bestElement.className ? '.'+bestElement.className.replace(/\s+/g, '.') : ''}`);
            
            // 获取所有需要排除的元素
            const excludeElements = settings.excludeSelectors ?
                Array.from(document.querySelectorAll(settings.excludeSelectors)) : [];
                
            // 检测参考文献区域
            const referencesElements = Array.from(bestElement.querySelectorAll('h2, h3, h4')).filter(el =>
                el.textContent.toLowerCase().includes('reference') ||
                el.textContent.toLowerCase().includes('bibliography') ||
                el.textContent.includes('参考文献')
            );
            
            let isInReferencesSection = false;
            let referencesSectionElement = null;
            
            if (referencesElements.length > 0) {
                referencesSectionElement = referencesElements[0];
                console.log("在文章主体中检测到参考文献区域:", referencesSectionElement.textContent);
            }
            
            // 存储提取的文本段落
            const textSegments = [];
            
            // 深度优先遍历DOM树
            extractTextNodesFromElement(bestElement, textSegments, 0, excludeElements, isInReferencesSection, referencesSectionElement);
            
            return textSegments.length > 0 ? textSegments : null;
        }
        
        // 尝试基于内容区域比例的检测策略
        if (!bestElement) {
            console.log("尝试使用内容区域比例检测策略");
            // 获取所有段落和文本块
            const textBlocks = Array.from(document.querySelectorAll('p, article, .post, .content, div > h1 + p, div > h2 + p'));
            
            // 按照父元素对文本块进行分组
            const contentGroups = {};
            for (const block of textBlocks) {
                if (!block.textContent.trim()) continue;
                
                // 获取所有父元素,直到body
                let parent = block.parentElement;
                while (parent && parent.tagName !== 'BODY') {
                    const key = parent.tagName + (parent.id ? '#' + parent.id : '') + 
                                (parent.className ? '.' + parent.className.replace(/\s+/g, '.') : '');
                    
                    contentGroups[key] = contentGroups[key] || { element: parent, textLength: 0 };
                    contentGroups[key].textLength += block.textContent.trim().length;
                    
                    parent = parent.parentElement;
                }
            }
            
            // 找出包含最多文本内容的容器
            let bestContentGroup = null;
            let maxGroupTextLength = 0;
            
            for (const key in contentGroups) {
                if (contentGroups[key].textLength > maxGroupTextLength) {
                    maxGroupTextLength = contentGroups[key].textLength;
                    bestContentGroup = contentGroups[key];
                }
            }
            
            if (bestContentGroup && maxGroupTextLength > 300) {
                console.log(`通过内容区域比例检测到文章主体: ${bestContentGroup.element.tagName}${bestContentGroup.element.id ? '#'+bestContentGroup.element.id : ''}${bestContentGroup.element.className ? '.'+bestContentGroup.element.className.replace(/\s+/g, '.') : ''}`);
                
                // 获取所有需要排除的元素
                const excludeElements = settings.excludeSelectors ?
                    Array.from(document.querySelectorAll(settings.excludeSelectors)) : [];
                
                // 存储提取的文本段落
                const textSegments = [];
                
                // 深度优先遍历DOM树
                extractTextNodesFromElement(bestContentGroup.element, textSegments, 0, excludeElements, false, null);
                
                return textSegments.length > 0 ? textSegments : null;
            }
        }
        
        return null;
    }

    // 翻译整个页面
    function translateFullPage() {
        // 确保使用最新的API设置
        syncApiSettings();
        console.log('全文翻译 - 确保使用最新API设置:', settings.apiEndpoint, settings.model);
        
        if (!settings.apiKey) {
            alert('请先在设置中配置API密钥');
            const settingsPanel = document.getElementById('translator-settings-panel') || createSettingsPanel();
            settingsPanel.style.display = 'block';
            return;
        }

        // 如果当前已经在翻译中但被暂停了,则恢复翻译
        if (isTranslatingFullPage && isTranslationPaused) {
            isTranslationPaused = false;
            const pauseBtn = document.getElementById('pause-translation-button');
            if (pauseBtn) {
                pauseBtn.textContent = '暂停翻译';
                pauseBtn.title = '暂停当前的翻译任务';
            }
            // 恢复翻译,从最后翻译的段落的下一段开始
            translateNextSegment(lastTranslatedIndex + 1);
            return;
        }

        // 如果当前正在翻译且未暂停,则不做任何操作
        if (isTranslatingFullPage && !isTranslationPaused) {
            alert('正在翻译中,请稍候...');
            return;
        }

        // 开始新的翻译任务
        isTranslatingFullPage = true;
        isTranslationPaused = false;
        lastTranslatedIndex = -1;
        
        // 清除可能存在的暂停/停止按钮
        removeControlButtons();
        
        // 清除可能存在的状态提示元素
        const existingStatusElement = document.getElementById('translation-status');
        if (existingStatusElement) {
            existingStatusElement.remove();
        }
        
        // 提取页面内容
        translationSegments = extractPageContent();

        if (translationSegments.length === 0) {
            alert('未找到可翻译的内容');
            isTranslatingFullPage = false;
            return;
        }

        // 显示进度条
        const progressContainer = document.getElementById('translation-progress-container') || createProgressBar();
        const progressBar = document.getElementById('translation-progress-bar');
        progressContainer.style.display = 'block';
        progressBar.style.width = '0%';

        // 创建一个状态提示
        const statusElement = document.createElement('div');
        statusElement.id = 'translation-status';
        statusElement.style.cssText = `
            position: fixed;
            top: 10px;
            left: 50%;
            transform: translateX(-50%);
            background-color: rgba(66, 133, 244, 0.9);
            color: white;
            padding: 8px 15px;
            border-radius: 20px;
            font-size: 14px;
            z-index: 10001;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
            transition: background-color 0.3s;
            min-width: 200px;
            text-align: center;
        `;
        statusElement.textContent = `正在翻译 (0/${translationSegments.length})`;
        document.body.appendChild(statusElement);

        // 记录所有段落的原始文本,用于恢复
        originalTexts = translationSegments.map(segment => ({
            nodes: segment.nodes.map(n => ({ node: n.node, text: n.original }))
        }));

        // 创建用于切换原文/译文的按钮
        createToggleButton();
        
        // 创建暂停和停止按钮
        createControlButtons();

        // 开始翻译第一个段落
        translateNextSegment(0);
    }
    
    // 创建暂停和停止按钮
    function createControlButtons() {
        // 创建暂停按钮
        const pauseButton = document.createElement('div');
        pauseButton.id = 'pause-translation-button';
        pauseButton.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 310px;
            padding: 8px 12px;
            background-color: rgba(255, 152, 0, 0.8);
            color: white;
            border-radius: 20px;
            font-size: 14px;
            cursor: pointer;
            z-index: 9999;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        `;
        pauseButton.textContent = '暂停翻译';
        pauseButton.title = '暂停当前的翻译任务';
        document.body.appendChild(pauseButton);

        // 创建停止按钮
        const stopButton = document.createElement('div');
        stopButton.id = 'stop-translation-button';
        stopButton.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 240px;
            padding: 8px 12px;
            background-color: rgba(244, 67, 54, 0.8);
            color: white;
            border-radius: 20px;
            font-size: 14px;
            cursor: pointer;
            z-index: 9999;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        `;
        stopButton.textContent = '停止翻译';
        stopButton.title = '停止当前的翻译任务';
        document.body.appendChild(stopButton);

        // 绑定暂停按钮事件
        pauseButton.addEventListener('click', function() {
            if (!isTranslatingFullPage) return;

            isTranslationPaused = !isTranslationPaused;
            if (isTranslationPaused) {
                pauseButton.textContent = '继续翻译';
                pauseButton.title = '继续未完成的翻译任务';
                
                // 更新状态提示
                const statusElement = document.getElementById('translation-status');
                if (statusElement) {
                    statusElement.textContent = `翻译已暂停 (${lastTranslatedIndex + 1}/${translationSegments.length})`;
                }
                
                // 显示切换按钮,允许在暂停时切换查看原文/译文
                const toggleButton = document.getElementById('toggle-translation-button');
                if (toggleButton) toggleButton.style.display = 'block';
            } else {
                pauseButton.textContent = '暂停翻译';
                pauseButton.title = '暂停当前的翻译任务';
                
                // 恢复翻译
                translateNextSegment(lastTranslatedIndex + 1);
            }
        });

        // 绑定停止按钮事件
        stopButton.addEventListener('click', function() {
            if (!isTranslatingFullPage) return;

            // 确认是否要停止翻译
            if (confirm('确定要停止翻译吗?已翻译的内容将保留。')) {
                // 停止翻译
                isTranslatingFullPage = false;
                isTranslationPaused = false;
                
                // 更新状态提示
                const statusElement = document.getElementById('translation-status');
                if (statusElement) {
                    statusElement.textContent = `翻译已停止 (${lastTranslatedIndex + 1}/${translationSegments.length})`;
                    
                    // 添加关闭按钮
                    if (!statusElement.querySelector('.close-btn')) {
                        const closeButton = document.createElement('span');
                        closeButton.className = 'close-btn';
                        closeButton.style.cssText = `
                            margin-left: 10px;
                            cursor: pointer;
                            font-weight: bold;
                        `;
                        closeButton.textContent = '×';
                        closeButton.addEventListener('click', function() {
                            statusElement.remove();
                            const progressContainer = document.getElementById('translation-progress-container');
                            if (progressContainer) progressContainer.style.display = 'none';
                        });
                        statusElement.appendChild(closeButton);
                    }
                }
                
                // 显示切换按钮
                const toggleButton = document.getElementById('toggle-translation-button');
                if (toggleButton) toggleButton.style.display = 'block';
                
                // 删除控制按钮
                removeControlButtons();
            }
        });
    }
    
    // 移除控制按钮
    function removeControlButtons() {
        const pauseButton = document.getElementById('pause-translation-button');
        if (pauseButton) pauseButton.remove();
        
        const stopButton = document.getElementById('stop-translation-button');
        if (stopButton) stopButton.remove();
    }
    
    // 创建切换按钮
    function createToggleButton() {
        // 检查是否已存在切换按钮
        let toggleButton = document.getElementById('toggle-translation-button');
        
        if (!toggleButton) {
            toggleButton = document.createElement('div');
            toggleButton.id = 'toggle-translation-button';
            toggleButton.style.cssText = `
                position: fixed;
                bottom: 20px;
                right: 180px;
                padding: 8px 12px;
                background-color: rgba(66, 133, 244, 0.8);
                color: white;
                border-radius: 20px;
                font-size: 14px;
                cursor: pointer;
                z-index: 9999;
                box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
                display: none;
            `;
            toggleButton.textContent = '查看原文';
            toggleButton.dataset.showing = 'translation';
            document.body.appendChild(toggleButton);

            // 用于跟踪当前显示状态
            let isShowingTranslation = true;

            // 创建映射,用于跟踪每个节点的翻译状态
            const nodeTranslationMap = new Map();

            toggleButton.addEventListener('click', function () {
                isShowingTranslation = !isShowingTranslation;
                toggleButton.textContent = isShowingTranslation ? '查看原文' : '查看译文';
                toggleButton.dataset.showing = isShowingTranslation ? 'translation' : 'original';

                if (isShowingTranslation) {
                    // 恢复译文
                    translationSegments.forEach((segment, index) => {
                        // 特殊处理参考文献条目
                        if (segment.isReferenceItem && segment.translation && segment.element) {
                            segment.element.innerHTML = segment.translation;
                            return;
                        }

                        if (segment.translation) {
                            if (segment.nodes.length === 1) {
                                // 单节点情况,直接应用翻译
                                const nodeInfo = segment.nodes[0];
                                if (nodeInfo.node && nodeInfo.node.nodeType === Node.TEXT_NODE) {
                                    nodeInfo.node.textContent = segment.translation;
                                    // 记录这个节点已经被翻译过
                                    nodeTranslationMap.set(nodeInfo.node, true);
                                }
                            } else {
                                // 多节点情况,使用比例分配
                                const totalOriginalLength = segment.nodes.reduce(
                                    (sum, nodeInfo) => sum + (nodeInfo.original ? nodeInfo.original.length : 0), 0);

                                if (totalOriginalLength > 0) {
                                    let startPos = 0;
                                    for (let i = 0; i < segment.nodes.length; i++) {
                                        const nodeInfo = segment.nodes[i];
                                        if (nodeInfo.node && nodeInfo.node.nodeType === Node.TEXT_NODE && nodeInfo.original) {
                                            // 计算该节点在原始文本中的比例
                                            const ratio = nodeInfo.original.length / totalOriginalLength;
                                            // 计算应该分配给该节点的翻译文本长度
                                            const chunkLength = Math.round(segment.translation.length * ratio);
                                            // 提取翻译文本的一部分
                                            let chunk = '';
                                            if (i === segment.nodes.length - 1) {
                                                // 最后一个节点,获取剩余所有文本
                                                chunk = segment.translation.substring(startPos);
                                            } else {
                                                // 非最后节点,按比例获取
                                                chunk = segment.translation.substring(startPos, startPos + chunkLength);
                                                startPos += chunkLength;
                                            }

                                            // 更新节点文本
                                            nodeInfo.node.textContent = chunk;
                                            // 记录这个节点已经被翻译过
                                            nodeTranslationMap.set(nodeInfo.node, true);
                                        }
                                    }
                                }
                            }
                        }
                    });
                } else {
                    // 恢复原文 - 使用原始数据而不是依赖当前DOM状态
                    nodeTranslationMap.clear(); // 清除翻译状态记录

                    // 先处理参考文献条目
                    translationSegments.forEach((segment) => {
                        if (segment.isReferenceItem && segment.element && segment.originalHtml) {
                            segment.element.innerHTML = segment.originalHtml;
                        }
                    });

                    // 再处理普通段落
                    originalTexts.forEach((originalSegment) => {
                        originalSegment.nodes.forEach(nodeInfo => {
                            if (nodeInfo.node && nodeInfo.node.nodeType === Node.TEXT_NODE) {
                                nodeInfo.node.textContent = nodeInfo.text;
                            }
                        });
                    });
                }
            });
        }
        
        return toggleButton;
    }

    // 添加一个专门用于翻译参考文献条目的函数
    function translateReferenceItem(referenceItem, callback) {
        if (!referenceItem || !referenceItem.text) {
            callback(null);
            return;
        }

        console.log("翻译参考文献条目:", referenceItem.text.substring(0, 50) + "...");

        // 如果有HTML内容,使用更精确的方法
        if (referenceItem.htmlContent) {
            // 提取HTML中的纯文本部分
            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = referenceItem.htmlContent;

            // 递归处理HTML元素,只翻译文本节点
            function processNode(node, callback) {
                if (node.nodeType === Node.TEXT_NODE) {
                    const text = node.textContent.trim();
                    if (text && text.length > 1) {
                        // 翻译文本节点
                        translateText(text, function (result) {
                            if (result.type === 'complete' || result.type === 'stream-end') {
                                node.textContent = result.content;
                                callback(true);
                            } else {
                                // 保持原文
                                callback(false);
                            }
                        }, false);
                    } else {
                        callback(true); // 空文本,直接继续
                    }
                } else if (node.nodeType === Node.ELEMENT_NODE) {
                    // 对于元素节点,递归处理其子节点
                    const childNodes = Array.from(node.childNodes);
                    processNodeSequentially(childNodes, 0, function () {
                        callback(true);
                    });
                } else {
                    callback(true); // 其他类型节点,直接继续
                }
            }

            // 按顺序处理节点列表
            function processNodeSequentially(nodes, index, finalCallback) {
                if (index >= nodes.length) {
                    finalCallback();
                    return;
                }

                processNode(nodes[index], function (success) {
                    // 无论成功与否,继续处理下一个节点
                    setTimeout(() => processNodeSequentially(nodes, index + 1, finalCallback), 10);
                });
            }

            // 开始处理整个HTML片段
            processNodeSequentially(Array.from(tempDiv.childNodes), 0, function () {
                // 处理完成后,返回完整的翻译后HTML
                callback(tempDiv.innerHTML);
            });
        } else {
            // 如果没有HTML内容,直接翻译文本
            translateText(referenceItem.text, function (result) {
                if (result.type === 'complete' || result.type === 'stream-end') {
                    callback(result.content);
                } else {
                    callback(null);
                }
            }, false);
        }
    }

    // 创建翻译按钮
    function createTranslateButton() {
        const button = document.createElement('div');
        button.className = 'translate-button';
        button.innerHTML = '翻译';
        button.style.cssText = `
            position: absolute;
            background-color: rgba(66, 133, 244, 0.8);
            color: white;
            padding: 5px 10px;
            border-radius: 4px;
            font-size: 14px;
            cursor: pointer;
            z-index: 9999;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
            opacity: 0.8;
            transition: opacity 0.2s;
        `;

        button.addEventListener('mouseover', function () {
            button.style.opacity = '1';
        });

        button.addEventListener('mouseout', function () {
            button.style.opacity = '0.8';
        });

        return button;
    }

    // 创建历史记录面板
    function createHistoryPanel() {
        const panel = document.createElement('div');
        panel.id = 'translator-history-panel';
        panel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 600px;
            max-height: 80vh;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
            z-index: 10000;
            display: none;
            padding: 20px;
            overflow-y: auto;
        `;

        panel.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
                <h2 style="margin: 0;">翻译历史</h2>
                <div style="display: flex; gap: 10px;">
                    <button id="clear-history" style="padding: 5px 10px; color: #666;">清除历史</button>
                    <div id="close-history" style="cursor: pointer; font-size: 20px; color: #666;">×</div>
                </div>
            </div>
            <div id="history-list"></div>
        `;

        document.body.appendChild(panel);

        // 清除历史记录按钮
        document.getElementById('clear-history').addEventListener('click', function () {
            if (confirm('确定要清除所有翻译历史吗?')) {
                translationHistory = [];
                GM_setValue('translationHistory', []);
                updateHistoryList();
            }
        });

        // 关闭按钮
        document.getElementById('close-history').addEventListener('click', function () {
            panel.style.display = 'none';
        });

        return panel;
    }

    // 创建收藏夹面板
    function createFavoritesPanel() {
        const panel = document.createElement('div');
        panel.id = 'translator-favorites-panel';
        panel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 600px;
            max-height: 80vh;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
            z-index: 10000;
            display: none;
            padding: 20px;
            overflow-y: auto;
        `;

        panel.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
                <h2 style="margin: 0;">收藏夹</h2>
                <div style="display: flex; gap: 10px;">
                    <button id="clear-favorites" style="padding: 5px 10px; color: #666;">清除收藏</button>
                    <div id="close-favorites" style="cursor: pointer; font-size: 20px; color: #666;">×</div>
                </div>
            </div>
            <div id="favorites-list"></div>
        `;

        document.body.appendChild(panel);

        // 清除收藏按钮
        document.getElementById('clear-favorites').addEventListener('click', function () {
            if (confirm('确定要清除所有收藏吗?')) {
                translationFavorites = [];
                GM_setValue('translationFavorites', []);
                updateFavoritesList();
            }
        });

        // 关闭按钮
        document.getElementById('close-favorites').addEventListener('click', function () {
            panel.style.display = 'none';
        });

        return panel;
    }

    // 更新历史记录列表
    function updateHistoryList() {
        const historyList = document.getElementById('history-list');
        if (!historyList) return;

        if (translationHistory.length === 0) {
            historyList.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">暂无翻译历史</div>';
            return;
        }

        // 清除之前的内容
        historyList.innerHTML = '';

        // 为每个历史记录创建DOM元素
        translationHistory.forEach((item, index) => {
            const historyItem = document.createElement('div');
            historyItem.style.cssText = 'border-bottom: 1px solid #eee; padding: 10px 0;';

            const header = document.createElement('div');
            header.style.cssText = 'display: flex; justify-content: space-between; margin-bottom: 5px;';

            const timestamp = document.createElement('span');
            timestamp.style.color = '#666';
            timestamp.textContent = item.timestamp;

            const buttons = document.createElement('div');

            const copyBtn = document.createElement('button');
            copyBtn.textContent = '复制';
            copyBtn.style.cssText = 'padding: 2px 5px; margin-right: 5px;';
            copyBtn.addEventListener('click', function () {
                copyTranslation(index);
            });

            const favoriteBtn = document.createElement('button');
            favoriteBtn.textContent = '收藏';
            favoriteBtn.style.cssText = 'padding: 2px 5px;';
            favoriteBtn.addEventListener('click', function () {
                addToFavorites(translationHistory[index]);
                favoriteBtn.textContent = '已收藏';
                favoriteBtn.disabled = true;
            });

            buttons.appendChild(copyBtn);
            buttons.appendChild(favoriteBtn);

            header.appendChild(timestamp);
            header.appendChild(buttons);

            const sourceDiv = document.createElement('div');
            sourceDiv.style.marginBottom = '5px';
            sourceDiv.innerHTML = `<strong>原文:</strong>${item.source}`;

            const translationDiv = document.createElement('div');
            translationDiv.innerHTML = `<strong>译文:</strong>${item.translation}`;

            historyItem.appendChild(header);
            historyItem.appendChild(sourceDiv);
            historyItem.appendChild(translationDiv);

            historyList.appendChild(historyItem);
        });
    }

    // 更新收藏夹列表
    function updateFavoritesList() {
        const favoritesList = document.getElementById('favorites-list');
        if (!favoritesList) return;

        if (translationFavorites.length === 0) {
            favoritesList.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">暂无收藏内容</div>';
            return;
        }

        // 清除之前的内容
        favoritesList.innerHTML = '';

        // 为每个收藏项创建DOM元素
        translationFavorites.forEach((item, index) => {
            const favoriteItem = document.createElement('div');
            favoriteItem.style.cssText = 'border-bottom: 1px solid #eee; padding: 10px 0;';

            const header = document.createElement('div');
            header.style.cssText = 'display: flex; justify-content: space-between; margin-bottom: 5px;';

            const timestamp = document.createElement('span');
            timestamp.style.color = '#666';
            timestamp.textContent = item.timestamp;

            const buttons = document.createElement('div');

            const copyBtn = document.createElement('button');
            copyBtn.textContent = '复制';
            copyBtn.style.cssText = 'padding: 2px 5px; margin-right: 5px;';
            copyBtn.addEventListener('click', function () {
                copyFavorite(index);
            });

            const deleteBtn = document.createElement('button');
            deleteBtn.textContent = '删除';
            deleteBtn.style.cssText = 'padding: 2px 5px;';
            deleteBtn.addEventListener('click', function () {
                removeFromFavorites(index);
            });

            buttons.appendChild(copyBtn);
            buttons.appendChild(deleteBtn);

            header.appendChild(timestamp);
            header.appendChild(buttons);

            const sourceDiv = document.createElement('div');
            sourceDiv.style.marginBottom = '5px';
            sourceDiv.innerHTML = `<strong>原文:</strong>${item.source}`;

            const translationDiv = document.createElement('div');
            translationDiv.style.marginBottom = '5px';
            translationDiv.innerHTML = `<strong>译文:</strong>${item.translation}`;

            const sourceUrlDiv = document.createElement('div');
            sourceUrlDiv.style.cssText = 'font-size: 12px; color: #666;';

            const sourceTitleDiv = document.createElement('div');
            sourceTitleDiv.style.marginBottom = '2px';
            sourceTitleDiv.innerHTML = '<strong>来源:</strong>';

            const sourceLink = document.createElement('a');
            sourceLink.href = item.url;
            sourceLink.target = '_blank';
            sourceLink.style.cssText = 'color: #4285f4; text-decoration: none;';
            sourceLink.textContent = item.title || item.url;

            sourceTitleDiv.appendChild(sourceLink);

            const urlDiv = document.createElement('div');
            urlDiv.style.wordBreak = 'break-all';
            urlDiv.textContent = item.url;

            sourceUrlDiv.appendChild(sourceTitleDiv);
            sourceUrlDiv.appendChild(urlDiv);

            favoriteItem.appendChild(header);
            favoriteItem.appendChild(sourceDiv);
            favoriteItem.appendChild(translationDiv);
            favoriteItem.appendChild(sourceUrlDiv);

            favoritesList.appendChild(favoriteItem);
        });
    }

    // 添加到历史记录
    function addToHistory(source, translation) {
        const timestamp = new Date().toLocaleString();
        translationHistory.unshift({ source, translation, timestamp });

        // 限制历史记录数量
        if (translationHistory.length > settings.maxHistoryItems) {
            translationHistory.pop();
        }

        GM_setValue('translationHistory', translationHistory);
        updateHistoryList();
    }

    // 添加到收藏夹
    function addToFavorites(item) {
        // 添加URL信息
        item.url = window.location.href;
        item.title = document.title;

        // 检查是否已存在
        if (!translationFavorites.some(fav => fav.source === item.source)) {
            translationFavorites.unshift(item);

            // 限制收藏数量
            if (translationFavorites.length > settings.maxFavoritesItems) {
                translationFavorites.pop();
            }

            GM_setValue('translationFavorites', translationFavorites);
            updateFavoritesList();
        }
    }

    // 从收藏夹移除
    function removeFromFavorites(index) {
        translationFavorites.splice(index, 1);
        GM_setValue('translationFavorites', translationFavorites);
        updateFavoritesList();
    }

    // 复制翻译结果
    function copyTranslation(index) {
        const item = translationHistory[index];
        if (item) {
            navigator.clipboard.writeText(item.translation).then(() => {
                alert('已复制到剪贴板!');
            });
        }
    }

    // 复制收藏的翻译
    function copyFavorite(index) {
        const item = translationFavorites[index];
        if (item) {
            navigator.clipboard.writeText(item.translation).then(() => {
                alert('已复制到剪贴板!');
            });
        }
    }

    // 注册(不可用)菜单命令
    GM_registerMenuCommand('翻译整页', translateFullPage);
    GM_registerMenuCommand('查看翻译历史', function () {
        const panel = document.getElementById('translator-history-panel') || createHistoryPanel();
        panel.style.display = 'block';
        updateHistoryList();
    });
    GM_registerMenuCommand('查看收藏夹', function () {
        const panel = document.getElementById('translator-favorites-panel') || createFavoritesPanel();
        panel.style.display = 'block';
        updateFavoritesList();
    });

    // 创建翻译弹窗
    function createTranslationPopup() {
        // 主容器
        const popup = document.createElement('div');
        popup.className = 'translation-popup';
        popup.style.cssText = `
            position: absolute;
            background-color: white;
            min-width: 200px;
            max-width: 400px;
            max-height: 80vh;
            display: flex;
            flex-direction: column;
            padding: 0;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            z-index: 10000;
            font-size: 14px;
            line-height: 1.5;
            overflow: hidden;
            user-select: none; /* 防止拖动时选中文本 */
        `;

        // ===== 顶部区域(可拖动) =====
        const header = document.createElement('div');
        header.className = 'translation-header';
        header.style.cssText = `
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px 15px;
            border-bottom: 1px solid #eee;
            flex-shrink: 0;
            cursor: move; /* 指示可拖动 */
            background-color: #f9f9f9; /* 轻微背景色以区分可拖动区域 */
            border-radius: 8px 8px 0 0;
            position: relative; /* 确保关闭按钮定位正确 */
        `;
        
        // 拖动提示图标
        const dragHandleIcon = document.createElement('div');
        dragHandleIcon.className = 'drag-handle-icon';
        dragHandleIcon.innerHTML = '⋮⋮';
        dragHandleIcon.style.cssText = `
            margin-right: 6px;
            color: #999;
            font-size: 10px;
            transform: rotate(90deg);
            display: inline-block;
        `;
        
        // 源语言显示区域
        const sourceLanguage = document.createElement('div');
        sourceLanguage.className = 'source-language';
        sourceLanguage.style.cssText = `
            color: #666;
            font-size: 12px;
            margin-left: 8px;
            flex-grow: 1;
        `;
        sourceLanguage.textContent = '源语言: 加载中...';
        
        // 关闭按钮
        const closeButton = document.createElement('div');
        closeButton.className = 'close-button';
        closeButton.innerHTML = '×';
        closeButton.style.cssText = `
            width: 20px;
            height: 20px;
            text-align: center;
            line-height: 20px;
            font-size: 18px;
            cursor: pointer;
            color: #666;
            background-color: white;
            border-radius: 50%;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            margin-left: 10px;
        `;
        
        // 组装头部
        header.appendChild(dragHandleIcon);
        header.appendChild(sourceLanguage);
        header.appendChild(closeButton);
        
        // ===== 内容区域(可滚动) =====
        const contentWrapper = document.createElement('div');
        contentWrapper.className = 'content-wrapper';
        contentWrapper.style.cssText = `
            padding: 10px 15px;
            flex-grow: 1;
            overflow-y: auto;
            max-height: calc(80vh - 90px); /* 减去头部和底部的高度 */
        `;
        
        // 创建内容区域
        const content = document.createElement('div');
        content.className = 'translation-content';
        content.style.cssText = `
            white-space: pre-wrap;
        `;

        // 添加内容区域到内容包装器
        contentWrapper.appendChild(content);
        
        // ===== 底部区域 =====
        const footer = document.createElement('div');
        footer.className = 'translation-footer';
        footer.style.cssText = `
            display: flex;
            justify-content: flex-end;
            gap: 10px;
            padding: 10px 15px;
            border-top: 1px solid #eee;
            background-color: white;
            flex-shrink: 0;
        `;

        // 复制按钮
        const copyButton = document.createElement('button');
        copyButton.className = 'copy-button';
        copyButton.textContent = '复制译文';
        copyButton.style.cssText = `
            padding: 5px 10px;
            background-color: #4285f4;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
        `;

        // 收藏按钮
        const favoriteButton = document.createElement('button');
        favoriteButton.className = 'favorite-button';
        favoriteButton.textContent = '收藏';
        favoriteButton.style.cssText = `
            padding: 5px 10px;
            background-color: #fbbc05;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
        `;

        // 添加按钮到底部
        footer.appendChild(copyButton);
        footer.appendChild(favoriteButton);

        // ===== 组装整个弹窗 =====
        popup.appendChild(header);
        popup.appendChild(contentWrapper);
        popup.appendChild(footer);

        // ===== 事件处理 =====
        // 绑定事件 - 改进版
        let isDragging = false;
        let offsetX, offsetY;
        let popupRect = null;
        
        // 存储当前的鼠标事件处理函数,用于后续移除
        let mouseMoveHandler, mouseUpHandler;
        
        // 关闭按钮事件
        closeButton.addEventListener('click', function() {
            if (typeof popup.cleanup === 'function') {
                popup.cleanup();
            }
            popup.remove();
        });
        
        // 复制按钮事件
        copyButton.addEventListener('click', function() {
            const translationText = content.textContent;
            navigator.clipboard.writeText(translationText)
                .then(() => {
                    copyButton.textContent = '✓ 已复制';
                    setTimeout(() => {
                        copyButton.textContent = '复制译文';
                    }, 2000);
                })
                .catch(err => {
                    console.error('无法复制文本: ', err);
            });
        });

        // 收藏按钮事件
        favoriteButton.addEventListener('click', function() {
            const translationText = content.textContent;
            const sourceText = sourceLanguage.textContent.replace('源语言: ', '');
            
            addToFavorites({
                source: sourceText,
                translation: translationText
            });
            
            favoriteButton.textContent = '✓ 已收藏';
            setTimeout(() => {
                favoriteButton.textContent = '收藏';
            }, 2000);
        });
        
        // 鼠标按下事件 - 拖动开始
        header.addEventListener('mousedown', function(e) {
            // 如果点击的是关闭按钮,不启动拖动
            if (e.target === closeButton) return;
            
            isDragging = true;
            
            // 获取弹窗的当前位置和尺寸
            popupRect = popup.getBoundingClientRect();
            // 调整偏移量,只考虑相对于视口的位置,不考虑滚动
            offsetX = e.clientX - popupRect.left;
            offsetY = e.clientY - popupRect.top;
            
            // 添加临时样式增强拖动体验
            popup.style.transition = 'none';
            popup.style.opacity = '0.95';
            popup.style.boxShadow = '0 5px 15px rgba(0, 0, 0, 0.3)';
            document.body.style.cursor = 'move';
            
            // 防止文本选择和其他默认行为
            e.preventDefault();
            
            // 临时添加鼠标移动和释放事件处理函数
            mouseMoveHandler = handleMouseMove;
            mouseUpHandler = handleMouseUp;
            
            document.addEventListener('mousemove', mouseMoveHandler);
            document.addEventListener('mouseup', mouseUpHandler);
        });
        
        // 鼠标移动处理函数
        function handleMouseMove(e) {
            if (!isDragging) return;
            
            // 计算新位置,考虑页面滚动偏移量
            const left = e.clientX - offsetX + window.scrollX;
            const top = e.clientY - offsetY + window.scrollY;
            
            // 防止拖出窗口边界
            const windowWidth = window.innerWidth;
            const windowHeight = window.innerHeight;
            
            const maxLeft = window.scrollX + windowWidth - 100; // 保留至少100px在视口内
            const maxTop = window.scrollY + windowHeight - 50;  // 保留至少50px在视口内
            
            // 设置约束后的位置
            popup.style.left = `${Math.max(window.scrollX, Math.min(maxLeft, left))}px`;
            popup.style.top = `${Math.max(window.scrollY, Math.min(maxTop, top))}px`;
            
            // 防止事件传递给下层元素
            e.stopPropagation();
            e.preventDefault();
        }
        
        // 鼠标释放处理函数
        function handleMouseUp() {
            if (!isDragging) return;
            
            isDragging = false;
            
            // 恢复正常样式
            popup.style.opacity = '1';
            popup.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.2)';
            document.body.style.cursor = 'auto';
            
            // 移除临时事件处理函数
            document.removeEventListener('mousemove', mouseMoveHandler);
            document.removeEventListener('mouseup', mouseUpHandler);
        }
        
        // 清理函数 - 当弹窗被移除时调用
        function cleanup() {
            // 确保移除所有事件监听器,防止内存泄漏
            document.removeEventListener('mousemove', mouseMoveHandler);
            document.removeEventListener('mouseup', mouseUpHandler);
        }
        
        // 将清理函数和关键元素引用附加到弹窗上,方便外部访问
        popup.cleanup = cleanup;
        popup.content = content;
        popup.sourceLanguage = sourceLanguage;
        popup.contentWrapper = contentWrapper;

        return popup;
    }

    // 创建加载动画
    function createLoadingAnimation() {
        const loading = document.createElement('div');
        loading.className = 'loading-animation';
        loading.style.cssText = `
            display: inline-block;
            width: 20px;
            height: 20px;
            border: 3px solid rgba(0, 0, 0, 0.1);
            border-radius: 50%;
            border-top-color: #4285f4;
            animation: spin 1s ease-in-out infinite;
            margin-right: 10px;
        `;

        const style = document.createElement('style');
        style.textContent = `
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
        `;

        document.head.appendChild(style);

        return loading;
    }

    // 调用 API 进行翻译
    function translateText(text, callback, retryWithoutStreaming = false, context = null) {
        // 确保使用最新的API设置
        syncApiSettings();
        
        if (!settings.apiKey) {
            callback({
                type: 'error',
                content: '错误:请先设置 API 密钥'
            });
            return;
        }

        // 调试模式
        const debugMode = true;
        const debugLog = debugMode ? console.log : function () { };

        // 创建内容收集器和状态跟踪
        let collectedContent = '';
        let lastProcessedLength = 0; // 跟踪上次处理的响应长度
        let responseBuffer = ''; // 用于存储部分响应,解决跨块JSON问题

        // 确定是否使用流式响应
        const useStreaming = retryWithoutStreaming ? false : settings.useStreaming;

        // 通知回调开始处理
        if (useStreaming) {
            callback({
                type: 'stream-start',
                content: ''
            });
        }

        debugLog(`开始翻译请求,文本长度: ${text.length}, 使用流式响应: ${useStreaming}, 模型: ${settings.model}`);

        // 准备消息数组
        const messages = [
            {
                role: "system",
                content: settings.systemPrompt + " 请保持原文本的段落格式,每个段落之间应当保留一个空行。"
            }
        ];

        // 如果有上下文且启用了上下文功能,添加上下文消息
        if (settings.useTranslationContext && context && context.length > 0) {
            messages.push({
                role: "system",
                content: "以下是之前已经翻译的上下文,供你参考以保持翻译的一致性和连贯性:"
            });
            
            // 添加上下文
            context.forEach(ctx => {
                messages.push({
                    role: "user",
                    content: ctx.original
                });
                messages.push({
                    role: "assistant",
                    content: ctx.translation
                });
            });
        }

        // 添加当前要翻译的文本
        messages.push({
            role: "user",
            content: text
        });
        debugLog(messages)
        debugLog(settings.temperature)
        GM_xmlhttpRequest({
            method: 'POST',
            url: settings.apiEndpoint,
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${settings.apiKey}`
            },
            responseType: useStreaming ? 'stream' : '',
            timeout: 60000,
            data: JSON.stringify({
                model: settings.model,
                messages: messages,
                temperature: settings.temperature,
                stream: useStreaming,
                top_p: 1,
                frequency_penalty: 0,
                presence_penalty: 0,
                max_tokens: 4000
            }),
            onloadstart: function () {
                if (useStreaming) {
                    debugLog("流式请求已开始");
                } else {
                    debugLog("标准请求已开始");
                    debugLog(settings.temperature)
                }
            },
            onprogress: function (response) {
                if (!useStreaming) return; // 非流式模式不处理进度更新

                try {
                    // 获取完整响应文本
                    const responseText = response.responseText || '';

                    // 如果响应没变长,不处理
                    if (!responseText || responseText.length <= lastProcessedLength) {
                        return;
                    }

                    debugLog(`进度更新: 响应总长度=${responseText.length}, 上次处理位置=${lastProcessedLength}`);

                    // 获取新增内容
                    const newChunk = responseText.substring(lastProcessedLength);
                    debugLog(`接收新数据: ${newChunk.length}字节`);

                    // 更新处理位置要放在处理数据前,防止数据处理期间发生新的onprogress事件
                    lastProcessedLength = responseText.length;

                    // 追加到缓冲区并处理
                    responseBuffer += newChunk;

                    // 不同API可能有不同的流式输出格式,我们需要支持多种格式
                    // 1. OpenAI风格: "data: {\"id\":\"...\",\"choices\":[{\"delta\":{\"content\":\"文本片段\"}}]}\n"
                    // 2. 简单文本块风格: 直接是文本内容

                    let newContent = '';
                    let dataMatches = [];

                    // 尝试提取所有"data: {...}"格式的行
                    const regex = /data: ({.+?})\n/g;
                    let match;

                    // 收集所有匹配项
                    while ((match = regex.exec(responseBuffer)) !== null) {
                        dataMatches.push(match[0]);

                        try {
                            // 提取JSON部分并解析
                            const jsonStr = match[1];
                            const data = JSON.parse(jsonStr);

                            // 从JSON提取文本内容
                            if (data.choices && data.choices[0]) {
                                if (data.choices[0].delta && data.choices[0].delta.content) {
                                    // OpenAI风格的delta格式
                                    newContent += data.choices[0].delta.content;
                                } else if (data.choices[0].text) {
                                    // 有些API使用text字段
                                    newContent += data.choices[0].text;
                                } else if (data.choices[0].message && data.choices[0].message.content) {
                                    // 完整消息格式
                                    newContent += data.choices[0].message.content;
                                }
                            }
                        } catch (parseError) {
                            // 解析出错,记录但继续处理
                            debugLog(`JSON解析失败: ${parseError.message}, 内容: ${match[0].substring(0, 50)}...`);
                        }
                    }

                    // 检查是否找到了标准格式的数据行
                    if (dataMatches.length > 0) {
                        // 从缓冲区中移除已处理的部分,但保留可能不完整的最后部分
                        const lastMatchEndIndex = regex.lastIndex;
                        if (lastMatchEndIndex > 0 && lastMatchEndIndex < responseBuffer.length) {
                            responseBuffer = responseBuffer.substring(lastMatchEndIndex);
                        } else {
                            responseBuffer = '';
                        }

                        debugLog(`找到${dataMatches.length}个数据块,提取了${newContent.length}字符的内容`);
                    } else {
                        // 如果没有找到标准格式,尝试按行分割处理
                        const lines = responseBuffer.split('\n');

                        // 保留最后一行作为可能的不完整行
                        responseBuffer = lines.pop() || '';

                        // 处理每一行
                        for (const line of lines) {
                            // 尝试提取"data: "后面的内容
                            if (line.trim().startsWith('data: ')) {
                                try {
                                    const content = line.substring(6).trim();
                                    if (content === '[DONE]') {
                                        debugLog("收到流结束标记");
                                        continue;
                                    }

                                    // 尝试解析JSON
                                    const data = JSON.parse(content);
                                    if (data.choices && data.choices[0]) {
                                        if (data.choices[0].delta && data.choices[0].delta.content) {
                                            newContent += data.choices[0].delta.content;
                                        } else if (data.choices[0].text) {
                                            newContent += data.choices[0].text;
                                        } else if (data.choices[0].message && data.choices[0].message.content) {
                                            newContent += data.choices[0].message.content;
                                        }
                                    }
                                } catch (e) {
                                    // JSON解析失败,可能是普通文本或特殊格式
                                    debugLog(`处理行出错: ${e.message}, 行内容: ${line.substring(0, 50)}...`);
                                }
                            } else if (line.trim() && !line.includes('event:') && !line.includes('id:')) {
                                // 如果不是控制行且非空,可能是直接的文本内容
                                // 一些API直接发送文本而不是JSON
                                newContent += line + '\n';
                            }
                        }
                    }

                    // 如果有新内容
                    if (newContent) {
                        collectedContent += newContent;
                        debugLog(`新增内容: ${newContent.length}字符, 当前总内容: ${collectedContent.length}字符`);

                        // 发送更新
                        callback({
                            type: 'stream-progress',
                            content: collectedContent
                        });
                    } else if (dataMatches.length > 0 || responseBuffer.includes('data:')) {
                        // 找到了数据行但没有提取到内容,可能是API发送的是控制消息
                        debugLog("收到数据但未提取到新内容,可能是控制消息");
                    } else {
                        debugLog("本次更新没有提取到新内容");
                    }
                } catch (e) {
                    console.error("处理流数据错误:", e);
                    debugLog("错误详情:", e.stack);

                    // 即使出错也更新位置,防止重复处理导致死循环
                    if (response && response.responseText) {
                        lastProcessedLength = response.responseText.length;
                    }
                }
            },
            onload: function (response) {
                try {
                    // 检查HTTP状态码
                    if (response.status && response.status !== 200) {
                        // 非200状态码,记录错误但不替换原文
                        console.error(`API返回非200状态码: ${response.status}`);
                        let errorMsg = `API返回错误状态码: ${response.status}`;
                        
                        // 尝试从响应中提取更详细的错误信息
                        if (response.responseText) {
                            try {
                                const errorData = JSON.parse(response.responseText);
                                if (errorData.error) {
                                    errorMsg += ` - ${errorData.error.message || errorData.error.type || JSON.stringify(errorData.error)}`;
                                }
                            } catch (e) {
                                // 无法解析JSON,使用原始响应内容
                                if (response.responseText.length < 100) {
                                    errorMsg += ` - ${response.responseText}`;
                                }
                            }
                        }
                        
                        callback({
                            type: 'error',
                            content: errorMsg,
                            statusCode: response.status
                        });
                        return;
                    }
                    
                    // 检查是否已经收集到内容(通过流式API)
                    if (useStreaming && collectedContent) {
                        // 已收集到了内容 - 直接使用收集到的内容
                        debugLog("onload: 使用已收集的流式内容,长度:", collectedContent.length);
                        callback({
                            type: 'stream-end',
                            content: collectedContent
                        });
                        return;
                    }

                    debugLog(`onload: ${useStreaming ? '没有收集到流式内容' : '使用标准响应'}, 尝试处理完整响应`);

                    // 在流式模式下,如果没有responseText但我们仍然到达onload,这可能是一个API限制
                    if (useStreaming && (!response || typeof response.responseText !== 'string' || response.responseText.trim() === '')) {
                        // 检查是否有响应头,可能表明API连接是成功的
                        if (response && response.responseHeaders) {
                            // 尝试从缓冲区中恢复,有时onload触发时缓冲区仍有未处理的内容
                            if (responseBuffer.trim()) {
                                debugLog("onload: 从缓冲区恢复内容,尝试解析");
                                try {
                                    // 处理缓冲区中的内容
                                    let content = '';
                                    const lines = responseBuffer.split('\n').filter(line => line.trim());

                                    for (const line of lines) {
                                        if (line.startsWith('data: ') && line !== 'data: [DONE]') {
                                            try {
                                                const jsonStr = line.substring(6).trim();
                                                const data = JSON.parse(jsonStr);
                                                if (data.choices && data.choices[0]) {
                                                    if (data.choices[0].delta && data.choices[0].delta.content) {
                                                        content += data.choices[0].delta.content;
                                                    } else if (data.choices[0].message && data.choices[0].message.content) {
                                                        content += data.choices[0].message.content;
                                                    }
                                                }
                                            } catch (e) {
                                                debugLog("缓冲区解析出错:", e.message);
                                            }
                                        }
                                    }

                                    if (content) {
                                        // 找到了内容,使用它
                                        callback({
                                            type: 'complete',
                                            content: content.trim()
                                        });
                                        return;
                                    }
                                } catch (e) {
                                    debugLog("缓冲区处理失败:", e.message);
                                }
                            }

                            if (response.responseHeaders.includes("content-type:text/event-stream")) {
                                debugLog("onload: 检测到有效的SSE响应头,但无responseText。尝试使用非流式模式重试...");

                                // 自动重试:使用非流式模式
                                if (!retryWithoutStreaming) {
                                    debugLog("切换到非流式模式重试翻译请求");
                                    translateText(text, callback, true);
                                    return;
                                } else {
                                    // 如果这是重试尝试但仍然失败,则报告错误
                                    throw new Error("即使在非流式模式下,API也未能返回翻译内容。请检查API密钥和网络连接。");
                                }
                            } else {
                                // 其他内容类型,可能是API错误
                                throw new Error(`响应没有包含预期的内容。响应头: ${response.responseHeaders.substring(0, 100)}...`);
                            }
                        } else {
                            // 完全无效的响应
                            throw new Error("响应对象无效或不包含内容。请检查API端点和密钥是否正确。");
                        }
                    }

                    // 有responseText,处理它
                    const responseText = response.responseText;
                    debugLog(`获取到响应文本,长度: ${responseText.length}`);

                    // 检查是否是SSE格式(以'data: '开头)
                    if (responseText.trim().startsWith('data: ')) {
                        debugLog("检测到SSE格式响应,解析数据...");
                        // 处理SSE格式
                        const lines = responseText.split('\n');
                        let fullContent = '';
                        let processedCount = 0;

                        for (let i = 0; i < lines.length; i++) {
                            const line = lines[i].trim();
                            if (line.startsWith('data: ') && line !== 'data: [DONE]') {
                                try {
                                    // 提取JSON部分
                                    const jsonData = line.substring(6);
                                    const data = JSON.parse(jsonData);
                                    processedCount++;

                                    if (data.choices && data.choices[0]) {
                                        // 对于非流式响应中的SSE格式,提取content
                                        if (data.choices[0].delta && data.choices[0].delta.content) {
                                            fullContent += data.choices[0].delta.content;
                                        }
                                        // 支持普通响应格式
                                        else if (data.choices[0].message && data.choices[0].message.content) {
                                            fullContent += data.choices[0].message.content;
                                        }
                                        // 支持一些API的不同格式
                                        else if (data.choices[0].text) {
                                            fullContent += data.choices[0].text;
                                        }
                                    }
                                } catch (e) {
                                    console.error("处理单行SSE数据出错:", e, "行内容:", line);
                                }
                            }
                        }

                        debugLog(`解析了${processedCount}行SSE数据,提取了${fullContent.length}字符的内容`);

                        if (fullContent) {
                            callback({
                                type: 'complete',
                                content: fullContent.trim()
                            });
                            return;
                        } else {
                            throw new Error("无法从SSE响应中提取内容");
                        }
                    }

                    // 尝试作为单个JSON对象解析
                    try {
                        debugLog("尝试解析为标准JSON响应");
                        const data = JSON.parse(responseText);
                        if (data.error) {
                            callback({
                                type: 'error',
                                content: `错误:${data.error.message || JSON.stringify(data.error)}`
                            });
                        } else if (data.choices && data.choices[0]) {
                            // 提取不同格式的内容
                            let content = '';
                            if (data.choices[0].message && data.choices[0].message.content) {
                                content = data.choices[0].message.content;
                            } else if (data.choices[0].text) {
                                content = data.choices[0].text;
                            }

                            if (content) {
                                callback({
                                    type: 'complete',
                                    content: content.trim()
                                });
                            } else {
                                throw new Error("API响应中未找到内容字段");
                            }
                        } else {
                            throw new Error("API响应格式不符合预期");
                        }
                    } catch (e) {
                        // JSON解析失败,可能是纯文本响应或其他格式
                        debugLog("JSON解析失败,尝试其他格式:", e.message);

                        // 如果响应看起来像纯文本,直接使用
                        if (responseText && !responseText.startsWith('{') && !responseText.startsWith('[') && !responseText.includes('<!DOCTYPE')) {
                            debugLog("响应似乎是纯文本,直接返回");
                            callback({
                                type: 'complete',
                                content: responseText.trim()
                            });
                            return;
                        }

                        // 其他情况下,报告错误
                        throw new Error(`非标准响应格式: ${e.message}`);
                    }
                } catch (e) {
                    // 安全地获取响应预览,避免undefined错误
                    let responsePreview = "无法获取响应内容";
                    try {
                        if (response && typeof response.responseText === 'string') {
                            responsePreview = response.responseText.substring(0, 200);
                        } else if (response) {
                            // 尝试获取响应头信息作为调试参考
                            responsePreview = JSON.stringify(response).substring(0, 300);
                        }
                    } catch (previewError) {
                        responsePreview = `获取响应预览时出错: ${previewError.message}`;
                    }

                    console.error("响应处理错误:", e.message);
                    console.error("错误详情:", e.stack);
                    console.error("响应信息:", responsePreview);

                    // 给用户一些有用的建议
                    let errorMessage = e.message;
                    if (e.message.includes("API返回了成功响应") || e.message.includes("无法从SSE响应中提取内容")) {
                        errorMessage += "<br><br>建议:<br>1. 检查API密钥是否有足够的使用额度<br>2. 尝试减少翻译文本长度<br>3. <b>在设置面板中禁用流式响应</b>";
                    } else if (e.message.includes("非标准响应格式")) {
                        errorMessage += "<br><br>可能原因:<br>1. API响应格式与脚本不兼容<br>2. API返回了错误信息<br>3. API密钥可能无效";
                    }

                    callback({
                        type: 'error',
                        content: `解析响应时出错:${errorMessage}<br><br><small>响应信息: ${escapeHtml(responsePreview)}</small>`
                    });
                }
            },
            onerror: function (error) {
                console.error("API请求错误:", error);

                // 获取状态码
                const statusCode = error.status || 0;
                let errorMessage = error.statusText || '无法连接到 API';
                
                // 添加状态码到错误信息
                if (statusCode > 0) {
                    errorMessage = `(${statusCode}) ${errorMessage}`;
                }

                // 如果是流式请求失败,尝试非流式请求
                if (useStreaming && !retryWithoutStreaming) {
                    debugLog(`流式请求失败,状态码: ${statusCode},尝试使用非流式模式重试...`);
                    translateText(text, callback, true);
                    return;
                }

                callback({
                    type: 'error',
                    content: `请求错误:${errorMessage}`,
                    statusCode: statusCode
                });
            }
        });
    }

    // 辅助函数:转义HTML,防止XSS
    function escapeHtml(text) {
        return text
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#039;");
    }

    // 显示翻译结果
    async function showTranslation(text, rect) {
        // 确保使用最新的API设置
        syncApiSettings();
        console.log('显示翻译弹窗 - 确保使用最新API设置:', settings.apiEndpoint, settings.model);
        
        // 移除之前的弹窗,确保清理事件处理函数
        const oldPopups = document.querySelectorAll('.translation-popup');
        oldPopups.forEach(popup => {
            // 如果有清理函数,调用它
            if (popup.cleanup && typeof popup.cleanup === 'function') {
                popup.cleanup();
            }
            popup.remove();
        });

        // 创建新弹窗
        const popup = createTranslationPopup();
        
        // 通过附加到弹窗上的引用获取内容元素
        const content = popup.content;
        const sourceLanguage = popup.sourceLanguage;
        const contentWrapper = popup.contentWrapper;

        // 添加加载动画
        const loadingAnimation = createLoadingAnimation();
        content.innerHTML = ''; // 清空内容
        content.appendChild(loadingAnimation);
        content.appendChild(document.createTextNode('正在翻译...'));

        // 先添加到DOM,以便获取尺寸
        document.body.appendChild(popup);

        // 获取窗口尺寸和弹窗尺寸
        const windowWidth = window.innerWidth;
        const windowHeight = window.innerHeight;
        const popupWidth = popup.offsetWidth;
        const popupHeight = popup.offsetHeight;

        // 计算初始位置(选中文字的右侧)
        let left = window.scrollX + rect.right + 10; // 右侧间隔10px
        let top = window.scrollY + rect.top;

        // 检查右侧空间是否足够,如果不够则改为左侧显示
        if (left + popupWidth > window.scrollX + windowWidth) {
            left = window.scrollX + rect.left - popupWidth - 10; // 左侧间隔10px
        }

        // 如果左侧也没有足够空间,则居中显示
        if (left < window.scrollX) {
            left = window.scrollX + (windowWidth - popupWidth) / 2;
        }

        // 确保弹窗不超出视窗底部
        if (top + popupHeight > window.scrollY + windowHeight) {
            top = window.scrollY + windowHeight - popupHeight - 10; // 底部间隔10px
        }

        // 确保弹窗不超出视窗顶部
        if (top < window.scrollY) {
            top = window.scrollY + 10; // 顶部间隔10px
        }

        // 设置弹窗位置
        popup.style.left = `${left}px`;
        popup.style.top = `${top}px`;

        // 检测语言并翻译
        if (settings.autoDetectLanguage) {
            try {
                const language = await detectLanguage(text);
                sourceLanguage.textContent = `源语言: ${language}`;
                
                // 显示原文(如果启用)
                if (settings.showSourceLanguage) {
                    // 创建源文本元素
                    const sourceText = document.createElement('div');
                    sourceText.style.cssText = `
                        margin-bottom: 10px;
                        padding: 5px;
                        background-color: #f8f9fa;
                        border-radius: 4px;
                        font-size: 13px;
                        color: #666;
                    `;
                    sourceText.textContent = text;
                    
                    // 在内容前添加源文本
                    contentWrapper.insertBefore(sourceText, content);
                }
                
                // 翻译文本
                translateText(text, result => {
                    // 清除加载动画和加载文本
                content.innerHTML = '';
                    
                    // 确保结果正确显示
                    if (typeof result === 'object') {
                        if (result.type === 'error') {
                            content.textContent = `错误: ${result.content}`;
                        } else if (result.type === 'complete' || result.type === 'stream-end') {
                            content.textContent = result.content;
                            // 添加到历史记录
                            addToHistory(text, result.content);
                        } else if (result.content) {
                            content.textContent = result.content;
                        } else {
                            content.textContent = '无法解析翻译结果';
                            console.error('未能正确解析翻译结果', result);
                        }
                    } else if (typeof result === 'string') {
                        content.textContent = result;
                    } else {
                        content.textContent = '翻译过程发生错误';
                        console.error('翻译回调收到意外类型的结果', result);
                    }
                });
            } catch (error) {
                console.error('语言检测失败:', error);
                sourceLanguage.textContent = '源语言: 检测失败';
                // 继续翻译流程
                translateText(text, result => {
                    // ... 与上面相同的翻译处理逻辑 ...
                });
            }
        } else {
            sourceLanguage.textContent = '源语言: 未检测';
            
            // 显示原文(如果启用)
            if (settings.showSourceLanguage) {
                // 创建源文本元素
                const sourceText = document.createElement('div');
                sourceText.style.cssText = `
                    margin-bottom: 10px;
                    padding: 5px;
                    background-color: #f8f9fa;
                    border-radius: 4px;
                    font-size: 13px;
                    color: #666;
                `;
                sourceText.textContent = text;
                
                // 在内容前添加源文本
                contentWrapper.insertBefore(sourceText, content);
            }
            
            // 翻译文本
            translateText(text, result => {
                // 清除加载动画和加载文本
                content.innerHTML = '';
                
                // 确保结果正确显示
                if (typeof result === 'object') {
                    if (result.type === 'error') {
                        content.textContent = `错误: ${result.content}`;
                    } else if (result.type === 'complete' || result.type === 'stream-end') {
                        content.textContent = result.content;
                // 添加到历史记录
                addToHistory(text, result.content);
                    } else if (result.content) {
                        content.textContent = result.content;
                    } else {
                        content.textContent = '无法解析翻译结果';
                        console.error('未能正确解析翻译结果', result);
                    }
                } else if (typeof result === 'string') {
                    content.textContent = result;
                } else {
                    content.textContent = '翻译过程发生错误';
                    console.error('翻译回调收到意外类型的结果', result);
                }
            });
        }

        // 将选择的文本保存为全局变量,用于收藏功能
        lastSelectedText = text;
    }

    // 监听选择事件
    document.addEventListener('mouseup', function (e) {
        // 如果点击的是翻译按钮或翻译弹窗,不处理
        if (e.target.classList.contains('translate-button') ||
            e.target.closest('.translate-button') ||
            e.target.classList.contains('translation-popup') ||
            e.target.closest('.translation-popup')) {
            return;
        }

        const selection = window.getSelection();
        const selectedText = selection.toString().trim();

        // 清除之前的翻译按钮
        if (activeTranslateButton) {
            activeTranslateButton.remove();
            activeTranslateButton = null;
        }

        if (selectedText.length > 0) {
            const range = selection.getRangeAt(0);
            const rect = range.getBoundingClientRect();

            // 保存最后选择的文本和位置
            lastSelectedText = selectedText;
            lastSelectionRect = {
                top: rect.top,
                left: rect.left,
                bottom: rect.bottom,
                right: rect.right
            };

            const translateButton = createTranslateButton();
            translateButton.style.top = `${window.scrollY + rect.bottom + 5}px`;
            translateButton.style.left = `${window.scrollX + rect.right - 50}px`;

            translateButton.addEventListener('click', function (event) {
                event.stopPropagation();
                event.preventDefault();

                // 显示翻译
                showTranslation(lastSelectedText, lastSelectionRect);
            });

            document.body.appendChild(translateButton);
            activeTranslateButton = translateButton;
        }
    });

    // 在初始化时创建UI组件
    function initUI() {
        createSettingsButton();
        createTranslatePageButton();
        createProgressBar();
        
    }

    // 初始化UI
    settings.currentTab =  'general'
    initUI();
    
    // 重试失败的翻译
    function retryFailedTranslations() {
        // 确保使用最新的API设置
        syncApiSettings();
        console.log('重试失败翻译 - 确保使用最新API设置:', settings.apiEndpoint, settings.model);
        
        // 确认有段落需要翻译
        if (!translationSegments || translationSegments.length === 0) {
            alert('没有找到需要重试的翻译段落');
            return;
        }
        
        // 收集失败项
        const failedSegments = [];
        for (let i = 0; i < translationSegments.length; i++) {
            if (translationSegments[i].error) {
                failedSegments.push(i);
            }
        }
        
        if (failedSegments.length === 0) {
            alert('没有找到失败的翻译段落');
            return;
        }
        
        // 确认是否重试
        if (!confirm(`找到${failedSegments.length}个失败的翻译段落,是否重试?`)) {
            return;
        }
        
        // 更新状态
        const statusElement = document.getElementById('translation-status');
        if (statusElement) {
            statusElement.textContent = `正在重试失败的翻译 (0/${failedSegments.length})`;
            
            // 移除之前的错误摘要
            const oldSummary = statusElement.querySelector('.error-summary');
            if (oldSummary) oldSummary.remove();
        }
        
        // 重置错误计数
        translationSegments.errorCount = 0;
        
        // 开始重试
        isTranslatingFullPage = true;
        isTranslationPaused = false;
        
        // 创建控制按钮
        createControlButtons();
        
        // 递归重试
        function retryNext(index) {
            if (index >= failedSegments.length) {
                // 重试完成
                if (statusElement) {
                    const newErrors = translationSegments.errorCount || 0;
                    statusElement.textContent = `重试完成 (${failedSegments.length}/${failedSegments.length})${newErrors > 0 ? ` (仍有${newErrors}个错误)` : ''}`;
                }
                isTranslatingFullPage = false;
                return;
            }
            
            // 如果暂停,不继续
            if (isTranslationPaused || !isTranslatingFullPage) {
                return;
            }
            
            // 更新状态
            if (statusElement) {
                statusElement.textContent = `正在重试失败的翻译 (${index + 1}/${failedSegments.length})`;
            }
            
            // 获取当前要重试的段落索引
            const segmentIndex = failedSegments[index];
            
            // 清除之前的错误标记
            if (translationSegments[segmentIndex].nodes && translationSegments[segmentIndex].nodes.length > 0) {
                const nodeInfo = translationSegments[segmentIndex].nodes[0];
                if (nodeInfo.node && nodeInfo.node.parentElement) {
                    const errorMark = nodeInfo.node.parentElement.querySelector('.translation-error-mark');
                    if (errorMark) errorMark.remove();
                }
            }
            
            // 清除错误状态
            delete translationSegments[segmentIndex].error;
            
            // 翻译该段落
            translateText(translationSegments[segmentIndex].text, function (result) {
                if (result.type === 'complete' || result.type === 'stream-end') {
                    // 翻译成功,更新
                    translationSegments[segmentIndex].translation = result.content;
                    
                    // 更新DOM
                    if (translationSegments[segmentIndex].nodes && translationSegments[segmentIndex].nodes.length > 0) {
                        // 复用现有逻辑更新DOM
                        if (translationSegments[segmentIndex].nodes.length === 1) {
                            const nodeInfo = translationSegments[segmentIndex].nodes[0];
                            if (nodeInfo.node && nodeInfo.node.nodeType === Node.TEXT_NODE) {
                                nodeInfo.node.textContent = result.content;
                                if (nodeInfo.node.parentElement) {
                                    nodeInfo.node.parentElement.style.fontWeight = 'inherit';
                                    nodeInfo.node.parentElement.dataset.translated = 'true';
                                }
                            }
                        } else {
                            // 复杂节点结构的处理略过,会在前面代码相同逻辑处理
                        }
                    }
                    
                    // 继续下一个
                    setTimeout(() => retryNext(index + 1), 50);
                } else if (result.type === 'error') {
                    // 仍然出错
                    console.error(`重试段落 ${segmentIndex + 1} 翻译失败:`, result.content);
                    translationSegments[segmentIndex].error = result.content;
                    
                    // 错误计数
                    translationSegments.errorCount = (translationSegments.errorCount || 0) + 1;
                    
                    // 添加错误标记
                    if (translationSegments[segmentIndex].nodes && translationSegments[segmentIndex].nodes.length > 0) {
                        const nodeInfo = translationSegments[segmentIndex].nodes[0];
                        if (nodeInfo.node && nodeInfo.node.parentElement) {
                            // 在段落旁创建错误标记
                            const errorMark = document.createElement('span');
                            errorMark.className = 'translation-error-mark';
                            errorMark.innerHTML = '⚠️';
                            errorMark.title = `翻译失败: ${result.content}`;
                            errorMark.style.cssText = `
                                color: #ff4d4f;
                                cursor: pointer;
                                margin-left: 5px;
                                font-size: 16px;
                            `;
                            errorMark.addEventListener('click', function() {
                                alert(`翻译错误: ${result.content}`);
                            });
                            
                            nodeInfo.node.parentElement.appendChild(errorMark);
                            
                            // 确保不修改原文内容,防止错误码替换原文
                            if (nodeInfo.original && nodeInfo.node.nodeType === Node.TEXT_NODE) {
                                if (nodeInfo.node.textContent !== nodeInfo.original) {
                                    nodeInfo.node.textContent = nodeInfo.original;
                                }
                            }
                        }
                    }
                    
                    // 继续下一个
                    setTimeout(() => retryNext(index + 1), 50);
                }
            }, false);
        }
        
        // 开始重试第一个
        retryNext(0);
    }

    // 递归翻译段落
    let translatedCount = 0;
    // 添加一些全局错误处理和减速变量
    let consecutiveErrors = 0;
    let translationDelay = 50; // 初始延迟时间(ms)
    
    function translateNextSegment(index) {
        // 如果已暂停,不继续翻译
        if (isTranslationPaused) {
            return;
        }
        
        // 记录最后翻译的索引
        lastTranslatedIndex = index - 1;
        
        // 获取进度条和状态元素
        const progressBar = document.getElementById('translation-progress-bar');
        const statusElement = document.getElementById('translation-status');
        
        if (index >= translationSegments.length) {
            // 全部翻译完成
            if (progressBar) progressBar.style.width = '100%';
            
            // 获取错误统计
            const totalErrors = translationSegments.errorCount || 0;
            const errorInfo = totalErrors > 0 ? ` (${totalErrors}个错误)` : '';
            
            if (statusElement) {
                statusElement.textContent = `翻译完成 (${translationSegments.length}/${translationSegments.length})${errorInfo}`;
                
                // 如果有错误,添加错误摘要信息
                if (totalErrors > 0) {
                    // 移除之前的错误警告(如果有)
                    const oldWarning = statusElement.querySelector('.error-warning');
                    if (oldWarning) oldWarning.remove();
                    
                    const errorSummary = document.createElement('div');
                    errorSummary.className = 'error-summary';
                    errorSummary.style.cssText = `
                        margin-top: 10px;
                        color: #ff4d4f;
                        font-size: 13px;
                        padding: 8px;
                        background-color: rgba(255, 77, 79, 0.1);
                        border-radius: 4px;
                    `;
                    
                    // 创建错误摘要内容
                    let summaryText = `翻译过程中遇到了${totalErrors}个错误。`;
                    
                    // 添加解决建议
                    if (totalErrors > 5) {
                        summaryText += `可能原因:<br>
                        1. API密钥额度不足或API限制<br>
                        2. 网络连接不稳定<br>
                        3. 翻译内容过长或API不兼容<br><br>
                        建议:<br>
                        · 检查API设置和密钥<br>
                        · 在设置中禁用流式响应<br>
                        · 尝试使用不同的API提供商`;
                    }
                    
                    errorSummary.innerHTML = summaryText;
                    statusElement.appendChild(errorSummary);
                    
                    // 添加重试错误项按钮
                    const retryButton = document.createElement('button');
                    retryButton.textContent = '重试失败项';
                    retryButton.style.cssText = `
                        margin-top: 10px;
                        padding: 5px 10px;
                        background-color: #ff4d4f;
                        color: white;
                        border: none;
                        border-radius: 4px;
                        cursor: pointer;
                    `;
                    retryButton.addEventListener('click', function() {
                        retryFailedTranslations();
                    });
                    errorSummary.appendChild(retryButton);
                }
            }

            // 添加一个关闭按钮
            if (statusElement && !statusElement.querySelector('.close-btn')) {
                const closeButton = document.createElement('span');
                closeButton.className = 'close-btn';
                closeButton.style.cssText = `
                    margin-left: 10px;
                    cursor: pointer;
                    font-weight: bold;
                `;
                closeButton.textContent = '×';
                closeButton.addEventListener('click', function () {
                    statusElement.remove();
                    const progressContainer = document.getElementById('translation-progress-container');
                    if (progressContainer) progressContainer.style.display = 'none';
                });
                statusElement.appendChild(closeButton);
            }

            // 显示切换按钮
            const toggleButton = document.getElementById('toggle-translation-button');
            if (toggleButton) toggleButton.style.display = 'block';
            
            // 移除控制按钮
            removeControlButtons();

            isTranslatingFullPage = false;
            return;
        }

        // 更新进度
        const progress = Math.round((index / translationSegments.length) * 100);
        if (progressBar) progressBar.style.width = `${progress}%`;
        if (statusElement) statusElement.textContent = `正在翻译 (${index}/${translationSegments.length})`;

        // 如果段落文本为空,直接跳到下一个
        if (!translationSegments[index].text.trim()) {
            translateNextSegment(index + 1);
            return;
        }

        // 获取上下文
        let context = [];
        if (settings.useTranslationContext && settings.contextSize > 0) {
            // 从当前段落往前获取已翻译的段落作为上下文
            for (let i = index - 1; i >= 0 && context.length < settings.contextSize; i--) {
                if (translationSegments[i] && translationSegments[i].translation) {
                    context.unshift({
                        original: translationSegments[i].text,
                        translation: translationSegments[i].translation
                    });
                }
            }
        }

        // 处理参考文献条目 - 特殊处理以保留格式
        if (translationSegments[index].isReferenceItem) {
            translateReferenceItem(translationSegments[index], function (translatedHtml) {
                if (translatedHtml) {
                    // 保存翻译结果
                    translationSegments[index].translation = translatedHtml;

                    // 保存原始HTML以便切换回来
                    if (!translationSegments[index].originalHtml && translationSegments[index].element) {
                        translationSegments[index].originalHtml = translationSegments[index].element.innerHTML;
                    }

                    // 更新DOM显示翻译结果
                    if (translationSegments[index].element) {
                        // 将翻译后的HTML设置到元素中
                        translationSegments[index].element.innerHTML = translatedHtml;
                    }

                    // 更新最后翻译的索引
                    lastTranslatedIndex = index;
                    
                    // 继续翻译下一个段落
                    setTimeout(() => translateNextSegment(index + 1), 50);
                } else {
                    // 翻译失败时继续下一个
                    console.error(`参考文献条目 ${index + 1} 翻译失败`);
                    
                    // 更新最后翻译的索引
                    lastTranslatedIndex = index;
                    
                    setTimeout(() => translateNextSegment(index + 1), 50);
                }
            });
            return;
        }

        // 常规段落翻译
        translateText(translationSegments[index].text, function (result) {
            // 如果已暂停或停止,不处理翻译结果
            if (isTranslationPaused || !isTranslatingFullPage) {
                return;
            }
            
            if (result.type === 'complete' || result.type === 'stream-end') {
                // 翻译成功
                translatedCount++;
                translationSegments[index].translation = result.content;

                // 更新DOM中的文本,保留原始样式
                if (translationSegments[index].nodes && translationSegments[index].nodes.length > 0) {
                    // 使用更智能的方式分配翻译结果
                    if (translationSegments[index].nodes.length === 1) {
                        // 只有一个节点的简单情况
                        const nodeInfo = translationSegments[index].nodes[0];
                        if (nodeInfo.node && nodeInfo.node.nodeType === Node.TEXT_NODE) {
                            nodeInfo.node.textContent = result.content;
                            
                        }
                    } else {
                        // 多个节点的情况,尝试保留原始结构
                        // 计算原始内容中每个节点文本的比例
                        const totalOriginalLength = translationSegments[index].nodes.reduce(
                            (sum, nodeInfo) => sum + (nodeInfo.original ? nodeInfo.original.length : 0), 0);

                        if (totalOriginalLength > 0) {
                            // 按照原始文本长度比例分配翻译后的文本
                            let startPos = 0;
                            for (let i = 0; i < translationSegments[index].nodes.length; i++) {
                                const nodeInfo = translationSegments[index].nodes[i];
                                if (nodeInfo.node && nodeInfo.node.nodeType === Node.TEXT_NODE && nodeInfo.original) {
                                    // 计算该节点在原始文本中的比例
                                    const ratio = nodeInfo.original.length / totalOriginalLength;
                                    // 计算应该分配给该节点的翻译文本长度
                                    const chunkLength = Math.round(result.content.length * ratio);
                                    // 提取翻译文本的一部分
                                    let chunk = '';
                                    if (i === translationSegments[index].nodes.length - 1) {
                                        // 最后一个节点,获取剩余所有文本
                                        chunk = result.content.substring(startPos);
                                    } else {
                                        // 非最后节点,按比例获取
                                        chunk = result.content.substring(startPos, startPos + chunkLength);
                                        startPos += chunkLength;
                                    }
                                    // 更新节点文本
                                    nodeInfo.node.textContent = chunk;
                                   
                                }
                            }
                        } else {
                            // 回退方案:如果无法计算比例,则将所有文本放在第一个节点
                            let foundFirstNode = false;
                            for (let i = 0; i < translationSegments[index].nodes.length; i++) {
                                const nodeInfo = translationSegments[index].nodes[i];
                                if (nodeInfo.node && nodeInfo.node.nodeType === Node.TEXT_NODE) {
                                    if (!foundFirstNode) {
                                        nodeInfo.node.textContent = result.content;
                                        foundFirstNode = true;
                                    } else {
                                        nodeInfo.node.textContent = '';
                                    }
                                }
                            }
                        }
                    }
                }
                
                // 更新最后翻译的索引
                lastTranslatedIndex = index;
                
                // 重置连续错误计数,因为成功了一次
                consecutiveErrors = 0;
                
                // 如果之前增加了延迟,且现在连续成功,则尝试逐步恢复
                if (translationDelay > 50 && translatedCount % 5 === 0) {
                    // 逐步减少延迟,但不低于初始值
                    translationDelay = Math.max(50, translationDelay * 0.8);
                    console.log(`连续翻译成功,减少延迟至${translationDelay}ms`);
                    
                    // 更新延迟信息显示
                    if (statusElement && statusElement.querySelector('.delay-info')) {
                        statusElement.querySelector('.delay-info').textContent = `已自动调整延迟至${Math.round(translationDelay)}ms`;
                        
                        // 如果延迟已经降回正常,移除通知并恢复颜色
                        if (translationDelay <= 60) {
                            statusElement.querySelector('.delay-info').remove();
                            statusElement.style.backgroundColor = 'rgba(66, 133, 244, 0.9)';
                        }
                    }
                }

                // 继续下一个段落
                setTimeout(() => translateNextSegment(index + 1), translationDelay);
            } else if (result.type === 'error') {
                // 翻译出错,记录错误并继续下一个
                console.error(`段落 ${index + 1} 翻译失败:`, result.content);
                translationSegments[index].error = result.content;
                
                // 更新最后翻译的索引
                lastTranslatedIndex = index;
                
                // 错误计数
                translationSegments.errorCount = (translationSegments.errorCount || 0) + 1;
                consecutiveErrors++;
                
                // 如果连续错误过多,自动增加延迟
                if (consecutiveErrors >= 3) {
                    // 增加延迟,但最大不超过2秒
                    translationDelay = Math.min(2000, translationDelay * 1.5);
                    console.log(`检测到连续错误,自动增加翻译延迟至${translationDelay}ms`);
                    
                    // 更新状态显示
                    if (statusElement && !statusElement.querySelector('.delay-info')) {
                        const delayInfo = document.createElement('div');
                        delayInfo.className = 'delay-info';
                        delayInfo.style.cssText = `
                            font-size: 12px;
                            margin-top: 5px;
                        `;
                        delayInfo.textContent = `已自动增加延迟至${translationDelay}ms以减轻API负载`;
                        statusElement.appendChild(delayInfo);
                    } else if (statusElement && statusElement.querySelector('.delay-info')) {
                        statusElement.querySelector('.delay-info').textContent = `已自动增加延迟至${translationDelay}ms以减轻API负载`;
                    }
                    
                    // 改变状态颜色提示错误
                    statusElement.style.backgroundColor = 'rgba(255, 152, 0, 0.9)';
                }
                
                // 显示错误提示标记在段落旁
                if (translationSegments[index].nodes && translationSegments[index].nodes.length > 0) {
                    const nodeInfo = translationSegments[index].nodes[0];
                    if (nodeInfo.node && nodeInfo.node.parentElement) {
                        // 在段落旁创建错误标记
                        const errorMark = document.createElement('span');
                        errorMark.className = 'translation-error-mark';
                        errorMark.innerHTML = '⚠️';
                        errorMark.title = `翻译失败: ${result.content}`;
                        errorMark.style.cssText = `
                            color: #ff4d4f;
                            cursor: pointer;
                            margin-left: 5px;
                            font-size: 16px;
                        `;
                        errorMark.addEventListener('click', function() {
                            alert(`翻译错误: ${result.content}`);
                        });
                        
                        nodeInfo.node.parentElement.appendChild(errorMark);
                        
                        // 重要:确保不修改原文内容,防止错误码替换原文
                        // 如果有通过错误处理设置了节点文本,恢复原始文本
                        if (nodeInfo.original && nodeInfo.node.nodeType === Node.TEXT_NODE) {
                            if (nodeInfo.node.textContent !== nodeInfo.original) {
                                nodeInfo.node.textContent = nodeInfo.original;
                            }
                        }
                    }
                }
                
                // 更新状态面板显示总体进度和错误信息
                if (statusElement) {
                    const totalErrors = translationSegments.errorCount;
                    const errorInfo = totalErrors > 0 ? ` (${totalErrors}个错误)` : '';
                    statusElement.textContent = `正在翻译 (${index + 1}/${translationSegments.length})${errorInfo}`;
                    
                    // 如果错误超过阈值,添加暂停建议
                    if (totalErrors > 5 && !statusElement.querySelector('.error-warning')) {
                        const warningElement = document.createElement('div');
                        warningElement.className = 'error-warning';
                        warningElement.style.cssText = `
                            margin-top: 5px;
                            color: #ff4d4f;
                            font-size: 12px;
                        `;
                        warningElement.textContent = `检测到多处翻译错误,可能是API限制或网络问题。考虑暂停翻译后再继续。`;
                        statusElement.appendChild(warningElement);
                    }
                }

                // 继续下一个段落
                setTimeout(() => translateNextSegment(index + 1), translationDelay);
            }
        }, false, context);  // 添加context参数
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址