@replyke/core
Version:
Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.
144 lines • 7.24 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = require("react");
const hooks_1 = require("../../../store/hooks");
const chatSlice_1 = require("../../../store/slices/chatSlice");
const useAxiosPrivate_1 = __importDefault(require("../../../config/useAxiosPrivate"));
const useProject_1 = __importDefault(require("../../projects/useProject"));
const handleError_1 = require("../../../utils/handleError");
function useChatMessages({ conversationId, parentId, limit = 50, includeFiles, }) {
const dispatch = (0, hooks_1.useReplykeDispatch)();
const { projectId } = (0, useProject_1.default)();
const axios = (0, useAxiosPrivate_1.default)();
const isThread = Boolean(parentId);
// Read from the correct Redux bucket
const mainMessages = (0, hooks_1.useReplykeSelector)((0, chatSlice_1.selectMessages)(conversationId));
const mainLoading = (0, hooks_1.useReplykeSelector)((0, chatSlice_1.selectMessagesLoading)(conversationId));
const mainHasMore = (0, hooks_1.useReplykeSelector)((0, chatSlice_1.selectMessagesHasMore)(conversationId));
const threadMessages = (0, hooks_1.useReplykeSelector)((0, chatSlice_1.selectThreadReplies)(parentId ?? ""));
const threadLoading = (0, hooks_1.useReplykeSelector)((0, chatSlice_1.selectThreadLoading)(parentId ?? ""));
const threadHasMore = (0, hooks_1.useReplykeSelector)((0, chatSlice_1.selectThreadHasMore)(parentId ?? ""));
const messages = isThread ? threadMessages : mainMessages;
const loading = isThread ? threadLoading : mainLoading;
const hasMore = isThread ? threadHasMore : mainHasMore;
// Keep fresh refs to message arrays so loadOlder can read cursors without
// closing over stale state
const mainMessagesRef = (0, react_1.useRef)(mainMessages);
mainMessagesRef.current = mainMessages;
const threadMessagesRef = (0, react_1.useRef)(threadMessages);
threadMessagesRef.current = threadMessages;
// Fetch a page of messages.
// `before` is an ISO 8601 timestamp — the server queries messages created
// before this point in time (not a UUID cursor).
const fetchPage = (0, react_1.useCallback)(async (before) => {
if (!projectId || !conversationId)
return;
const params = {
limit,
sort: isThread ? "asc" : "desc",
};
if (parentId)
params.parentId = parentId;
if (before)
params.before = before;
if (includeFiles)
params.include = "files";
try {
const response = await axios.get(`/${projectId}/chat/conversations/${conversationId}/messages`, { params });
const { messages: items, hasMore: more } = response.data;
if (isThread) {
// Thread replies come back ASC — dispatch as-is
dispatch((0, chatSlice_1.setThreadReplies)({
parentMessageId: parentId,
messages: items,
hasMore: more,
}));
}
else {
// Main stream comes back DESC (newest first for cursor efficiency).
// Redux stores messages ASC — reverse before dispatching.
const ascending = [...items].reverse();
ascending.forEach((msg) => dispatch((0, chatSlice_1.upsertMessage)(msg)));
dispatch((0, chatSlice_1.setMessagesHasMore)({ conversationId, hasMore: more }));
}
}
catch (err) {
(0, handleError_1.handleError)(err, "Failed to load messages");
}
}, [projectId, conversationId, parentId, isThread, limit, includeFiles, axios, dispatch]);
// Initial fetch on mount
(0, react_1.useEffect)(() => {
if (!projectId || !conversationId)
return;
const initialFetch = async () => {
if (isThread) {
dispatch((0, chatSlice_1.setThreadLoading)({ parentMessageId: parentId, loading: true }));
}
else {
dispatch((0, chatSlice_1.setMessagesLoading)({ conversationId, loading: true }));
}
await fetchPage(null);
if (isThread) {
dispatch((0, chatSlice_1.setThreadLoading)({ parentMessageId: parentId, loading: false }));
}
else {
dispatch((0, chatSlice_1.setMessagesLoading)({ conversationId, loading: false }));
}
};
initialFetch();
// Only re-run when the conversation/thread identity changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectId, conversationId, parentId]);
// Load more messages:
// - Main stream: fetch older messages using `before` cursor (oldest loaded createdAt)
// - Thread: fetch newer replies using `after` cursor (newest loaded createdAt), append
const loadOlder = (0, react_1.useCallback)(async () => {
if (loading || !hasMore)
return;
if (isThread) {
const currentItems = threadMessagesRef.current;
const newest = currentItems[currentItems.length - 1];
if (!newest || !parentId || !projectId || !conversationId)
return;
const after = new Date(newest.createdAt).toISOString();
dispatch((0, chatSlice_1.setThreadLoading)({ parentMessageId: parentId, loading: true }));
try {
const response = await axios.get(`/${projectId}/chat/conversations/${conversationId}/messages`, {
params: {
parentId,
after,
limit,
sort: "asc",
...(includeFiles ? { include: "files" } : {}),
},
});
const { messages: newItems, hasMore: more } = response.data;
dispatch((0, chatSlice_1.setThreadReplies)({
parentMessageId: parentId,
messages: [...currentItems, ...newItems],
hasMore: more,
}));
}
catch (err) {
(0, handleError_1.handleError)(err, "Failed to load more thread replies");
}
finally {
dispatch((0, chatSlice_1.setThreadLoading)({ parentMessageId: parentId, loading: false }));
}
return;
}
const oldest = mainMessagesRef.current[0];
if (!oldest)
return;
const before = new Date(oldest.createdAt).toISOString();
dispatch((0, chatSlice_1.setMessagesLoading)({ conversationId, loading: true }));
await fetchPage(before);
dispatch((0, chatSlice_1.setMessagesLoading)({ conversationId, loading: false }));
}, [loading, hasMore, isThread, parentId, projectId, conversationId, limit, includeFiles, axios, dispatch, fetchPage]);
return { messages, loading, hasMore, loadOlder };
}
exports.default = useChatMessages;
//# sourceMappingURL=useChatMessages.js.map