v0.1.5
parent
ecf223f945
commit
b8e958da13
|
|
@ -88,6 +88,48 @@ function normalizeMessageId(raw: unknown): number | undefined {
|
||||||
return i > 0 ? i : 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 {
|
function normalizeChannelName(raw: unknown): string {
|
||||||
const channel = String(raw || '').trim().toLowerCase();
|
const channel = String(raw || '').trim().toLowerCase();
|
||||||
if (channel === 'dashboard_channel' || channel === 'dashboard-channel') return 'dashboard';
|
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)
|
.filter((msg) => msg.text.trim().length > 0 || (msg.attachments || []).length > 0)
|
||||||
.slice(-300);
|
.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 existing = (activeBotsRef.current[target]?.messages || []).filter((m) => (m.kind || 'final') !== 'progress');
|
||||||
const mergedMap = new Map<string, ChatMessage>();
|
const messages = mergeLatestMessages(existing, latestPage);
|
||||||
[...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);
|
|
||||||
setBotMessages(target, messages);
|
setBotMessages(target, messages);
|
||||||
|
|
||||||
const lastUser = [...messages].reverse().find((m) => m.role === 'user');
|
const lastUser = [...messages].reverse().find((m) => m.role === 'user');
|
||||||
|
|
|
||||||
|
|
@ -112,42 +112,7 @@ export function useDashboardChatMessageActions({
|
||||||
return matched?.id || null;
|
return matched?.id || null;
|
||||||
}, [hydrateLatestMessages, selectedBotId]);
|
}, [hydrateLatestMessages, selectedBotId]);
|
||||||
|
|
||||||
const removeConversationMessageLocally = useCallback((message: ChatMessage, deletedMessageId: number) => {
|
const preserveChatScrollAfterMessagesChange = useCallback((prevTop: number | null) => {
|
||||||
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);
|
|
||||||
|
|
||||||
if (prevTop === null || chatAutoFollowRef.current) return;
|
if (prevTop === null || chatAutoFollowRef.current) return;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const box = chatScrollRef.current;
|
const box = chatScrollRef.current;
|
||||||
|
|
@ -155,7 +120,33 @@ export function useDashboardChatMessageActions({
|
||||||
const maxTop = Math.max(0, box.scrollHeight - box.clientHeight);
|
const maxTop = Math.max(0, box.scrollHeight - box.clientHeight);
|
||||||
box.scrollTop = Math.min(prevTop, maxTop);
|
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) => {
|
const deleteConversationMessageOnServer = useCallback(async (messageId: number) => {
|
||||||
await axios.delete(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/messages/${messageId}`);
|
await axios.delete(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/messages/${messageId}`);
|
||||||
|
|
@ -166,7 +157,9 @@ export function useDashboardChatMessageActions({
|
||||||
notify(t.deleteMessagePending, { tone: 'warning' });
|
notify(t.deleteMessagePending, { tone: 'warning' });
|
||||||
return;
|
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) {
|
if (!Number.isFinite(targetMessageId) || targetMessageId <= 0) {
|
||||||
targetMessageId = Number(await resolveMessageIdFromLatest(message));
|
targetMessageId = Number(await resolveMessageIdFromLatest(message));
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +182,11 @@ export function useDashboardChatMessageActions({
|
||||||
setDeletingMessageIdMap((prev) => ({ ...prev, [targetMessageId]: true }));
|
setDeletingMessageIdMap((prev) => ({ ...prev, [targetMessageId]: true }));
|
||||||
try {
|
try {
|
||||||
await deleteConversationMessageOnServer(targetMessageId);
|
await deleteConversationMessageOnServer(targetMessageId);
|
||||||
removeConversationMessageLocally(message, targetMessageId);
|
if (hasOriginalMessageId) {
|
||||||
|
removeConversationMessageLocally(targetMessageId, originalMessageId);
|
||||||
|
} else {
|
||||||
|
await refreshConversationMessages();
|
||||||
|
}
|
||||||
notify(t.deleteMessageDone, { tone: 'success' });
|
notify(t.deleteMessageDone, { tone: 'success' });
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
notify(resolveApiErrorMessage(error, t.deleteMessageFail), { tone: 'error' });
|
notify(resolveApiErrorMessage(error, t.deleteMessageFail), { tone: 'error' });
|
||||||
|
|
@ -200,7 +197,7 @@ export function useDashboardChatMessageActions({
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [confirm, deleteConversationMessageOnServer, deletingMessageIdMap, notify, removeConversationMessageLocally, resolveMessageIdFromLatest, selectedBotId, t]);
|
}, [confirm, deleteConversationMessageOnServer, deletingMessageIdMap, notify, refreshConversationMessages, removeConversationMessageLocally, resolveMessageIdFromLatest, selectedBotId, t]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
deleteConversationMessage,
|
deleteConversationMessage,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue