diff --git a/src/renderer/src/components/TTSButton.tsx b/src/renderer/src/components/TTSButton.tsx new file mode 100644 index 0000000000..36b856a434 --- /dev/null +++ b/src/renderer/src/components/TTSButton.tsx @@ -0,0 +1,64 @@ +import { SoundOutlined } from '@ant-design/icons' +import { Button, Tooltip } from 'antd' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import TTSService from '@renderer/services/TTSService' +import { Message } from '@renderer/types' + +interface TTSButtonProps { + message: Message + className?: string +} + +const TTSButton: React.FC = ({ message, className }) => { + const { t } = useTranslation() + const [isSpeaking, setIsSpeaking] = useState(false) + + const handleTTS = useCallback(async () => { + if (isSpeaking) { + TTSService.stop() + setIsSpeaking(false) + return + } + + setIsSpeaking(true) + try { + await TTSService.speakFromMessage(message) + + // 监听播放结束 + const checkPlayingStatus = () => { + if (!TTSService.isCurrentlyPlaying()) { + setIsSpeaking(false) + clearInterval(checkInterval) + } + } + + const checkInterval = setInterval(checkPlayingStatus, 500) + + // 安全机制,确保即使出错也会重置状态 + setTimeout(() => { + if (isSpeaking) { + TTSService.stop() + setIsSpeaking(false) + clearInterval(checkInterval) + } + }, 30000) // 30秒后检查 + } catch (error) { + console.error('TTS error:', error) + setIsSpeaking(false) + } + }, [isSpeaking, message]) + + return ( + + + + {t('settings.tts.reset_help')} + + + + + + {t('settings.tts.api_settings')} +
+ {/* TTS服务类型选择 */} + + + dispatch(setTtsApiUrl(e.target.value))} + placeholder={t('settings.tts.api_url.placeholder')} + disabled={!ttsEnabled} + /> + + + )} + + {/* Edge TTS设置 */} + {ttsServiceType === 'edge' && ( + + + dispatch(setTtsVoice(value))} + options={ttsCustomVoices.map((voice: any) => { + // 确保voice是字符串 + const voiceStr = typeof voice === 'string' ? voice : String(voice); + return { label: voiceStr, value: voiceStr }; + })} + disabled={!ttsEnabled} + style={{ width: '100%' }} + placeholder={t('settings.tts.voice.placeholder')} + showSearch + optionFilterProp="label" + allowClear + /> + + + {/* 自定义音色列表 */} + + {ttsCustomVoices && ttsCustomVoices.length > 0 ? ( + ttsCustomVoices.map((voice: any, index: number) => { + // 确保voice是字符串 + const voiceStr = typeof voice === 'string' ? voice : String(voice); + return ( + handleRemoveVoice(voiceStr)} + style={{ padding: '4px 8px' }} + > + {voiceStr} + + ); + }) + ) : ( + + {t('settings.tts.voice_empty')} + + )} + + + {/* 添加自定义音色 */} + + + setNewVoice(e.target.value)} + disabled={!ttsEnabled} + style={{ flex: 1 }} + /> + + + + + {/* 模型选择 */} + + setNewModel(e.target.value)} + disabled={!ttsEnabled} + style={{ flex: 1 }} + /> + + + + + )} + + {/* TTS过滤选项 */} + + + dispatch(setTtsFilterOptions({ filterThinkingProcess: checked }))} + disabled={!ttsEnabled} + /> {t('settings.tts.filter.thinking_process')} + + + dispatch(setTtsFilterOptions({ filterMarkdown: checked }))} + disabled={!ttsEnabled} + /> {t('settings.tts.filter.markdown')} + + + dispatch(setTtsFilterOptions({ filterCodeBlocks: checked }))} + disabled={!ttsEnabled} + /> {t('settings.tts.filter.code_blocks')} + + + dispatch(setTtsFilterOptions({ filterHtmlTags: checked }))} + disabled={!ttsEnabled} + /> {t('settings.tts.filter.html_tags')} + + + {t('settings.tts.max_text_length')}: +