// ==UserScript==
// @name Google 搜索 AI 翻译增强
// @namespace http://tampermonkey.net/
// @version 1.1
// @description 自动将中文搜索翻译成英文搜索,修复重复翻译/流程错误,支持通义千问 DashScope API。
// @author lanshi17
// @match https://www.google.com/search?*
// @match https://www.google.com.hk/search?*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect dashscope.aliyuncs.com
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// --- 配置管理 ---
const DEFAULT_CONFIG = {
apiKey: '',
apiEndpoint: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
model: 'qwen-mt-flash',
};
let CURRENT_CONFIG = loadConfig();
let isProcessing = false; // 防止重复处理
function loadConfig() {
return {
apiKey: GM_getValue('aiApiKey', DEFAULT_CONFIG.apiKey),
apiEndpoint: GM_getValue('aiApiEndpoint', DEFAULT_CONFIG.apiEndpoint),
model: GM_getValue('aiModel', DEFAULT_CONFIG.model),
};
}
function saveConfig(config) {
GM_setValue('aiApiKey', config.apiKey);
GM_setValue('aiApiEndpoint', config.apiEndpoint);
GM_setValue('aiModel', config.model);
CURRENT_CONFIG = config;
}
// --- UI 样式 ---
function injectStyles() {
GM_addStyle(`
#ai-config-button-container {
position: absolute;
top: 0;
right: 50px;
z-index: 9999;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 5px;
}
#ai-config-button {
background: #4285F4;
color: white;
border: none;
border-radius: 6px;
padding: 5px 10px;
cursor: pointer;
font-size: 14px;
line-height: 1.2;
font-weight: bold;
display: flex;
flex-direction: column;
align-items: center;
width: 75px;
height: 38px;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
transition: all 0.2s ease;
}
#ai-config-button:hover {
background: #3367d6;
transform: translateY(-1px);
}
#ai-config-button .main-text {
font-size: 16px;
white-space: nowrap;
}
#ai-config-button .sub-text {
font-size: 10px;
margin-top: 2px;
}
#translation-status {
position: fixed;
top: 5px;
right: 150px;
padding: 8px 12px;
background: #fff3cd;
border: 1px solid #ffeeba;
color: #856404;
border-radius: 4px;
z-index: 10000;
font-size: 14px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
max-width: 300px;
word-wrap: break-word;
}
#ai-config-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border: 1px solid #ccc;
padding: 25px;
border-radius: 8px;
display: none;
width: 420px;
z-index: 10001;
box-shadow: 0 8px 30px rgba(0,0,0,0.3);
max-height: 80vh;
overflow-y: auto;
}
#ai-config-modal h2 {
margin-top: 0;
color: #333;
border-bottom: 2px solid #4285F4;
padding-bottom: 10px;
}
#ai-config-modal label {
display: block;
margin: 15px 0 5px 0;
font-weight: bold;
color: #555;
}
#ai-config-modal input, #ai-config-modal select {
width: 100%;
padding: 10px;
margin-top: 5px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
#ai-config-modal input:focus, #ai-config-modal select:focus {
outline: none;
border-color: #4285F4;
box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.2);
}
#ai-config-modal .config-buttons {
text-align: right;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
#ai-config-modal button {
padding: 10px 20px;
margin-left: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
}
#ai-config-modal .save {
background: #34A853;
color: white;
}
#ai-config-modal .close {
background: #EA4335;
color: white;
}
#ai-config-modal .reset {
background: #fbbc04;
color: white;
}
#ai-config-modal .save:hover {
background: #2d9247;
}
#ai-config-modal .close:hover {
background: #d32f2f;
}
#ai-config-modal .reset:hover {
background: #eaa002;
}
`);
}
// --- 设置界面 ---
function createSettingsUI() {
injectStyles();
const configContainer = document.createElement('div');
configContainer.id = 'ai-config-button-container';
configContainer.innerHTML = `
`;
// 尝试添加到Google的顶部工具栏
const topBar = document.querySelector('#gb, [role="banner"], header, nav') || document.body;
topBar.appendChild(configContainer);
const modal = document.createElement('div');
modal.id = 'ai-config-modal';
modal.innerHTML = `
AI 翻译配置
`;
document.body.appendChild(modal);
// 填充当前配置
const keyInput = document.getElementById("api-key");
const endpointInput = document.getElementById("api-endpoint");
const modelSelect = document.getElementById("ai-model");
keyInput.value = CURRENT_CONFIG.apiKey;
endpointInput.value = CURRENT_CONFIG.apiEndpoint;
modelSelect.value = CURRENT_CONFIG.model;
// 事件监听器
const configButton = document.getElementById("ai-config-button");
configButton.onclick = () => modal.style.display = "block";
modal.querySelector('.close').onclick = () => {
modal.style.display = "none";
// 重置输入框为当前值
keyInput.value = CURRENT_CONFIG.apiKey;
endpointInput.value = CURRENT_CONFIG.apiEndpoint;
modelSelect.value = CURRENT_CONFIG.model;
};
modal.querySelector('.save').onclick = () => {
if (!validateAndSaveConfig()) {
return;
}
modal.style.display = "none";
};
modal.querySelector('.reset').onclick = () => {
if (confirm('确定要重置所有设置吗?')) {
keyInput.value = '';
endpointInput.value = DEFAULT_CONFIG.apiEndpoint;
modelSelect.value = DEFAULT_CONFIG.model;
saveConfig(DEFAULT_CONFIG);
showNotification('设置已重置为默认值');
}
};
// Enter键保存
keyInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') validateAndSaveConfig();
});
endpointInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') validateAndSaveConfig();
});
}
function validateAndSaveConfig() {
const key = document.getElementById("api-key").value.trim();
const endpoint = document.getElementById("api-endpoint").value.trim();
const model = document.getElementById("ai-model").value.trim();
if (!key) {
alert('API密钥不能为空!');
return false;
}
if (!endpoint || !isValidUrl(endpoint)) {
alert('请输入有效的API端点URL!');
return false;
}
saveConfig({ apiKey: key, apiEndpoint: endpoint, model: model });
showNotification('配置已保存!');
return true;
}
function isValidUrl(string) {
try {
new URL(string);
return true;
} catch (_) {
return false;
}
}
function showNotification(message) {
const notice = document.createElement('div');
notice.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #333;
color: white;
padding: 12px 20px;
border-radius: 6px;
z-index: 10002;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
`;
notice.textContent = message;
document.body.appendChild(notice);
setTimeout(() => {
if (notice.parentNode) {
notice.parentNode.removeChild(notice);
}
}, 2000);
}
// --- AI 翻译功能 ---
async function translateText(text) {
if (!CURRENT_CONFIG.apiKey) {
console.warn('AI翻译:API密钥未配置');
return null;
}
const body = {
model: CURRENT_CONFIG.model,
input: {
messages: [
{ role: "system", content: "你是一个专业的语言翻译助手,专门负责将中文翻译成准确、自然的英文。请保持原意不变,翻译结果要有专业性。" },
{ role: "user", content: `请将以下中文文本翻译成英文,要求准确、自然、符合英语表达习惯:\n\n${text}` }
]
},
parameters: {
result_format: "message",
temperature: 0.3
}
};
try {
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: CURRENT_CONFIG.apiEndpoint,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${CURRENT_CONFIG.apiKey}`
},
data: JSON.stringify(body),
onload: resolve,
onerror: reject,
timeout: 15000 // 15秒超时
});
});
const result = JSON.parse(response.responseText);
if (result.output && result.output.choices && result.output.choices[0]) {
return result.output.choices[0].message.content.trim();
}
return null;
} catch (error) {
console.error('AI翻译请求失败:', error);
return null;
}
}
// --- 主处理逻辑 ---
async function processSearch() {
if (isProcessing) return; // 防止重复执行
const params = new URLSearchParams(location.search);
const query = params.get("q");
if (!query) return;
// 已经是AI翻译过的,跳过
if (params.get("ai_translated") === "1") return;
const decodedQuery = decodeURIComponent(query);
// 检测是否包含中文字符
if (!/[\u4e00-\u9fa5]/.test(decodedQuery)) return;
isProcessing = true;
// 显示翻译状态
const status = document.createElement("div");
status.id = "translation-status";
status.textContent = `🤖 正在翻译: "${decodedQuery}"`;
document.body.appendChild(status);
try {
const translatedText = await translateText(decodedQuery);
if (status.parentNode) {
status.parentNode.removeChild(status);
}
if (!translatedText) {
console.warn('AI翻译失败或返回空结果');
return;
}
// 构建新的查询参数
const newParams = new URLSearchParams();
newParams.set("q", encodeURIComponent(translatedText));
newParams.set("ai_translated", "1");
// 保留其他原始参数
for (const [key, value] of params) {
if (key !== "q" && key !== "ai_translated") {
newParams.set(key, value);
}
}
// 跳转到翻译后的搜索页面
const newUrl = `${location.pathname}?${newParams.toString()}`;
if (newUrl !== location.href) {
location.replace(newUrl);
}
} catch (error) {
console.error('处理搜索翻译时出错:', error);
if (status.parentNode) {
status.parentNode.removeChild(status);
}
} finally {
isProcessing = false;
}
}
// --- 初始化 ---
function init() {
createSettingsUI();
// 监听页面变化,处理动态加载的情况
const observer = new MutationObserver(processSearch);
observer.observe(document, { childList: true, subtree: true });
// 页面加载完成后立即检查
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', processSearch);
} else {
processSearch();
}
}
init();
})();