"use client" import { useEffect, useState } from "react" import { ArrowUpRight, Link as LinkIcon, MoreHorizontal, Trash2, Plus, MessagesSquare, Loader2, } from "lucide-react" import { toast } from "sonner" import { usePathname, useRouter } from "next/navigation" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, useSidebar, } from "@/components/ui/sidebar" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { getProjects, getThreads, clearApiCache, Project, Thread } from "@/lib/api" import Link from "next/link" // Thread with associated project info for display in sidebar type ThreadWithProject = { threadId: string; projectId: string; projectName: string; url: string; updatedAt: string; } export function NavAgents() { const { isMobile, state } = useSidebar() const [threads, setThreads] = useState([]) const [isLoading, setIsLoading] = useState(true) const [loadingThreadId, setLoadingThreadId] = useState(null) const pathname = usePathname() const router = useRouter() // Helper to sort threads by updated_at (most recent first) const sortThreads = (threadsList: ThreadWithProject[]): ThreadWithProject[] => { return [...threadsList].sort((a, b) => { return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); }); }; // Function to load threads data with associated projects const loadThreadsWithProjects = async (showLoading = true) => { try { if (showLoading) { setIsLoading(true) } // Get all projects const projects = await getProjects() as Project[] console.log("Projects loaded:", projects.length, projects.map(p => ({ id: p.id, name: p.name }))); // Create a map of projects by ID for faster lookups const projectsById = new Map(); projects.forEach(project => { projectsById.set(project.id, project); }); // Get all threads at once const allThreads = await getThreads() console.log("Threads loaded:", allThreads.length, allThreads.map(t => ({ thread_id: t.thread_id, project_id: t.project_id }))); // Create display objects for threads with their project info const threadsWithProjects: ThreadWithProject[] = []; for (const thread of allThreads) { const projectId = thread.project_id; // Skip threads without a project ID if (!projectId) continue; // Get the associated project const project = projectsById.get(projectId); if (!project) { console.log(`❌ Thread ${thread.thread_id} has project_id=${projectId} but no matching project found`); continue; } console.log(`✅ Thread ${thread.thread_id} matched with project "${project.name}" (${projectId})`); // Add to our list threadsWithProjects.push({ threadId: thread.thread_id, projectId: projectId, projectName: project.name || 'Unnamed Project', url: `/dashboard/agents/${thread.thread_id}`, updatedAt: thread.updated_at || project.updated_at || new Date().toISOString() }); } // Set threads, ensuring consistent sort order setThreads(sortThreads(threadsWithProjects)) } catch (err) { console.error("Error loading threads with projects:", err) } finally { if (showLoading) { setIsLoading(false) } } } // Load threads dynamically from the API on initial load useEffect(() => { loadThreadsWithProjects(true); }, []); // Listen for project-updated events to update the sidebar without full reload useEffect(() => { const handleProjectUpdate = (event: Event) => { const customEvent = event as CustomEvent; if (customEvent.detail) { const { projectId, updatedData } = customEvent.detail; // Update just the name for the threads with the matching project ID setThreads(prevThreads => { const updatedThreads = prevThreads.map(thread => thread.projectId === projectId ? { ...thread, projectName: updatedData.name, } : thread ); // Return the threads without re-sorting immediately return updatedThreads; }); // Silently refresh in background to fetch updated timestamp and re-sort setTimeout(() => loadThreadsWithProjects(false), 1000); } } // Add event listener window.addEventListener('project-updated', handleProjectUpdate as EventListener); // Cleanup return () => { window.removeEventListener('project-updated', handleProjectUpdate as EventListener); } }, []); // Reset loading state when navigation completes (pathname changes) useEffect(() => { setLoadingThreadId(null) }, [pathname]) // Function to handle thread click with loading state const handleThreadClick = (e: React.MouseEvent, threadId: string, url: string) => { e.preventDefault() setLoadingThreadId(threadId) router.push(url) } return (
Agents {state !== "collapsed" ? ( New Agent New Agent ) : null}
{state === "collapsed" && ( New Agent New Agent )} {isLoading ? ( // Show skeleton loaders while loading Array.from({length: 3}).map((_, index) => (
)) ) : threads.length > 0 ? ( // Show all threads with project info <> {threads.map((thread) => { // Check if this thread is currently active const isActive = pathname?.includes(thread.threadId) || false; const isThreadLoading = loadingThreadId === thread.threadId; return ( {state === "collapsed" ? ( handleThreadClick(e, thread.threadId, thread.url)}> {isThreadLoading ? ( ) : ( )} {thread.projectName} {thread.projectName} ) : ( handleThreadClick(e, thread.threadId, thread.url)}> {isThreadLoading ? ( ) : ( )} {thread.projectName} )} {state !== "collapsed" && ( More { navigator.clipboard.writeText(window.location.origin + thread.url) toast.success("Link copied to clipboard") }}> Copy Link Open in New Tab Delete )} ); })} ) : ( // Empty state No agents yet )}
) }