import { useState, useEffect } from "react"; import { AlertTriangle, CheckCircle2, ArrowRight, ArrowLeft, Lightbulb, Copy, RefreshCw } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"; import type { Anomaly, AnomalyType, SrtSegment } from "@shared/schema"; interface AnomalyReviewProps { anomalies: Anomaly[]; segments: SrtSegment[]; onApplyCorrection: (anomalyId: string, correction: string, applyToSimilar: boolean) => void; onSkip: (anomalyId: string) => void; currentIndex: number; onIndexChange: (index: number) => void; onTimeJump?: (time: number) => void; } const anomalyTypeLabels: Record = { unusual_sentence: { label: "Unusual Sentence", description: "This sentence structure seems unusual or incomplete", color: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400", }, out_of_context: { label: "Out of Context", description: "This text doesn't fit the provided context", color: "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400", }, similar_sounding: { label: "Similar Sounding Word", description: "This might be a misheard word (homophone)", color: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400", }, grammar_issue: { label: "Grammar Issue", description: "Potential grammatical error detected", color: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400", }, }; export function AnomalyReview({ anomalies, segments, onApplyCorrection, onSkip, currentIndex, onIndexChange, onTimeJump, }: AnomalyReviewProps) { const [correction, setCorrection] = useState(""); const [showBatchDialog, setShowBatchDialog] = useState(false); const [pendingCorrection, setPendingCorrection] = useState<{ anomalyId: string; correction: string } | null>(null); const unresolvedAnomalies = anomalies.filter(a => !a.resolved); const currentAnomaly = unresolvedAnomalies[currentIndex]; const parseTime = (timeStr: string) => { const [time, ms] = timeStr.split(","); const [h, m, s] = time.split(":").map(Number); return h * 3600 + m * 60 + s + Number(ms) / 1000; }; useEffect(() => { if (currentAnomaly && onTimeJump) { const segment = segments.find(s => s.id === currentAnomaly.segmentId); if (segment) { onTimeJump(parseTime(segment.startTime)); } } }, [currentAnomaly?.id, onTimeJump, segments]); if (!currentAnomaly) { return (

All Issues Reviewed

You've reviewed all detected anomalies. Your subtitles are ready for export.

); } const currentSegment = segments.find(s => s.id === currentAnomaly.segmentId); const typeConfig = anomalyTypeLabels[currentAnomaly.type]; const findSimilarAnomalies = () => { return unresolvedAnomalies.filter( a => a.id !== currentAnomaly.id && a.flaggedText.toLowerCase() === currentAnomaly.flaggedText.toLowerCase() ); }; const similarAnomalies = findSimilarAnomalies(); const handleApplyCorrection = () => { const correctionText = correction.trim() || currentAnomaly.suggestion; if (similarAnomalies.length > 0) { setPendingCorrection({ anomalyId: currentAnomaly.id, correction: correctionText }); setShowBatchDialog(true); } else { onApplyCorrection(currentAnomaly.id, correctionText, false); setCorrection(""); if (currentIndex < unresolvedAnomalies.length - 1) { onIndexChange(currentIndex); } } }; const handleBatchDecision = (applyToAll: boolean) => { if (pendingCorrection) { onApplyCorrection(pendingCorrection.anomalyId, pendingCorrection.correction, applyToAll); setPendingCorrection(null); setShowBatchDialog(false); setCorrection(""); } }; const handleUseSuggestion = () => { setCorrection(currentAnomaly.suggestion); }; const handlePrevious = () => { if (currentIndex > 0) { onIndexChange(currentIndex - 1); setCorrection(""); } }; const handleNext = () => { onSkip(currentAnomaly.id); setCorrection(""); }; const confidencePercent = Math.round(currentAnomaly.confidence * 100); return ( <>
Review Issue {currentIndex + 1} of {unresolvedAnomalies.length} Review and correct potential subtitle errors
{typeConfig.label}
Segment #{currentAnomaly.segmentId} {currentSegment && ( <> {currentSegment.startTime} → {currentSegment.endTime} )}
{currentSegment && (

{currentSegment.text.split(currentAnomaly.flaggedText).map((part, i, arr) => ( {part} {i < arr.length - 1 && ( {currentAnomaly.flaggedText} )} ))}

)}

{typeConfig.description}

Confidence:
70 ? "bg-green-500" : confidencePercent > 40 ? "bg-amber-500" : "bg-red-500" }`} style={{ width: `${confidencePercent}%` }} />
{confidencePercent}%

Suggested Correction

{currentAnomaly.suggestion}

{similarAnomalies.length > 0 && (
Found {similarAnomalies.length} similar occurrence{similarAnomalies.length > 1 ? "s" : ""} of this issue
)}
setCorrection(e.target.value)} data-testid="input-correction" />
Apply to Similar Issues? We found {similarAnomalies.length} other occurrence{similarAnomalies.length > 1 ? "s" : ""} of "{currentAnomaly.flaggedText}" in your subtitles.

Would you like to apply the same correction to all of them?

Correction: {pendingCorrection?.correction}
); }