import { describe, expect, it } from "vitest"; import { applyRegexScripts, hasRegexFor, readRegexScripts, runRegexScript, type PresetRegexScript, } from "../src/preset/regex-scripts.js"; const base: PresetRegexScript = { name: "t", findRegex: "/[\\s\\S]*?<\\/choices>/g", replaceString: "", trimStrings: [], placement: [2], promptOnly: true, markdownOnly: false, disabled: false, runOnEdit: false, substituteRegex: 0, minDepth: null, maxDepth: null, approximateSubstitute: false, }; describe("readRegexScripts", () => { it("从 raw.regex_scripts / extensions.regex_scripts / SPreset.RegexBinding 读并去重", () => { const raw = { regex_scripts: [ { id: "a", scriptName: "A", findRegex: "/x/g", placement: [1], promptOnly: true }, ], extensions: { regex_scripts: [ { id: "a", scriptName: "A", findRegex: "/x/g", placement: [1], promptOnly: true }, { id: "b", scriptName: "B", findRegex: "/y/g", placement: [2], markdownOnly: true }, ], SPreset: { RegexBinding: { regexes: [ { id: "c", scriptName: "C", findRegex: "/z/g", placement: [2] }, ], }, }, }, }; const scripts = readRegexScripts(raw); expect(scripts.map((s) => s.name)).toEqual(["A", "B", "C"]); expect(scripts[1]?.markdownOnly).toBe(true); expect(scripts[2]?.promptOnly).toBe(false); }); it("没有 findRegex 的条目跳过", () => { expect(readRegexScripts({ regex_scripts: [{ id: "x" }] })).toEqual([]); }); }); describe("runRegexScript(对齐酒馆 runRegexScript)", () => { it("支持 {{match}} 与 $1 / 命名组", () => { expect( runRegexScript("前言a后记", { ...base, replaceString: "[$1]", findRegex: "/(.*?)<\\/choices>/g", }), ).toBe("前言[a]后记"); expect( runRegexScript("abc", { ...base, replaceString: "<<$>>", findRegex: "/(?b)/g", }), ).toBe("a<>c"); expect( runRegexScript("打勾", { ...base, replaceString: "✓{{match}}", findRegex: "/打/g" }), ).toBe("✓打勾"); }); it("trimStrings 从匹配片段里剔掉", () => { expect( runRegexScript("前((去掉))后", { ...base, findRegex: "/\\(\\((.*?)\\)\\)/g", replaceString: "$1", trimStrings: ["去掉"], }), ).toBe("前后"); }); it("坏正则原样返回", () => { expect(runRegexScript("x", { ...base, findRegex: "/([/g" })).toBe("x"); }); }); describe("applyRegexScripts(方向 / placement / depth)", () => { it("prompt 只跑 promptOnly,display 只跑 markdownOnly,raw 只跑两者都不是的", () => { const scripts: PresetRegexScript[] = [ { ...base, name: "p", promptOnly: true, findRegex: "/A/g", replaceString: "P" }, { ...base, name: "d", promptOnly: false, markdownOnly: true, findRegex: "/A/g", replaceString: "D" }, { ...base, name: "r", promptOnly: false, markdownOnly: false, findRegex: "/A/g", replaceString: "R" }, ]; expect(applyRegexScripts("A", scripts, { placement: 2, direction: "prompt" })).toBe("P"); expect(applyRegexScripts("A", scripts, { placement: 2, direction: "display" })).toBe("D"); expect(applyRegexScripts("A", scripts, { placement: 2, direction: "raw" })).toBe("R"); }); it("placement 不匹配就不跑", () => { const scripts: PresetRegexScript[] = [{ ...base, findRegex: "/A/g", replaceString: "P" }]; expect(applyRegexScripts("A", scripts, { placement: 1, direction: "prompt" })).toBe("A"); }); it("depth 超出 min/max 就跳过", () => { const scripts: PresetRegexScript[] = [ { ...base, findRegex: "/A/g", replaceString: "P", minDepth: 2 }, ]; expect(applyRegexScripts("A", scripts, { placement: 2, direction: "prompt", depth: 1 })).toBe("A"); expect(applyRegexScripts("A", scripts, { placement: 2, direction: "prompt", depth: 3 })).toBe("P"); }); it("disabled 的脚本不跑", () => { const scripts: PresetRegexScript[] = [{ ...base, disabled: true, findRegex: "/A/g", replaceString: "P" }]; expect(applyRegexScripts("A", scripts, { placement: 2, direction: "prompt" })).toBe("A"); }); it("substituteRegex=RAW 时先展开 findRegex 里的宏", () => { const scripts: PresetRegexScript[] = [ { ...base, findRegex: "/{{char}}/g", replaceString: "X", substituteRegex: 1 }, ]; expect( applyRegexScripts("角色名", scripts, { placement: 2, direction: "prompt", expand: (text) => text.replaceAll("{{char}}", "角色名"), }), ).toBe("X"); }); it("hasRegexFor 快速判断", () => { const scripts: PresetRegexScript[] = [{ ...base }]; expect(hasRegexFor(scripts, "prompt", 2)).toBe(true); expect(hasRegexFor(scripts, "display", 2)).toBe(false); }); }); describe("装配时对对话历史跑发送前正则", () => { it("user/assistant 分别按 placement 1/2 处理,depth 生效", async () => { const { importSillyTavernPreset } = await import("../src/preset/importer.js"); const { assemblePlayWorkerMessages } = await import("../src/preset/play-frame.js"); const report = importSillyTavernPreset( { prompts: [ { identifier: "chatHistory", name: "History", role: "system", marker: true, content: "", }, ], prompt_order: [ { character_id: 100001, order: [{ identifier: "chatHistory", enabled: true }] }, ], regex_scripts: [ { id: "strip", scriptName: "去选项", findRegex: "/[\\s\\S]*?<\\/choices>/g", replaceString: "", placement: [2], promptOnly: true, }, { id: "user-only", scriptName: "改用户输入", findRegex: "/你好/g", replaceString: "您好", placement: [1], promptOnly: true, }, ], }, { name: "regex-history" }, ); const messages = assemblePlayWorkerMessages({ systemPrompt: "", preset: report.preset, pack: { worldBookBefore: [], history: [ { role: "user", content: "你好" }, { role: "assistant", content: "回答ABC" }, ], worldBookAfter: [], turn: "", postTurn: [], }, persona: null, charName: null, }); expect(messages.map((m) => m.content)).toEqual(["您好", "回答"]); expect(messages.map((m) => m.role)).toEqual(["user", "assistant"]); }); });