import { useState } from "react"; import { patterns } from "@/app/utils/highlightEntities"; interface EntityMapping { originalValue: string; anonymizedValue: string; entityType: string; startIndex: number; endIndex: number; } // L'API retourne des objets avec snake_case interface PresidioAnalyzerResult { entity_type: string; start: number; end: number; score: number; } // La réponse de l'API utilise camelCase pour les clés principales interface ProcessDocumentResponse { text?: string; // Texte original en cas de fallback anonymizedText?: string; analyzerResults?: PresidioAnalyzerResult[]; error?: string; } interface AnonymizationLogicProps { sourceText: string; fileContent: string; uploadedFile: File | null; setOutputText: (text: string) => void; setError: (error: string | null) => void; setEntityMappings: (mappings: EntityMapping[]) => void; } export const useAnonymization = ({ sourceText, fileContent, uploadedFile, setOutputText, setError, setEntityMappings, }: AnonymizationLogicProps) => { const [isProcessing, setIsProcessing] = useState(false); const anonymizeData = async () => { const textToProcess = sourceText || fileContent || ""; if (!textToProcess.trim()) { setError( "Veuillez saisir du texte à anonymiser ou télécharger un fichier" ); return; } setIsProcessing(true); setError(null); setOutputText(""); setEntityMappings([]); try { const formData = new FormData(); if (uploadedFile) { formData.append("file", uploadedFile); } else { const textBlob = new Blob([textToProcess], { type: "text/plain" }); const textFile = new File([textBlob], "input.txt", { type: "text/plain", }); formData.append("file", textFile); } const response = await fetch("/api/process-document", { method: "POST", body: formData, }); if (!response.ok) { let errorMessage = `Erreur HTTP: ${response.status}`; try { const errorData = await response.json(); if (errorData.error) errorMessage = errorData.error; } catch { /* Ignore */ } throw new Error(errorMessage); } const data: ProcessDocumentResponse = await response.json(); if (data.error) { throw new Error(data.error); } // Utiliser camelCase pour les propriétés de la réponse principale if (data.anonymizedText && data.analyzerResults) { setOutputText(data.anonymizedText); const entityTypeMap = new Map(); patterns.forEach((p) => { const match = p.regex.toString().match(/<([A-Z_]+)>/); if (match && match[1]) { entityTypeMap.set(match[1], p.label); } }); // 1. Compter les occurrences de chaque tag d'entité dans le texte anonymisé const tagCounts = new Map(); data.analyzerResults.forEach((result) => { const tag = `<${result.entity_type}>`; if (!tagCounts.has(result.entity_type)) { const count = ( data.anonymizedText?.match(new RegExp(tag, "g")) || [] ).length; tagCounts.set(result.entity_type, count); } }); // 2. Créer un mapping basé sur l'ordre d'apparition dans le texte anonymisé const uniqueMappings: EntityMapping[] = []; // Vérifier que les données nécessaires sont disponibles if (!data.analyzerResults || !data.anonymizedText) { setEntityMappings([]); return; } const entityCounters = new Map(); // Parcourir le texte anonymisé pour trouver les tags dans l'ordre const anonymizedText = data.anonymizedText; const allMatches: Array<{ match: RegExpMatchArray; entityType: string; position: number; }> = []; // Trouver tous les tags dans le texte anonymisé patterns.forEach(pattern => { const entityTypeKey = pattern.regex.toString().match(/<([A-Z_]+)>/)?.[1]; if (entityTypeKey) { const regex = new RegExp(pattern.regex.source, 'g'); let match; while ((match = regex.exec(anonymizedText)) !== null) { allMatches.push({ match, entityType: entityTypeKey, position: match.index }); } } }); // Trier par position dans le texte anonymisé allMatches.sort((a, b) => a.position - b.position); // Créer les mappings dans l'ordre d'apparition const seen = new Set(); allMatches.forEach(({ entityType }) => { const frenchLabel = entityTypeMap.get(entityType) || entityType; const currentCount = (entityCounters.get(entityType) || 0) + 1; entityCounters.set(entityType, currentCount); // Trouver l'entité correspondante dans les résultats d'analyse const correspondingResult = data.analyzerResults ?.filter(result => result.entity_type === entityType) .find(result => { const originalValue = textToProcess.substring(result.start, result.end); const uniqueKey = `${frenchLabel}|${originalValue}|${currentCount}`; return !seen.has(uniqueKey); }); if (correspondingResult) { const originalValue = textToProcess.substring( correspondingResult.start, correspondingResult.end ); const uniqueKey = `${frenchLabel}|${originalValue}|${currentCount}`; if (!seen.has(uniqueKey)) { uniqueMappings.push({ entityType: frenchLabel, originalValue: originalValue, anonymizedValue: `${frenchLabel} [${currentCount}]`, startIndex: correspondingResult.start, endIndex: correspondingResult.end, }); seen.add(uniqueKey); } } }); setEntityMappings(uniqueMappings); } else if (data.text) { setOutputText(data.text); setError("Presidio temporairement indisponible. Texte non anonymisé."); } } catch (error) { setError( error instanceof Error ? error.message : "Erreur lors de l'anonymisation avec Presidio" ); } finally { setIsProcessing(false); } }; return { anonymizeData, isProcessing }; };