diff --git a/frontend/nordic-privacy-ai/app/try/page.tsx b/frontend/nordic-privacy-ai/app/try/page.tsx index 66c51f5..4840894 100644 --- a/frontend/nordic-privacy-ai/app/try/page.tsx +++ b/frontend/nordic-privacy-ai/app/try/page.tsx @@ -14,7 +14,7 @@ export default function TryPage() {
-
+
setTab("bias-analysis")} />
diff --git a/frontend/nordic-privacy-ai/components/try/CenterPanel.tsx b/frontend/nordic-privacy-ai/components/try/CenterPanel.tsx index 73c8a2c..8fd8a05 100644 --- a/frontend/nordic-privacy-ai/components/try/CenterPanel.tsx +++ b/frontend/nordic-privacy-ai/components/try/CenterPanel.tsx @@ -5,6 +5,7 @@ import { saveLatestUpload, getLatestUpload, deleteLatestUpload } from "../../lib interface CenterPanelProps { tab: TryTab; + onAnalyze?: () => void; } interface UploadedFileMeta { @@ -14,11 +15,19 @@ interface UploadedFileMeta { contentPreview: string; } -export function CenterPanel({ tab }: CenterPanelProps) { +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 [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); @@ -26,10 +35,43 @@ export function CenterPanel({ tab }: CenterPanelProps) { setFileMeta(null); setProgress(0); setProgressLabel("Processing"); + setTablePreview(null); }; + 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); // For large files, show a progress bar while reading the file stream (no preview) if (f.size > 1024 * 1024) { @@ -38,13 +80,35 @@ export function CenterPanel({ tab }: CenterPanelProps) { name: f.name, size: f.size, type: f.type || "unknown", - contentPreview: "File too large for preview (limit 1MB).", + 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); @@ -101,6 +165,12 @@ export function CenterPanel({ tab }: CenterPanelProps) { 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); @@ -115,6 +185,7 @@ export function CenterPanel({ tab }: CenterPanelProps) { contentPreview: "Unable to decode preview.", }; setFileMeta(metaObj); + setTablePreview(null); try { await saveLatestUpload(f, metaObj); } catch {} @@ -164,13 +235,13 @@ export function CenterPanel({ tab }: CenterPanelProps) { }, [tab]); function renderTabContent() { - switch (tab) { + switch (tab) { case "processing": - return ( -
+ return ( +

Upload & Process Data

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

-
+
Loaded from browser cache
)}
{fileMeta.type || "Unknown type"}
-
-										{fileMeta.contentPreview || "(no preview)"}
-									
-
+ {/* 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)"}
+													
+ )} +
+
)}