Fix chat detection for graph persistence

This commit is contained in:
Youzini-afk
2026-03-28 14:10:00 +08:00
parent e58fef240b
commit 3cdfe01137
2 changed files with 207 additions and 7 deletions

164
index.js
View File

@@ -92,6 +92,7 @@ const GRAPH_LOAD_STATES = Object.freeze({
EMPTY_CONFIRMED: "empty-confirmed",
BLOCKED: "blocked",
});
const GRAPH_LOAD_PENDING_CHAT_ID = "__pending_chat__";
const GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX = `${MODULE_NAME}:graph-shadow:`;
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()) {
return String(context?.chatId || context?.getCurrentChatId?.() || "");
return resolveCurrentChatIdentity(context).chatId;
}
function resolveInjectionPromptType(settings = {}) {
@@ -1472,30 +1533,48 @@ function scheduleGraphLoadRetry(
chatId,
reason = "metadata-pending",
attemptIndex = 0,
{ allowPendingChat = false, expectedChatId = "" } = {},
) {
const normalizedChatId = String(chatId || "");
const normalizedExpectedChatId = String(
expectedChatId || normalizedChatId || "",
);
const delayMs = GRAPH_LOAD_RETRY_DELAYS_MS[attemptIndex];
if (!normalizedChatId || !Number.isFinite(delayMs)) {
if ((!normalizedChatId && !allowPendingChat) || !Number.isFinite(delayMs)) {
clearPendingGraphLoadRetry();
return false;
}
clearPendingGraphLoadRetry({ resetChatId: false });
pendingGraphLoadRetryChatId = normalizedChatId;
pendingGraphLoadRetryChatId =
normalizedChatId || (allowPendingChat ? GRAPH_LOAD_PENDING_CHAT_ID : "");
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 = null;
if (getCurrentChatId() !== normalizedChatId) {
const currentChatId = getCurrentChatId();
if (
normalizedExpectedChatId &&
currentChatId &&
currentChatId !== normalizedExpectedChatId
) {
clearPendingGraphLoadRetry();
return;
}
if (
!allowPendingChat &&
normalizedChatId &&
currentChatId !== normalizedChatId
) {
clearPendingGraphLoadRetry();
return;
}
loadGraphFromChat({
attemptIndex: attemptIndex + 1,
expectedChatId: normalizedChatId,
expectedChatId: normalizedExpectedChatId,
source: `retry:${reason}`,
});
}, delayMs);
@@ -2112,7 +2191,8 @@ function loadGraphFromChat(options = {}) {
source = "direct-load",
} = options;
const context = getContext();
const chatId = getCurrentChatId(context);
const chatIdentity = resolveCurrentChatIdentity(context);
const chatId = chatIdentity.chatId;
const normalizedExpectedChatId = String(expectedChatId || "");
if (attemptIndex === 0) {
clearPendingGraphLoadRetry();
@@ -2135,6 +2215,76 @@ function loadGraphFromChat(options = {}) {
}
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();
currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), "");
extractionCount = 0;

View File

@@ -91,6 +91,10 @@ async function createGraphPersistenceHarness({
chatId = "chat-test",
chatMetadata = undefined,
sessionStore = null,
globalChatId = "",
characterId = "",
groupId = null,
chat = [],
} = {}) {
const timers = new Map();
let nextTimerId = 1;
@@ -136,6 +140,12 @@ async function createGraphPersistenceHarness({
return null;
},
},
SillyTavern: {
getCurrentChatId() {
return runtimeContext.__globalChatId;
},
},
__globalChatId: String(globalChatId || ""),
refreshPanelLiveState() {
runtimeContext.__panelRefreshCount += 1;
},
@@ -173,6 +183,9 @@ async function createGraphPersistenceHarness({
__chatContext: {
chatId,
chatMetadata,
characterId,
groupId,
chat,
updateChatMetadata(patch) {
const base =
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({
chatId: "chat-blocked",