import React, { useState, useCallback } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Upload, File, Trash2, ShieldCheck } from "lucide-react"; import { Button, Card, CardHeader, CardTitle, CardContent, Badge } from "@/components/ui"; import { PiiDiffViewer } from "@/components/PiiDiffViewer"; import { useSessionStore } from "@/stores/sessionStore"; import { uploadLogFileCmd, detectPiiCmd, type LogFile, type PiiSpan, type PiiDetectionResult, } from "@/lib/tauriCommands"; export default function LogUpload() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { piiSpans, approvedRedactions, setPiiSpans, setApprovedRedactions } = useSessionStore(); const [files, setFiles] = useState<{ file: File; uploaded?: LogFile }[]>([]); const [piiResult, setPiiResult] = useState(null); const [isUploading, setIsUploading] = useState(false); const [isDetecting, setIsDetecting] = useState(false); const [error, setError] = useState(null); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); const droppedFiles = Array.from(e.dataTransfer.files); setFiles((prev) => [...prev, ...droppedFiles.map((f) => ({ file: f }))]); }, [] ); const handleFileSelect = (e: React.ChangeEvent) => { if (e.target.files) { const selected = Array.from(e.target.files); setFiles((prev) => [...prev, ...selected.map((f) => ({ file: f }))]); } }; const removeFile = (index: number) => { setFiles((prev) => prev.filter((_, i) => i !== index)); }; const handleUpload = async () => { if (!id || files.length === 0) return; setIsUploading(true); setError(null); try { const uploaded = await Promise.all( files.map(async (entry) => { if (entry.uploaded) return entry; const content = await entry.file.text(); const logFile = await uploadLogFileCmd(id, entry.file.name); return { ...entry, uploaded: logFile }; }) ); setFiles(uploaded); } catch (err) { setError(String(err)); } finally { setIsUploading(false); } }; const handleDetectPii = async () => { const allContent = files .map((f) => f.uploaded?.file_name) .filter(Boolean) .join("\n---\n"); if (!allContent) return; setIsDetecting(true); setError(null); try { const result = files[0]?.uploaded ? await detectPiiCmd(files[0].uploaded.id) : null; setPiiResult(result); if (result) { setPiiSpans(result.detections); setApprovedRedactions(result.detections); }; } catch (err) { setError(String(err)); } finally { setIsDetecting(false); } }; const handleToggleSpan = (span: PiiSpan, approved: boolean) => { if (approved) { setApprovedRedactions([...approvedRedactions, span]); } else { setApprovedRedactions( approvedRedactions.filter( (s) => !(s.start === span.start && s.end === span.end) ) ); } }; const allUploaded = files.length > 0 && files.every((f) => f.uploaded); const piiReviewed = piiResult != null; return (

Upload Logs

Upload log files for PII detection and redaction before triage.

{/* Drop zone */}
e.preventDefault()} onDrop={handleDrop} className="border-2 border-dashed rounded-lg p-8 text-center hover:border-primary transition-colors cursor-pointer" onClick={() => document.getElementById("file-input")?.click()} >

Drag and drop log files here, or click to browse

{/* File list */} {files.length > 0 && ( Files ({files.length}) {files.map((entry, idx) => (
{entry.file.name} ({(entry.file.size / 1024).toFixed(1)} KB) {entry.uploaded && ( Uploaded )}
))} {!allUploaded && ( )}
)} {/* PII Detection */} {allUploaded && ( PII Detection {!piiResult && ( )} {piiResult && ( )} )} {/* Error */} {error && (
{error}
)} {/* Continue */}
); }