4.4 KB
ConversationList.jsx
import * as React from 'react';
// Format relative time (e.g., "2 minutes ago", "Yesterday")
function formatRelativeTime(dateStr) {
const date = new Date(dateStr);
const now = new Date();
const diffMs = now - date;
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins} min${diffMins > 1 ? 's' : ''} ago`;
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
if (diffDays === 1) return 'Yesterday';
if (diffDays < 7) return `${diffDays} days ago`;
return date.toLocaleDateString();
}
export function ConversationList({ conversations, current, onSelect, onNew, onDelete }) {
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-base-300 bg-base-200/50">
<h3 className="font-semibold text-sm">Conversations</h3>
<button
onClick={onNew}
className="btn btn-ghost btn-xs btn-circle"
title="New conversation"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v16m8-8H4" />
</svg>
</button>
</div>
{/* Conversation List */}
<div className="flex-1 overflow-y-auto p-2">
{conversations.length > 0 ? (
<div className="space-y-1">
{conversations.map(conv => (
<ConversationItem
key={conv.id}
conversation={conv}
isActive={current?.id === conv.id}
onSelect={() => onSelect(conv.id)}
onDelete={() => onDelete(conv.id)}
/>
))}
</div>
) : (
<div className="text-center py-8 text-base-content/50">
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 mx-auto mb-2 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<p className="text-sm">No conversations yet</p>
<button
onClick={onNew}
className="btn btn-primary btn-sm mt-3"
>
Start a conversation
</button>
</div>
)}
</div>
</div>
);
}
function ConversationItem({ conversation, isActive, onSelect, onDelete }) {
const [showDelete, setShowDelete] = React.useState(false);
const handleDelete = (e) => {
e.stopPropagation();
if (confirm('Delete this conversation?')) {
onDelete();
}
};
return (
<div
onClick={onSelect}
onMouseEnter={() => setShowDelete(true)}
onMouseLeave={() => setShowDelete(false)}
className={`relative p-3 rounded-lg cursor-pointer transition-all duration-200 ${
isActive
? 'bg-primary/10 border border-primary/30'
: 'hover:bg-base-200 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 ${isActive ? 'text-primary' : ''}`}>
{conversation.title || 'New Conversation'}
</p>
<p className="text-xs text-base-content/50 mt-0.5">
{formatRelativeTime(conversation.updated || conversation.created)}
</p>
</div>
{showDelete && !isActive && (
<button
onClick={handleDelete}
className="btn btn-ghost btn-xs btn-circle text-base-content/40 hover:text-error flex-shrink-0"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
)}
</div>
</div>
);
}