| # doc: https://platform.openai.com/docs/guides/text-to-speech | # doc: https://platform.openai.com/docs/guides/text-to-speech | ||||
| credentials_kwargs = self._to_credential_kwargs(credentials) | credentials_kwargs = self._to_credential_kwargs(credentials) | ||||
| client = OpenAI(**credentials_kwargs) | client = OpenAI(**credentials_kwargs) | ||||
| if not voice or voice not in self.get_tts_model_voices(model=model, credentials=credentials): | |||||
| model_support_voice = [x.get("value") for x in self.get_tts_model_voices(model=model, credentials=credentials)] | |||||
| if not voice or voice not in model_support_voice: | |||||
| voice = self._get_model_default_voice(model, credentials) | voice = self._get_model_default_voice(model, credentials) | ||||
| word_limit = self._get_model_word_limit(model, credentials) | word_limit = self._get_model_word_limit(model, credentials) | ||||
| if len(content_text) > word_limit: | if len(content_text) > word_limit: |
| let languageItem = languages.find(item => item.value === textToSpeechConfig.language) | let languageItem = languages.find(item => item.value === textToSpeechConfig.language) | ||||
| const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select') | const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select') | ||||
| if (languages && !languageItem) | |||||
| if (languages && !languageItem && languages.length > 0) | |||||
| languageItem = languages[0] | languageItem = languages[0] | ||||
| const language = languageItem?.value | const language = languageItem?.value | ||||
| const voiceItems = useSWR({ appId, language }, fetchAppVoices).data | const voiceItems = useSWR({ appId, language }, fetchAppVoices).data | ||||
| let voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice) | let voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice) | ||||
| if (voiceItems && !voiceItem) | |||||
| if (voiceItems && !voiceItem && voiceItems.length > 0) | |||||
| voiceItem = voiceItems[0] | voiceItem = voiceItems[0] | ||||
| const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select') | const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select') | ||||
| <div | <div | ||||
| className='mb-2 leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.voice')}</div> | className='mb-2 leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.voice')}</div> | ||||
| <Listbox | <Listbox | ||||
| value={voiceItem} | |||||
| value={voiceItem ?? {}} | |||||
| disabled={!languageItem} | disabled={!languageItem} | ||||
| onChange={(value: Item) => { | onChange={(value: Item) => { | ||||
| if (!value.value) | |||||
| return | |||||
| setTextToSpeechConfig({ | setTextToSpeechConfig({ | ||||
| ...textToSpeechConfig, | ...textToSpeechConfig, | ||||
| voice: String(value.value), | voice: String(value.value), |
| <AudioBtn | <AudioBtn | ||||
| value={languageInfo?.example} | value={languageInfo?.example} | ||||
| isAudition | isAudition | ||||
| voice={textToSpeechConfig.voice} | |||||
| noCache | noCache | ||||
| /> | /> | ||||
| )} | )} |
| import WorkflowProcessItem from '@/app/components/base/chat/chat/answer/workflow-process' | import WorkflowProcessItem from '@/app/components/base/chat/chat/answer/workflow-process' | ||||
| import type { WorkflowProcess } from '@/app/components/base/chat/types' | import type { WorkflowProcess } from '@/app/components/base/chat/types' | ||||
| import type { SiteInfo } from '@/models/share' | import type { SiteInfo } from '@/models/share' | ||||
| import { useChatContext } from '@/app/components/base/chat/chat/context' | |||||
| const MAX_DEPTH = 3 | const MAX_DEPTH = 3 | ||||
| const [childFeedback, setChildFeedback] = useState<Feedbacktype>({ | const [childFeedback, setChildFeedback] = useState<Feedbacktype>({ | ||||
| rating: null, | rating: null, | ||||
| }) | }) | ||||
| const { | |||||
| config, | |||||
| } = useChatContext() | |||||
| const setCurrentLogItem = useAppStore(s => s.setCurrentLogItem) | const setCurrentLogItem = useAppStore(s => s.setCurrentLogItem) | ||||
| const setShowPromptLogModal = useAppStore(s => s.setShowPromptLogModal) | const setShowPromptLogModal = useAppStore(s => s.setShowPromptLogModal) | ||||
| <AudioBtn | <AudioBtn | ||||
| id={messageId!} | id={messageId!} | ||||
| className={'mr-1'} | className={'mr-1'} | ||||
| voice={config?.text_to_speech?.voice} | |||||
| /> | /> | ||||
| </> | </> | ||||
| )} | )} |
| } | } | ||||
| this.msgId = id | this.msgId = id | ||||
| this.audioPlayers = new AudioPlayer(url, isPublic, id, msgContent, callback) | |||||
| this.audioPlayers = new AudioPlayer(url, isPublic, id, msgContent, voice, callback) | |||||
| return this.audioPlayers | return this.audioPlayers | ||||
| } | } | ||||
| } | } |
| isPublic: boolean | isPublic: boolean | ||||
| callback: ((event: string) => {}) | null | callback: ((event: string) => {}) | null | ||||
| constructor(streamUrl: string, isPublic: boolean, msgId: string | undefined, msgContent: string | null | undefined, callback: ((event: string) => {}) | null) { | |||||
| constructor(streamUrl: string, isPublic: boolean, msgId: string | undefined, msgContent: string | null | undefined, voice: string | undefined, callback: ((event: string) => {}) | null) { | |||||
| this.audioContext = new AudioContext() | this.audioContext = new AudioContext() | ||||
| this.msgId = msgId | this.msgId = msgId | ||||
| this.msgContent = msgContent | this.msgContent = msgContent | ||||
| this.url = streamUrl | this.url = streamUrl | ||||
| this.isPublic = isPublic | this.isPublic = isPublic | ||||
| this.voice = voice | |||||
| this.callback = callback | this.callback = callback | ||||
| // Compatible with iphone ios17 ManagedMediaSource | // Compatible with iphone ios17 ManagedMediaSource | ||||
| this.mediaSource?.endOfStream() | this.mediaSource?.endOfStream() | ||||
| clearInterval(endTimer) | clearInterval(endTimer) | ||||
| } | } | ||||
| console.log('finishStream endOfStream endTimer') | |||||
| }, 10) | }, 10) | ||||
| } | } | ||||
| const arrayBuffer = this.cacheBuffers.shift()! | const arrayBuffer = this.cacheBuffers.shift()! | ||||
| this.sourceBuffer?.appendBuffer(arrayBuffer) | this.sourceBuffer?.appendBuffer(arrayBuffer) | ||||
| } | } | ||||
| console.log('finishStream timer') | |||||
| }, 10) | }, 10) | ||||
| } | } | ||||
| } | } | ||||
| const handleToggle = async () => { | const handleToggle = async () => { | ||||
| if (audioState === 'playing' || audioState === 'loading') { | if (audioState === 'playing' || audioState === 'loading') { | ||||
| setAudioState('paused') | |||||
| setTimeout(() => setAudioState('paused'), 1) | |||||
| AudioPlayerManager.getInstance().getAudioPlayer(url, isPublic, id, value, voice, audio_finished_call).pauseAudio() | AudioPlayerManager.getInstance().getAudioPlayer(url, isPublic, id, value, voice, audio_finished_call).pauseAudio() | ||||
| } | } | ||||
| else { | else { | ||||
| setAudioState('loading') | |||||
| setTimeout(() => setAudioState('loading'), 1) | |||||
| AudioPlayerManager.getInstance().getAudioPlayer(url, isPublic, id, value, voice, audio_finished_call).playAudio() | AudioPlayerManager.getInstance().getAudioPlayer(url, isPublic, id, value, voice, audio_finished_call).playAudio() | ||||
| } | } | ||||
| } | } |
| id={id} | id={id} | ||||
| value={content} | value={content} | ||||
| noCache={false} | noCache={false} | ||||
| voice={config?.text_to_speech?.voice} | |||||
| className='hidden group-hover:block' | className='hidden group-hover:block' | ||||
| /> | /> | ||||
| </> | </> |
| <div | <div | ||||
| className='mb-2 leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.voice')}</div> | className='mb-2 leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.voice')}</div> | ||||
| <Listbox | <Listbox | ||||
| value={voiceItem} | |||||
| value={voiceItem ?? {}} | |||||
| disabled={!languageItem} | disabled={!languageItem} | ||||
| onChange={(value: Item) => { | onChange={(value: Item) => { | ||||
| handleChange({ | handleChange({ |