test / src /components /sidebar.tsx
aiioe's picture
Automated build & deploy
e454383 verified
Raw History Blame Contribute Delete
7.51 kB
"use client";
import { useEffect, useState, useCallback } from "react";
import { Plus, Search, MessageSquare, Bot } from "lucide-react"; // Removed Library, Sparkles, Archive
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarHeader,
SidebarMenuItem,
SidebarMenuButton,
useSidebar,
} from "./ui/sidebar";
import { getAllChats, createChat } from "@/lib/storage";
import type { Chat } from "@/lib/types";
import ChatButton from "@/components/sidebar/chatButton";
import { chatEventEmitter, CHAT_RENAMED_EVENT } from "@/lib/chatEventEmitter";
import { useChatStore } from "@/stores/chatStore";
import { cn } from "@/lib/utils";
import SearchModal from "./search-modal";
const AppSidebar = () => {
const [chats, setChats] = useState<Chat[]>([]);
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
const { loadChat, selectedChatId, updateChatName, deleteChat } =
useChatStore();
const { state } = useSidebar();
const isCollapsed = state === "collapsed";
const loadChats = useCallback(async () => {
try {
const loadedChats = await getAllChats();
setChats(loadedChats.reverse());
} catch (error) {
console.error("Failed to load chats:", error);
}
}, []);
const handleChatDelete = async (chatId: string) => {
try {
const deleted = await deleteChat(chatId);
if (!deleted) return false;
setChats((prevChats) => prevChats.filter((chat) => chat.id !== chatId));
return true;
} catch (error) {
console.error("Failed to delete chat:", error);
return false;
}
};
const handleChatRename = async (chatId: string, newTitle: string) => {
try {
await updateChatName(chatId, newTitle);
setChats((prevChats) =>
prevChats.map((chat) =>
chat.id === chatId ? { ...chat, title: newTitle } : chat
)
);
return true;
} catch (error) {
console.error("Failed to rename chat:", error);
return false;
}
};
useEffect(() => {
loadChats();
chatEventEmitter.on(CHAT_RENAMED_EVENT, loadChats);
return () => {
chatEventEmitter.off(CHAT_RENAMED_EVENT, loadChats);
};
}, [loadChats]);
const handleNewChat = async () => {
try {
const chatName = "New Chat";
const newChat = await createChat(chatName, []);
loadChat(newChat.id);
setChats((prevChats) => [newChat, ...prevChats]);
} catch (error) {
console.error("Failed to create new chat:", error);
}
};
const menuItems = [
{ icon: Plus, label: "New chat", action: handleNewChat },
{
icon: Search,
label: "Search chats",
action: () => setIsSearchModalOpen(true),
},
];
return (
<>
<Sidebar
className="border-r border-border/50 bg-gradient-to-b from-card/50 to-background"
collapsible="icon"
>
<SidebarHeader
className={cn(
"border-b border-border/50 bg-background/95 backdrop-blur",
isCollapsed && "px-2"
)}
>
{!isCollapsed && (
<div className="p-4 flex items-center justify-between">
<div className="flex items-center gap-2 min-w-0">
<h1 className="font-semibold text-lg bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent truncate">
Transcriptinator 6000
</h1>
</div>
</div>
)}
</SidebarHeader>
<SidebarContent className="flex-1 overflow-x-clip custom-scrollbar">
<SidebarGroup>
{/* Menu Items */}
<SidebarMenu
className={cn("px-2 py-2", isCollapsed && "space-y-1")}
>
{menuItems.map((item, index) => (
<SidebarMenuItem key={index}>
<SidebarMenuButton
onClick={item.action}
tooltip={isCollapsed ? item.label : undefined}
className={cn(
"transition-all duration-200 hover:bg-accent/50 rounded-lg h-10",
isCollapsed ? "justify-center w-10" : "justify-start",
item.label === "New chat" &&
"bg-primary/10 hover:bg-primary/20 text-primary border border-primary/20"
)}
>
<item.icon className="h-4 w-4" />
{!isCollapsed && (
<span className="text-sm font-medium">{item.label}</span>
)}
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
{!isCollapsed && (
<SidebarGroupLabel className="px-4 text-xs font-medium text-muted-foreground/80 mt-4 mb-2">
Recent Chats
</SidebarGroupLabel>
)}
<SidebarGroupContent>
<SidebarMenu className="px-2">
{!isCollapsed ? (
chats.length > 0 ? (
chats.map((chat) => (
<ChatButton
key={chat.id}
chat={chat}
isSelected={selectedChatId === chat.id}
onClick={loadChat}
onDelete={handleChatDelete}
onRename={handleChatRename}
/>
))
) : (
<div className="px-4 py-8 text-center">
<MessageSquare className="h-8 w-8 text-muted-foreground/50 mx-auto mb-3" />
<p className="text-sm text-muted-foreground">
No conversations yet
</p>
<p className="text-xs text-muted-foreground/60 mt-1">
Create your first chat to get started
</p>
</div>
)
) : (
// Collapsed state - show recent chats as icons
chats.slice(0, 8).map((chat) => (
<SidebarMenuItem key={chat.id}>
<SidebarMenuButton
onClick={() => loadChat(chat.id)}
tooltip={chat.title}
className={cn(
"justify-center w-10 h-10 rounded-lg transition-all duration-200", // Explicitly set w-10 h-10
selectedChatId === chat.id
? "bg-primary/20 border border-primary/30 text-primary"
: "hover:bg-accent/50"
)}
>
<Bot
className={cn(
"h-4 w-4",
selectedChatId === chat.id
? "text-primary"
: "text-muted-foreground"
)}
/>
</SidebarMenuButton>
</SidebarMenuItem>
))
)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SearchModal
open={isSearchModalOpen}
onOpenChange={setIsSearchModalOpen}
/>
</>
);
};
export default AppSidebar;