From 0360e1ca9fa993d484738a6631d75cbd6e650fd5 Mon Sep 17 00:00:00 2001 From: nBiqoz Date: Sun, 7 Sep 2025 18:01:14 +0200 Subject: [PATCH] logs good, replacement good --- app/api/process-document/route.ts | 75 +++++-------- app/components/AnonymizationLogic.tsx | 2 +- app/components/hooks/useContextMenu.ts | 144 +++++++++++++++++++++---- 3 files changed, 147 insertions(+), 74 deletions(-) diff --git a/app/api/process-document/route.ts b/app/api/process-document/route.ts index 007d947..b469f6f 100644 --- a/app/api/process-document/route.ts +++ b/app/api/process-document/route.ts @@ -251,58 +251,33 @@ export async function POST(req: NextRequest) { analyzerResults: AnalyzerResult[] ) => { const replacementMap: Record = {}; - - // Approche simple : comparer caractère par caractère - let originalIndex = 0; - let anonymizedIndex = 0; - - // Trier les résultats par position - const sortedResults = [...analyzerResults].sort( - (a, b) => a.start - b.start - ); - - for (const result of sortedResults) { - const originalValue = originalText.substring( - result.start, - result.end - ); - - // Avancer jusqu'à la position de l'entité dans le texte original - while (originalIndex < result.start) { - originalIndex++; - anonymizedIndex++; - } - - // Maintenant on est au début de l'entité - // Dans le texte anonymisé, on doit avoir un remplacement qui commence par '[' - if (anonymizedText[anonymizedIndex] === "[") { - // Trouver la fin du remplacement (le ']') - let endBracket = anonymizedIndex; - while ( - endBracket < anonymizedText.length && - anonymizedText[endBracket] !== "]" - ) { - endBracket++; - } - endBracket++; // Inclure le ']' - - const replacementValue = anonymizedText.substring( - anonymizedIndex, - endBracket - ); - replacementMap[originalValue] = replacementValue; - - // Avancer les index - originalIndex = result.end; - anonymizedIndex = endBracket; + + // Extraire tous les remplacements [XXX] du texte anonymisé + const replacementPattern = /\[[^\]]+\]/g; + const foundReplacements = anonymizedText.match(replacementPattern) || []; + + console.log("🔍 Remplacements trouvés dans le texte anonymisé:", foundReplacements); + + // Trier les entités par position + const sortedResults = [...analyzerResults].sort((a, b) => a.start - b.start); + + // Associer chaque entité avec son remplacement correspondant + sortedResults.forEach((result, index) => { + const originalValue = originalText.substring(result.start, result.end); + + if (index < foundReplacements.length) { + // Utiliser le remplacement correspondant par ordre d'apparition + replacementMap[originalValue] = foundReplacements[index]; + console.log(`✅ Mapping ordonné: "${originalValue}" -> "${foundReplacements[index]}"`); } else { - // Si pas de '[', avancer normalement - originalIndex = result.end; - anonymizedIndex += result.end - result.start; + // Fallback si pas assez de remplacements trouvés + const fallbackValue = `[${result.entity_type.toUpperCase()}]`; + replacementMap[originalValue] = fallbackValue; + console.log(`⚠️ Fallback: "${originalValue}" -> "${fallbackValue}"`); } - } - - console.log("🔧 Valeurs de remplacement extraites:", replacementMap); + }); + + console.log("🔧 Mapping final:", replacementMap); return replacementMap; }; diff --git a/app/components/AnonymizationLogic.tsx b/app/components/AnonymizationLogic.tsx index d6ceda8..f6fd2f2 100644 --- a/app/components/AnonymizationLogic.tsx +++ b/app/components/AnonymizationLogic.tsx @@ -111,7 +111,7 @@ export const useAnonymization = ({ end: end, text: detectedText, replacementValue: replacementValues[detectedText] || `[${entity_type}]`, - displayName: replacementValues[detectedText] || `[${entity_type}]`, // Ajouter cette ligne + displayName: replacementValues[detectedText], // CORRECTION: Supprimer le fallback customColor: undefined, }); } diff --git a/app/components/hooks/useContextMenu.ts b/app/components/hooks/useContextMenu.ts index 580b470..49f279d 100644 --- a/app/components/hooks/useContextMenu.ts +++ b/app/components/hooks/useContextMenu.ts @@ -1,6 +1,6 @@ -import { useState, useCallback, useEffect } from "react"; +import { useState, useCallback, useEffect, useMemo, useRef } from "react"; import { EntityMapping } from "@/app/config/entityLabels"; -import { Word } from "./useTextParsing"; // AJOUTER cet import +import { Word } from "./useTextParsing"; interface ContextMenuState { visible: boolean; @@ -12,7 +12,7 @@ interface ContextMenuState { interface UseContextMenuProps { entityMappings: EntityMapping[]; - words: Word[]; // Maintenant le type Word est reconnu + words: Word[]; onUpdateMapping: ( originalValue: string, newLabel: string, @@ -29,7 +29,7 @@ interface UseContextMenuProps { export const useContextMenu = ({ entityMappings, - words, // Paramètre ajouté + words, onUpdateMapping, onRemoveMapping, getCurrentColor, @@ -43,6 +43,10 @@ export const useContextMenu = ({ wordIndices: [], }); + // Référence pour tracker les mappings précédents + const previousMappingsRef = useRef([]); + const previousLabelsRef = useRef([]); + const closeContextMenu = useCallback(() => { setContextMenu((prev) => ({ ...prev, visible: false })); }, []); @@ -54,15 +58,105 @@ export const useContextMenu = ({ [] ); - const getExistingLabels = useCallback(() => { + // OPTIMISATION INTELLIGENTE: Ne log que les changements + const existingLabels = useMemo(() => { const uniqueLabels = new Set(); - entityMappings.forEach((mapping) => { - uniqueLabels.add(mapping.displayName || mapping.entity_type); // Utiliser displayName + const newMappings: EntityMapping[] = []; + const changedMappings: EntityMapping[] = []; + const removedMappings: EntityMapping[] = []; + + // Détecter les changements + const previousMap = new Map(previousMappingsRef.current.map(m => [m.text, m])); + const currentMap = new Map(entityMappings.map(m => [m.text, m])); + + // Nouveaux mappings + entityMappings.forEach(mapping => { + if (!previousMap.has(mapping.text)) { + newMappings.push(mapping); + } else { + const previous = previousMap.get(mapping.text)!; + if (JSON.stringify(previous) !== JSON.stringify(mapping)) { + changedMappings.push(mapping); + } + } }); - return Array.from(uniqueLabels).sort(); + + // Mappings supprimés + previousMappingsRef.current.forEach(mapping => { + if (!currentMap.has(mapping.text)) { + removedMappings.push(mapping); + } + }); + + // Logger seulement les changements + if (newMappings.length > 0) { + console.log("🆕 Nouveaux mappings détectés:", newMappings.length); + newMappings.forEach(mapping => { + console.log("📋 Nouveau mapping:", { + text: mapping.text, + displayName: mapping.displayName, + entity_type: mapping.entity_type, + }); + }); + } + + if (changedMappings.length > 0) { + console.log("🔄 Mappings modifiés:", changedMappings.length); + changedMappings.forEach(mapping => { + console.log("📝 Mapping modifié:", { + text: mapping.text, + displayName: mapping.displayName, + entity_type: mapping.entity_type, + }); + }); + } + + if (removedMappings.length > 0) { + console.log("🗑️ Mappings supprimés:", removedMappings.length); + removedMappings.forEach(mapping => { + console.log("❌ Mapping supprimé:", { + text: mapping.text, + displayName: mapping.displayName, + }); + }); + } + + // Traitement de tous les mappings pour les labels + entityMappings.forEach((mapping) => { + if ( + mapping.displayName && + typeof mapping.displayName === "string" && + mapping.displayName.startsWith("[") && + mapping.displayName.endsWith("]") && + mapping.displayName.length > 2 + ) { + uniqueLabels.add(mapping.displayName); + } + }); + + const result = Array.from(uniqueLabels).sort(); + + // Logger seulement si les labels ont changé + const previousLabels = previousLabelsRef.current; + if (JSON.stringify(previousLabels) !== JSON.stringify(result)) { + console.log("🎯 Labels mis à jour:", { + ajoutés: result.filter(l => !previousLabels.includes(l)), + supprimés: previousLabels.filter(l => !result.includes(l)), + total: result.length + }); + } + + // Mettre à jour les références + previousMappingsRef.current = [...entityMappings]; + previousLabelsRef.current = [...result]; + + return result; }, [entityMappings]); - // CORRECTION: Accepter displayName comme premier paramètre + const getExistingLabels = useCallback(() => { + return existingLabels; + }, [existingLabels]); + const applyLabel = useCallback( (displayName: string, applyToAll?: boolean) => { if (!contextMenu.selectedText) return; @@ -70,7 +164,6 @@ export const useContextMenu = ({ const originalText = contextMenu.selectedText; const firstWordIndex = contextMenu.wordIndices[0]; - // Calculer les vraies coordonnées start/end du mot cliqué const clickedWord = words[firstWordIndex]; const wordStart = clickedWord?.start; const wordEnd = clickedWord?.end; @@ -82,14 +175,21 @@ export const useContextMenu = ({ existingMapping?.entity_type || displayName.replace(/[\[\]]/g, "").toUpperCase(); + console.log("🏷️ Application de label:", { + text: originalText, + label: displayName, + entityType, + applyToAll + }); + onUpdateMapping( originalText, displayName, entityType, applyToAll, - undefined, // customColor - wordStart, // vraies coordonnées start - wordEnd // vraies coordonnées end + undefined, + wordStart, + wordEnd ); setSelectedWords(new Set()); @@ -97,7 +197,7 @@ export const useContextMenu = ({ }, [ contextMenu, - words, // NOUVEAU + words, entityMappings, onUpdateMapping, closeContextMenu, @@ -105,7 +205,6 @@ export const useContextMenu = ({ ] ); - // CORRECTION: Accepter applyToAll comme paramètre const applyColorDirectly = useCallback( (color: string, colorName: string, applyToAll?: boolean) => { if (!contextMenu.selectedText) return; @@ -114,17 +213,18 @@ export const useContextMenu = ({ (mapping) => mapping.text === contextMenu.selectedText ); - console.log("useContextMenu - applyColorDirectly:", { + console.log("🎨 Application de couleur:", { + text: contextMenu.selectedText, color, colorName, applyToAll, - existingMapping, + existingMapping: !!existingMapping, }); if (existingMapping) { onUpdateMapping( contextMenu.selectedText, - existingMapping.displayName || existingMapping.entity_type, // Utiliser displayName + existingMapping.displayName || existingMapping.entity_type, existingMapping.entity_type, applyToAll, color @@ -144,20 +244,19 @@ export const useContextMenu = ({ }, [ contextMenu.selectedText, - entityMappings, // Ajouter cette dépendance + entityMappings, onUpdateMapping, closeContextMenu, setSelectedWords, ] ); - // CORRECTION: Accepter applyToAll comme paramètre const removeLabel = useCallback( (applyToAll?: boolean) => { if (!contextMenu.selectedText || !onRemoveMapping) return; - console.log("useContextMenu - removeLabel:", { - selectedText: contextMenu.selectedText, + console.log("🗑️ Suppression de label:", { + text: contextMenu.selectedText, applyToAll, }); @@ -173,7 +272,6 @@ export const useContextMenu = ({ ] ); - // Gestion des clics en dehors du menu useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (contextMenu.visible) {