diff --git a/formatMessages.mjs b/formatMessages.mjs index c43056f..5d29947 100644 --- a/formatMessages.mjs +++ b/formatMessages.mjs @@ -18,10 +18,7 @@ export function formatMessages(messages, proxyModel, randomFileName) { // 如果启用 clewd const CLEWD_ENABLED = process.env.CLEWD_ENABLED === 'true'; if (CLEWD_ENABLED) { - messages = messages.map(message => { - message.content = xmlPlot(message.content, roleFeatures); - return message; - }); + messages = xmlPlotAllMessages(messages, roleFeatures); } const hasAIRound0 = messages.some(message => message.content.includes('')); @@ -150,7 +147,7 @@ function getRoleFeatures(messages, isClaudeModel, useBackspacePrefix) { customRoles[roleKey.toLowerCase()] = match[2]; } }); - + if (Object.keys(customRoles).length > 0) { prefix = ''; @@ -190,7 +187,7 @@ function removeCustomRoleDefinitions(messages) { // 转换角色 function convertRoles(messages, roleFeatures) { - const { systemRole, userRole, assistantRole } = roleFeatures; + const {systemRole, userRole, assistantRole} = roleFeatures; const roleMap = { 'system': systemRole, 'user': userRole, @@ -207,7 +204,7 @@ function convertRoles(messages, roleFeatures) { } else { const roleKey = currentRole.toLowerCase(); const newRole = roleMap[roleKey] || currentRole; - return { ...message, role: newRole }; + return {...message, role: newRole}; } }); } @@ -253,6 +250,42 @@ function replaceRolesInContent(messages, roleFeatures) { }); } +/** + * xmlPlotAllMessages(messages, roleFeatures, options) + * + * 在 CLEWD 阶段,对一组 messages 进行二次处理: + * 默认将所有消息的 role 设置为空字符串(可以视为“去掉角色”)。 + * 若 message.content 中含有 <|KEEP_ROLE|> 标记,则保留原先的 role,不置空。 + * 通过 options.skipSystem = true/false,决定是否跳过 system 段落处理。 + * 调用 xmlPlot(...) 时传入 apiKey / skipSystem 等参数,以进一步根据老版逻辑进行区分。 + * + * @param {Array} messages - [{ role: 'user'/..., content: '...' }, ...] + * @param {object} roleFeatures - { systemRole, userRole, assistantRole, prefix } + * @return {Array} 新的 messages(content 已经过多轮正则处理) + * ----------------------------------------------------------------------------------- + */ +export function xmlPlotAllMessages(messages, roleFeatures) { + return messages.map(msg => { + if (!msg.content.includes('<|KEEP_ROLE|>')) { + msg = { + ...msg, + role: '' // 置空 + }; + } + const newContent = xmlPlot(msg.content, roleFeatures); + + return { + ...msg, + content: newContent + }; + }); +} + +/** + * 对单条文本 content 进行多轮正则及合并处理 + * @param {string} content - 消息体文本 + * @param {object} roleFeatures - { systemRole, userRole, assistantRole, prefix } + */ function xmlPlot(content, roleFeatures) { let regexLog = ''; // 第一次正则替换 @@ -267,19 +300,20 @@ function xmlPlot(content, roleFeatures) { }; content = xmlPlot_merge(content, mergeTag, roleFeatures); - // 自定义插入处理 - const escapeRegExp = (string) => string.replace(/[\b.*+?^${}()|[\]\\]/g, '\\$&'); + // 处理内嵌 <@N> ... 插入 + const escapeRegExp = (str) => str.replace(/[\b.*+?^${}()|[\]\\]/g, '\\$&'); const humanLabelRaw = `${roleFeatures.userRole}:`; const assistantLabelRaw = `${roleFeatures.assistantRole}:`; const humanLabel = escapeRegExp(humanLabelRaw); const assistantLabel = escapeRegExp(assistantLabelRaw); + // 根据段落分隔符拆分 let splitContent = content.split(new RegExp(`\\n\\n(?=${humanLabel}|${assistantLabel})`, 'g')); let match; while ((match = /<@(\d+)>(.*?)<\/@\1>/gs.exec(content)) !== null) { - let index = splitContent.length - parseInt(match[1]) - 1; - if (index >= 0) { - splitContent[index] += '\n\n' + match[2]; + let insertionIndex = splitContent.length - parseInt(match[1], 10) - 1; + if (insertionIndex >= 0) { + splitContent[insertionIndex] += '\n\n' + match[2]; } content = content.replace(match[0], ''); } @@ -295,62 +329,123 @@ function xmlPlot(content, roleFeatures) { const humanLabelPattern = new RegExp(`\\n\\n${humanLabel}`, 'g'); let segcontentHuman = content.split(humanLabelPattern); let segcontentlastIndex = segcontentHuman.length - 1; - if (segcontentlastIndex >= 2 && segcontentHuman[segcontentlastIndex].includes('<|Plain Prompt Enable|>') && !content.includes(`\n\nPlainPrompt:`)) { - content = segcontentHuman.slice(0, segcontentlastIndex).join(`\n\n${humanLabelRaw}`) + `\n\nPlainPrompt:` + segcontentHuman.slice(segcontentlastIndex).join(`\n\n${humanLabelRaw}`).replace(new RegExp(`\\n\\n${humanLabel}\\s*PlainPrompt:`, 'g'), '\n\nPlainPrompt:'); + if ( + segcontentlastIndex >= 2 && + segcontentHuman[segcontentlastIndex].includes('<|Plain Prompt Enable|>') && + !content.includes(`\n\nPlainPrompt:`) + ) { + content = segcontentHuman + .slice(0, segcontentlastIndex) + .join(`\n\n${humanLabelRaw}`) + + `\n\nPlainPrompt:` + + segcontentHuman + .slice(segcontentlastIndex) + .join(`\n\n${humanLabelRaw}`) + .replace(new RegExp(`\\n\\n${humanLabel}\\s*PlainPrompt:`, 'g'), '\n\nPlainPrompt:'); } // 第三次正则替换 content = xmlPlot_regex(content, 3, regexLog); // 清理和格式化 - content = content.replace(/.*?<\/regex>/gm, '') + content = content + // 移除剩余 包裹 + .replace(/.*?<\/regex>/gm, '') + // 统一换行 .replace(/\r\n|\r/gm, '\n') + // <|curtail|> 替换为换行 .replace(/\s*<\|curtail\|>\s*/g, '\n') + // <|join|> 去掉 .replace(/\s*<\|join\|>\s*/g, '') + // <|space|> 替换为" " .replace(/\s*<\|space\|>\s*/g, ' ') + // 修正多余的空格/换行 .replace(new RegExp(`\\s*\\n\\n(${humanLabel}|${assistantLabel})\\s+`, 'g'), '\n\n$1 ') - .replace(/<\|(\\.*?)\|>/g, function (match, p1) { + // 对 <|xxx|> 做 JSON.parse 反序列化 + .replace(/<\|(\\.*?)\|>/g, function (m, p1) { try { return JSON.parse(`"${p1.replace(/\\?"/g, '\\"')}"`); - } catch { return match } - }); - - // 确保格式正确 - content = content.replace(/\s*<\|(?!padtxt).*?\|>\s*/g, '\n\n').trim() + } catch { + return m; + } + }) + // 最后去掉多余 + .replace(/\s*<\|(?!padtxt).*?\|>\s*/g, '\n\n') + .trim() .replace(/(?<=\n)\n(?=\n)/g, ''); return content; } +/** + * 解析 "/pattern/flags":"replacement" 标签并执行替换 + * @param {string} content + * @param {number} order + * @param {string} regexLog + * @returns {string} 替换后的文本 + */ function xmlPlot_regex(content, order, regexLog) { - let matches = content.match(new RegExp(` *"(/?)(.*?)\\1(.*?)" *: *"(.*?)" *`, 'gm')); - if (matches) { - matches.forEach(match => { - try { - const reg = / *"(\/?)(.*?)\1(.*?)" *: *"(.*?)" *<\/regex>/.exec(match); - regexLog += match + '\n'; - content = content.replace(new RegExp(reg[2], reg[3]), JSON.parse(`"${reg[4].replace(/\\?"/g, '\\"')}"`)); - } catch (err) { - console.log(`Regex error: ` + match + '\n' + err); - } - }); + // 只匹配与当前 order 相符的 标签 + const patternRegex = new RegExp( + `\\s*"\\/([^"]*?)\\/([gimsyu]*)"\\s*:\\s*"(.*?)"\\s*<\\/regex>`, + 'gm' + ); + + let match; + while ((match = patternRegex.exec(content)) !== null) { + // match[0] : ... + // match[1] : order + // match[2] : pattern + // match[3] : flags + // match[4] : replacement + + const fullBlock = match[0]; + const subPattern = match[2]; + const subFlags = match[3]; + let replacement = match[4]; + + regexLog += fullBlock + '\n'; + + try { + // 构造 JS 正则 + const regObj = new RegExp(subPattern, subFlags); + // 反序列化 replacement + replacement = JSON.parse(`"${replacement.replace(/\\?"/g, '\\"')}"`); + // 执行替换 + content = content.replace(regObj, replacement); + } catch (err) { + console.log(`Regex error: ` + fullBlock + '\n' + err); + } } return content; } +/** + * 多段 "Human:..." 或 "Assistant:..." 合并 + * @param {string} content + * @param {*} mergeTag { all, system, human, assistant } + * @param {*} roleFeatures + * @returns {string} + */ function xmlPlot_merge(content, mergeTag, roleFeatures) { - const escapeRegExp = (string) => string.replace(/[\b.*+?^${}()|[\]\\]/g, '\\$&'); + const escapeRegExp = (str) => str.replace(/[\b.*+?^${}()|[\]\\]/g, '\\$&'); const humanLabelRaw = `${roleFeatures.userRole}:`; const assistantLabelRaw = `${roleFeatures.assistantRole}:`; const humanLabel = escapeRegExp(humanLabelRaw); const assistantLabel = escapeRegExp(assistantLabelRaw); + // 如果出现 xmlPlot: if (/(\n\n|^\s*)xmlPlot:\s*/.test(content)) { - content = content.replace(/(\n\n|^\s*)xmlPlot: */g, mergeTag.system && mergeTag.human && mergeTag.all ? `\n\n${humanLabelRaw} ` : '$1'); + content = content.replace( + /(\n\n|^\s*)xmlPlot:\s*/g, + mergeTag.system && mergeTag.human && mergeTag.all + ? `\n\n${humanLabelRaw} ` + : '$1' + ); } - // 合并 Human 段落 + // 合并 Human 段 if (mergeTag.all && mergeTag.human) { const humanRegex = new RegExp(`(?:\\n\\n|^\\s*)${humanLabel}(.*?)(?=\\n\\n(?:${assistantLabel}|$))`, 'gs'); content = content.replace(humanRegex, (match, p1) => { @@ -359,7 +454,7 @@ function xmlPlot_merge(content, mergeTag, roleFeatures) { }); } - // 合并 Assistant 段落 + // 合并 Assistant 段 if (mergeTag.all && mergeTag.assistant) { const assistantRegex = new RegExp(`(?:\\n\\n|^\\s*)${assistantLabel}(.*?)(?=\\n\\n(?:${humanLabel}|$))`, 'gs'); content = content.replace(assistantRegex, (match, p1) => {