mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-11-27 19:19:22 +08:00
[speech]
This commit is contained in:
27
app/api/tts.ts
Normal file
27
app/api/tts.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
AudioConfig,
|
||||
SpeechConfig,
|
||||
SpeechSynthesizer,
|
||||
SpeakerAudioDestination,
|
||||
SpeechSynthesisResult,
|
||||
} from "microsoft-cognitiveservices-speech-sdk";
|
||||
import { getLang } from "../locales";
|
||||
|
||||
const SUB_KEY = process.env.SPEECH_SUB_KEY ?? "";
|
||||
const REGION = process.env.SPEECH_REGION ?? "";
|
||||
|
||||
export async function speak(
|
||||
text: string,
|
||||
callback: ((e: SpeechSynthesisResult) => void) | undefined,
|
||||
) {
|
||||
if (!text) return;
|
||||
const player = new SpeakerAudioDestination();
|
||||
const audioConfig = AudioConfig.fromSpeakerOutput(player);
|
||||
const speechConfig = SpeechConfig.fromSubscription(SUB_KEY, REGION);
|
||||
speechConfig.speechSynthesisVoiceName =
|
||||
getLang() === "cn" ? "zh-CN-XiaoxiaoNeural" : "en-US-AriaNeural";
|
||||
const synthesizer = new SpeechSynthesizer(speechConfig, audioConfig);
|
||||
synthesizer.speakTextAsync(text, callback);
|
||||
|
||||
player.resume();
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import styles from "./home.module.scss";
|
||||
import chatStyle from "./chat.module.scss";
|
||||
|
||||
import { Input, Modal, showModal } from "./ui-lib";
|
||||
import { speak } from "../api/tts";
|
||||
|
||||
const Markdown = dynamic(
|
||||
async () => memo((await import("./markdown")).Markdown),
|
||||
@@ -490,6 +491,13 @@ export function Chat(props: {
|
||||
|
||||
const accessStore = useAccessStore();
|
||||
|
||||
const callback = (result: any) => {
|
||||
const audio = new Audio();
|
||||
audio.src = URL.createObjectURL(result.audioData);
|
||||
audio.volume = 0.5;
|
||||
audio.play();
|
||||
};
|
||||
|
||||
if (
|
||||
context.length === 0 &&
|
||||
session.messages.at(0)?.content !== BOT_HELLO.content
|
||||
@@ -670,6 +678,17 @@ export function Chat(props: {
|
||||
>
|
||||
{Locale.Chat.Actions.Copy}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={styles["chat-message-top-action"]}
|
||||
onClick={() =>
|
||||
accessStore.isAuthorized()
|
||||
? speak(message.content, callback)
|
||||
: () => {}
|
||||
}
|
||||
>
|
||||
{Locale.Chat.Actions.Speak}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(message.preview || message.content.length === 0) &&
|
||||
|
||||
@@ -7,6 +7,8 @@ declare global {
|
||||
CODE?: string;
|
||||
PROXY_URL?: string;
|
||||
VERCEL?: string;
|
||||
SPEECH_SUB_KEY?: string;
|
||||
SPEECH_REGION?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ const cn = {
|
||||
Copy: "复制",
|
||||
Stop: "停止",
|
||||
Retry: "重试",
|
||||
Speak: "朗读",
|
||||
},
|
||||
Rename: "重命名对话",
|
||||
Typing: "正在输入…",
|
||||
|
||||
@@ -19,6 +19,7 @@ const en: LocaleType = {
|
||||
Copy: "Copy",
|
||||
Stop: "Stop",
|
||||
Retry: "Retry",
|
||||
Speak: "Speak",
|
||||
},
|
||||
Rename: "Rename Chat",
|
||||
Typing: "Typing…",
|
||||
|
||||
@@ -19,6 +19,7 @@ const es: LocaleType = {
|
||||
Copy: "Copiar",
|
||||
Stop: "Detener",
|
||||
Retry: "Reintentar",
|
||||
Speak: "Leer",
|
||||
},
|
||||
Rename: "Renombrar chat",
|
||||
Typing: "Escribiendo...",
|
||||
|
||||
@@ -19,6 +19,7 @@ const it: LocaleType = {
|
||||
Copy: "Copia",
|
||||
Stop: "Stop",
|
||||
Retry: "Riprova",
|
||||
Speak: "Leggi",
|
||||
},
|
||||
Rename: "Rinomina Chat",
|
||||
Typing: "Typing…",
|
||||
|
||||
@@ -19,6 +19,7 @@ const tr: LocaleType = {
|
||||
Copy: "Kopyala",
|
||||
Stop: "Durdur",
|
||||
Retry: "Tekrar Dene",
|
||||
Speak: "Oku",
|
||||
},
|
||||
Rename: "Sohbeti Yeniden Adlandır",
|
||||
Typing: "Yazıyor…",
|
||||
@@ -136,11 +137,13 @@ const tr: LocaleType = {
|
||||
Model: "Model",
|
||||
Temperature: {
|
||||
Title: "Gerçeklik",
|
||||
SubTitle: "Daha büyük bir değer girildiğinde gerçeklik oranı düşer ve daha rastgele çıktılar üretir",
|
||||
SubTitle:
|
||||
"Daha büyük bir değer girildiğinde gerçeklik oranı düşer ve daha rastgele çıktılar üretir",
|
||||
},
|
||||
MaxTokens: {
|
||||
Title: "Maksimum Belirteç",
|
||||
SubTitle: "Girdi belirteçlerinin ve oluşturulan belirteçlerin maksimum uzunluğu",
|
||||
SubTitle:
|
||||
"Girdi belirteçlerinin ve oluşturulan belirteçlerin maksimum uzunluğu",
|
||||
},
|
||||
PresencePenlty: {
|
||||
Title: "Varlık Cezası",
|
||||
@@ -161,7 +164,8 @@ const tr: LocaleType = {
|
||||
Summarize:
|
||||
"Gelecekteki bağlam için bir bilgi istemi olarak kullanmak üzere tartışmamızı en fazla 200 kelimeyle özetleyin.",
|
||||
},
|
||||
ConfirmClearAll: "Tüm sohbet ve ayar verilerini temizlemeyi onaylıyor musunuz?",
|
||||
ConfirmClearAll:
|
||||
"Tüm sohbet ve ayar verilerini temizlemeyi onaylıyor musunuz?",
|
||||
},
|
||||
Copy: {
|
||||
Success: "Panoya kopyalandı",
|
||||
|
||||
@@ -18,6 +18,7 @@ const tw: LocaleType = {
|
||||
Copy: "複製",
|
||||
Stop: "停止",
|
||||
Retry: "重試",
|
||||
Speak: "朗讀",
|
||||
},
|
||||
Rename: "重命名對話",
|
||||
Typing: "正在輸入…",
|
||||
|
||||
Reference in New Issue
Block a user