This commit is contained in:
Nicolas Cantu 2025-09-16 06:15:55 +02:00
parent 328d2584de
commit b18a3077a2
8 changed files with 1402 additions and 263 deletions

264
backend/cniOcrEnhancer.js Normal file
View File

@ -0,0 +1,264 @@
const sharp = require('sharp')
const path = require('path')
const fs = require('fs')
/**
* Améliorateur OCR spécialisé pour les Cartes Nationales d'Identité françaises
* Utilise des techniques avancées de preprocessing et de segmentation
*/
// Fonction pour détecter si une image est probablement une CNI
async function isCNIDocument(inputPath) {
try {
const image = sharp(inputPath)
const metadata = await image.metadata()
// Vérifications pour une CNI française
const isPortrait = metadata.height > metadata.width
const hasGoodResolution = metadata.width > 800 && metadata.height > 500
const aspectRatio = metadata.width / metadata.height
const isCNIRatio = aspectRatio > 0.6 && aspectRatio < 0.7 // Ratio typique d'une CNI
console.log(`[CNI_DETECT] ${path.basename(inputPath)} - Portrait: ${isPortrait}, Résolution: ${metadata.width}x${metadata.height}, Ratio: ${aspectRatio.toFixed(2)}`)
return isPortrait && hasGoodResolution && isCNIRatio
} catch (error) {
console.error(`[CNI_DETECT] Erreur détection CNI:`, error.message)
return false
}
}
// Fonction pour extraire la zone MRZ (Machine Readable Zone) d'une CNI
async function extractMRZ(inputPath) {
try {
const image = sharp(inputPath)
const metadata = await image.metadata()
// La MRZ est généralement en bas de la CNI
const mrzHeight = Math.floor(metadata.height * 0.15) // 15% de la hauteur
const mrzTop = metadata.height - mrzHeight
console.log(`[MRZ] Extraction de la zone MRZ: ${mrzTop},${mrzHeight}`)
const mrzImage = await image
.extract({
left: 0,
top: mrzTop,
width: metadata.width,
height: mrzHeight
})
.grayscale()
.normalize()
.sharpen()
.threshold(128)
.png()
.toBuffer()
return mrzImage
} catch (error) {
console.error(`[MRZ] Erreur extraction MRZ:`, error.message)
return null
}
}
// Fonction pour segmenter une CNI en zones spécifiques
async function segmentCNIZones(inputPath) {
try {
const image = sharp(inputPath)
const metadata = await image.metadata()
const zones = {
// Zone du nom (généralement en haut à gauche)
nameZone: {
left: Math.floor(metadata.width * 0.05),
top: Math.floor(metadata.height * 0.25),
width: Math.floor(metadata.width * 0.4),
height: Math.floor(metadata.height * 0.15)
},
// Zone du prénom
firstNameZone: {
left: Math.floor(metadata.width * 0.05),
top: Math.floor(metadata.height * 0.35),
width: Math.floor(metadata.width * 0.4),
height: Math.floor(metadata.height * 0.15)
},
// Zone de la date de naissance
birthDateZone: {
left: Math.floor(metadata.width * 0.05),
top: Math.floor(metadata.height * 0.45),
width: Math.floor(metadata.width * 0.3),
height: Math.floor(metadata.height * 0.1)
},
// Zone du numéro de CNI
idNumberZone: {
left: Math.floor(metadata.width * 0.05),
top: Math.floor(metadata.height * 0.55),
width: Math.floor(metadata.width * 0.4),
height: Math.floor(metadata.height * 0.1)
}
}
console.log(`[CNI_SEGMENT] Segmentation en ${Object.keys(zones).length} zones`)
return zones
} catch (error) {
console.error(`[CNI_SEGMENT] Erreur segmentation:`, error.message)
return null
}
}
// Fonction pour extraire une zone spécifique d'une CNI
async function extractCNIZone(inputPath, zone, zoneName) {
try {
const image = sharp(inputPath)
const zoneImage = await image
.extract(zone)
.grayscale()
.normalize()
.sharpen({ sigma: 2, m1: 0.5, m2: 2, x1: 2, y2: 10 })
.modulate({ brightness: 1.2, contrast: 1.5 })
.threshold(140)
.png()
.toBuffer()
console.log(`[CNI_ZONE] Zone ${zoneName} extraite: ${zone.width}x${zone.height}`)
return zoneImage
} catch (error) {
console.error(`[CNI_ZONE] Erreur extraction zone ${zoneName}:`, error.message)
return null
}
}
// Fonction pour améliorer le preprocessing spécifiquement pour les CNI
async function enhanceCNIPreprocessing(inputPath) {
try {
console.log(`[CNI_ENHANCE] Amélioration CNI pour: ${path.basename(inputPath)}`)
const image = sharp(inputPath)
const metadata = await image.metadata()
// Configuration spécialisée pour les CNI
const enhancedImage = await image
.resize({
width: 2000,
height: Math.floor(2000 * (metadata.height / metadata.width)),
fit: 'fill',
kernel: sharp.kernel.lanczos3
})
.grayscale()
.normalize()
.modulate({
brightness: 1.3,
contrast: 1.8,
saturation: 0
})
.sharpen({
sigma: 1.5,
m1: 0.5,
m2: 3,
x1: 2,
y2: 20
})
.median(3)
.threshold(135)
.png()
.toBuffer()
console.log(`[CNI_ENHANCE] Image améliorée: ${enhancedImage.length} bytes`)
return enhancedImage
} catch (error) {
console.error(`[CNI_ENHANCE] Erreur amélioration CNI:`, error.message)
return null
}
}
// Fonction pour traiter une CNI avec segmentation par zones
async function processCNIWithZones(inputPath) {
try {
console.log(`[CNI_PROCESS] Traitement CNI par zones: ${path.basename(inputPath)}`)
// Vérifier si c'est une CNI
const isCNI = await isCNIDocument(inputPath)
if (!isCNI) {
console.log(`[CNI_PROCESS] Document non reconnu comme CNI`)
return null
}
// Segmenter en zones
const zones = await segmentCNIZones(inputPath)
if (!zones) {
console.log(`[CNI_PROCESS] Échec de la segmentation`)
return null
}
const results = {
isCNI: true,
zones: {},
mrz: null
}
// Extraire chaque zone
for (const [zoneName, zone] of Object.entries(zones)) {
const zoneImage = await extractCNIZone(inputPath, zone, zoneName)
if (zoneImage) {
results.zones[zoneName] = zoneImage
}
}
// Extraire la MRZ
const mrzImage = await extractMRZ(inputPath)
if (mrzImage) {
results.mrz = mrzImage
}
console.log(`[CNI_PROCESS] CNI traitée: ${Object.keys(results.zones).length} zones + MRZ`)
return results
} catch (error) {
console.error(`[CNI_PROCESS] Erreur traitement CNI:`, error.message)
return null
}
}
// Fonction pour décoder la MRZ (Machine Readable Zone)
function decodeMRZ(mrzText) {
try {
if (!mrzText || mrzText.length < 88) {
return null
}
// Format MRZ de la CNI française (2 lignes de 36 caractères)
const lines = mrzText.split('\n').filter(line => line.trim().length > 0)
if (lines.length < 2) {
return null
}
const line1 = lines[0].trim()
const line2 = lines[1].trim()
const result = {
documentType: line1.substring(0, 2),
country: line1.substring(2, 5),
surname: line1.substring(5, 36).replace(/</g, ' ').trim(),
givenNames: line2.substring(0, 30).replace(/</g, ' ').trim(),
documentNumber: line2.substring(30, 36).trim()
}
console.log(`[MRZ_DECODE] MRZ décodée: ${result.surname} ${result.givenNames}`)
return result
} catch (error) {
console.error(`[MRZ_DECODE] Erreur décodage MRZ:`, error.message)
return null
}
}
module.exports = {
isCNIDocument,
extractMRZ,
segmentCNIZones,
extractCNIZone,
enhanceCNIPreprocessing,
processCNIWithZones,
decodeMRZ
}

248
backend/enhancedOcr.js Normal file
View File

@ -0,0 +1,248 @@
const sharp = require('sharp')
const path = require('path')
const fs = require('fs')
const { execSync } = require('child_process')
const {
isCNIDocument,
enhanceCNIPreprocessing,
processCNIWithZones,
decodeMRZ
} = require('./cniOcrEnhancer')
/**
* OCR amélioré avec support spécialisé pour les CNI
* Combine Tesseract avec des techniques de preprocessing avancées
*/
// Fonction pour exécuter Tesseract avec des paramètres optimisés
async function runTesseractOCR(imageBuffer, options = {}) {
try {
const tempInput = path.join(__dirname, 'temp_input.png')
const tempOutput = path.join(__dirname, 'temp_output')
// Sauvegarder l'image temporaire
fs.writeFileSync(tempInput, imageBuffer)
// Paramètres Tesseract optimisés
const tesseractOptions = {
language: options.language || 'fra',
psm: options.psm || '6', // Mode uniforme de bloc de texte
oem: options.oem || '3', // Mode par défaut
...options
}
// Construire la commande Tesseract
const cmd = `tesseract "${tempInput}" "${tempOutput}" -l ${tesseractOptions.language} --psm ${tesseractOptions.psm} --oem ${tesseractOptions.oem}`
console.log(`[TESSERACT] Commande: ${cmd}`)
// Exécuter Tesseract
execSync(cmd, { stdio: 'pipe' })
// Lire le résultat
const resultText = fs.readFileSync(`${tempOutput}.txt`, 'utf8')
// Nettoyer les fichiers temporaires
try {
fs.unlinkSync(tempInput)
fs.unlinkSync(`${tempOutput}.txt`)
} catch (cleanupError) {
console.warn(`[TESSERACT] Erreur nettoyage: ${cleanupError.message}`)
}
console.log(`[TESSERACT] Texte extrait: ${resultText.length} caractères`)
return {
text: resultText.trim(),
confidence: 0.8, // Estimation
method: 'tesseract_enhanced'
}
} catch (error) {
console.error(`[TESSERACT] Erreur OCR:`, error.message)
throw error
}
}
// Fonction pour extraire le texte d'une image avec améliorations CNI
async function extractTextFromImageEnhanced(inputPath) {
try {
console.log(`[ENHANCED_OCR] Début extraction: ${path.basename(inputPath)}`)
// Vérifier si c'est une CNI
const isCNI = await isCNIDocument(inputPath)
console.log(`[ENHANCED_OCR] CNI détectée: ${isCNI}`)
if (isCNI) {
return await extractTextFromCNI(inputPath)
} else {
return await extractTextFromStandardDocument(inputPath)
}
} catch (error) {
console.error(`[ENHANCED_OCR] Erreur extraction:`, error.message)
throw error
}
}
// Fonction spécialisée pour l'extraction de texte des CNI
async function extractTextFromCNI(inputPath) {
try {
console.log(`[CNI_OCR] Traitement CNI: ${path.basename(inputPath)}`)
// Améliorer le preprocessing pour les CNI
const enhancedImage = await enhanceCNIPreprocessing(inputPath)
if (!enhancedImage) {
throw new Error('Échec du preprocessing CNI')
}
// Traitement par zones
const cniZones = await processCNIWithZones(inputPath)
let combinedText = ''
let mrzData = null
// Extraire le texte de l'image améliorée
const mainText = await runTesseractOCR(enhancedImage, {
language: 'fra',
psm: '6' // Mode uniforme de bloc de texte
})
combinedText += mainText.text + '\n'
// Si on a des zones, traiter chaque zone séparément
if (cniZones && cniZones.zones) {
for (const [zoneName, zoneImage] of Object.entries(cniZones.zones)) {
try {
const zoneText = await runTesseractOCR(zoneImage, {
language: 'fra',
psm: '8' // Mode mot unique
})
combinedText += `[${zoneName.toUpperCase()}] ${zoneText.text}\n`
console.log(`[CNI_OCR] Zone ${zoneName}: ${zoneText.text}`)
} catch (zoneError) {
console.warn(`[CNI_OCR] Erreur zone ${zoneName}:`, zoneError.message)
}
}
}
// Traiter la MRZ si disponible
if (cniZones && cniZones.mrz) {
try {
const mrzText = await runTesseractOCR(cniZones.mrz, {
language: 'eng', // La MRZ est en anglais
psm: '8' // Mode mot unique
})
combinedText += `[MRZ] ${mrzText.text}\n`
// Décoder la MRZ
mrzData = decodeMRZ(mrzText.text)
if (mrzData) {
combinedText += `[MRZ_DECODED] Nom: ${mrzData.surname}, Prénom: ${mrzData.givenNames}, Numéro: ${mrzData.documentNumber}\n`
}
} catch (mrzError) {
console.warn(`[CNI_OCR] Erreur MRZ:`, mrzError.message)
}
}
// Post-traitement du texte pour corriger les erreurs communes
const processedText = postProcessCNIText(combinedText)
console.log(`[CNI_OCR] Texte final: ${processedText.length} caractères`)
return {
text: processedText,
confidence: 0.85, // Confiance élevée pour les CNI traitées
method: 'cni_enhanced',
mrzData: mrzData,
zones: cniZones ? Object.keys(cniZones.zones || {}) : []
}
} catch (error) {
console.error(`[CNI_OCR] Erreur traitement CNI:`, error.message)
throw error
}
}
// Fonction pour l'extraction de texte des documents standards
async function extractTextFromStandardDocument(inputPath) {
try {
console.log(`[STANDARD_OCR] Traitement document standard: ${path.basename(inputPath)}`)
// Preprocessing standard
const image = sharp(inputPath)
const metadata = await image.metadata()
const processedImage = await image
.resize({
width: Math.min(metadata.width * 2, 2000),
height: Math.min(metadata.height * 2, 2000),
fit: 'inside',
withoutEnlargement: false
})
.grayscale()
.normalize()
.sharpen()
.png()
.toBuffer()
// OCR standard
const result = await runTesseractOCR(processedImage, {
language: 'fra',
psm: '6'
})
return {
text: result.text,
confidence: result.confidence,
method: 'standard_enhanced'
}
} catch (error) {
console.error(`[STANDARD_OCR] Erreur traitement standard:`, error.message)
throw error
}
}
// Fonction pour post-traiter le texte des CNI
function postProcessCNIText(text) {
try {
let processedText = text
// Corrections communes pour les CNI
const corrections = [
// Corrections de caractères corrompus
{ from: /RÉPUBLIQUE FRANCATSEN/g, to: 'RÉPUBLIQUE FRANÇAISE' },
{ from: /CARTE NATIONALE DIDENTITE/g, to: 'CARTE NATIONALE D\'IDENTITÉ' },
{ from: /Ne :/g, to: 'N° :' },
{ from: /Fe - 0/g, to: 'Féminin' },
{ from: /Mele:/g, to: 'Mâle:' },
{ from: /IDFRACANTUCCKKLLLLKLLLLLLLLLLLK/g, to: 'IDFRA' },
// Nettoyage des caractères parasites
{ from: /[^\w\sÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ.,;:!?()\-'"]/g, to: ' ' },
// Normalisation des espaces
{ from: /\s+/g, to: ' ' },
{ from: /^\s+|\s+$/g, to: '' }
]
// Appliquer les corrections
for (const correction of corrections) {
processedText = processedText.replace(correction.from, correction.to)
}
console.log(`[POST_PROCESS] Texte post-traité: ${processedText.length} caractères`)
return processedText
} catch (error) {
console.error(`[POST_PROCESS] Erreur post-traitement:`, error.message)
return text
}
}
module.exports = {
extractTextFromImageEnhanced,
extractTextFromCNI,
extractTextFromStandardDocument,
runTesseractOCR,
postProcessCNIText
}

View File

@ -224,7 +224,7 @@ function getMimeTypeFromExtension(extension) {
function listFolderResults(folderHash) {
const cachePath = path.join('cache', folderHash)
const uploadsPath = path.join('uploads', folderHash)
const results = []
const pending = []
let hasPending = false
@ -232,7 +232,7 @@ function listFolderResults(folderHash) {
// Traiter les fichiers en cache (avec résultats d'extraction)
if (fs.existsSync(cachePath)) {
const cacheFiles = fs.readdirSync(cachePath)
for (const file of cacheFiles) {
if (file.endsWith('.json')) {
const fileHash = path.basename(file, '.json')
@ -260,23 +260,23 @@ function listFolderResults(folderHash) {
// Traiter les fichiers en uploads (sans résultats d'extraction)
if (fs.existsSync(uploadsPath)) {
const uploadFiles = fs.readdirSync(uploadsPath)
for (const file of uploadFiles) {
// Extraire le hash du nom de fichier (format: hash.extension)
const fileHash = path.basename(file, path.extname(file))
// Vérifier si ce fichier n'a pas déjà un résultat en cache
const hasCacheResult = results.some(result => result.fileHash === fileHash)
// Vérifier si ce fichier n'est pas déjà en pending
const isAlreadyPending = pending.some(p => p.fileHash === fileHash)
if (!hasCacheResult && !isAlreadyPending) {
// Mettre le fichier en pending et le traiter automatiquement
const filePath = path.join(uploadsPath, file)
const stats = fs.statSync(filePath)
console.log(`[FOLDER] Fichier non traité détecté, mise en pending: ${file}`)
// Créer le flag pending
const pendingData = {
fileHash,
@ -285,14 +285,14 @@ function listFolderResults(folderHash) {
timestamp: new Date().toISOString(),
status: 'processing'
}
// Sauvegarder le flag pending
createPendingFlag(folderHash, fileHash, pendingData)
// Ajouter à la liste des pending
pending.push(pendingData)
hasPending = true
// Traiter le fichier en arrière-plan
processFileInBackground(filePath, fileHash, folderHash)
}
@ -305,15 +305,15 @@ function listFolderResults(folderHash) {
// Fonction pour traiter un document (extraction de la logique de /api/extract)
async function processDocument(filePath, fileHash) {
const startTime = Date.now()
try {
console.log(`[PROCESS] Début du traitement: ${filePath}`)
// Obtenir les informations du fichier
const stats = fs.statSync(filePath)
const ext = path.extname(filePath)
const mimeType = getMimeTypeFromExtension(ext)
// Créer un objet file similaire à celui de multer
const file = {
path: filePath,
@ -321,10 +321,10 @@ async function processDocument(filePath, fileHash) {
mimetype: mimeType,
size: stats.size
}
let ocrResult
let result
// Si c'est un PDF, extraire le texte directement
if (mimeType === 'application/pdf') {
console.log(`[PROCESS] Extraction de texte depuis PDF...`)
@ -336,8 +336,9 @@ async function processDocument(filePath, fileHash) {
throw new Error(`Erreur lors de l'extraction PDF: ${error.message}`)
}
} else {
// Pour les images, utiliser l'OCR avec préprocessing
ocrResult = await extractTextFromImage(filePath)
// Pour les images, utiliser l'OCR amélioré avec détection CNI
const { extractTextFromImageEnhanced } = require('./enhancedOcr')
ocrResult = await extractTextFromImageEnhanced(filePath)
}
// Extraction NER
@ -348,10 +349,10 @@ async function processDocument(filePath, fileHash) {
// Génération du format JSON standard
result = generateStandardJSON(file, ocrResult, entities, processingTime)
console.log(`[PROCESS] Traitement terminé en ${processingTime}ms`)
return result
} catch (error) {
console.error(`[PROCESS] Erreur lors du traitement:`, error)
throw error
@ -362,13 +363,13 @@ async function processDocument(filePath, fileHash) {
async function processFileInBackground(filePath, fileHash, folderHash) {
try {
console.log(`[BACKGROUND] Début du traitement en arrière-plan: ${filePath}`)
// Traiter le document
const result = await processDocument(filePath, fileHash)
// Sauvegarder le résultat dans le cache du dossier
const success = saveJsonCacheInFolder(folderHash, fileHash, result)
if (success) {
// Supprimer le flag pending
removePendingFlag(folderHash, fileHash)
@ -376,7 +377,7 @@ async function processFileInBackground(filePath, fileHash, folderHash) {
} else {
console.error(`[BACKGROUND] Erreur lors de la sauvegarde du résultat: ${fileHash}`)
}
} catch (error) {
console.error(`[BACKGROUND] Erreur lors du traitement en arrière-plan:`, error)
// Supprimer le flag pending même en cas d'erreur
@ -389,7 +390,7 @@ function removePendingFlag(folderHash, fileHash) {
try {
const cachePath = path.join('cache', folderHash)
const pendingFile = path.join(cachePath, `${fileHash}.pending`)
if (fs.existsSync(pendingFile)) {
fs.unlinkSync(pendingFile)
console.log(`[PENDING] Flag pending supprimé: ${fileHash}`)
@ -1184,8 +1185,9 @@ app.post('/api/extract', upload.single('document'), async (req, res) => {
throw new Error(`Erreur lors de l'extraction PDF: ${error.message}`)
}
} else {
// Pour les images, utiliser l'OCR avec préprocessing
ocrResult = await extractTextFromImage(req.file.path)
// Pour les images, utiliser l'OCR amélioré avec détection CNI
const { extractTextFromImageEnhanced } = require('./enhancedOcr')
ocrResult = await extractTextFromImageEnhanced(req.file.path)
}
// Extraction NER
@ -1560,86 +1562,6 @@ app.get('/api/health', (req, res) => {
})
})
// Vider le cache d'un dossier
app.delete('/api/folders/:folderHash/cache', (req, res) => {
const { folderHash } = req.params
console.log(`[CACHE] Demande de suppression du cache pour le dossier: ${folderHash}`)
try {
const { cachePath, uploadsPath } = createFolderStructure(folderHash)
let deletedFiles = 0
let deletedDirs = 0
// Supprimer le dossier cache s'il existe
if (fs.existsSync(cachePath)) {
const files = fs.readdirSync(cachePath)
for (const file of files) {
const filePath = path.join(cachePath, file)
try {
fs.unlinkSync(filePath)
deletedFiles++
console.log(`[CACHE] Fichier supprimé: ${file}`)
} catch (error) {
console.error(`[CACHE] Erreur lors de la suppression de ${file}:`, error.message)
}
}
// Supprimer le dossier cache vide
try {
fs.rmdirSync(cachePath)
deletedDirs++
console.log(`[CACHE] Dossier cache supprimé: ${cachePath}`)
} catch (error) {
console.error(`[CACHE] Erreur lors de la suppression du dossier cache:`, error.message)
}
}
// Supprimer le dossier uploads s'il existe
if (fs.existsSync(uploadsPath)) {
const files = fs.readdirSync(uploadsPath)
for (const file of files) {
const filePath = path.join(uploadsPath, file)
try {
fs.unlinkSync(filePath)
deletedFiles++
console.log(`[CACHE] Fichier upload supprimé: ${file}`)
} catch (error) {
console.error(`[CACHE] Erreur lors de la suppression de ${file}:`, error.message)
}
}
// Supprimer le dossier uploads vide
try {
fs.rmdirSync(uploadsPath)
deletedDirs++
console.log(`[CACHE] Dossier uploads supprimé: ${uploadsPath}`)
} catch (error) {
console.error(`[CACHE] Erreur lors de la suppression du dossier uploads:`, error.message)
}
}
console.log(`[CACHE] Cache vidé pour le dossier ${folderHash}: ${deletedFiles} fichiers, ${deletedDirs} dossiers supprimés`)
res.json({
success: true,
message: `Cache vidé pour le dossier ${folderHash}`,
deletedFiles,
deletedDirs,
folderHash
})
} catch (error) {
console.error(`[CACHE] Erreur lors du vidage du cache:`, error)
res.status(500).json({
success: false,
error: 'Erreur lors du vidage du cache',
details: error.message
})
}
})
// Démarrage du serveur
app.listen(PORT, () => {
console.log(`🚀 Serveur backend démarré sur le port ${PORT}`)

937
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,8 @@
"@mui/material": "^7.3.2",
"@reduxjs/toolkit": "^2.9.0",
"axios": "^1.11.0",
"canvas": "^3.2.0",
"jimp": "^1.6.0",
"pdf-parse": "^1.1.1",
"pdf-poppler": "^0.2.1",
"pdf2pic": "^3.2.0",

View File

@ -166,31 +166,15 @@ export async function uploadFileToFolder(file: File, folderHash: string): Promis
const formData = new FormData()
formData.append('document', file)
formData.append('folderHash', folderHash)
const response = await fetch(`${API_BASE_URL}/extract`, {
method: 'POST',
body: formData,
})
if (!response.ok) {
throw new Error(`Erreur lors de l'upload: ${response.statusText}`)
}
return response.json()
}
// Vider le cache d'un dossier
export async function clearFolderCache(folderHash: string): Promise<{ success: boolean; message: string; deletedFiles: number; deletedDirs: number }> {
const response = await fetch(`${API_BASE_URL}/folders/${folderHash}/cache`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
throw new Error(`Erreur lors du vidage du cache: ${response.statusText}`)
}
return response.json()
}

View File

@ -4,7 +4,7 @@ import type { Document, ExtractionResult, AnalysisResult, ContextResult, Conseil
import { documentApi } from '../services/api'
import { openaiDocumentApi } from '../services/openai'
import { backendDocumentApi, checkBackendHealth } from '../services/backendApi'
import { createDefaultFolder, getDefaultFolder, getFolderResults, uploadFileToFolder, clearFolderCache, type FolderResult } from '../services/folderApi'
import { createDefaultFolder, getDefaultFolder, getFolderResults, uploadFileToFolder, type FolderResult } from '../services/folderApi'
interface DocumentState {
documents: Document[]
@ -214,13 +214,6 @@ export const uploadFileToFolderThunk = createAsyncThunk(
}
)
export const clearFolderCacheThunk = createAsyncThunk(
'document/clearFolderCache',
async (folderHash: string) => {
return await clearFolderCache(folderHash)
}
)
const documentSlice = createSlice({
name: 'document',
initialState,
@ -387,7 +380,7 @@ const documentSlice = createSlice({
.addCase(loadFolderResults.fulfilled, (state, action) => {
console.log(`[STORE] loadFolderResults.fulfilled appelé avec:`, action.payload)
console.log(`[STORE] Nombre de résultats reçus:`, action.payload.results?.length || 0)
state.folderResults = action.payload.results
state.currentFolderHash = action.payload.folderHash
state.loading = false
@ -404,7 +397,7 @@ const documentSlice = createSlice({
fileName: result.document?.fileName,
mimeType: result.document?.mimeType
})
return {
id: result.fileHash,
name: result.document.fileName,
@ -443,21 +436,6 @@ const documentSlice = createSlice({
state.loading = false
state.error = action.error.message || 'Erreur lors de l\'upload du fichier'
})
.addCase(clearFolderCacheThunk.fulfilled, (state, action) => {
console.log(`[STORE] Cache vidé: ${action.payload.deletedFiles} fichiers, ${action.payload.deletedDirs} dossiers supprimés`)
// Vider les résultats du dossier actuel
state.folderResults = []
state.documents = []
state.currentResultIndex = 0
state.loading = false
})
.addCase(clearFolderCacheThunk.pending, (state) => {
state.loading = true
})
.addCase(clearFolderCacheThunk.rejected, (state, action) => {
state.loading = false
state.error = action.error.message || 'Erreur lors du vidage du cache'
})
},
})

View File

@ -33,11 +33,10 @@ import {
PictureAsPdf,
FolderOpen,
Add,
ContentCopy,
Delete
ContentCopy
} from '@mui/icons-material'
import { useAppDispatch, useAppSelector } from '../store'
import { uploadFileToFolderThunk, loadFolderResults, removeDocument, createDefaultFolderThunk, setCurrentFolderHash, clearFolderCacheThunk } from '../store/documentSlice'
import { uploadFileToFolderThunk, loadFolderResults, removeDocument, createDefaultFolderThunk, setCurrentFolderHash } from '../store/documentSlice'
import { Layout } from '../components/Layout'
import { FilePreview } from '../components/FilePreview'
import type { Document } from '../types'
@ -85,22 +84,6 @@ export default function UploadView() {
}
}, [currentFolderHash])
// Fonction pour vider le cache du dossier
const handleClearCache = useCallback(async () => {
if (!currentFolderHash) return
if (window.confirm('Êtes-vous sûr de vouloir vider le cache de ce dossier ? Cette action supprimera tous les fichiers et résultats d\'extraction.')) {
try {
const result = await dispatch(clearFolderCacheThunk(currentFolderHash)).unwrap()
console.log('✅ [UPLOAD] Cache vidé:', result)
// Recharger les résultats du dossier (qui seront vides)
await dispatch(loadFolderResults(currentFolderHash)).unwrap()
} catch (error) {
console.error('❌ [UPLOAD] Erreur lors du vidage du cache:', error)
}
}
}, [dispatch, currentFolderHash])
const onDrop = useCallback(
async (acceptedFiles: File[]) => {
if (!currentFolderHash) {
@ -229,17 +212,6 @@ export default function UploadView() {
>
Charger dossier
</Button>
{currentFolderHash && (
<Button
variant="outlined"
startIcon={<Delete />}
onClick={handleClearCache}
size="small"
color="error"
>
Vider le cache
</Button>
)}
</Box>
</Box>
</Box>