mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Fix chat detection for graph persistence
This commit is contained in:
164
index.js
164
index.js
@@ -92,6 +92,7 @@ const GRAPH_LOAD_STATES = Object.freeze({
|
|||||||
EMPTY_CONFIRMED: "empty-confirmed",
|
EMPTY_CONFIRMED: "empty-confirmed",
|
||||||
BLOCKED: "blocked",
|
BLOCKED: "blocked",
|
||||||
});
|
});
|
||||||
|
const GRAPH_LOAD_PENDING_CHAT_ID = "__pending_chat__";
|
||||||
const GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX = `${MODULE_NAME}:graph-shadow:`;
|
const GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX = `${MODULE_NAME}:graph-shadow:`;
|
||||||
|
|
||||||
function cloneRuntimeDebugValue(value, fallback = null) {
|
function cloneRuntimeDebugValue(value, fallback = null) {
|
||||||
@@ -1152,8 +1153,68 @@ function getEmbeddingConfig(mode = null) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeChatIdCandidate(value = "") {
|
||||||
|
return String(value ?? "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function readGlobalCurrentChatId() {
|
||||||
|
try {
|
||||||
|
return normalizeChatIdCandidate(
|
||||||
|
globalThis.SillyTavern?.getCurrentChatId?.() ||
|
||||||
|
globalThis.getCurrentChatId?.() ||
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasLikelySelectedChatContext(context = getContext()) {
|
||||||
|
if (!context || typeof context !== "object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasChatMetadata =
|
||||||
|
context.chatMetadata &&
|
||||||
|
typeof context.chatMetadata === "object" &&
|
||||||
|
!Array.isArray(context.chatMetadata);
|
||||||
|
const hasChatMessages = Array.isArray(context.chat);
|
||||||
|
const hasCharacterId =
|
||||||
|
context.characterId !== undefined &&
|
||||||
|
context.characterId !== null &&
|
||||||
|
String(context.characterId).trim() !== "";
|
||||||
|
const hasGroupId =
|
||||||
|
context.groupId !== undefined &&
|
||||||
|
context.groupId !== null &&
|
||||||
|
String(context.groupId).trim() !== "";
|
||||||
|
|
||||||
|
return hasChatMetadata || hasChatMessages || hasCharacterId || hasGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCurrentChatIdentity(context = getContext()) {
|
||||||
|
const candidates = [
|
||||||
|
context?.chatId,
|
||||||
|
context?.getCurrentChatId?.(),
|
||||||
|
readGlobalCurrentChatId(),
|
||||||
|
context?.chatMetadata?.chat_id,
|
||||||
|
context?.chatMetadata?.chatId,
|
||||||
|
context?.chatMetadata?.session_id,
|
||||||
|
context?.chatMetadata?.sessionId,
|
||||||
|
];
|
||||||
|
|
||||||
|
const chatId =
|
||||||
|
candidates
|
||||||
|
.map((candidate) => normalizeChatIdCandidate(candidate))
|
||||||
|
.find(Boolean) || "";
|
||||||
|
|
||||||
|
return {
|
||||||
|
chatId,
|
||||||
|
hasLikelySelectedChat: hasLikelySelectedChatContext(context),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getCurrentChatId(context = getContext()) {
|
function getCurrentChatId(context = getContext()) {
|
||||||
return String(context?.chatId || context?.getCurrentChatId?.() || "");
|
return resolveCurrentChatIdentity(context).chatId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveInjectionPromptType(settings = {}) {
|
function resolveInjectionPromptType(settings = {}) {
|
||||||
@@ -1472,30 +1533,48 @@ function scheduleGraphLoadRetry(
|
|||||||
chatId,
|
chatId,
|
||||||
reason = "metadata-pending",
|
reason = "metadata-pending",
|
||||||
attemptIndex = 0,
|
attemptIndex = 0,
|
||||||
|
{ allowPendingChat = false, expectedChatId = "" } = {},
|
||||||
) {
|
) {
|
||||||
const normalizedChatId = String(chatId || "");
|
const normalizedChatId = String(chatId || "");
|
||||||
|
const normalizedExpectedChatId = String(
|
||||||
|
expectedChatId || normalizedChatId || "",
|
||||||
|
);
|
||||||
const delayMs = GRAPH_LOAD_RETRY_DELAYS_MS[attemptIndex];
|
const delayMs = GRAPH_LOAD_RETRY_DELAYS_MS[attemptIndex];
|
||||||
if (!normalizedChatId || !Number.isFinite(delayMs)) {
|
if ((!normalizedChatId && !allowPendingChat) || !Number.isFinite(delayMs)) {
|
||||||
clearPendingGraphLoadRetry();
|
clearPendingGraphLoadRetry();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPendingGraphLoadRetry({ resetChatId: false });
|
clearPendingGraphLoadRetry({ resetChatId: false });
|
||||||
pendingGraphLoadRetryChatId = normalizedChatId;
|
pendingGraphLoadRetryChatId =
|
||||||
|
normalizedChatId || (allowPendingChat ? GRAPH_LOAD_PENDING_CHAT_ID : "");
|
||||||
console.debug(
|
console.debug(
|
||||||
`[ST-BME] 图谱元数据尚未就绪,${delayMs}ms 后重试加载(chat=${normalizedChatId},attempt=${attemptIndex + 1},reason=${reason})`,
|
`[ST-BME] 图谱元数据尚未就绪,${delayMs}ms 后重试加载(chat=${normalizedChatId || "pending"},attempt=${attemptIndex + 1},reason=${reason})`,
|
||||||
);
|
);
|
||||||
|
|
||||||
pendingGraphLoadRetryTimer = setTimeout(() => {
|
pendingGraphLoadRetryTimer = setTimeout(() => {
|
||||||
pendingGraphLoadRetryTimer = null;
|
pendingGraphLoadRetryTimer = null;
|
||||||
if (getCurrentChatId() !== normalizedChatId) {
|
const currentChatId = getCurrentChatId();
|
||||||
|
if (
|
||||||
|
normalizedExpectedChatId &&
|
||||||
|
currentChatId &&
|
||||||
|
currentChatId !== normalizedExpectedChatId
|
||||||
|
) {
|
||||||
|
clearPendingGraphLoadRetry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!allowPendingChat &&
|
||||||
|
normalizedChatId &&
|
||||||
|
currentChatId !== normalizedChatId
|
||||||
|
) {
|
||||||
clearPendingGraphLoadRetry();
|
clearPendingGraphLoadRetry();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadGraphFromChat({
|
loadGraphFromChat({
|
||||||
attemptIndex: attemptIndex + 1,
|
attemptIndex: attemptIndex + 1,
|
||||||
expectedChatId: normalizedChatId,
|
expectedChatId: normalizedExpectedChatId,
|
||||||
source: `retry:${reason}`,
|
source: `retry:${reason}`,
|
||||||
});
|
});
|
||||||
}, delayMs);
|
}, delayMs);
|
||||||
@@ -2112,7 +2191,8 @@ function loadGraphFromChat(options = {}) {
|
|||||||
source = "direct-load",
|
source = "direct-load",
|
||||||
} = options;
|
} = options;
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const chatId = getCurrentChatId(context);
|
const chatIdentity = resolveCurrentChatIdentity(context);
|
||||||
|
const chatId = chatIdentity.chatId;
|
||||||
const normalizedExpectedChatId = String(expectedChatId || "");
|
const normalizedExpectedChatId = String(expectedChatId || "");
|
||||||
if (attemptIndex === 0) {
|
if (attemptIndex === 0) {
|
||||||
clearPendingGraphLoadRetry();
|
clearPendingGraphLoadRetry();
|
||||||
@@ -2135,6 +2215,76 @@ function loadGraphFromChat(options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!chatId) {
|
if (!chatId) {
|
||||||
|
const shouldRetry = attemptIndex < GRAPH_LOAD_RETRY_DELAYS_MS.length;
|
||||||
|
if (chatIdentity.hasLikelySelectedChat) {
|
||||||
|
currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), "");
|
||||||
|
extractionCount = 0;
|
||||||
|
lastExtractedItems = [];
|
||||||
|
lastRecalledItems = [];
|
||||||
|
lastInjectionContent = "";
|
||||||
|
runtimeStatus = createUiStatus(
|
||||||
|
"图谱加载中",
|
||||||
|
"正在等待当前聊天会话 ID 与元数据就绪",
|
||||||
|
shouldRetry ? "running" : "warning",
|
||||||
|
);
|
||||||
|
lastExtractionStatus = createUiStatus(
|
||||||
|
"待命",
|
||||||
|
shouldRetry
|
||||||
|
? "正在等待当前聊天会话 ID 就绪"
|
||||||
|
: "当前聊天会话 ID 长时间未就绪,已暂停修改图谱",
|
||||||
|
shouldRetry ? "idle" : "warning",
|
||||||
|
);
|
||||||
|
lastVectorStatus = createUiStatus(
|
||||||
|
"待命",
|
||||||
|
shouldRetry
|
||||||
|
? "正在等待当前聊天会话 ID 就绪"
|
||||||
|
: "当前聊天会话 ID 长时间未就绪,已暂停修改图谱",
|
||||||
|
shouldRetry ? "idle" : "warning",
|
||||||
|
);
|
||||||
|
lastRecallStatus = createUiStatus(
|
||||||
|
"待命",
|
||||||
|
shouldRetry
|
||||||
|
? "正在等待当前聊天会话 ID 就绪"
|
||||||
|
: "当前聊天会话 ID 长时间未就绪,已暂停图谱写回",
|
||||||
|
shouldRetry ? "idle" : "warning",
|
||||||
|
);
|
||||||
|
applyGraphLoadState(
|
||||||
|
shouldRetry ? GRAPH_LOAD_STATES.LOADING : GRAPH_LOAD_STATES.BLOCKED,
|
||||||
|
{
|
||||||
|
chatId: "",
|
||||||
|
reason: shouldRetry ? "chat-id-missing" : "chat-id-timeout",
|
||||||
|
attemptIndex,
|
||||||
|
revision: 0,
|
||||||
|
lastPersistedRevision: 0,
|
||||||
|
queuedPersistRevision: 0,
|
||||||
|
pendingPersist: false,
|
||||||
|
shadowSnapshotUsed: false,
|
||||||
|
shadowSnapshotRevision: 0,
|
||||||
|
shadowSnapshotUpdatedAt: "",
|
||||||
|
shadowSnapshotReason: "",
|
||||||
|
writesBlocked: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (shouldRetry) {
|
||||||
|
scheduleGraphLoadRetry("", "chat-id-missing", attemptIndex, {
|
||||||
|
allowPendingChat: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clearPendingGraphLoadRetry();
|
||||||
|
}
|
||||||
|
refreshPanelLiveState();
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
loaded: false,
|
||||||
|
loadState: shouldRetry
|
||||||
|
? GRAPH_LOAD_STATES.LOADING
|
||||||
|
: GRAPH_LOAD_STATES.BLOCKED,
|
||||||
|
reason: shouldRetry ? "chat-id-missing" : "chat-id-timeout",
|
||||||
|
chatId: "",
|
||||||
|
attemptIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
clearPendingGraphLoadRetry();
|
clearPendingGraphLoadRetry();
|
||||||
currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), "");
|
currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), "");
|
||||||
extractionCount = 0;
|
extractionCount = 0;
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ async function createGraphPersistenceHarness({
|
|||||||
chatId = "chat-test",
|
chatId = "chat-test",
|
||||||
chatMetadata = undefined,
|
chatMetadata = undefined,
|
||||||
sessionStore = null,
|
sessionStore = null,
|
||||||
|
globalChatId = "",
|
||||||
|
characterId = "",
|
||||||
|
groupId = null,
|
||||||
|
chat = [],
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const timers = new Map();
|
const timers = new Map();
|
||||||
let nextTimerId = 1;
|
let nextTimerId = 1;
|
||||||
@@ -136,6 +140,12 @@ async function createGraphPersistenceHarness({
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
SillyTavern: {
|
||||||
|
getCurrentChatId() {
|
||||||
|
return runtimeContext.__globalChatId;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
__globalChatId: String(globalChatId || ""),
|
||||||
refreshPanelLiveState() {
|
refreshPanelLiveState() {
|
||||||
runtimeContext.__panelRefreshCount += 1;
|
runtimeContext.__panelRefreshCount += 1;
|
||||||
},
|
},
|
||||||
@@ -173,6 +183,9 @@ async function createGraphPersistenceHarness({
|
|||||||
__chatContext: {
|
__chatContext: {
|
||||||
chatId,
|
chatId,
|
||||||
chatMetadata,
|
chatMetadata,
|
||||||
|
characterId,
|
||||||
|
groupId,
|
||||||
|
chat,
|
||||||
updateChatMetadata(patch) {
|
updateChatMetadata(patch) {
|
||||||
const base =
|
const base =
|
||||||
this.chatMetadata &&
|
this.chatMetadata &&
|
||||||
@@ -254,6 +267,43 @@ result = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const harness = await createGraphPersistenceHarness({
|
||||||
|
chatId: "",
|
||||||
|
globalChatId: "chat-global",
|
||||||
|
chatMetadata: {
|
||||||
|
st_bme_graph: createMeaningfulGraph("chat-global", "global"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const result = harness.api.loadGraphFromChat({
|
||||||
|
attemptIndex: 0,
|
||||||
|
source: "global-chat-id",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.loadState, "loaded");
|
||||||
|
assert.equal(harness.api.getCurrentGraph().historyState.chatId, "chat-global");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const harness = await createGraphPersistenceHarness({
|
||||||
|
chatId: "",
|
||||||
|
globalChatId: "",
|
||||||
|
characterId: "char-1",
|
||||||
|
chatMetadata: undefined,
|
||||||
|
chat: [{ is_user: true, mes: "hello" }],
|
||||||
|
});
|
||||||
|
const result = harness.api.loadGraphFromChat({
|
||||||
|
attemptIndex: 0,
|
||||||
|
source: "pending-chat-context",
|
||||||
|
});
|
||||||
|
const live = harness.api.getGraphPersistenceLiveState();
|
||||||
|
|
||||||
|
assert.equal(result.loadState, "loading");
|
||||||
|
assert.equal(live.loadState, "loading");
|
||||||
|
assert.equal(live.reason, "chat-id-missing");
|
||||||
|
assert.equal(live.writesBlocked, true);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const harness = await createGraphPersistenceHarness({
|
const harness = await createGraphPersistenceHarness({
|
||||||
chatId: "chat-blocked",
|
chatId: "chat-blocked",
|
||||||
|
|||||||
Reference in New Issue
Block a user