// ==UserScript== // @name USACO题面翻译 // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description 翻译USACO题面,支持云存储。 // @author zhoukeyv // @license GNU GPLv3 // @match *://*.usaco.org/index.php?page=viewproblem2* // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @connect api.github.com // @connect raw.githubusercontent.com // @connect generativelanguage.googleapis.com // @connect api.deepseek.com // @connect api.openai.com // @connect dashscope.aliyuncs.com // @connect open.bigmodel.cn // @connect api.moonshot.cn // @connect api.siliconflow.cn // @connect 127.0.0.1 // @connect localhost // @connect * // @downloadURL https://update.greasyfork.icu/scripts/573025/USACO%E9%A2%98%E9%9D%A2%E7%BF%BB%E8%AF%91.user.js // @updateURL https://update.greasyfork.icu/scripts/573025/USACO%E9%A2%98%E9%9D%A2%E7%BF%BB%E8%AF%91.meta.js // ==/UserScript== (function() { 'use strict'; const GITHUB_REPO = "zhoukeyv/USACO-translate-storage"; const ENCODED_TOKEN = "Z2l0aHViX3BhdF8xMUI3Q1dXTVkwdDdaTjN1cFN4UVdHX3pUNnUydG01ZkpzWG02eGVQTUZOejNQZUdyTVV2VXY0aHR6bG15Skh6cXpCU1VaQ1FLWFlrT0lOWTJs"; const GITHUB_TOKEN = atob(ENCODED_TOKEN); const AI_MODELS = { "ds-chat": { group: "DeepSeek", name: "DeepSeek V3", protocol: "openai", url: "https://api.deepseek.com/chat/completions", actualModel: "deepseek-chat" }, "ds-reasoner": { group: "DeepSeek", name: "DeepSeek R1", protocol: "openai", url: "https://api.deepseek.com/chat/completions", actualModel: "deepseek-reasoner" }, "gemini-3.1-pro": { group: "Gemini", name: "Gemini 3.1 Pro", protocol: "gemini", url: "", actualModel: "gemini-3.1-pro-preview" }, "gemini-3.1-flash": { group: "Gemini", name: "Gemini 3.1 Flash", protocol: "gemini", url: "", actualModel: "gemini-3.1-flash-preview" }, "gemini-3.1-flash-lite": { group: "Gemini", name: "Gemini 3.1 Flash-Lite", protocol: "gemini", url: "", actualModel: "gemini-3.1-flash-lite-preview" }, "gemini-2.5-pro": { group: "Gemini", name: "Gemini 2.5 Pro", protocol: "gemini", url: "", actualModel: "gemini-2.5-pro" }, "gemini-2.5-flash": { group: "Gemini", name: "Gemini 2.5 Flash", protocol: "gemini", url: "", actualModel: "gemini-2.5-flash" }, "gpt-4o": { group: "OpenAI", name: "GPT-4o", protocol: "openai", url: "https://api.openai.com/v1/chat/completions", actualModel: "gpt-4o" }, "gpt-4o-mini": { group: "OpenAI", name: "GPT-4o-mini", protocol: "openai", url: "https://api.openai.com/v1/chat/completions", actualModel: "gpt-4o-mini" }, "o3-mini": { group: "OpenAI", name: "o3-mini", protocol: "openai", url: "https://api.openai.com/v1/chat/completions", actualModel: "o3-mini" }, "o1-mini": { group: "OpenAI", name: "o1-mini", protocol: "openai", url: "https://api.openai.com/v1/chat/completions", actualModel: "o1-mini" }, "qwen-max": { group: "通义千问", name: "Qwen Max", protocol: "openai", url: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", actualModel: "qwen-max" }, "qwen-plus": { group: "通义千问", name: "Qwen Plus", protocol: "openai", url: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", actualModel: "qwen-plus" }, "qwen-turbo": { group: "通义千问", name: "Qwen Turbo", protocol: "openai", url: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", actualModel: "qwen-turbo" }, "glm-4-plus": { group: "智谱GLM", name: "GLM-4 Plus", protocol: "openai", url: "https://open.bigmodel.cn/api/paas/v4/chat/completions", actualModel: "glm-4-plus" }, "glm-4-air": { group: "智谱GLM", name: "GLM-4 Air", protocol: "openai", url: "https://open.bigmodel.cn/api/paas/v4/chat/completions", actualModel: "glm-4-air" }, "glm-4-flash": { group: "智谱GLM", name: "GLM-4 Flash", protocol: "openai", url: "https://open.bigmodel.cn/api/paas/v4/chat/completions", actualModel: "glm-4-flash" }, "kimi-8k": { group: "Kimi", name: "Moonshot v1 8K", protocol: "openai", url: "https://api.moonshot.cn/v1/chat/completions", actualModel: "moonshot-v1-8k" }, "kimi-32k": { group: "Kimi", name: "Moonshot v1 32K", protocol: "openai", url: "https://api.moonshot.cn/v1/chat/completions", actualModel: "moonshot-v1-32k" }, "kimi-128k": { group: "Kimi", name: "Moonshot v1 128K", protocol: "openai", url: "https://api.moonshot.cn/v1/chat/completions", actualModel: "moonshot-v1-128k" }, "sf-ds-v3": { group: "硅基流动", name: "DeepSeek V3", protocol: "openai", url: "https://api.siliconflow.cn/v1/chat/completions", actualModel: "deepseek-ai/DeepSeek-V3" }, "sf-ds-r1": { group: "硅基流动", name: "DeepSeek R1", protocol: "openai", url: "https://api.siliconflow.cn/v1/chat/completions", actualModel: "deepseek-ai/DeepSeek-R1" }, "sf-qwen-72b": { group: "硅基流动", name: "Qwen 2.5 72B Instruct", protocol: "openai", url: "https://api.siliconflow.cn/v1/chat/completions", actualModel: "Qwen/Qwen2.5-72B-Instruct" }, "sf-llama-3": { group: "硅基流动", name: "Llama 3.3 70B", protocol: "openai", url: "https://api.siliconflow.cn/v1/chat/completions", actualModel: "meta-llama/Llama-3.3-70B-Instruct" }, "custom": { group: "高级选项", name: "自定义模型", protocol: "custom", url: "", actualModel: "" } }; let savedPresetId = GM_getValue("ai_preset_id", "gemini-3.1-pro"); if (!AI_MODELS[savedPresetId]) savedPresetId = "gemini-3.1-pro"; // 【旧版API Key迁移逻辑】无缝将旧全局Key绑定到当前选择的模型上 let oldUsacoKey = GM_getValue("gemini_api_key", ""); if (oldUsacoKey && !GM_getValue(`ai_api_key_${savedPresetId}`, "")) { GM_setValue(`ai_api_key_${savedPresetId}`, oldUsacoKey); GM_setValue("gemini_api_key", ""); } let customBaseUrl = GM_getValue("ai_custom_url", "http://127.0.0.1:11434/v1/chat/completions"); let customModelName = GM_getValue("ai_custom_model", ""); let enableLocalCache = GM_getValue("usaco_enable_cache", true); function gmFetch(url, options) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method || 'GET', url: url, headers: options.headers || {}, data: options.body, onload: function(response) { const isOk = response.status >= 200 && response.status < 300; resolve({ ok: isOk, status: response.status, json: async () => { try { return JSON.parse(response.responseText); } catch(e) { return { error: { message: response.responseText || "Unknown Error" } }; } } }); }, onerror: function(err) { reject(new Error("网络请求被拦截或失败,请检查网络")); }, ontimeout: function() { reject(new Error("网络请求超时")); } }); }); } function injectSettingsUI() { const fab = document.createElement('button'); fab.className = 'btn btn-default'; fab.textContent = '翻译设置'; const savedLeft = GM_getValue("usaco_fab_left", "20px"); const savedTop = GM_getValue("usaco_fab_top", "auto"); const savedBottom = GM_getValue("usaco_fab_bottom", "20px"); fab.style.cssText = `position: fixed; left: ${savedLeft}; top: ${savedTop}; bottom: ${savedBottom}; z-index: 9998; box-shadow: 0 4px 10px rgba(0,0,0,0.2); cursor: pointer; user-select: none; background: #2b3e50; color: white; border: none; outline: none; padding: 8px 15px; border-radius: 20px; font-weight: bold; transition: background 0.3s;`; fab.onfocus = () => fab.blur(); fab.onmouseenter = () => fab.style.background = '#1a252f'; fab.onmouseleave = () => fab.style.background = '#2b3e50'; let isDragging = false, hasDragged = false, startX, startY, startLeft, startTop; fab.addEventListener('mousedown', (e) => { isDragging = true; hasDragged = false; startX = e.clientX; startY = e.clientY; startLeft = fab.getBoundingClientRect().left; startTop = fab.getBoundingClientRect().top; fab.style.bottom = 'auto'; fab.style.right = 'auto'; fab.style.left = startLeft + 'px'; fab.style.top = startTop + 'px'; fab.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const dx = e.clientX - startX, dy = e.clientY - startY; if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasDragged = true; fab.style.left = (startLeft + dx) + 'px'; fab.style.top = (startTop + dy) + 'px'; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; fab.style.cursor = 'pointer'; GM_setValue('usaco_fab_left', fab.style.left); GM_setValue('usaco_fab_top', fab.style.top); GM_setValue('usaco_fab_bottom', 'auto'); } }); let groups = {}; for (const [id, info] of Object.entries(AI_MODELS)) { if (!groups[info.group]) groups[info.group] = ''; groups[info.group] += ``; } let optionsHtml = ''; for (const [groupName, options] of Object.entries(groups)) { optionsHtml += `${options}`; } const modalOverlay = document.createElement('div'); modalOverlay.id = 'gemini-settings-overlay'; modalOverlay.style.cssText = `display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 9999; justify-content: center; align-items: flex-start; padding-top: 10vh; overflow-y: auto;`; modalOverlay.innerHTML = `

AI 翻译配置

`; document.body.appendChild(fab); document.body.appendChild(modalOverlay); const selPreset = document.getElementById('ai-input-preset'); const customFields = document.getElementById('ai-custom-fields'); const inputKey = document.getElementById('ai-input-key'); // 模型切换时,自动填充对应的 API Key selPreset.addEventListener('change', (e) => { const preset = e.target.value; if (preset === 'custom') customFields.style.display = 'block'; else customFields.style.display = 'none'; inputKey.value = GM_getValue(`ai_api_key_${preset}`, ""); }); fab.addEventListener('click', () => { if (hasDragged) return; selPreset.value = savedPresetId; document.getElementById('ai-input-customurl').value = customBaseUrl; document.getElementById('ai-input-custommodel').value = customModelName; document.getElementById('ai-input-cache').checked = enableLocalCache; // 触发 change 事件以自动加载对应 Key selPreset.dispatchEvent(new Event('change')); modalOverlay.style.display = 'flex'; }); document.getElementById('ai-btn-cancel').onclick = function() { this.blur(); modalOverlay.style.display = 'none'; }; document.getElementById('ai-btn-save').onclick = function() { this.blur(); const selectedPreset = selPreset.value; GM_setValue("ai_preset_id", selectedPreset); GM_setValue("ai_custom_url", document.getElementById('ai-input-customurl').value.trim()); GM_setValue("ai_custom_model", document.getElementById('ai-input-custommodel').value.trim()); // 绑定保存当前的 API Key 到所选模型 GM_setValue(`ai_api_key_${selectedPreset}`, inputKey.value.trim()); GM_setValue("usaco_enable_cache", document.getElementById('ai-input-cache').checked); location.reload(); }; } function cleanHTMLForAI(node) { const visualMathClasses = ['.MathJax_Preview', '.MathJax', '.MathJax_Display', '.mjx-chtml', '.mjx-container', '.katex-html', '.base']; node.querySelectorAll(visualMathClasses.join(', ')).forEach(el => el.remove()); node.querySelectorAll('script[type^="math/tex"], annotation').forEach(el => { let tex = el.textContent.replace(/^\s*\$+|\$+\s*$/g, '').trim(); const varEl = document.createElement('var'); varEl.textContent = tex; if (el.tagName.toLowerCase() === 'annotation') { const mathWrapper = el.closest('math') || el.closest('.katex') || el.closest('span'); if (mathWrapper && mathWrapper.parentNode) { mathWrapper.parentNode.insertBefore(varEl, mathWrapper); mathWrapper.remove(); } } else { el.parentNode.insertBefore(varEl, el); el.remove(); } }); node.querySelectorAll('[style*="display: none"], .hidden').forEach(el => el.remove()); return node.innerHTML.trim(); } function getProblemFileName() { const match = location.href.match(/cpid=(\d+)/); if (match) return `cpid_${match[1]}.html`; const title = document.querySelector('.prb-title') || document.querySelector('h2'); let hash = btoa(unescape(encodeURIComponent(title ? title.textContent : location.href))).replace(/[^a-zA-Z0-9]/g, ''); return `custom_${hash.slice(0, 20)}.html`; } function utf8_to_b64(str) { return btoa(unescape(encodeURIComponent(str))); } function b64_to_utf8(str) { return decodeURIComponent(escape(atob(str))); } async function fetchFromGitHub(fileName) { if (enableLocalCache) { let localCache = GM_getValue("usaco_local_" + fileName, null); if (localCache) return { html: localCache, source: '翻译结果' }; } const apiUrl = `https://api.github.com/repos/${GITHUB_REPO}/contents/${fileName}`; const headers = { 'Accept': 'application/vnd.github+json', 'Authorization': `Bearer ${GITHUB_TOKEN}`, 'X-GitHub-Api-Version': '2022-11-28' }; try { const res = await gmFetch(apiUrl, { headers }); if (res.ok) { const data = await res.json(); const htmlContent = b64_to_utf8(data.content.replace(/\n/g, '')); if (enableLocalCache) GM_setValue("usaco_local_" + fileName, htmlContent); return { html: htmlContent, source: '翻译结果' }; } } catch(e) {} return null; } async function pushToGitHub(fileName, htmlContent) { const apiUrl = `https://api.github.com/repos/${GITHUB_REPO}/contents/${fileName}`; const headers = { 'Accept': 'application/vnd.github+json', 'Authorization': `Bearer ${GITHUB_TOKEN}`, 'X-GitHub-Api-Version': '2022-11-28' }; let fileSha = null; try { const getRes = await gmFetch(apiUrl, { headers }); if (getRes.ok) fileSha = (await getRes.json()).sha; } catch (e) {} const body = { message: `Auto Translate: ${fileName}`, content: utf8_to_b64(htmlContent), branch: 'main' }; if (fileSha) body.sha = fileSha; const putRes = await gmFetch(apiUrl, { method: 'PUT', headers, body: JSON.stringify(body) }); if (!putRes.ok) throw new Error("GitHub HTTP " + putRes.status); } const addUSACOTranslateButton = () => { let titleContainer = document.querySelector('.prb-title') || document.querySelector('h2'); if (!titleContainer || titleContainer.querySelector('.gemini-trans-btn')) return; const btn = document.createElement('button'); btn.className = 'gemini-trans-btn'; btn.style.cssText = 'margin-left: 15px; color: #fff; border: 1px solid #4cae4c; background-color: #5cb85c; font-weight: bold; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 14px; outline: none !important; transition: all 0.2s; box-shadow: 0 1px 2px rgba(0,0,0,0.1);'; btn.textContent = '翻译'; btn.onmouseenter = () => { btn.style.backgroundColor = '#449d44'; }; btn.onmouseleave = () => { btn.style.backgroundColor = '#5cb85c'; }; let contentContainer = document.querySelector('.problem-text') || titleContainer.parentElement; btn.onclick = (e) => { e.preventDefault(); btn.blur(); startTranslationFlow(contentContainer, btn, btn.textContent === '重新翻译'); }; const h2 = titleContainer.tagName === 'H2' ? titleContainer : titleContainer.querySelector('h2'); if (h2) { btn.style.verticalAlign = 'middle'; h2.appendChild(btn); } else { titleContainer.appendChild(btn); } }; function renderTranslatedUI(section, btn, translatedHTML, sourceStr) { let safeText = translatedHTML.replace(/(<[^>]+>|\$\$[\s\S]*?\$\$|\$[^\$\n]+\$|\\\[[\s\S]*?\\\]|\\\([\s\S]*?\\\))/g, (m) => m.replace(/\*/g, '___STAR___')); safeText = safeText.replace(/\*\*([^*]+)\*\*/g, '$1').replace(/\*([^*]+)\*/g, '$1').replace(/`([^`]+)`/g, '$1'); const renderHTML = safeText.replace(/___STAR___/g, '*'); let transContainer = section.querySelector('.translated-zh-section'); if (!transContainer) { transContainer = document.createElement('div'); transContainer.className = 'translated-zh-section'; transContainer.style.cssText = 'margin-top: 15px; margin-bottom: 20px; padding: 15px; background-color: #fcfcfc; border: 1px solid #ddd; border-left: 4px solid #5cb85c; border-radius: 4px; position: relative; color: #333; box-shadow: 0 2px 5px rgba(0,0,0,0.05);'; section.insertBefore(transContainer, section.firstChild); } transContainer.innerHTML = `
${sourceStr}
${renderHTML}
`; const copyBtn = transContainer.querySelector('.gemini-copy-btn'); copyBtn.onmouseenter = () => { copyBtn.style.backgroundColor = '#eef1f4'; copyBtn.style.borderColor = '#9097a3'; }; copyBtn.onmouseleave = () => { copyBtn.style.backgroundColor = '#f6f8fa'; copyBtn.style.borderColor = '#d0d7de'; }; copyBtn.onclick = function() { let mkd = translatedHTML.replace(/([\s\S]*?)<\/var>/gi, (m, i) => '$' + i.replace(/<[^>]+>/g, '').replace(/^\s*\$+|\$+\s*$/g, '').trim() + '$'); mkd = mkd.replace(/]*>([\s\S]*?)<\/pre>/gi, '\n```text\n$1\n```\n').replace(/(.*?)<\/strong>/gi, '**$1**').replace(/(.*?)<\/em>/gi, '*$1*').replace(/(.*?)<\/code>/gi, '`$1`'); mkd = mkd.replace(//gi, '\n').replace(/<\/p>/gi, '\n\n').replace(/
  • /gi, '- ').replace(/<\/li>/gi, '\n').replace(/<\/h[1-6]>/gi, '\n\n').replace(/<[^>]+>/g, ''); const ta = document.createElement('textarea'); ta.innerHTML = mkd; navigator.clipboard.writeText(ta.value.replace(/\n{3,}/g, '\n\n').trim()).then(() => { copyBtn.textContent = '已复制!'; copyBtn.style.backgroundColor = '#d4edda'; setTimeout(() => { copyBtn.textContent = '复制 Markdown'; copyBtn.style.backgroundColor = '#f6f8fa'; }, 2000); }); }; const contentDiv = transContainer.querySelector('.gemini-trans-content'); contentDiv.querySelectorAll('pre').forEach(pre => { const wrapper = document.createElement('div'); wrapper.style.cssText = 'margin: 15px 0; background-color: #f6f8fa; border: 1px solid #d0d7de; border-radius: 6px; overflow: hidden;'; pre.parentNode.insertBefore(wrapper, pre); const header = document.createElement('div'); header.style.cssText = 'display: flex; justify-content: flex-end; padding: 4px 8px; background-color: #eaedf0; border-bottom: 1px solid #d0d7de;'; const preCopyBtn = document.createElement('button'); preCopyBtn.textContent = '复制'; preCopyBtn.style.cssText = 'padding: 2px 8px; font-size: 12px; cursor: pointer; outline: none !important; box-shadow: 0 1px 2px rgba(0,0,0,0.05) !important; border-radius: 4px; background: #ffffff; border: 1px solid #d0d7de !important; color: #656d76; transition: all 0.2s;'; preCopyBtn.onmouseenter = () => { preCopyBtn.style.backgroundColor = '#f0f3f6'; preCopyBtn.style.borderColor = '#9097a3'; }; preCopyBtn.onmouseleave = () => { preCopyBtn.style.backgroundColor = '#ffffff'; preCopyBtn.style.borderColor = '#d0d7de'; }; preCopyBtn.onclick = function() { let textToCopy = pre.innerText || pre.textContent; navigator.clipboard.writeText(textToCopy.trim()).then(() => { preCopyBtn.textContent = '已复制!'; preCopyBtn.style.backgroundColor = '#d4edda'; setTimeout(() => { preCopyBtn.textContent = '复制'; preCopyBtn.style.backgroundColor = '#ffffff'; }, 2000); }); }; // 组装 HTML 结构 header.appendChild(preCopyBtn); wrapper.appendChild(header); wrapper.appendChild(pre); pre.style.cssText = 'margin: 0 !important; padding: 12px 15px !important; display: block !important; overflow-x: auto !important; background: transparent !important; border: none !important; font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace !important; font-size: 13px !important; color: #1f2328 !important; line-height: 1.5 !important;'; }); if (typeof MathJax !== 'undefined') MathJax.Hub.Queue(["Typeset", MathJax.Hub, contentDiv]); btn.textContent = '重新翻译'; btn.disabled = false; } async function startTranslationFlow(section, btn, forceAI = false) { try { const fileName = getProblemFileName(); if (!forceAI) { btn.textContent = '查询题库...'; btn.disabled = true; const cloudData = await fetchFromGitHub(fileName); if (cloudData) { renderTranslatedUI(section, btn, cloudData.html, cloudData.source); return; } } // 获取当前选择模型的独立 API Key const currentApiKey = GM_getValue(`ai_api_key_${savedPresetId}`, ""); if (!currentApiKey) { btn.textContent = '翻译'; btn.disabled = false; alert(`无可用翻译记录!请点击【翻译设置】,为当前模型配置对应的 API Key!`); return; } btn.textContent = '翻译中...'; btn.disabled = true; const clone = section.cloneNode(true); if (clone.querySelector('.translated-zh-section')) clone.querySelector('.translated-zh-section').remove(); cleanHTMLForAI(clone); const preBlocks = []; clone.querySelectorAll('pre').forEach((pre, index) => { preBlocks.push(pre.outerHTML); const placeholder = document.createElement('div'); placeholder.id = `gemini-pre-placeholder-${index}`; placeholder.textContent = `[PRE_BLOCK_${index}]`; pre.parentNode.replaceChild(placeholder, pre); }); const htmlToTranslate = clone.innerHTML.trim(); if (!htmlToTranslate) { btn.textContent = '无内容'; btn.disabled = false; return; } let rawAIResponse = await callAIEngine(htmlToTranslate, currentApiKey); let translatedHTML = rawAIResponse.replace(/^```(?:html)?\s*/i, '').replace(/\s*```$/i, '').trim(); preBlocks.forEach((preHTML, index) => { const regex = new RegExp(`]*id="gemini-pre-placeholder-${index}"[^>]*>.*?`, 'gi'); const textRegex = new RegExp(`\\[PRE_BLOCK_${index}\\]`, 'gi'); if (regex.test(translatedHTML)) translatedHTML = translatedHTML.replace(regex, preHTML); else translatedHTML = translatedHTML.replace(textRegex, preHTML); }); if (enableLocalCache) GM_setValue("usaco_local_" + fileName, translatedHTML); renderTranslatedUI(section, btn, translatedHTML, forceAI ? '翻译结果' : '翻译结果'); pushToGitHub(fileName, translatedHTML).catch(e => { const statusSpan = section.querySelector('.source-status-text'); if (statusSpan) statusSpan.innerHTML += ' (⚠️ 云端同步失败)'; }); } catch (error) { console.error(error); btn.textContent = '翻译失败'; btn.disabled = false; alert("API 报错: " + error.message); } } async function callAIEngine(htmlText, apiKey) { let prompt = `你是一名资深的 USACO 算法竞赛教练,你的任务是将给定的英文 HTML 题目完美意译为符合中文算法选手阅读习惯的题面。 【🎯 核心语感】消除欧式句式。条件前置。术语规范:Subsequence -> 子序列 | Substring -> 子串 | Permutation -> 排列 | modulo -> 取模。保留 Farmer John 等原题设定。 【🛡️ 公式排版与清洗】原 HTML 中可能存在渲染废料(如 xx, NN 重复)。请你主动剔除乱码废料,只保留真正的数学源码,并转换为 Markdown 格式 ($N$)。保留 [PRE_BLOCK_X] 占位符。 【⚠️ 严格指令】请直接输出最终的纯净 HTML 代码,不要输出任何代码块标记(如 \`\`\`html),也绝对不要输出任何思考过程、解释说明或 标签!`; const modelInfo = AI_MODELS[savedPresetId] || AI_MODELS['gemini-3.1-pro']; let result = ""; let finalUrl = modelInfo.url; let finalModel = modelInfo.actualModel; if (savedPresetId === 'custom') { finalUrl = customBaseUrl; finalModel = customModelName; } if (modelInfo.protocol === "openai" || savedPresetId === "custom") { if (!finalUrl) throw new Error("自定义接口地址为空!"); const response = await gmFetch(finalUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: finalModel, messages: [ { role: "system", content: "你是一个专业的 USACO 算法竞赛翻译助手。" }, { role: "user", content: prompt + `\n\n【原文】:\n${htmlText}` } ], temperature: 0.4 }) }); const data = await response.json(); if (!response.ok) throw new Error(data.error?.message || "OpenAI / 第三方 API 请求失败"); result = data.choices[0].message.content; } else { const url = `https://generativelanguage.googleapis.com/v1beta/models/${finalModel}:generateContent?key=${apiKey}`; const response = await gmFetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt + `\n\n【原文】:\n${htmlText}` }] }], generationConfig: { temperature: 0.4 } }) }); const data = await response.json(); if (!response.ok) throw new Error(data.error?.message || "Gemini API 请求失败"); const candidate = data.candidates?.[0]; if (!candidate?.content) throw new Error(`已被 Gemini 安全策略拦截 (原因: ${candidate?.finishReason})。`); result = candidate.content.parts[0].text; } return result; } injectSettingsUI(); setTimeout(addUSACOTranslateButton, 1000); })();