"use client"; import { ArchiveIcon, MessageSquareIcon, MoreVerticalIcon, PlusIcon, RotateCcwIcon, TrashIcon, } from "lucide-react"; import { useRouter } from "next/navigation"; import { memo, useCallback, useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { createThreadListManager, type ThreadListItem, type ThreadListState, } from "@/lib/chat/thread-persistence"; import { cn } from "@/lib/utils"; interface ThreadListProps { searchSpaceId: number; currentThreadId?: number; className?: string; } export function ThreadList({ searchSpaceId, currentThreadId, className }: ThreadListProps) { const router = useRouter(); const [state, setState] = useState({ threads: [], archivedThreads: [], isLoading: true, error: null, }); const [showArchived, setShowArchived] = useState(false); // Create the thread list manager const manager = useCallback( () => createThreadListManager({ searchSpaceId, currentThreadId: currentThreadId ?? null, onThreadSwitch: (threadId) => { router.push(`/dashboard/${searchSpaceId}/new-chat/${threadId}`); }, onNewThread: (threadId) => { router.push(`/dashboard/${searchSpaceId}/new-chat/${threadId}`); }, }), [searchSpaceId, currentThreadId, router] ); // Load threads on mount and when searchSpaceId changes const loadThreads = useCallback(async () => { setState((prev) => ({ ...prev, isLoading: true })); const newState = await manager().loadThreads(); setState(newState); }, [manager]); useEffect(() => { loadThreads(); }, [loadThreads]); // Handle new thread creation const handleNewThread = async () => { await manager().createNewThread(); await loadThreads(); }; // Handle thread actions const handleArchive = async (threadId: number) => { const success = await manager().archiveThread(threadId); if (success) await loadThreads(); }; const handleUnarchive = async (threadId: number) => { const success = await manager().unarchiveThread(threadId); if (success) await loadThreads(); }; const handleDelete = async (threadId: number) => { const success = await manager().deleteThread(threadId); if (success) { await loadThreads(); // If we deleted the current thread, redirect to new chat if (threadId === currentThreadId) { router.push(`/dashboard/${searchSpaceId}/new-chat`); } } }; const handleSwitchToThread = (threadId: number) => { manager().switchToThread(threadId); }; const displayedThreads = showArchived ? state.archivedThreads : state.threads; if (state.isLoading) { return (
Loading threads...
); } if (state.error) { return (
{state.error}
); } return (
{/* Header with New Chat button */}

Conversations

{/* Tab toggle for active/archived */}
{/* Thread list */}
{displayedThreads.length === 0 ? (

{showArchived ? "No archived conversations" : "No conversations yet"}

{!showArchived && ( )}
) : (
{displayedThreads.map((thread) => ( handleSwitchToThread(thread.id)} onArchive={() => handleArchive(thread.id)} onUnarchive={() => handleUnarchive(thread.id)} onDelete={() => handleDelete(thread.id)} /> ))}
)}
); } interface ThreadListItemComponentProps { thread: ThreadListItem; isActive: boolean; isArchived: boolean; onClick: () => void; onArchive: () => void; onUnarchive: () => void; onDelete: () => void; } const ThreadListItemComponent = memo(function ThreadListItemComponent({ thread, isActive, isArchived, onClick, onArchive, onUnarchive, onDelete, }: ThreadListItemComponentProps) { const relativeTime = useMemo( () => formatRelativeTime(new Date(thread.updatedAt)), [thread.updatedAt] ); return ( {isArchived ? ( Unarchive ) : ( Archive )} Delete ); }); /** * Format a date as relative time (e.g., "2 hours ago", "Yesterday") */ function formatRelativeTime(date: Date): string { const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffSecs = Math.floor(diffMs / 1000); const diffMins = Math.floor(diffSecs / 60); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); if (diffSecs < 60) return "Just now"; if (diffMins < 60) return `${diffMins} min${diffMins === 1 ? "" : "s"} ago`; if (diffHours < 24) return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`; if (diffDays === 1) return "Yesterday"; if (diffDays < 7) return `${diffDays} days ago`; return date.toLocaleDateString(); }