import React, { useState } from 'react'; import { Zap, RefreshCw, Layers, ShieldCheck, Search, Loader2, AlertTriangle, CheckCircle2, Network, GitMerge } from 'lucide-react'; import { useQuery, useMutation } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { apiClient } from "../../utils/apiClient"; import { Card } from "../../components/ui/Card"; import { Button } from "../../components/ui/Button"; import { Badge } from "../../components/ui/Badge"; import { AnimatedPage } from "../../components/ui/AnimatedPage"; import { GraphAudit } from '../../types'; const GraphDebuggerPage: React.FC = () => { const { t } = useTranslation(); const [healingId, setHealingId] = useState(''); const { data: audit, isLoading, refetch } = useQuery({ queryKey: ['graph-audit'], queryFn: () => apiClient('/api/v1/graph/debugger/'), }); const cleanupMutation = useMutation({ mutationFn: () => apiClient('/api/v1/graph/debugger/', { method: 'POST', body: JSON.stringify({ action: 'cleanup' }) }), onSuccess: () => refetch() }); const healMutation = useMutation({ mutationFn: (mediaId: string) => apiClient('/api/v1/graph/debugger/', { method: 'POST', body: JSON.stringify({ action: 'heal', media_id: mediaId }) }), onSuccess: () => { setHealingId(''); refetch(); } }); const mergeDuplicatesMutation = useMutation({ mutationFn: () => apiClient('/api/v1/graph/debugger/', { method: 'POST', body: JSON.stringify({ action: 'merge_duplicates' }) }), onSuccess: () => refetch() }); if (isLoading || !audit) return (

{t('admin.graph.loading', 'Auditing Knowledge Graph...')}

); return (
{/* Header */}
{t('admin.graph.protocol', 'Lore Consistency Protocol')}

GRAPH HEALER

{t('admin.graph.subtitle', 'Diagnostic et résolution des conflits de lore dans la base Neo4j.')}

{/* Top Stats Dashboard */}

{t('admin.graph.isolated', 'Isolated Nodes')}

{audit.isolated_nodes} {t('admin.graph.entities', 'Entities')}

{t('admin.graph.isolated_desc', 'Nœuds sans aucune relation sémantique ou temporelle.')}

{t('admin.graph.conflicts', 'Lore Conflicts')}

{audit.temporal_conflicts} {t('admin.graph.detected', 'Detected')}

{t('admin.graph.conflicts_desc', 'Contradictions temporelles (ex: suites antérieures à l\'original).')}

{t('admin.graph.orphans', 'Orphan Entities')}

{audit.orphan_entities} {t('admin.graph.entities', 'Entities')}

{t('admin.graph.orphan_desc', 'Entités extraites non rattachées à un média parent.')}

{t('admin.graph.duplicates', 'Duplicate Entities')}

{audit.duplicate_entities ?? 0} {t('admin.graph.entities', 'Entities')}

{t('admin.graph.duplicate_desc', 'Entités doublons possédant le même nom/titre.')}

{/* Actions & Healing */}

{t('admin.graph.auto_heal', 'Auto-Heal')}

{t('admin.graph.cleanup_desc', 'Lancer un cycle de nettoyage global pour supprimer les bruits d\'extraction et les contradictions logiques triviales.')}

{t('admin.graph.merge', 'Merge Duplicates')}

{t('admin.graph.merge_desc', 'Fusionner les entités en doublon partageant le même nom et combiner leurs relations sémantiques.')}

Manual Healing

setHealingId(e.target.value)} aria-label="Identifiant du média à réparer" placeholder="Media ID (ex: anime-123)" className="w-full bg-gray-50 dark:bg-black/20 border-2 border-black/5 dark:border-white/5 rounded-2xl px-6 py-4 text-sm font-bold focus:border-purple-500 outline-none transition-all" />
{/* Conflicts List */}

Critical Conflicts

TOP 5
{audit.details && audit.details.length > 0 ? (
{audit.details.map((conflict: { t1: string; y1: number; t2: string; y2: number }, i: number) => (
{conflict.t1} {conflict.y1}
CONTRA-SEQUEL
{conflict.t2} {conflict.y2}
))}
) : (

No critical conflicts

)}
); }; export default GraphDebuggerPage;