'use client'; import React, { useState, useEffect } from 'react'; import HelpModal from '@/components/HelpModal'; import QuickTourPopup from '@/components/QuickTourPopup'; import TextInput from '@/components/TextInput'; import AudioRecorder from '@/components/AudioRecorder'; import FontSelector from '@/components/FontSelector'; import DatasetStats from '@/components/DatasetStats'; import SettingsModal from '@/components/SettingsModal'; import { Mic2, Moon, Sun, Settings, Search, SkipForward, SkipBack, Bookmark, Hash, HelpCircle } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts'; import { toast } from 'sonner'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { cn } from '@/lib/utils'; import { detectLanguage, isRTL } from '@/lib/language'; export default function Home() { const [sentences, setSentences] = useState([]); const [currentIndex, setCurrentIndex] = useLocalStorage('currentIndex', 0); const [speakerId, setSpeakerId] = useLocalStorage('speakerId', ''); const [datasetName, setDatasetName] = useLocalStorage('datasetName', 'dataset1'); const [fontStyle, setFontStyle] = useLocalStorage('fontStyle', 'Times New Roman'); const [fontFamily, setFontFamily] = useState('Times New Roman'); // Actual CSS font family const [darkMode, setDarkMode] = useLocalStorage('darkMode', true); // Settings const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isHelpOpen, setIsHelpOpen] = useState(false); const [autoAdvance, setAutoAdvance] = useLocalStorage('autoAdvance', true); const [autoSave, setAutoSave] = useLocalStorage('autoSave', false); const [silenceThreshold, setSilenceThreshold] = useLocalStorage('silenceThreshold', 5); // Navigation & Search const [jumpIndex, setJumpIndex] = useState(''); const [bookmarks, setBookmarks] = useState([]); const [detectedLang, setDetectedLang] = useState('eng'); const [isRTLDir, setIsRTLDir] = useState(false); useEffect(() => { if (sentences.length > 0 && sentences[currentIndex]) { const lang = detectLanguage(sentences[currentIndex]); setDetectedLang(lang); setIsRTLDir(isRTL(lang)); } }, [currentIndex, sentences]); const [searchQuery, setSearchQuery] = useState(''); useEffect(() => { if (darkMode) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }, [darkMode]); useEffect(() => { if (speakerId && datasetName) { fetch(`/api/bookmarks?speaker_id=${speakerId}&dataset_name=${datasetName}`) .then(res => res.json()) .then(data => setBookmarks(data.bookmarks || [])); } }, [speakerId, datasetName]); // Keyboard Shortcuts useKeyboardShortcuts({ 'arrowright': () => handleNext(), 'arrowleft': () => handlePrev(), 'ctrl+s': () => document.getElementById('save-btn')?.click(), ' ': () => document.getElementById('record-btn')?.click(), 'ctrl+f': () => document.getElementById('search-input')?.focus(), }); const handleSentencesLoaded = (loadedSentences: string[]) => { setSentences(loadedSentences); setCurrentIndex(0); }; const handleNext = () => { if (currentIndex < sentences.length - 1) { setCurrentIndex(prev => prev + 1); } else { toast.info('Reached end of sentences'); } }; const handlePrev = () => { if (currentIndex > 0) { setCurrentIndex(prev => prev - 1); } }; const handleJump = (e: React.FormEvent) => { e.preventDefault(); const idx = parseInt(jumpIndex) - 1; if (idx >= 0 && idx < sentences.length) { setCurrentIndex(idx); setJumpIndex(''); } else { toast.error('Invalid sentence number'); } }; const handleSearch = (e: React.FormEvent) => { e.preventDefault(); if (!searchQuery) return; // Find next occurrence after current index let nextIndex = sentences.findIndex((s, i) => i > currentIndex && s.toLowerCase().includes(searchQuery.toLowerCase())); // If not found, wrap around if (nextIndex === -1) { nextIndex = sentences.findIndex((s) => s.toLowerCase().includes(searchQuery.toLowerCase())); } if (nextIndex !== -1) { setCurrentIndex(nextIndex); toast.success(`Found match at #${nextIndex + 1}`); } else { toast.error('No matches found'); } }; const handleSkip = async () => { try { const res = await fetch('/api/skip-recording', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ speaker_id: speakerId, dataset_name: datasetName, index: currentIndex, text: sentences[currentIndex], reason: 'User skipped' }) }); if (res.ok) { toast.info('Sentence skipped'); handleNext(); } else { toast.error('Failed to skip'); } } catch (err) { toast.error('Error skipping'); } }; const handleFontChange = (font: string) => { setFontStyle(font); setFontFamily(font); }; const toggleBookmark = async () => { try { const res = await fetch('/api/bookmarks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ speaker_id: speakerId, dataset_name: datasetName, index: currentIndex }) }); const data = await res.json(); if (data.success) { setBookmarks(data.bookmarks); toast.success(bookmarks.includes(currentIndex) ? 'Bookmark removed' : 'Bookmarked'); } } catch (err) { toast.error('Failed to toggle bookmark'); } }; return (

TTS Dataset Collector

Sentence {currentIndex + 1} / {sentences.length || 0}
{/* Left Sidebar */}
Configuration
setSpeakerId(e.target.value)} />
setDatasetName(e.target.value)} />
{/* Main Content */}
{sentences.length > 0 ? ( {/* Navigation Bar */}
setJumpIndex(e.target.value)} />
setSearchQuery(e.target.value)} />
SENTENCE {currentIndex + 1} {detectedLang}
{sentences[currentIndex]}
{currentIndex < sentences.length - 1 && (
Next Up
{sentences[currentIndex + 1]}
)} { }} onNext={handleNext} onPrev={handlePrev} onSkip={handleSkip} hasPrev={currentIndex > 0} hasNext={currentIndex < sentences.length - 1} autoAdvance={autoAdvance} autoSave={autoSave} silenceThreshold={silenceThreshold} /> ) : (

No Sentences Loaded

Import a text file or paste content to begin your recording session.

)}
setIsSettingsOpen(false)} autoAdvance={autoAdvance} setAutoAdvance={setAutoAdvance} autoSave={autoSave} setAutoSave={setAutoSave} silenceThreshold={silenceThreshold} setSilenceThreshold={setSilenceThreshold} datasetName={datasetName} /> setIsHelpOpen(false)} />
); }