import React from 'react'; import { Box, Text, useInput } from 'ink'; import type { ConversationSummary } from '../hooks/use-conversations.js'; export interface SidebarProps { conversations: ConversationSummary[]; activeConversationId: string | undefined; selectedIndex: number; onSelectIndex: (index: number) => void; onSwitchConversation: (id: string) => void; onDeleteConversation: (id: string) => void; loading: boolean; focused: boolean; width: number; } function formatRelativeTime(dateStr: string): string { const date = new Date(dateStr); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffDays === 0) { const hh = String(date.getHours()).padStart(2, '0'); const mm = String(date.getMinutes()).padStart(2, '0'); return `${hh}:${mm}`; } if (diffDays < 7) { return `${diffDays}d ago`; } const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', ]; const mon = months[date.getMonth()]; const dd = String(date.getDate()).padStart(2, '0'); return `${mon} ${dd}`; } function truncate(text: string, maxLen: number): string { if (text.length <= maxLen) return text; return text.slice(0, maxLen - 1) + '…'; } export function Sidebar({ conversations, activeConversationId, selectedIndex, onSelectIndex, onSwitchConversation, onDeleteConversation, loading, focused, width, }: SidebarProps) { useInput( (_input, key) => { if (key.upArrow) { onSelectIndex(Math.max(0, selectedIndex - 1)); } if (key.downArrow) { onSelectIndex(Math.min(conversations.length - 1, selectedIndex + 1)); } if (key.return) { const conv = conversations[selectedIndex]; if (conv) { onSwitchConversation(conv.id); } } if (_input === 'd') { const conv = conversations[selectedIndex]; if (conv) { onDeleteConversation(conv.id); } } }, { isActive: focused }, ); const borderColor = focused ? 'cyan' : 'gray'; // Available width for content inside border + padding const innerWidth = width - 4; // 2 border + 2 padding return ( Conversations {loading && conversations.length === 0 ? ( Loading… ) : conversations.length === 0 ? ( No conversations ) : ( conversations.map((conv, idx) => { const isActive = conv.id === activeConversationId; const isSelected = idx === selectedIndex && focused; const marker = isActive ? '● ' : ' '; const time = formatRelativeTime(conv.updatedAt); const title = conv.title ?? 'Untitled'; // marker(2) + title + space(1) + time const maxTitleLen = Math.max(4, innerWidth - marker.length - time.length - 1); const displayTitle = truncate(title, maxTitleLen); return ( {marker} {displayTitle} {' '.repeat( Math.max(0, innerWidth - marker.length - displayTitle.length - time.length), )} {time} ); }) )} {focused && ↑↓ navigate • enter switch • d delete} ); }