fix recall card binding lag

This commit is contained in:
Youzini-afk
2026-04-04 02:56:04 +08:00
parent 0220609018
commit 4bbbd4d09d
3 changed files with 126 additions and 4 deletions

View File

@@ -219,11 +219,32 @@ export function onChatLoadedController(runtime) {
export function onMessageSentController(runtime, messageId) {
const context = runtime.getContext();
const chat = context?.chat;
const message =
Array.isArray(chat) && Number.isFinite(messageId) ? chat[messageId] : null;
const normalizedMessageId =
messageId === null || messageId === undefined || messageId === ""
? null
: Number(messageId);
let resolvedMessageId = Number.isFinite(normalizedMessageId)
? normalizedMessageId
: null;
let message =
Array.isArray(chat) && Number.isFinite(resolvedMessageId)
? chat[resolvedMessageId]
: null;
if (!message?.is_user && Array.isArray(chat)) {
for (let index = chat.length - 1; index >= 0; index--) {
if (!chat[index]?.is_user) continue;
resolvedMessageId = index;
message = chat[index];
break;
}
}
if (!message?.is_user) return;
runtime.recordRecallSentUserMessage(messageId, message.mes || "");
runtime.recordRecallSentUserMessage(
resolvedMessageId,
message.mes || "",
);
runtime.refreshPersistedRecallMessageUi?.();
}

View File

@@ -8802,6 +8802,20 @@ function onGenerationStarted(type, params = {}, dryRun = false) {
}
function onGenerationEnded(_chatLength = null) {
const recentTransaction = findRecentGenerationRecallTransactionForChat();
const recentRecallResult =
getGenerationRecallTransactionResult(recentTransaction);
ensurePersistedRecallRecordForGeneration({
generationType: recentTransaction?.generationType || "normal",
recallResult: recentRecallResult,
transaction: recentTransaction,
recallOptions: recentTransaction?.frozenRecallOptions || null,
hookName:
recentRecallResult?.hookName ||
recentTransaction?.lastRecallMeta?.hookName ||
"",
});
schedulePersistedRecallMessageUiRefresh(320);
if (typeof scheduleMessageHideApply === "function") {
scheduleMessageHideApply("generation-ended", 180);
}

View File

@@ -10,6 +10,7 @@ import {
onChatChangedController,
onGenerationAfterCommandsController,
onGenerationStartedController,
onMessageSentController,
onMessageReceivedController,
onMessageSwipedController,
registerCoreEventHooksController,
@@ -344,6 +345,7 @@ function createGenerationRecallHarness(options = {}) {
moduleInjectionCalls: [],
recordedInjectionSnapshots: [],
refreshPanelCalls: 0,
hideScheduleCalls: [],
createRecallInputRecord,
createRecallRunResult,
hashRecallInput,
@@ -380,6 +382,7 @@ function createGenerationRecallHarness(options = {}) {
};
},
getSettings: () => ({}),
$: () => ({}),
triggerChatMetadataSave: () => {
context.metadataSaveCalls += 1;
return "debounced";
@@ -393,6 +396,11 @@ function createGenerationRecallHarness(options = {}) {
schedulePersistedRecallMessageUiRefresh: () => {
context.recallUiRefreshCalls += 1;
},
getMessageHideSettings: () => ({}),
getHideRuntimeAdapters: () => ({}),
scheduleHideSettingsApply: (...args) => {
context.hideScheduleCalls.push(args);
},
estimateTokens: (text = "") =>
normalizeRecallInputText(text)
.split(/\s+/)
@@ -414,7 +422,7 @@ function createGenerationRecallHarness(options = {}) {
};
vm.createContext(context);
vm.runInContext(
`${snippet}\nresult = { hashRecallInput, buildPreGenerationRecallKey, buildGenerationAfterCommandsRecallInput, cleanupGenerationRecallTransactions, buildGenerationRecallTransactionId, beginGenerationRecallTransaction, markGenerationRecallTransactionHookState, shouldRunRecallForTransaction, createGenerationRecallContext, onGenerationStarted, onGenerationAfterCommands, onBeforeCombinePrompts, applyFinalRecallInjectionForGeneration, generationRecallTransactions, freezeHostGenerationInputSnapshot, consumeHostGenerationInputSnapshot, getPendingHostGenerationInputSnapshot, recordRecallSendIntent, recordRecallSentUserMessage, getPendingRecallSendIntent: () => pendingRecallSendIntent, getLastRecallSentUserMessage: () => lastRecallSentUserMessage };`,
`${snippet}\nresult = { hashRecallInput, buildPreGenerationRecallKey, buildGenerationAfterCommandsRecallInput, cleanupGenerationRecallTransactions, buildGenerationRecallTransactionId, beginGenerationRecallTransaction, markGenerationRecallTransactionHookState, shouldRunRecallForTransaction, createGenerationRecallContext, onGenerationStarted, onGenerationEnded, onGenerationAfterCommands, onBeforeCombinePrompts, applyFinalRecallInjectionForGeneration, ensurePersistedRecallRecordForGeneration, findRecentGenerationRecallTransactionForChat, getGenerationRecallTransactionResult, generationRecallTransactions, freezeHostGenerationInputSnapshot, consumeHostGenerationInputSnapshot, getPendingHostGenerationInputSnapshot, recordRecallSendIntent, recordRecallSentUserMessage, getPendingRecallSendIntent: () => pendingRecallSendIntent, getLastRecallSentUserMessage: () => lastRecallSentUserMessage };`,
context,
{ filename: indexPath },
);
@@ -3143,6 +3151,39 @@ async function testSwipeRoutesToRerollWithoutHistoryRecoveryFallback() {
assert.equal(result.recoveryPath, "reverse-journal");
}
async function testMessageSentFallsBackToLatestUserWhenHostMessageIdInvalid() {
const recorded = [];
let refreshCalls = 0;
onMessageSentController(
{
getContext: () => ({
chat: [
{ is_user: true, mes: "较早用户楼层" },
{ is_user: false, mes: "assistant-tail" },
{ is_user: true, mes: "最新用户楼层" },
],
}),
recordRecallSentUserMessage(messageId, text, source = "message-sent") {
recorded.push({ messageId, text, source });
},
refreshPersistedRecallMessageUi() {
refreshCalls += 1;
},
},
null,
);
assert.deepEqual(recorded, [
{
messageId: 2,
text: "最新用户楼层",
source: "message-sent",
},
]);
assert.equal(refreshCalls, 1);
}
async function testMessageReceivedQueuesExtractionWithoutRuntimeQueueMicrotask() {
let runExtractionCalls = 0;
let refreshCalls = 0;
@@ -3825,6 +3866,50 @@ async function testGenerationRecallImmediateAfterCommandsBackfillsPersistedRecor
assert.equal(harness.metadataSaveCalls > 0, true);
}
async function testGenerationEndedBackfillsRecentRecallAndSchedulesHideRefresh() {
const harness = await createGenerationRecallHarness({ realApplyFinal: true });
harness.chat = [{ is_user: true, mes: "生成结束后补写目标" }];
const transaction = harness.result.beginGenerationRecallTransaction({
chatId: "chat-main",
generationType: "normal",
recallKey: "chat-main:normal:test-generation-ended",
forceNew: true,
});
transaction.frozenRecallOptions = {
generationType: "normal",
targetUserMessageIndex: null,
overrideUserMessage: "生成结束后补写目标",
lockedSource: "send-intent",
hookName: "GENERATION_AFTER_COMMANDS",
};
harness.result.generationRecallTransactions.set(transaction.id, transaction);
harness.result.markGenerationRecallTransactionHookState(
transaction,
"GENERATION_AFTER_COMMANDS",
"completed",
);
harness.result.getGenerationRecallTransactionResult(transaction);
transaction.lastRecallResult = {
status: "completed",
didRecall: true,
injectionText: "generation-ended-memory",
selectedNodeIds: ["node-z"],
sourceCandidates: [{ text: "生成结束后补写目标" }],
hookName: "GENERATION_AFTER_COMMANDS",
};
transaction.updatedAt = Date.now();
harness.result.generationRecallTransactions.set(transaction.id, transaction);
harness.result.onGenerationEnded();
assert.equal(
harness.chat[0]?.extra?.bme_recall?.injectionText,
"generation-ended-memory",
);
assert.equal(harness.hideScheduleCalls.length, 1);
assert.equal(harness.hideScheduleCalls[0]?.[2], 180);
}
async function testRecallSubGraphAndDataLayerEntryPoints() {
// Sub-graph build test (pure function, no DOM needed)
const { buildRecallSubGraph } = await import("../recall-message-ui.js");
@@ -4624,6 +4709,7 @@ await testGenerationRecallSentMessageClearsStaleTransactionForSameKey();
await testRegisterCoreEventHooksIsIdempotent();
await testChatChangedDoesNotClearCoreEventBindings();
await testSwipeRoutesToRerollWithoutHistoryRecoveryFallback();
await testMessageSentFallsBackToLatestUserWhenHostMessageIdInvalid();
await testMessageReceivedQueuesExtractionWithoutRuntimeQueueMicrotask();
await testAutoExtractionDefersWhenGraphNotReady();
await testAutoExtractionDefersWhenAlreadyExtracting();
@@ -4636,6 +4722,7 @@ await testPersistentRecallSourceResolutionAndTargetRouting();
await testGenerationRecallFinalInjectionRebindsLatestMatchingUserFloor();
await testGenerationRecallFinalInjectionBackfillsPersistedRecord();
await testGenerationRecallImmediateAfterCommandsBackfillsPersistedRecord();
await testGenerationEndedBackfillsRecentRecallAndSchedulesHideRefresh();
await testRecallCardMountsOnStandardUserMessageDom();
await testRecallCardSkipsMountWithoutStableMessageIndex();
await testRecallCardDelayedDomInsertionEventuallyRenders();