Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/Main.vue
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@
<el-row v-show="compute.showAI" class="margin-bottom margin-left-2em">
<el-col :span="8" class="lightblue rounded-corner">
<el-tooltip class="box-item" effect="dark"
content="以用户身份 user 发送的对话,其中{{to}}表示目标语言,{{origin}}表示待翻译的文本内容,两者不可缺少。"
content="以用户身份 user 发送的对话,其中{{to}}表示目标语言,{{origin}}表示待翻译文本,{{context}}表示用于辅助消歧的上下文。建议保留{{origin}},并在提示词中显式使用{{context}}。"
placement="top-start" :show-after="500">
<span class="popup-text popup-vertical-left">user<el-icon class="icon-margin">
<ChatDotRound />
Expand Down
75 changes: 75 additions & 0 deletions entrypoints/main/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { buildTranslationContext } from "@/entrypoints/utils/context";

const MAX_CONTEXT_LENGTH = 120;
const CONTEXT_TAGS = 'h1, h2, h3, h4, h5, h6, p, li, dd, blockquote, figcaption, div, label';

function compactText(text: string | null | undefined, maxLength: number = MAX_CONTEXT_LENGTH) {
const compact = (text || "").replace(/\s+/g, ' ').trim();
if (!compact) return "";
return compact.length > maxLength ? `${compact.slice(0, maxLength)}...` : compact;
}

function findNearbyNodeText(nodes: Element[], startIndex: number, step: -1 | 1) {
let index = startIndex + step;
while (index >= 0 && index < nodes.length) {
const text = compactText(nodes[index]?.textContent);
if (text) return text;
index += step;
}
return "";
}

function findNearbyHeading(node: Element) {
let current: Element | null = node;
while (current) {
let sibling: Element | null = current.previousElementSibling;
while (sibling) {
if (/^H[1-6]$/.test(sibling.tagName)) {
const heading = compactText(sibling.textContent, 100);
if (heading) return heading;
}
sibling = sibling.previousElementSibling;
}
current = current.parentElement;
}

const headings = Array.from(document.querySelectorAll('h1, h2, h3'));
for (let index = headings.length - 1; index >= 0; index -= 1) {
const heading = headings[index];
const position = heading.compareDocumentPosition(node);
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
const text = compactText(heading.textContent, 100);
if (text) return text;
}
}

return "";
}

function collectContextNodes() {
return Array.from(document.querySelectorAll(CONTEXT_TAGS)).filter(node => {
if (!(node instanceof Element)) return false;
const text = compactText(node.textContent);
if (!text) return false;
if (node.classList.contains('notranslate') || node.classList.contains('sr-only')) return false;
if (node.closest('header, footer, nav, aside, script, style, noscript')) return false;
if (node.matches('button, input, textarea, select, code, pre')) return false;
return true;
});
}

export function buildNodeTranslationContext(node: Element) {
const nodes = collectContextNodes();
const index = nodes.indexOf(node);

const previousText = index >= 0 ? findNearbyNodeText(nodes, index, -1) : "";
const nextText = index >= 0 ? findNearbyNodeText(nodes, index, 1) : "";
const nearbyHeading = findNearbyHeading(node);

return [
buildTranslationContext(),
nearbyHeading ? `Nearby heading: ${nearbyHeading}` : "",
previousText ? `Previous text: ${previousText}` : "",
nextText ? `Next text: ${nextText}` : "",
].filter(Boolean).join('\n');
}
23 changes: 16 additions & 7 deletions entrypoints/main/dom.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getMainDomain, selectCompatFn } from "@/entrypoints/main/compat";
import { html } from 'js-beautify';
import { handleBtnTranslation } from "@/entrypoints/main/trans";
import { buildNodeTranslationContext } from "@/entrypoints/main/context";

// 直接翻译的标签集合(块级元素)
const directSet = new Set([
Expand Down Expand Up @@ -33,7 +34,11 @@ export function grabAllNode(rootNode: Node): Element[] {
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
{
acceptNode: (node: Node): number => {
if (node instanceof Text) return NodeFilter.FILTER_ACCEPT;
if (node instanceof Text) {
// 检查文本节点是否有实际内容(去除空白后)
const textContent = node.textContent?.replace(/[\s\u3000]/g, '') || '';
return textContent.length > 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}

if (!(node instanceof Element)) return NodeFilter.FILTER_SKIP;

Expand All @@ -59,13 +64,17 @@ export function grabAllNode(rootNode: Node): Element[] {
for (const child of node.childNodes) {
if (child.nodeType === Node.ELEMENT_NODE) {
hasElement = true;
// 检查子元素是否包含文本
if (child.textContent?.trim()) {
// 检查子元素是否包含文本(去除空白后)
const childText = child.textContent?.replace(/[\s\u3000]/g, '') || '';
if (childText.length > 0) {
hasNonEmptyElement = true;
}
}
if (child.nodeType === Node.TEXT_NODE && child.textContent?.trim()) {
hasText = true;
if (child.nodeType === Node.TEXT_NODE) {
const textContent = child.textContent?.replace(/[\s\u3000]/g, '') || '';
if (textContent.length > 0) {
hasText = true;
}
}
}

Expand Down Expand Up @@ -365,7 +374,7 @@ function handleFirstLineText(node: any): boolean {
while (child) {
if (child.nodeType === Node.TEXT_NODE && child.textContent.trim()) {
browser.runtime.sendMessage({
context: document.title,
context: buildNodeTranslationContext(node),
origin: child.textContent
})
.then((text: string) => child.textContent = text)
Expand Down Expand Up @@ -461,4 +470,4 @@ export function smashTruncationStyle(node: any) {
checkAndRemoveStyle(node, 'webkitLineClamp');
node.style.webkitLineClamp = 'unset';
node.style.maxHeight = 'unset';
}
}
17 changes: 11 additions & 6 deletions entrypoints/main/trans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { detectlang, throttle } from "@/entrypoints/utils/common";
import { getMainDomain, replaceCompatFn } from "@/entrypoints/main/compat";
import { config } from "@/entrypoints/utils/config";
import { translateText, cancelAllTranslations } from '@/entrypoints/utils/translateApi';
import { buildNodeTranslationContext } from '@/entrypoints/main/context';

let hoverTimer: any; // 鼠标悬停计时器
let htmlSet = new Set(); // 防抖
Expand Down Expand Up @@ -252,13 +253,15 @@ export function handleSingleTranslation(node: any, slide: boolean) {


function bilingualTranslate(node: any, nodeOuterHTML: any) {
if (detectlang(node.textContent.replace(/[\s\u3000]/g, '')) === config.to) return;
const cleanedText = node.textContent.replace(/[\s\u3000]/g, '');
if (!cleanedText || cleanedText.length === 0) return;
if (detectlang(cleanedText) === config.to) return;

let origin = node.textContent;
let spinner = insertLoadingSpinner(node);

// 使用队列管理的翻译API
translateText(origin, document.title)
translateText(origin, buildNodeTranslationContext(node))
.then((text: string) => {
spinner.remove();
htmlSet.delete(nodeOuterHTML);
Expand All @@ -272,13 +275,15 @@ function bilingualTranslate(node: any, nodeOuterHTML: any) {


export function singleTranslate(node: any) {
if (detectlang(node.textContent.replace(/[\s\u3000]/g, '')) === config.to) return;
const cleanedText = node.textContent.replace(/[\s\u3000]/g, '');
if (!cleanedText || cleanedText.length === 0) return;
if (detectlang(cleanedText) === config.to) return;

let origin = servicesType.isMachine(config.service) ? node.innerHTML : LLMStandardHTML(node);
let spinner = insertLoadingSpinner(node);

// 使用队列管理的翻译API
translateText(origin, document.title)
translateText(origin, buildNodeTranslationContext(node))
.then((text: string) => {
spinner.remove();

Expand Down Expand Up @@ -311,7 +316,7 @@ export const handleBtnTranslation = throttle((node: any) => {

config.count++ && storage.setItem('local:config', JSON.stringify(config));

browser.runtime.sendMessage({ context: document.title, origin: origin })
browser.runtime.sendMessage({ context: buildNodeTranslationContext(node), origin: origin })
.then((text: string) => {
cache.localSetDual(origin, text);
node.innerText = text;
Expand All @@ -331,4 +336,4 @@ function bilingualAppendChild(node: any, text: string) {
newNode.append(text);
smashTruncationStyle(node);
node.appendChild(newNode);
}
}
4 changes: 2 additions & 2 deletions entrypoints/service/azure-openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ async function azureOpenai(message: any) {
const resp = await fetch(endpoint, {
method: method.POST,
headers,
body: commonMsgTemplate(message.origin)
});
body: commonMsgTemplate(message.origin, message.context)
});

if (!resp.ok) {
const errorText = await resp.text();
Expand Down
4 changes: 2 additions & 2 deletions entrypoints/service/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ async function claude(message: any) {
const resp = await fetch(url, {
method: method.POST,
headers,
body: claudeMsgTemplate(message.origin)
});
body: claudeMsgTemplate(message.origin, message.context)
});

if (!resp.ok) {
throw new Error(`请求失败: ${resp.status} ${resp.statusText} body: ${await resp.text()}`);
Expand Down
4 changes: 2 additions & 2 deletions entrypoints/service/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async function common(message: any) {
const resp = await fetch(url, {
method: method.POST,
headers,
body: commonMsgTemplate(message.origin)
body: commonMsgTemplate(message.origin, message.context)
});

if (!resp.ok) {
Expand All @@ -37,4 +37,4 @@ async function common(message: any) {
}
}

export default common;
export default common;
2 changes: 1 addition & 1 deletion entrypoints/service/coze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async function coze( message: any) {
const resp = await fetch(url, {
method: method.POST,
headers: headers,
body: cozeTemplate(message.origin)
body: cozeTemplate(message.origin, message.context)
});

if (resp.ok) {
Expand Down
4 changes: 2 additions & 2 deletions entrypoints/service/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async function custom(message: any) {
const resp = await fetch(config.custom, {
method: method.POST,
headers: headers,
body: commonMsgTemplate(message.origin)
body: commonMsgTemplate(message.origin, message.context)
});

if (resp.ok) {
Expand All @@ -25,4 +25,4 @@ async function custom(message: any) {
}
}

export default custom;
export default custom;
6 changes: 3 additions & 3 deletions entrypoints/service/deepseek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ async function deepseek(message: any) {
const resp = await fetch(url, {
method: method.POST,
headers,
body: deepseekMsgTemplate(message.origin)
});
body: deepseekMsgTemplate(message.origin, message.context)
});

if (!resp.ok) {
throw new Error(`翻译失败: ${resp.status} ${resp.statusText} body: ${await resp.text()}`);
Expand All @@ -30,4 +30,4 @@ async function deepseek(message: any) {
}
}

export default deepseek;
export default deepseek;
2 changes: 1 addition & 1 deletion entrypoints/service/gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ async function gemini(message: any) {
const resp = await fetch(url, {
method: method.POST,
headers: {'Content-Type': 'application/json'},
body: geminiMsgTemplate(message.origin),
body: geminiMsgTemplate(message.origin, message.context),
});
if (resp.ok) {
let result = await resp.json();
Expand Down
6 changes: 3 additions & 3 deletions entrypoints/service/grok.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ async function grok(message: any) {
const resp = await fetch(url, {
method: method.POST,
headers,
body: commonMsgTemplate(message.origin)
});
body: commonMsgTemplate(message.origin, message.context)
});

if (!resp.ok) {
throw new Error(`翻译失败: ${resp.status} ${resp.statusText} body: ${await resp.text()}`);
Expand All @@ -35,4 +35,4 @@ async function grok(message: any) {
}
}

export default grok;
export default grok;
2 changes: 1 addition & 1 deletion entrypoints/service/infini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async function infini(message: any) {
const resp = await fetch(`https://cloud.infini-ai.com/maas/${model}/nvidia/chat/completions`, {
method: method.POST,
headers: headers,
body: commonMsgTemplate(message.origin)
body: commonMsgTemplate(message.origin, message.context)
});

if (resp.ok) {
Expand Down
4 changes: 2 additions & 2 deletions entrypoints/service/minimax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ async function minimax(message: any) {
const resp = await fetch(url, {
method: method.POST,
headers: headers,
body: minimaxTemplate(message.origin)
body: minimaxTemplate(message.origin, message.context)
})
if (resp.ok) {
let result = await resp.json();
Expand All @@ -30,4 +30,4 @@ async function minimax(message: any) {
}


export default minimax;
export default minimax;
4 changes: 2 additions & 2 deletions entrypoints/service/newapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async function newapi(message: any) {
const resp = await fetch(url, {
method: method.POST,
headers,
body: commonMsgTemplate(message.origin)
body: commonMsgTemplate(message.origin, message.context)
});

if (!resp.ok) {
Expand All @@ -50,4 +50,4 @@ async function newapi(message: any) {
}
}

export default newapi;
export default newapi;
2 changes: 1 addition & 1 deletion entrypoints/service/tongyi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async function tongyi(message: any) {
const resp = await fetch(url, {
method: method.POST,
headers: headers,
body: tongyiMsgTemplate(message.origin)
body: tongyiMsgTemplate(message.origin, message.context)
});

if (resp.ok) {
Expand Down
4 changes: 2 additions & 2 deletions entrypoints/service/yiyan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async function yiyan(message: any) {
const resp = await fetch(url, {
method: method.POST,
headers: {'Content-Type': 'application/json'},
body: yiyanMsgTemplate(message.origin)
body: yiyanMsgTemplate(message.origin, message.context)
});

if (resp.ok) {
Expand Down Expand Up @@ -67,4 +67,4 @@ async function getSecret() {
} else throw new Error(res.error_description || '文心一言获取 token 失败');
}

export default yiyan;
export default yiyan;
2 changes: 1 addition & 1 deletion entrypoints/service/zhipu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async function zhipu(message: any) {
const resp = await fetch(urls[services.zhipu], {
method: method.POST,
headers: headers,
body: commonMsgTemplate(message.origin)
body: commonMsgTemplate(message.origin, message.context)
});

if (resp.ok) {
Expand Down
Loading