Files
2026-01-10 17:38:47 +08:00

399 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 提示词格式化工具
* 提供提示词相关的格式化方法
*/
import { logger } from './logger.js';
class PromptFormatter {
// 定义特殊分隔符
static SEPARATORS = {
START: '【T:', // 标签开始标记
END: ':T】', // 标签结束标记
NEWLINE: '\n' // 换行符用于分隔多个标签
};
// 定义标点符号映射表
static PUNCTUATION_MAP = {
// 基础标点
',': ',',
'。': '.',
'、': ',',
';': ';',
':': ':',
'?': '?',
'!': '!',
// 引号
'\u201C': '"', // 中文左双引号
'\u201D': '"', // 中文右双引号
'\u2018': "'", // 中文左单引号
'\u2019': "'", // 中文右单引号
// 括号
'(': '(',
')': ')',
'【': '[',
'】': ']',
'「': '{',
'」': '}',
'『': '{',
'』': '}',
'〔': '[',
'〕': ']',
'[': '[',
']': ']',
'{': '{',
'}': '}',
'《': '<',
'》': '>',
'〈': '<',
'〉': '>',
// 其他符号
'~': '~',
'|': '|',
'·': '.',
'…': '...',
'━': '-',
'—': '-',
'──': '--',
'-': '-',
'_': '_',
'+': '+',
'×': '*',
'÷': '/',
'=': '=',
'@': '@',
'#': '#',
'$': '$',
'%': '%',
'^': '^',
'&': '&',
'*': '*',
};
// 定义需要检测的标点符号集合
static PUNCTUATION_SET = new Set([',', '.', ',', '。']);
/**
* 在文本中查找最近的标点符号
*/
static findNearestPunctuation(text, searchForward = true) {
if (!text) return false;
// 移除开头和结尾的空格
const cleanText = searchForward ? text.trimStart() : text.trimEnd();
if (!cleanText) return false;
// 搜索第一个非空格字符
const char = searchForward ? cleanText[0] : cleanText[cleanText.length - 1];
return this.PUNCTUATION_SET.has(char);
}
/**
* 格式化标签,生成四种格式
*/
static formatTag(tagValue) {
try {
// 生成四种格式
const format1 = ` ${tagValue}`; // 空格+标签
const format2 = ` ${tagValue},`; // 空格+标签+逗号
const format3 = `, ${tagValue}`; // 逗号+空格+标签
const format4 = `, ${tagValue},`; // 逗号+空格+标签+逗号
logger.debug(`标签格式化 | 结果:成功 | 原始值:${tagValue} | 格式1:"${format1}" | 格式2:"${format2}" | 格式3:"${format3}" | 格式4:"${format4}"`);
return {
format1,
format2,
format3,
format4,
insertedFormat: null // 实际插入的格式,初始为null
};
} catch (error) {
logger.error(`标签格式化 | 结果:异常 | 错误:${error.message}`);
return {
format1: ` ${tagValue}`,
format2: ` ${tagValue},`,
format3: `, ${tagValue}`,
format4: `, ${tagValue},`,
insertedFormat: null
};
}
}
/**
* 确定应该使用哪种格式
*/
static determineFormatType(beforeText, afterText) {
try {
// 判断是否为空输入框
if (!beforeText && !afterText) {
// logger.debug('格式判断 | 结果:空输入框 | 使用格式:2');
return 2; // 空输入框使用格式2(空格+标签+逗号)
}
// 检查前方文本是否只包含空格
const hasTextBefore = beforeText.trim().length > 0;
// 跨空格查找前后的标点符号
const hasCommaBefore = this.findNearestPunctuation(beforeText, false);
const hasCommaAfter = this.findNearestPunctuation(afterText, true);
// 如果前方没有实际文本(只有空格),使用格式2
if (!hasTextBefore) {
// logger.debug('格式判断 | 结果:前方无文本 | 使用格式:2');
return 2; // 使用格式2(空格+标签+逗号)
}
// 根据前后标点符号情况决定使用哪种格式
let formatType;
if (hasCommaBefore && hasCommaAfter) {
formatType = 1; // 前后都有标点,使用格式1(空格+标签)
} else if (hasCommaBefore && !hasCommaAfter) {
formatType = 2; // 前有标点后无标点,使用格式2(空格+标签+逗号)
} else if (!hasCommaBefore && hasCommaAfter) {
formatType = 3; // 前无标点后有标点,使用格式3(逗号+空格+标签)
} else {
formatType = 4; // 前后都无标点,使用格式4(逗号+空格+标签+逗号)
}
// logger.debug(`格式判断 | 前标点:${hasCommaBefore} | 后标点:${hasCommaAfter} | 前方文本:${hasTextBefore} | 使用格式:${formatType}`);
return formatType;
} catch (error) {
logger.error(`格式判断 | 结果:异常 | 错误:${error.message}`);
return 2; // 发生错误时默认使用格式2
}
}
/**
* 格式化提示词用于API调用
*/
static formatPromptForAPI(prompt) {
// 暂时直接返回原始文本,不做格式化处理
return {
formattedText: prompt,
extractedParts: [],
originalText: prompt
};
}
/**
* 获取用于API调用的纯文本
*/
static getAPIText(formatInfo) {
// 直接返回原始文本
return formatInfo?.formattedText || '';
}
/**
* 将API返回的结果恢复为原始格式
*/
static restorePromptFormat(apiResult, formatInfo) {
// 直接返回API结果
return apiResult;
}
/**
* 格式化翻译后的文本
* 根据用户设置的格式化选项进行处理
* 注意:此方法负责处理所有翻译结果的格式(包括百度翻译和LLM翻译),
* 后端不再进行任何格式预处理或后处理。
*/
static formatTranslatedText(text) {
try {
if (!text) return '';
// 记录原始文本用于日志
const originalText = text;
// 获取格式化选项(从全局 FEATURES 对象)
const options = {
punctuation: window.FEATURES?.translateFormatPunctuation ?? true,
space: window.FEATURES?.translateFormatSpace ?? true,
dots: window.FEATURES?.translateFormatDots ?? false,
newline: window.FEATURES?.translateFormatNewline ?? false
};
let formattedText = text;
// 根据是否保留换行符选择不同的处理方式
if (options.newline) {
// 保留换行符:按行处理
const lines = text.split('\n');
const formattedLines = lines.map(line => {
return this._formatLine(line, options);
});
formattedText = formattedLines.join('\n');
} else {
// 不保留换行符:整体处理(换行符会被空格替换逻辑处理)
formattedText = this._formatLine(text, options);
}
// 记录日志
if (originalText !== formattedText) {
const enabledOptions = [];
if (options.punctuation) enabledOptions.push('标点转换');
if (options.space) enabledOptions.push('空格处理');
if (options.dots) enabledOptions.push('点号处理');
if (options.newline) enabledOptions.push('保留换行');
const logFormatted = formattedText.length > 100 ?
formattedText.substring(0, 100) + '...' : formattedText;
logger.debug(`文本格式化 | 选项:[${enabledOptions.join(', ')}] | 结果:"${logFormatted}"`);
}
return formattedText;
} catch (error) {
logger.error(`文本格式化 | 结果:异常 | 错误:${error.message}`);
return text; // 发生错误时返回原始文本
}
}
/**
* 格式化单行文本
* 根据选项执行对应的格式化操作
*/
static _formatLine(line, options) {
let formattedLine = line;
// 1. 标点符号转换
if (options.punctuation) {
for (const [cnPunct, enPunct] of Object.entries(this.PUNCTUATION_MAP)) {
formattedLine = formattedLine.split(cnPunct).join(enPunct);
}
}
// 2. 处理连续点号
if (options.dots) {
formattedLine = formattedLine.replace(/\.{3,}/g, '...');
}
// 3. 处理多余空格
if (options.space) {
formattedLine = formattedLine
.replace(/\s+/g, ' ') // 多个空格转换为单个空格
.replace(/\s*,\s*/g, ', ') // 统一逗号后的空格
.trim(); // 去除首尾空格
}
return formattedLine;
}
/**
* 判断文本的语言类型
*/
static detectLanguage(text) {
try {
if (!text) {
return {
from: 'en',
to: 'zh'
};
}
// 检查是否包含中文字符
const hasChineseChars = /[\u4e00-\u9fff]/.test(text);
// 检查是否包含英文字符
const hasEnglishChars = /[a-zA-Z]/.test(text);
let from, to, type;
if (hasChineseChars && !hasEnglishChars) {
// 纯中文
from = 'zh';
to = 'en';
type = '纯中文';
} else if (!hasChineseChars && hasEnglishChars) {
// 纯英文
from = 'en';
to = 'zh';
type = '纯英文';
} else {
// 混合语言:按中文汉字数量 vs 英文单词数量比较以决定方向
const cnChars = text.match(/[\u4e00-\u9fff]/g) || [];
const enWords = text.match(/[A-Za-z]+(?:['’\-][A-Za-z]+)*/g) || [];
const cnUnits = cnChars.length;
const enUnits = enWords.length;
// 获取混合语言翻译规则
const rule = window.FEATURES?.mixedLangTranslateRule || 'auto_minor';
switch (rule) {
case 'to_zh':
// 固定翻译成中文
from = 'en';
to = 'zh';
type = '混合语言→中文';
break;
case 'to_en':
// 固定翻译成英文
from = 'zh';
to = 'en';
type = '混合语言→英文';
break;
case 'auto_major':
// 翻译大比例语言
if (cnUnits > enUnits) {
from = 'zh';
to = 'en';
type = '混合语言-翻译中文';
} else if (enUnits > cnUnits) {
from = 'en';
to = 'zh';
type = '混合语言-翻译英文';
} else {
from = 'zh';
to = 'en';
type = '混合语言';
}
break;
case 'auto_minor':
default:
// 翻译小比例语言(原逻辑)
if (cnUnits > enUnits) {
from = 'en';
to = 'zh';
type = '混合语言-翻译英文';
} else if (enUnits > cnUnits) {
from = 'zh';
to = 'en';
type = '混合语言-翻译中文';
} else {
from = 'zh';
to = 'en';
type = '混合语言';
}
break;
}
}
// 记录日志
logger.debug(`语言检测 | 结果:${type} | 翻译方向:${from}→${to}`);
return { from, to };
} catch (error) {
logger.error(`语言检测 | 结果:异常 | 错误:${error.message}`);
return {
from: 'en',
to: 'zh'
};
}
}
/**
* 判断是否为中英文混合文本
*/
static isMixedChineseEnglish(text) {
if (!text) return false;
const hasChinese = /[\u4e00-\u9fff]/.test(text);
const hasEnglish = /[A-Za-z]/.test(text);
return hasChinese && hasEnglish;
}
}
export { PromptFormatter };