Fix recall card refresh after rendered messages

This commit is contained in:
Youzini-afk
2026-04-05 18:53:33 +08:00
parent 12f2a7a6eb
commit 3446a44387
3 changed files with 104 additions and 1 deletions

View File

@@ -175,6 +175,15 @@ export function registerCoreEventHooksController(runtime) {
if (eventTypes.MESSAGE_UPDATED) {
bind(eventTypes.MESSAGE_UPDATED, handlers.onMessageEdited);
}
if (eventTypes.USER_MESSAGE_RENDERED) {
bind(eventTypes.USER_MESSAGE_RENDERED, handlers.onUserMessageRendered);
}
if (eventTypes.CHARACTER_MESSAGE_RENDERED) {
bind(
eventTypes.CHARACTER_MESSAGE_RENDERED,
handlers.onCharacterMessageRendered,
);
}
const nextState = {
registered: true,
@@ -248,6 +257,31 @@ export function onMessageSentController(runtime, messageId) {
runtime.refreshPersistedRecallMessageUi?.();
}
export function onUserMessageRenderedController(runtime, messageId = null) {
// MESSAGE_SENT 早于实际 DOM 挂载;这里等宿主确认 user 楼层渲染完成后,
// 再补一次 Recall Card 刷新,避免“当前楼层没卡片,下一楼才补出来”。
runtime.refreshPersistedRecallMessageUi?.(40);
return {
messageId: Number.isFinite(Number(messageId)) ? Number(messageId) : null,
refreshed: true,
source: "user-message-rendered",
};
}
export function onCharacterMessageRenderedController(
runtime,
messageId = null,
type = "",
) {
runtime.refreshPersistedRecallMessageUi?.(80);
return {
messageId: Number.isFinite(Number(messageId)) ? Number(messageId) : null,
type: String(type || ""),
refreshed: true,
source: "character-message-rendered",
};
}
export function onGenerationStartedController(
runtime,
type,

View File

@@ -50,6 +50,7 @@ import {
import {
installSendIntentHooksController,
onBeforeCombinePromptsController,
onCharacterMessageRenderedController,
onChatChangedController,
onChatLoadedController,
onGenerationAfterCommandsController,
@@ -59,6 +60,7 @@ import {
onMessageReceivedController,
onMessageSentController,
onMessageSwipedController,
onUserMessageRenderedController,
registerBeforeCombinePromptsController,
registerCoreEventHooksController,
registerGenerationAfterCommandsController,
@@ -9099,6 +9101,25 @@ function onMessageSent(messageId) {
return result;
}
function onUserMessageRendered(messageId = null) {
return onUserMessageRenderedController(
{
refreshPersistedRecallMessageUi: schedulePersistedRecallMessageUiRefresh,
},
messageId,
);
}
function onCharacterMessageRendered(messageId = null, type = "") {
return onCharacterMessageRenderedController(
{
refreshPersistedRecallMessageUi: schedulePersistedRecallMessageUiRefresh,
},
messageId,
type,
);
}
function onMessageDeleted(chatLengthOrMessageId, meta = null) {
const result = onMessageDeletedController(
{
@@ -9574,6 +9595,7 @@ async function onReembedDirect() {
getCoreEventBindingState,
handlers: {
onBeforeCombinePrompts,
onCharacterMessageRendered,
onChatChanged,
onChatLoaded,
onGenerationAfterCommands,
@@ -9584,6 +9606,7 @@ async function onReembedDirect() {
onMessageReceived,
onMessageSent,
onMessageSwiped,
onUserMessageRendered,
},
registerBeforeCombinePrompts,
registerGenerationAfterCommands,

View File

@@ -7,12 +7,14 @@ import vm from "node:vm";
import { pruneProcessedMessageHashesFromFloor } from "../chat-history.js";
import {
onBeforeCombinePromptsController,
onCharacterMessageRenderedController,
onChatChangedController,
onGenerationAfterCommandsController,
onGenerationStartedController,
onMessageSentController,
onMessageReceivedController,
onMessageSwipedController,
onUserMessageRenderedController,
registerCoreEventHooksController,
} from "../event-binding.js";
import {
@@ -3580,23 +3582,29 @@ async function testRegisterCoreEventHooksIsIdempotent() {
CHAT_LOADED: "chat-loaded",
MESSAGE_SENT: "message-sent",
GENERATION_STARTED: "generation-started",
GENERATION_ENDED: "generation-ended",
MESSAGE_RECEIVED: "message-received",
MESSAGE_DELETED: "message-deleted",
MESSAGE_EDITED: "message-edited",
MESSAGE_SWIPED: "message-swiped",
MESSAGE_UPDATED: "message-updated",
USER_MESSAGE_RENDERED: "user-message-rendered",
CHARACTER_MESSAGE_RENDERED: "character-message-rendered",
},
handlers: {
onChatChanged() {},
onChatLoaded() {},
onMessageSent() {},
onGenerationStarted() {},
onGenerationEnded() {},
onGenerationAfterCommands() {},
onBeforeCombinePrompts() {},
onMessageReceived() {},
onMessageDeleted() {},
onMessageEdited() {},
onMessageSwiped() {},
onUserMessageRendered() {},
onCharacterMessageRendered() {},
},
registerGenerationAfterCommands(listener) {
makeFirstRegistrations.push({ hook: "after", listener });
@@ -3620,7 +3628,7 @@ async function testRegisterCoreEventHooksIsIdempotent() {
registerCoreEventHooksController(runtime);
registerCoreEventHooksController(runtime);
assert.equal(eventRegistrations.length, 9);
assert.equal(eventRegistrations.length, 12);
assert.equal(makeFirstRegistrations.length, 2);
assert.equal(bindingState.registered, true);
}
@@ -3743,6 +3751,42 @@ async function testMessageSentFallsBackToLatestUserWhenHostMessageIdInvalid() {
assert.equal(refreshCalls, 1);
}
async function testUserMessageRenderedRefreshesRecallUiAfterRealDomRender() {
const refreshCalls = [];
const result = onUserMessageRenderedController(
{
refreshPersistedRecallMessageUi(delayMs = 0) {
refreshCalls.push(delayMs);
},
},
7,
);
assert.deepEqual(refreshCalls, [40]);
assert.equal(result.messageId, 7);
assert.equal(result.source, "user-message-rendered");
}
async function testCharacterMessageRenderedRefreshesRecallUiAfterAssistantRender() {
const refreshCalls = [];
const result = onCharacterMessageRenderedController(
{
refreshPersistedRecallMessageUi(delayMs = 0) {
refreshCalls.push(delayMs);
},
},
8,
"normal",
);
assert.deepEqual(refreshCalls, [80]);
assert.equal(result.messageId, 8);
assert.equal(result.type, "normal");
assert.equal(result.source, "character-message-rendered");
}
async function testMessageReceivedQueuesExtractionWithoutRuntimeQueueMicrotask() {
let runExtractionCalls = 0;
let refreshCalls = 0;
@@ -5275,6 +5319,8 @@ await testRegisterCoreEventHooksIsIdempotent();
await testChatChangedDoesNotClearCoreEventBindings();
await testSwipeRoutesToRerollWithoutHistoryRecoveryFallback();
await testMessageSentFallsBackToLatestUserWhenHostMessageIdInvalid();
await testUserMessageRenderedRefreshesRecallUiAfterRealDomRender();
await testCharacterMessageRenderedRefreshesRecallUiAfterAssistantRender();
await testMessageReceivedQueuesExtractionWithoutRuntimeQueueMicrotask();
await testAutoExtractionDefersWhenGraphNotReady();
await testAutoExtractionDefersWhenAlreadyExtracting();