Files

91 lines
3.0 KiB
JavaScript
Raw Permalink Normal View History

"use strict";
const SKILL_LINK_RE = /\[\$([^\]\n]+)\]\(([^)\n]*)\)/g;
const LEADING_SKILL_LINK_RE = /^\s*\[\$([^\]\n]+)\]\(([^)\n]*)\)\s*/;
const TRUNCATED_LEADING_SKILL_LINK_RE = /^\s*\[\$([^\]\n]+)\]\([^)\n]*$/;
const LEADING_SKILL_TOKEN_RE = /^\s*\$[A-Za-z][\w:.-]*(?:\s+|$)/;
function normalizeInlineText(text) {
return String(text || "")
.replace(/\s+/g, " ")
.trim();
}
function cleanSkillLinks(text) {
return String(text || "").replace(SKILL_LINK_RE, (_m, name) => `$${String(name || "").trim()}`);
}
function stripLeadingSkillMentions(text) {
let out = cleanSkillLinks(text);
let changed = true;
while (changed) {
changed = false;
const before = out;
out = out.replace(LEADING_SKILL_LINK_RE, "");
out = out.replace(TRUNCATED_LEADING_SKILL_LINK_RE, "");
out = out.replace(LEADING_SKILL_TOKEN_RE, "");
out = out.replace(/^\s*[::,,;;||\-–—]+\s*/, "");
changed = out !== before;
}
return out;
}
function cleanUserVisibleText(text) {
return cleanSkillLinks(text);
}
// 某些中转模型会把内部思考误当成普通正文,用 XML 风格标签直接输出。
// 只清理明确的内部块,不改动普通文本中的尖括号内容。
function stripInternalThoughtBlocks(text) {
let out = String(text || "");
for (const name of ["thinking", "analysis", "reasoning"]) {
const re = new RegExp("<" + name + "\\b[^>]*>[\\s\\S]*?</" + name + "\\s*>", "gi");
out = out.replace(re, "");
}
return out.replace(/<\/?(?:thinking|analysis|reasoning)\b[^>]*>/gi, "");
}
// 流式内容可能把标签拆在多个 chunk 中:未闭合的内部块整体暂不输出。
function stripInternalThoughtStreaming(text) {
const source = String(text || "");
let out = "";
let cursor = 0;
const openRe = /<(thinking|analysis|reasoning)\b[^>]*>/gi;
while (true) {
openRe.lastIndex = cursor;
const open = openRe.exec(source);
if (!open) {
out += source.slice(cursor);
break;
}
out += source.slice(cursor, open.index);
const closeRe = new RegExp("</" + open[1] + "\\s*>", "i");
const tail = source.slice(openRe.lastIndex);
const close = closeRe.exec(tail);
if (!close) break;
cursor = openRe.lastIndex + close.index + close[0].length;
}
return stripInternalThoughtBlocks(out);
}
function cleanThreadTitle(title, fallback = "", maxLen = 80) {
const primary = normalizeInlineText(stripLeadingSkillMentions(title));
const secondary = normalizeInlineText(stripLeadingSkillMentions(fallback));
const cleaned = primary || secondary || normalizeInlineText(cleanSkillLinks(title)) || normalizeInlineText(cleanSkillLinks(fallback));
return cleaned ? cleaned.slice(0, maxLen) : "";
}
function looksLikeSkillMentionTitle(title) {
const text = normalizeInlineText(title);
return /^\[\$[^\]]+\]\(/.test(text) || /^\$[A-Za-z][\w:.-]*(?:\s|$)/.test(text);
}
module.exports = {
cleanSkillLinks,
cleanThreadTitle,
cleanUserVisibleText,
stripInternalThoughtBlocks,
stripInternalThoughtStreaming,
looksLikeSkillMentionTitle,
};