Spaces:
Build error
Build error
| import { useState, useEffect } from "react"; | |
| import { Plus, Trash2, ChevronLeft, ChevronRight } from "lucide-react"; | |
| /** | |
| * ChatSidebar Component | |
| * | |
| * Features: | |
| * - Display chat history from local storage | |
| * - Create new chat | |
| * - Delete chat history | |
| * - Switch between chats | |
| * - Collapsible sidebar | |
| * - Glassmorphism UI matching theme | |
| */ | |
| export interface ChatSession { | |
| id: string; | |
| title: string; | |
| timestamp: number; | |
| mode: "ask" | "imagine"; | |
| messageCount: number; | |
| } | |
| interface ChatSidebarProps { | |
| currentChatId: string | null; | |
| onNewChat: () => void; | |
| onSelectChat: (chatId: string) => void; | |
| onDeleteChat: (chatId: string) => void; | |
| isOpen: boolean; | |
| onToggle: () => void; | |
| } | |
| export default function ChatSidebar({ | |
| currentChatId, | |
| onNewChat, | |
| onSelectChat, | |
| onDeleteChat, | |
| isOpen, | |
| onToggle, | |
| }: ChatSidebarProps) { | |
| const [chats, setChats] = useState<ChatSession[]>([]); | |
| // Load chats from local storage | |
| useEffect(() => { | |
| const savedChats = localStorage.getItem("domify_chats"); | |
| if (savedChats) { | |
| try { | |
| const parsed = JSON.parse(savedChats); | |
| setChats(parsed.sort((a: ChatSession, b: ChatSession) => b.timestamp - a.timestamp)); | |
| } catch (error) { | |
| console.error("Error loading chats:", error); | |
| } | |
| } | |
| }, []); | |
| // Handle delete chat | |
| const handleDelete = (chatId: string, e: React.MouseEvent) => { | |
| e.stopPropagation(); | |
| // Remove from local storage | |
| const savedChats = localStorage.getItem("domify_chats"); | |
| if (savedChats) { | |
| const parsed = JSON.parse(savedChats); | |
| const filtered = parsed.filter((c: ChatSession) => c.id !== chatId); | |
| localStorage.setItem("domify_chats", JSON.stringify(filtered)); | |
| setChats(filtered); | |
| } | |
| // Also remove the chat messages | |
| localStorage.removeItem(`domify_chat_${chatId}`); | |
| onDeleteChat(chatId); | |
| }; | |
| // Format date | |
| const formatDate = (timestamp: number) => { | |
| const date = new Date(timestamp); | |
| const today = new Date(); | |
| const yesterday = new Date(today); | |
| yesterday.setDate(yesterday.getDate() - 1); | |
| if (date.toDateString() === today.toDateString()) { | |
| return date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" }); | |
| } else if (date.toDateString() === yesterday.toDateString()) { | |
| return "Yesterday"; | |
| } else { | |
| return date.toLocaleDateString("en-US", { month: "short", day: "numeric" }); | |
| } | |
| }; | |
| return ( | |
| <> | |
| {/* Sidebar */} | |
| <div | |
| className={`fixed left-0 top-0 h-screen glass-panel-lg rounded-none border-r border-white/10 transition-all duration-300 z-40 flex flex-col ${ | |
| isOpen ? "w-64" : "w-0" | |
| } overflow-hidden`} | |
| > | |
| {/* Header */} | |
| <div className="p-4 border-b border-white/10 flex items-center justify-between"> | |
| <h2 className="text-lg font-semibold gradient-text">Chats</h2> | |
| <button | |
| onClick={onToggle} | |
| className="p-2 hover:bg-white/5 rounded-lg transition-smooth" | |
| title="Close sidebar" | |
| > | |
| <ChevronLeft size={20} /> | |
| </button> | |
| </div> | |
| {/* New Chat Button */} | |
| <div className="p-4 border-b border-white/10"> | |
| <button | |
| onClick={onNewChat} | |
| className="w-full flex items-center justify-center gap-2 px-4 py-2 rounded-lg bg-primary/20 text-primary border border-primary/30 hover:bg-primary/30 transition-smooth" | |
| > | |
| <Plus size={18} /> | |
| <span>New Chat</span> | |
| </button> | |
| </div> | |
| {/* Chat List */} | |
| <div className="flex-1 overflow-y-auto scrollbar-thin p-2 space-y-2"> | |
| {chats.length === 0 ? ( | |
| <div className="text-center py-8 text-muted-foreground text-sm"> | |
| <p>No chats yet</p> | |
| <p className="text-xs mt-2">Start a new conversation</p> | |
| </div> | |
| ) : ( | |
| chats.map((chat) => ( | |
| <button | |
| key={chat.id} | |
| onClick={() => onSelectChat(chat.id)} | |
| className={`w-full text-left p-3 rounded-lg transition-smooth group ${ | |
| currentChatId === chat.id | |
| ? "bg-primary/20 border border-primary/30" | |
| : "hover:bg-white/5 border border-transparent" | |
| }`} | |
| > | |
| <div className="flex items-start justify-between gap-2"> | |
| <div className="flex-1 min-w-0"> | |
| <p className="text-sm font-medium truncate text-foreground"> | |
| {chat.title} | |
| </p> | |
| <div className="flex items-center gap-2 mt-1"> | |
| <span className="text-xs text-muted-foreground"> | |
| {formatDate(chat.timestamp)} | |
| </span> | |
| <span className="text-xs px-2 py-0.5 rounded bg-white/5 text-muted-foreground"> | |
| {chat.mode === "ask" ? "💬" : "🎨"} {chat.messageCount} | |
| </span> | |
| </div> | |
| </div> | |
| {/* Delete Button */} | |
| <button | |
| onClick={(e) => handleDelete(chat.id, e)} | |
| className="p-1.5 hover:bg-destructive/20 rounded transition-smooth opacity-0 group-hover:opacity-100" | |
| title="Delete chat" | |
| > | |
| <Trash2 size={16} className="text-destructive" /> | |
| </button> | |
| </div> | |
| </button> | |
| )) | |
| )} | |
| </div> | |
| {/* Footer */} | |
| <div className="p-4 border-t border-white/10 text-xs text-muted-foreground text-center"> | |
| <p>Chats stored locally</p> | |
| </div> | |
| </div> | |
| {/* Toggle Button (when sidebar is closed) */} | |
| {!isOpen && ( | |
| <button | |
| onClick={onToggle} | |
| className="fixed left-4 top-4 z-40 p-2 glass-panel hover:bg-white/10 transition-smooth" | |
| title="Open sidebar" | |
| > | |
| <ChevronRight size={20} /> | |
| </button> | |
| )} | |
| {/* Overlay (when sidebar is open on mobile) */} | |
| {isOpen && ( | |
| <div | |
| className="fixed inset-0 bg-black/20 backdrop-blur-sm z-30 md:hidden" | |
| onClick={onToggle} | |
| /> | |
| )} | |
| </> | |
| ); | |
| } | |