v0.1.5
parent
ecf223f945
commit
b8e958da13
|
|
@ -88,6 +88,48 @@ function normalizeMessageId(raw: unknown): number | undefined {
|
|||
return i > 0 ? i : undefined;
|
||||
}
|
||||
|
||||
function mergeLatestMessages(existing: ChatMessage[], latestPage: ChatMessage[]): ChatMessage[] {
|
||||
if (latestPage.length <= 0) {
|
||||
return existing.slice(-300);
|
||||
}
|
||||
|
||||
const latestIds = latestPage
|
||||
.map((msg) => normalizeMessageId(msg.id))
|
||||
.filter((id): id is number => typeof id === 'number');
|
||||
const oldestLatestId = latestIds.length > 0 ? Math.min(...latestIds) : null;
|
||||
const latestIdSet = new Set(latestIds);
|
||||
const latestFallbackKeys = new Set(
|
||||
latestPage
|
||||
.filter((msg) => !normalizeMessageId(msg.id))
|
||||
.map((msg) => `k:${msg.role}:${msg.ts}:${msg.text}`),
|
||||
);
|
||||
|
||||
const keptExisting = existing.filter((msg) => {
|
||||
const id = normalizeMessageId(msg.id);
|
||||
if (id && oldestLatestId !== null && id >= oldestLatestId) {
|
||||
return latestIdSet.has(id);
|
||||
}
|
||||
if (!id && latestFallbackKeys.has(`k:${msg.role}:${msg.ts}:${msg.text}`)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const mergedMap = new Map<string, ChatMessage>();
|
||||
[...keptExisting, ...latestPage].forEach((msg) => {
|
||||
const id = normalizeMessageId(msg.id);
|
||||
const key = id ? `id:${id}` : `k:${msg.role}:${msg.ts}:${msg.text}`;
|
||||
mergedMap.set(key, msg);
|
||||
});
|
||||
|
||||
return Array.from(mergedMap.values())
|
||||
.sort((a, b) => {
|
||||
if (a.ts !== b.ts) return a.ts - b.ts;
|
||||
return Number(a.id || 0) - Number(b.id || 0);
|
||||
})
|
||||
.slice(-300);
|
||||
}
|
||||
|
||||
function normalizeChannelName(raw: unknown): string {
|
||||
const channel = String(raw || '').trim().toLowerCase();
|
||||
if (channel === 'dashboard_channel' || channel === 'dashboard-channel') return 'dashboard';
|
||||
|
|
@ -195,19 +237,8 @@ export function useBotsSync(forcedBotId?: string, enableMonitorSockets: boolean
|
|||
.filter((msg) => msg.text.trim().length > 0 || (msg.attachments || []).length > 0)
|
||||
.slice(-300);
|
||||
|
||||
// Keep already lazy-loaded history; only merge/refresh the latest page.
|
||||
const existing = (activeBotsRef.current[target]?.messages || []).filter((m) => (m.kind || 'final') !== 'progress');
|
||||
const mergedMap = new Map<string, ChatMessage>();
|
||||
[...existing, ...latestPage].forEach((msg) => {
|
||||
const key = msg.id ? `id:${msg.id}` : `k:${msg.role}:${msg.ts}:${msg.text}`;
|
||||
if (!mergedMap.has(key)) mergedMap.set(key, msg);
|
||||
});
|
||||
const messages = Array.from(mergedMap.values())
|
||||
.sort((a, b) => {
|
||||
if (a.ts !== b.ts) return a.ts - b.ts;
|
||||
return Number(a.id || 0) - Number(b.id || 0);
|
||||
})
|
||||
.slice(-300);
|
||||
const messages = mergeLatestMessages(existing, latestPage);
|
||||
setBotMessages(target, messages);
|
||||
|
||||
const lastUser = [...messages].reverse().find((m) => m.role === 'user');
|
||||
|
|
|
|||
|
|
@ -112,42 +112,7 @@ export function useDashboardChatMessageActions({
|
|||
return matched?.id || null;
|
||||
}, [hydrateLatestMessages, selectedBotId]);
|
||||
|
||||
const removeConversationMessageLocally = useCallback((message: ChatMessage, deletedMessageId: number) => {
|
||||
if (!selectedBotId) return;
|
||||
const originalMessageId = Number(message.id);
|
||||
const hasOriginalId = Number.isFinite(originalMessageId) && originalMessageId > 0;
|
||||
const idsToRemove = new Set<number>([deletedMessageId]);
|
||||
if (hasOriginalId) {
|
||||
idsToRemove.add(originalMessageId);
|
||||
}
|
||||
|
||||
const scrollBox = chatScrollRef.current;
|
||||
const prevTop = scrollBox?.scrollTop ?? null;
|
||||
const normalizedTargetText = message.role === 'user'
|
||||
? normalizeUserMessageText(message.text)
|
||||
: normalizeAssistantMessageText(message.text);
|
||||
const targetAttachments = JSON.stringify(message.attachments || []);
|
||||
|
||||
const nextMessages = messages.filter((row) => {
|
||||
const rowId = Number(row.id);
|
||||
if (Number.isFinite(rowId) && rowId > 0) {
|
||||
return !idsToRemove.has(rowId);
|
||||
}
|
||||
if (hasOriginalId || row.role !== message.role) {
|
||||
return true;
|
||||
}
|
||||
const normalizedRowText = row.role === 'user'
|
||||
? normalizeUserMessageText(row.text)
|
||||
: normalizeAssistantMessageText(row.text);
|
||||
return !(
|
||||
normalizedRowText === normalizedTargetText
|
||||
&& JSON.stringify(row.attachments || []) === targetAttachments
|
||||
&& Math.abs((row.ts || 0) - (message.ts || 0)) <= 1000
|
||||
);
|
||||
});
|
||||
|
||||
setBotMessages(selectedBotId, nextMessages);
|
||||
|
||||
const preserveChatScrollAfterMessagesChange = useCallback((prevTop: number | null) => {
|
||||
if (prevTop === null || chatAutoFollowRef.current) return;
|
||||
requestAnimationFrame(() => {
|
||||
const box = chatScrollRef.current;
|
||||
|
|
@ -155,7 +120,33 @@ export function useDashboardChatMessageActions({
|
|||
const maxTop = Math.max(0, box.scrollHeight - box.clientHeight);
|
||||
box.scrollTop = Math.min(prevTop, maxTop);
|
||||
});
|
||||
}, [chatAutoFollowRef, chatScrollRef, messages, selectedBotId, setBotMessages]);
|
||||
}, [chatAutoFollowRef, chatScrollRef]);
|
||||
|
||||
const removeConversationMessageLocally = useCallback((deletedMessageId: number, originalMessageId?: number) => {
|
||||
if (!selectedBotId) return;
|
||||
const originalId = Number(originalMessageId);
|
||||
const idsToRemove = new Set<number>([deletedMessageId]);
|
||||
if (Number.isFinite(originalId) && originalId > 0) {
|
||||
idsToRemove.add(originalId);
|
||||
}
|
||||
|
||||
const scrollBox = chatScrollRef.current;
|
||||
const prevTop = scrollBox?.scrollTop ?? null;
|
||||
const nextMessages = messages.filter((row) => {
|
||||
const rowId = Number(row.id);
|
||||
return !(Number.isFinite(rowId) && rowId > 0 && idsToRemove.has(rowId));
|
||||
});
|
||||
setBotMessages(selectedBotId, nextMessages);
|
||||
preserveChatScrollAfterMessagesChange(prevTop);
|
||||
}, [chatScrollRef, messages, preserveChatScrollAfterMessagesChange, selectedBotId, setBotMessages]);
|
||||
|
||||
const refreshConversationMessages = useCallback(async () => {
|
||||
if (!selectedBotId) return;
|
||||
const scrollBox = chatScrollRef.current;
|
||||
const prevTop = scrollBox?.scrollTop ?? null;
|
||||
await hydrateLatestMessages(selectedBotId);
|
||||
preserveChatScrollAfterMessagesChange(prevTop);
|
||||
}, [chatScrollRef, hydrateLatestMessages, preserveChatScrollAfterMessagesChange, selectedBotId]);
|
||||
|
||||
const deleteConversationMessageOnServer = useCallback(async (messageId: number) => {
|
||||
await axios.delete(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/messages/${messageId}`);
|
||||
|
|
@ -166,7 +157,9 @@ export function useDashboardChatMessageActions({
|
|||
notify(t.deleteMessagePending, { tone: 'warning' });
|
||||
return;
|
||||
}
|
||||
let targetMessageId = Number(message.id);
|
||||
const originalMessageId = Number(message.id);
|
||||
const hasOriginalMessageId = Number.isFinite(originalMessageId) && originalMessageId > 0;
|
||||
let targetMessageId = originalMessageId;
|
||||
if (!Number.isFinite(targetMessageId) || targetMessageId <= 0) {
|
||||
targetMessageId = Number(await resolveMessageIdFromLatest(message));
|
||||
}
|
||||
|
|
@ -189,7 +182,11 @@ export function useDashboardChatMessageActions({
|
|||
setDeletingMessageIdMap((prev) => ({ ...prev, [targetMessageId]: true }));
|
||||
try {
|
||||
await deleteConversationMessageOnServer(targetMessageId);
|
||||
removeConversationMessageLocally(message, targetMessageId);
|
||||
if (hasOriginalMessageId) {
|
||||
removeConversationMessageLocally(targetMessageId, originalMessageId);
|
||||
} else {
|
||||
await refreshConversationMessages();
|
||||
}
|
||||
notify(t.deleteMessageDone, { tone: 'success' });
|
||||
} catch (error: unknown) {
|
||||
notify(resolveApiErrorMessage(error, t.deleteMessageFail), { tone: 'error' });
|
||||
|
|
@ -200,7 +197,7 @@ export function useDashboardChatMessageActions({
|
|||
return next;
|
||||
});
|
||||
}
|
||||
}, [confirm, deleteConversationMessageOnServer, deletingMessageIdMap, notify, removeConversationMessageLocally, resolveMessageIdFromLatest, selectedBotId, t]);
|
||||
}, [confirm, deleteConversationMessageOnServer, deletingMessageIdMap, notify, refreshConversationMessages, removeConversationMessageLocally, resolveMessageIdFromLatest, selectedBotId, t]);
|
||||
|
||||
return {
|
||||
deleteConversationMessage,
|
||||
|
|
|
|||
Loading…
Reference in New Issue