import { useCallback, useState } from 'react'; import { Alert, Box, IconButton, Typography, CircularProgress, Divider, } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline'; import { useSessionStore } from '@/store/sessionStore'; import { useAgentStore } from '@/store/agentStore'; import { apiFetch } from '@/utils/api'; interface SessionSidebarProps { onClose?: () => void; } /** Small coloured dot for connection status */ const StatusDot = ({ connected }: { connected: boolean }) => ( ); export default function SessionSidebar({ onClose }: SessionSidebarProps) { const { sessions, activeSessionId, createSession, deleteSession, switchSession } = useSessionStore(); const { isConnected, setPlan, setPanelContent } = useAgentStore(); const [isCreatingSession, setIsCreatingSession] = useState(false); const [capacityError, setCapacityError] = useState(null); // ── Handlers ────────────────────────────────────────────────────── const handleNewSession = useCallback(async () => { if (isCreatingSession) return; setIsCreatingSession(true); setCapacityError(null); try { const response = await apiFetch('/api/session', { method: 'POST' }); if (response.status === 503) { const data = await response.json(); setCapacityError(data.detail || 'Server is at capacity.'); return; } const data = await response.json(); createSession(data.session_id); setPlan([]); setPanelContent(null); onClose?.(); } catch { setCapacityError('Failed to create session.'); } finally { setIsCreatingSession(false); } }, [isCreatingSession, createSession, setPlan, setPanelContent, onClose]); const handleDelete = useCallback( async (sessionId: string, e: React.MouseEvent) => { e.stopPropagation(); try { await apiFetch(`/api/session/${sessionId}`, { method: 'DELETE' }); deleteSession(sessionId); } catch { // Delete locally even if backend fails (session may already be gone) deleteSession(sessionId); } }, [deleteSession], ); const handleSelect = useCallback( (sessionId: string) => { switchSession(sessionId); setPlan([]); setPanelContent(null); onClose?.(); }, [switchSession, setPlan, setPanelContent, onClose], ); const formatTime = (d: string) => new Date(d).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); // ── Render ──────────────────────────────────────────────────────── return ( {/* ── Header ─────────────────────────────────────────────────── */} Recent chats {/* ── Capacity error ─────────────────────────────────────────── */} {capacityError && ( setCapacityError(null)} sx={{ m: 1, fontSize: '0.7rem', py: 0.25, '& .MuiAlert-message': { py: 0 }, borderColor: '#FF9D00', color: 'var(--text)', }} > {capacityError} )} {/* ── Session list ───────────────────────────────────────────── */} {sessions.length === 0 ? ( No sessions yet ) : ( [...sessions].reverse().map((session, index) => { const num = sessions.length - index; const isSelected = session.id === activeSessionId; return ( handleSelect(session.id)} sx={{ display: 'flex', alignItems: 'center', gap: 1, px: 1.5, py: 0.875, mx: 0.75, borderRadius: '10px', cursor: 'pointer', transition: 'background-color 0.12s ease', bgcolor: isSelected ? 'var(--hover-bg)' : 'transparent', '&:hover': { bgcolor: 'var(--hover-bg)', }, '& .delete-btn': { opacity: 0, transition: 'opacity 0.12s', }, '&:hover .delete-btn': { opacity: 1, }, }} > {session.title.startsWith('Chat ') ? `Session ${String(num).padStart(2, '0')}` : session.title} {formatTime(session.createdAt)} handleDelete(session.id, e)} sx={{ color: 'var(--muted-text)', width: 26, height: 26, flexShrink: 0, '&:hover': { color: 'var(--accent-red)', bgcolor: 'rgba(244,67,54,0.08)' }, }} > ); }) )} {/* ── Footer: New Session + status ──────────────────────────── */} {isCreatingSession ? ( <> Creating... ) : ( <> New Session )} {sessions.length} session{sessions.length !== 1 ? 's' : ''} · Backend {isConnected ? 'online' : 'offline'} ); }