parakeet-v3-streaming / source /src /components /TranscriptionDisplay.jsx
andito's picture
andito HF Staff
Make UI more minimal and compact
b76aacc
/**
* Transcription Display Component
*
* Shows progressive transcription with:
* - Yellow text for fixed sentences (completed, won't change)
* - Cyan dim text for active transcription (in-progress)
*/
import { useEffect, useRef } from 'react';
export default function TranscriptionDisplay({ fixedText, activeText, timestamp, isRecording, autoScroll = true, onAutoScrollToggle }) {
const containerRef = useRef(null);
// Auto-scroll to bottom when new text appears (if enabled)
useEffect(() => {
if (autoScroll && containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight;
}
}, [fixedText, activeText, autoScroll]);
const formatTimestamp = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = (seconds % 60).toFixed(1);
return `${mins}:${secs.padStart(4, '0')}`;
};
return (
<div className="w-full max-w-4xl mx-auto">
<div className="bg-gray-900 rounded-lg border border-gray-700 p-6 shadow-xl">
{/* Header */}
<div className="flex items-center justify-between mb-4 pb-4 border-b border-gray-700">
<h2 className="text-xl font-semibold text-gray-100">
Live Transcription
</h2>
<div className="flex items-center gap-4">
{isRecording && (
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-red-500 rounded-full animate-pulse"></div>
<span className="text-sm text-gray-300">Recording</span>
</div>
)}
{timestamp > 0 && (
<span className="text-sm text-gray-400 font-mono">
{formatTimestamp(timestamp)}
</span>
)}
</div>
</div>
{/* Transcription Text */}
<div
ref={containerRef}
className="min-h-[200px] max-h-[400px] overflow-y-auto font-sans text-lg leading-relaxed"
>
{!fixedText && !activeText && !isRecording && (
<p className="text-gray-500 italic">
Click "Start Recording" to begin transcription...
</p>
)}
{!fixedText && !activeText && isRecording && (
<p className="text-gray-500 italic animate-pulse">
Listening...
</p>
)}
{/* Fixed text (yellow) - sentences that won't change */}
{fixedText && (
<span className="text-yellow-400 font-medium">
{fixedText}
</span>
)}
{/* Space between fixed and active */}
{fixedText && activeText && ' '}
{/* Active text (cyan dim) - current partial transcription */}
{activeText && (
<span className="text-cyan-400 opacity-80">
{activeText}
</span>
)}
</div>
{/* Legend + Auto-scroll Toggle */}
<div className="mt-4 pt-4 border-t border-gray-700 flex items-center justify-between">
<div className="flex gap-6 text-sm">
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-yellow-400 rounded"></div>
<span className="text-gray-300">Fixed sentences</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-cyan-400 opacity-80 rounded"></div>
<span className="text-gray-300">Active transcription</span>
</div>
</div>
{/* Auto-scroll Toggle */}
{onAutoScrollToggle && (
<button
onClick={onAutoScrollToggle}
className={`px-3 py-1 rounded text-xs font-medium transition-all duration-200 ${
autoScroll
? 'bg-cyan-900/20 border border-cyan-700/50 text-cyan-400 hover:bg-cyan-900/30'
: 'bg-gray-800 border border-gray-600 text-gray-400 hover:bg-gray-700'
}`}
title={autoScroll ? 'Disable auto-scroll to read from top' : 'Enable auto-scroll to follow live transcription'}
>
{autoScroll ? '🔒 Auto-scroll' : '🔓 Scroll locked'}
</button>
)}
</div>
</div>
{/* Technical Details */}
<div className="mt-4 text-xs text-gray-500 text-center">
<p>
Smart progressive streaming: Growing window (0-15s) → Sentence-aware sliding (&gt;15s)
</p>
</div>
</div>
);
}