From aba43a67d7faf0bea5178369abc59c588fc3b4e9 Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Wed, 29 Apr 2026 19:03:25 +0100 Subject: [PATCH] feat(l7): diff AI baseline vs current VTT in QCDetail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VttDiffView component (frontend/src/components/VttEditor/VttDiffView.tsx): - Lazy-loads VTT version list (newest-first) and diffs version 1 (AI baseline) against the latest version - Renders unified diff: green lines = added, red lines = removed (unchanged hidden) - Collapsed by default; expand with "↔ Diff vs AI baseline" button - Shows +N/-N change summary in header QCDetail integration: - VttDiffView added below both Captions and Audio Description VttEditors (only appears for the selected language) Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/VttEditor/VttDiffView.tsx | 104 ++++++++++++++++++ frontend/src/routes/admin/QCDetail.tsx | 7 ++ 2 files changed, 111 insertions(+) create mode 100644 frontend/src/components/VttEditor/VttDiffView.tsx diff --git a/frontend/src/components/VttEditor/VttDiffView.tsx b/frontend/src/components/VttEditor/VttDiffView.tsx new file mode 100644 index 0000000..7a1f46a --- /dev/null +++ b/frontend/src/components/VttEditor/VttDiffView.tsx @@ -0,0 +1,104 @@ +import { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { apiClient } from '../../lib/api'; +import type { VttKind } from '../../types/api'; + +interface VttDiffViewProps { + jobId: string; + lang: string; + kind: VttKind; +} + +export function VttDiffView({ jobId, lang, kind }: VttDiffViewProps) { + const [open, setOpen] = useState(false); + + const { data: versions } = useQuery({ + queryKey: ['vtt-versions', jobId, lang, kind], + queryFn: () => apiClient.listVttVersions(jobId, lang, kind, 0, 200), + enabled: open, + staleTime: 30_000, + }); + + const versionList = versions?.versions ?? []; + const baseline = versionList.length > 0 ? versionList[versionList.length - 1] : null; // oldest = version 1 + const latest = versionList.length > 0 ? versionList[0] : null; // newest first + + const canShowDiff = baseline && latest && baseline.version !== latest.version; + + const { data: diffData, isLoading: diffLoading } = useQuery({ + queryKey: ['vtt-diff', jobId, lang, kind, baseline?.version, latest?.version], + queryFn: () => apiClient.diffVttVersions(jobId, lang, kind, baseline!.version, latest!.version), + enabled: open && !!canShowDiff, + staleTime: 30_000, + }); + + if (!open) { + return ( + + ); + } + + return ( +
+
+
+ Diff vs AI baseline + {diffData && ( + + +{diffData.added_count} + {' / '} + -{diffData.removed_count} + {` lines changed from v${baseline?.version} → v${latest?.version}`} + + )} +
+ +
+ +
+ {diffLoading && ( +
Loading diff…
+ )} + + {!diffLoading && !canShowDiff && versionList.length > 0 && ( +
No changes from baseline — content is identical to AI output.
+ )} + + {!diffLoading && versionList.length === 0 && ( +
No version history yet.
+ )} + + {diffData?.lines.map((line, i) => { + if (line.type === 'unchanged') return null; + return ( +
+ + {line.type === 'added' ? '+' : '-'} + + {line.content} +
+ ); + })} + + {diffData && diffData.added_count === 0 && diffData.removed_count === 0 && ( +
No changes from AI baseline.
+ )} +
+
+ ); +} diff --git a/frontend/src/routes/admin/QCDetail.tsx b/frontend/src/routes/admin/QCDetail.tsx index b6d62fb..8756b01 100644 --- a/frontend/src/routes/admin/QCDetail.tsx +++ b/frontend/src/routes/admin/QCDetail.tsx @@ -11,6 +11,7 @@ import { } from '../../hooks/useAccessibleVideoEdit'; import { StatusBadge } from '../../components/StatusBadge'; import { VttEditor } from '../../components/VttEditor/VttEditor'; +import { VttDiffView } from '../../components/VttEditor/VttDiffView'; import { VideoWithCaptions } from '../../components/VideoWithCaptions'; import { VoiceSelector } from '../../components/VoiceSelector'; import { TimelinePreview } from '../../components/TimelinePreview'; @@ -1584,6 +1585,9 @@ export function QCDetail() { glossaryTerms={glossaryTerms} language={selectedLanguage} /> +
+ +
)} @@ -1611,6 +1615,9 @@ export function QCDetail() { glossaryTerms={glossaryTerms} language={selectedLanguage} /> +
+ +
)}