重构了tts
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-01-05 17:34:15 +08:00
parent bd7eca1bd0
commit be3eb17490
24 changed files with 170 additions and 1593 deletions

View File

@@ -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");
};

View File

@@ -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,
});
}

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -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>