This commit is contained in:
@@ -2,8 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
||||
import { getTTSAudioUrl } from "@/lib/browser/tts";
|
||||
import { VOICES } from "@/config/locales";
|
||||
import { getTTSUrl, TTS_SUPPORTED_LANGUAGES } from "@/lib/server/bigmodel/tts";
|
||||
import { useTranslations } from "next-intl";
|
||||
import localFont from "next/font/local";
|
||||
import { isNonNegativeInteger, SeededRandom } from "@/lib/utils";
|
||||
@@ -59,20 +58,32 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
|
||||
if (show === "answer") {
|
||||
const newIndex = (index + 1) % getTextPairs().length;
|
||||
setIndex(newIndex);
|
||||
if (dictation)
|
||||
getTTSAudioUrl(
|
||||
getTextPairs()[newIndex][reverse ? "text2" : "text1"],
|
||||
VOICES.find(
|
||||
(v) =>
|
||||
v.locale ===
|
||||
getTextPairs()[newIndex][
|
||||
reverse ? "locale2" : "locale1"
|
||||
],
|
||||
)!.short_name,
|
||||
).then((url) => {
|
||||
if (dictation) {
|
||||
const textPair = getTextPairs()[newIndex];
|
||||
const language = textPair[reverse ? "language2" : "language1"];
|
||||
const text = textPair[reverse ? "text2" : "text1"];
|
||||
|
||||
// 映射语言到 TTS 支持的格式
|
||||
const languageMap: Record<string, TTS_SUPPORTED_LANGUAGES> = {
|
||||
"chinese": "Chinese",
|
||||
"english": "English",
|
||||
"japanese": "Japanese",
|
||||
"korean": "Korean",
|
||||
"french": "French",
|
||||
"german": "German",
|
||||
"italian": "Italian",
|
||||
"portuguese": "Portuguese",
|
||||
"spanish": "Spanish",
|
||||
"russian": "Russian",
|
||||
};
|
||||
|
||||
const ttsLanguage = languageMap[language?.toLowerCase()] || "Auto";
|
||||
|
||||
getTTSUrl(text, ttsLanguage).then((url) => {
|
||||
load(url);
|
||||
play();
|
||||
});
|
||||
}
|
||||
}
|
||||
setShow(show === "question" ? "answer" : "question");
|
||||
};
|
||||
|
||||
@@ -12,7 +12,6 @@ import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import z from "zod";
|
||||
import SaveList from "./SaveList";
|
||||
|
||||
import { VOICES } from "@/config/locales";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { getLocalStorageOperator } from "@/lib/browser/localStorageOperators";
|
||||
import { genIPA, genLanguage } from "@/lib/server/bigmodel/translatorActions";
|
||||
@@ -160,7 +159,7 @@ export default function TextSpeakerPage() {
|
||||
const handleUseItem = (item: z.infer<typeof TextSpeakerItemSchema>) => {
|
||||
if (textareaRef.current) textareaRef.current.value = item.text;
|
||||
textRef.current = item.text;
|
||||
setLanguage(item.locale);
|
||||
setLanguage(item.language);
|
||||
setIPA(item.ipa || "");
|
||||
if (objurlRef.current) URL.revokeObjectURL(objurlRef.current);
|
||||
objurlRef.current = null;
|
||||
@@ -202,12 +201,12 @@ export default function TextSpeakerPage() {
|
||||
} else if (theIPA.length === 0) {
|
||||
save.push({
|
||||
text: textRef.current,
|
||||
locale: theLanguage as string,
|
||||
language: theLanguage as string,
|
||||
});
|
||||
} else {
|
||||
save.push({
|
||||
text: textRef.current,
|
||||
locale: theLanguage as string,
|
||||
language: theLanguage as string,
|
||||
ipa: theIPA,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
|
||||
createPair({
|
||||
text1: item.text1,
|
||||
text2: item.text2,
|
||||
locale1: item.locale1,
|
||||
locale2: item.locale2,
|
||||
language1: item.language1,
|
||||
language2: item.language2,
|
||||
folder: {
|
||||
connect: {
|
||||
id: folder.id,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { LightButton } from "@/components/ui/buttons";
|
||||
import { IconClick } from "@/components/ui/buttons";
|
||||
import IMAGES from "@/config/images";
|
||||
import { VOICES } from "@/config/locales";
|
||||
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
||||
import { TranslationHistorySchema } from "@/lib/interfaces";
|
||||
import { tlsoPush, tlso } from "@/lib/browser/localStorageOperators";
|
||||
@@ -23,7 +22,7 @@ import FolderSelector from "./FolderSelector";
|
||||
import { createPair } from "@/lib/server/services/pairService";
|
||||
import { shallowEqual } from "@/lib/utils";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { getTTSUrl } from "@/lib/server/bigmodel/tts";
|
||||
import { getTTSUrl, TTS_SUPPORTED_LANGUAGES } from "@/lib/server/bigmodel/tts";
|
||||
|
||||
export default function TranslatorPage() {
|
||||
const t = useTranslations("translator");
|
||||
@@ -51,7 +50,20 @@ export default function TranslatorPage() {
|
||||
const tts = async (text: string, locale: string) => {
|
||||
if (lastTTS.current.text !== text) {
|
||||
try {
|
||||
const url = await getTTSUrl(text, locale);
|
||||
// Map language name to TTS format
|
||||
let theLanguage = locale.toLowerCase().replace(/[^a-z]/g, '').replace(/^./, match => match.toUpperCase());
|
||||
|
||||
// Check if language is in TTS supported list
|
||||
const supportedLanguages: TTS_SUPPORTED_LANGUAGES[] = [
|
||||
"Auto", "Chinese", "English", "German", "Italian", "Portuguese",
|
||||
"Spanish", "Japanese", "Korean", "French", "Russian"
|
||||
];
|
||||
|
||||
if (!supportedLanguages.includes(theLanguage as TTS_SUPPORTED_LANGUAGES)) {
|
||||
theLanguage = "Auto";
|
||||
}
|
||||
|
||||
const url = await getTTSUrl(text, theLanguage as TTS_SUPPORTED_LANGUAGES);
|
||||
await load(url);
|
||||
lastTTS.current.text = text;
|
||||
lastTTS.current.url = url;
|
||||
@@ -74,15 +86,15 @@ export default function TranslatorPage() {
|
||||
const llmres: {
|
||||
text1: string | null;
|
||||
text2: string | null;
|
||||
locale1: string | null;
|
||||
locale2: string | null;
|
||||
language1: string | null;
|
||||
language2: string | null;
|
||||
ipa1: string | null;
|
||||
ipa2: string | null;
|
||||
} = {
|
||||
text1: text1,
|
||||
text2: null,
|
||||
locale1: null,
|
||||
locale2: null,
|
||||
language1: null,
|
||||
language2: null,
|
||||
ipa1: null,
|
||||
ipa2: null,
|
||||
};
|
||||
@@ -92,21 +104,21 @@ export default function TranslatorPage() {
|
||||
// 检查更新历史记录
|
||||
const checkUpdateLocalStorage = () => {
|
||||
if (historyUpdated) return;
|
||||
if (llmres.text1 && llmres.text2 && llmres.locale1 && llmres.locale2) {
|
||||
if (llmres.text1 && llmres.text2 && llmres.language1 && llmres.language2) {
|
||||
setHistory(
|
||||
tlsoPush({
|
||||
text1: llmres.text1,
|
||||
text2: llmres.text2,
|
||||
locale1: llmres.locale1,
|
||||
locale2: llmres.locale2,
|
||||
language1: llmres.language1,
|
||||
language2: llmres.language2,
|
||||
}),
|
||||
);
|
||||
if (autoSave && autoSaveFolderId) {
|
||||
createPair({
|
||||
text1: llmres.text1,
|
||||
text2: llmres.text2,
|
||||
locale1: llmres.locale1,
|
||||
locale2: llmres.locale2,
|
||||
language1: llmres.language1,
|
||||
language2: llmres.language2,
|
||||
folder: {
|
||||
connect: {
|
||||
id: autoSaveFolderId,
|
||||
@@ -143,10 +155,10 @@ export default function TranslatorPage() {
|
||||
setTresult(text2);
|
||||
// 生成两个locale
|
||||
genLocale(text1).then((locale) => {
|
||||
updateState("locale1", locale);
|
||||
updateState("language1", locale);
|
||||
});
|
||||
genLocale(text2).then((locale) => {
|
||||
updateState("locale2", locale);
|
||||
updateState("language2", locale);
|
||||
});
|
||||
// 生成俩IPA
|
||||
if (genIpa) {
|
||||
@@ -202,7 +214,7 @@ export default function TranslatorPage() {
|
||||
onClick={() => {
|
||||
const t = taref.current?.value;
|
||||
if (!t) return;
|
||||
tts(t, tlso.get().find((v) => v.text1 === t)?.locale1 || "");
|
||||
tts(t, tlso.get().find((v) => v.text1 === t)?.language1 || "");
|
||||
}}
|
||||
></IconClick>
|
||||
</div>
|
||||
@@ -240,7 +252,7 @@ export default function TranslatorPage() {
|
||||
onClick={() => {
|
||||
tts(
|
||||
tresult,
|
||||
tlso.get().find((v) => v.text2 === tresult)?.locale2 || "",
|
||||
tlso.get().find((v) => v.text2 === tresult)?.language2 || "",
|
||||
);
|
||||
}}
|
||||
></IconClick>
|
||||
|
||||
@@ -11,8 +11,8 @@ interface AddTextPairModalProps {
|
||||
onAdd: (
|
||||
text1: string,
|
||||
text2: string,
|
||||
locale1: string,
|
||||
locale2: string,
|
||||
language1: string,
|
||||
language2: string,
|
||||
) => void;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ export default function AddTextPairModal({
|
||||
const t = useTranslations("folder_id");
|
||||
const input1Ref = useRef<HTMLInputElement>(null);
|
||||
const input2Ref = useRef<HTMLInputElement>(null);
|
||||
const [locale1, setLocale1] = useState("en-US");
|
||||
const [locale2, setLocale2] = useState("zh-CN");
|
||||
const [language1, setLanguage1] = useState("english");
|
||||
const [language2, setLanguage2] = useState("chinese");
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
@@ -33,8 +33,8 @@ export default function AddTextPairModal({
|
||||
if (
|
||||
!input1Ref.current?.value ||
|
||||
!input2Ref.current?.value ||
|
||||
!locale1 ||
|
||||
!locale2
|
||||
!language1 ||
|
||||
!language2
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -44,14 +44,14 @@ export default function AddTextPairModal({
|
||||
if (
|
||||
typeof text1 === "string" &&
|
||||
typeof text2 === "string" &&
|
||||
typeof locale1 === "string" &&
|
||||
typeof locale2 === "string" &&
|
||||
typeof language1 === "string" &&
|
||||
typeof language2 === "string" &&
|
||||
text1.trim() !== "" &&
|
||||
text2.trim() !== "" &&
|
||||
locale1.trim() !== "" &&
|
||||
locale2.trim() !== ""
|
||||
language1.trim() !== "" &&
|
||||
language2.trim() !== ""
|
||||
) {
|
||||
onAdd(text1, text2, locale1, locale2);
|
||||
onAdd(text1, text2, language1, language2);
|
||||
input1Ref.current.value = "";
|
||||
input2Ref.current.value = "";
|
||||
}
|
||||
@@ -84,12 +84,12 @@ export default function AddTextPairModal({
|
||||
<Input ref={input2Ref} className="w-full"></Input>
|
||||
</div>
|
||||
<div>
|
||||
{t("locale1")}
|
||||
<LocaleSelector value={locale1} onChange={setLocale1} />
|
||||
{t("language1")}
|
||||
<LocaleSelector value={language1} onChange={setLanguage1} />
|
||||
</div>
|
||||
<div>
|
||||
{t("locale2")}
|
||||
<LocaleSelector value={locale2} onChange={setLocale2} />
|
||||
{t("language2")}
|
||||
<LocaleSelector value={language2} onChange={setLanguage2} />
|
||||
</div>
|
||||
</div>
|
||||
<LightButton onClick={handleAdd}>{t("add")}</LightButton>
|
||||
|
||||
@@ -140,14 +140,14 @@ export default function InFolder({ folderId }: { folderId: number }) {
|
||||
onAdd={async (
|
||||
text1: string,
|
||||
text2: string,
|
||||
locale1: string,
|
||||
locale2: string,
|
||||
language1: string,
|
||||
language2: string,
|
||||
) => {
|
||||
await createPair({
|
||||
text1: text1,
|
||||
text2: text2,
|
||||
language1: locale1,
|
||||
language2: locale2,
|
||||
language1: language1,
|
||||
language2: language2,
|
||||
folder: {
|
||||
connect: {
|
||||
id: folderId,
|
||||
|
||||
@@ -23,8 +23,8 @@ export default function UpdateTextPairModal({
|
||||
const t = useTranslations("folder_id");
|
||||
const input1Ref = useRef<HTMLInputElement>(null);
|
||||
const input2Ref = useRef<HTMLInputElement>(null);
|
||||
const [locale1, setLocale1] = useState(textPair.language1);
|
||||
const [locale2, setLocale2] = useState(textPair.language2);
|
||||
const [language1, setLanguage1] = useState(textPair.language1);
|
||||
const [language2, setLanguage2] = useState(textPair.language2);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
@@ -32,8 +32,8 @@ export default function UpdateTextPairModal({
|
||||
if (
|
||||
!input1Ref.current?.value ||
|
||||
!input2Ref.current?.value ||
|
||||
!locale1 ||
|
||||
!locale2
|
||||
!language1 ||
|
||||
!language2
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -43,14 +43,14 @@ export default function UpdateTextPairModal({
|
||||
if (
|
||||
typeof text1 === "string" &&
|
||||
typeof text2 === "string" &&
|
||||
typeof locale1 === "string" &&
|
||||
typeof locale2 === "string" &&
|
||||
typeof language1 === "string" &&
|
||||
typeof language2 === "string" &&
|
||||
text1.trim() !== "" &&
|
||||
text2.trim() !== "" &&
|
||||
locale1.trim() !== "" &&
|
||||
locale2.trim() !== ""
|
||||
language1.trim() !== "" &&
|
||||
language2.trim() !== ""
|
||||
) {
|
||||
onUpdate(textPair.id, { text1, text2, locale1, locale2 });
|
||||
onUpdate(textPair.id, { text1, text2, language1, language2 });
|
||||
}
|
||||
};
|
||||
return (
|
||||
@@ -88,12 +88,12 @@ export default function UpdateTextPairModal({
|
||||
></Input>
|
||||
</div>
|
||||
<div>
|
||||
{t("locale1")}
|
||||
<LocaleSelector value={locale1} onChange={setLocale1} />
|
||||
{t("language1")}
|
||||
<LocaleSelector value={language1} onChange={setLanguage1} />
|
||||
</div>
|
||||
<div>
|
||||
{t("locale2")}
|
||||
<LocaleSelector value={locale2} onChange={setLocale2} />
|
||||
{t("language2")}
|
||||
<LocaleSelector value={language2} onChange={setLanguage2} />
|
||||
</div>
|
||||
</div>
|
||||
<LightButton onClick={handleUpdate}>{t("update")}</LightButton>
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { LOCALES } from "@/config/locales";
|
||||
import { useState } from "react";
|
||||
|
||||
const COMMON_LOCALES = [
|
||||
{ label: "中文", value: "zh-CN" },
|
||||
{ label: "英文", value: "en-US" },
|
||||
{ label: "意大利语", value: "it-IT" },
|
||||
{ label: "日语", value: "ja-JP" },
|
||||
const COMMON_LANGUAGES = [
|
||||
{ label: "中文", value: "chinese" },
|
||||
{ label: "英文", value: "english" },
|
||||
{ label: "意大利语", value: "italian" },
|
||||
{ label: "日语", value: "japanese" },
|
||||
{ label: "韩语", value: "korean" },
|
||||
{ label: "法语", value: "french" },
|
||||
{ label: "德语", value: "german" },
|
||||
{ label: "西班牙语", value: "spanish" },
|
||||
{ label: "葡萄牙语", value: "portuguese" },
|
||||
{ label: "俄语", value: "russian" },
|
||||
{ label: "其他", value: "other" },
|
||||
];
|
||||
|
||||
@@ -14,34 +20,50 @@ interface LocaleSelectorProps {
|
||||
}
|
||||
|
||||
export function LocaleSelector({ value, onChange }: LocaleSelectorProps) {
|
||||
const isCommonLocale = COMMON_LOCALES.some((l) => l.value === value && l.value !== "other");
|
||||
const showFullList = value === "other" || !isCommonLocale;
|
||||
const [customInput, setCustomInput] = useState("");
|
||||
const isCommonLanguage = COMMON_LANGUAGES.some((l) => l.value === value && l.value !== "other");
|
||||
const showCustomInput = value === "other" || !isCommonLanguage;
|
||||
|
||||
// 计算输入框的值:如果是"other"使用自定义输入,否则使用外部传入的值
|
||||
const inputValue = value === "other" ? customInput : value;
|
||||
|
||||
// 处理自定义输入
|
||||
const handleCustomInputChange = (inputValue: string) => {
|
||||
setCustomInput(inputValue);
|
||||
onChange(inputValue);
|
||||
};
|
||||
|
||||
// 当选择常见语言或"其他"时
|
||||
const handleSelectChange = (selectedValue: string) => {
|
||||
if (selectedValue === "other") {
|
||||
setCustomInput("");
|
||||
onChange("other");
|
||||
} else {
|
||||
onChange(selectedValue);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<select
|
||||
value={isCommonLocale ? value : "other"}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={isCommonLanguage ? value : "other"}
|
||||
onChange={(e) => handleSelectChange(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-[#35786f]"
|
||||
>
|
||||
{COMMON_LOCALES.map((locale) => (
|
||||
<option key={locale.value} value={locale.value}>
|
||||
{locale.label}
|
||||
{COMMON_LANGUAGES.map((lang) => (
|
||||
<option key={lang.value} value={lang.value}>
|
||||
{lang.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{showFullList && (
|
||||
<select
|
||||
value={value === "other" ? LOCALES[0] : value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
{showCustomInput && (
|
||||
<input
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) => handleCustomInputChange(e.target.value)}
|
||||
placeholder="请输入语言名称"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-[#35786f] mt-2"
|
||||
>
|
||||
{LOCALES.map((locale) => (
|
||||
<option key={locale} value={locale}>
|
||||
{locale}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,15 +19,15 @@ export type SupportedAlphabets =
|
||||
export const TextSpeakerItemSchema = z.object({
|
||||
text: z.string(),
|
||||
ipa: z.string().optional(),
|
||||
locale: z.string(),
|
||||
language: z.string(),
|
||||
});
|
||||
export const TextSpeakerArraySchema = z.array(TextSpeakerItemSchema);
|
||||
|
||||
export const WordDataSchema = z.object({
|
||||
locales: z
|
||||
languages: z
|
||||
.tuple([z.string(), z.string()])
|
||||
.refine(([first, second]) => first !== second, {
|
||||
message: "Locales must be different",
|
||||
message: "Languages must be different",
|
||||
}),
|
||||
wordPairs: z
|
||||
.array(z.tuple([z.string(), z.string()]))
|
||||
@@ -47,8 +47,8 @@ export const WordDataSchema = z.object({
|
||||
export const TranslationHistorySchema = z.object({
|
||||
text1: z.string(),
|
||||
text2: z.string(),
|
||||
locale1: z.string(),
|
||||
locale2: z.string(),
|
||||
language1: z.string(),
|
||||
language2: z.string(),
|
||||
});
|
||||
|
||||
export const TranslationHistoryArraySchema = z.array(TranslationHistorySchema);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
"use server";
|
||||
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
/**
|
||||
* 支持的语音合成模型
|
||||
@@ -135,13 +138,12 @@ class QwenTTSService {
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
const data: TTSResponse = await response.json();
|
||||
|
||||
// 4. 错误处理
|
||||
if (data.status_code !== 200) {
|
||||
throw new Error(`API错误: [${data.code}] ${data.message}`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`TTS API错误: [${response.status}] ${response.statusText}}`);
|
||||
}
|
||||
|
||||
const data: TTSResponse = await response.json();
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
@@ -149,74 +151,6 @@ class QwenTTSService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式合成语音(边生成边输出Base64音频数据)
|
||||
*/
|
||||
async synthesizeStream(
|
||||
text: string,
|
||||
options: {
|
||||
voice?: string;
|
||||
language?: SupportedLanguage;
|
||||
model?: TTSModel;
|
||||
onAudioChunk?: (chunk: string) => void; // 接收音频片段的回调
|
||||
} = {}
|
||||
): Promise<void> {
|
||||
const {
|
||||
voice = 'Cherry',
|
||||
language = 'Auto',
|
||||
model = 'qwen3-tts-flash',
|
||||
onAudioChunk
|
||||
} = options;
|
||||
|
||||
this.validateTextLength(text, model);
|
||||
|
||||
const requestBody: TTSRequest = {
|
||||
model,
|
||||
input: {
|
||||
text,
|
||||
voice,
|
||||
language_type: language
|
||||
},
|
||||
parameters: {
|
||||
stream: true // 启用流式输出
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(this.baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
'X-DashScope-SSE': 'enable' // 关键:启用服务器发送事件
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
if (!response.ok || !response.body) {
|
||||
throw new Error(`流式请求失败: ${response.status}`);
|
||||
}
|
||||
|
||||
// 处理流式响应(此处为简化示例,实际需解析SSE格式)
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
if (onAudioChunk && chunk.trim()) {
|
||||
onAudioChunk(chunk); // 处理音频数据片段
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('流式合成失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type TTS_SUPPORTED_LANGUAGES = 'Auto' | 'Chinese' | 'English' | 'German' | 'Italian' | 'Portuguese' | 'Spanish' | 'Japanese' | 'Korean' | 'French' | 'Russian';
|
||||
@@ -231,7 +165,7 @@ export async function getTTSUrl(text: string, lang: TTS_SUPPORTED_LANGUAGES) {
|
||||
throw "API Key设置错误";
|
||||
}
|
||||
const ttsService = new QwenTTSService(
|
||||
process.env.DASHSCOPE_API_KEY || 'sk-xxx',
|
||||
process.env.DASHSCORE_API_KEY
|
||||
);
|
||||
const result = await ttsService.synthesize(
|
||||
text,
|
||||
|
||||
Reference in New Issue
Block a user