Files
Anonyme/app/components/AnonymizationLogic.tsx
2025-08-09 15:23:20 +02:00

212 lines
6.6 KiB
TypeScript

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<string, string>();
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<string, number>();
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<string, number>();
// 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<string>();
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 };
};