fix(dashboard): load all messages in chat on initial open (useChat + useQuickChat)
Both useChat (full ChatView) and useQuickChat (FAB) loaded only the first 50 messages on open and never paginated further. In useChat, loadMoreMessages exists but ChatView never calls it — dead code. In useQuickChat, there was no pagination at all. Fix: add fetchAllMessagesInChat / fetchAllMessages helpers (paginate through the API 200-msg cap), replace all limit:50 initial-load call sites, and add stale-session guards via activeSessionRef before calling setMessages. hasMoreMessages is set to false after a full initial load. Forward-pagination path in useChat (isPaginationRequest=true) is preserved for backward compat.
This commit is contained in:
15
.changeset/fix-chat-view-message-truncation.md
Normal file
15
.changeset/fix-chat-view-message-truncation.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix `useChat` truncating sessions longer than 50 messages on initial open.
|
||||||
|
|
||||||
|
`loadMessages()` fetched `{ limit: 50 }` for the initial load. The
|
||||||
|
`loadMoreMessages` callback was never called from `ChatView` (no scroll
|
||||||
|
sentinel exists), so sessions beyond 50 messages were permanently cut off.
|
||||||
|
|
||||||
|
Fix: introduce `fetchAllMessagesInChat()` that paginates through the API's
|
||||||
|
200-message cap and replace the initial load path. A stale-session guard
|
||||||
|
(via `activeSessionRef`) prevents overwriting a switched session's messages.
|
||||||
|
The forward-pagination path (`isPaginationRequest = true`) is preserved
|
||||||
|
unchanged for backward compatibility.
|
||||||
@@ -239,6 +239,26 @@ function mapChatMessageToInfo(message: ChatMessage): ChatMessageInfo {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all messages for a session, paginating through the API's 200-message cap.
|
||||||
|
* Used for the initial full load so sessions with >50 messages are never truncated.
|
||||||
|
*/
|
||||||
|
async function fetchAllMessagesInChat(
|
||||||
|
sessionId: string,
|
||||||
|
projectId: string | undefined,
|
||||||
|
): Promise<ChatMessageInfo[]> {
|
||||||
|
const PAGE = 200;
|
||||||
|
const all: ChatMessageInfo[] = [];
|
||||||
|
let offset = 0;
|
||||||
|
for (;;) {
|
||||||
|
const data = await fetchChatMessages(sessionId, { limit: PAGE, offset }, projectId);
|
||||||
|
all.push(...data.messages.map(mapChatMessageToInfo));
|
||||||
|
if (data.messages.length < PAGE) break;
|
||||||
|
offset += PAGE;
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
export function useChat(
|
export function useChat(
|
||||||
projectId?: string,
|
projectId?: string,
|
||||||
addToast?: (msg: string, type?: "success" | "error" | "warning") => void,
|
addToast?: (msg: string, type?: "success" | "error" | "warning") => void,
|
||||||
@@ -451,18 +471,23 @@ export function useChat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await fetchChatMessages(sessionId, { limit: 50, ...opts }, projectId);
|
|
||||||
const mappedMessages = data.messages.map(mapChatMessageToInfo);
|
|
||||||
if (isPaginationRequest) {
|
if (isPaginationRequest) {
|
||||||
// Prepend older messages
|
// Prepend older messages (forward pagination, used when hasMoreMessages)
|
||||||
|
const data = await fetchChatMessages(sessionId, { limit: 50, ...opts }, projectId);
|
||||||
|
const mappedMessages = data.messages.map(mapChatMessageToInfo);
|
||||||
setMessages((prev) => [...mappedMessages, ...prev]);
|
setMessages((prev) => [...mappedMessages, ...prev]);
|
||||||
|
setHasMoreMessages(data.messages.length >= 50);
|
||||||
} else {
|
} else {
|
||||||
setMessages(mappedMessages);
|
// Initial full load — fetch all messages, never truncate at 50
|
||||||
if (cacheKey) {
|
const allMessages = await fetchAllMessagesInChat(sessionId, projectId);
|
||||||
writeCache(cacheKey, mappedMessages, { maxBytes: 500_000 });
|
if (activeSessionRef.current?.id === sessionId) {
|
||||||
|
setMessages(allMessages);
|
||||||
|
if (cacheKey) {
|
||||||
|
writeCache(cacheKey, allMessages, { maxBytes: 500_000 });
|
||||||
|
}
|
||||||
|
setHasMoreMessages(false); // all messages loaded — inside guard
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setHasMoreMessages(data.messages.length >= 50);
|
|
||||||
} catch {
|
} catch {
|
||||||
if (!isPaginationRequest && messagesRef.current.length === 0 && hasCachedMessages) {
|
if (!isPaginationRequest && messagesRef.current.length === 0 && hasCachedMessages) {
|
||||||
setMessages(cachedMessages);
|
setMessages(cachedMessages);
|
||||||
|
|||||||
@@ -183,6 +183,22 @@ function mapChatMessageToInfo(message: ChatMessage): ChatMessageInfo {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all messages for a session, paginating through the API's 200-message cap.
|
||||||
|
* Replaces the former hardcoded `limit: 50` that silently truncated long sessions.
|
||||||
|
*/
|
||||||
|
async function fetchAllMessages(sessionId: string, projectId?: string): Promise<ChatMessageInfo[]> {
|
||||||
|
const PAGE = 200; // API hard cap (Math.min(limit, 200))
|
||||||
|
const all: ChatMessageInfo[] = [];
|
||||||
|
let offset = 0;
|
||||||
|
for (;;) {
|
||||||
|
const data = await fetchChatMessages(sessionId, { limit: PAGE, offset }, projectId);
|
||||||
|
all.push(...data.messages.map(mapChatMessageToInfo));
|
||||||
|
if (data.messages.length < PAGE) break;
|
||||||
|
offset += PAGE;
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Hook for the QuickChatFAB component.
|
* Hook for the QuickChatFAB component.
|
||||||
* Provides chat session management and SSE streaming for real-time AI responses.
|
* Provides chat session management and SSE streaming for real-time AI responses.
|
||||||
@@ -330,8 +346,8 @@ export function useQuickChat(
|
|||||||
isStreamingRef.current = false;
|
isStreamingRef.current = false;
|
||||||
streamRef.current = null;
|
streamRef.current = null;
|
||||||
lastAttachedGenerationRef.current = null;
|
lastAttachedGenerationRef.current = null;
|
||||||
void fetchChatMessages(sessionId, { limit: 50 }, projectId).then((data) => {
|
void fetchAllMessages(sessionId, projectId).then((msgs) => {
|
||||||
setMessages(data.messages.map(mapChatMessageToInfo));
|
if (activeSessionRef.current?.id === sessionId) setMessages(msgs);
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
flushPendingMessage();
|
flushPendingMessage();
|
||||||
},
|
},
|
||||||
@@ -347,8 +363,8 @@ export function useQuickChat(
|
|||||||
if (!options?.silent) {
|
if (!options?.silent) {
|
||||||
addToast?.(errorMessage, "error");
|
addToast?.(errorMessage, "error");
|
||||||
}
|
}
|
||||||
void fetchChatMessages(sessionId, { limit: 50 }, projectId).then((resp) => {
|
void fetchAllMessages(sessionId, projectId).then((msgs) => {
|
||||||
setMessages(resp.messages.map(mapChatMessageToInfo));
|
if (activeSessionRef.current?.id === sessionId) setMessages(msgs);
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
flushPendingMessage();
|
flushPendingMessage();
|
||||||
},
|
},
|
||||||
@@ -427,8 +443,9 @@ export function useQuickChat(
|
|||||||
|
|
||||||
setMessagesLoading(true);
|
setMessagesLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await fetchChatMessages(activeSession.id, { limit: 50 }, projectId);
|
const sessionId = activeSession.id;
|
||||||
setMessages(data.messages.map(mapChatMessageToInfo));
|
const msgs = await fetchAllMessages(sessionId, projectId);
|
||||||
|
if (activeSessionRef.current?.id === sessionId) setMessages(msgs);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[useQuickChat] Failed to load messages:", err);
|
console.error("[useQuickChat] Failed to load messages:", err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -471,8 +488,9 @@ export function useQuickChat(
|
|||||||
if (!data.session.isGenerating) {
|
if (!data.session.isGenerating) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
// Reload messages to pick up the completed assistant message
|
// Reload messages to pick up the completed assistant message
|
||||||
const msgData = await fetchChatMessages(activeSession.id, { limit: 50 }, projectId);
|
const sessionId = activeSession.id;
|
||||||
setMessages(msgData.messages.map(mapChatMessageToInfo));
|
const msgs = await fetchAllMessages(sessionId, projectId);
|
||||||
|
if (activeSessionRef.current?.id === sessionId) setMessages(msgs);
|
||||||
setStreamingText("");
|
setStreamingText("");
|
||||||
setStreamingThinking("");
|
setStreamingThinking("");
|
||||||
setStreamingToolCalls([]);
|
setStreamingToolCalls([]);
|
||||||
@@ -493,8 +511,9 @@ export function useQuickChat(
|
|||||||
if (!activeSession) return;
|
if (!activeSession) return;
|
||||||
setMessagesLoading(true);
|
setMessagesLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await fetchChatMessages(activeSession.id, { limit: 50 }, projectId);
|
const sessionId = activeSession.id;
|
||||||
setMessages(data.messages.map(mapChatMessageToInfo));
|
const msgs = await fetchAllMessages(sessionId, projectId);
|
||||||
|
if (activeSessionRef.current?.id === sessionId) setMessages(msgs);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[useQuickChat] Failed to reload messages:", err);
|
console.error("[useQuickChat] Failed to reload messages:", err);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user