'use client'; import { useState, useEffect } from 'react'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { useI18n } from '@/lib/hooks/use-i18n'; import { useSettingsStore } from '@/lib/store/settings'; import { TTS_PROVIDERS, DEFAULT_TTS_VOICES } from '@/lib/audio/constants'; import type { TTSProviderId } from '@/lib/audio/types'; import { Volume2, Loader2, CheckCircle2, XCircle, Eye, EyeOff } from 'lucide-react'; import { cn } from '@/lib/utils'; import { createLogger } from '@/lib/logger'; import { useTTSPreview } from '@/lib/audio/use-tts-preview'; const log = createLogger('TTSSettings'); interface TTSSettingsProps { selectedProviderId: TTSProviderId; } export function TTSSettings({ selectedProviderId }: TTSSettingsProps) { const { t } = useI18n(); const ttsVoice = useSettingsStore((state) => state.ttsVoice); const ttsSpeed = useSettingsStore((state) => state.ttsSpeed); const ttsProvidersConfig = useSettingsStore((state) => state.ttsProvidersConfig); const setTTSProviderConfig = useSettingsStore((state) => state.setTTSProviderConfig); const activeProviderId = useSettingsStore((state) => state.ttsProviderId); // When testing a non-active provider, use that provider's default voice // instead of the active provider's voice (which may be incompatible). const effectiveVoice = selectedProviderId === activeProviderId ? ttsVoice : DEFAULT_TTS_VOICES[selectedProviderId] || 'default'; const ttsProvider = TTS_PROVIDERS[selectedProviderId] ?? TTS_PROVIDERS['openai-tts']; const isServerConfigured = !!ttsProvidersConfig[selectedProviderId]?.isServerConfigured; const [showApiKey, setShowApiKey] = useState(false); const [testText, setTestText] = useState(t('settings.ttsTestTextDefault')); const [testStatus, setTestStatus] = useState<'idle' | 'testing' | 'success' | 'error'>('idle'); const [testMessage, setTestMessage] = useState(''); const { previewing: testingTTS, startPreview, stopPreview } = useTTSPreview(); // Doubao TTS uses compound "appId:accessKey" — split for separate UI fields const isDoubao = selectedProviderId === 'doubao-tts'; const rawApiKey = ttsProvidersConfig[selectedProviderId]?.apiKey || ''; const doubaoColonIdx = rawApiKey.indexOf(':'); const doubaoAppId = isDoubao && doubaoColonIdx > 0 ? rawApiKey.slice(0, doubaoColonIdx) : ''; const doubaoAccessKey = isDoubao && doubaoColonIdx > 0 ? rawApiKey.slice(doubaoColonIdx + 1) : isDoubao ? rawApiKey : ''; const setDoubaoCompoundKey = (appId: string, accessKey: string) => { const combined = appId && accessKey ? `${appId}:${accessKey}` : appId || accessKey; setTTSProviderConfig(selectedProviderId, { apiKey: combined }); }; // Keep the sample text in sync with locale changes. useEffect(() => { setTestText(t('settings.ttsTestTextDefault')); }, [t]); // Reset transient UI state when switching providers. useEffect(() => { stopPreview(); setShowApiKey(false); setTestStatus('idle'); setTestMessage(''); }, [selectedProviderId, stopPreview]); const handleTestTTS = async () => { if (!testText.trim()) return; setTestStatus('testing'); setTestMessage(''); try { await startPreview({ text: testText, providerId: selectedProviderId, modelId: ttsProvidersConfig[selectedProviderId]?.modelId || ttsProvider.defaultModelId, voice: effectiveVoice, speed: ttsSpeed, apiKey: ttsProvidersConfig[selectedProviderId]?.apiKey, baseUrl: ttsProvidersConfig[selectedProviderId]?.baseUrl, }); setTestStatus('success'); setTestMessage(t('settings.ttsTestSuccess')); } catch (error) { log.error('TTS test failed:', error); setTestStatus('error'); setTestMessage( error instanceof Error && error.message ? `${t('settings.ttsTestFailed')}: ${error.message}` : t('settings.ttsTestFailed'), ); } }; return (
{t('settings.requestUrl')}: {effectiveBaseUrl + endpointPath}
); })()} > )} {/* Test TTS */}{testMessage}
{t('settings.modelSelectedViaVoice')}