mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Fix recall card mounting for current user messages
This commit is contained in:
89
index.js
89
index.js
@@ -560,7 +560,18 @@ const plannerRecallHandoffs = new Map();
|
|||||||
let persistedRecallUiRefreshTimer = null;
|
let persistedRecallUiRefreshTimer = null;
|
||||||
let persistedRecallUiRefreshObserver = null;
|
let persistedRecallUiRefreshObserver = null;
|
||||||
let persistedRecallUiRefreshSession = 0;
|
let persistedRecallUiRefreshSession = 0;
|
||||||
const PERSISTED_RECALL_UI_REFRESH_RETRY_DELAYS_MS = [0, 80, 180, 320, 500];
|
const PERSISTED_RECALL_UI_REFRESH_RETRY_DELAYS_MS = [
|
||||||
|
0,
|
||||||
|
80,
|
||||||
|
180,
|
||||||
|
320,
|
||||||
|
500,
|
||||||
|
850,
|
||||||
|
1300,
|
||||||
|
2000,
|
||||||
|
3000,
|
||||||
|
4200,
|
||||||
|
];
|
||||||
const PERSISTED_RECALL_UI_DIAGNOSTIC_THROTTLE_MS = 1500;
|
const PERSISTED_RECALL_UI_DIAGNOSTIC_THROTTLE_MS = 1500;
|
||||||
const persistedRecallUiDiagnosticTimestamps = new Map();
|
const persistedRecallUiDiagnosticTimestamps = new Map();
|
||||||
const persistedRecallPersistDiagnosticTimestamps = new Map();
|
const persistedRecallPersistDiagnosticTimestamps = new Map();
|
||||||
@@ -2281,6 +2292,27 @@ function resolveRecallCardAnchor(messageElement) {
|
|||||||
return isDomNodeAttached(messageElement) ? messageElement : null;
|
return isDomNodeAttached(messageElement) ? messageElement : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRecallMessageElementPriority(messageElement) {
|
||||||
|
if (!messageElement || !isDomNodeAttached(messageElement)) return -1;
|
||||||
|
|
||||||
|
let priority = 0;
|
||||||
|
const anchor = resolveRecallCardAnchor(messageElement);
|
||||||
|
if (anchor === messageElement) priority += 1;
|
||||||
|
else if (anchor) priority += 3;
|
||||||
|
|
||||||
|
if (messageElement.querySelector?.(".mes_text")) priority += 1;
|
||||||
|
if (messageElement.classList?.contains("last_mes")) priority += 2;
|
||||||
|
if (
|
||||||
|
messageElement.getAttribute?.("is_user") === "true" ||
|
||||||
|
messageElement.dataset?.isUser === "true" ||
|
||||||
|
messageElement.classList?.contains("user_mes")
|
||||||
|
) {
|
||||||
|
priority += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeRecallCardUserInputDisplayMode(mode) {
|
function normalizeRecallCardUserInputDisplayMode(mode) {
|
||||||
const normalized = String(mode || "").trim();
|
const normalized = String(mode || "").trim();
|
||||||
if (
|
if (
|
||||||
@@ -2383,14 +2415,26 @@ function refreshPersistedRecallMessageUi() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (messageElementMap.has(messageIndex)) {
|
if (messageElementMap.has(messageIndex)) {
|
||||||
|
const previousElement = messageElementMap.get(messageIndex) || null;
|
||||||
|
const previousPriority = getRecallMessageElementPriority(previousElement);
|
||||||
|
const nextPriority = getRecallMessageElementPriority(messageElement);
|
||||||
|
const shouldReplace = nextPriority >= previousPriority;
|
||||||
debugPersistedRecallUi(
|
debugPersistedRecallUi(
|
||||||
"检测到重复消息 DOM 索引,保留首个锚点",
|
"检测到重复消息 DOM 索引,已挑选更可靠的锚点",
|
||||||
{
|
{
|
||||||
messageIndex,
|
messageIndex,
|
||||||
|
previousPriority,
|
||||||
|
nextPriority,
|
||||||
|
replaced: shouldReplace,
|
||||||
},
|
},
|
||||||
`duplicate-message-index:${messageIndex}`,
|
`duplicate-message-index:${messageIndex}`,
|
||||||
);
|
);
|
||||||
cleanupRecallArtifacts(messageElement);
|
if (shouldReplace) {
|
||||||
|
cleanupRecallArtifacts(previousElement);
|
||||||
|
messageElementMap.set(messageIndex, messageElement);
|
||||||
|
} else {
|
||||||
|
cleanupRecallArtifacts(messageElement);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
messageElementMap.set(messageIndex, messageElement);
|
messageElementMap.set(messageIndex, messageElement);
|
||||||
@@ -2598,7 +2642,13 @@ function armPersistedRecallMessageUiObserver(sessionId, runAttempt) {
|
|||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: ["mesid", "data-mesid", "data-message-id"],
|
attributeFilter: [
|
||||||
|
"mesid",
|
||||||
|
"data-mesid",
|
||||||
|
"data-message-id",
|
||||||
|
"class",
|
||||||
|
"is_user",
|
||||||
|
],
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2620,23 +2670,40 @@ function schedulePersistedRecallMessageUiRefresh(delayMs = 0) {
|
|||||||
|
|
||||||
const summary = refreshPersistedRecallMessageUi();
|
const summary = refreshPersistedRecallMessageUi();
|
||||||
|
|
||||||
const shouldRetry =
|
const shouldRetryForPending =
|
||||||
(summary.status === "missing_chat_root" ||
|
(summary.status === "missing_chat_root" ||
|
||||||
summary.status === "waiting_dom" ||
|
summary.status === "waiting_dom" ||
|
||||||
summary.status === "missing_message_anchor") &&
|
summary.status === "missing_message_anchor") &&
|
||||||
attemptIndex < retryDelays.length - 1;
|
attemptIndex < retryDelays.length - 1;
|
||||||
|
|
||||||
if (!shouldRetry) {
|
const shouldWatchForRepaint =
|
||||||
|
summary.status === "rendered" && summary.renderedCount > 0;
|
||||||
|
|
||||||
|
if (!shouldRetryForPending && !shouldWatchForRepaint) {
|
||||||
clearPersistedRecallMessageUiObserver();
|
clearPersistedRecallMessageUiObserver();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
armPersistedRecallMessageUiObserver(sessionId, runAttempt);
|
armPersistedRecallMessageUiObserver(sessionId, runAttempt);
|
||||||
attemptIndex += 1;
|
if (shouldRetryForPending) {
|
||||||
persistedRecallUiRefreshTimer = setTimeout(
|
attemptIndex += 1;
|
||||||
runAttempt,
|
persistedRecallUiRefreshTimer = setTimeout(
|
||||||
retryDelays[attemptIndex],
|
runAttempt,
|
||||||
);
|
retryDelays[attemptIndex],
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lingerMs = retryDelays[retryDelays.length - 1] || 0;
|
||||||
|
if (lingerMs <= 0) {
|
||||||
|
clearPersistedRecallMessageUiObserver();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
persistedRecallUiRefreshTimer = setTimeout(() => {
|
||||||
|
if (sessionId !== persistedRecallUiRefreshSession) return;
|
||||||
|
clearPersistedRecallMessageUiObserver();
|
||||||
|
persistedRecallUiRefreshTimer = null;
|
||||||
|
}, lingerMs);
|
||||||
};
|
};
|
||||||
|
|
||||||
persistedRecallUiRefreshTimer = setTimeout(
|
persistedRecallUiRefreshTimer = setTimeout(
|
||||||
|
|||||||
@@ -1551,6 +1551,73 @@ async function testRecallCardDelayedStableMessageIndexEventuallyRenders() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function testRecallCardSurvivesLateMessageDomReplacement() {
|
||||||
|
const chat = [
|
||||||
|
{
|
||||||
|
is_user: true,
|
||||||
|
mes: "user-0",
|
||||||
|
extra: {
|
||||||
|
bme_recall: buildPersistedRecallRecord({
|
||||||
|
injectionText: "recall-0",
|
||||||
|
selectedNodeIds: ["n1"],
|
||||||
|
nowIso: "2026-01-01T00:00:00.000Z",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const harness = await createRecallUiHarness({ chat });
|
||||||
|
harness.context.PERSISTED_RECALL_UI_REFRESH_RETRY_DELAYS_MS = [
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
40,
|
||||||
|
120,
|
||||||
|
260,
|
||||||
|
];
|
||||||
|
const originalElement = createMessageElement(harness.document, 0, {
|
||||||
|
stableId: true,
|
||||||
|
withMesBlock: true,
|
||||||
|
isUser: true,
|
||||||
|
});
|
||||||
|
harness.chatRoot.appendChild(originalElement);
|
||||||
|
|
||||||
|
try {
|
||||||
|
harness.api.schedulePersistedRecallMessageUiRefresh();
|
||||||
|
await waitForTick();
|
||||||
|
await waitForTick();
|
||||||
|
assert.equal(
|
||||||
|
harness.chatRoot.querySelectorAll(".bme-recall-card").length,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
originalElement.remove();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 180));
|
||||||
|
|
||||||
|
const replacementElement = createMessageElement(harness.document, 0, {
|
||||||
|
stableId: true,
|
||||||
|
withMesBlock: true,
|
||||||
|
isUser: true,
|
||||||
|
});
|
||||||
|
harness.chatRoot.appendChild(replacementElement);
|
||||||
|
|
||||||
|
await waitForTick();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 120));
|
||||||
|
await waitForTick();
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
harness.chatRoot.querySelectorAll(".bme-recall-card").length,
|
||||||
|
1,
|
||||||
|
"延迟重渲染后的当前 user 楼层应自动补挂 Recall Card",
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
replacementElement.querySelectorAll(".bme-recall-card").length,
|
||||||
|
1,
|
||||||
|
"卡片应重新挂到替换后的消息 DOM 上",
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
harness.restoreGlobals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function testRecallCardKeepsRetryingWhenOlderCardsAlreadyRendered() {
|
async function testRecallCardKeepsRetryingWhenOlderCardsAlreadyRendered() {
|
||||||
const chat = [
|
const chat = [
|
||||||
{
|
{
|
||||||
@@ -1613,6 +1680,57 @@ async function testRecallCardKeepsRetryingWhenOlderCardsAlreadyRendered() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function testRecallCardPrefersBetterDuplicateMessageAnchor() {
|
||||||
|
const chat = [
|
||||||
|
{
|
||||||
|
is_user: true,
|
||||||
|
mes: "user-0",
|
||||||
|
extra: {
|
||||||
|
bme_recall: buildPersistedRecallRecord({
|
||||||
|
injectionText: "recall-0",
|
||||||
|
selectedNodeIds: ["n1"],
|
||||||
|
nowIso: "2026-01-01T00:00:00.000Z",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const harness = await createRecallUiHarness({ chat });
|
||||||
|
const staleElement = createMessageElement(harness.document, 0, {
|
||||||
|
stableId: true,
|
||||||
|
withMesBlock: false,
|
||||||
|
isUser: true,
|
||||||
|
});
|
||||||
|
const liveElement = createMessageElement(harness.document, 0, {
|
||||||
|
stableId: true,
|
||||||
|
withMesBlock: true,
|
||||||
|
isUser: true,
|
||||||
|
});
|
||||||
|
liveElement.classList.add("last_mes");
|
||||||
|
harness.chatRoot.appendChild(staleElement);
|
||||||
|
harness.chatRoot.appendChild(liveElement);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const summary = harness.api.refreshPersistedRecallMessageUi();
|
||||||
|
assert.equal(summary.status, "rendered");
|
||||||
|
assert.equal(
|
||||||
|
staleElement.querySelectorAll(".bme-recall-card").length,
|
||||||
|
0,
|
||||||
|
"低质量的重复 DOM 不应抢走当前楼层卡片",
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
liveElement.querySelectorAll(".bme-recall-card").length,
|
||||||
|
1,
|
||||||
|
"应优先挂到结构更完整的那条消息 DOM 上",
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
harness.chatRoot.querySelectorAll(".mes_block .bme-recall-card").length,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
harness.restoreGlobals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function testRecallCardDoesNotMountOnNonUserFloor() {
|
async function testRecallCardDoesNotMountOnNonUserFloor() {
|
||||||
const chat = [
|
const chat = [
|
||||||
{
|
{
|
||||||
@@ -5174,7 +5292,9 @@ await testRecallCardMountsOnStandardUserMessageDom();
|
|||||||
await testRecallCardSkipsMountWithoutStableMessageIndex();
|
await testRecallCardSkipsMountWithoutStableMessageIndex();
|
||||||
await testRecallCardDelayedDomInsertionEventuallyRenders();
|
await testRecallCardDelayedDomInsertionEventuallyRenders();
|
||||||
await testRecallCardDelayedStableMessageIndexEventuallyRenders();
|
await testRecallCardDelayedStableMessageIndexEventuallyRenders();
|
||||||
|
await testRecallCardSurvivesLateMessageDomReplacement();
|
||||||
await testRecallCardKeepsRetryingWhenOlderCardsAlreadyRendered();
|
await testRecallCardKeepsRetryingWhenOlderCardsAlreadyRendered();
|
||||||
|
await testRecallCardPrefersBetterDuplicateMessageAnchor();
|
||||||
await testRecallCardDoesNotMountOnNonUserFloor();
|
await testRecallCardDoesNotMountOnNonUserFloor();
|
||||||
await testRecallCardRefreshCleansLegacyBadgeAndAvoidsDuplicates();
|
await testRecallCardRefreshCleansLegacyBadgeAndAvoidsDuplicates();
|
||||||
await testRecallCardExpandedContentRerendersAfterRecordUpdate();
|
await testRecallCardExpandedContentRerendersAfterRecordUpdate();
|
||||||
|
|||||||
Reference in New Issue
Block a user