Spaces:
Running
on
A100
Running
on
A100
| import React, { useState, useEffect } from 'react'; | |
| import { InformationCircleIcon } from '@heroicons/react/24/outline'; | |
| import { useTranscriptionStore } from '../stores/transcriptionStore'; | |
| import { formatTime } from '../utils/subtitleUtils'; | |
| const DEFAULT_MERGE_THRESHOLD = 2; | |
| const MediaEditControls: React.FC = () => { | |
| const [mergeThreshold, setMergeThreshold] = useState(DEFAULT_MERGE_THRESHOLD); | |
| const { | |
| transcription, | |
| currentTime, | |
| activeSegmentIndex, | |
| currentSegments, | |
| undo, | |
| redo, | |
| canUndo, | |
| canRedo, | |
| mergeSegmentsByProximity, | |
| } = useTranscriptionStore(); | |
| const MAX_MERGE_INTERVAL_SECONDS = 30; | |
| if (!transcription) { | |
| return null; | |
| } | |
| const displaySegments = currentSegments || transcription.aligned_segments; | |
| // Handle merge threshold changes | |
| useEffect(() => { | |
| mergeSegmentsByProximity(mergeThreshold); | |
| }, [mergeThreshold, mergeSegmentsByProximity]); | |
| // Keyboard shortcuts for undo/redo | |
| useEffect(() => { | |
| const handleKeyDown = (e: KeyboardEvent) => { | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) { | |
| e.preventDefault(); | |
| undo(); | |
| } else if ((e.ctrlKey || e.metaKey) && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) { | |
| e.preventDefault(); | |
| redo(); | |
| } | |
| }; | |
| document.addEventListener('keydown', handleKeyDown); | |
| return () => document.removeEventListener('keydown', handleKeyDown); | |
| }, [undo, redo]); | |
| return ( | |
| <div className="flex items-center justify-center"> | |
| <div className="flex items-center space-x-4"> | |
| {/* Current Status Info */} | |
| <div className="flex items-center space-x-4 text-xs text-gray-400"> | |
| <span className="text-green-400"> | |
| {formatTime(currentTime)} / {formatTime(transcription.total_duration)} | |
| </span> | |
| <span className="text-blue-400"> | |
| {activeSegmentIndex !== null | |
| ? `Segment ${activeSegmentIndex + 1}/${displaySegments.length}` | |
| : "No active segment"} | |
| </span> | |
| </div> | |
| {/* Combine Segments Slider */} | |
| <div className="flex items-center space-x-2"> | |
| <label className="text-xs text-gray-300 whitespace-nowrap flex items-center space-x-1"> | |
| <span>Combine Words:</span> | |
| <div className="tooltip" data-tip="This slider merges nearby words. The higher the more words are combined"> | |
| <InformationCircleIcon className="w-4 h-4 text-gray-100 hover:text-gray-300 cursor-help inline ml-1" /> | |
| </div> | |
| </label> | |
| <input | |
| type="range" | |
| min="0" | |
| max={MAX_MERGE_INTERVAL_SECONDS} | |
| step="0.5" | |
| value={mergeThreshold} | |
| onChange={(e) => setMergeThreshold(Number(e.target.value))} | |
| className="w-20 h-1 bg-gray-600 rounded-lg appearance-none cursor-pointer slider" | |
| style={{ | |
| background: `linear-gradient(to right, #3B82F6 0%, #3B82F6 ${(mergeThreshold / MAX_MERGE_INTERVAL_SECONDS) * 100}%, #4B5563 ${(mergeThreshold / MAX_MERGE_INTERVAL_SECONDS) * 100}%, #4B5563 100%)` | |
| }} | |
| /> | |
| </div> | |
| {/* Undo/Redo Buttons */} | |
| <div className="flex items-center space-x-2"> | |
| <button | |
| onClick={undo} | |
| disabled={!canUndo} | |
| className="px-3 py-1 text-xs bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white rounded transition-colors" | |
| title="Undo (Ctrl+Z)" | |
| > | |
| ↶ Undo | |
| </button> | |
| <button | |
| onClick={redo} | |
| disabled={!canRedo} | |
| className="px-3 py-1 text-xs bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white rounded transition-colors" | |
| title="Redo (Ctrl+Y)" | |
| > | |
| ↷ Redo | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default MediaEditControls; | |