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

View File

@@ -573,6 +573,7 @@ export const genTransReq = async ({ reqHook, resHook, ...args }) => {
to,
texts,
docInfo,
glossary,
customHeader,
customBody,
} = args;
@@ -587,7 +588,14 @@ export const genTransReq = async ({ reqHook, resHook, ...args }) => {
if (API_SPE_TYPES.ai.has(apiType)) {
args.systemPrompt = genSystemPrompt({ systemPrompt, from, to });
args.userPrompt = genUserPrompt({ userPrompt, from, to, texts, docInfo });
args.userPrompt = genUserPrompt({
userPrompt,
from,
to,
texts,
docInfo,
glossary,
});
}
const {
@@ -784,7 +792,17 @@ export const parseTransRes = async (
*/
export const handleTranslate = async (
texts = [],
{ from, to, fromLang, toLang, langMap, docInfo, apiSetting, usePool }
{
from,
to,
fromLang,
toLang,
langMap,
docInfo,
glossary,
apiSetting,
usePool,
}
) => {
let history = null;
let hisMsgs = [];
@@ -815,6 +833,7 @@ export const handleTranslate = async (
toLang,
langMap,
docInfo,
glossary,
hisMsgs,
token,
...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.`,
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: {
zh: `选择器节点样式`,
en: `Selector Style`,
@@ -1453,11 +1463,6 @@ export const I18N = {
en: `Placeholder tag name`,
zh_TW: `佔位標名`,
},
ai_terms: {
zh: `AI识别术语表`,
en: `AI Identification Glossary`,
zh_TW: `AI辨識術語表`,
},
system_prompt_helper: {
zh: `在未完全理解默认Prompt的情况下请勿随意修改否则可能翻译失败。`,
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: "", // 选择器
keepSelector: "", // 保留元素选择器
terms: "", // 专业术语
aiTerms: "", // AI专业术语
apiSlug: GLOBAL_KEY, // 翻译服务
fromLang: GLOBAL_KEY, // 源语言
toLang: GLOBAL_KEY, // 目标语言
@@ -116,6 +117,7 @@ export const GLOBLA_RULE = {
selector: DEFAULT_SELECTOR, // 选择器
keepSelector: DEFAULT_KEEP_SELECTOR, // 保留元素选择器
terms: "", // 专业术语
aiTerms: "", // AI专业术语
apiSlug: OPT_TRANS_MICROSOFT, // 翻译服务
fromLang: "auto", // 源语言
toLang: "zh-CN", // 目标语言

View File

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

View File

@@ -271,6 +271,7 @@ export class Translator {
#translationTagName = APP_NAME; // 翻译容器的标签名
#eventName = ""; // 通信事件名称
#docInfo = {}; // 网页信息
#glossary = {}; // AI词典
#textClass = {}; // 译文样式class
#textSheet = ""; // 译文样式字典
#apiSetting = null;
@@ -334,6 +335,7 @@ export class Translator {
);
this.#placeholderRegex = this.#createPlaceholderRegex();
this.#parseTerms(this.#rule.terms);
this.#parseAITerms(this.#rule.aiTerms);
this.#createTextStyles();
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总结
#getDocDescription() {
try {
@@ -1157,6 +1177,7 @@ export class Translator {
toLang,
apiSetting: this.#apiSetting,
docInfo: this.#docInfo,
glossary: this.#glossary,
});
}

View File

@@ -352,49 +352,6 @@ function ApiFields({ apiSlug, isUserApi, deleteApi }) {
multiline
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 = "",
ignoreSelector = "",
terms = "",
aiTerms = "",
selectStyle = "",
parentStyle = "",
injectJs = "",
@@ -443,30 +444,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
))}
</TextField>
</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}>
<TextField
select
@@ -497,6 +474,25 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
onChange={handleChange}
/>
</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>
@@ -527,6 +523,17 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
multiline
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
size="small"