import React, { useState } from 'react';
import {
FileDiff,
AlertTriangle,
CheckCircle2,
XCircle,
Archive,
Layers,
Sparkles,
Quote,
Eye,
Columns,
AlignLeft,
ChevronRight,
MessageSquare,
Send,
SlidersHorizontal,
ShieldCheck
} from 'lucide-react';
import confetti from 'canvas-confetti';
export default function SemanticDiffViewer({ diffData, onReviewProposal, onBatchReview, loading }) {
const [viewMode, setViewMode] = useState('split'); // 'split' | 'unified'
const [filterType, setFilterType] = useState('all'); // 'all' | 'conflicts' | 'additions' | 'changes'
const [comments, setComments] = useState({});
const [expandedComments, setExpandedComments] = useState({});
const [actionLoading, setActionLoading] = useState({});
const [reviewedLocal, setReviewedLocal] = useState({});
const [lastActionToast, setLastActionToast] = useState(null);
if (loading) {
return (
Running semantic fact diff against Master Knowledge Register...
);
}
const rawAdditions = diffData?.additions || [];
const rawChanges = diffData?.changes || [];
const rawConflicts = diffData?.conflicts || [];
const rawRemovals = diffData?.removals || [];
// Filter out any delta that was just approved or rejected in local state
const additions = rawAdditions.filter((a) => !reviewedLocal[a.proposal_id] && !reviewedLocal[a.id]);
const changes = rawChanges.filter((c) => !reviewedLocal[c.proposal_id] && !reviewedLocal[c.id]);
const conflicts = rawConflicts.filter((c) => !reviewedLocal[c.proposal_id] && !reviewedLocal[c.id]);
const removals = rawRemovals.filter((r) => !reviewedLocal[r.proposal_id] && !reviewedLocal[r.id]);
const totalDeltas = additions.length + changes.length + conflicts.length + removals.length;
const handleAction = async (proposalId, decision) => {
const allDeltas = [...rawConflicts, ...rawAdditions, ...rawChanges, ...rawRemovals];
const found = allDeltas.find((d) => (d.proposal_id === proposalId || d.id === proposalId));
const title = found?.title || found?.proposed_value || found?.summary || `Delta #${proposalId.slice(0, 8)}`;
// Instant local removal & feedback banner
setReviewedLocal((prev) => ({
...prev,
[proposalId]: { decision, title, time: new Date().toLocaleTimeString() }
}));
setLastActionToast({
proposalId,
decision,
title,
time: new Date().toLocaleTimeString()
});
setActionLoading((prev) => ({ ...prev, [proposalId]: true }));
try {
if (decision === 'APPROVED') {
confetti({
particleCount: 50,
spread: 60,
origin: { y: 0.8 },
colors: ['#10B981', '#14B8A6', '#3B82F6'],
});
}
await onReviewProposal(proposalId, decision, comments[proposalId] || '');
} catch (err) {
console.error('Error applying diff review:', err);
} finally {
setActionLoading((prev) => ({ ...prev, [proposalId]: false }));
}
};
const handleBatch = async (decision) => {
const allIds = [...rawConflicts, ...rawAdditions, ...rawChanges, ...rawRemovals].map((d) => d.proposal_id || d.id);
const batchMap = {};
allIds.forEach((id) => {
batchMap[id] = { decision, title: 'Batch Knowledge Item' };
});
setReviewedLocal((prev) => ({ ...prev, ...batchMap }));
setLastActionToast({
decision,
title: `Batch of ${allIds.length} semantic fact deltas`,
time: new Date().toLocaleTimeString()
});
if (decision === 'APPROVED') {
confetti({
particleCount: 90,
spread: 80,
origin: { y: 0.7 },
});
}
await onBatchReview(decision);
};
return (
{/* Diff Toolbar & Delta Summary */}
3-Way Semantic Fact Diff
{totalDeltas} pending {totalDeltas === 1 ? 'delta' : 'deltas'}
+{additions.length} added
~{changes.length} updated
{conflicts.length > 0 && (
!{conflicts.length} conflicts
)}
{/* View Controls & Filters */}
{/* Filter Pills */}
{/* Split / Unified Toggle */}
{/* Bulk Approve Button */}
{totalDeltas > 0 && (
)}
{/* Reactive Recent Action Toast Banner */}
{lastActionToast && (
{lastActionToast.decision === 'APPROVED' ? : }
{lastActionToast.decision === 'APPROVED' ? '✓ Approved & Merged to Master Truth Register' : '✕ Rejected & Dismissed'}:
{lastActionToast.proposalId && (
ID: {lastActionToast.proposalId.slice(0, 8)}
)}
at {lastActionToast.time}
"{lastActionToast.title}"
)}
{totalDeltas === 0 ? (
Knowledge Register is Up to Date
No pending knowledge proposals or conflicts found. Stage a new document or protocol version to generate 3-way fact diffs.
) : (
{/* Conflicts Section */}
{(filterType === 'all' || filterType === 'conflicts') && conflicts.map((c) => (
{/* Card Header */}
{c.title || 'Fact Conflict'}
Conflict
ID: {c.proposal_id.slice(0, 8)}
{/* Conflict Body */}
{c.rationale && (
Reconciliation Note:
{c.rationale}
)}
{/* Side by Side Diff Comparison */}
{/* Baseline / Existing */}
- Master Truth (Existing Fact)
Conf: {Math.round((c.existing?.confidence || 0.9) * 100)}%
{c.existing?.value || 'N/A'}
{/* Proposed / Amendment */}
+ Proposed Fact (Extracted Amendment)
Conf: {Math.round((c.proposed?.confidence || 0.95) * 100)}%
{c.proposed?.value || 'N/A'}
{/* Evidence Provenance */}
{c.proposed?.evidence && c.proposed.evidence.length > 0 && (
Source Provenance Citation
{c.proposed.evidence[0]}
)}
{/* Action Bar */}
{/* Reviewer Note Drawer */}
{expandedComments[c.proposal_id] && (
setComments({ ...comments, [c.proposal_id]: e.target.value })}
className="w-full bg-[#161F32] border border-slate-700 text-xs text-slate-200 px-3 py-2 rounded-lg focus:outline-none focus:ring-1 focus:ring-emerald-500"
/>
)}
))}
{/* Additions Section */}
{(filterType === 'all' || filterType === 'additions') && additions.map((a) => (
{/* Card Header */}
{a.title || 'New Knowledge Delta'}
+{a.type || 'CLAIM'}
{Math.round((a.confidence || 0.95) * 100)}% Confidence
{/* Card Body */}
{a.proposed_value || a.summary || 'Extracted content'}
{a.evidence && a.evidence.length > 0 && (
Document Citation
{a.evidence[0]}
)}
{/* Action Bar */}
))}
{/* Changes / Updates Section */}
{(filterType === 'all' || filterType === 'changes') && changes.map((ch) => (
{ch.title || 'Knowledge Update'}
UPDATE
- Previous Value
{ch.existing?.value || 'N/A'}
+ Updated Value
{ch.proposed?.value || 'N/A'}
))}
)}
);
}