import { useState } from 'react'; import { Archive, MessageSquare, Send, ChevronDown, ChevronRight, RotateCcw, Trash2 } from 'lucide-react'; import type { Chat } from '@/api/types'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { Input } from '@/components/ui/input'; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, } from '@/components/ui/context-menu'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from '@/components/ui/dialog'; import { formatTokens } from '@/lib/format'; interface Props { sessionId: string; projectId: string; chats: Chat[]; onOpenChat: (chatId: string) => void; onSend: (content: string) => void; onReopenChat: (chatId: string) => Promise; onArchiveChat: (chatId: string) => Promise; onRenameChat: (chatId: string, name: string) => Promise; onDeleteChat: (chatId: string) => Promise; } function relTime(iso: string): string { const now = Date.now(); const t = Date.parse(iso); if (Number.isNaN(t)) return ''; const sec = Math.max(0, Math.floor((now - t) / 1000)); if (sec < 60) return `${sec}s ago`; const min = Math.floor(sec / 60); if (min < 60) return `${min}m ago`; const hr = Math.floor(min / 60); if (hr < 24) return `${hr}h ago`; const day = Math.floor(hr / 24); return `${day}d ago`; } interface ChatRowProps { chat: Chat; onClick: () => void; dimmed?: boolean; trailing?: React.ReactNode; actions?: React.ReactNode; renamingId: string | null; renameValue: string; setRenameValue: (s: string) => void; onFinishRename: () => void; onCancelRename: () => void; onContextStartRename: () => void; onContextArchive: () => void; onContextDelete: () => void; showContextMenu: boolean; } function ChatRow({ chat, onClick, dimmed, trailing, actions, renamingId, renameValue, setRenameValue, onFinishRename, onCancelRename, onContextStartRename, onContextArchive, onContextDelete, showContextMenu, }: ChatRowProps) { const meta: string[] = [relTime(chat.updated_at)]; if (chat.message_count !== undefined && chat.message_count > 0) { meta.push(`${chat.message_count} msg`); } const tokens = formatTokens(chat.effective_context_tokens); if (tokens) meta.push(tokens); const preview = chat.last_message_preview; const isRenaming = renamingId === chat.id; const inner = ( ); if (!showContextMenu) return inner; return ( {inner} Open Rename Archive Delete ); } export function SessionLandingPage({ chats, onOpenChat, onSend, onReopenChat, onArchiveChat, onRenameChat, onDeleteChat, }: Props) { const [composerValue, setComposerValue] = useState(''); const [showArchived, setShowArchived] = useState(false); const [renamingId, setRenamingId] = useState(null); const [renameValue, setRenameValue] = useState(''); const [archiveConfirm, setArchiveConfirm] = useState(null); const [deleteConfirm, setDeleteConfirm] = useState(null); const [deleteInput, setDeleteInput] = useState(''); const openChats = chats .filter((c) => c.status === 'open') .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()); const archivedChats = chats .filter((c) => c.status === 'archived') .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()); function handleSend() { const text = composerValue.trim(); if (!text) return; onSend(text); setComposerValue(''); } function startRename(chat: Chat) { setRenamingId(chat.id); setRenameValue(chat.name ?? ''); } async function finishRename() { if (renamingId && renameValue.trim()) { await onRenameChat(renamingId, renameValue.trim()); } setRenamingId(null); } const deleteExpected = deleteConfirm?.name ?? ''; const deleteEnabled = deleteConfirm !== null && deleteInput === deleteExpected && deleteExpected.length > 0; // TODO: Landing page chat counts are a snapshot at mount. New messages in // visible chats won't update the per-row stats until next mount/navigation. return (
{openChats.length > 0 && (

Open chats

    {openChats.map((chat) => (
  • onOpenChat(chat.id)} renamingId={renamingId} renameValue={renameValue} setRenameValue={setRenameValue} onFinishRename={() => void finishRename()} onCancelRename={() => setRenamingId(null)} onContextStartRename={() => startRename(chat)} onContextArchive={() => setArchiveConfirm(chat)} onContextDelete={() => { setDeleteConfirm(chat); setDeleteInput(''); }} showContextMenu actions={ <> } />
  • ))}
)} {archivedChats.length > 0 && (
{showArchived && (
    {archivedChats.map((chat) => (
  • void onReopenChat(chat.id)} dimmed trailing={<>Restore} renamingId={null} renameValue="" setRenameValue={() => {}} onFinishRename={() => {}} onCancelRename={() => {}} onContextStartRename={() => {}} onContextArchive={() => {}} onContextDelete={() => {}} showContextMenu={false} />
  • ))}
)}
)} {openChats.length === 0 && archivedChats.length === 0 && (
No chats yet. Type below to start a conversation.
)}