Spaces:
Running
Running
File size: 4,506 Bytes
b830719 b76aacc b830719 b76aacc b830719 b76aacc b830719 b76aacc b830719 b76aacc b830719 b76aacc b830719 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | /**
* 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 (>15s)
</p>
</div>
</div>
);
}
|