diff --git a/src/App.tsx b/src/App.tsx index 810ef6d..6f03af2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,6 +15,7 @@ import { RegisterPage } from './pages/RegisterPage' import { SchedulersPage } from './pages/SchedulersPage' import { SequencerPage } from './pages/SequencerPage' import { SoundsPage } from './pages/SoundsPage' +import { TTSPage } from './pages/TTSPage' import { SettingsPage } from './pages/admin/SettingsPage' import { UsersPage } from './pages/admin/UsersPage' @@ -112,6 +113,14 @@ function AppRoutes() { } /> + + + + } + /> + diff --git a/src/components/tts/CreateTTSDialog.tsx b/src/components/tts/CreateTTSDialog.tsx new file mode 100644 index 0000000..cdd0891 --- /dev/null +++ b/src/components/tts/CreateTTSDialog.tsx @@ -0,0 +1,299 @@ +import { useState, useEffect } from 'react' + +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Button } from '@/components/ui/button' +import { Textarea } from '@/components/ui/textarea' +import { Switch } from '@/components/ui/switch' +import { Badge } from '@/components/ui/badge' +import { Label } from '@/components/ui/label' +import { Loader2, Mic } from 'lucide-react' +import { ttsService, type TTSProvider } from '@/lib/api/services/tts' +import { toast } from 'sonner' + +interface FormData { + text: string + provider: string + language?: string + tld?: string + slow?: boolean +} + +interface CreateTTSDialogProps { + open: boolean + onOpenChange: (open: boolean) => void +} + +export function CreateTTSDialog({ open, onOpenChange }: CreateTTSDialogProps) { + const [selectedProvider, setSelectedProvider] = useState(null) + const [providers, setProviders] = useState | null>(null) + const [isLoadingProviders, setIsLoadingProviders] = useState(false) + const [isGenerating, setIsGenerating] = useState(false) + const [formErrors, setFormErrors] = useState>({}) + + const [formData, setFormData] = useState({ + text: '', + provider: 'gtts', + language: 'en', + tld: 'com', + slow: false, + }) + + // Load providers when dialog opens + useEffect(() => { + if (open && !providers) { + const loadProviders = async () => { + try { + setIsLoadingProviders(true) + const data = await ttsService.getProviders() + setProviders(data) + } catch (error) { + toast.error('Failed to load TTS providers') + } finally { + setIsLoadingProviders(false) + } + } + loadProviders() + } + }, [open, providers]) + + const generateTTS = async (data: FormData) => { + try { + setIsGenerating(true) + const { text, provider, language, tld, slow } = data + const response = await ttsService.generateTTS({ + text, + provider, + options: { + ...(language && { lang: language }), + ...(tld && { tld }), + ...(slow !== undefined && { slow }), + }, + }) + + toast.success(response.message) + onOpenChange(false) + handleReset() + + // Trigger refresh of parent list if needed + window.dispatchEvent(new CustomEvent('tts-generated')) + } catch (error: any) { + toast.error(error.response?.data?.detail || 'Failed to generate TTS') + } finally { + setIsGenerating(false) + } + } + + // Update selected provider when form provider changes + useEffect(() => { + if (providers && formData.provider) { + setSelectedProvider(providers[formData.provider] || null) + } + }, [formData.provider, providers]) + + const validateForm = (): boolean => { + const errors: Record = {} + + if (!formData.text.trim()) { + errors.text = 'Text is required' + } else if (formData.text.length > 1000) { + errors.text = 'Text must be less than 1000 characters' + } + + if (!formData.provider) { + errors.provider = 'Provider is required' + } + + setFormErrors(errors) + return Object.keys(errors).length === 0 + } + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (validateForm()) { + generateTTS(formData) + } + } + + const handleReset = () => { + setFormData({ + text: '', + provider: 'gtts', + language: 'en', + tld: 'com', + slow: false, + }) + setFormErrors({}) + setSelectedProvider(null) + } + + const handleClose = () => { + if (!isGenerating) { + handleReset() + onOpenChange(false) + } + } + + return ( + + + + + + Generate Text to Speech + + + Convert text to speech using various TTS providers. The audio will be processed and added to your soundboard. + + + +
+
+ +