Spaces:
Running
Running
import React from "react"; | |
import { format } from "date-fns"; | |
import { Bot, TrashIcon, User, FileText, Settings, PanelLeft, Plus } from "lucide-react"; | |
import { Button } from "@/components/ui/button"; | |
import { cn } from "@/lib/utils"; | |
import { Chat } from "@/types/chat"; | |
import { ModeToggle } from "@/components/layout/ModeToggle"; | |
interface ChatSidebarProps { | |
chats: Chat[]; | |
activeChat: Chat | null; | |
isGeneratingTitle: boolean; | |
createNewChat: () => void; | |
selectChat: (id: string) => void; | |
onRequestDelete: (id: string) => void; | |
onOpenSettings: () => void; | |
onOpenSources: () => void; | |
openProfileModal: () => void; | |
isSidebarOpen: boolean; | |
setIsSidebarOpen: (open: boolean) => void; | |
} | |
export const ChatSidebar: React.FC<ChatSidebarProps> = ({ | |
chats, | |
activeChat, | |
isGeneratingTitle, | |
createNewChat, | |
selectChat, | |
onRequestDelete, | |
onOpenSettings, | |
onOpenSources, | |
openProfileModal, | |
isSidebarOpen, | |
setIsSidebarOpen, | |
}) => { | |
return ( | |
<div className={cn( | |
"fixed top-0 bottom-0 left-0 z-20 bg-background/90 backdrop-blur-lg", | |
"transition-transform duration-300 ease-in-out", | |
isSidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0', | |
"w-72 lg:w-80 border-r border-border/50 flex-shrink-0", | |
"md:relative md:inset-auto h-full md:z-0" | |
)}> | |
<div className="flex flex-col h-full"> | |
<div className="p-4 pb-0"> | |
<div className="flex justify-between items-center"> | |
<div className="flex items-center space-x-2"> | |
<div className="h-8 w-8 bg-primary/90 rounded-md flex items-center justify-center"> | |
<span className="text-white font-bold text-lg">AI</span> | |
</div> | |
<h1 className="text-xl font-semibold"> | |
Insight AI | |
</h1> | |
</div> | |
<Button className="md:hidden" variant="ghost" size="icon" onClick={() => setIsSidebarOpen(false)}> | |
<PanelLeft className="h-5 w-5" /> | |
</Button> | |
</div> | |
<div className="py-4"> | |
<Button | |
onClick={createNewChat} | |
className="w-full justify-start gap-2" | |
variant="outline" | |
> | |
<Plus className="h-4 w-4" /> | |
New Chat | |
</Button> | |
</div> | |
<div className="flex items-center justify-between"> | |
<div className="flex items-center gap-2"> | |
<Bot className="h-5 w-5 text-primary" /> | |
<span className="font-medium">Recent Chats</span> | |
</div> | |
</div> | |
</div> | |
<div className="flex-1 overflow-y-auto p-2 space-y-1 scrollbar-thin"> | |
{chats.length === 0 ? ( | |
<div className="text-center text-muted-foreground p-4"> | |
No conversations yet. Start a new one! | |
</div> | |
) : ( | |
chats.map(chat => ( | |
<div | |
key={chat.id} | |
onClick={() => selectChat(chat.id)} | |
className={cn( | |
"flex items-center justify-between p-2 px-3 rounded-lg cursor-pointer group transition-all", | |
activeChat?.id === chat.id | |
? "bg-primary/10 border border-primary/20" | |
: "hover:bg-muted/50 border border-transparent" | |
)} | |
> | |
<div className="flex-1 truncate"> | |
<div className={cn( | |
"font-medium truncate flex items-center", | |
activeChat?.id === chat.id && "text-primary" | |
)}> | |
<Bot className="h-3.5 w-3.5 mr-1.5 opacity-70" /> | |
{chat.title} | |
{chat.id === activeChat?.id && isGeneratingTitle && ( | |
<span className="ml-1.5 inline-block h-2 w-2 rounded-full bg-primary/70 animate-pulse"></span> | |
)} | |
</div> | |
<div className="text-xs text-muted-foreground"> | |
{chat.messages.filter(m => m.sender === "user").length} messages • {format(new Date(chat.updatedAt), "MMM d")} | |
</div> | |
</div> | |
<Button | |
variant="ghost" | |
size="icon" | |
className="h-7 w-7 opacity-0 group-hover:opacity-100 transition-opacity hover:bg-destructive/10 hover:text-destructive" | |
onClick={(e) => { | |
e.stopPropagation(); | |
onRequestDelete(chat.id); | |
}} | |
> | |
<TrashIcon className="h-3.5 w-3.5" /> | |
</Button> | |
</div> | |
)) | |
)} | |
</div> | |
{/* Sidebar Footer */} | |
<div className="p-3 space-y-2"> | |
<Button | |
onClick={openProfileModal} | |
variant="ghost" | |
className="w-full justify-start gap-2 text-muted-foreground hover:text-foreground" | |
size="sm" | |
> | |
<User className="h-4 w-4" /> | |
Profile | |
</Button> | |
<Button | |
onClick={onOpenSources} | |
variant="ghost" | |
className="w-full justify-start gap-2 text-muted-foreground hover:text-foreground" | |
size="sm" | |
> | |
<FileText className="h-4 w-4" /> | |
View Sources | |
</Button> | |
<Button | |
onClick={onOpenSettings} | |
variant="ghost" | |
className="w-full justify-start gap-2 text-muted-foreground hover:text-foreground" | |
size="sm" | |
> | |
<Settings className="h-4 w-4" /> | |
Settings | |
</Button> | |
<div className="flex items-center justify-between pt-2 border-t"> | |
<span className="text-xs text-muted-foreground">Theme</span> | |
<ModeToggle /> | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
}; | |