# Bulk Vector Import Implementation Guide ## Overview This document provides the exact code changes needed to add bulk vector import functionality (CSV/JSON) to the RvLite dashboard. ## Changes Required ### 1. Add Icon Import (Line ~78) **Location:** After `XCircle,` in the lucide-react imports ```typescript XCircle, FileSpreadsheet, // ADD THIS LINE } from 'lucide-react'; ``` ### 2. Add Modal Disclosure Hook (Line ~526) **Location:** After the existing `useDisclosure()` declarations ```typescript const { isOpen: isScenariosOpen, onOpen: onScenariosOpen, onClose: onScenariosClose } = useDisclosure(); const { isOpen: isBulkImportOpen, onOpen: onBulkImportOpen, onClose: onBulkImportClose } = useDisclosure(); // ADD THIS LINE ``` ### 3. Add State Variables (Line ~539) **Location:** After `const [importJson, setImportJson] = useState('');` ```typescript const [importJson, setImportJson] = useState(''); // Bulk import states const [bulkImportData, setBulkImportData] = useState(''); const [bulkImportFormat, setBulkImportFormat] = useState<'csv' | 'json'>('json'); const [bulkImportPreview, setBulkImportPreview] = useState}>>([]); const [bulkImportProgress, setBulkImportProgress] = useState({ current: 0, total: 0, errors: 0 }); const [isBulkImporting, setIsBulkImporting] = useState(false); ``` ### 4. Add CSV Parsing Function (After state declarations, around line ~545) ```typescript // CSV Parser for bulk import const parseCsvVectors = useCallback((csvText: string): Array<{id: string, embedding: number[], metadata?: Record}> => { const lines = csvText.trim().split('\n'); if (lines.length < 2) { throw new Error('CSV must have header row and at least one data row'); } const header = lines[0].toLowerCase().split(',').map(h => h.trim()); const idIndex = header.indexOf('id'); const embeddingIndex = header.indexOf('embedding'); const metadataIndex = header.indexOf('metadata'); if (idIndex === -1 || embeddingIndex === -1) { throw new Error('CSV must have "id" and "embedding" columns'); } const vectors: Array<{id: string, embedding: number[], metadata?: Record}> = []; for (let i = 1; i < lines.length; i++) { const line = lines[i].trim(); if (!line) continue; // Simple CSV parsing (handles quoted fields) const values: string[] = []; let current = ''; let inQuotes = false; for (let j = 0; j < line.length; j++) { const char = line[j]; if (char === '"') { inQuotes = !inQuotes; } else if (char === ',' && !inQuotes) { values.push(current.trim()); current = ''; } else { current += char; } } values.push(current.trim()); if (values.length < header.length) continue; try { const id = values[idIndex].replace(/^"(.*)"$/, '$1'); const embeddingStr = values[embeddingIndex].replace(/^"(.*)"$/, '$1'); const embedding = JSON.parse(embeddingStr); if (!Array.isArray(embedding) || !embedding.every(n => typeof n === 'number')) { throw new Error(`Invalid embedding format at row ${i + 1}`); } let metadata: Record = {}; if (metadataIndex !== -1 && values[metadataIndex]) { const metadataStr = values[metadataIndex].replace(/^"(.*)"$/, '$1').replace(/""/g, '"'); metadata = JSON.parse(metadataStr); } vectors.push({ id, embedding, metadata }); } catch (err) { console.error(`Error parsing row ${i + 1}:`, err); throw new Error(`Failed to parse row ${i + 1}: ${err instanceof Error ? err.message : String(err)}`); } } return vectors; }, []); ``` ### 5. Add JSON Parsing Function (After CSV parser) ```typescript // JSON Parser for bulk import const parseJsonVectors = useCallback((jsonText: string): Array<{id: string, embedding: number[], metadata?: Record}> => { try { const data = JSON.parse(jsonText); if (!Array.isArray(data)) { throw new Error('JSON must be an array of vectors'); } return data.map((item, index) => { if (!item.id || !item.embedding) { throw new Error(`Vector at index ${index} missing required "id" or "embedding" field`); } if (!Array.isArray(item.embedding) || !item.embedding.every((n: unknown) => typeof n === 'number')) { throw new Error(`Vector at index ${index} has invalid embedding format`); } return { id: String(item.id), embedding: item.embedding, metadata: item.metadata || {} }; }); } catch (err) { throw new Error(`Failed to parse JSON: ${err instanceof Error ? err.message : String(err)}`); } }, []); ``` ### 6. Add Preview Handler (After parsing functions) ```typescript // Handle preview generation const handleGeneratePreview = useCallback(() => { if (!bulkImportData.trim()) { addLog('warning', 'No data to preview'); return; } try { const vectors = bulkImportFormat === 'csv' ? parseCsvVectors(bulkImportData) : parseJsonVectors(bulkImportData); setBulkImportPreview(vectors.slice(0, 5)); addLog('success', `Preview generated: ${vectors.length} vectors found (showing first 5)`); } catch (err) { addLog('error', `Preview failed: ${err instanceof Error ? err.message : String(err)}`); setBulkImportPreview([]); } }, [bulkImportData, bulkImportFormat, parseCsvVectors, parseJsonVectors, addLog]); ``` ### 7. Add Bulk Import Handler (After preview handler) ```typescript // Handle bulk import execution const handleBulkImport = useCallback(async () => { if (!bulkImportData.trim()) { addLog('warning', 'No data to import'); return; } try { setIsBulkImporting(true); const vectors = bulkImportFormat === 'csv' ? parseCsvVectors(bulkImportData) : parseJsonVectors(bulkImportData); setBulkImportProgress({ current: 0, total: vectors.length, errors: 0 }); let successCount = 0; let errorCount = 0; for (let i = 0; i < vectors.length; i++) { try { const { id, embedding, metadata } = vectors[i]; insertVectorWithId(id, embedding, metadata || {}); successCount++; } catch (err) { console.error(`Failed to import vector ${vectors[i].id}:`, err); errorCount++; } setBulkImportProgress({ current: i + 1, total: vectors.length, errors: errorCount }); // Small delay to prevent UI blocking if (i % 10 === 0) { await new Promise(resolve => setTimeout(resolve, 0)); } } refreshVectors(); addLog('success', `Bulk import complete: ${successCount} success, ${errorCount} errors`); // Reset and close setTimeout(() => { setBulkImportData(''); setBulkImportPreview([]); setBulkImportProgress({ current: 0, total: 0, errors: 0 }); setIsBulkImporting(false); onBulkImportClose(); }, 1500); } catch (err) { addLog('error', `Bulk import failed: ${err instanceof Error ? err.message : String(err)}`); setIsBulkImporting(false); } }, [bulkImportData, bulkImportFormat, parseCsvVectors, parseJsonVectors, insertVectorWithId, refreshVectors, addLog, onBulkImportClose]); ``` ### 8. Add File Upload Handler (After bulk import handler) ```typescript // Handle file upload const handleBulkImportFileUpload = useCallback((event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { const text = e.target?.result as string; setBulkImportData(text); // Auto-detect format from file extension const extension = file.name.split('.').pop()?.toLowerCase(); if (extension === 'csv') { setBulkImportFormat('csv'); } else if (extension === 'json') { setBulkImportFormat('json'); } addLog('info', `File loaded: ${file.name} (${(file.size / 1024).toFixed(1)} KB)`); }; reader.onerror = () => { addLog('error', 'Failed to read file'); }; reader.readAsText(file); }, [addLog]); ``` ### 9. Add Bulk Import Button to Quick Actions (Around line ~1964) **Location:** In the Quick Actions CardBody, after the "Import Data" button ```typescript {/* ADD THIS BUTTON */} ``` ### 10. Add Bulk Import Modal (After the Import Modal, around line ~2306) **Location:** After the `{/* Import Modal */}` section closes ```typescript {/* Bulk Import Modal */}
Bulk Import Vectors
{/* Format Selector */}
{/* File Upload */}
{/* Format Guide */}

Expected Format:

{bulkImportFormat === 'csv' ? (
{`id,embedding,metadata
vec1,"[1.0,2.0,3.0]","{\\"category\\":\\"test\\"}"
vec2,"[4.0,5.0,6.0]","{}"`}
                    
) : (
{`[
  { "id": "vec1", "embedding": [1.0, 2.0, 3.0], "metadata": { "category": "test" } },
  { "id": "vec2", "embedding": [4.0, 5.0, 6.0], "metadata": {} }
]`}
                    
)}
{/* Data Input */}