import { setRouteCache } from '@/atoms/route-cache'; import routeCachekey from '@/config/route-cachekey'; import { AlertInfo, IconFont, SpeechContent } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Spin } from 'antd'; import _ from 'lodash'; import React, { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { AUDIO_TEXT_TO_SPEECH_API } from '../apis'; import MessageInput from '../components/message-input'; import RightContainer from '../components/right-container'; import ViewCommonCode from '../components/view-common-code'; import { MessageItem } from '../config/types'; import '../style/ground-llm.less'; import '../style/system-message-wrap.less'; import { TextToSpeechCode } from '../view-code/audio'; import TTSDataForm from './forms/tts-form'; import { useNonStreamTTS, useStreamTTS } from './hooks'; interface MessageProps { modelList: Global.BaseOption[]; loaded?: boolean; ref?: any; } const GroundTTS: React.FC = forwardRef((props, ref) => { const { modelList } = props; const messageId = useRef(0); const [messageList, setMessageList] = useState< { input: string; voice: string; format: string; speed: number; uid: number; autoplay: boolean; audioUrl: string; }[] >([]); const intl = useIntl(); const [parameters, setParams] = useState({ model: '', voice: '', response_format: 'mp3' }); const [show, setShow] = useState(false); const [loading, setLoading] = useState(false); const [tokenResult, setTokenResult] = useState(null); const [collapse, setCollapse] = useState(false); const checkvalueRef = useRef(true); const [currentPrompt, setCurrentPrompt] = useState(''); const formRef = useRef(null); const [playingStream, setPlayingStream] = useState(false); const handleOnPlay = () => { // TODO console.log('Play audio'); }; const handleOnPause = () => { // TODO console.log('Pause audio'); }; const nonStreamTTS = useNonStreamTTS({ onSuccess: (result) => { setMessageList([ { input: currentPrompt, voice: parameters.voice, format: parameters.response_format, speed: parameters.speed, uid: messageId.current, autoplay: checkvalueRef.current, audioUrl: result.url } ]); }, onError: (error) => { setTokenResult({ error: true, errorMessage: error }); setMessageList([]); } }); const playerRef = useRef(null); const streamTTS = useStreamTTS({ autoPlay: checkvalueRef.current, playerRef: playerRef, onUrlReady: (url) => { console.log('onUrlReady called with URL:', url); // Update messageList with stream URL when it's ready setMessageList((prev) => { if (prev.length > 0) { const updated = [...prev]; updated[updated.length - 1].audioUrl = url; return updated; } return prev; }); }, onComplete: (audioUrl) => { console.log('onComplete called with audioUrl:', audioUrl); setMessageList((prev) => { if (prev.length > 0) { const updated = [...prev]; updated[updated.length - 1].audioUrl = audioUrl; return updated; } return prev; }); setPlayingStream(false); }, onError: (error) => { setTokenResult({ error: true, errorMessage: error.message || _.toString(error) }); setMessageList([]); setPlayingStream(false); } }); console.log('isPlaying:', streamTTS.isPlaying); useImperativeHandle(ref, () => { return { viewCode() { setShow(true); }, setCollapse() { setCollapse(!collapse); }, collapse: collapse }; }); const dropEmptyFields = (parameters: Record) => { const fields = [ 'task_type', 'instructions', 'max_new_tokens', 'ref_audio', 'ref_text', 'language', 'x_vector_only_mode' ]; const newParams = { ...parameters }; return _.omitBy(newParams, (value: any, key: string) => { return fields.includes(key) && !value; }); }; const viewCodeContent = useMemo(() => { return TextToSpeechCode({ api: AUDIO_TEXT_TO_SPEECH_API, parameters: { ...dropEmptyFields(parameters), input: currentPrompt } }); }, [parameters, currentPrompt]); const setMessageId = () => { messageId.current = messageId.current + 1; }; const handleStopConversation = () => { nonStreamTTS.abort(); streamTTS.abort(); setLoading(false); }; const handleInputChange = (e: any) => { setCurrentPrompt(e.target.value); }; const submitMessage = async (current?: { role: string; content: string }) => { try { setLoading(true); setMessageId(); setTokenResult(null); const inputText = current?.content || currentPrompt; setCurrentPrompt(inputText); setMessageList([]); setRouteCache(routeCachekey['/playground/speech'], true); const params = { ...dropEmptyFields(parameters), input: inputText }; setParams(params); // Choose stream or non-stream based on parameters if (parameters.stream) { setPlayingStream(true); // Stream mode: create initial message entry, URL will be set via onUrlReady callback setMessageList([ { input: inputText, voice: parameters.voice, format: parameters.response_format, speed: parameters.speed, uid: messageId.current, autoplay: false, audioUrl: '' // Will be updated via onUrlReady callback } ]); await streamTTS.generate(params); } else { // Non-stream mode: get complete audio URL await nonStreamTTS.generate(params); } } catch (error: any) { setPlayingStream(false); console.error('Error generating TTS:', error); setTokenResult({ error: true, errorMessage: error?.message || 'Unknown error' }); } finally { setLoading(false); setRouteCache(routeCachekey['/playground/speech'], false); } }; const handleClear = () => { setMessageId(); setMessageList([]); setTokenResult(null); }; const handleSendMessage = (message: Omit) => { formRef.current?.form.submit(); }; const handleCloseViewCode = () => { setShow(false); }; const updatateParams = (values: Record) => { setParams((pre: any) => { return { ...pre, ...values }; }); }; const handleOnCheckChange = (e: any) => { checkvalueRef.current = e.target.checked; }; return (
{messageList.length ? ( ) : (
{intl.formatMessage({ id: 'playground.audio.texttospeech.tips' })}
)} {loading && (
)}
{tokenResult && (
)}
); }); export default GroundTTS;