diff --git a/src/llm/message-squash.ts b/src/llm/message-squash.ts index 7e6392b..a01b06d 100644 --- a/src/llm/message-squash.ts +++ b/src/llm/message-squash.ts @@ -109,6 +109,11 @@ function prefixInto(message: ChatMessage, prefix: string): ChatMessage { return { ...message, content: joinText(prefix, contentOf(message)) } as ChatMessage; } +/** 排在末尾、后面没有可吞的消息时,往最后一条**后面**接(前缀会把顺序倒过来)。 */ +function suffixInto(message: ChatMessage, suffix: string): ChatMessage { + return { ...message, content: joinText(contentOf(message), suffix) } as ChatMessage; +} + /** * 把所有 system 内容并进**紧随其后**的非 system 消息(无则并回上一条), * 使序列只剩 user / assistant。 @@ -140,7 +145,7 @@ function foldSystemMessages(messages: ChatMessage[]): ChatMessage[] { if (pending.length > 0) { for (let i = out.length - 1; i >= 0; i--) { if (!isToolGroupMessage(out[i]!)) { - out[i] = prefixInto(out[i]!, pending.splice(0).join("\n\n")); + out[i] = suffixInto(out[i]!, pending.splice(0).join("\n\n")); break; } } diff --git a/src/preset/markers.ts b/src/preset/markers.ts index 38307b5..c1660a7 100644 --- a/src/preset/markers.ts +++ b/src/preset/markers.ts @@ -28,7 +28,7 @@ export const WORLD_BOOK_AFTER = "worldBookAfter"; export const CURRENT_TURN = "currentTurn"; export const POST_TURN = "postTurn"; -/** 导入时插入的自有洞(不含共用的 chatHistory)。 */ +/** 运行时填洞的四个自有洞(不含共用的 chatHistory);缺失时自动补进顺序。 */ export const APP_INSERTED_MARKERS = [ WORLD_BOOK_BEFORE, WORLD_BOOK_AFTER, @@ -36,8 +36,6 @@ export const APP_INSERTED_MARKERS = [ POST_TURN, ] as const; -const APP_INSERTED_SET = new Set(APP_INSERTED_MARKERS); - type MarkerDef = { id: string; name: string; @@ -78,8 +76,10 @@ function reindex( } /** - * 以 chatHistory 为锚点插入四个自有 marker。 - * 可重复调用:先摘掉已插入的自有洞,再按规则放回。 + * 以 chatHistory 为锚点补齐四个自有 marker:**只补缺的,不动已有的**。 + * 已经排进 `prompt_order` 的洞保留作者给的顺序——工作台左轨就是真实发送顺序; + * 缺的洞按酒馆的规范位置插进来:历史前 → 对话历史 → 历史后 → 本轮对话 → … → 本轮之后。 + * 可重复调用:补齐之后再调一次是无操作(之前会把这四个洞重新搅回锚点,覆盖作者排的顺序)。 */ export function applyExtendedMarkers(preset: PresetPackage): PresetPackage { let prompts = [...preset.prompts]; @@ -87,59 +87,46 @@ export function applyExtendedMarkers(preset: PresetPackage): PresetPackage { prompts = upsertMarkerEntry(prompts, def); } - const rawOrder = [...preset.promptOrder] - .sort((a, b) => a.orderIndex - b.orderIndex) - .filter((item) => !APP_INSERTED_SET.has(item.promptId)); - - const histIndex = rawOrder.findIndex( - (item) => item.promptId === CHAT_HISTORY_MARKER, + const enabledOf = new Map( + preset.promptOrder.map((item) => [item.promptId, item.enabled]), ); + const next = [...preset.promptOrder].sort((a, b) => a.orderIndex - b.orderIndex); + const has = (id: string) => next.some((item) => item.promptId === id); + const indexOf = (id: string) => next.findIndex((item) => item.promptId === id); + const insertAt = (index: number, id: string) => { + const item: PresetPromptOrderItem = { + promptId: id, + enabled: enabledOf.get(id) ?? true, + orderIndex: 0, + }; + next.splice(Math.max(0, Math.min(index, next.length)), 0, item); + }; + // 预设自带酒馆的 worldInfoBefore / worldInfoAfter(并启用)时,不插自有的世界书洞: // 内容直接填进预设那两条,位置上才对得上酒馆。 - const hasStBefore = rawOrder.some( + const hasStBefore = next.some( (item) => item.promptId === WORLD_INFO_BEFORE_MARKER && item.enabled, ); - const hasStAfter = rawOrder.some( + const hasStAfter = next.some( (item) => item.promptId === WORLD_INFO_AFTER_MARKER && item.enabled, ); - const inserted = (id: string): PresetPromptOrderItem => ({ - promptId: id, - enabled: true, - orderIndex: 0, - }); - const beforeMarkers = hasStBefore ? [] : [inserted(WORLD_BOOK_BEFORE)]; - const afterMarkers = hasStAfter ? [] : [inserted(WORLD_BOOK_AFTER)]; - - let next: PresetPromptOrderItem[]; - if (histIndex < 0) { - next = reindex([ - ...rawOrder, - ...beforeMarkers, - inserted(CHAT_HISTORY_MARKER), - ...afterMarkers, - inserted(CURRENT_TURN), - inserted(POST_TURN), - ]); - } else { - const before = rawOrder.slice(0, histIndex); - const history = rawOrder[histIndex]!; - const after = rawOrder.slice(histIndex + 1); - next = reindex([ - ...before, - ...beforeMarkers, - history, - ...afterMarkers, - inserted(CURRENT_TURN), - ...after, - inserted(POST_TURN), - ]); + if (!has(CHAT_HISTORY_MARKER)) insertAt(next.length, CHAT_HISTORY_MARKER); + if (!has(WORLD_BOOK_BEFORE) && !hasStBefore) insertAt(indexOf(CHAT_HISTORY_MARKER), WORLD_BOOK_BEFORE); + if (!has(WORLD_BOOK_AFTER) && !hasStAfter) insertAt(indexOf(CHAT_HISTORY_MARKER) + 1, WORLD_BOOK_AFTER); + if (!has(CURRENT_TURN)) { + const afterWorldBook = indexOf(WORLD_BOOK_AFTER); + insertAt( + afterWorldBook >= 0 ? afterWorldBook + 1 : indexOf(CHAT_HISTORY_MARKER) + 1, + CURRENT_TURN, + ); } + if (!has(POST_TURN)) insertAt(next.length, POST_TURN); return { ...preset, prompts, - promptOrder: next, + promptOrder: reindex(next), }; }