refactoring

This commit is contained in:
4NK IA 2025-09-18 17:05:42 +00:00
parent 9af63f22fe
commit d5a29d9b04

View File

@ -1,13 +1,13 @@
import React, { useState, useCallback } from 'react' import React, { useState, useCallback } from 'react'
import { import {
Box, Typography, Paper, Card, CardContent, Chip, Button, List, ListItemText, ListItemButton, Box, Typography, Paper, Card, CardContent, Chip, Button, List, ListItemText, ListItemButton,
Tooltip, Alert, Accordion, AccordionSummary, AccordionDetails, CircularProgress, TextField, Tooltip, Alert, Accordion, AccordionSummary, AccordionDetails, CircularProgress, TextField,
Grid, Divider, Badge, Stack, Avatar, CardHeader, Fade, Divider, Badge, Stack, Avatar, CardHeader, Fade,
Snackbar, Alert as MuiAlert Snackbar, Alert as MuiAlert
} from '@mui/material' } from '@mui/material'
import { import {
Person, LocationOn, Business, Description, Language, Verified, ExpandMore, TextFields, Assessment, Person, LocationOn, Business, Description, Language, Verified, ExpandMore, TextFields, Assessment,
Save, Delete, Search, Visibility, Check, Refresh, Save, Delete, Search, Visibility, Check, Refresh,
FileDownload, FileOpen, AutoAwesome, Security FileDownload, FileOpen, AutoAwesome, Security
} from '@mui/icons-material' } from '@mui/icons-material'
import { useAppDispatch, useAppSelector } from '../store' import { useAppDispatch, useAppSelector } from '../store'
@ -67,13 +67,13 @@ export default function ExtractionView() {
const handleSaveEntity = useCallback(async (type: 'person' | 'address' | 'company', index: number, entity: any) => { const handleSaveEntity = useCallback(async (type: 'person' | 'address' | 'company', index: number, entity: any) => {
if (!currentFolderHash || !currentResult) return if (!currentFolderHash || !currentResult) return
try { try {
setSavingKey(`${type}-${index}`) setSavingKey(`${type}-${index}`)
await updateEntity(currentFolderHash, currentResult.fileHash, type, { await updateEntity(currentFolderHash, currentResult.fileHash, type, {
index, index,
id: entity.id, id: entity.id,
patch: entity patch: entity
}) })
showSnackbar(`${type === 'person' ? 'Personne' : type === 'address' ? 'Adresse' : 'Entreprise'} sauvegardée`, 'success') showSnackbar(`${type === 'person' ? 'Personne' : type === 'address' ? 'Adresse' : 'Entreprise'} sauvegardée`, 'success')
} catch (error: any) { } catch (error: any) {
@ -85,11 +85,11 @@ export default function ExtractionView() {
const handleDeleteEntity = useCallback(async (type: 'person' | 'address' | 'company', index: number, entity: any) => { const handleDeleteEntity = useCallback(async (type: 'person' | 'address' | 'company', index: number, entity: any) => {
if (!currentFolderHash || !currentResult) return if (!currentFolderHash || !currentResult) return
try { try {
await deleteEntity(currentFolderHash, currentResult.fileHash, type, { index, id: entity.id }) await deleteEntity(currentFolderHash, currentResult.fileHash, type, { index, id: entity.id })
showSnackbar(`${type === 'person' ? 'Personne' : type === 'address' ? 'Adresse' : 'Entreprise'} supprimée`, 'success') showSnackbar(`${type === 'person' ? 'Personne' : type === 'address' ? 'Adresse' : 'Entreprise'} supprimée`, 'success')
// Mettre à jour les brouillons locaux // Mettre à jour les brouillons locaux
if (type === 'person') { if (type === 'person') {
setPersonsDraft(prev => prev.filter((_, i) => i !== index)) setPersonsDraft(prev => prev.filter((_, i) => i !== index))
@ -105,12 +105,12 @@ export default function ExtractionView() {
const handleEnrichment = useCallback(async (type: 'person' | 'address' | 'company', index: number) => { const handleEnrichment = useCallback(async (type: 'person' | 'address' | 'company', index: number) => {
if (!currentFolderHash || !currentResult) return if (!currentFolderHash || !currentResult) return
try { try {
setEnriching(prev => ({ ...prev, [`${type}-${index}`]: 'running' })) setEnriching(prev => ({ ...prev, [`${type}-${index}`]: 'running' }))
await startEnrichment(currentFolderHash, currentResult.fileHash, type) await startEnrichment(currentFolderHash, currentResult.fileHash, type)
showSnackbar(`Enrichissement ${type} démarré`, 'info') showSnackbar(`Enrichissement ${type} démarré`, 'info')
setTimeout(async () => { setTimeout(async () => {
try { try {
const status = await getEnrichmentStatus(currentFolderHash, currentResult.fileHash, type) const status = await getEnrichmentStatus(currentFolderHash, currentResult.fileHash, type)
@ -172,7 +172,7 @@ export default function ExtractionView() {
Gestion et enrichissement des entités extraites Gestion et enrichissement des entités extraites
</Typography> </Typography>
</Box> </Box>
<Stack direction="row" spacing={2}> <Stack direction="row" spacing={2}>
<Tooltip title="Re-traiter tous les documents du dossier"> <Tooltip title="Re-traiter tous les documents du dossier">
<Button <Button
@ -204,8 +204,8 @@ export default function ExtractionView() {
{/* Sidebar de navigation moderne */} {/* Sidebar de navigation moderne */}
<Box sx={{ flex: '0 0 300px', minWidth: 0 }}> <Box sx={{ flex: '0 0 300px', minWidth: 0 }}>
<Card sx={{ height: 'fit-content', position: 'sticky', top: 20 }}> <Card sx={{ height: 'fit-content', position: 'sticky', top: 20 }}>
<CardHeader <CardHeader
title="Documents" title="Documents"
subheader={`${folderResults.length} fichier${folderResults.length > 1 ? 's' : ''}`} subheader={`${folderResults.length} fichier${folderResults.length > 1 ? 's' : ''}`}
avatar={<Avatar sx={{ bgcolor: 'primary.main' }}><Description /></Avatar>} avatar={<Avatar sx={{ bgcolor: 'primary.main' }}><Description /></Avatar>}
/> />
@ -216,7 +216,7 @@ export default function ExtractionView() {
key={result.fileHash} key={result.fileHash}
selected={index === currentIndex} selected={index === currentIndex}
onClick={() => gotoResult(index)} onClick={() => gotoResult(index)}
sx={{ sx={{
borderLeft: index === currentIndex ? 3 : 0, borderLeft: index === currentIndex ? 3 : 0,
borderLeftColor: 'primary.main', borderLeftColor: 'primary.main',
bgcolor: index === currentIndex ? 'primary.50' : 'transparent' bgcolor: index === currentIndex ? 'primary.50' : 'transparent'
@ -224,9 +224,9 @@ export default function ExtractionView() {
> >
<ListItemText <ListItemText
primary={ primary={
<Typography <Typography
variant="body2" variant="body2"
sx={{ sx={{
fontWeight: index === currentIndex ? 600 : 400, fontWeight: index === currentIndex ? 600 : 400,
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
@ -261,10 +261,10 @@ export default function ExtractionView() {
subheader={`Téléversé le ${new Date(extraction.document.uploadTimestamp as unknown as string).toLocaleString()}`} subheader={`Téléversé le ${new Date(extraction.document.uploadTimestamp as unknown as string).toLocaleString()}`}
action={ action={
<Stack direction="row" spacing={1}> <Stack direction="row" spacing={1}>
<Chip <Chip
label={extraction.document.mimeType} label={extraction.document.mimeType}
size="small" size="small"
variant="outlined" variant="outlined"
color="info" color="info"
/> />
<Chip <Chip
@ -308,40 +308,6 @@ export default function ExtractionView() {
</CardContent> </CardContent>
</Card> </Card>
{/* Texte extrait avec toggle */}
<Card sx={{ mb: 3 }}>
<CardHeader
title="Texte extrait"
avatar={<Avatar sx={{ bgcolor: 'info.main' }}><TextFields /></Avatar>}
action={
<Button
size="small"
startIcon={showTextExtract ? <Visibility /> : <Visibility />}
onClick={() => setShowTextExtract(!showTextExtract)}
>
{showTextExtract ? 'Masquer' : 'Afficher'}
</Button>
}
/>
<Fade in={showTextExtract}>
<CardContent sx={{ pt: 0 }}>
<Paper
sx={{
p: 2,
bgcolor: 'grey.50',
maxHeight: 300,
overflow: 'auto',
border: '1px solid',
borderColor: 'grey.200'
}}
>
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap', fontFamily: 'monospace' }}>
{extraction.extraction.text.raw}
</Typography>
</Paper>
</CardContent>
</Fade>
</Card>
{/* Entités extraites avec design moderne */} {/* Entités extraites avec design moderne */}
<Box sx={{ display: 'flex', gap: 3, flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', gap: 3, flexWrap: 'wrap' }}>
@ -404,18 +370,20 @@ export default function ExtractionView() {
})} })}
/> />
</Box> </Box>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap> <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
startIcon={enriching[`person-${i}`] === 'running' ? <CircularProgress size={16} /> : <Search />} startIcon={enriching[`person-${i}`] === 'running' ? <CircularProgress size={16} /> : <Search />}
disabled={enriching[`person-${i}`] === 'running'} disabled={enriching[`person-${i}`] === 'running'}
onClick={() => handleEnrichment('person', i)} onClick={() => handleEnrichment('person', i)}
> >
{enriching[`person-${i}`] === 'running' ? 'Collecte...' : 'Enrichir'} {enriching[`person-${i}`] === 'running' ? 'Collecte...' :
</Button> enriching[`person-${i}`] === 'completed' ? 'Bodacc ✓' :
enriching[`person-${i}`] === 'error' ? 'Erreur' : 'Enrichir'}
</Button>
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
@ -427,7 +395,7 @@ export default function ExtractionView() {
> >
PDF PDF
</Button> </Button>
<Button <Button
size="small" size="small"
variant="contained" variant="contained"
@ -437,7 +405,7 @@ export default function ExtractionView() {
> >
Sauver Sauver
</Button> </Button>
<Button <Button
size="small" size="small"
color="error" color="error"
@ -453,12 +421,12 @@ export default function ExtractionView() {
</Stack> </Stack>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Box>
)} )}
{/* Adresses */} {/* Adresses */}
{addressesDraft.length > 0 && ( {addressesDraft.length > 0 && (
<Grid item xs={12} lg={4}> <Box sx={{ flex: '1 1 300px', minWidth: 0 }}>
<Card sx={{ height: '100%' }}> <Card sx={{ height: '100%' }}>
<CardHeader <CardHeader
title="Adresses" title="Adresses"
@ -476,21 +444,19 @@ export default function ExtractionView() {
{addressesDraft.map((a: any, i: number) => ( {addressesDraft.map((a: any, i: number) => (
<Paper key={`a-${i}`} sx={{ p: 2, border: '1px solid', borderColor: 'grey.200' }}> <Paper key={`a-${i}`} sx={{ p: 2, border: '1px solid', borderColor: 'grey.200' }}>
<Stack spacing={2}> <Stack spacing={2}>
<Grid container spacing={1}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Grid item xs={12}> <TextField
<TextField size="small"
size="small" fullWidth
fullWidth label="Rue"
label="Rue" value={a.street}
value={a.street} onChange={(e) => setAddressesDraft((prev) => {
onChange={(e) => setAddressesDraft((prev) => { const c = [...prev]
const c = [...prev] c[i] = { ...c[i], street: e.target.value }
c[i] = { ...c[i], street: e.target.value } return c
return c })}
})} />
/> <Box sx={{ display: 'flex', gap: 1 }}>
</Grid>
<Grid item xs={4}>
<TextField <TextField
size="small" size="small"
fullWidth fullWidth
@ -502,8 +468,6 @@ export default function ExtractionView() {
return c return c
})} })}
/> />
</Grid>
<Grid item xs={8}>
<TextField <TextField
size="small" size="small"
fullWidth fullWidth
@ -515,48 +479,46 @@ export default function ExtractionView() {
return c return c
})} })}
/> />
</Grid> </Box>
<Grid item xs={6}> <TextField
<TextField
size="small"
fullWidth
label="Pays"
value={a.country}
onChange={(e) => setAddressesDraft((prev) => {
const c = [...prev]
c[i] = { ...c[i], country: e.target.value }
return c
})}
/>
</Grid>
<Grid item xs={12}>
<TextField
size="small"
fullWidth
label="Description"
multiline
rows={2}
value={a.description}
onChange={(e) => setAddressesDraft((prev) => {
const c = [...prev]
c[i] = { ...c[i], description: e.target.value }
return c
})}
/>
</Grid>
</Grid>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
<Button
size="small" size="small"
variant="outlined" fullWidth
startIcon={enriching[`address-${i}`] === 'running' ? <CircularProgress size={16} /> : <Search />} label="Pays"
disabled={enriching[`address-${i}`] === 'running'} value={a.country}
onClick={() => handleEnrichment('address', i)} onChange={(e) => setAddressesDraft((prev) => {
> const c = [...prev]
{enriching[`address-${i}`] === 'running' ? 'Collecte...' : 'Enrichir'} c[i] = { ...c[i], country: e.target.value }
</Button> return c
})}
/>
<TextField
size="small"
fullWidth
label="Description"
multiline
rows={2}
value={a.description}
onChange={(e) => setAddressesDraft((prev) => {
const c = [...prev]
c[i] = { ...c[i], description: e.target.value }
return c
})}
/>
</Box>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
<Button
size="small"
variant="outlined"
startIcon={enriching[`address-${i}`] === 'running' ? <CircularProgress size={16} /> : <Search />}
disabled={enriching[`address-${i}`] === 'running'}
onClick={() => handleEnrichment('address', i)}
>
{enriching[`address-${i}`] === 'running' ? 'Collecte...' :
enriching[`address-${i}`] === 'completed' ? 'BAN+GéoRisque+Cadastre ✓' :
enriching[`address-${i}`] === 'error' ? 'Erreur' : 'Enrichir'}
</Button>
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
@ -568,7 +530,7 @@ export default function ExtractionView() {
> >
PDF PDF
</Button> </Button>
<Button <Button
size="small" size="small"
variant="contained" variant="contained"
@ -578,7 +540,7 @@ export default function ExtractionView() {
> >
Sauver Sauver
</Button> </Button>
<Button <Button
size="small" size="small"
color="error" color="error"
@ -594,12 +556,12 @@ export default function ExtractionView() {
</Stack> </Stack>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Box>
)} )}
{/* Entreprises */} {/* Entreprises */}
{companiesDraft.length > 0 && ( {companiesDraft.length > 0 && (
<Grid item xs={12} lg={4}> <Box sx={{ flex: '1 1 300px', minWidth: 0 }}>
<Card sx={{ height: '100%' }}> <Card sx={{ height: '100%' }}>
<CardHeader <CardHeader
title="Entreprises" title="Entreprises"
@ -617,8 +579,8 @@ export default function ExtractionView() {
{companiesDraft.map((c: any, i: number) => ( {companiesDraft.map((c: any, i: number) => (
<Paper key={`c-${i}`} sx={{ p: 2, border: '1px solid', borderColor: 'grey.200' }}> <Paper key={`c-${i}`} sx={{ p: 2, border: '1px solid', borderColor: 'grey.200' }}>
<Stack spacing={2}> <Stack spacing={2}>
<Grid container spacing={1}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Grid item xs={12}> <Box>
<TextField <TextField
size="small" size="small"
fullWidth fullWidth
@ -630,8 +592,7 @@ export default function ExtractionView() {
return x return x
})} })}
/> />
</Grid> </Box>
<Grid item xs={12}>
<TextField <TextField
size="small" size="small"
fullWidth fullWidth
@ -645,20 +606,21 @@ export default function ExtractionView() {
return x return x
})} })}
/> />
</Grid> </Box>
</Grid>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap> <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
startIcon={enriching[`company-${i}`] === 'running' ? <CircularProgress size={16} /> : <Search />} startIcon={enriching[`company-${i}`] === 'running' ? <CircularProgress size={16} /> : <Search />}
disabled={enriching[`company-${i}`] === 'running'} disabled={enriching[`company-${i}`] === 'running'}
onClick={() => handleEnrichment('company', i)} onClick={() => handleEnrichment('company', i)}
> >
{enriching[`company-${i}`] === 'running' ? 'Collecte...' : 'Enrichir'} {enriching[`company-${i}`] === 'running' ? 'Collecte...' :
</Button> enriching[`company-${i}`] === 'completed' ? 'Inforgreffe+Societe.com ✓' :
enriching[`company-${i}`] === 'error' ? 'Erreur' : 'Enrichir'}
</Button>
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
@ -670,7 +632,7 @@ export default function ExtractionView() {
> >
PDF PDF
</Button> </Button>
<Button <Button
size="small" size="small"
variant="contained" variant="contained"
@ -680,7 +642,7 @@ export default function ExtractionView() {
> >
Sauver Sauver
</Button> </Button>
<Button <Button
size="small" size="small"
color="error" color="error"
@ -696,65 +658,100 @@ export default function ExtractionView() {
</Stack> </Stack>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Box>
)} )}
</Grid> </Box>
{/* Métadonnées détaillées */} {/* Métadonnées détaillées */}
<Card sx={{ mt: 3 }}> <Card sx={{ mt: 3 }}>
<CardHeader <CardHeader
title="Métadonnées techniques" title="Métadonnées techniques"
avatar={<Avatar sx={{ bgcolor: 'grey.600' }}><Security /></Avatar>} avatar={<Avatar sx={{ bgcolor: 'grey.600' }}><Security /></Avatar>}
/> />
<Divider /> <Divider />
<CardContent> <CardContent>
<Accordion> <Accordion>
<AccordionSummary expandIcon={<ExpandMore />}> <AccordionSummary expandIcon={<ExpandMore />}>
<Typography>Informations de traitement</Typography> <Typography>Informations de traitement</Typography>
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<Grid container spacing={2}> <Box sx={{ display: 'flex', gap: 3, flexWrap: 'wrap' }}>
<Grid item xs={12} sm={6}> <Box sx={{ flex: '1 1 300px' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Hash du fichier Hash du fichier
</Typography> </Typography>
<Typography variant="body2" sx={{ fontFamily: 'monospace', wordBreak: 'break-all' }}> <Typography variant="body2" sx={{ fontFamily: 'monospace', wordBreak: 'break-all' }}>
{extraction.fileHash} {extraction.fileHash}
</Typography> </Typography>
</Grid> </Box>
<Grid item xs={12} sm={6}> <Box sx={{ flex: '1 1 300px' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Traitement effectué Traitement effectué
</Typography> </Typography>
<Typography variant="body2"> <Typography variant="body2">
{new Date(extraction.status.timestamp).toLocaleString()} {new Date(extraction.status.timestamp).toLocaleString()}
</Typography> </Typography>
</Grid> </Box>
<Grid item xs={12} sm={6}> <Box sx={{ flex: '1 1 300px' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Confiance globale Confiance globale
</Typography> </Typography>
<Typography variant="body2"> <Typography variant="body2">
{(extraction.metadata.quality.globalConfidence * 100).toFixed(1)}% {(extraction.metadata.quality.globalConfidence * 100).toFixed(1)}%
</Typography> </Typography>
</Grid> </Box>
{(extraction.metadata.quality as any).ollamaScore && ( {(extraction.metadata.quality as any).ollamaScore && (
<Grid item xs={12} sm={6}> <Box sx={{ flex: '1 1 300px' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Score IA (Ollama) Score IA (Ollama)
</Typography> </Typography>
<Typography variant="body2"> <Typography variant="body2">
{((extraction.metadata.quality as any).ollamaScore * 100).toFixed(1)}% {((extraction.metadata.quality as any).ollamaScore * 100).toFixed(1)}%
</Typography> </Typography>
</Grid> </Box>
)} )}
</Grid> </Box>
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
</CardContent> </CardContent>
</Card> </Card>
</Grid>
</Grid> {/* Texte extrait avec toggle - déplacé en bas */}
<Card sx={{ mt: 3 }}>
<CardHeader
title="Texte extrait"
avatar={<Avatar sx={{ bgcolor: 'info.main' }}><TextFields /></Avatar>}
action={
<Button
size="small"
startIcon={showTextExtract ? <Visibility /> : <Visibility />}
onClick={() => setShowTextExtract(!showTextExtract)}
>
{showTextExtract ? 'Masquer' : 'Afficher'}
</Button>
}
/>
<Fade in={showTextExtract}>
<CardContent sx={{ pt: 0 }}>
<Paper
sx={{
p: 2,
bgcolor: 'grey.50',
maxHeight: 300,
overflow: 'auto',
border: '1px solid',
borderColor: 'grey.200'
}}
>
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap', fontFamily: 'monospace' }}>
{extraction.extraction.text.raw}
</Typography>
</Paper>
</CardContent>
</Fade>
</Card>
</Box>
</Box>
{/* Snackbar pour les notifications */} {/* Snackbar pour les notifications */}
<Snackbar <Snackbar