import { useState } from 'react'; import { embedChunks, clearIndex, parseWithDocling } from '../api/client'; import { processSelectedFiles } from '../api/dropbox'; import { chunkFiles } from '../api/chunker'; import ProcessingStatus from './ProcessingStatus'; import IndexSummary from './IndexSummary'; import CloudConnect from './CloudConnect'; import DoclingOutput from './DoclingOutput'; export default function Sidebar({ onStatusChange, onAccessTokenChange }) { const [loading, setLoading] = useState(false); const [message, setMessage] = useState(null); const [processingState, setProcessingState] = useState(null); const [indexResult, setIndexResult] = useState(null); // New state for two-step flow const [stagedFiles, setStagedFiles] = useState([]); const [accessToken, setAccessToken] = useState(null); // State for Docling parsing output const [parsedDocuments, setParsedDocuments] = useState(null); const [pendingFileContents, setPendingFileContents] = useState(null); // Handle files staged from CloudConnect (not processed yet) const handleFilesStaged = (files) => { setStagedFiles(files); setMessage(null); setIndexResult(null); }; // Handle access token from CloudConnect - propagate to parent for queries const handleAccessTokenChange = (token) => { setAccessToken(token); onAccessTokenChange?.(token); // Propagate to App for QueryPanel if (!token) { setStagedFiles([]); } }; // Remove a single file from staged list const removeFile = (fileId) => { setStagedFiles(stagedFiles.filter(f => f.id !== fileId)); }; // Clear all staged files const clearStagedFiles = () => { setStagedFiles([]); }; // Start indexing the staged files - Phase 1: Read and Parse const handleIndexFiles = async () => { if (stagedFiles.length === 0 || !accessToken) return; setLoading(true); setMessage(null); setIndexResult(null); try { // Step 1: Read files from Dropbox setProcessingState({ step: 'read', fileName: `${stagedFiles.length} files`, progress: 10 }); const fileContents = await processSelectedFiles(stagedFiles, accessToken, (progress) => { setProcessingState({ step: 'read', fileName: progress.fileName, progress: 10 + (progress.current / progress.total) * 15, }); }); if (fileContents.length === 0) { setMessage({ type: 'error', text: 'No readable files found' }); setLoading(false); setProcessingState(null); return; } // Step 2: Parse with Docling setProcessingState({ step: 'parse', fileName: `${fileContents.length} files`, progress: 28 }); const parseResult = await parseWithDocling( stagedFiles.map(f => ({ path: f.path_lower, name: f.name })), accessToken ); if (parseResult.error) { setMessage({ type: 'error', text: parseResult.error }); setLoading(false); setProcessingState(null); return; } // Store results and pause for user review setParsedDocuments(parseResult.results); setPendingFileContents(fileContents); setProcessingState(null); setLoading(false); // User will click "Continue to Indexing" in DoclingOutput } catch (err) { setMessage({ type: 'error', text: err.message }); setProcessingState(null); setLoading(false); } }; // Continue indexing after user reviews Docling output const handleContinueIndexing = async () => { if (!pendingFileContents) return; setLoading(true); setParsedDocuments(null); try { const fileContents = pendingFileContents; setPendingFileContents(null); // Step 3: Chunk files (client-side) setProcessingState({ step: 'chunk', fileName: `${fileContents.length} files`, progress: 40 }); await new Promise(r => setTimeout(r, 100)); const chunks = chunkFiles(fileContents); // Step 4: Clear existing index setProcessingState({ step: 'clear', fileName: 'Clearing old data', progress: 50 }); await clearIndex(); // Step 5: Send chunks to server for embedding setProcessingState({ step: 'embed', fileName: `${chunks.length} chunks`, progress: 65 }); const result = await embedChunks(chunks); // Step 6: Show discard step setProcessingState({ step: 'discard', fileName: '', progress: 85 }); await new Promise(r => setTimeout(r, 300)); // Step 7: Complete setProcessingState({ step: 'save', fileName: '', progress: 100 }); await new Promise(r => setTimeout(r, 200)); if (result.status === 'success') { setIndexResult({ files: fileContents.length, chunks: result.vectors_upserted, }); setStagedFiles([]); // Clear staged files after successful indexing onStatusChange?.(); } else { setMessage({ type: 'error', text: result.error || 'Embedding failed' }); } } catch (err) { setMessage({ type: 'error', text: err.message }); } setProcessingState(null); setLoading(false); }; // Format file size const formatSize = (bytes) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; return (
{file.name}
{formatSize(file.size)}