Files
lobe-chat/src/features/AgentSetting/AgentTTS/SelectWithTTSPreview.tsx
CanisMinor 4fa2ef410f feat: support TTS & STT (#443)
*  feat(tts): Add tts and stt basic features

*  feat(tts): Handle error

* 💄 style(tts): Add alert to error handler

* 🐛 fix(tts): Error display

* ♻️ refactor: refactor the openai initial code to the createBizOpenAI

* ♻️ refactor(tts): Refactor header config

*  feat: Add TTS voice preview

* 🐛 fix(tts): Fix header

* 🐛 fix: Fix api

---------

Co-authored-by: Arvin Xu <arvinx@foxmail.com>
2023-11-19 21:43:58 +08:00

120 lines
3.7 KiB
TypeScript

import { AudioPlayer } from '@lobehub/tts/react';
import { Alert, Highlighter } from '@lobehub/ui';
import { Button, RefSelectProps, Select, SelectProps } from 'antd';
import { useTheme } from 'antd-style';
import { forwardRef, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { useTTS } from '@/hooks/useTTS';
import { ChatMessageError } from '@/types/chatMessage';
import { TTSServer } from '@/types/session';
import { getMessageError } from '@/utils/fetch';
interface SelectWithTTSPreviewProps extends SelectProps {
server: TTSServer;
}
const SelectWithTTSPreview = forwardRef<RefSelectProps, SelectWithTTSPreviewProps>(
({ value, options, server, onSelect, ...rest }, ref) => {
const [error, setError] = useState<ChatMessageError>();
const [voice, setVoice] = useState<string>(value);
const { t } = useTranslation('welcome');
const theme = useTheme();
const PREVIEW_TEXT = ['Lobe Chat', t('slogan.title'), t('slogan.desc1')].join('. ');
const setDefaultError = useCallback(
(err?: any) => {
setError({ body: err, message: t('tts.responseError', { ns: 'error' }), type: 500 });
},
[t],
);
const { isGlobalLoading, audio, stop, start, response, setText } = useTTS(PREVIEW_TEXT, {
onError: (err) => {
stop();
setDefaultError(err);
},
onErrorRetry: (err) => {
stop();
setDefaultError(err);
},
onSuccess: async () => {
if (!response) return;
if (response.status === 200) return;
const message = await getMessageError(response);
if (message) {
setError(message);
} else {
setDefaultError();
}
stop();
},
server,
voice,
});
const handleCloseError = useCallback(() => {
setError(undefined);
stop();
}, [stop]);
const handleRetry = useCallback(() => {
setError(undefined);
stop();
start();
}, [stop, start]);
const handleSelect: SelectProps['onSelect'] = (value, option) => {
stop();
setVoice(value as string);
setText([PREVIEW_TEXT, option?.label].join(' - '));
onSelect?.(value, option);
};
return (
<Flexbox gap={8}>
<Flexbox align={'center'} gap={8} horizontal style={{ width: '100%' }}>
<Select onSelect={handleSelect} options={options} ref={ref} value={value} {...rest} />
<AudioPlayer
allowPause={false}
audio={audio}
buttonActive
buttonSize={{ blockSize: 36, fontSize: 16 }}
buttonStyle={{ border: `1px solid ${theme.colorBorder}` }}
isLoading={isGlobalLoading}
onInitPlay={start}
onLoadingStop={stop}
showSlider={false}
showTime={false}
style={{ flex: 'none', padding: 0, width: 'unset' }}
title={t('settingTTS.voice.preview', { ns: 'setting' })}
/>
</Flexbox>
{error && (
<Alert
action={
<Button onClick={handleRetry} size={'small'} type={'primary'}>
{t('retry', { ns: 'common' })}
</Button>
}
closable
extra={
error.body && (
<Highlighter copyButtonSize={'small'} language={'json'} type={'pure'}>
{JSON.stringify(error.body, null, 2)}
</Highlighter>
)
}
message={error.message}
onClose={handleCloseError}
style={{ alignItems: 'center', width: '100%' }}
type="error"
/>
)}
</Flexbox>
);
},
);
export default SelectWithTTSPreview;