"use client"; import { useState, useCallback } from "react"; import Image from "next/image"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Upload, FileText, ShieldCheck, Download, Trash2, AlertCircle, X, Zap, Lock, Shield, Clock, User, AtSign, MapPin, Cake, Home as HomeIcon, Venus, Phone, Building, Fingerprint, CreditCard, Check, Eye // <-- Icône ajoutée } from "lucide-react"; import MarkdownModal from "./components/MarkdownModal"; // <-- Composant de la modale ajouté // === Interfaces et Données === // L'interface est mise à jour pour inclure le contenu texte pour l'aperçu interface ProcessedFile { id: string; name: string; status: 'processing' | 'completed' | 'error'; timestamp: Date; originalSize?: string; processedSize?: string; piiCount?: number; errorMessage?: string; processedBlob?: Blob; textContent?: string; // <-- Ajouté pour stocker le contenu du fichier } interface AnonymizationOptions { [key: string]: boolean; } const piiOptions = [ { id: 'name', label: 'Nom & Prénom', icon: User }, { id: 'email', label: 'Email', icon: AtSign }, { id: 'location', label: 'Lieu', icon: MapPin }, { id: 'birthdate', label: 'Date de naissance', icon: Cake }, { id: 'address', label: 'Adresse', icon: HomeIcon }, { id: 'gender', label: 'Genre / Sexe', icon: Venus }, { id: 'phone', label: 'Téléphone', icon: Phone }, { id: 'organization', label: 'Entreprise', icon: Building }, { id: 'idNumber', label: 'N° Identification', icon: Fingerprint }, { id: 'financial', label: 'Données financières', icon: CreditCard }, ]; export default function Home() { // === Déclaration des états (States) === const [file, setFile] = useState(null); const [isProcessing, setIsProcessing] = useState(false); const [progress, setProgress] = useState(0); const [isDragOver, setIsDragOver] = useState(false); const [history, setHistory] = useState([]); const [error, setError] = useState(null); const [anonymizationOptions, setAnonymizationOptions] = useState( piiOptions.reduce((acc, option) => ({ ...acc, [option.id]: true }), {}) ); // Nouvel état pour gérer la modale const [modalContent, setModalContent] = useState(null); // === Fonctions utilitaires et Handlers === const handleOptionChange = (id: string) => setAnonymizationOptions(prev => ({ ...prev, [id]: !prev[id] })); const handleFileChange = (event: React.ChangeEvent) => { if (event.target.files?.length) { setFile(event.target.files[0]); setError(null); }}; const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragOver(false); if (e.dataTransfer.files?.length) { setFile(e.dataTransfer.files[0]); setError(null); }}, []); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragOver(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragOver(false); }, []); const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; const clearFile = () => { setFile(null); setError(null); }; const clearHistory = () => setHistory([]); const removeFromHistory = (id: string) => setHistory(prev => prev.filter(item => item.id !== id)); const handleDownload = (id: string) => { const fileToDownload = history.find(item => item.id === id); if (!fileToDownload?.processedBlob) return; const url = URL.createObjectURL(fileToDownload.processedBlob); const a = document.createElement('a'); a.href = url; a.download = `anonymized_${fileToDownload.name.split('.')[0] || 'file'}.txt`; a.click(); URL.revokeObjectURL(url); a.remove(); }; // Nouvelle fonction pour gérer l'ouverture de l'aperçu const handlePreview = (id: string) => { const fileToPreview = history.find((item) => item.id === id); if (fileToPreview?.textContent) { setModalContent(fileToPreview.textContent); } }; const getStatusInfo = (item: ProcessedFile) => { switch (item.status) { case 'completed': return { icon: , color: 'bg-[#061717]' }; case 'error': return { icon: , color: 'bg-[#F7AB6E]' }; default: return { icon:
, color: 'bg-[#061717]' }; } }; // === Fonction principale pour l'appel à n8n === const processFile = async () => { if (!file) return; const n8nWebhookUrl = process.env.NEXT_PUBLIC_N8N_WEBHOOK_URL; if (!n8nWebhookUrl) { setError("L'URL du service est manquante. Contactez l'administrateur."); return; } setIsProcessing(true); setProgress(0); setError(null); const fileId = `${Date.now()}-${file.name}`; setHistory(prev => [{ id: fileId, name: file.name, status: 'processing', timestamp: new Date(), originalSize: formatFileSize(file.size) }, ...prev]); setFile(null); setProgress(10); try { const formData = new FormData(); formData.append('file', file); formData.append('options', JSON.stringify(anonymizationOptions)); setProgress(30); const response = await fetch(n8nWebhookUrl, { method: 'POST', body: formData }); setProgress(70); if (!response.ok) { const errorResult = await response.json().catch(() => ({ error: `Erreur serveur [${response.status}]` })); throw new Error(errorResult.error || `Échec du traitement.`); } const processedBlob = await response.blob(); const piiCount = parseInt(response.headers.get('X-Pii-Count') || '0', 10); // On lit le contenu du blob en texte pour pouvoir l'afficher const textContent = await processedBlob.text(); setProgress(90); // On met à jour l'historique avec toutes les informations, y compris le contenu texte setHistory(prev => prev.map(item => item.id === fileId ? { ...item, status: 'completed', processedSize: formatFileSize(processedBlob.size), piiCount, processedBlob, textContent } : item)); setProgress(100); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Une erreur inconnue est survenue."; setError(errorMessage); setHistory(prev => prev.map(item => item.id === fileId ? { ...item, status: 'error', errorMessage } : item)); } finally { setIsProcessing(false); setTimeout(() => setProgress(0), 1000); } }; // === Rendu du composant (JSX) === return (
{/* --- Sidebar (Historique) --- */} {/* --- Contenu Principal --- */}

LeCercle.IA

Anonymisation • RGPD • Sécurisé

{file ? : }
{file ? (

{file.name}

{formatFileSize(file.size)}

) : (

Glissez votre document

Ou cliquez ici

)}

Options d'Anonymisation

{piiOptions.map((option) => ())}
{isProcessing && (
Traitement...{Math.round(progress)}%
)} {error && (

{error}

)}
{file && !isProcessing && }
{[{ icon: Shield, title: "RGPD", subtitle: "Conforme" }, { icon: Clock, title: "Rapide", subtitle: "Local" }, { icon: Lock, title: "Sécurisé", subtitle: "Sans Serveur" }].map((item, index) => (

{item.title}

{item.subtitle}

))}
{/* Le composant de la modale est rendu ici, en dehors du flux principal */} setModalContent(null)} />
); }