"use client"; import LightButton from "@/components/buttons/LightButton"; import IconClick from "@/components/IconClick"; 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"; import { getTTSAudioUrl } from "@/lib/browser/tts"; import { Plus, Trash } from "lucide-react"; import { useTranslations } from "next-intl"; import { useRef, useState } from "react"; import z from "zod"; import AddToFolder from "./AddToFolder"; import { genIPA, genLocale, genTranslation, } from "@/lib/actions/translatorActions"; import { toast } from "sonner"; import FolderSelector from "./FolderSelector"; import { useSession } from "next-auth/react"; import { createPair } from "@/lib/actions/services/pairService"; import { shallowEqual } from "@/lib/utils"; export default function TranslatorPage() { const t = useTranslations("translator"); const session = useSession(); const taref = useRef(null); const [lang, setLang] = useState("chinese"); const [tresult, setTresult] = useState(""); const [genIpa, setGenIpa] = useState(true); const [ipaTexts, setIpaTexts] = useState(["", ""]); const [processing, setProcessing] = useState(false); const { load, play } = useAudioPlayer(); const [history, setHistory] = useState< z.infer[] >(tlso.get()); const [showAddToFolder, setShowAddToFolder] = useState(false); const [addToFolderItem, setAddToFolderItem] = useState | null>(null); const lastTTS = useRef({ text: "", url: "", }); const [autoSave, setAutoSave] = useState(false); const [autoSaveFolderId, setAutoSaveFolderId] = useState(null); const tts = async (text: string, locale: string) => { if (lastTTS.current.text !== text) { const shortName = VOICES.find((v) => v.locale === locale)?.short_name; if (!shortName) { toast.error("Voice not found"); return; } try { const url = await getTTSAudioUrl(text, shortName); await load(url); lastTTS.current.text = text; lastTTS.current.url = url; } catch (error) { toast.error("Failed to generate audio"); console.error(error); } } await play(); }; const translate = async () => { if (!taref.current) return; if (processing) return; setProcessing(true); const text1 = taref.current.value; const llmres: { text1: string | null; text2: string | null; locale1: string | null; locale2: string | null; ipa1: string | null; ipa2: string | null; } = { text1: text1, text2: null, locale1: null, locale2: null, ipa1: null, ipa2: null, }; let historyUpdated = false; // 检查更新历史记录 const checkUpdateLocalStorage = () => { if (historyUpdated) return; if (llmres.text1 && llmres.text2 && llmres.locale1 && llmres.locale2) { setHistory( tlsoPush({ text1: llmres.text1, text2: llmres.text2, locale1: llmres.locale1, locale2: llmres.locale2, }), ); if (autoSave && autoSaveFolderId) { createPair({ text1: llmres.text1, text2: llmres.text2, locale1: llmres.locale1, locale2: llmres.locale2, folder: { connect: { id: autoSaveFolderId, }, }, }) .then(() => { toast.success( llmres.text1 + "保存到文件夹" + autoSaveFolderId + "成功", ); }) .catch((error) => { toast.error( llmres.text1 + "保存到文件夹" + autoSaveFolderId + "失败:" + error.message, ); }); } historyUpdated = true; } }; // 更新局部翻译状态 const updateState = (stateName: keyof typeof llmres, value: string) => { llmres[stateName] = value; checkUpdateLocalStorage(); }; genTranslation(text1, lang) .then(async (text2) => { updateState("text2", text2); setTresult(text2); // 生成两个locale genLocale(text1).then((locale) => { updateState("locale1", locale); }); genLocale(text2).then((locale) => { updateState("locale2", locale); }); // 生成俩IPA if (genIpa) { genIPA(text1).then((ipa1) => { setIpaTexts((prev) => [ipa1, prev[1]]); updateState("ipa1", ipa1); }); genIPA(text2).then((ipa2) => { setIpaTexts((prev) => [prev[0], ipa2]); updateState("ipa2", ipa2); }); } }) .catch(() => { toast.error("Translation failed"); }) .finally(() => { setProcessing(false); }); }; return ( <> {/* TCard Component */}
{/* Card Component - Left Side */}
{/* ICard1 Component */}
{ipaTexts[0]}
{ await navigator.clipboard.writeText( taref.current?.value || "", ); }} > { const t = taref.current?.value; if (!t) return; tts(t, tlso.get().find((v) => v.text1 === t)?.locale1 || ""); }} >
{t("detectLanguage")} setGenIpa((prev) => !prev)} > {t("generateIPA")}
{/* Card Component - Right Side */}
{/* ICard2 Component */}
{tresult}
{ipaTexts[1]}
{ await navigator.clipboard.writeText(tresult); }} > { tts( tresult, tlso.get().find((v) => v.text2 === tresult)?.locale2 || "", ); }} >
{t("translateInto")} setLang("chinese")} > {t("chinese")} setLang("english")} > {t("english")} setLang("italian")} > {t("italian")} { const newLang = prompt(t("enterLanguage")); if (newLang) { setLang(newLang); } }} > {t("other")}
{/* TranslateButton Component */}
{/* AutoSave Component */}
{history.length > 0 && (

{t("history")}

{history.toReversed().map((item, index) => (

{item.text1}

{item.text2}

))}
{showAddToFolder && ( )} {autoSave && !autoSaveFolderId && ( setAutoSave(false)} setSelectedFolderId={(id) => setAutoSaveFolderId(id)} /> )}
)} ); }