fix(messages): normalize webhook reply references

This commit is contained in:
2026-07-12 22:17:04 +08:00
parent 32d3329d1b
commit 813faa4cd7
4 changed files with 182 additions and 27 deletions
@@ -52,6 +52,16 @@ const currentChat = useMapGetter('getSelectedChat');
// Cache for fetched reply messages to avoid duplicate API calls
const fetchedReplyMessages = reactive(new Map());
const pendingReplyMessageFetches = new Map();
const replyMessageMatches = (message, referenceId) => {
const normalizedReferenceId = String(referenceId);
return (
String(message.id) === normalizedReferenceId ||
message.source_id === normalizedReferenceId ||
message.sourceId === normalizedReferenceId
);
};
/**
* Fetches a specific message from the API by trying to get messages around it
@@ -60,33 +70,58 @@ const fetchedReplyMessages = reactive(new Map());
* @returns {Promise<Object|null>} - The fetched message or null if not found/error
*/
const fetchReplyMessage = async (messageId, conversationId) => {
const cacheKey = String(messageId);
// Return cached result if already fetched
if (fetchedReplyMessages.has(messageId)) {
return fetchedReplyMessages.get(messageId);
if (fetchedReplyMessages.has(cacheKey)) {
return fetchedReplyMessages.get(cacheKey);
}
try {
const response = await MessageApi.getPreviousMessages({
conversationId,
before: messageId + 100,
after: messageId - 100,
});
const numericMessageId = Number(messageId);
if (!Number.isSafeInteger(numericMessageId) || numericMessageId <= 0) {
// Legacy provider payloads may contain an external source ID here. Never
// pass those strings to the numeric before/after cursor endpoint.
fetchedReplyMessages.set(cacheKey, null);
return null;
}
const messages = response.data?.payload || [];
const targetMessage = messages.find(msg => msg.id === messageId);
if (pendingReplyMessageFetches.has(cacheKey)) {
return pendingReplyMessageFetches.get(cacheKey);
}
if (targetMessage) {
const camelCaseMessage = useCamelCase(targetMessage);
fetchedReplyMessages.set(messageId, camelCaseMessage);
return camelCaseMessage;
const request = (async () => {
try {
const response = await MessageApi.getPreviousMessages({
conversationId,
before: numericMessageId + 100,
after: numericMessageId - 100,
});
const messages = response.data?.payload || [];
const targetMessage = messages.find(
message => Number(message.id) === numericMessageId
);
if (targetMessage) {
const camelCaseMessage = useCamelCase(targetMessage);
fetchedReplyMessages.set(cacheKey, camelCaseMessage);
return camelCaseMessage;
}
// Cache null result to avoid repeated API calls
fetchedReplyMessages.set(cacheKey, null);
return null;
} catch (error) {
fetchedReplyMessages.set(cacheKey, null);
return null;
}
})();
// Cache null result to avoid repeated API calls
fetchedReplyMessages.set(messageId, null);
return null;
} catch (error) {
fetchedReplyMessages.set(messageId, null);
return null;
pendingReplyMessageFetches.set(cacheKey, request);
try {
return await request;
} finally {
pendingReplyMessageFetches.delete(cacheKey);
}
};
@@ -137,19 +172,23 @@ const getInReplyToMessage = parentMessage => {
if (!inReplyToMessageId) return null;
const cacheKey = String(inReplyToMessageId);
// Try to find in current messages first
let replyMessage = props.messages?.find(msg => msg.id === inReplyToMessageId);
let replyMessage = props.messages?.find(message =>
replyMessageMatches(message, inReplyToMessageId)
);
// Then try store messages
if (!replyMessage && currentChat.value?.messages) {
replyMessage = currentChat.value.messages.find(
msg => msg.id === inReplyToMessageId
replyMessage = currentChat.value.messages.find(message =>
replyMessageMatches(message, inReplyToMessageId)
);
}
// Then check fetch cache
if (!replyMessage && fetchedReplyMessages.has(inReplyToMessageId)) {
replyMessage = fetchedReplyMessages.get(inReplyToMessageId);
if (!replyMessage && fetchedReplyMessages.has(cacheKey)) {
replyMessage = fetchedReplyMessages.get(cacheKey);
}
// If still not found and we have conversation context, fetch it