4NK_IA_front/src/views/ConseilView.tsx
Nicolas Cantu 1fb8a56cf0 backend
2025-09-16 04:27:07 +02:00

327 lines
11 KiB
TypeScript

import { useEffect } from 'react'
import {
Box,
Typography,
Paper,
Card,
CardContent,
List,
ListItem,
ListItemText,
ListItemIcon,
Alert,
Chip,
Button,
CircularProgress,
LinearProgress,
} from '@mui/material'
import {
Lightbulb,
Warning,
CheckCircle,
TrendingUp,
Schedule,
Psychology,
Assessment,
Error,
} from '@mui/icons-material'
import type { SvgIconProps, ChipProps, LinearProgressProps } from '@mui/material'
import { useAppDispatch, useAppSelector } from '../store'
import { getConseil, analyzeDocument } from '../store/documentSlice'
import { Layout } from '../components/Layout'
export default function ConseilView() {
const dispatch = useAppDispatch()
const { currentDocument, conseilResult, analysisResult, loading } = useAppSelector(
(state) => state.document
)
useEffect(() => {
if (currentDocument) {
if (!conseilResult) {
dispatch(getConseil(currentDocument.id))
}
if (!analysisResult) {
dispatch(analyzeDocument(currentDocument.id))
}
}
}, [currentDocument, conseilResult, analysisResult, dispatch])
if (!currentDocument) {
return (
<Layout>
<Alert severity="info">
Veuillez d'abord téléverser et sélectionner un document.
</Alert>
</Layout>
)
}
if (loading) {
return (
<Layout>
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
<CircularProgress />
<Typography sx={{ ml: 2 }}>Génération des conseils LLM...</Typography>
</Box>
</Layout>
)
}
if (!conseilResult) {
return (
<Layout>
<Alert severity="warning">
Aucun conseil disponible.
</Alert>
</Layout>
)
}
const getRiskColor = (risk: string): SvgIconProps['color'] => {
if (risk.toLowerCase().includes('élevé') || risk.toLowerCase().includes('critique')) {
return 'error'
}
if (risk.toLowerCase().includes('moyen') || risk.toLowerCase().includes('modéré')) {
return 'warning'
}
return 'info'
}
const getScoreColor = (score: number): ChipProps['color'] => {
if (score >= 0.8) return 'success'
if (score >= 0.6) return 'warning'
return 'error'
}
const getScoreIcon = (score: number) => {
if (score >= 0.8) return <CheckCircle color="success" />
if (score >= 0.6) return <Warning color="warning" />
return <Error color="error" />
}
return (
<Layout>
<Typography variant="h4" gutterBottom>
<Psychology sx={{ mr: 1, verticalAlign: 'middle' }} />
Conseil LLM
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{/* Score de vraisemblance */}
{analysisResult && (
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
<Assessment sx={{ mr: 1, verticalAlign: 'middle' }} />
Score de vraisemblance
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
{getScoreIcon(analysisResult.credibilityScore)}
<Typography variant="h4" sx={{ ml: 2 }}>
{(analysisResult.credibilityScore * 100).toFixed(1)}%
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={analysisResult.credibilityScore * 100}
color={getScoreColor(analysisResult.credibilityScore) as LinearProgressProps['color']}
sx={{ height: 10, borderRadius: 5, mb: 2 }}
/>
<Typography variant="body2" color="text.secondary">
{analysisResult.credibilityScore >= 0.8
? 'Document très fiable'
: analysisResult.credibilityScore >= 0.6
? 'Document moyennement fiable'
: 'Document peu fiable - vérification recommandée'}
</Typography>
{analysisResult.summary && (
<Paper
sx={{
p: 2,
bgcolor: 'grey.50',
border: '1px solid',
borderColor: 'grey.200',
mt: 2,
}}
>
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
{analysisResult.summary}
</Typography>
</Paper>
)}
</CardContent>
</Card>
)}
{/* Analyse LLM */}
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
<Lightbulb sx={{ mr: 1, verticalAlign: 'middle' }} />
Analyse LLM
</Typography>
<Paper
sx={{
p: 2,
bgcolor: 'grey.50',
border: '1px solid',
borderColor: 'grey.200',
}}
>
<Typography variant="body1" sx={{ whiteSpace: 'pre-wrap' }}>
{conseilResult.analysis}
</Typography>
</Paper>
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
Généré le {new Date(conseilResult.generatedAt).toLocaleString()}
</Typography>
</CardContent>
</Card>
<Box sx={{ display: 'flex', gap: 3, flexWrap: 'wrap' }}>
{/* Recommandations */}
<Box sx={{ flex: '1 1 300px' }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
<CheckCircle sx={{ mr: 1, verticalAlign: 'middle' }} />
Recommandations ({conseilResult.recommendations.length + (analysisResult?.recommendations?.length || 0)})
</Typography>
<List dense>
{conseilResult.recommendations.map((recommendation, index) => (
<ListItem key={index}>
<ListItemIcon>
<CheckCircle color="success" />
</ListItemIcon>
<ListItemText primary={recommendation} />
</ListItem>
))}
{/* Ajouter les recommandations d'analyse si disponibles */}
{analysisResult?.recommendations?.map((recommendation, index) => (
<ListItem key={`analysis-${index}`}>
<ListItemIcon>
<Assessment color="info" />
</ListItemIcon>
<ListItemText primary={recommendation} />
</ListItem>
))}
</List>
</CardContent>
</Card>
</Box>
{/* Risques identifiés */}
<Box sx={{ flex: '1 1 300px' }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
<Warning sx={{ mr: 1, verticalAlign: 'middle' }} />
Risques identifiés ({conseilResult.risks.length})
</Typography>
<List dense>
{conseilResult.risks.map((risk, index) => (
<ListItem key={index}>
<ListItemIcon>
<Warning color={getRiskColor(risk)} />
</ListItemIcon>
<ListItemText
primary={risk}
primaryTypographyProps={{
color: getRiskColor(risk) === 'error' ? 'error.main' :
getRiskColor(risk) === 'warning' ? 'warning.main' : 'info.main'
}}
/>
</ListItem>
))}
</List>
</CardContent>
</Card>
</Box>
</Box>
{/* Prochaines étapes */}
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
<TrendingUp sx={{ mr: 1, verticalAlign: 'middle' }} />
Prochaines étapes recommandées
</Typography>
<List>
{conseilResult.nextSteps.map((step, index) => (
<ListItem key={index}>
<ListItemIcon>
<Schedule color="primary" />
</ListItemIcon>
<ListItemText
primary={`Étape ${index + 1}`}
secondary={step}
/>
</ListItem>
))}
</List>
</CardContent>
</Card>
{/* Actions */}
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Actions
</Typography>
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
<Button
variant="contained"
onClick={() => dispatch(getConseil(currentDocument.id))}
disabled={loading}
>
Régénérer les conseils
</Button>
<Button variant="outlined">
Exporter le rapport
</Button>
<Button variant="outlined">
Partager avec l'équipe
</Button>
</Box>
</CardContent>
</Card>
{/* Résumé exécutif */}
<Paper sx={{ p: 2, bgcolor: 'primary.50' }}>
<Typography variant="h6" gutterBottom>
Résumé exécutif
</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', mb: 2 }}>
{analysisResult && (
<Chip
icon={<Assessment />}
label={`Score: ${(analysisResult.credibilityScore * 100).toFixed(1)}%`}
color={getScoreColor(analysisResult.credibilityScore)}
variant="filled"
/>
)}
<Chip
label={`${conseilResult.recommendations.length + (analysisResult?.recommendations?.length || 0)} recommandations`}
color="success"
variant="outlined"
/>
<Chip
label={`${conseilResult.risks.length} risques identifiés`}
color="warning"
variant="outlined"
/>
<Chip
label={`${conseilResult.nextSteps.length} étapes suivantes`}
color="info"
variant="outlined"
/>
</Box>
<Typography variant="body2" color="text.secondary">
Cette analyse LLM a é générée automatiquement et doit être validée par un expert notarial.
</Typography>
</Paper>
</Box>
</Layout>
)
}