livrare lot 2
This commit is contained in:
commit
8ecc78e729
763 changed files with 164593 additions and 0 deletions
|
|
@ -0,0 +1,271 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Box, Paper, Typography, Chip, IconButton, Tooltip, LinearProgress,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
History as HistoryIcon,
|
||||
Delete as DeleteIcon,
|
||||
Visibility as VisibilityIcon,
|
||||
ContentCopy as CopyIcon,
|
||||
Facebook as FacebookIcon,
|
||||
Cancel as CancelIcon,
|
||||
Replay as ResumeIcon,
|
||||
} from '@mui/icons-material';
|
||||
import type { AnalysisSession, PaginationInfo } from './history-types';
|
||||
import { getRiskIcon, getRiskCategoryColor, formatDuration, formatDate } from './history-helpers';
|
||||
|
||||
// Sessions still in flight can be canceled (agent-v3 /:id/cancel).
|
||||
const CANCELABLE = new Set(['pending', 'running', 'processing']);
|
||||
// Failed/canceled sessions can be resumed from checkpoint (/:id/resume).
|
||||
const RESUMABLE = new Set(['failed', 'canceled']);
|
||||
|
||||
interface Props {
|
||||
loading: boolean;
|
||||
analyses: AnalysisSession[];
|
||||
pagination: PaginationInfo;
|
||||
copiedId: string | null;
|
||||
onPageChange: (event: unknown, newPage: number) => void;
|
||||
onRowsPerPageChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onViewDetails: (sessionId: string) => void;
|
||||
onDeleteClick: (session: AnalysisSession) => void;
|
||||
onCopyId: (e: React.MouseEvent, id: string) => void;
|
||||
onSocialPostClick?: (session: AnalysisSession) => void;
|
||||
onCancelClick?: (session: AnalysisSession) => void;
|
||||
onResumeClick?: (session: AnalysisSession) => void;
|
||||
}
|
||||
|
||||
export const HistoryTable: React.FC<Props> = ({
|
||||
loading,
|
||||
analyses,
|
||||
pagination,
|
||||
copiedId,
|
||||
onPageChange,
|
||||
onRowsPerPageChange,
|
||||
onViewDetails,
|
||||
onDeleteClick,
|
||||
onCopyId,
|
||||
onSocialPostClick,
|
||||
onCancelClick,
|
||||
onResumeClick,
|
||||
}) => (
|
||||
<Paper>
|
||||
{loading && <LinearProgress />}
|
||||
<TableContainer>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow sx={{ bgcolor: 'action.hover' }}>
|
||||
<TableCell sx={{ fontWeight: 600 }}>Session ID</TableCell>
|
||||
<TableCell sx={{ fontWeight: 600 }}>Date</TableCell>
|
||||
<TableCell sx={{ fontWeight: 600 }}>Email</TableCell>
|
||||
<TableCell sx={{ fontWeight: 600 }}>Source</TableCell>
|
||||
<TableCell align="center" sx={{ fontWeight: 600 }}>Risk</TableCell>
|
||||
<TableCell align="center" sx={{ fontWeight: 600 }}>Techniques</TableCell>
|
||||
<TableCell align="center" sx={{ fontWeight: 600 }}>AI %</TableCell>
|
||||
<TableCell align="center" sx={{ fontWeight: 600 }}>Claims</TableCell>
|
||||
<TableCell align="center" sx={{ fontWeight: 600 }}>Duration</TableCell>
|
||||
<TableCell align="center" sx={{ fontWeight: 600 }}>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{!loading && analyses.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={10} align="center" sx={{ py: 4 }}>
|
||||
<HistoryIcon sx={{ fontSize: 48, color: 'text.disabled', mb: 1 }} />
|
||||
<Typography color="text.secondary">No analyses found</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
analyses.map((analysis) => (
|
||||
<TableRow
|
||||
key={analysis.session_id}
|
||||
hover
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => onViewDetails(analysis.session_id)}
|
||||
>
|
||||
<TableCell onClick={(e) => e.stopPropagation()}>
|
||||
<Box display="flex" alignItems="center" gap={0.5}>
|
||||
<Tooltip title={analysis.session_id}>
|
||||
<Typography variant="caption" sx={{ fontFamily: 'monospace' }}>
|
||||
{analysis.session_id.substring(0, 8)}...
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Tooltip title={copiedId === analysis.session_id ? 'Copied!' : 'Copy'}>
|
||||
<IconButton size="small" onClick={(e) => onCopyId(e, analysis.session_id)}>
|
||||
<CopyIcon sx={{ fontSize: 14, color: copiedId === analysis.session_id ? 'success.main' : 'action.active' }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||
{formatDate(analysis.started_at)}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{analysis.input_type}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Tooltip title={`User ID: ${analysis.user_id}`}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
maxWidth: 180,
|
||||
}}
|
||||
>
|
||||
{analysis.user_email || <span style={{ color: '#999' }}>No email</span>}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||
<Chip
|
||||
label={analysis.input_type}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color={analysis.input_type === 'url' ? 'primary' : analysis.input_type === 'text' ? 'default' : 'secondary'}
|
||||
sx={{ textTransform: 'uppercase', fontSize: 11, height: 22 }}
|
||||
/>
|
||||
<Chip label={analysis.source_app} size="small" variant="outlined" sx={{ fontSize: 11, height: 22 }} />
|
||||
</Box>
|
||||
{(analysis.source_verdict || analysis.domain) && (
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.3 }}>
|
||||
{analysis.source_publication || analysis.domain || ''}
|
||||
{(analysis.source_verdict || analysis.domain_verdict) && (
|
||||
<Chip
|
||||
label={analysis.source_verdict || analysis.domain_verdict}
|
||||
size="small"
|
||||
sx={{
|
||||
ml: 0.5, height: 18, fontSize: 10,
|
||||
bgcolor: (analysis.source_verdict || analysis.domain_verdict) === 'TRUSTED' ? '#22c55e'
|
||||
: (analysis.source_verdict || analysis.domain_verdict) === 'SUSPICIOUS' ? '#f97316'
|
||||
: (analysis.source_verdict || analysis.domain_verdict) === 'UNTRUSTED' ? '#ef4444' : undefined,
|
||||
color: (analysis.source_verdict || analysis.domain_verdict) !== 'NEUTRAL' ? '#fff' : undefined,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{analysis.risk_score !== null ? (
|
||||
<Tooltip title={`${analysis.risk_category} - ${analysis.risk_level}`}>
|
||||
<Chip
|
||||
icon={getRiskIcon(analysis.risk_level)}
|
||||
label={`${analysis.risk_score} ${analysis.risk_category || ''}`}
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: getRiskCategoryColor(analysis.risk_category),
|
||||
color: '#fff',
|
||||
fontWeight: 600,
|
||||
'& .MuiChip-icon': { color: '#fff' },
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Chip label="-" size="small" variant="outlined" />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{analysis.techniques_count !== null ? (
|
||||
<Chip
|
||||
label={analysis.techniques_count}
|
||||
size="small"
|
||||
color={Number(analysis.techniques_count) > 0 ? 'warning' : 'default'}
|
||||
variant="outlined"
|
||||
/>
|
||||
) : '-'}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{analysis.ai_probability !== null ? (
|
||||
<Tooltip title={analysis.ai_verdict || ''}>
|
||||
<Chip
|
||||
label={`${Math.round(Number(analysis.ai_probability))}%`}
|
||||
size="small"
|
||||
color={Number(analysis.ai_probability) > 50 ? 'warning' : 'default'}
|
||||
variant="outlined"
|
||||
/>
|
||||
</Tooltip>
|
||||
) : '-'}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{analysis.total_claims !== null ? (
|
||||
<Tooltip title={`True: ${analysis.verified_true}, False: ${analysis.verified_false}`}>
|
||||
<Typography variant="body2">
|
||||
{analysis.total_claims}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
) : '-'}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{formatDuration(analysis.total_duration_ms)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="center" onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => onViewDetails(analysis.session_id)}
|
||||
title="View details"
|
||||
>
|
||||
<VisibilityIcon fontSize="small" />
|
||||
</IconButton>
|
||||
{onSocialPostClick && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => onSocialPostClick(analysis)}
|
||||
title="Post to Facebook"
|
||||
sx={{ color: '#1877F2' }}
|
||||
>
|
||||
<FacebookIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
{onCancelClick && CANCELABLE.has(analysis.status) && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => onCancelClick(analysis)}
|
||||
title="Cancel running analysis"
|
||||
color="warning"
|
||||
>
|
||||
<CancelIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
{onResumeClick && RESUMABLE.has(analysis.status) && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => onResumeClick(analysis)}
|
||||
title="Resume from checkpoint (re-runs only unfinished components)"
|
||||
color="primary"
|
||||
>
|
||||
<ResumeIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => onDeleteClick(analysis)}
|
||||
title="Delete"
|
||||
color="error"
|
||||
>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TablePagination
|
||||
component="div"
|
||||
count={pagination.total}
|
||||
page={pagination.page - 1}
|
||||
onPageChange={onPageChange}
|
||||
rowsPerPage={pagination.limit}
|
||||
onRowsPerPageChange={onRowsPerPageChange}
|
||||
rowsPerPageOptions={[10, 20, 50, 100]}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue