198 lines
5.5 KiB
Plaintext
198 lines
5.5 KiB
Plaintext
|
||
const defaultDelimiters = [
|
||
{ left: '\\[', right: '\\]', display: true },
|
||
{ left: '\\(', right: '\\)', display: false },
|
||
];
|
||
|
||
// 转义括号规则 - 通用数学公式解析器
|
||
function escapedBracketRule(delimiters: any) {
|
||
return (text: string, startPos: number = 0) => {
|
||
const max = text.length;
|
||
const start = startPos;
|
||
|
||
for (const { left, right, display } of delimiters) {
|
||
// 检查是否以左标记开始
|
||
if (!text.slice(start).startsWith(left)) continue;
|
||
|
||
// 跳过左标记的长度
|
||
let pos = start + left.length;
|
||
|
||
// 寻找匹配的右标记
|
||
while (pos < max) {
|
||
if (text.slice(pos).startsWith(right)) {
|
||
break;
|
||
}
|
||
pos++;
|
||
}
|
||
|
||
// 没找到匹配的右标记,跳过,进入下个匹配
|
||
if (pos >= max) continue;
|
||
|
||
// 提取数学公式内容
|
||
const content = text.slice(start + left.length, pos);
|
||
const endPos = pos + right.length;
|
||
|
||
return {
|
||
formula: content,
|
||
display,
|
||
start,
|
||
end: endPos,
|
||
left,
|
||
right,
|
||
success: true,
|
||
};
|
||
}
|
||
|
||
return {
|
||
formula: '',
|
||
display: false,
|
||
start: 0,
|
||
end: 0,
|
||
left: '',
|
||
right: '',
|
||
success: false,
|
||
};
|
||
};
|
||
}
|
||
|
||
|
||
/**
|
||
* 根据规则将连续的过程标签(ToolCall, Skill等)合并
|
||
* 规则:
|
||
* 1. 连续 2 个及以上的过程标签合并
|
||
* 2. 中间包含“执行计划”的不合并(作为分隔符)
|
||
* 3. 标签间只包含空白字符时不中断合并
|
||
* 4. 支持 :::container 语法和 PC 端的 HTML 标签语法
|
||
* @param text - 待处理的 Markdown 文本
|
||
* @returns 处理后的文本
|
||
*/
|
||
export function groupMarkdownContainers(text : string) : string {
|
||
if (!text) return '';
|
||
|
||
// 1. 匹配 HTML 格式的过程标签 (markdown-custom-process)
|
||
// 2. 匹配 :::container 格式的标签
|
||
const blockRegex = /(?:[\r\n\s]*)(?:(?:<(?:div|p)\b[^>]*>)?\s*(<markdown-custom-process\b[^>]*?>(?:<\/markdown-custom-process>|\/>)?)\s*(?:<\/(?:div|p)>)?|(:::container\b[^\n]*?\n:::))/g;
|
||
|
||
let result = '';
|
||
let lastIndex = 0;
|
||
let currentGroup : string[] = [];
|
||
let match : RegExpExecArray | null;
|
||
|
||
const flushGroup = () => {
|
||
if (currentGroup.length > 0) {
|
||
if (currentGroup.length >= 2) {
|
||
// 多个时,为了支持层级,必须使用规范化后的 HTML 标签
|
||
const groupContent = `\n\n<markdown-custom-process-group>\n${normalizedGroup.join('\n')}\n</markdown-custom-process-group>\n\n`;
|
||
result += groupContent;
|
||
} else {
|
||
// 只有一个工具时,保持原样输出,最稳妥
|
||
result += `\n\n${currentGroup[0]}\n\n`;
|
||
}
|
||
currentGroup = [];
|
||
normalizedGroup = [];
|
||
}
|
||
};
|
||
|
||
let count = 0;
|
||
let normalizedGroup : string[] = [];
|
||
|
||
while ((match = blockRegex.exec(text)) !== null) {
|
||
count++;
|
||
const textBefore = text.slice(lastIndex, match.index);
|
||
|
||
if (textBefore.trim() !== '') {
|
||
flushGroup();
|
||
result += textBefore;
|
||
} else {
|
||
if (currentGroup.length === 0) {
|
||
result += textBefore;
|
||
}
|
||
}
|
||
|
||
let tagMatch = match[1] || match[2] || '';
|
||
|
||
// 为了分组显示,我们依然需要一份规范化后的 HTML 标签
|
||
let normalizedTag = '';
|
||
if (tagMatch.startsWith(':::container')) {
|
||
const executeId = extractAttribute(tagMatch, 'executeId');
|
||
const type = extractAttribute(tagMatch, 'type');
|
||
const status = extractAttribute(tagMatch, 'status');
|
||
const name = extractAttribute(tagMatch, 'name');
|
||
const safeName = name.replace(/"/g, '"');
|
||
normalizedTag = `<markdown-custom-process executeid="${executeId}" type="${type || ''}" status="${status || ''}" name="${safeName}"></markdown-custom-process>`;
|
||
} else {
|
||
normalizedTag = tagMatch;
|
||
if (!normalizedTag.includes('</markdown-custom-process>') && !normalizedTag.endsWith('/>')) {
|
||
normalizedTag = normalizedTag.replace('>', '></markdown-custom-process>');
|
||
}
|
||
}
|
||
|
||
const isPlan = /type=["']Plan["']/.test(normalizedTag);
|
||
if (isPlan) {
|
||
flushGroup();
|
||
// Plan 类型保持原样输出
|
||
result += `\n\n${tagMatch}\n\n`;
|
||
} else {
|
||
currentGroup.push(tagMatch);
|
||
normalizedGroup.push(normalizedTag);
|
||
}
|
||
|
||
lastIndex = blockRegex.lastIndex;
|
||
}
|
||
|
||
flushGroup();
|
||
result += text.slice(lastIndex);
|
||
|
||
const finalResult = result.replace(/\n{3,}/g, '\n\n').trim();
|
||
return finalResult;
|
||
}
|
||
|
||
|
||
/**
|
||
* 从属性字符串中提取指定属性的值
|
||
*/
|
||
function extractAttribute(attrString : string, attrName : string) : string {
|
||
const regex = new RegExp(`${attrName}=["']([^"']*)["']`);
|
||
const match = attrString.match(regex);
|
||
if (match != null) {
|
||
const val = match[1];
|
||
// 还原 HTML 实体,避免在后续处理中出现双重转义
|
||
return val.replace(/"/g, '"');
|
||
}
|
||
return '';
|
||
}
|
||
|
||
|
||
|
||
// 新的数学公式替换函数 - 直接替换为 $$ 分隔符
|
||
export function replaceMathBracket(text : string) : string {
|
||
// return text; //临时关闭
|
||
|
||
// 创建只包含非美元符号分隔符的选项
|
||
const nonDollarDelimiters = defaultDelimiters.filter(
|
||
(delimiter) =>
|
||
!delimiter.left.includes('$') && !delimiter.right.includes('$'),
|
||
);
|
||
|
||
const rule = escapedBracketRule(nonDollarDelimiters);
|
||
let result = '';
|
||
let pos = 0;
|
||
|
||
while (pos < text.length) {
|
||
const match = rule(text, pos);
|
||
if (match.success) {
|
||
// 添加匹配前的文本
|
||
result += text.slice(pos, match.start);
|
||
// 替换为 $$ 分隔符
|
||
const delimiter = match.display ? '$$' : '$';
|
||
result += `${delimiter}${match.formula}${delimiter}`;
|
||
pos = match.end;
|
||
} else {
|
||
// 没有匹配,添加当前字符
|
||
result += text[pos];
|
||
pos++;
|
||
}
|
||
}
|
||
|
||
return result;
|
||
} |