装配只补缺不重排,消息整形也不再把尾部系统提示搬到前面。

工作台左轨的顺序被当成真实顺序用:补洞时只插缺的、不动已有的条目;整形把落单的 system 接在最后一条后面,之前是插在它前面,排出来的正文顺序跟左轨对不上。
This commit is contained in:
2026-10-26 01:09:20 +08:00
parent 9dc927282f
commit b4c83e706b
2 changed files with 38 additions and 46 deletions
+6 -1
View File
@@ -109,6 +109,11 @@ function prefixInto(message: ChatMessage, prefix: string): ChatMessage {
return { ...message, content: joinText(prefix, contentOf(message)) } as 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 消息(无则并回上一条), * 把所有 system 内容并进**紧随其后**的非 system 消息(无则并回上一条),
* 使序列只剩 user / assistant。 * 使序列只剩 user / assistant。
@@ -140,7 +145,7 @@ function foldSystemMessages(messages: ChatMessage[]): ChatMessage[] {
if (pending.length > 0) { if (pending.length > 0) {
for (let i = out.length - 1; i >= 0; i--) { for (let i = out.length - 1; i >= 0; i--) {
if (!isToolGroupMessage(out[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; break;
} }
} }
+32 -45
View File
@@ -28,7 +28,7 @@ export const WORLD_BOOK_AFTER = "worldBookAfter";
export const CURRENT_TURN = "currentTurn"; export const CURRENT_TURN = "currentTurn";
export const POST_TURN = "postTurn"; export const POST_TURN = "postTurn";
/** 导入时插入的自有洞(不含共用的 chatHistory)。 */ /** 运行时填洞的四个自有洞(不含共用的 chatHistory);缺失时自动补进顺序。 */
export const APP_INSERTED_MARKERS = [ export const APP_INSERTED_MARKERS = [
WORLD_BOOK_BEFORE, WORLD_BOOK_BEFORE,
WORLD_BOOK_AFTER, WORLD_BOOK_AFTER,
@@ -36,8 +36,6 @@ export const APP_INSERTED_MARKERS = [
POST_TURN, POST_TURN,
] as const; ] as const;
const APP_INSERTED_SET = new Set<string>(APP_INSERTED_MARKERS);
type MarkerDef = { type MarkerDef = {
id: string; id: string;
name: string; name: string;
@@ -78,8 +76,10 @@ function reindex(
} }
/** /**
* 以 chatHistory 为锚点插入四个自有 marker。 * 以 chatHistory 为锚点补齐四个自有 marker:**只补缺的,不动已有的**。
* 可重复调用:先摘掉已插入的自有洞,再按规则放回。 * 已经排进 `prompt_order` 的洞保留作者给的顺序——工作台左轨就是真实发送顺序;
* 缺的洞按酒馆的规范位置插进来:历史前 → 对话历史 → 历史后 → 本轮对话 → … → 本轮之后。
* 可重复调用:补齐之后再调一次是无操作(之前会把这四个洞重新搅回锚点,覆盖作者排的顺序)。
*/ */
export function applyExtendedMarkers(preset: PresetPackage): PresetPackage { export function applyExtendedMarkers(preset: PresetPackage): PresetPackage {
let prompts = [...preset.prompts]; let prompts = [...preset.prompts];
@@ -87,59 +87,46 @@ export function applyExtendedMarkers(preset: PresetPackage): PresetPackage {
prompts = upsertMarkerEntry(prompts, def); prompts = upsertMarkerEntry(prompts, def);
} }
const rawOrder = [...preset.promptOrder] const enabledOf = new Map(
.sort((a, b) => a.orderIndex - b.orderIndex) preset.promptOrder.map((item) => [item.promptId, item.enabled]),
.filter((item) => !APP_INSERTED_SET.has(item.promptId));
const histIndex = rawOrder.findIndex(
(item) => item.promptId === CHAT_HISTORY_MARKER,
); );
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(并启用)时,不插自有的世界书洞: // 预设自带酒馆的 worldInfoBefore / worldInfoAfter(并启用)时,不插自有的世界书洞:
// 内容直接填进预设那两条,位置上才对得上酒馆。 // 内容直接填进预设那两条,位置上才对得上酒馆。
const hasStBefore = rawOrder.some( const hasStBefore = next.some(
(item) => item.promptId === WORLD_INFO_BEFORE_MARKER && item.enabled, (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, (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)]; if (!has(CHAT_HISTORY_MARKER)) insertAt(next.length, CHAT_HISTORY_MARKER);
const afterMarkers = hasStAfter ? [] : [inserted(WORLD_BOOK_AFTER)]; 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);
let next: PresetPromptOrderItem[]; if (!has(CURRENT_TURN)) {
if (histIndex < 0) { const afterWorldBook = indexOf(WORLD_BOOK_AFTER);
next = reindex([ insertAt(
...rawOrder, afterWorldBook >= 0 ? afterWorldBook + 1 : indexOf(CHAT_HISTORY_MARKER) + 1,
...beforeMarkers, CURRENT_TURN,
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(POST_TURN)) insertAt(next.length, POST_TURN);
return { return {
...preset, ...preset,
prompts, prompts,
promptOrder: next, promptOrder: reindex(next),
}; };
} }