amaniquery-frontend / src /components /chat /ChatSidebar.tsx
Benaah's picture
Upload folder using huggingface_hub
6a059d3 verified
Raw
History Blame Contribute Delete
10.3 kB
"use client"
import { useState, useRef, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Plus, MessageSquare, Trash2, X, Pencil, Check, X as XIcon } from "lucide-react"
import type { ChatSession } from "./types"
interface ChatSidebarProps {
chatHistory: ChatSession[]
currentSessionId: string | null
showHistory: boolean
onLoadSession: (sessionId: string) => void
onDeleteSession: (sessionId: string) => void
onRenameSession: (sessionId: string, newTitle: string) => void
onCreateSession: () => void
onCloseHistory: () => void
}
export function ChatSidebar({
chatHistory,
currentSessionId,
showHistory,
onLoadSession,
onDeleteSession,
onRenameSession,
onCreateSession,
onCloseHistory
}: ChatSidebarProps) {
const [editingSessionId, setEditingSessionId] = useState<string | null>(null)
const [editTitle, setEditTitle] = useState("")
const editInputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
if (editingSessionId && editInputRef.current) {
editInputRef.current.focus()
}
}, [editingSessionId])
const startEditing = (session: ChatSession, e: React.MouseEvent) => {
e.stopPropagation()
setEditingSessionId(session.id)
setEditTitle(session.title || "")
}
const cancelEditing = (e?: React.MouseEvent) => {
e?.stopPropagation()
setEditingSessionId(null)
setEditTitle("")
}
const saveTitle = (sessionId: string, e?: React.MouseEvent) => {
e?.stopPropagation()
if (editTitle.trim()) {
onRenameSession(sessionId, editTitle.trim())
}
setEditingSessionId(null)
setEditTitle("")
}
const handleKeyDown = (e: React.KeyboardEvent, sessionId: string) => {
if (e.key === "Enter") {
saveTitle(sessionId)
} else if (e.key === "Escape") {
cancelEditing()
}
}
// Prevent body scroll when mobile sidebar is open
useEffect(() => {
if (showHistory) {
document.body.style.overflow = "hidden"
} else {
document.body.style.overflow = "unset"
}
return () => {
document.body.style.overflow = "unset"
}
}, [showHistory])
return (
<>
{/* Desktop Sidebar */}
<div className="hidden md:flex w-72 border-r border-white/5 bg-black/20 backdrop-blur-xl flex-col transition-all duration-300 ease-in-out h-full">
<div className="p-4 border-b border-white/5 flex-shrink-0">
<div className="flex items-center justify-between">
<div>
<p className="text-xs uppercase tracking-widest text-muted-foreground">Sessions</p>
<h3 className="font-semibold text-sm">Chat History</h3>
</div>
<Button variant="ghost" size="sm" onClick={onCreateSession} className="h-8 w-8 rounded-full">
<Plus className="w-3 h-3" />
</Button>
</div>
</div>
<div className="p-3 space-y-2 flex-1 overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
{chatHistory.map((session) => (
<div key={session.id} className="flex items-center space-x-2 p-1 group min-w-0">
<div
className={`flex-1 flex items-center justify-between text-left h-auto px-3 py-2 text-xs transition-all duration-200 rounded-2xl border border-transparent group-hover:border-white/10 min-w-0 cursor-pointer ${currentSessionId === session.id ? "bg-secondary text-secondary-foreground" : "hover:bg-accent hover:text-accent-foreground"}`}
onClick={() => onLoadSession(session.id)}
>
<div className="flex items-center w-full min-w-0">
<div className="flex h-7 w-7 items-center justify-center rounded-xl bg-white/10 mr-3 flex-shrink-0">
<MessageSquare className="w-3.5 h-3.5" />
</div>
<div className="flex-1 min-w-0 overflow-hidden">
{editingSessionId === session.id ? (
<div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
<Input
ref={editInputRef}
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
onKeyDown={(e) => handleKeyDown(e, session.id)}
className="h-6 text-xs py-0 px-1 bg-background/50"
/>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 hover:bg-green-500/20 hover:text-green-500"
onClick={(e) => saveTitle(session.id, e)}
>
<Check className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 hover:bg-red-500/20 hover:text-red-500"
onClick={cancelEditing}
>
<XIcon className="w-3 h-3" />
</Button>
</div>
) : (
<>
<div
className="font-medium truncate text-xs block"
title={session.title || `Chat ${session.id.slice(-6)}`}
>
{session.title || `Chat ${session.id.slice(-6)}`}
</div>
<div className="text-[11px] text-muted-foreground truncate">{session.message_count} messages</div>
</>
)}
</div>
</div>
</div>
<div className="flex flex-col gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<Button
variant="ghost"
size="sm"
className="text-muted-foreground hover:text-primary h-6 w-6 rounded-full p-0"
onClick={(e) => startEditing(session, e)}
>
<Pencil className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="text-muted-foreground hover:text-destructive h-6 w-6 rounded-full p-0"
onClick={(e) => {
e.stopPropagation()
onDeleteSession(session.id)
}}
>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</div>
))}
</div>
</div>
{/* Mobile Sidebar Overlay */}
{showHistory && (
<div className="md:hidden fixed inset-0 z-[60] bg-black/70 backdrop-blur-sm" onClick={onCloseHistory}>
<div className="absolute left-0 top-0 h-full w-[85%] max-w-sm bg-background border-r border-white/10 shadow-2xl z-[61] flex flex-col" onClick={(e) => e.stopPropagation()}>
<div className="p-3 md:p-4 border-b border-white/10 flex-shrink-0">
<div className="flex items-center justify-between mb-2">
<h3 className="font-semibold text-sm md:text-base">Chat History</h3>
<Button variant="ghost" size="sm" className="h-9 w-9 rounded-full" onClick={onCloseHistory}>
<X className="w-4 h-4" />
</Button>
</div>
<Button
variant="outline"
size="sm"
className="w-full h-9 md:h-10 rounded-xl text-xs md:text-sm"
onClick={() => {
onCreateSession()
onCloseHistory()
}}
>
<Plus className="w-4 h-4 mr-2" />
New Chat
</Button>
</div>
<div className="p-2 md:p-3 space-y-1 flex-1 overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
{chatHistory.map((session) => (
<div key={session.id} className="flex items-center space-x-1.5 md:space-x-2 p-1 group min-w-0">
<Button
variant={currentSessionId === session.id ? "secondary" : "ghost"}
className="flex-1 justify-start text-left h-auto p-2 md:p-3 min-w-0 min-h-[44px]"
onClick={() => {
onLoadSession(session.id)
onCloseHistory()
}}
>
<MessageSquare className="w-3.5 h-3.5 md:w-4 md:h-4 mr-1.5 md:mr-2 flex-shrink-0" />
<div className="flex-1 min-w-0 overflow-hidden">
<div
className="font-medium truncate block text-xs md:text-sm"
title={session.title || `Chat ${session.id.slice(-8)}`}
>
{session.title || `Chat ${session.id.slice(-8)}`}
</div>
<div className="text-[10px] md:text-xs text-muted-foreground truncate">{session.message_count} messages</div>
</div>
</Button>
<Button
variant="ghost"
size="sm"
className="text-muted-foreground hover:text-destructive h-9 w-9 md:h-10 md:w-10 rounded-full flex-shrink-0"
onClick={(e) => {
e.stopPropagation()
onDeleteSession(session.id)
}}
>
<Trash2 className="w-3.5 h-3.5 md:w-4 md:h-4" />
</Button>
</div>
))}
</div>
</div>
</div>
)}
</>
)
}