diff --git a/jam-ui/src/store/features/sessionChatSlice.js b/jam-ui/src/store/features/sessionChatSlice.js index 8af8cc757..612c11d76 100644 --- a/jam-ui/src/store/features/sessionChatSlice.js +++ b/jam-ui/src/store/features/sessionChatSlice.js @@ -1,4 +1,24 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; +import { getChatMessages } from '../../helpers/rest'; + +/** + * Async thunk to fetch chat history for a channel + * @param {Object} params - Request parameters + * @param {string} params.channel - Channel ID (session ID, lesson ID, or 'global') + * @param {string} params.sessionId - Session ID for session channels + * @param {number} [params.before] - Pagination cursor + */ +export const fetchChatHistory = createAsyncThunk( + 'sessionChat/fetchChatHistory', + async ({ channel, sessionId, before }) => { + const response = await getChatMessages({ + channel: sessionId ? 'session' : 'global', + sessionId, + before + }); + return { channel, ...response }; + } +); /** * Initial state for session chat @@ -139,6 +159,46 @@ const sessionChatSlice = createSlice({ setWindowPosition: (state, action) => { state.windowPosition = action.payload; } + }, + extraReducers: (builder) => { + builder + // fetchChatHistory pending + .addCase(fetchChatHistory.pending, (state, action) => { + const channel = action.meta.arg.channel; + state.fetchStatus[channel] = 'loading'; + state.fetchError[channel] = null; + }) + // fetchChatHistory fulfilled + .addCase(fetchChatHistory.fulfilled, (state, action) => { + const channel = action.meta.arg.channel; + const { messages, next } = action.payload; + + // Initialize channel if not exists + if (!state.messagesByChannel[channel]) { + state.messagesByChannel[channel] = []; + } + + // Deduplicate messages + const existingIds = new Set(state.messagesByChannel[channel].map(m => m.id)); + const newMessages = messages.filter(m => !existingIds.has(m.id)); + + // Prepend new messages (oldest first for pagination) + state.messagesByChannel[channel] = [...newMessages, ...state.messagesByChannel[channel]]; + + // Sort by createdAt ASC to maintain chronological order + state.messagesByChannel[channel].sort((a, b) => + new Date(a.createdAt) - new Date(b.createdAt) + ); + + state.fetchStatus[channel] = 'succeeded'; + state.nextCursors[channel] = next; + }) + // fetchChatHistory rejected + .addCase(fetchChatHistory.rejected, (state, action) => { + const channel = action.meta.arg.channel; + state.fetchStatus[channel] = 'failed'; + state.fetchError[channel] = action.error.message; + }); } });