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:
Josemi Liebana
2026-05-22 14:42:42 +02:00
parent fbf7e2cb4d
commit 64056b380d
3 changed files with 76 additions and 17 deletions

View File

@@ -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(
projectId?: string,
addToast?: (msg: string, type?: "success" | "error" | "warning") => void,
@@ -451,18 +471,23 @@ export function useChat(
}
try {
const data = await fetchChatMessages(sessionId, { limit: 50, ...opts }, projectId);
const mappedMessages = data.messages.map(mapChatMessageToInfo);
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]);
setHasMoreMessages(data.messages.length >= 50);
} else {
setMessages(mappedMessages);
if (cacheKey) {
writeCache(cacheKey, mappedMessages, { maxBytes: 500_000 });
// Initial full load — fetch all messages, never truncate at 50
const allMessages = await fetchAllMessagesInChat(sessionId, projectId);
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 {
if (!isPaginationRequest && messagesRef.current.length === 0 && hasCachedMessages) {
setMessages(cachedMessages);

View File

@@ -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.
* Provides chat session management and SSE streaming for real-time AI responses.
@@ -330,8 +346,8 @@ export function useQuickChat(
isStreamingRef.current = false;
streamRef.current = null;
lastAttachedGenerationRef.current = null;
void fetchChatMessages(sessionId, { limit: 50 }, projectId).then((data) => {
setMessages(data.messages.map(mapChatMessageToInfo));
void fetchAllMessages(sessionId, projectId).then((msgs) => {
if (activeSessionRef.current?.id === sessionId) setMessages(msgs);
}).catch(() => {});
flushPendingMessage();
},
@@ -347,8 +363,8 @@ export function useQuickChat(
if (!options?.silent) {
addToast?.(errorMessage, "error");
}
void fetchChatMessages(sessionId, { limit: 50 }, projectId).then((resp) => {
setMessages(resp.messages.map(mapChatMessageToInfo));
void fetchAllMessages(sessionId, projectId).then((msgs) => {
if (activeSessionRef.current?.id === sessionId) setMessages(msgs);
}).catch(() => {});
flushPendingMessage();
},
@@ -427,8 +443,9 @@ export function useQuickChat(
setMessagesLoading(true);
try {
const data = await fetchChatMessages(activeSession.id, { limit: 50 }, projectId);
setMessages(data.messages.map(mapChatMessageToInfo));
const sessionId = activeSession.id;
const msgs = await fetchAllMessages(sessionId, projectId);
if (activeSessionRef.current?.id === sessionId) setMessages(msgs);
} catch (err) {
console.error("[useQuickChat] Failed to load messages:", err);
} finally {
@@ -471,8 +488,9 @@ export function useQuickChat(
if (!data.session.isGenerating) {
clearInterval(interval);
// Reload messages to pick up the completed assistant message
const msgData = await fetchChatMessages(activeSession.id, { limit: 50 }, projectId);
setMessages(msgData.messages.map(mapChatMessageToInfo));
const sessionId = activeSession.id;
const msgs = await fetchAllMessages(sessionId, projectId);
if (activeSessionRef.current?.id === sessionId) setMessages(msgs);
setStreamingText("");
setStreamingThinking("");
setStreamingToolCalls([]);
@@ -493,8 +511,9 @@ export function useQuickChat(
if (!activeSession) return;
setMessagesLoading(true);
try {
const data = await fetchChatMessages(activeSession.id, { limit: 50 }, projectId);
setMessages(data.messages.map(mapChatMessageToInfo));
const sessionId = activeSession.id;
const msgs = await fetchAllMessages(sessionId, projectId);
if (activeSessionRef.current?.id === sessionId) setMessages(msgs);
} catch (err) {
console.error("[useQuickChat] Failed to reload messages:", err);
} finally {