From 00a0784e6b5d566d972eec68cc90c9b6a68923a9 Mon Sep 17 00:00:00 2001 From: dionjoshualobo <23h13.joshua@sjec.ac.in> Date: Fri, 7 Nov 2025 12:19:40 +0530 Subject: [PATCH 1/2] Added idb.ts --- .gitignore | 2 +- frontend/nordic-privacy-ai/lib/idb.ts | 113 ++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 frontend/nordic-privacy-ai/lib/idb.ts diff --git a/.gitignore b/.gitignore index 44cf1b7..c025570 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ dist/ downloads/ eggs/ .eggs/ -lib/ + lib64/ parts/ sdist/ diff --git a/frontend/nordic-privacy-ai/lib/idb.ts b/frontend/nordic-privacy-ai/lib/idb.ts new file mode 100644 index 0000000..507b85a --- /dev/null +++ b/frontend/nordic-privacy-ai/lib/idb.ts @@ -0,0 +1,113 @@ +/** + * IndexedDB utilities for persisting file uploads in the browser. + * Stores the latest uploaded file and its metadata for recovery across sessions. + */ + +const DB_NAME = "NordicPrivacyAI"; +const DB_VERSION = 1; +const STORE_NAME = "latestUpload"; + +interface UploadedFileMeta { + name: string; + size: number; + type: string; + contentPreview: string; +} + +interface LatestUploadData { + file: File; + meta: UploadedFileMeta; + timestamp: number; +} + +/** + * Open or create the IndexedDB database + */ +function openDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, DB_VERSION); + + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(request.result); + + request.onupgradeneeded = (event) => { + const db = (event.target as IDBOpenDBRequest).result; + + // Create object store if it doesn't exist + if (!db.objectStoreNames.contains(STORE_NAME)) { + db.createObjectStore(STORE_NAME); + } + }; + }); +} + +/** + * Save the latest uploaded file and its metadata to IndexedDB + */ +export async function saveLatestUpload( + file: File, + meta: UploadedFileMeta +): Promise { + const db = await openDB(); + + return new Promise((resolve, reject) => { + const transaction = db.transaction([STORE_NAME], "readwrite"); + const store = transaction.objectStore(STORE_NAME); + + const data: LatestUploadData = { + file, + meta, + timestamp: Date.now(), + }; + + const request = store.put(data, "latest"); + + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(); + + transaction.oncomplete = () => db.close(); + }); +} + +/** + * Retrieve the latest uploaded file and metadata from IndexedDB + */ +export async function getLatestUpload(): Promise { + const db = await openDB(); + + return new Promise((resolve, reject) => { + const transaction = db.transaction([STORE_NAME], "readonly"); + const store = transaction.objectStore(STORE_NAME); + const request = store.get("latest"); + + request.onerror = () => reject(request.error); + request.onsuccess = () => { + const result = request.result as LatestUploadData | undefined; + if (result) { + resolve(result); + } else { + reject(new Error("No cached upload found")); + } + }; + + transaction.oncomplete = () => db.close(); + }); +} + +/** + * Delete the latest upload from IndexedDB + */ +export async function deleteLatestUpload(): Promise { + const db = await openDB(); + + return new Promise((resolve, reject) => { + const transaction = db.transaction([STORE_NAME], "readwrite"); + const store = transaction.objectStore(STORE_NAME); + const request = store.delete("latest"); + + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(); + + transaction.oncomplete = () => db.close(); + }); +} From c3fe7daf6fcf0eb1390e8cee5255c180a410a6d7 Mon Sep 17 00:00:00 2001 From: Ashishyg Date: Fri, 7 Nov 2025 12:36:30 +0530 Subject: [PATCH 2/2] updated csv preview and analyze --- frontend/nordic-privacy-ai/app/try/page.tsx | 2 +- .../components/try/CenterPanel.tsx | 122 ++++++++++++++++-- 2 files changed, 113 insertions(+), 11 deletions(-) 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)"}
+													
+ )} +
+
)}