"use client"; import { Trash2 } from "lucide-react"; import { useEffect, useState } from "react"; import { AppSidebar } from "@/components/sidebar/app-sidebar"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { apiClient } from "@/lib/api"; // Import the API client interface Chat { created_at: string; id: number; type: string; title: string; messages: string[]; search_space_id: number; } interface SearchSpace { created_at: string; id: number; name: string; description: string; user_id: string; } interface AppSidebarProviderProps { searchSpaceId: string; navSecondary: { title: string; url: string; icon: string; }[]; navMain: { title: string; url: string; icon: string; isActive?: boolean; items?: { title: string; url: string; }[]; }[]; } export function AppSidebarProvider({ searchSpaceId, navSecondary, navMain, }: AppSidebarProviderProps) { const [recentChats, setRecentChats] = useState< { name: string; url: string; icon: string; id: number; search_space_id: number; actions: { name: string; icon: string; onClick: () => void }[]; }[] >([]); const [searchSpace, setSearchSpace] = useState(null); const [isLoadingChats, setIsLoadingChats] = useState(true); const [isLoadingSearchSpace, setIsLoadingSearchSpace] = useState(true); const [chatError, setChatError] = useState(null); const [searchSpaceError, setSearchSpaceError] = useState(null); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [chatToDelete, setChatToDelete] = useState<{ id: number; name: string } | null>(null); const [isDeleting, setIsDeleting] = useState(false); const [isClient, setIsClient] = useState(false); // Set isClient to true when component mounts on the client useEffect(() => { setIsClient(true); }, []); // Fetch recent chats useEffect(() => { const fetchRecentChats = async () => { try { // Only run on client-side if (typeof window === "undefined") return; try { // Use the API client instead of direct fetch - filter by current search space ID const chats: Chat[] = await apiClient.get( `api/v1/chats/?limit=5&skip=0&search_space_id=${searchSpaceId}` ); // Sort chats by created_at in descending order (newest first) const sortedChats = chats.sort( (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime() ); // console.log("sortedChats", sortedChats); // Transform API response to the format expected by AppSidebar const formattedChats = sortedChats.map((chat) => ({ name: chat.title || `Chat ${chat.id}`, // Fallback if title is empty url: `/dashboard/${chat.search_space_id}/researcher/${chat.id}`, icon: "MessageCircleMore", id: chat.id, search_space_id: chat.search_space_id, actions: [ { name: "View Details", icon: "ExternalLink", onClick: () => { window.location.href = `/dashboard/${chat.search_space_id}/researcher/${chat.id}`; }, }, { name: "Delete", icon: "Trash2", onClick: () => { setChatToDelete({ id: chat.id, name: chat.title || `Chat ${chat.id}` }); setShowDeleteDialog(true); }, }, ], })); setRecentChats(formattedChats); setChatError(null); } catch (error) { console.error("Error fetching chats:", error); setChatError(error instanceof Error ? error.message : "Unknown error occurred"); // Provide empty array to ensure UI still renders setRecentChats([]); } finally { setIsLoadingChats(false); } } catch (error) { console.error("Error in fetchRecentChats:", error); setIsLoadingChats(false); } }; fetchRecentChats(); // Set up a refresh interval (every 5 minutes) const intervalId = setInterval(fetchRecentChats, 5 * 60 * 1000); // Clean up interval on component unmount return () => clearInterval(intervalId); }, [searchSpaceId]); // Handle delete chat const handleDeleteChat = async () => { if (!chatToDelete) return; try { setIsDeleting(true); // Use the API client instead of direct fetch await apiClient.delete(`api/v1/chats/${chatToDelete.id}`); // Close dialog and refresh chats setRecentChats(recentChats.filter((chat) => chat.id !== chatToDelete.id)); } catch (error) { console.error("Error deleting chat:", error); } finally { setIsDeleting(false); setShowDeleteDialog(false); setChatToDelete(null); } }; // Fetch search space details useEffect(() => { const fetchSearchSpace = async () => { try { // Only run on client-side if (typeof window === "undefined") return; try { // Use the API client instead of direct fetch const data: SearchSpace = await apiClient.get( `api/v1/searchspaces/${searchSpaceId}` ); setSearchSpace(data); setSearchSpaceError(null); } catch (error) { console.error("Error fetching search space:", error); setSearchSpaceError(error instanceof Error ? error.message : "Unknown error occurred"); } finally { setIsLoadingSearchSpace(false); } } catch (error) { console.error("Error in fetchSearchSpace:", error); setIsLoadingSearchSpace(false); } }; fetchSearchSpace(); }, [searchSpaceId]); // Create a fallback chat if there's an error or no chats const fallbackChats = chatError || (!isLoadingChats && recentChats.length === 0) ? [ { name: chatError ? "Error loading chats" : "No recent chats", url: "#", icon: chatError ? "AlertCircle" : "MessageCircleMore", id: 0, search_space_id: Number(searchSpaceId), actions: [], }, ] : []; // Use fallback chats if there's an error or no chats const displayChats = recentChats.length > 0 ? recentChats : fallbackChats; // Update the first item in navSecondary to show the search space name const updatedNavSecondary = [...navSecondary]; if (updatedNavSecondary.length > 0 && isClient) { updatedNavSecondary[0] = { ...updatedNavSecondary[0], title: searchSpace?.name || (isLoadingSearchSpace ? "Loading..." : searchSpaceError ? "Error loading search space" : "Unknown Search Space"), }; } return ( <> {/* Delete Confirmation Dialog - Only render on client */} {isClient && ( Delete Chat Are you sure you want to delete{" "} {chatToDelete?.name}? This action cannot be undone. )} ); }