feat(web-chat): add message history pagination
- add loadMoreMessages with before_id cursor in chat store - track hasMore/loading state per chat - add 'Load older messages' control in message list
This commit is contained in:
@@ -7,12 +7,15 @@ interface ChatState {
|
||||
activeChatId: number | null;
|
||||
messagesByChat: Record<number, Message[]>;
|
||||
draftsByChat: Record<number, string>;
|
||||
hasMoreByChat: Record<number, boolean>;
|
||||
loadingMoreByChat: Record<number, boolean>;
|
||||
typingByChat: Record<number, number[]>;
|
||||
replyToByChat: Record<number, Message | null>;
|
||||
unreadBoundaryByChat: Record<number, number>;
|
||||
loadChats: (query?: string) => Promise<void>;
|
||||
setActiveChatId: (chatId: number | null) => void;
|
||||
loadMessages: (chatId: number) => Promise<void>;
|
||||
loadMoreMessages: (chatId: number) => Promise<void>;
|
||||
prependMessage: (chatId: number, message: Message) => boolean;
|
||||
addOptimisticMessage: (params: {
|
||||
chatId: number;
|
||||
@@ -48,6 +51,8 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
activeChatId: null,
|
||||
messagesByChat: {},
|
||||
draftsByChat: {},
|
||||
hasMoreByChat: {},
|
||||
loadingMoreByChat: {},
|
||||
typingByChat: {},
|
||||
replyToByChat: {},
|
||||
unreadBoundaryByChat: {},
|
||||
@@ -71,6 +76,14 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
...state.unreadBoundaryByChat,
|
||||
[chatId]: unreadCount
|
||||
},
|
||||
hasMoreByChat: {
|
||||
...state.hasMoreByChat,
|
||||
[chatId]: messages.length >= 50
|
||||
},
|
||||
loadingMoreByChat: {
|
||||
...state.loadingMoreByChat,
|
||||
[chatId]: false
|
||||
},
|
||||
chats: state.chats.map((chat) => (chat.id === chatId ? { ...chat, unread_count: 0 } : chat))
|
||||
}));
|
||||
const lastMessage = sorted[sorted.length - 1];
|
||||
@@ -78,6 +91,43 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
void updateMessageStatus(chatId, lastMessage.id, "message_read");
|
||||
}
|
||||
},
|
||||
loadMoreMessages: async (chatId) => {
|
||||
if (get().loadingMoreByChat[chatId]) {
|
||||
return;
|
||||
}
|
||||
const existing = get().messagesByChat[chatId] ?? [];
|
||||
if (!existing.length) {
|
||||
await get().loadMessages(chatId);
|
||||
return;
|
||||
}
|
||||
const oldestId = existing[0]?.id;
|
||||
if (!oldestId) {
|
||||
return;
|
||||
}
|
||||
set((state) => ({
|
||||
loadingMoreByChat: { ...state.loadingMoreByChat, [chatId]: true }
|
||||
}));
|
||||
try {
|
||||
const older = await getMessages(chatId, oldestId);
|
||||
const olderSorted = [...older].reverse();
|
||||
const knownIds = new Set(existing.map((m) => m.id));
|
||||
const merged = [...olderSorted.filter((m) => !knownIds.has(m.id)), ...existing];
|
||||
set((state) => ({
|
||||
messagesByChat: {
|
||||
...state.messagesByChat,
|
||||
[chatId]: merged
|
||||
},
|
||||
hasMoreByChat: {
|
||||
...state.hasMoreByChat,
|
||||
[chatId]: older.length >= 50
|
||||
}
|
||||
}));
|
||||
} finally {
|
||||
set((state) => ({
|
||||
loadingMoreByChat: { ...state.loadingMoreByChat, [chatId]: false }
|
||||
}));
|
||||
}
|
||||
},
|
||||
prependMessage: (chatId, message) => {
|
||||
const old = get().messagesByChat[chatId] ?? [];
|
||||
if (old.some((m) => m.id === message.id || (message.client_message_id && m.client_message_id === message.client_message_id))) {
|
||||
|
||||
Reference in New Issue
Block a user