import { useEffect, useRef } from 'react'; import { Check, Edit2, Trash2, X } from 'lucide-react'; import type { TFunction } from 'i18next'; import { Badge, Button, Tooltip } from '../../../../shared/view/ui'; import { cn } from '../../../../lib/utils'; import type { Project, ProjectSession, LLMProvider } from '../../../../types/app'; import type { SessionWithProvider } from '../../types/types'; import { createSessionViewModel } from '../../utils/utils'; import SessionProviderLogo from '../../../llm-logo-provider/SessionProviderLogo'; type SidebarSessionItemProps = { project: Project; session: SessionWithProvider; selectedSession: ProjectSession | null; currentTime: Date; editingSession: string | null; editingSessionName: string; onEditingSessionNameChange: (value: string) => void; onStartEditingSession: (sessionId: string, initialName: string) => void; onCancelEditingSession: () => void; onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: LLMProvider) => void; onProjectSelect: (project: Project) => void; onSessionSelect: (session: SessionWithProvider, projectName: string) => void; onDeleteSession: ( projectName: string, sessionId: string, sessionTitle: string, provider: LLMProvider, ) => void; t: TFunction; }; /** * Compact relative time for sidebar rows: * <1m, Xm, Xhr, Xd. */ const formatCompactSessionAge = (dateString: string, currentTime: Date): string => { const date = new Date(dateString); if (Number.isNaN(date.getTime())) { return ''; } const diffInMinutes = Math.floor(Math.max(0, currentTime.getTime() - date.getTime()) / (1000 * 60)); if (diffInMinutes < 1) { return '<1m'; } if (diffInMinutes < 60) { return `${diffInMinutes}m`; } const diffInHours = Math.floor(diffInMinutes / 60); if (diffInHours < 24) { return `${diffInHours}hr`; } const diffInDays = Math.floor(diffInHours / 24); return `${diffInDays}d`; }; export default function SidebarSessionItem({ project, session, selectedSession, currentTime, editingSession, editingSessionName, onEditingSessionNameChange, onStartEditingSession, onCancelEditingSession, onSaveEditingSession, onProjectSelect, onSessionSelect, onDeleteSession, t, }: SidebarSessionItemProps) { const sessionView = createSessionViewModel(session, currentTime, t); const isSelected = selectedSession?.id === session.id; const isEditing = editingSession === session.id; const compactSessionAge = formatCompactSessionAge(sessionView.sessionTime, currentTime); const editingContainerRef = useRef(null); // The rename panel sits inside a group-hover opacity wrapper, so leaving the row // would visually hide it. While editing, dismiss only when the user clicks outside // the panel (matches Escape / cancel-button behaviour). useEffect(() => { if (!isEditing) { return; } const handlePointerDown = (event: MouseEvent) => { const container = editingContainerRef.current; if (container && !container.contains(event.target as Node)) { onCancelEditingSession(); } }; document.addEventListener('mousedown', handlePointerDown); return () => document.removeEventListener('mousedown', handlePointerDown); }, [isEditing, onCancelEditingSession]); // Sessions are owned by a project identified by `projectId` (DB primary key) // after the projectName → projectId migration. const selectMobileSession = () => { onProjectSelect(project); onSessionSelect(session, project.projectId); }; const saveEditedSession = () => { onSaveEditingSession(project.projectId, session.id, editingSessionName, session.__provider); }; const requestDeleteSession = () => { onDeleteSession(project.projectId, session.id, sessionView.sessionName, session.__provider); }; return (
{sessionView.isActive && (
)}
{sessionView.sessionName}
{compactSessionAge && ( {compactSessionAge} )}
{sessionView.messageCount > 0 && ( {sessionView.messageCount} )}
{!sessionView.isCursorSession && ( )}
{isEditing ? ( <> onEditingSessionNameChange(event.target.value)} onKeyDown={(event) => { event.stopPropagation(); if (event.key === 'Enter') { saveEditedSession(); } else if (event.key === 'Escape') { onCancelEditingSession(); } }} onClick={(event) => event.stopPropagation()} className="w-32 rounded border border-border bg-background px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-primary" autoFocus /> ) : ( <> {!sessionView.isCursorSession && ( )} )}
); }