@lobehub/chat 0.101.6 → 0.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.i18nrc.js +1 -1
- package/CHANGELOG.md +50 -0
- package/README.md +38 -4
- package/README.zh-CN.md +41 -9
- package/__mocks__/zustand/traditional.ts +4 -4
- package/locales/en_US/chat.json +11 -1
- package/locales/en_US/error.json +6 -0
- package/locales/en_US/setting.json +40 -1
- package/locales/ja_JP/chat.json +11 -1
- package/locales/ja_JP/error.json +6 -0
- package/locales/ja_JP/setting.json +40 -1
- package/locales/ko_KR/chat.json +11 -1
- package/locales/ko_KR/error.json +6 -0
- package/locales/ko_KR/setting.json +40 -1
- package/locales/ru_RU/chat.json +11 -1
- package/locales/ru_RU/error.json +6 -0
- package/locales/ru_RU/setting.json +40 -1
- package/locales/zh_CN/chat.json +10 -1
- package/locales/zh_CN/error.json +6 -0
- package/locales/zh_CN/setting.json +36 -1
- package/locales/zh_TW/chat.json +11 -1
- package/locales/zh_TW/error.json +6 -0
- package/locales/zh_TW/setting.json +40 -1
- package/next.config.mjs +2 -20
- package/package.json +4 -3
- package/src/app/api/openai/chat/route.ts +5 -41
- package/src/app/api/openai/{createAzureOpenai.ts → createBizOpenAI/createAzureOpenai.ts} +1 -1
- package/src/app/api/openai/{createOpenai.ts → createBizOpenAI/createOpenai.ts} +1 -1
- package/src/app/api/openai/createBizOpenAI/index.ts +46 -0
- package/src/app/api/openai/models/route.ts +1 -1
- package/src/app/api/openai/stt/route.ts +29 -0
- package/src/app/api/openai/tts/route.ts +17 -0
- package/src/app/api/tts/edge-speech/route.ts +9 -0
- package/src/app/api/tts/microsoft-speech/route.ts +9 -0
- package/src/app/chat/(desktop)/features/ChatHeader.tsx +3 -2
- package/src/app/chat/(desktop)/features/ChatInput/Footer.tsx +62 -0
- package/src/app/chat/(desktop)/features/ChatInput/index.tsx +0 -1
- package/src/app/chat/(desktop)/features/SessionHeader.tsx +2 -2
- package/src/app/chat/(desktop)/features/SideBar.tsx +7 -1
- package/src/app/chat/(mobile)/features/ChatHeader.tsx +29 -11
- package/src/app/chat/(mobile)/features/ChatInput/Mobile.tsx +20 -4
- package/src/app/chat/features/ChatHeader/ShareButton/index.tsx +23 -15
- package/src/app/chat/features/ChatInput/ActionBar/config.ts +16 -6
- package/src/app/chat/features/ChatInput/ActionBar/index.tsx +40 -16
- package/src/app/chat/features/ChatInput/STT/index.tsx +146 -0
- package/src/app/chat/features/ChatInput/Topic/index.tsx +1 -3
- package/src/app/chat/features/Conversation/ChatList/Actions/Assistant.tsx +2 -2
- package/src/app/chat/features/Conversation/ChatList/Actions/User.tsx +2 -2
- package/src/app/chat/features/Conversation/ChatList/Actions/customAction.ts +9 -2
- package/src/app/chat/features/Conversation/ChatList/Actions/index.ts +8 -1
- package/src/app/chat/features/Conversation/ChatList/Extras/Assistant.tsx +33 -27
- package/src/app/chat/features/Conversation/ChatList/Extras/AudioPlayer.tsx +139 -0
- package/src/app/chat/features/Conversation/ChatList/Extras/ExtraContainer.tsx +13 -0
- package/src/app/chat/features/Conversation/ChatList/Extras/TTS.tsx +122 -0
- package/src/app/chat/features/Conversation/ChatList/Extras/User.tsx +18 -11
- package/src/app/chat/settings/features/HeaderContent.tsx +6 -4
- package/src/app/chat/settings/features/SubmitAgentButton/index.tsx +2 -3
- package/src/app/settings/features/SideBar/List.tsx +2 -1
- package/src/app/settings/features/SideBar/index.tsx +1 -1
- package/src/app/settings/features/UpgradeAlert.tsx +1 -1
- package/src/app/settings/tts/TTS/index.tsx +73 -0
- package/src/app/settings/tts/TTS/options.ts +30 -0
- package/src/app/settings/tts/index.tsx +21 -0
- package/src/app/settings/tts/layout.tsx +1 -0
- package/src/app/settings/tts/page.tsx +3 -0
- package/src/components/HotKeys/index.tsx +4 -7
- package/src/const/layoutTokens.ts +3 -0
- package/src/const/settings.ts +22 -1
- package/src/features/AgentSetting/AgentTTS/SelectWithTTSPreview.tsx +119 -0
- package/src/features/AgentSetting/AgentTTS/index.tsx +115 -0
- package/src/features/AgentSetting/AgentTTS/options.ts +16 -0
- package/src/features/AgentSetting/index.tsx +2 -1
- package/src/hooks/useSTT.ts +61 -0
- package/src/hooks/useTTS.ts +87 -0
- package/src/locales/default/chat.ts +10 -1
- package/src/locales/default/error.ts +6 -0
- package/src/locales/default/setting.ts +35 -1
- package/src/services/_url.ts +7 -0
- package/src/store/global/initialState.ts +1 -0
- package/src/store/global/selectors/settings.test.ts +32 -0
- package/src/store/global/selectors/settings.ts +9 -1
- package/src/store/session/slices/agentConfig/selectors.ts +9 -0
- package/src/store/session/slices/chat/actions/translate.ts +24 -1
- package/src/store/session/slices/chat/selectors/utils.test.ts +16 -0
- package/src/store/session/slices/session/reducers/session.test.ts +24 -0
- package/src/types/chatMessage.ts +6 -0
- package/src/types/session.ts +17 -0
- package/src/types/settings.ts +13 -2
- package/src/app/chat/(mobile)/features/ChatInput/style.mobile.ts +0 -19
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { AudioPlayer } from '@lobehub/tts/react';
|
|
2
|
+
import { Alert, Highlighter } from '@lobehub/ui';
|
|
3
|
+
import { Button, RefSelectProps, Select, SelectProps } from 'antd';
|
|
4
|
+
import { useTheme } from 'antd-style';
|
|
5
|
+
import { forwardRef, useCallback, useState } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { Flexbox } from 'react-layout-kit';
|
|
8
|
+
|
|
9
|
+
import { useTTS } from '@/hooks/useTTS';
|
|
10
|
+
import { ChatMessageError } from '@/types/chatMessage';
|
|
11
|
+
import { TTSServer } from '@/types/session';
|
|
12
|
+
import { getMessageError } from '@/utils/fetch';
|
|
13
|
+
|
|
14
|
+
interface SelectWithTTSPreviewProps extends SelectProps {
|
|
15
|
+
server: TTSServer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SelectWithTTSPreview = forwardRef<RefSelectProps, SelectWithTTSPreviewProps>(
|
|
19
|
+
({ value, options, server, onSelect, ...rest }, ref) => {
|
|
20
|
+
const [error, setError] = useState<ChatMessageError>();
|
|
21
|
+
const [voice, setVoice] = useState<string>(value);
|
|
22
|
+
const { t } = useTranslation('welcome');
|
|
23
|
+
const theme = useTheme();
|
|
24
|
+
const PREVIEW_TEXT = ['Lobe Chat', t('slogan.title'), t('slogan.desc1')].join('. ');
|
|
25
|
+
|
|
26
|
+
const setDefaultError = useCallback(
|
|
27
|
+
(err?: any) => {
|
|
28
|
+
setError({ body: err, message: t('tts.responseError', { ns: 'error' }), type: 500 });
|
|
29
|
+
},
|
|
30
|
+
[t],
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const { isGlobalLoading, audio, stop, start, response, setText } = useTTS(PREVIEW_TEXT, {
|
|
34
|
+
onError: (err) => {
|
|
35
|
+
stop();
|
|
36
|
+
setDefaultError(err);
|
|
37
|
+
},
|
|
38
|
+
onErrorRetry: (err) => {
|
|
39
|
+
stop();
|
|
40
|
+
setDefaultError(err);
|
|
41
|
+
},
|
|
42
|
+
onSuccess: async () => {
|
|
43
|
+
if (!response) return;
|
|
44
|
+
if (response.status === 200) return;
|
|
45
|
+
const message = await getMessageError(response);
|
|
46
|
+
if (message) {
|
|
47
|
+
setError(message);
|
|
48
|
+
} else {
|
|
49
|
+
setDefaultError();
|
|
50
|
+
}
|
|
51
|
+
stop();
|
|
52
|
+
},
|
|
53
|
+
server,
|
|
54
|
+
voice,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const handleCloseError = useCallback(() => {
|
|
58
|
+
setError(undefined);
|
|
59
|
+
stop();
|
|
60
|
+
}, [stop]);
|
|
61
|
+
|
|
62
|
+
const handleRetry = useCallback(() => {
|
|
63
|
+
setError(undefined);
|
|
64
|
+
stop();
|
|
65
|
+
start();
|
|
66
|
+
}, [stop, start]);
|
|
67
|
+
|
|
68
|
+
const handleSelect: SelectProps['onSelect'] = (value, option) => {
|
|
69
|
+
stop();
|
|
70
|
+
setVoice(value as string);
|
|
71
|
+
setText([PREVIEW_TEXT, option?.label].join(' - '));
|
|
72
|
+
onSelect?.(value, option);
|
|
73
|
+
};
|
|
74
|
+
return (
|
|
75
|
+
<Flexbox gap={8}>
|
|
76
|
+
<Flexbox align={'center'} gap={8} horizontal style={{ width: '100%' }}>
|
|
77
|
+
<Select onSelect={handleSelect} options={options} ref={ref} value={value} {...rest} />
|
|
78
|
+
<AudioPlayer
|
|
79
|
+
allowPause={false}
|
|
80
|
+
audio={audio}
|
|
81
|
+
buttonActive
|
|
82
|
+
buttonSize={{ blockSize: 36, fontSize: 16 }}
|
|
83
|
+
buttonStyle={{ border: `1px solid ${theme.colorBorder}` }}
|
|
84
|
+
isLoading={isGlobalLoading}
|
|
85
|
+
onInitPlay={start}
|
|
86
|
+
onLoadingStop={stop}
|
|
87
|
+
showSlider={false}
|
|
88
|
+
showTime={false}
|
|
89
|
+
style={{ flex: 'none', padding: 0, width: 'unset' }}
|
|
90
|
+
title={t('settingTTS.voice.preview', { ns: 'setting' })}
|
|
91
|
+
/>
|
|
92
|
+
</Flexbox>
|
|
93
|
+
{error && (
|
|
94
|
+
<Alert
|
|
95
|
+
action={
|
|
96
|
+
<Button onClick={handleRetry} size={'small'} type={'primary'}>
|
|
97
|
+
{t('retry', { ns: 'common' })}
|
|
98
|
+
</Button>
|
|
99
|
+
}
|
|
100
|
+
closable
|
|
101
|
+
extra={
|
|
102
|
+
error.body && (
|
|
103
|
+
<Highlighter copyButtonSize={'small'} language={'json'} type={'pure'}>
|
|
104
|
+
{JSON.stringify(error.body, null, 2)}
|
|
105
|
+
</Highlighter>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
message={error.message}
|
|
109
|
+
onClose={handleCloseError}
|
|
110
|
+
style={{ alignItems: 'center', width: '100%' }}
|
|
111
|
+
type="error"
|
|
112
|
+
/>
|
|
113
|
+
)}
|
|
114
|
+
</Flexbox>
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
export default SelectWithTTSPreview;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { VoiceList } from '@lobehub/tts';
|
|
2
|
+
import { Form, ItemGroup } from '@lobehub/ui';
|
|
3
|
+
import { Form as AFrom, Select, Switch } from 'antd';
|
|
4
|
+
import isEqual from 'fast-deep-equal';
|
|
5
|
+
import { debounce } from 'lodash-es';
|
|
6
|
+
import { Mic } from 'lucide-react';
|
|
7
|
+
import { memo, useEffect } from 'react';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
|
|
10
|
+
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
11
|
+
import SelectWithTTSPreview from '@/features/AgentSetting/AgentTTS/SelectWithTTSPreview';
|
|
12
|
+
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
13
|
+
|
|
14
|
+
import { useStore } from '../store';
|
|
15
|
+
import { ttsOptions } from './options';
|
|
16
|
+
|
|
17
|
+
const TTS_SETTING_KEY = 'tts';
|
|
18
|
+
const { openaiVoiceOptions, localeOptions } = VoiceList;
|
|
19
|
+
|
|
20
|
+
const AgentTTS = memo(() => {
|
|
21
|
+
const { t } = useTranslation('setting');
|
|
22
|
+
const updateConfig = useStore((s) => s.setAgentConfig);
|
|
23
|
+
const [form] = AFrom.useForm();
|
|
24
|
+
const voiceList = useGlobalStore((s) => {
|
|
25
|
+
const locale = settingsSelectors.currentLanguage(s);
|
|
26
|
+
return (all?: boolean) => new VoiceList(all ? undefined : locale);
|
|
27
|
+
});
|
|
28
|
+
const config = useStore((s) => s.config, isEqual);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
form.setFieldsValue(config);
|
|
32
|
+
}, [config]);
|
|
33
|
+
|
|
34
|
+
const showAllLocaleVoice = config.tts.showAllLocaleVoice;
|
|
35
|
+
|
|
36
|
+
const { edgeVoiceOptions, microsoftVoiceOptions } = voiceList(showAllLocaleVoice);
|
|
37
|
+
|
|
38
|
+
const tts: ItemGroup = {
|
|
39
|
+
children: [
|
|
40
|
+
{
|
|
41
|
+
children: <Select options={ttsOptions} />,
|
|
42
|
+
desc: t('settingTTS.ttsService.desc'),
|
|
43
|
+
label: t('settingTTS.ttsService.title'),
|
|
44
|
+
name: [TTS_SETTING_KEY, 'ttsService'],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
children: <Switch />,
|
|
48
|
+
desc: t('settingTTS.showAllLocaleVoice.desc'),
|
|
49
|
+
hidden: config.tts.ttsService === 'openai',
|
|
50
|
+
label: t('settingTTS.showAllLocaleVoice.title'),
|
|
51
|
+
minWidth: undefined,
|
|
52
|
+
name: [TTS_SETTING_KEY, 'showAllLocaleVoice'],
|
|
53
|
+
valuePropName: 'checked',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
children: <SelectWithTTSPreview options={openaiVoiceOptions} server={'openai'} />,
|
|
57
|
+
desc: t('settingTTS.voice.desc'),
|
|
58
|
+
hidden: config.tts.ttsService !== 'openai',
|
|
59
|
+
label: t('settingTTS.voice.title'),
|
|
60
|
+
name: [TTS_SETTING_KEY, 'voice', 'openai'],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
children: <SelectWithTTSPreview options={edgeVoiceOptions} server={'edge'} />,
|
|
64
|
+
desc: t('settingTTS.voice.desc'),
|
|
65
|
+
divider: false,
|
|
66
|
+
hidden: config.tts.ttsService !== 'edge',
|
|
67
|
+
label: t('settingTTS.voice.title'),
|
|
68
|
+
name: [TTS_SETTING_KEY, 'voice', 'edge'],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
children: <SelectWithTTSPreview options={microsoftVoiceOptions} server={'microsoft'} />,
|
|
72
|
+
desc: t('settingTTS.voice.desc'),
|
|
73
|
+
divider: false,
|
|
74
|
+
hidden: config.tts.ttsService !== 'microsoft',
|
|
75
|
+
label: t('settingTTS.voice.title'),
|
|
76
|
+
name: [TTS_SETTING_KEY, 'voice', 'microsoft'],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
children: (
|
|
80
|
+
<Select
|
|
81
|
+
options={[
|
|
82
|
+
{ label: t('settingTheme.lang.autoMode'), value: 'auto' },
|
|
83
|
+
...(localeOptions || []),
|
|
84
|
+
]}
|
|
85
|
+
/>
|
|
86
|
+
),
|
|
87
|
+
desc: t('settingTTS.sttLocale.desc'),
|
|
88
|
+
label: t('settingTTS.sttLocale.title'),
|
|
89
|
+
name: [TTS_SETTING_KEY, 'sttLocale'],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
icon: Mic,
|
|
93
|
+
title: t('settingTTS.title'),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Form
|
|
98
|
+
form={form}
|
|
99
|
+
initialValues={{
|
|
100
|
+
[TTS_SETTING_KEY]: {
|
|
101
|
+
voice: {
|
|
102
|
+
edge: edgeVoiceOptions?.[0].value,
|
|
103
|
+
microsoft: microsoftVoiceOptions?.[0].value,
|
|
104
|
+
openai: openaiVoiceOptions?.[0].value,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}}
|
|
108
|
+
items={[tts]}
|
|
109
|
+
onValuesChange={debounce(updateConfig, 100)}
|
|
110
|
+
{...FORM_STYLE}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export default AgentTTS;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SelectProps } from 'antd';
|
|
2
|
+
|
|
3
|
+
export const ttsOptions: SelectProps['options'] = [
|
|
4
|
+
{
|
|
5
|
+
label: 'OpenAI',
|
|
6
|
+
value: 'openai',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
label: 'Edge Speech',
|
|
10
|
+
value: 'edge',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'Microsoft Speech',
|
|
14
|
+
value: 'microsoft',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
@@ -4,6 +4,7 @@ import AgentConfig from './AgentConfig';
|
|
|
4
4
|
import AgentMeta from './AgentMeta';
|
|
5
5
|
import AgentPlugin from './AgentPlugin';
|
|
6
6
|
import AgentPrompt from './AgentPrompt';
|
|
7
|
+
import AgentTTS from './AgentTTS';
|
|
7
8
|
import StoreUpdater, { StoreUpdaterProps } from './StoreUpdater';
|
|
8
9
|
import { Provider, createStore } from './store';
|
|
9
10
|
|
|
@@ -13,10 +14,10 @@ const AgentSettings = memo<AgentSettingsProps>((props) => {
|
|
|
13
14
|
return (
|
|
14
15
|
<Provider createStore={createStore}>
|
|
15
16
|
<StoreUpdater {...props} />
|
|
16
|
-
|
|
17
17
|
<AgentPrompt />
|
|
18
18
|
<AgentMeta />
|
|
19
19
|
<AgentConfig />
|
|
20
|
+
<AgentTTS />
|
|
20
21
|
<AgentPlugin />
|
|
21
22
|
</Provider>
|
|
22
23
|
);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getRecordMineType } from '@lobehub/tts';
|
|
2
|
+
import {
|
|
3
|
+
OpenAISTTOptions,
|
|
4
|
+
SpeechRecognitionOptions,
|
|
5
|
+
useOpenAISTT,
|
|
6
|
+
useSpeechRecognition,
|
|
7
|
+
} from '@lobehub/tts/react';
|
|
8
|
+
import isEqual from 'fast-deep-equal';
|
|
9
|
+
import { SWRConfiguration } from 'swr';
|
|
10
|
+
|
|
11
|
+
import { createHeaderWithOpenAI } from '@/services/_header';
|
|
12
|
+
import { OPENAI_URLS } from '@/services/_url';
|
|
13
|
+
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
14
|
+
import { useSessionStore } from '@/store/session';
|
|
15
|
+
import { agentSelectors } from '@/store/session/slices/agentConfig';
|
|
16
|
+
|
|
17
|
+
interface STTConfig extends SWRConfiguration {
|
|
18
|
+
onTextChange: (value: string) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const useSTT = (config: STTConfig) => {
|
|
22
|
+
const ttsSettings = useGlobalStore(settingsSelectors.currentTTS, isEqual);
|
|
23
|
+
const ttsAgentSettings = useSessionStore(agentSelectors.currentAgentTTS, isEqual);
|
|
24
|
+
const locale = useGlobalStore(settingsSelectors.currentLanguage);
|
|
25
|
+
|
|
26
|
+
const autoStop = ttsSettings.sttAutoStop;
|
|
27
|
+
const sttLocale =
|
|
28
|
+
ttsAgentSettings?.sttLocale && ttsAgentSettings.sttLocale !== 'auto'
|
|
29
|
+
? ttsAgentSettings.sttLocale
|
|
30
|
+
: locale;
|
|
31
|
+
|
|
32
|
+
let useSelectedSTT;
|
|
33
|
+
let options: any = {};
|
|
34
|
+
|
|
35
|
+
switch (ttsSettings.sttServer) {
|
|
36
|
+
case 'openai': {
|
|
37
|
+
useSelectedSTT = useOpenAISTT;
|
|
38
|
+
options = {
|
|
39
|
+
api: {
|
|
40
|
+
headers: createHeaderWithOpenAI(),
|
|
41
|
+
serviceUrl: OPENAI_URLS.stt,
|
|
42
|
+
},
|
|
43
|
+
autoStop,
|
|
44
|
+
options: {
|
|
45
|
+
mineType: getRecordMineType(),
|
|
46
|
+
model: ttsSettings.openAI.sttModel,
|
|
47
|
+
},
|
|
48
|
+
} as OpenAISTTOptions;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case 'browser': {
|
|
52
|
+
options = {
|
|
53
|
+
autoStop,
|
|
54
|
+
} as SpeechRecognitionOptions;
|
|
55
|
+
useSelectedSTT = useSpeechRecognition;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return useSelectedSTT(sttLocale, { ...config, ...options });
|
|
61
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { VoiceList } from '@lobehub/tts';
|
|
2
|
+
import {
|
|
3
|
+
EdgeSpeechOptions,
|
|
4
|
+
MicrosoftSpeechOptions,
|
|
5
|
+
OpenAITTSOptions,
|
|
6
|
+
useEdgeSpeech,
|
|
7
|
+
useMicrosoftSpeech,
|
|
8
|
+
useOpenAITTS,
|
|
9
|
+
} from '@lobehub/tts/react';
|
|
10
|
+
import isEqual from 'fast-deep-equal';
|
|
11
|
+
import { SWRConfiguration } from 'swr';
|
|
12
|
+
|
|
13
|
+
import { createHeaderWithOpenAI } from '@/services/_header';
|
|
14
|
+
import { OPENAI_URLS, TTS_URL } from '@/services/_url';
|
|
15
|
+
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
16
|
+
import { useSessionStore } from '@/store/session';
|
|
17
|
+
import { agentSelectors } from '@/store/session/slices/agentConfig';
|
|
18
|
+
import { TTSServer } from '@/types/session';
|
|
19
|
+
|
|
20
|
+
interface TTSConfig extends SWRConfiguration {
|
|
21
|
+
server?: TTSServer;
|
|
22
|
+
voice?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const useTTS = (content: string, config?: TTSConfig) => {
|
|
26
|
+
const ttsSettings = useGlobalStore(settingsSelectors.currentTTS, isEqual);
|
|
27
|
+
const ttsAgentSettings = useSessionStore(agentSelectors.currentAgentTTS, isEqual);
|
|
28
|
+
const voiceList = useGlobalStore((s) => new VoiceList(settingsSelectors.currentLanguage(s)));
|
|
29
|
+
|
|
30
|
+
let useSelectedTTS;
|
|
31
|
+
let options: any = {};
|
|
32
|
+
switch (config?.server || ttsAgentSettings.ttsService) {
|
|
33
|
+
case 'openai': {
|
|
34
|
+
useSelectedTTS = useOpenAITTS;
|
|
35
|
+
options = {
|
|
36
|
+
api: {
|
|
37
|
+
headers: createHeaderWithOpenAI(),
|
|
38
|
+
serviceUrl: OPENAI_URLS.tts,
|
|
39
|
+
},
|
|
40
|
+
options: {
|
|
41
|
+
model: ttsSettings.openAI.ttsModel,
|
|
42
|
+
voice:
|
|
43
|
+
config?.voice ||
|
|
44
|
+
ttsAgentSettings.voice.openai ||
|
|
45
|
+
VoiceList.openaiVoiceOptions?.[0].value,
|
|
46
|
+
},
|
|
47
|
+
} as OpenAITTSOptions;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'edge': {
|
|
51
|
+
useSelectedTTS = useEdgeSpeech;
|
|
52
|
+
options = {
|
|
53
|
+
api: {
|
|
54
|
+
/**
|
|
55
|
+
* @description client fetch
|
|
56
|
+
* serviceUrl: TTS_URL.edge,
|
|
57
|
+
*/
|
|
58
|
+
},
|
|
59
|
+
options: {
|
|
60
|
+
voice:
|
|
61
|
+
config?.voice || ttsAgentSettings.voice.edge || voiceList.edgeVoiceOptions?.[0].value,
|
|
62
|
+
},
|
|
63
|
+
} as EdgeSpeechOptions;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case 'microsoft': {
|
|
67
|
+
useSelectedTTS = useMicrosoftSpeech;
|
|
68
|
+
options = {
|
|
69
|
+
api: {
|
|
70
|
+
serviceUrl: TTS_URL.microsoft,
|
|
71
|
+
},
|
|
72
|
+
options: {
|
|
73
|
+
voice:
|
|
74
|
+
config?.voice ||
|
|
75
|
+
ttsAgentSettings.voice.microsoft ||
|
|
76
|
+
voiceList.microsoftVoiceOptions?.[0].value,
|
|
77
|
+
},
|
|
78
|
+
} as MicrosoftSpeechOptions;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return useSelectedTTS(content, {
|
|
84
|
+
...config,
|
|
85
|
+
...options,
|
|
86
|
+
});
|
|
87
|
+
};
|
|
@@ -38,6 +38,11 @@ export default {
|
|
|
38
38
|
withSystemRole: '包含助手角色设定',
|
|
39
39
|
},
|
|
40
40
|
stop: '停止',
|
|
41
|
+
stt: {
|
|
42
|
+
action: '语音输入',
|
|
43
|
+
loading: '识别中...',
|
|
44
|
+
prettifying: '润色中...',
|
|
45
|
+
},
|
|
41
46
|
temp: '临时',
|
|
42
47
|
tokenDetail: '角色设定: {{systemRoleToken}} · 历史消息: {{chatsToken}}',
|
|
43
48
|
tokenTag: {
|
|
@@ -58,9 +63,13 @@ export default {
|
|
|
58
63
|
title: '话题列表',
|
|
59
64
|
},
|
|
60
65
|
translate: {
|
|
66
|
+
action: '翻译',
|
|
61
67
|
clear: '删除翻译',
|
|
62
68
|
},
|
|
63
|
-
|
|
69
|
+
tts: {
|
|
70
|
+
action: '语音朗读',
|
|
71
|
+
clear: '删除语音',
|
|
72
|
+
},
|
|
64
73
|
updateAgent: '更新助理信息',
|
|
65
74
|
upload: {
|
|
66
75
|
actionTooltip: '上传图片',
|
|
@@ -35,6 +35,12 @@ export default {
|
|
|
35
35
|
NoAPIKey: 'OpenAI API Key 为空,请添加自定义 OpenAI API Key',
|
|
36
36
|
/* eslint-enable */
|
|
37
37
|
},
|
|
38
|
+
stt: {
|
|
39
|
+
responseError: '服务请求失败,请检查配置或重试',
|
|
40
|
+
},
|
|
41
|
+
tts: {
|
|
42
|
+
responseError: '服务请求失败,请检查配置或重试',
|
|
43
|
+
},
|
|
38
44
|
unlock: {
|
|
39
45
|
apikey: {
|
|
40
46
|
addProxyUrl: '添加 OpenAI 代理地址(可选)',
|
|
@@ -109,7 +109,6 @@ export default {
|
|
|
109
109
|
},
|
|
110
110
|
title: '助手信息',
|
|
111
111
|
},
|
|
112
|
-
|
|
113
112
|
settingChat: {
|
|
114
113
|
chatStyleType: {
|
|
115
114
|
title: '聊天窗口样式',
|
|
@@ -195,6 +194,40 @@ export default {
|
|
|
195
194
|
},
|
|
196
195
|
title: '系统设置',
|
|
197
196
|
},
|
|
197
|
+
settingTTS: {
|
|
198
|
+
openai: {
|
|
199
|
+
sttModel: 'OpenAI 语音识别模型',
|
|
200
|
+
ttsModel: 'OpenAI 语音合成模型',
|
|
201
|
+
},
|
|
202
|
+
showAllLocaleVoice: {
|
|
203
|
+
desc: '关闭则只显示当前语种的声源',
|
|
204
|
+
title: '显示所有语种声源',
|
|
205
|
+
},
|
|
206
|
+
stt: '语音识别设置',
|
|
207
|
+
sttAutoStop: {
|
|
208
|
+
desc: '关闭后,语音识别将不会自动结束,需要手动点击结束按钮',
|
|
209
|
+
title: '自动结束语音识别',
|
|
210
|
+
},
|
|
211
|
+
sttLocale: {
|
|
212
|
+
desc: '语音输入的语种,此选项可提高语音识别准确率',
|
|
213
|
+
title: '语音识别语种',
|
|
214
|
+
},
|
|
215
|
+
sttService: {
|
|
216
|
+
desc: '其中 broswer 为浏览器原生的语音识别服务',
|
|
217
|
+
title: '语音识别服务',
|
|
218
|
+
},
|
|
219
|
+
title: '语音服务',
|
|
220
|
+
tts: '语音合成设置',
|
|
221
|
+
ttsService: {
|
|
222
|
+
desc: '如使用 OpenAI 语音合成服务,需要保证 OpenAI 模型服务已开启',
|
|
223
|
+
title: '语音合成服务',
|
|
224
|
+
},
|
|
225
|
+
voice: {
|
|
226
|
+
desc: '为当前助手挑选一个声音,不同 TTS 服务支持的声源不同',
|
|
227
|
+
preview: '试听声源',
|
|
228
|
+
title: '语音合成声源',
|
|
229
|
+
},
|
|
230
|
+
},
|
|
198
231
|
settingTheme: {
|
|
199
232
|
avatar: {
|
|
200
233
|
title: '头像',
|
|
@@ -234,5 +267,6 @@ export default {
|
|
|
234
267
|
agent: '默认助手',
|
|
235
268
|
common: '通用设置',
|
|
236
269
|
llm: '语言模型',
|
|
270
|
+
tts: '语音服务',
|
|
237
271
|
},
|
|
238
272
|
};
|
package/src/services/_url.ts
CHANGED
|
@@ -6,4 +6,11 @@ export const URLS = {
|
|
|
6
6
|
export const OPENAI_URLS = {
|
|
7
7
|
chat: '/api/openai/chat',
|
|
8
8
|
models: '/api/openai/models',
|
|
9
|
+
stt: '/api/openai/stt',
|
|
10
|
+
tts: '/api/openai/tts',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const TTS_URL = {
|
|
14
|
+
edge: '/api/tts/edge-speech',
|
|
15
|
+
microsoft: '/api/tts/microsoft-speech',
|
|
9
16
|
};
|
|
@@ -20,12 +20,28 @@ describe('settingsSelectors', () => {
|
|
|
20
20
|
systemRole: '',
|
|
21
21
|
model: LanguageModel.GPT3_5,
|
|
22
22
|
params: {},
|
|
23
|
+
tts: {
|
|
24
|
+
showAllLocaleVoice: false,
|
|
25
|
+
sttLocale: 'auto',
|
|
26
|
+
ttsService: 'openai',
|
|
27
|
+
voice: {
|
|
28
|
+
openai: 'alloy',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
23
31
|
},
|
|
24
32
|
meta: {
|
|
25
33
|
avatar: 'Default Agent',
|
|
26
34
|
description: 'Default agent for testing',
|
|
27
35
|
},
|
|
28
36
|
},
|
|
37
|
+
tts: {
|
|
38
|
+
openAI: {
|
|
39
|
+
sttModel: 'whisper-1',
|
|
40
|
+
ttsModel: 'tts-1',
|
|
41
|
+
},
|
|
42
|
+
sttAutoStop: true,
|
|
43
|
+
sttServer: 'openai',
|
|
44
|
+
},
|
|
29
45
|
languageModel: {
|
|
30
46
|
openAI: {
|
|
31
47
|
OPENAI_API_KEY: 'openai-api-key',
|
|
@@ -59,12 +75,28 @@ describe('settingsSelectors', () => {
|
|
|
59
75
|
top_p: 1,
|
|
60
76
|
},
|
|
61
77
|
plugins: [],
|
|
78
|
+
tts: {
|
|
79
|
+
showAllLocaleVoice: false,
|
|
80
|
+
sttLocale: 'auto',
|
|
81
|
+
ttsService: 'openai',
|
|
82
|
+
voice: {
|
|
83
|
+
openai: 'alloy',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
62
86
|
},
|
|
63
87
|
meta: {
|
|
64
88
|
avatar: 'Default Agent',
|
|
65
89
|
description: 'Default agent for testing',
|
|
66
90
|
},
|
|
67
91
|
},
|
|
92
|
+
tts: {
|
|
93
|
+
openAI: {
|
|
94
|
+
sttModel: 'whisper-1',
|
|
95
|
+
ttsModel: 'tts-1',
|
|
96
|
+
},
|
|
97
|
+
sttAutoStop: true,
|
|
98
|
+
sttServer: 'openai',
|
|
99
|
+
},
|
|
68
100
|
languageModel: {
|
|
69
101
|
openAI: {
|
|
70
102
|
OPENAI_API_KEY: 'openai-api-key',
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { DEFAULT_OPENAI_MODEL_LIST } from '@/const/llm';
|
|
2
2
|
import { DEFAULT_LANG } from '@/const/locale';
|
|
3
3
|
import { DEFAULT_AGENT_META } from '@/const/meta';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_AGENT,
|
|
6
|
+
DEFAULT_AGENT_CONFIG,
|
|
7
|
+
DEFAULT_SETTINGS,
|
|
8
|
+
DEFAULT_TTS_CONFIG,
|
|
9
|
+
} from '@/const/settings';
|
|
5
10
|
import { Locales } from '@/locales/resources';
|
|
6
11
|
import { GlobalSettings } from '@/types/settings';
|
|
7
12
|
import { isOnServerSide } from '@/utils/env';
|
|
@@ -11,6 +16,8 @@ import { GlobalStore } from '../store';
|
|
|
11
16
|
|
|
12
17
|
const currentSettings = (s: GlobalStore) => merge(DEFAULT_SETTINGS, s.settings);
|
|
13
18
|
|
|
19
|
+
const currentTTS = (s: GlobalStore) => merge(DEFAULT_TTS_CONFIG, s.settings.tts);
|
|
20
|
+
|
|
14
21
|
const defaultAgent = (s: GlobalStore) => merge(DEFAULT_AGENT, s.settings.defaultAgent);
|
|
15
22
|
|
|
16
23
|
const defaultAgentConfig = (s: GlobalStore) => merge(DEFAULT_AGENT_CONFIG, defaultAgent(s).config);
|
|
@@ -49,6 +56,7 @@ const currentLanguage = (s: GlobalStore) => {
|
|
|
49
56
|
export const settingsSelectors = {
|
|
50
57
|
currentLanguage,
|
|
51
58
|
currentSettings,
|
|
59
|
+
currentTTS,
|
|
52
60
|
defaultAgent,
|
|
53
61
|
defaultAgentConfig,
|
|
54
62
|
defaultAgentMeta,
|
|
@@ -2,9 +2,11 @@ import { t } from 'i18next';
|
|
|
2
2
|
|
|
3
3
|
import { DEFAULT_OPENAI_MODEL_LIST, VISION_MODEL_WHITE_LIST } from '@/const/llm';
|
|
4
4
|
import { DEFAULT_AVATAR, DEFAULT_BACKGROUND_COLOR } from '@/const/meta';
|
|
5
|
+
import { DEFAUTT_AGENT_TTS_CONFIG } from '@/const/settings';
|
|
5
6
|
import { SessionStore } from '@/store/session';
|
|
6
7
|
import { LanguageModel } from '@/types/llm';
|
|
7
8
|
import { MetaData } from '@/types/meta';
|
|
9
|
+
import { LobeAgentTTSConfig } from '@/types/session';
|
|
8
10
|
import { merge } from '@/utils/merge';
|
|
9
11
|
|
|
10
12
|
import { sessionSelectors } from '../session/selectors';
|
|
@@ -78,6 +80,12 @@ const showTokenTag = (s: SessionStore) => {
|
|
|
78
80
|
return DEFAULT_OPENAI_MODEL_LIST.includes(model);
|
|
79
81
|
};
|
|
80
82
|
|
|
83
|
+
const currentAgentTTS = (s: SessionStore): LobeAgentTTSConfig => {
|
|
84
|
+
const config = currentAgentConfig(s);
|
|
85
|
+
|
|
86
|
+
return config?.tts || DEFAUTT_AGENT_TTS_CONFIG;
|
|
87
|
+
};
|
|
88
|
+
|
|
81
89
|
export const agentSelectors = {
|
|
82
90
|
currentAgentAvatar,
|
|
83
91
|
currentAgentBackgroundColor,
|
|
@@ -87,6 +95,7 @@ export const agentSelectors = {
|
|
|
87
95
|
currentAgentModel,
|
|
88
96
|
currentAgentPlugins,
|
|
89
97
|
currentAgentSystemRole,
|
|
98
|
+
currentAgentTTS,
|
|
90
99
|
currentAgentTitle,
|
|
91
100
|
getAvatar,
|
|
92
101
|
getDescription,
|