// ======================================== // SessionPreviewPanel Component // ======================================== // Preview and select sessions for Memory V2 extraction import { useState, useMemo } from 'react'; import { useIntl } from 'react-intl'; import { formatDistanceToNow } from 'date-fns'; import { Search, Eye, Loader2, CheckCircle2, XCircle, Clock } from 'lucide-react'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; import { Input } from '@/components/ui/Input'; import { Checkbox } from '@/components/ui/Checkbox'; import { usePreviewSessions, useTriggerSelectiveExtraction, } from '@/hooks/useMemoryV2'; import { cn } from '@/lib/utils'; interface SessionPreviewPanelProps { onClose?: () => void; onExtractComplete?: () => void; } // Helper function to format bytes function formatBytes(bytes: number): string { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`; } // Helper function to format timestamp function formatTimestamp(timestamp: number): string { try { const date = new Date(timestamp); return formatDistanceToNow(date, { addSuffix: true }); } catch { return '-'; } } export function SessionPreviewPanel({ onClose, onExtractComplete }: SessionPreviewPanelProps) { const intl = useIntl(); const [searchQuery, setSearchQuery] = useState(''); const [selectedIds, setSelectedIds] = useState>(new Set()); const [includeNative, setIncludeNative] = useState(false); const { data, isLoading, refetch } = usePreviewSessions(includeNative); const triggerExtraction = useTriggerSelectiveExtraction(); // Filter sessions based on search query const filteredSessions = useMemo(() => { if (!data?.sessions) return []; if (!searchQuery.trim()) return data.sessions; const query = searchQuery.toLowerCase(); return data.sessions.filter( (session) => session.sessionId.toLowerCase().includes(query) || session.tool.toLowerCase().includes(query) || session.source.toLowerCase().includes(query) ); }, [data?.sessions, searchQuery]); // Get ready sessions (eligible and not extracted) const readySessions = useMemo(() => { return filteredSessions.filter((s) => s.eligible && !s.extracted); }, [filteredSessions]); // Toggle session selection const toggleSelection = (sessionId: string) => { setSelectedIds((prev) => { const next = new Set(prev); if (next.has(sessionId)) { next.delete(sessionId); } else { next.add(sessionId); } return next; }); }; // Select all ready sessions const selectAll = () => { setSelectedIds(new Set(readySessions.map((s) => s.sessionId))); }; // Clear selection const selectNone = () => { setSelectedIds(new Set()); }; // Trigger extraction for selected sessions const handleExtract = async () => { if (selectedIds.size === 0) return; triggerExtraction.mutate( { sessionIds: Array.from(selectedIds), includeNative, }, { onSuccess: () => { setSelectedIds(new Set()); onExtractComplete?.(); }, } ); }; return (
{/* Header */}

{intl.formatMessage({ id: 'memory.v2.preview.title', defaultMessage: 'Extraction Queue Preview' })}

{/* Summary Bar */} {data?.summary && (
{data.summary.total}
{intl.formatMessage({ id: 'memory.v2.preview.total', defaultMessage: 'Total' })}
{data.summary.eligible}
{intl.formatMessage({ id: 'memory.v2.preview.eligible', defaultMessage: 'Eligible' })}
{data.summary.alreadyExtracted}
{intl.formatMessage({ id: 'memory.v2.preview.extracted', defaultMessage: 'Already Extracted' })}
{data.summary.readyForExtraction}
{intl.formatMessage({ id: 'memory.v2.preview.ready', defaultMessage: 'Ready' })}
)} {/* Search and Actions */}
setSearchQuery(e.target.value)} className="pl-9" />
{/* Session Table */}
{isLoading ? (
) : filteredSessions.length === 0 ? (
{intl.formatMessage({ id: 'memory.v2.preview.noSessions', defaultMessage: 'No sessions found' })}
) : ( {filteredSessions.map((session) => { const isReady = session.eligible && !session.extracted; const isSelected = selectedIds.has(session.sessionId); const isDisabled = !isReady; return ( ); })}
Source Session ID Tool Timestamp Size Turns Status
toggleSelection(session.sessionId)} /> {session.source === 'ccw' ? intl.formatMessage({ id: 'memory.v2.preview.sourceCcw', defaultMessage: 'CCW' }) : intl.formatMessage({ id: 'memory.v2.preview.sourceNative', defaultMessage: 'Native' })} {session.sessionId} {session.tool || '-'} {formatTimestamp(session.timestamp)} {formatBytes(session.bytes)} {session.turns} {session.extracted ? ( {intl.formatMessage({ id: 'memory.v2.preview.extracted', defaultMessage: 'Extracted' })} ) : session.eligible ? ( {intl.formatMessage({ id: 'memory.v2.preview.ready', defaultMessage: 'Ready' })} ) : ( {intl.formatMessage({ id: 'memory.v2.preview.ineligible', defaultMessage: 'Ineligible' })} )}
)}
{/* Footer Actions */}
{selectedIds.size > 0 ? ( intl.formatMessage( { id: 'memory.v2.preview.selected', defaultMessage: '{count} sessions selected' }, { count: selectedIds.size } ) ) : ( intl.formatMessage({ id: 'memory.v2.preview.selectHint', defaultMessage: 'Select sessions to extract' }) )}
{onClose && ( )}
); } export default SessionPreviewPanel;