feat: support AI terms

This commit is contained in:
Gabe
2025-10-01 16:18:19 +08:00
parent 261bb7aa6f
commit 3c5ffc045f
8 changed files with 91 additions and 74 deletions

View File

@@ -217,6 +217,7 @@ export const apiTranslate = async ({
toLang, toLang,
apiSetting = DEFAULT_API_SETTING, apiSetting = DEFAULT_API_SETTING,
docInfo = {}, docInfo = {},
glossary = {},
useCache = true, useCache = true,
usePool = true, usePool = true,
}) => { }) => {
@@ -265,6 +266,7 @@ export const apiTranslate = async ({
toLang, toLang,
langMap, langMap,
docInfo, docInfo,
glossary,
apiSetting, apiSetting,
usePool, usePool,
batchInterval, batchInterval,
@@ -285,6 +287,7 @@ export const apiTranslate = async ({
toLang, toLang,
langMap, langMap,
docInfo, docInfo,
glossary,
apiSetting, apiSetting,
usePool, usePool,
}); });

View File

@@ -573,6 +573,7 @@ export const genTransReq = async ({ reqHook, resHook, ...args }) => {
to, to,
texts, texts,
docInfo, docInfo,
glossary,
customHeader, customHeader,
customBody, customBody,
} = args; } = args;
@@ -587,7 +588,14 @@ export const genTransReq = async ({ reqHook, resHook, ...args }) => {
if (API_SPE_TYPES.ai.has(apiType)) { if (API_SPE_TYPES.ai.has(apiType)) {
args.systemPrompt = genSystemPrompt({ systemPrompt, from, to }); args.systemPrompt = genSystemPrompt({ systemPrompt, from, to });
args.userPrompt = genUserPrompt({ userPrompt, from, to, texts, docInfo }); args.userPrompt = genUserPrompt({
userPrompt,
from,
to,
texts,
docInfo,
glossary,
});
} }
const { const {
@@ -784,7 +792,17 @@ export const parseTransRes = async (
*/ */
export const handleTranslate = async ( export const handleTranslate = async (
texts = [], texts = [],
{ from, to, fromLang, toLang, langMap, docInfo, apiSetting, usePool } {
from,
to,
fromLang,
toLang,
langMap,
docInfo,
glossary,
apiSetting,
usePool,
}
) => { ) => {
let history = null; let history = null;
let hisMsgs = []; let hisMsgs = [];
@@ -815,6 +833,7 @@ export const handleTranslate = async (
toLang, toLang,
langMap, langMap,
docInfo, docInfo,
glossary,
hisMsgs, hisMsgs,
token, token,
...apiSetting, ...apiSetting,

View File

@@ -668,6 +668,16 @@ export const I18N = {
en: `1. Supports regular expression matching, no slash required, and no modifiers are supported. 2. Separate multiple terms with newlines or semicolons ";". 3. Terms and translations are separated by English commas ",". 4. If there is no translation, the term will be deemed not to be translated.`, en: `1. Supports regular expression matching, no slash required, and no modifiers are supported. 2. Separate multiple terms with newlines or semicolons ";". 3. Terms and translations are separated by English commas ",". 4. If there is no translation, the term will be deemed not to be translated.`,
zh_TW: `1. 支援正則表達式比對無需斜線且不支援修飾符。2. 多條術語以換行或分號「;」分隔。3. 術語與譯文以英文逗號「,」分隔。4. 無譯文者視為不翻譯該術語。`, zh_TW: `1. 支援正則表達式比對無需斜線且不支援修飾符。2. 多條術語以換行或分號「;」分隔。3. 術語與譯文以英文逗號「,」分隔。4. 無譯文者視為不翻譯該術語。`,
}, },
ai_terms: {
zh: `AI专业术语`,
en: `AI Terms`,
zh_TW: `AI專業術語`,
},
ai_terms_helper: {
zh: `1、AI智能替换不支持正则表达式。2、多条术语用换行或分号“;”隔开。3、术语和译文用英文逗号“,”隔开。4、没有译文视为不翻译术语。`,
en: `1. AI intelligent replacement does not support regular expressions.2. Separate multiple terms with newlines or semicolons ";". 3. Terms and translations are separated by English commas ",". 4. If there is no translation, the term will be deemed not to be translated.`,
zh_TW: `1.AI智能替換不支援正規表示式。2. 多條術語以換行或分號「;」分隔。3. 術語與譯文以英文逗號「,」分隔。4. 無譯文者視為不翻譯該術語。`,
},
selector_style: { selector_style: {
zh: `选择器节点样式`, zh: `选择器节点样式`,
en: `Selector Style`, en: `Selector Style`,
@@ -1453,11 +1463,6 @@ export const I18N = {
en: `Placeholder tag name`, en: `Placeholder tag name`,
zh_TW: `佔位標名`, zh_TW: `佔位標名`,
}, },
ai_terms: {
zh: `AI识别术语表`,
en: `AI Identification Glossary`,
zh_TW: `AI辨識術語表`,
},
system_prompt_helper: { system_prompt_helper: {
zh: `在未完全理解默认Prompt的情况下请勿随意修改否则可能翻译失败。`, zh: `在未完全理解默认Prompt的情况下请勿随意修改否则可能翻译失败。`,
en: `If you do not fully understand the default prompt, please do not modify it at will, otherwise the translation may fail.`, en: `If you do not fully understand the default prompt, please do not modify it at will, otherwise the translation may fail.`,

View File

@@ -80,6 +80,7 @@ export const DEFAULT_RULE = {
selector: "", // 选择器 selector: "", // 选择器
keepSelector: "", // 保留元素选择器 keepSelector: "", // 保留元素选择器
terms: "", // 专业术语 terms: "", // 专业术语
aiTerms: "", // AI专业术语
apiSlug: GLOBAL_KEY, // 翻译服务 apiSlug: GLOBAL_KEY, // 翻译服务
fromLang: GLOBAL_KEY, // 源语言 fromLang: GLOBAL_KEY, // 源语言
toLang: GLOBAL_KEY, // 目标语言 toLang: GLOBAL_KEY, // 目标语言
@@ -116,6 +117,7 @@ export const GLOBLA_RULE = {
selector: DEFAULT_SELECTOR, // 选择器 selector: DEFAULT_SELECTOR, // 选择器
keepSelector: DEFAULT_KEEP_SELECTOR, // 保留元素选择器 keepSelector: DEFAULT_KEEP_SELECTOR, // 保留元素选择器
terms: "", // 专业术语 terms: "", // 专业术语
aiTerms: "", // AI专业术语
apiSlug: OPT_TRANS_MICROSOFT, // 翻译服务 apiSlug: OPT_TRANS_MICROSOFT, // 翻译服务
fromLang: "auto", // 源语言 fromLang: "auto", // 源语言
toLang: "zh-CN", // 目标语言 toLang: "zh-CN", // 目标语言

View File

@@ -52,6 +52,7 @@ export const matchRule = async (href, { injectRules, subrulesList }) => {
"rootsSelector", "rootsSelector",
"ignoreSelector", "ignoreSelector",
"terms", "terms",
"aiTerms",
"selectStyle", "selectStyle",
"parentStyle", "parentStyle",
"injectJs", "injectJs",
@@ -134,6 +135,7 @@ export const checkRules = (rules) => {
rootsSelector, rootsSelector,
ignoreSelector, ignoreSelector,
terms, terms,
aiTerms,
selectStyle, selectStyle,
parentStyle, parentStyle,
injectJs, injectJs,
@@ -166,6 +168,7 @@ export const checkRules = (rules) => {
rootsSelector: type(rootsSelector) === "string" ? rootsSelector : "", rootsSelector: type(rootsSelector) === "string" ? rootsSelector : "",
ignoreSelector: type(ignoreSelector) === "string" ? ignoreSelector : "", ignoreSelector: type(ignoreSelector) === "string" ? ignoreSelector : "",
terms: type(terms) === "string" ? terms : "", terms: type(terms) === "string" ? terms : "",
aiTerms: type(aiTerms) === "string" ? aiTerms : "",
selectStyle: type(selectStyle) === "string" ? selectStyle : "", selectStyle: type(selectStyle) === "string" ? selectStyle : "",
parentStyle: type(parentStyle) === "string" ? parentStyle : "", parentStyle: type(parentStyle) === "string" ? parentStyle : "",
injectJs: type(injectJs) === "string" ? injectJs : "", injectJs: type(injectJs) === "string" ? injectJs : "",

View File

@@ -271,6 +271,7 @@ export class Translator {
#translationTagName = APP_NAME; // 翻译容器的标签名 #translationTagName = APP_NAME; // 翻译容器的标签名
#eventName = ""; // 通信事件名称 #eventName = ""; // 通信事件名称
#docInfo = {}; // 网页信息 #docInfo = {}; // 网页信息
#glossary = {}; // AI词典
#textClass = {}; // 译文样式class #textClass = {}; // 译文样式class
#textSheet = ""; // 译文样式字典 #textSheet = ""; // 译文样式字典
#apiSetting = null; #apiSetting = null;
@@ -334,6 +335,7 @@ export class Translator {
); );
this.#placeholderRegex = this.#createPlaceholderRegex(); this.#placeholderRegex = this.#createPlaceholderRegex();
this.#parseTerms(this.#rule.terms); this.#parseTerms(this.#rule.terms);
this.#parseAITerms(this.#rule.aiTerms);
this.#createTextStyles(); this.#createTextStyles();
this.#boundMouseMoveHandler = this.#handleMouseMove.bind(this); this.#boundMouseMoveHandler = this.#handleMouseMove.bind(this);
@@ -512,6 +514,24 @@ export class Translator {
} }
} }
#parseAITerms(termsString) {
if (!termsString || typeof termsString !== "string") return;
try {
this.#glossary = Object.fromEntries(
termsString
.split(/\n|;/)
.map((line) => {
const [k = "", v = ""] = line.split(",").map((s) => s.trim());
return [k, v];
})
.filter(([k]) => k)
);
} catch (err) {
kissLog("parse aiterms", err);
}
}
// todo: 利用AI总结 // todo: 利用AI总结
#getDocDescription() { #getDocDescription() {
try { try {
@@ -1157,6 +1177,7 @@ export class Translator {
toLang, toLang,
apiSetting: this.#apiSetting, apiSetting: this.#apiSetting,
docInfo: this.#docInfo, docInfo: this.#docInfo,
glossary: this.#glossary,
}); });
} }

View File

@@ -352,49 +352,6 @@ function ApiFields({ apiSlug, isUserApi, deleteApi }) {
multiline multiline
maxRows={10} maxRows={10}
/> */} /> */}
{/* <Box>
<Grid container spacing={2} columns={12}>
<Grid item xs={6} sm={6} md={6} lg={3}>
<ReusableAutocomplete
freeSolo
size="small"
fullWidth
options={BUILTIN_PLACEHOLDERS}
name="placeholder"
label={i18n("placeholder")}
value={placeholder}
onChange={handleChange}
/>
</Grid>
<Grid item xs={6} sm={6} md={6} lg={3}>
<ReusableAutocomplete
freeSolo
size="small"
fullWidth
options={BUILTIN_TAG_NAMES}
name="tagName"
label={i18n("tag_name")}
value={tagName}
onChange={handleChange}
/>
</Grid>
<Grid item xs={6} sm={6} md={6} lg={3}>
<TextField
select
size="small"
fullWidth
name="aiTerms"
value={aiTerms}
label={i18n("ai_terms")}
onChange={handleChange}
>
<MenuItem value={true}>{i18n("enable")}</MenuItem>
<MenuItem value={false}>{i18n("disable")}</MenuItem>
</TextField>
</Grid>
</Grid>
</Box> */}
</> </>
)} )}

View File

@@ -95,6 +95,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
rootsSelector = "", rootsSelector = "",
ignoreSelector = "", ignoreSelector = "",
terms = "", terms = "",
aiTerms = "",
selectStyle = "", selectStyle = "",
parentStyle = "", parentStyle = "",
injectJs = "", injectJs = "",
@@ -443,30 +444,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
))} ))}
</TextField> </TextField>
</Grid> </Grid>
{/* <Grid item xs={12} sm={12} md={6} lg={3}>
<TextField
select
size="small"
fullWidth
name="transTiming"
value={transTiming}
label={i18n("trigger_mode")}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
{OPT_TIMING_ALL.map((item) => (
<MenuItem key={item} value={item}>
{i18n(item)}
</MenuItem>
))}
</TextField>
</Grid> */}
</Grid>
</Box>
<Box>
<Grid container spacing={2} columns={12}>
<Grid item xs={12} sm={12} md={6} lg={3}> <Grid item xs={12} sm={12} md={6} lg={3}>
<TextField <TextField
select select
@@ -497,6 +474,25 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
onChange={handleChange} onChange={handleChange}
/> />
</Grid> </Grid>
{/* <Grid item xs={12} sm={12} md={6} lg={3}>
<TextField
select
size="small"
fullWidth
name="transTiming"
value={transTiming}
label={i18n("trigger_mode")}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
{OPT_TIMING_ALL.map((item) => (
<MenuItem key={item} value={item}>
{i18n(item)}
</MenuItem>
))}
</TextField>
</Grid> */}
</Grid> </Grid>
</Box> </Box>
@@ -527,6 +523,17 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
multiline multiline
maxRows={10} maxRows={10}
/> />
<TextField
size="small"
label={i18n("ai_terms")}
helperText={i18n("ai_terms_helper")}
name="aiTerms"
value={aiTerms}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField <TextField
size="small" size="small"