| import React from 'react' | |
| import { Button } from '@/components/ui/button' | |
| import { Card } from '@/components/ui/card' | |
| import { Badge } from '@/components/ui/badge' | |
| import { ChatSession } from '@/types/chat' | |
| import { | |
| Plus, | |
| MessageSquare, | |
| Trash2, | |
| Edit2, | |
| Check, | |
| X | |
| } from 'lucide-react' | |
| interface ChatSessionsProps { | |
| sessions: ChatSession[] | |
| currentSessionId: string | null | |
| onSelectSession: (sessionId: string) => void | |
| onNewSession: () => void | |
| onDeleteSession: (sessionId: string) => void | |
| onRenameSession: (sessionId: string, newTitle: string) => void | |
| } | |
| export function ChatSessions({ | |
| sessions, | |
| currentSessionId, | |
| onSelectSession, | |
| onNewSession, | |
| onDeleteSession, | |
| onRenameSession | |
| }: ChatSessionsProps) { | |
| const [editingId, setEditingId] = React.useState<string | null>(null) | |
| const [editTitle, setEditTitle] = React.useState('') | |
| const startEditing = (session: ChatSession) => { | |
| setEditingId(session.id) | |
| setEditTitle(session.title) | |
| } | |
| const finishEditing = () => { | |
| if (editingId && editTitle.trim()) { | |
| onRenameSession(editingId, editTitle.trim()) | |
| } | |
| setEditingId(null) | |
| setEditTitle('') | |
| } | |
| const cancelEditing = () => { | |
| setEditingId(null) | |
| setEditTitle('') | |
| } | |
| return ( | |
| <div className="h-full flex flex-col"> | |
| {/* Header */} | |
| <div className="p-4 border-b"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <h2 className="font-semibold">Chat Sessions</h2> | |
| <Button onClick={onNewSession} size="sm"> | |
| <Plus className="h-4 w-4 mr-1" /> | |
| New | |
| </Button> | |
| </div> | |
| </div> | |
| {/* Sessions list */} | |
| <div className="flex-1 overflow-y-auto p-4 space-y-2"> | |
| {sessions.length === 0 ? ( | |
| <div className="text-center py-8"> | |
| <MessageSquare className="h-8 w-8 mx-auto text-muted-foreground mb-2" /> | |
| <p className="text-sm text-muted-foreground">No conversations yet</p> | |
| <Button onClick={onNewSession} variant="outline" size="sm" className="mt-2"> | |
| Start your first chat | |
| </Button> | |
| </div> | |
| ) : ( | |
| sessions.map((session) => ( | |
| <Card | |
| key={session.id} | |
| className={`p-3 cursor-pointer transition-colors hover:bg-accent ${ | |
| currentSessionId === session.id ? 'bg-accent border-primary' : '' | |
| }`} | |
| onClick={() => onSelectSession(session.id)} | |
| > | |
| <div className="space-y-2"> | |
| {/* Title */} | |
| {editingId === session.id ? ( | |
| <div className="flex items-center gap-1"> | |
| <input | |
| value={editTitle} | |
| onChange={(e) => setEditTitle(e.target.value)} | |
| onKeyPress={(e) => { | |
| if (e.key === 'Enter') finishEditing() | |
| if (e.key === 'Escape') cancelEditing() | |
| }} | |
| className="flex-1 text-sm bg-background border rounded px-2 py-1" | |
| autoFocus | |
| onClick={(e) => e.stopPropagation()} | |
| /> | |
| <Button | |
| size="sm" | |
| variant="ghost" | |
| onClick={(e) => { | |
| e.stopPropagation() | |
| finishEditing() | |
| }} | |
| > | |
| <Check className="h-3 w-3" /> | |
| </Button> | |
| <Button | |
| size="sm" | |
| variant="ghost" | |
| onClick={(e) => { | |
| e.stopPropagation() | |
| cancelEditing() | |
| }} | |
| > | |
| <X className="h-3 w-3" /> | |
| </Button> | |
| </div> | |
| ) : ( | |
| <div className="flex items-start justify-between"> | |
| <h3 className="font-medium text-sm line-clamp-2"> | |
| {session.title} | |
| </h3> | |
| <div className="flex items-center gap-1 ml-2"> | |
| <Button | |
| size="sm" | |
| variant="ghost" | |
| onClick={(e) => { | |
| e.stopPropagation() | |
| startEditing(session) | |
| }} | |
| className="h-6 w-6 p-0" | |
| > | |
| <Edit2 className="h-3 w-3" /> | |
| </Button> | |
| <Button | |
| size="sm" | |
| variant="ghost" | |
| onClick={(e) => { | |
| e.stopPropagation() | |
| onDeleteSession(session.id) | |
| }} | |
| className="h-6 w-6 p-0 text-destructive hover:text-destructive" | |
| > | |
| <Trash2 className="h-3 w-3" /> | |
| </Button> | |
| </div> | |
| </div> | |
| )} | |
| {/* Metadata */} | |
| <div className="flex items-center justify-between text-xs text-muted-foreground"> | |
| <span>{session.messages.length} messages</span> | |
| <span>{new Date(session.updatedAt).toLocaleDateString()}</span> | |
| </div> | |
| {/* Model info */} | |
| {session.model && ( | |
| <Badge variant="outline" className="text-xs"> | |
| {session.model} | |
| </Badge> | |
| )} | |
| </div> | |
| </Card> | |
| )) | |
| )} | |
| </div> | |
| </div> | |
| ) | |
| } | |