mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
175 lines
4.5 KiB
JavaScript
175 lines
4.5 KiB
JavaScript
// ST-BME: SillyTavern 上下文数据读取辅助
|
||
// 为 prompt 变量扩展(Phase 2)提供统一的 ST 上下文数据接口
|
||
|
||
import { getContext } from "../../../extensions.js";
|
||
|
||
function safeClone(value, fallback) {
|
||
if (value == null) {
|
||
return fallback;
|
||
}
|
||
|
||
try {
|
||
if (typeof structuredClone === "function") {
|
||
return structuredClone(value);
|
||
}
|
||
} catch {
|
||
// ignore and fall back to JSON clone
|
||
}
|
||
|
||
try {
|
||
return JSON.parse(JSON.stringify(value));
|
||
} catch {
|
||
return fallback ?? value;
|
||
}
|
||
}
|
||
|
||
function resolveCharacter(ctx) {
|
||
const charId = ctx?.characterId;
|
||
return (
|
||
ctx?.character ||
|
||
ctx?.characters?.[Number(charId)] ||
|
||
ctx?.characters?.[charId] ||
|
||
null
|
||
);
|
||
}
|
||
|
||
function resolvePersona(ctx) {
|
||
return (
|
||
ctx?.powerUserSettings?.persona_description ||
|
||
ctx?.extensionSettings?.persona_description ||
|
||
ctx?.name1_description ||
|
||
ctx?.persona ||
|
||
""
|
||
);
|
||
}
|
||
|
||
function resolveCharacterDescription(char) {
|
||
return (
|
||
char?.description ||
|
||
char?.data?.description ||
|
||
char?.data?.personality ||
|
||
""
|
||
);
|
||
}
|
||
|
||
function resolveLastUserMessage(chat = []) {
|
||
return (
|
||
chat.findLast?.((message) => message?.is_user)?.mes ||
|
||
[...chat].reverse().find((message) => message?.is_user)?.mes ||
|
||
""
|
||
);
|
||
}
|
||
|
||
function buildStructuredSnapshot(ctx = {}) {
|
||
const char = resolveCharacter(ctx);
|
||
const chat = Array.isArray(ctx.chat) ? safeClone(ctx.chat, []) : [];
|
||
const currentTime = new Date().toLocaleString("zh-CN");
|
||
const globalVars = safeClone(
|
||
ctx.extensionSettings?.variables?.global || {},
|
||
{},
|
||
);
|
||
const localVars = safeClone(ctx.chatMetadata?.variables || {}, {});
|
||
|
||
return {
|
||
persona: {
|
||
text: resolvePersona(ctx),
|
||
lorebook:
|
||
ctx.extensionSettings?.persona_description_lorebook ||
|
||
ctx.powerUserSettings?.persona_description_lorebook ||
|
||
ctx.power_user?.persona_description_lorebook ||
|
||
"",
|
||
},
|
||
character: {
|
||
id: ctx.characterId ?? null,
|
||
name: ctx.name2 || char?.name || "",
|
||
description: resolveCharacterDescription(char),
|
||
avatar: char?.avatar ? `/characters/${char.avatar}` : "",
|
||
worldbook: char?.data?.extensions?.world || char?.extensions?.world || "",
|
||
raw: safeClone(char, null),
|
||
},
|
||
user: {
|
||
name: ctx.name1 || "",
|
||
avatar: "",
|
||
raw: safeClone(ctx.user || null, null),
|
||
},
|
||
chat: {
|
||
id: ctx.chatId || globalThis.getCurrentChatId?.() || "",
|
||
messages: chat,
|
||
lastUserMessage: resolveLastUserMessage(chat),
|
||
},
|
||
worldbook: {
|
||
character: char?.data?.extensions?.world || char?.extensions?.world || "",
|
||
persona:
|
||
ctx.extensionSettings?.persona_description_lorebook ||
|
||
ctx.powerUserSettings?.persona_description_lorebook ||
|
||
ctx.power_user?.persona_description_lorebook ||
|
||
"",
|
||
chat: ctx.chatMetadata?.world || "",
|
||
},
|
||
variables: {
|
||
global: globalVars,
|
||
local: localVars,
|
||
merged: {
|
||
...globalVars,
|
||
...localVars,
|
||
},
|
||
},
|
||
time: {
|
||
current: currentTime,
|
||
locale: "zh-CN",
|
||
},
|
||
host: {
|
||
meta: {
|
||
onlineStatus: ctx.onlineStatus || "",
|
||
selectedGroupId: ctx.selectedGroupId ?? null,
|
||
},
|
||
capabilities: {
|
||
hasGetContext: typeof getContext === "function",
|
||
hasGlobalGetContext:
|
||
typeof globalThis.SillyTavern?.getContext === "function",
|
||
hasCurrentChatId: typeof globalThis.getCurrentChatId === "function",
|
||
},
|
||
},
|
||
raw: safeClone(ctx, {}),
|
||
};
|
||
}
|
||
|
||
function buildCompatPromptAliases(snapshot) {
|
||
return {
|
||
userPersona: snapshot.persona.text,
|
||
charDescription: snapshot.character.description,
|
||
charName: snapshot.character.name,
|
||
userName: snapshot.user.name,
|
||
currentTime: snapshot.time.current,
|
||
};
|
||
}
|
||
|
||
export function getSTContextSnapshot() {
|
||
try {
|
||
const ctx = getContext?.() || {};
|
||
const snapshot = buildStructuredSnapshot(ctx);
|
||
return {
|
||
snapshot,
|
||
prompt: buildCompatPromptAliases(snapshot),
|
||
};
|
||
} catch (e) {
|
||
console.warn("[ST-BME] getSTContextSnapshot 失败:", e);
|
||
const snapshot = buildStructuredSnapshot({});
|
||
return {
|
||
snapshot,
|
||
prompt: buildCompatPromptAliases(snapshot),
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从 SillyTavern 的 getContext() 提取当前上下文数据,
|
||
* 返回的字段可直接展开传入 buildTaskPrompt 的 context 参数,
|
||
* 用户在自定义 prompt 块中可通过 {{key}} 引用。
|
||
*
|
||
* @returns {object} 上下文字段映射
|
||
*/
|
||
export function getSTContextForPrompt() {
|
||
return getSTContextSnapshot().prompt;
|
||
}
|