import { describe, expect, it } from "vitest"; import { importSillyTavernPreset } from "../src/preset/importer.js"; import { assemblePlayWorkerMessages } from "../src/preset/play-frame.js"; import { parseExampleDialogue } from "../src/preset/examples.js"; const rawPreset = { new_chat_prompt: "[Start a new Chat]", new_example_chat_prompt: "[Example Chat]", prompts: [ { identifier: "main", name: "main", role: "system", content: "MAIN" }, { identifier: "dialogueExamples", name: "examples", role: "system", marker: true, content: "", }, { identifier: "chatHistory", name: "history", role: "system", marker: true, content: "", }, ], prompt_order: [ { character_id: 100001, order: [ { identifier: "main", enabled: true }, { identifier: "dialogueExamples", enabled: true }, { identifier: "chatHistory", enabled: true }, ], }, ], }; const emptyPack = { worldBookBefore: [], history: [], worldBookAfter: [], turn: "", postTurn: [], }; function assemble(overrides: Record = {}) { const report = importSillyTavernPreset(rawPreset, { name: "t" }); return assemblePlayWorkerMessages({ systemPrompt: "", preset: report.preset, pack: { ...emptyPack, ...overrides }, persona: null, charName: "ProbeCard", }); } describe("酒馆包装层:new_chat_prompt 与示例对话", () => { it("chatHistory 空时插一条 new_chat_prompt", () => { const messages = assemble(); const history = messages.filter((m) => m.content === "[Start a new Chat]"); expect(history).toHaveLength(1); expect(history[0]?.role).toBe("system"); }); it("有历史时 new_chat_prompt 仍排在历史最前", () => { const messages = assemble({ history: [ { role: "user", content: "第一句" }, { role: "assistant", content: "第二句" }, ], }); const texts = messages.map((m) => m.content); const head = texts.indexOf("[Start a new Chat]"); expect(head).toBeGreaterThanOrEqual(0); expect(texts.indexOf("第一句")).toBe(head + 1); expect(texts.indexOf("第二句")).toBe(head + 2); }); it("示例对话按 切段,带 example_user / example_assistant", () => { const messages = assemble({ exampleDialogue: "\n{{user}}: 今天做什么?\n{{char}}: 读书。\n更多解释", }); expect(messages.map((m) => m.content)).toContain("[Example Chat]"); const user = messages.find((m) => m.name === "example_user"); const assistant = messages.find((m) => m.name === "example_assistant"); expect(user?.role).toBe("system"); expect(user?.content).toBe("今天做什么?"); expect(assistant?.role).toBe("system"); expect(assistant?.content).toBe("读书。\n更多解释"); }); it("没有示例对话就不发 new_example_chat_prompt", () => { const texts = assemble().map((m) => m.content); expect(texts).not.toContain("[Example Chat]"); }); it("人设描述为空时不塞「姓名:X」", () => { const report = importSillyTavernPreset( { prompts: [ { identifier: "personaDescription", name: "persona", role: "system", marker: true, content: "", }, ], prompt_order: [ { character_id: 100001, order: [{ identifier: "personaDescription", enabled: true }] }, ], }, { name: "t2" }, ); const messages = assemblePlayWorkerMessages({ systemPrompt: "", preset: report.preset, pack: emptyPack, persona: { name: "User", description: "" }, charName: "ProbeCard", }); expect(messages.some((m) => String(m.content).startsWith("姓名:"))).toBe(false); }); }); describe("parseExampleDialogue", () => { it("多块示例各自成组", () => { const blocks = parseExampleDialogue({ text: "\nA: 一\nB: 二\n\nA: 三\nB: 四", userName: "A", charName: "B", }); expect(blocks.map((block) => block.length)).toEqual([2, 2]); expect(blocks[1]?.map((m) => m.content)).toEqual(["三", "四"]); }); it("续行并入上一条", () => { const blocks = parseExampleDialogue({ text: "A: 第一行\n第二行\nB: 回复", userName: "A", charName: "B", }); expect(blocks[0]?.map((m) => m.content)).toEqual(["第一行\n第二行", "回复"]); }); }); describe("角色卡三块(槽位填)", () => { const rawCardPreset = { personality_format: "\n{{personality}}\n", scenario_format: "\n{{scenario}}\n", prompts: [ { identifier: "charDescription", name: "desc", role: "system", marker: true, content: "" }, { identifier: "charPersonality", name: "pers", role: "system", marker: true, content: "" }, { identifier: "scenario", name: "scen", role: "system", marker: true, content: "" }, ], prompt_order: [ { character_id: 100001, order: [ { identifier: "charDescription", enabled: true }, { identifier: "charPersonality", enabled: true }, { identifier: "scenario", enabled: true }, ], }, ], }; function build(card: unknown) { const report = importSillyTavernPreset(rawCardPreset, { name: "card" }); return assemblePlayWorkerMessages({ systemPrompt: "", preset: report.preset, pack: { ...emptyPack, card }, persona: null, charName: "ProbeCard", }); } it("描述原样发,性格与场景套预设模板", () => { const messages = build({ description: "DESC", personality: "PERS", scenario: "SCEN", }); const texts = messages.map((m) => m.content); expect(texts).toContain("DESC"); expect(texts).toContain("\nPERS\n"); expect(texts).toContain("\nSCEN\n"); }); it("槽位为空就整条不发", () => { expect(build(undefined)).toEqual([]); }); });