interface interactive
This commit is contained in:
@@ -1,211 +1,140 @@
|
||||
import { useState } from "react";
|
||||
import { patterns } from "@/app/utils/highlightEntities";
|
||||
import {
|
||||
PresidioAnalyzerResult,
|
||||
EntityMapping,
|
||||
} from "@/app/config/entityLabels";
|
||||
|
||||
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 pour la réponse de l'API process-document
|
||||
interface ProcessDocumentResponse {
|
||||
text?: string; // Texte original en cas de fallback
|
||||
text?: string;
|
||||
anonymizedText?: string;
|
||||
analyzerResults?: PresidioAnalyzerResult[];
|
||||
replacementValues?: Record<string, string>; // Nouvelle propriété
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// Props du hook
|
||||
interface AnonymizationLogicProps {
|
||||
sourceText: string;
|
||||
fileContent: string;
|
||||
uploadedFile: File | null;
|
||||
setOutputText: (text: string) => void;
|
||||
setError: (error: string | null) => void;
|
||||
setEntityMappings: (mappings: EntityMapping[]) => void;
|
||||
}
|
||||
|
||||
// NOUVEAU: Définir les types pour le paramètre de anonymizeData
|
||||
interface AnonymizeDataParams {
|
||||
file?: File | null;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook pour la logique d'anonymisation.
|
||||
* Gère l'appel API et la création du tableau de mapping de manière simple et directe.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
const anonymizeData = async ({ file, text }: AnonymizeDataParams) => {
|
||||
setIsProcessing(true);
|
||||
setError(null);
|
||||
setOutputText("");
|
||||
setEntityMappings([]);
|
||||
setOutputText("");
|
||||
|
||||
try {
|
||||
// ÉTAPE 1: Construire le FormData ici pour garantir le bon format
|
||||
const formData = new FormData();
|
||||
if (uploadedFile) {
|
||||
formData.append("file", uploadedFile);
|
||||
if (file) {
|
||||
formData.append("file", file);
|
||||
} else if (text) {
|
||||
// Si c'est du texte, on le transforme en Blob pour l'envoyer comme un fichier
|
||||
const textBlob = new Blob([text], { type: "text/plain" });
|
||||
formData.append("file", textBlob, "input.txt");
|
||||
} else {
|
||||
const textBlob = new Blob([textToProcess], { type: "text/plain" });
|
||||
const textFile = new File([textBlob], "input.txt", {
|
||||
type: "text/plain",
|
||||
});
|
||||
formData.append("file", textFile);
|
||||
throw new Error("Aucune donnée à anonymiser (ni fichier, ni texte).");
|
||||
}
|
||||
|
||||
const response = await fetch("/api/process-document", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
body: formData, // Le Content-Type sera automatiquement défini par le navigateur
|
||||
});
|
||||
|
||||
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);
|
||||
if (!response.ok || data.error) {
|
||||
throw new Error(
|
||||
data.error || "Erreur lors de la communication avec l'API."
|
||||
);
|
||||
}
|
||||
|
||||
// Utiliser camelCase pour les propriétés de la réponse principale
|
||||
if (data.anonymizedText && data.analyzerResults) {
|
||||
setOutputText(data.anonymizedText);
|
||||
const originalText = data.text || "";
|
||||
const presidioResults = data.analyzerResults || [];
|
||||
const replacementValues = data.replacementValues || {}; // Récupérer les valeurs de remplacement
|
||||
|
||||
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);
|
||||
}
|
||||
// 🔍 AJOUT DES CONSOLE.LOG POUR DÉBOGUER
|
||||
console.log("📊 Données reçues de Presidio:", {
|
||||
originalTextLength: originalText.length,
|
||||
presidioResultsCount: presidioResults.length,
|
||||
presidioResults: presidioResults,
|
||||
replacementValues: replacementValues,
|
||||
replacementValuesKeys: Object.keys(replacementValues),
|
||||
replacementValuesEntries: Object.entries(replacementValues)
|
||||
});
|
||||
|
||||
// ÉTAPE 2 : Passer le texte ORIGINAL à l'état de sortie.
|
||||
setOutputText(originalText);
|
||||
|
||||
// ÉTAPE 3 : Créer le tableau de mapping avec la nouvelle structure
|
||||
const sortedResults = [...presidioResults].sort(
|
||||
(a, b) => a.start - b.start
|
||||
);
|
||||
const mappings: EntityMapping[] = [];
|
||||
|
||||
// Dans la fonction anonymizeData, section création des mappings :
|
||||
for (const result of sortedResults) {
|
||||
const { entity_type, start, end } = result;
|
||||
const detectedText = originalText.substring(start, end);
|
||||
|
||||
// 🔍 CONSOLE.LOG POUR CHAQUE ENTITÉ
|
||||
console.log(`🔍 Entité détectée:`, {
|
||||
entity_type,
|
||||
detectedText,
|
||||
replacementFromMap: replacementValues[detectedText],
|
||||
fallback: `[${entity_type}]`
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
mappings.push({
|
||||
entity_type: entity_type,
|
||||
start: start,
|
||||
end: end,
|
||||
text: detectedText,
|
||||
replacementValue: replacementValues[detectedText] || `[${entity_type}]`,
|
||||
displayName: replacementValues[detectedText] || `[${entity_type}]`, // Ajouter cette ligne
|
||||
customColor: undefined,
|
||||
});
|
||||
|
||||
// 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é.");
|
||||
}
|
||||
|
||||
// 🔍 CONSOLE.LOG FINAL DES MAPPINGS
|
||||
console.log("📋 Mappings créés:", mappings);
|
||||
|
||||
// ÉTAPE 4 : Mettre à jour l'état global avec les mappings créés.
|
||||
setEntityMappings(mappings);
|
||||
} catch (error) {
|
||||
console.error("Erreur dans useAnonymization:", error);
|
||||
setError(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Erreur lors de l'anonymisation avec Presidio"
|
||||
: "Une erreur inconnue est survenue."
|
||||
);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
return { anonymizeData, isProcessing };
|
||||
return {
|
||||
anonymizeData,
|
||||
isProcessing,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user