2024-08-03 19:14:38 +02:00
|
|
|
import React from 'react';
|
|
|
|
|
import { InView } from 'react-intersection-observer';
|
|
|
|
|
import { Message } from '@/components/messages/message';
|
2024-08-30 13:43:22 +02:00
|
|
|
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
|
|
|
|
import { SelectNode } from '@/data/schemas/workspace';
|
|
|
|
|
import { QueryResult, sql } from 'kysely';
|
|
|
|
|
import { NodeTypes } from '@/lib/constants';
|
|
|
|
|
import { useWorkspace } from '@/contexts/workspace';
|
|
|
|
|
import { mapNode } from '@/lib/nodes';
|
|
|
|
|
import { buildMessages } from '@/lib/messages';
|
|
|
|
|
|
|
|
|
|
const MESSAGES_PER_PAGE = 50;
|
2024-08-03 19:14:38 +02:00
|
|
|
|
|
|
|
|
interface MessageListProps {
|
2024-08-06 21:45:12 +02:00
|
|
|
conversationId: string;
|
2024-08-30 13:43:22 +02:00
|
|
|
onLastMessageIdChange: (id: string) => void;
|
2024-08-03 19:14:38 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-30 13:43:22 +02:00
|
|
|
export const MessageList = ({
|
|
|
|
|
conversationId,
|
|
|
|
|
onLastMessageIdChange,
|
|
|
|
|
}: MessageListProps) => {
|
|
|
|
|
const workspace = useWorkspace();
|
|
|
|
|
const lastMessageId = React.useRef<string | null>(null);
|
2024-08-03 19:14:38 +02:00
|
|
|
|
2024-08-30 13:43:22 +02:00
|
|
|
const { data, isPending, isFetchingNextPage, fetchNextPage, hasNextPage } =
|
|
|
|
|
useInfiniteQuery({
|
|
|
|
|
queryKey: ['conversation', conversationId],
|
|
|
|
|
initialPageParam: 0,
|
|
|
|
|
getNextPageParam: (lastPage: QueryResult<SelectNode>, pages) => {
|
|
|
|
|
if (lastPage && lastPage.rows) {
|
|
|
|
|
const messageCount = lastPage.rows.filter(
|
|
|
|
|
(row) => row.type === NodeTypes.Message,
|
|
|
|
|
).length;
|
2024-08-03 19:14:38 +02:00
|
|
|
|
2024-08-30 13:43:22 +02:00
|
|
|
if (messageCount >= MESSAGES_PER_PAGE) {
|
|
|
|
|
return pages.length;
|
|
|
|
|
}
|
2024-08-03 19:14:38 +02:00
|
|
|
}
|
2024-08-30 13:43:22 +02:00
|
|
|
return undefined;
|
|
|
|
|
},
|
|
|
|
|
queryFn: async ({ queryKey, pageParam }) => {
|
|
|
|
|
const offset = pageParam * MESSAGES_PER_PAGE;
|
|
|
|
|
const query = sql<SelectNode>`
|
2024-08-30 15:51:55 +02:00
|
|
|
WITH message_nodes AS (
|
|
|
|
|
SELECT *
|
|
|
|
|
FROM nodes
|
|
|
|
|
WHERE parent_id = ${conversationId} AND type = ${NodeTypes.Message}
|
|
|
|
|
ORDER BY id DESC
|
|
|
|
|
LIMIT ${sql.lit(MESSAGES_PER_PAGE)}
|
|
|
|
|
OFFSET ${sql.lit(offset)}
|
|
|
|
|
),
|
|
|
|
|
descendant_nodes AS (
|
|
|
|
|
SELECT *
|
|
|
|
|
FROM nodes
|
|
|
|
|
WHERE parent_id IN (SELECT id FROM message_nodes)
|
|
|
|
|
UNION ALL
|
|
|
|
|
SELECT child.*
|
|
|
|
|
FROM nodes child
|
|
|
|
|
INNER JOIN descendant_nodes parent ON child.parent_id = parent.id
|
|
|
|
|
),
|
2024-09-04 12:59:37 +02:00
|
|
|
author_nodes AS (
|
2024-08-30 15:51:55 +02:00
|
|
|
SELECT *
|
|
|
|
|
FROM nodes
|
|
|
|
|
WHERE id IN (SELECT DISTINCT created_by FROM message_nodes)
|
|
|
|
|
)
|
|
|
|
|
SELECT * FROM message_nodes
|
2024-08-30 13:43:22 +02:00
|
|
|
UNION ALL
|
2024-08-30 15:51:55 +02:00
|
|
|
SELECT * FROM descendant_nodes
|
|
|
|
|
UNION ALL
|
2024-09-04 12:59:37 +02:00
|
|
|
SELECT * FROM author_nodes;
|
2024-08-30 13:43:22 +02:00
|
|
|
`.compile(workspace.schema);
|
2024-08-03 19:14:38 +02:00
|
|
|
|
2024-08-30 13:43:22 +02:00
|
|
|
return await workspace.queryAndSubscribe({
|
|
|
|
|
key: queryKey,
|
|
|
|
|
page: pageParam,
|
|
|
|
|
query,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
2024-08-03 19:14:38 +02:00
|
|
|
|
2024-08-30 13:43:22 +02:00
|
|
|
const pages = data?.pages ?? [];
|
|
|
|
|
const allNodes =
|
|
|
|
|
pages
|
|
|
|
|
.map((page) => page.rows)
|
|
|
|
|
.flat()
|
|
|
|
|
.map((row) => mapNode(row)) ?? [];
|
|
|
|
|
const messages = buildMessages(allNodes);
|
2024-08-03 19:14:38 +02:00
|
|
|
|
2024-08-30 13:43:22 +02:00
|
|
|
React.useEffect(() => {
|
|
|
|
|
if (messages.length > 0) {
|
|
|
|
|
const lastMessage = messages[messages.length - 1];
|
|
|
|
|
if (lastMessage.id !== lastMessageId.current) {
|
|
|
|
|
lastMessageId.current = lastMessage.id;
|
|
|
|
|
onLastMessageIdChange(lastMessageId.current);
|
|
|
|
|
}
|
2024-08-03 19:14:38 +02:00
|
|
|
}
|
2024-08-30 13:43:22 +02:00
|
|
|
}, [messages]);
|
|
|
|
|
|
|
|
|
|
if (isPending) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2024-08-03 19:14:38 +02:00
|
|
|
|
|
|
|
|
return (
|
2024-08-30 13:43:22 +02:00
|
|
|
<React.Fragment>
|
2024-08-03 19:14:38 +02:00
|
|
|
<InView
|
|
|
|
|
rootMargin="200px"
|
|
|
|
|
onChange={(inView) => {
|
2024-08-30 13:43:22 +02:00
|
|
|
if (inView && hasNextPage && !isFetchingNextPage) {
|
|
|
|
|
fetchNextPage();
|
|
|
|
|
}
|
2024-08-03 19:14:38 +02:00
|
|
|
}}
|
2024-08-30 13:43:22 +02:00
|
|
|
></InView>
|
|
|
|
|
{messages.map((message, index) => {
|
|
|
|
|
const previousMessage = index > 0 ? messages[index - 1] : null;
|
2024-08-03 19:14:38 +02:00
|
|
|
|
2024-08-30 13:43:22 +02:00
|
|
|
const currentMessageDate = new Date(message.createdAt);
|
|
|
|
|
const previousMessageDate = previousMessage
|
|
|
|
|
? new Date(previousMessage.createdAt)
|
|
|
|
|
: null;
|
|
|
|
|
const showDate =
|
|
|
|
|
!previousMessageDate ||
|
|
|
|
|
currentMessageDate.getDate() !== previousMessageDate.getDate();
|
2024-08-03 19:14:38 +02:00
|
|
|
|
2024-08-30 13:43:22 +02:00
|
|
|
return (
|
|
|
|
|
<React.Fragment key={message.id}>
|
|
|
|
|
{showDate && (
|
|
|
|
|
<div className="relative flex items-center py-1">
|
|
|
|
|
<div className="flex-grow border-t border-gray-100" />
|
|
|
|
|
<span className="mx-4 flex-shrink text-xs text-muted-foreground">
|
|
|
|
|
{currentMessageDate.toDateString()}
|
|
|
|
|
</span>
|
|
|
|
|
<div className="flex-grow border-t border-gray-100" />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<Message message={message} previousMessage={previousMessage} />
|
|
|
|
|
</React.Fragment>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</React.Fragment>
|
2024-08-03 19:14:38 +02:00
|
|
|
);
|
|
|
|
|
};
|