"use client"; import { TryTab } from "./Sidebar"; import { useState, useRef, useCallback, useEffect } from "react"; import { saveLatestUpload, getLatestUpload, deleteLatestUpload } from "../../lib/indexeddb"; import { analyzeDataset, cleanDataset, getReportUrl, type AnalyzeResponse, type CleanResponse } from "../../lib/api"; interface CenterPanelProps { tab: TryTab; onAnalyze?: () => void; } interface UploadedFileMeta { name: string; size: number; type: string; contentPreview: string; } interface TablePreviewData { headers: string[]; rows: string[][]; origin: 'csv'; } export function CenterPanel({ tab, onAnalyze }: CenterPanelProps) { const PREVIEW_BYTES = 64 * 1024; // read first 64KB slice for large-file preview const [fileMeta, setFileMeta] = useState(null); const [uploadedFile, setUploadedFile] = useState(null); const [isDragging, setIsDragging] = useState(false); const [progress, setProgress] = useState(0); const [progressLabel, setProgressLabel] = useState("Processing"); const [tablePreview, setTablePreview] = useState(null); const inputRef = useRef(null); const [loadedFromCache, setLoadedFromCache] = useState(false); const [isProcessing, setIsProcessing] = useState(false); const [error, setError] = useState(null); // Analysis results const [analyzeResult, setAnalyzeResult] = useState(null); const [cleanResult, setCleanResult] = useState(null); const reset = () => { setFileMeta(null); setUploadedFile(null); setProgress(0); setProgressLabel("Processing"); setTablePreview(null); setError(null); }; // Handle API calls const handleAnalyze = async () => { if (!uploadedFile) { setError("No file uploaded"); return; } setIsProcessing(true); setError(null); setProgressLabel("Analyzing dataset..."); try { const result = await analyzeDataset(uploadedFile); setAnalyzeResult(result); setProgressLabel("Analysis complete!"); onAnalyze?.(); // Navigate to bias-analysis tab } catch (err: any) { setError(err.message || "Analysis failed"); } finally { setIsProcessing(false); } }; const handleClean = async () => { if (!uploadedFile) { setError("No file uploaded"); return; } setIsProcessing(true); setError(null); setProgressLabel("Cleaning dataset..."); try { const result = await cleanDataset(uploadedFile); setCleanResult(result); setProgressLabel("Cleaning complete!"); } catch (err: any) { setError(err.message || "Cleaning failed"); } finally { setIsProcessing(false); } }; function tryParseCSV(text: string, maxRows = 50, maxCols = 40): TablePreviewData | null { const lines = text.split(/\r?\n/).filter(l => l.trim().length > 0); if (lines.length < 2) return null; const commaDensity = lines.slice(0, 10).filter(l => l.includes(',')).length; if (commaDensity < 2) return null; const parseLine = (line: string) => { const out: string[] = []; let cur = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const ch = line[i]; if (ch === '"') { if (inQuotes && line[i + 1] === '"') { cur += '"'; i++; } else { inQuotes = !inQuotes; } } else if (ch === ',' && !inQuotes) { out.push(cur); cur = ''; } else { cur += ch; } } out.push(cur); return out.map(c => c.trim()); }; const raw = lines.slice(0, maxRows).map(parseLine); if (raw.length === 0) return null; const headers = raw[0]; const colCount = Math.min(headers.length, maxCols); const rows = raw.slice(1).map(r => r.slice(0, colCount)); return { headers: headers.slice(0, colCount), rows, origin: 'csv' }; } // We no longer build table preview for JSON; revert JSON to raw text view. const processFile = useCallback(async (f: File) => { if (!f) return; const isCSV = /\.csv$/i.test(f.name); setProgress(0); setUploadedFile(f); // Save the file for API calls // For large files, show a progress bar while reading the file stream (no preview) if (f.size > 1024 * 1024) { setProgressLabel("Uploading"); const metaObj: UploadedFileMeta = { name: f.name, size: f.size, type: f.type || "unknown", contentPreview: `Loading partial preview (first ${Math.round(PREVIEW_BYTES/1024)}KB)...`, }; setFileMeta(metaObj); setTablePreview(null); // Save to IndexedDB immediately so it persists without needing full read (async () => { try { await saveLatestUpload(f, metaObj); } catch {} })(); // Read head slice for partial preview & possible CSV table extraction try { const headBlob = f.slice(0, PREVIEW_BYTES); const headReader = new FileReader(); headReader.onload = async () => { try { const buf = headReader.result as ArrayBuffer; const decoder = new TextDecoder(); const text = decoder.decode(buf); setFileMeta(prev => prev ? { ...prev, contentPreview: text.slice(0, 4000) } : prev); if (isCSV) { const parsed = tryParseCSV(text); setTablePreview(parsed); } else { setTablePreview(null); } try { await saveLatestUpload(f, { ...metaObj, contentPreview: text.slice(0, 4000) }); } catch {} } catch { /* ignore */ } }; headReader.readAsArrayBuffer(headBlob); } catch { /* ignore */ } // Use streaming read for progress without buffering entire file in memory try { const stream: ReadableStream | undefined = (typeof (f as any).stream === "function" ? (f as any).stream() : undefined); if (stream && typeof stream.getReader === "function") { const reader = stream.getReader(); let loaded = 0; const total = f.size || 1; for (;;) { const { done, value } = await reader.read(); if (done) break; loaded += value ? value.length : 0; const pct = Math.min(100, Math.round((loaded / total) * 100)); setProgress(pct); } setProgress(100); } else { // Fallback to FileReader progress events const reader = new FileReader(); reader.onprogress = (evt) => { if (evt.lengthComputable) { const pct = Math.min(100, Math.round((evt.loaded / evt.total) * 100)); setProgress(pct); } else { setProgress((p) => (p < 90 ? p + 5 : p)); } }; reader.onloadend = () => setProgress(100); reader.onerror = () => setProgress(0); reader.readAsArrayBuffer(f); } } catch { setProgress(100); } return; } const reader = new FileReader(); reader.onprogress = (evt) => { if (evt.lengthComputable) { const pct = Math.min(100, Math.round((evt.loaded / evt.total) * 100)); setProgress(pct); } else { setProgress((p) => (p < 90 ? p + 5 : p)); } }; reader.onload = async () => { try { const buf = reader.result as ArrayBuffer; const decoder = new TextDecoder(); const text = decoder.decode(buf); const metaObj: UploadedFileMeta = { name: f.name, size: f.size, type: f.type || "unknown", contentPreview: text.slice(0, 4000), }; setFileMeta(metaObj); if (isCSV) { const parsed = tryParseCSV(text); setTablePreview(parsed); } else { setTablePreview(null); } // Save file blob and meta to browser cache (IndexedDB) try { await saveLatestUpload(f, metaObj); } catch {} setProgressLabel("Processing"); setProgress(100); } catch (e) { const metaObj: UploadedFileMeta = { name: f.name, size: f.size, type: f.type || "unknown", contentPreview: "Unable to decode preview.", }; setFileMeta(metaObj); setTablePreview(null); try { await saveLatestUpload(f, metaObj); } catch {} setProgressLabel("Processing"); setProgress(100); } }; reader.onerror = () => { setProgress(0); }; reader.readAsArrayBuffer(f); }, []); function handleFileChange(e: React.ChangeEvent) { const f = e.target.files?.[0]; processFile(f as File); } const onDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }; const onDragLeave = () => setIsDragging(false); const onDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const f = e.dataTransfer.files?.[0]; processFile(f as File); }; // Load last cached upload on mount (processing tab only) useEffect(() => { let ignore = false; if (tab !== "processing") return; (async () => { try { const { file, meta } = await getLatestUpload(); if (!ignore && meta) { setFileMeta(meta as UploadedFileMeta); if (file) { setUploadedFile(file); } setLoadedFromCache(true); } } catch {} })(); return () => { ignore = true; }; }, [tab]); function renderTabContent() { switch (tab) { case "processing": return (

Upload & Process Data

Upload a CSV / JSON / text file. We will later parse, detect PII, and queue analyses.

Drag & drop a CSV / JSON / TXT here, or click to browse.

{progress > 0 && (
{progressLabel} {progress}%
)} {fileMeta && (
{fileMeta.name}
{Math.round(fileMeta.size / 1024)} KB
{loadedFromCache && (
Loaded from browser cache
)}
{fileMeta.type || "Unknown type"}
{/* Table preview when structured data detected; otherwise show text */} {tablePreview && tablePreview.origin === 'csv' ? (
{tablePreview.headers.map((h, idx) => ( ))} {tablePreview.rows.map((r, i) => ( {r.map((c, j) => ( ))} ))}
{h}
{c}
) : (
														{fileMeta.contentPreview || "(no preview)"}
													
)} {error && (
❌ {error}
)} {analyzeResult && (
✅ Analysis complete! View results in tabs. Download Report
)} {cleanResult && (
✅ Cleaning complete! {cleanResult.summary.total_cells_affected} cells anonymized.
)}
)}
); case "bias-analysis": return (

Bias Analysis

{analyzeResult ? (
Overall Bias Score
{(analyzeResult.bias_metrics.overall_bias_score * 100).toFixed(1)}%
Violations Detected
{analyzeResult.bias_metrics.violations_detected.length}

Model Performance

Accuracy
{(analyzeResult.model_performance.accuracy * 100).toFixed(1)}%
Precision
{(analyzeResult.model_performance.precision * 100).toFixed(1)}%
Recall
{(analyzeResult.model_performance.recall * 100).toFixed(1)}%
F1 Score
{(analyzeResult.model_performance.f1_score * 100).toFixed(1)}%
) : (

Upload and analyze a dataset to see bias metrics.

)}
); case "risk-analysis": return (

Risk Analysis

{analyzeResult ? (
Overall Risk Score
{(analyzeResult.risk_assessment.overall_risk_score * 100).toFixed(1)}%
{cleanResult && (

PII Detection Results

Cells Anonymized: {cleanResult.summary.total_cells_affected}
Columns Removed: {cleanResult.summary.columns_removed.length}
Columns Anonymized: {cleanResult.summary.columns_anonymized.length}
)}
) : (

Upload and analyze a dataset to see risk assessment.

)}
); case "bias-risk-mitigation": return (

Mitigation Suggestions

{analyzeResult && analyzeResult.recommendations.length > 0 ? (
{analyzeResult.recommendations.map((rec, i) => (
{rec}
))}
) : (

Recommendations will appear here after analysis.

)}
); case "results": return (

Results Summary

{(analyzeResult || cleanResult) ? (
{analyzeResult && (

Analysis Results

Dataset: {analyzeResult.filename}
Rows: {analyzeResult.dataset_info.rows}
Columns: {analyzeResult.dataset_info.columns}
Bias Score: {(analyzeResult.bias_metrics.overall_bias_score * 100).toFixed(1)}%
Risk Score: {(analyzeResult.risk_assessment.overall_risk_score * 100).toFixed(1)}%
Download Full Report →
)} {cleanResult && (

Cleaning Results

Original: {cleanResult.dataset_info.original_rows} rows × {cleanResult.dataset_info.original_columns} cols
Cleaned: {cleanResult.dataset_info.cleaned_rows} rows × {cleanResult.dataset_info.cleaned_columns} cols
Cells Anonymized: {cleanResult.summary.total_cells_affected}
Columns Removed: {cleanResult.summary.columns_removed.length}
GDPR Compliant: {cleanResult.gdpr_compliance.length} articles applied
)}
) : (

Process a dataset to see aggregated results.

)}
); default: return null; } } return (
{renderTabContent()}
); }