diff --git a/src/renderer/components/team/members/MemberDetailDialog.tsx b/src/renderer/components/team/members/MemberDetailDialog.tsx index 34ff49a0..17ed92a8 100644 --- a/src/renderer/components/team/members/MemberDetailDialog.tsx +++ b/src/renderer/components/team/members/MemberDetailDialog.tsx @@ -1,11 +1,8 @@ -import { useEffect, useMemo, useState } from 'react'; - -import { api } from '@renderer/api'; +import { useMemo, useState } from 'react'; import { Button } from '@renderer/components/ui/button'; import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@renderer/components/ui/dialog'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs'; import { useMemberStats } from '@renderer/hooks/useMemberStats'; -import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages'; import { isLeadAgentType, isLeadMember } from '@shared/utils/leadDetection'; import { BarChart3, FileText, ListPlus, MessageSquare, UserMinus } from 'lucide-react'; @@ -42,8 +39,6 @@ interface MemberDetailDialogProps { onViewMemberChanges?: (memberName: string, filePath?: string) => void; } -const MEMBER_MESSAGES_PAGE_SIZE = 200; - export const MemberDetailDialog = ({ open, member, @@ -71,52 +66,7 @@ export const MemberDetailDialog = ({ () => (member ? messages.filter((m) => m.from === member.name || m.to === member.name) : []), [messages, member] ); - const [pagedMemberMessages, setPagedMemberMessages] = useState(null); - - useEffect(() => { - if (!open || !member) { - setPagedMemberMessages(null); - return; - } - - let cancelled = false; - setPagedMemberMessages(null); - - void (async () => { - let cursor: string | undefined; - let hasMore = true; - let allMessages: InboxMessage[] = []; - - while (!cancelled && hasMore) { - const page = await api.teams.getMessagesPage(teamName, { - beforeTimestamp: cursor, - limit: MEMBER_MESSAGES_PAGE_SIZE, - }); - allMessages = mergeTeamMessages(allMessages, page.messages); - hasMore = page.hasMore && page.nextCursor != null; - cursor = page.nextCursor ?? undefined; - } - - if (cancelled) return; - - setPagedMemberMessages( - allMessages.filter((message) => message.from === member.name || message.to === member.name) - ); - })().catch(() => { - if (!cancelled) { - setPagedMemberMessages([]); - } - }); - - return () => { - cancelled = true; - }; - }, [open, teamName, member?.name]); - - const memberMessages = useMemo( - () => mergeTeamMessages(seedMemberMessages, pagedMemberMessages ?? []), - [seedMemberMessages, pagedMemberMessages] - ); + const memberMessages = seedMemberMessages; const inProgressTasks = useMemo( () => memberTasks.filter((t) => t.status === 'in_progress').length, @@ -204,7 +154,11 @@ export const MemberDetailDialog = ({ - + void; } const MAX_MESSAGES = 100; +const MEMBER_MESSAGES_PAGE_SIZE = 50; export const MemberMessagesTab = ({ messages, teamName, + memberName, onCreateTask, }: MemberMessagesTabProps): React.JSX.Element => { + const [pagedMessages, setPagedMessages] = useState([]); + const [nextCursor, setNextCursor] = useState(null); + const [hasMore, setHasMore] = useState(false); + const [loading, setLoading] = useState(false); + + useEffect(() => { + let cancelled = false; + setPagedMessages([]); + setNextCursor(null); + setHasMore(false); + setLoading(true); + + void (async () => { + try { + const page = await api.teams.getMessagesPage(teamName, { limit: MEMBER_MESSAGES_PAGE_SIZE }); + if (cancelled) return; + const memberPageMessages = page.messages.filter( + (message) => message.from === memberName || message.to === memberName + ); + setPagedMessages(memberPageMessages); + setNextCursor(page.nextCursor); + setHasMore(page.hasMore); + } catch { + if (!cancelled) { + setPagedMessages([]); + setNextCursor(null); + setHasMore(false); + } + } finally { + if (!cancelled) setLoading(false); + } + })(); + + return () => { + cancelled = true; + }; + }, [teamName, memberName]); + + const loadOlderMessages = useCallback(async () => { + if (!nextCursor || loading) return; + setLoading(true); + try { + const page = await api.teams.getMessagesPage(teamName, { + beforeTimestamp: nextCursor, + limit: MEMBER_MESSAGES_PAGE_SIZE, + }); + const memberPageMessages = page.messages.filter( + (message) => message.from === memberName || message.to === memberName + ); + setPagedMessages((prev) => mergeTeamMessages(prev, memberPageMessages)); + setNextCursor(page.nextCursor); + setHasMore(page.hasMore); + } catch { + // best-effort + } finally { + setLoading(false); + } + }, [teamName, memberName, nextCursor, loading]); + + const effectiveMessages = useMemo( + () => mergeTeamMessages(messages, pagedMessages), + [messages, pagedMessages] + ); + const displayMessages = useMemo( () => - filterTeamMessages(messages, { + filterTeamMessages(effectiveMessages, { timeWindow: null, filter: { from: new Set(), to: new Set(), showNoise: true }, searchQuery: '', }).slice(0, MAX_MESSAGES), - [messages] + [effectiveMessages] ); if (displayMessages.length === 0) { @@ -47,6 +117,13 @@ export const MemberMessagesTab = ({ onCreateTask={onCreateTask} /> ))} + {hasMore && ( +
+ +
+ )} ); };