Files

126 lines
4.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useStore } from '@/lib/store';
import { api } from '@/lib/api';
function formatDate(iso: string): string {
const d = new Date(iso);
const now = new Date();
const diff = (now.getTime() - d.getTime()) / 1000;
if (diff < 60) return 'just now';
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
if (diff < 172800) return 'yesterday';
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
function groupConversations(convs: { id: string; title: string; updated_at: string; created_at: string; message_count: number }[]) {
const now = new Date();
const groups: Record<string, typeof convs> = { today: [], yesterday: [], week: [], older: [] };
convs.forEach(c => {
const diff = (now.getTime() - new Date(c.updated_at).getTime()) / 86400000;
if (diff < 1) groups.today.push(c);
else if (diff < 2) groups.yesterday.push(c);
else if (diff < 7) groups.week.push(c);
else groups.older.push(c);
});
return groups;
}
export default function Sidebar({ onClose }: { onClose?: () => void }) {
const { conversations, currentId, setCurrentId, setMessages, setConversations, setSidebarOpen } = useStore();
async function newConversation() {
const conv = await api.newConversation();
const updated = await api.getConversations();
setConversations(updated);
setCurrentId(conv.id);
setMessages([]);
if (onClose) onClose();
}
async function loadConversation(id: string) {
setCurrentId(id);
setSidebarOpen(false);
}
async function deleteConv(e: React.MouseEvent, id: string) {
e.stopPropagation();
if (!confirm('Delete this conversation?')) return;
await api.deleteConversation(id);
const updated = await api.getConversations();
setConversations(updated);
if (currentId === id) { setCurrentId(null); setMessages([]); }
}
const groups = groupConversations(conversations);
const labels: Record<string, string> = { today: 'Today', yesterday: 'Yesterday', week: 'This week', older: 'Older' };
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="p-3" style={{ borderBottom: '1px solid var(--border)' }}>
<button
onClick={newConversation}
className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-opacity hover:opacity-85"
style={{ background: 'var(--accent)', color: '#e8f5ed', border: 'none', cursor: 'pointer' }}
>
<span style={{ fontSize: '16px', lineHeight: 1 }}>+</span>
New conversation
</button>
</div>
{/* Conversation list */}
<div className="flex-1 overflow-y-auto py-1 px-2">
{Object.entries(labels).map(([key, label]) => {
const group = groups[key];
if (!group.length) return null;
return (
<div key={key}>
<div
className="px-2 py-2 text-xs uppercase tracking-wider"
style={{ color: 'var(--text3)', letterSpacing: '0.06em' }}
>
{label}
</div>
{group.map(c => (
<div
key={c.id}
onClick={() => loadConversation(c.id)}
className="flex items-center gap-2 px-2 py-2 rounded-lg cursor-pointer mb-0.5 group"
style={{
background: c.id === currentId ? 'var(--bg3)' : 'transparent',
}}
onMouseEnter={e => { if (c.id !== currentId) (e.currentTarget as HTMLElement).style.background = 'var(--bg3)'; }}
onMouseLeave={e => { if (c.id !== currentId) (e.currentTarget as HTMLElement).style.background = 'transparent'; }}
>
<div className="flex-1 min-w-0">
<div className="text-sm truncate" style={{ color: 'var(--text)' }} title={c.title}>
{c.title}
</div>
<div className="text-xs mt-0.5" style={{ color: 'var(--text3)' }}>
{formatDate(c.updated_at)}
</div>
</div>
<button
onClick={e => deleteConv(e, c.id)}
className="opacity-0 group-hover:opacity-100 px-1 py-0.5 rounded text-sm transition-opacity"
style={{ background: 'none', border: 'none', color: 'var(--text3)', cursor: 'pointer', minWidth: '24px', minHeight: '24px' }}
>
×
</button>
</div>
))}
</div>
);
})}
{conversations.length === 0 && (
<div className="px-3 py-4 text-sm" style={{ color: 'var(--text3)' }}>
No conversations yet
</div>
)}
</div>
</div>
);
}