diff --git a/app/components/tts-config.tsx b/app/components/tts-config.tsx index 39ae85730c2..e236064acb4 100644 --- a/app/components/tts-config.tsx +++ b/app/components/tts-config.tsx @@ -1,19 +1,76 @@ import { TTSConfig, TTSConfigValidator } from "../store"; +import React, { useState } from "react"; import Locale from "../locales"; import { ListItem, Select } from "./ui-lib"; import { + ModelProvider, DEFAULT_TTS_ENGINE, DEFAULT_TTS_ENGINES, DEFAULT_TTS_MODELS, DEFAULT_TTS_VOICES, } from "../constant"; import { InputRange } from "./input-range"; +import { IconButton } from "./button"; +import SpeakIcon from "../icons/speak.svg"; +import SpeakStopIcon from "../icons/speak-stop.svg"; +import { createTTSPlayer } from "../utils/audio"; +import { useAppConfig } from "../store"; +import { ClientApi } from "../client/api"; +import { showToast } from "../components/ui-lib"; +const ttsPlayer = createTTSPlayer(); export function TTSConfigList(props: { ttsConfig: TTSConfig; updateConfig: (updater: (config: TTSConfig) => void) => void; }) { + const [speechLoading, setSpeechLoading] = useState(false); + const [speechStatus, setSpeechStatus] = useState(false); + + const config = useAppConfig.getState(); + + function stopSpeech() { + ttsPlayer.stop(); + setSpeechStatus(false); + } + + async function playSpeech(text: string, ttsConfig: TTSConfig) { + try { + const api = new ClientApi(ModelProvider.GPT); + setSpeechLoading(true); + ttsPlayer.init(); + + const audioBuffer = await api.llm.speech({ + model: ttsConfig.model, + input: text, + voice: ttsConfig.voice, + speed: ttsConfig.speed, + }); + + setSpeechStatus(true); + await ttsPlayer.play(audioBuffer, () => { + setSpeechStatus(false); + }); + } catch (error) { + console.error("[OpenAI Speech]", error); + setSpeechStatus(false); + // Implement user-facing error notification here + if (typeof (error as Error).message === "string") { + showToast((error as Error).message); + } + } finally { + setSpeechLoading(false); + } + } + + async function openaiSpeech(text: string) { + if (speechStatus) { + stopSpeech(); + } else { + await playSpeech(text, config.ttsConfig); + } + } + return ( <> - { - props.updateConfig( - (config) => - (config.voice = TTSConfigValidator.voice( + + : } + text={ + speechLoading + ? "Loading..." + : speechStatus + ? Locale.Chat.Actions.Stop + : Locale.Chat.Actions.Speech + } + onClick={() => { + openaiSpeech( + "NextChat,Unleash your imagination, experience the future of AI conversation.", + ); + }} + /> + + { + props.updateConfig((config) => { + config.voice = TTSConfigValidator.voice( e.currentTarget.value, - )), - ); - }} - > - {DEFAULT_TTS_VOICES.map((v, i) => ( - - {v} - - ))} - + ); + }); + }} + > + {DEFAULT_TTS_VOICES.map((v, i) => ( + + {v} + + ))} + +