/** * SpeechSettings Component * * Settings page for configuring speech preferences per workspace. * Includes STT settings, TTS settings, voice preview, and provider status. * * Follows PDA-friendly design: calm colors, no aggressive language. * * Issue #404 */ "use client"; import { useState, useEffect, useCallback } from "react"; import type { ReactElement } from "react"; import { Card, CardHeader, CardContent, CardTitle, CardDescription } from "@/components/ui/card"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Slider } from "@/components/ui/slider"; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from "@/components/ui/select"; import { getVoices, getHealthStatus } from "@/lib/api/speech"; import type { VoiceInfo, HealthResponse } from "@/lib/api/speech"; import { useTextToSpeech } from "@/hooks/useTextToSpeech"; /** Supported languages for STT */ const STT_LANGUAGES = [ { value: "en", label: "English" }, { value: "es", label: "Spanish" }, { value: "fr", label: "French" }, { value: "de", label: "German" }, { value: "it", label: "Italian" }, { value: "pt", label: "Portuguese" }, { value: "ja", label: "Japanese" }, { value: "zh", label: "Chinese" }, { value: "ko", label: "Korean" }, { value: "auto", label: "Auto-detect" }, ]; /** TTS tier options */ const TIER_OPTIONS = [ { value: "default", label: "Default" }, { value: "premium", label: "Premium" }, { value: "fallback", label: "Fallback" }, ]; /** Sample text for voice preview */ const PREVIEW_TEXT = "Hello, this is a preview of the selected voice. How does it sound?"; /** * SpeechSettings provides a comprehensive settings interface for * configuring speech-to-text and text-to-speech preferences. */ export function SpeechSettings(): ReactElement { // STT state const [sttEnabled, setSttEnabled] = useState(true); const [sttLanguage, setSttLanguage] = useState("en"); // TTS state const [ttsEnabled, setTtsEnabled] = useState(true); const [selectedVoice, setSelectedVoice] = useState(""); const [selectedTier, setSelectedTier] = useState("default"); const [autoPlay, setAutoPlay] = useState(false); const [speed, setSpeed] = useState(1.0); // Data state const [voices, setVoices] = useState([]); const [voicesError, setVoicesError] = useState(null); const [healthData, setHealthData] = useState(null); const [healthError, setHealthError] = useState(null); // Preview hook const { synthesize, audioUrl, isLoading: isPreviewLoading, error: previewError, } = useTextToSpeech(); /** * Fetch available voices from the API */ const fetchVoices = useCallback(async (): Promise => { try { setVoicesError(null); const response = await getVoices(); setVoices(response.data); // Select the first default voice if none selected if (response.data.length > 0 && !selectedVoice) { const defaultVoice = response.data.find((v) => v.isDefault); const firstVoice = response.data[0]; setSelectedVoice(defaultVoice?.id ?? firstVoice?.id ?? ""); } } catch { setVoicesError("Unable to load voices. Please try again later."); } }, [selectedVoice]); /** * Fetch health status from the API */ const fetchHealth = useCallback(async (): Promise => { try { setHealthError(null); const response = await getHealthStatus(); setHealthData(response.data); } catch { setHealthError("Unable to check provider status. Please try again later."); } }, []); // Fetch voices and health on mount useEffect(() => { void fetchVoices(); void fetchHealth(); }, [fetchVoices, fetchHealth]); /** * Handle voice preview test */ const handleTestVoice = useCallback(async (): Promise => { const options: Record = { speed, tier: selectedTier, }; if (selectedVoice) { options.voice = selectedVoice; } await synthesize(PREVIEW_TEXT, options); }, [synthesize, selectedVoice, speed, selectedTier]); return (

Speech Settings

Configure voice input and output preferences for your workspace

{/* STT Settings */} Speech-to-Text Configure voice input preferences
{/* Enable STT Toggle */}

Allow voice input for text fields and commands

{/* Language Preference */}
{/* TTS Settings */} Text-to-Speech Configure voice output preferences
{/* Enable TTS Toggle */}

Allow reading content aloud with synthesized voice

{/* Default Voice Selector */}
{voicesError ? (

{voicesError}

) : ( )}
{/* Provider Tier Preference */}

Choose the preferred quality tier for voice synthesis

{/* Auto-play Toggle */}

Automatically play TTS responses when received

{/* Speed Control */}
{speed.toFixed(1)}x
{ const newSpeed = values[0]; if (newSpeed !== undefined) { setSpeed(newSpeed); } }} />
0.5x 1.0x 2.0x
{/* Voice Preview */} Voice Preview Preview the selected voice with sample text

“{PREVIEW_TEXT}”

{previewError &&

{previewError}

} {audioUrl && ( )}
{/* Provider Status */} Provider Status Current availability of speech service providers
{healthError ? (

{healthError}

) : healthData ? ( <> {/* STT Provider */}
Speech-to-Text Provider
{healthData.stt.available ? ( <> Active ) : ( <> Inactive )}
{/* TTS Provider */}
Text-to-Speech Provider
{healthData.tts.available ? ( <> Active ) : ( <> Inactive )}
) : (

Checking provider status...

)}
); } export default SpeechSettings;