"use client"; import { useEffect, useState, useRef } from 'react'; import { Search, Clock, MessageSquare, VolumeX } from 'lucide-react'; interface WordInfo { word: string; start: number; end: number; } interface TimelineMarker { type: 'speech' | 'silence'; start: number; end: number; text?: string; duration?: number; } export default function SyncedTranscriptPlayer({ taskId, baseUrl, audioRef }: { taskId: string, baseUrl: string, audioRef: React.RefObject }) { const [words, setWords] = useState([]); const [markers, setMarkers] = useState([]); const [currentTime, setCurrentTime] = useState(0); const [searchQuery, setSearchQuery] = useState(""); const containerRef = useRef(null); const activeWordRef = useRef(null); useEffect(() => { fetch(`${baseUrl}/api/stream/${taskId}/transcript.json`) .then(res => { if (!res.ok) throw new Error("Transcript not found"); return res.json(); }) .then(data => { if (data.words) setWords(data.words); }) .catch(err => console.log("No transcript found for this task")); fetch(`${baseUrl}/api/stream/${taskId}/timeline_markers.json`) .then(res => res.ok ? res.json() : null) .then(data => { if (data && data.markers) setMarkers(data.markers); }) .catch(() => {}); }, [taskId, baseUrl]); useEffect(() => { const audioEl = audioRef.current; if (!audioEl) return; const handleTimeUpdate = () => setCurrentTime(audioEl.currentTime); audioEl.addEventListener('timeupdate', handleTimeUpdate); return () => audioEl.removeEventListener('timeupdate', handleTimeUpdate); }, [audioRef]); // Auto-scroll to active word if not searching useEffect(() => { if (activeWordRef.current && containerRef.current && searchQuery === "") { activeWordRef.current.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' }); } }, [currentTime, searchQuery]); const seekTo = (time: number) => { if (audioRef.current) { audioRef.current.currentTime = time; } }; if (words.length === 0) return null; return (

Audio Intelligence Transcript

setSearchQuery(e.target.value)} className="w-full bg-[#1a1a1a] border border-[#27272a] rounded-full pl-9 pr-4 py-1.5 text-sm text-gray-200 focus:outline-none focus:border-[#1877F2] transition-colors" />
{/* Timeline Intelligence Strip */} {markers.length > 0 && (
Timeline Segments
{markers.map((m, i) => { const totalDuration = markers[markers.length-1].end; const widthPct = ((m.end - m.start) / totalDuration) * 100; return (
seekTo(m.start)} /> ) })}
0:00 Gray = Silence {Math.floor(markers[markers.length-1]?.end || 0)}s
)}