interface interactive

This commit is contained in:
nBiqoz
2025-09-07 12:30:23 +02:00
parent 74e56c956c
commit ef0819ae90
27 changed files with 1827 additions and 515 deletions

View File

@@ -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,
};
};