/** * Conversation History Dropdown Component * * Displays a list of past conversations for the assistant. * Allows selecting a conversation to resume or deleting old conversations. */ import { useState, useEffect } from 'react' import { MessageSquare, Trash2, Loader2, AlertCircle } from 'lucide-react' import { useConversations, useDeleteConversation } from '../hooks/useConversations' import { ConfirmDialog } from './ConfirmDialog' import type { AssistantConversation } from '../lib/types' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader } from '@/components/ui/card' interface ConversationHistoryProps { projectName: string currentConversationId: number | null isOpen: boolean onClose: () => void onSelectConversation: (conversationId: number) => void } /** * Format a relative time string from an ISO date */ function formatRelativeTime(dateString: string | null): string { if (!dateString) return '' const date = new Date(dateString) const now = new Date() const diffMs = now.getTime() - date.getTime() const diffSeconds = Math.floor(diffMs / 1000) const diffMinutes = Math.floor(diffSeconds / 60) const diffHours = Math.floor(diffMinutes / 60) const diffDays = Math.floor(diffHours / 24) if (diffSeconds < 60) return 'just now' if (diffMinutes < 60) return `${diffMinutes}m ago` if (diffHours < 24) return `${diffHours}h ago` if (diffDays === 1) return 'yesterday' if (diffDays < 7) return `${diffDays}d ago` return date.toLocaleDateString() } export function ConversationHistory({ projectName, currentConversationId, isOpen, onClose, onSelectConversation, }: ConversationHistoryProps) { const [conversationToDelete, setConversationToDelete] = useState(null) const [deleteError, setDeleteError] = useState(null) const { data: conversations, isLoading } = useConversations(projectName) const deleteConversation = useDeleteConversation(projectName) // Clear error when dropdown closes useEffect(() => { if (!isOpen) { setDeleteError(null) } }, [isOpen]) const handleDeleteClick = (e: React.MouseEvent, conversation: AssistantConversation) => { e.stopPropagation() setConversationToDelete(conversation) } const handleConfirmDelete = async () => { if (!conversationToDelete) return try { setDeleteError(null) await deleteConversation.mutateAsync(conversationToDelete.id) setConversationToDelete(null) } catch { // Keep dialog open and show error to user setDeleteError('Failed to delete conversation. Please try again.') } } const handleCancelDelete = () => { setConversationToDelete(null) setDeleteError(null) } const handleSelectConversation = (conversationId: number) => { onSelectConversation(conversationId) onClose() } // Handle Escape key to close dropdown useEffect(() => { if (!isOpen) return const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.preventDefault() onClose() } } document.addEventListener('keydown', handleKeyDown) return () => document.removeEventListener('keydown', handleKeyDown) }, [isOpen, onClose]) if (!isOpen) return null return ( <> {/* Backdrop */}
{/* Dropdown */} {/* Header */}

Conversation History

{/* Content */} {isLoading ? (
) : !conversations || conversations.length === 0 ? (
No conversations yet
) : (
{conversations.map((conversation) => { const isCurrent = conversation.id === currentConversationId return (
) })}
)}
{/* Delete Confirmation Dialog */}

{`Are you sure you want to delete "${conversationToDelete?.title || 'this conversation'}"? This action cannot be undone.`}

{deleteError}
) : ( `Are you sure you want to delete "${conversationToDelete?.title || 'this conversation'}"? This action cannot be undone.` ) } confirmLabel="Delete" cancelLabel="Cancel" variant="danger" isLoading={deleteConversation.isPending} onConfirm={handleConfirmDelete} onCancel={handleCancelDelete} /> ) }