import { Upload, FileText, AlertTriangle, Shield, Copy, Download, } from "lucide-react"; import { SampleTextComponent } from "./SampleTextComponent"; import { SupportedDataTypes } from "./SupportedDataTypes"; import { AnonymizationInterface } from "./AnonymizationInterface"; import { InteractiveTextEditor } from "./InteractiveTextEditor"; import React, { useState } from "react"; import { EntityMapping } from "../config/entityLabels"; // Importer l'interface unifiée // Supprimer l'interface locale EntityMapping (lignes 15-21) interface FileUploadComponentProps { uploadedFile: File | null; handleFileChange: (e: React.ChangeEvent) => void; sourceText: string; setSourceText: (text: string) => void; setUploadedFile: (file: File | null) => void; onAnonymize?: (category?: string) => void; isProcessing?: boolean; canAnonymize?: boolean; isLoadingFile?: boolean; onRestart?: () => void; outputText?: string; copyToClipboard?: () => void; downloadText?: () => void; isExampleLoaded?: boolean; setIsExampleLoaded?: (loaded: boolean) => void; entityMappings?: EntityMapping[]; onMappingsUpdate?: (mappings: EntityMapping[]) => void; } export const FileUploadComponent = ({ uploadedFile, handleFileChange, sourceText, setSourceText, setUploadedFile, onAnonymize, isProcessing = false, canAnonymize = false, isLoadingFile = false, onRestart, outputText, copyToClipboard, downloadText, setIsExampleLoaded, entityMappings, onMappingsUpdate, }: FileUploadComponentProps) => { const [isDragOver, setIsDragOver] = useState(false); const [selectedCategory, setSelectedCategory] = useState("pii"); // Fonction pour valider le type de fichier const isValidFileType = (file: File) => { const allowedTypes = ["text/plain", "application/pdf"]; const allowedExtensions = [".txt", ".pdf"]; return ( allowedTypes.includes(file.type) || allowedExtensions.some((ext) => file.name.toLowerCase().endsWith(ext)) ); }; // Gestionnaires de glisser-déposer const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }; const handleDragEnter = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(true); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); // Vérifier si on quitte vraiment la zone de drop if (!e.currentTarget.contains(e.relatedTarget as Node)) { setIsDragOver(false); } }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false); const files = Array.from(e.dataTransfer.files); if (files.length > 0) { const file = files[0]; // Vérifier le type de fichier if (!isValidFileType(file)) { alert( "Type de fichier non supporté. Veuillez sélectionner un fichier PDF ou TXT." ); return; } // Vérifier la taille (5MB max) if (file.size > 5 * 1024 * 1024) { alert("Le fichier est trop volumineux. Taille maximale : 5MB."); return; } const syntheticEvent = { target: { files: [file] }, } as unknown as React.ChangeEvent; handleFileChange(syntheticEvent); } }; // Gestionnaire de changement de fichier modifié pour valider le type const handleFileChangeWithValidation = ( e: React.ChangeEvent ) => { const file = e.target.files?.[0]; if (file && !isValidFileType(file)) { alert( "Type de fichier non supporté. Veuillez sélectionner un fichier PDF ou TXT." ); e.target.value = ""; // Reset l'input return; } handleFileChange(e); }; // On passe en preview seulement si : // 1. Un fichier est uploadé OU // 2. On a un résultat d'anonymisation // (On retire isExampleLoaded pour permettre l'édition du texte d'exemple) if (uploadedFile || outputText) { return (
{/* Si on a un résultat, afficher 2 blocs côte à côte */} {outputText ? (
{/* Preview du document original */}
{uploadedFile ? (

{uploadedFile.name} •{" "} {(uploadedFile.size / 1024).toFixed(1)} KB

) : (

Demo - Exemple de texte

)}
                    {sourceText || "Aucun contenu à afficher"}
                  
{/* Bloc résultat anonymisé - MODE INTERACTIF */}

DOCUMENT ANONYMISÉ MODE INTERACTIF

{/* Boutons d'action */}
{copyToClipboard && ( )} {downloadText && ( )}
{ if (onMappingsUpdate && entityMappings) { console.log("🔄 Mise à jour mapping:", { originalValue, newLabel, entityType, applyToAllOccurrences, customColor, wordStart, wordEnd, }); let updatedMappings: EntityMapping[]; if (applyToAllOccurrences) { // CORRECTION: Créer des mappings pour toutes les occurrences dans le texte const existingMappingsForOtherTexts = entityMappings.filter( (mapping) => mapping.text !== originalValue ); const newMappings: EntityMapping[] = []; let searchIndex = 0; // Chercher toutes les occurrences dans le texte source while (true) { const foundIndex = sourceText.indexOf( originalValue, searchIndex ); if (foundIndex === -1) break; // Vérifier que c'est une occurrence valide (limites de mots) const isValidBoundary = (foundIndex === 0 || !/\w/.test(sourceText[foundIndex - 1])) && (foundIndex + originalValue.length === sourceText.length || !/\w/.test( sourceText[foundIndex + originalValue.length] )); if (isValidBoundary) { newMappings.push({ text: originalValue, entity_type: entityType, start: foundIndex, end: foundIndex + originalValue.length, displayName: newLabel, customColor: customColor, }); } searchIndex = foundIndex + 1; } updatedMappings = [ ...existingMappingsForOtherTexts, ...newMappings, ]; } else { // Logique existante pour une seule occurrence if ( wordStart !== undefined && wordEnd !== undefined ) { const targetMapping = entityMappings.find( (mapping) => mapping.start === wordStart && mapping.end === wordEnd ); if (targetMapping) { updatedMappings = entityMappings.map( (mapping) => { if ( mapping.start === wordStart && mapping.end === wordEnd ) { return { ...mapping, displayName: newLabel, entity_type: entityType, customColor: customColor, }; } return mapping; } ); } else { const newMapping: EntityMapping = { text: originalValue, entity_type: entityType, start: wordStart, end: wordEnd, displayName: newLabel, customColor: customColor, }; updatedMappings = [...entityMappings, newMapping]; } } else { // Fallback: logique existante const existingMappingIndex = entityMappings.findIndex( (mapping) => mapping.text === originalValue ); if (existingMappingIndex !== -1) { updatedMappings = entityMappings.map( (mapping, index) => { if (index === existingMappingIndex) { return { ...mapping, displayName: newLabel, entity_type: entityType, customColor: customColor, }; } return mapping; } ); } else { const foundIndex = sourceText.indexOf(originalValue); if (foundIndex !== -1) { const newMapping: EntityMapping = { text: originalValue, entity_type: entityType, start: foundIndex, end: foundIndex + originalValue.length, displayName: newLabel, customColor: customColor, }; updatedMappings = [ ...entityMappings, newMapping, ]; } else { updatedMappings = entityMappings; } } } } console.log( "✅ Mappings mis à jour:", updatedMappings.length ); onMappingsUpdate( updatedMappings.sort((a, b) => a.start - b.start) ); } }} onRemoveMapping={(originalValue, applyToAll) => { if (onMappingsUpdate && entityMappings) { console.log("🗑️ Suppression mapping:", { originalValue, applyToAll, }); let filteredMappings: EntityMapping[]; if (applyToAll) { // Supprimer toutes les occurrences filteredMappings = entityMappings.filter( (mapping) => mapping.text !== originalValue ); } else { // Supprimer seulement la première occurrence const firstIndex = entityMappings.findIndex( (mapping) => mapping.text === originalValue ); if (firstIndex !== -1) { filteredMappings = entityMappings.filter( (_, index) => index !== firstIndex ); } else { filteredMappings = entityMappings; } } console.log( "✅ Mappings après suppression:", filteredMappings.length ); onMappingsUpdate( filteredMappings.sort((a, b) => a.start - b.start) ); } }} />
) : ( /* Preview normal quand pas de résultat */
{uploadedFile ? (

{uploadedFile.name} •{" "} {(uploadedFile.size / 1024).toFixed(1)} KB

) : (

Demo - Exemple de texte

)}
{/* Zone de texte avec limite de hauteur et scroll */}
{isLoadingFile ? (
Chargement du fichier en cours...
) : (
                    {sourceText || "Aucun contenu à afficher"}
                  
)}
{/* Disclaimer déplacé en dessous du texte */}

Cet outil IA peut ne pas détecter toutes les informations sensibles. Vérifiez le résultat avant de le partager.

)} {/* Boutons d'action - Responsive mobile */} {canAnonymize && !isLoadingFile && (
{/* Sélecteur de catégorie - NOUVEAU */} {onAnonymize && !outputText && (
)} {/* Bouton Anonymiser - seulement si pas encore anonymisé */} {onAnonymize && !outputText && ( )} {/* Bouton Recommencer - toujours visible */} {onRestart && ( )}
)} {/* Affichage conditionnel : Interface d'anonymisation OU Types de données supportées */} {isProcessing || outputText ? ( ) : ( )}
); } return (
{/* Deux colonnes côte à côte */}
{/* Colonne gauche - Zone de texte */}
{/* Header avec icône */}
{/* Titre */}

Saisissez votre texte

Tapez ou collez votre texte ici

{/* Zone de texte éditable */}
{/* Zone pour le texte - SANS overflow */}
{" "} {/* Ajout de pb-6 pour le compteur */} {/* Placeholder personnalisé avec lien cliquable */} {!sourceText && (
Commencez à taper du texte, ou 
)}