From 85085ba5ff97590c144f55896a98d93494dc4f09 Mon Sep 17 00:00:00 2001 From: goddonebianu Date: Sat, 11 Oct 2025 20:43:43 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=90=E6=AD=A5=E6=B7=BB=E5=8A=A0=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E4=BF=9D=E5=AD=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 5 +- package.json | 3 +- ...24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg | 1 + src/app/text-speaker/page.tsx | 154 ++++++++++++++---- src/config/images.ts | 1 + 5 files changed, 125 insertions(+), 39 deletions(-) create mode 100644 public/images/save_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg diff --git a/package-lock.json b/package-lock.json index 8de0c32..16c4f4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "edge-tts-universal": "^1.3.2", "next": "15.5.3", "react": "19.1.0", - "react-dom": "19.1.0" + "react-dom": "19.1.0", + "zod": "^3.25.76" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -14938,8 +14939,6 @@ "resolved": "https://mirrors.cloud.tencent.com/npm/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "optional": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index b684ce6..b3d88fd 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "edge-tts-universal": "^1.3.2", "next": "15.5.3", "react": "19.1.0", - "react-dom": "19.1.0" + "react-dom": "19.1.0", + "zod": "^3.25.76" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/public/images/save_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg b/public/images/save_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg new file mode 100644 index 0000000..5c0f961 --- /dev/null +++ b/public/images/save_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/text-speaker/page.tsx b/src/app/text-speaker/page.tsx index a6968db..531ac83 100644 --- a/src/app/text-speaker/page.tsx +++ b/src/app/text-speaker/page.tsx @@ -6,14 +6,17 @@ import IMAGES from "@/config/images"; import { useAudioPlayer } from "@/hooks/useAudioPlayer"; import { getTTSAudioUrl } from "@/utils"; import { ChangeEvent, useEffect, useRef, useState } from "react"; +import z from "zod"; export default function Home() { + const [showSpeedAdjust, setShowSpeedAdjust] = useState(false); const [ipaEnabled, setIPAEnabled] = useState(false); const [speed, setSpeed] = useState(1); const [pause, setPause] = useState(true); const [autopause, setAutopause] = useState(true); const textRef = useRef(''); - const localeRef = useRef(null); + // const localeRef = useRef(null); + const [locale, setLocale] = useState(null); const [ipa, setIPA] = useState(''); const objurlRef = useRef(null); const [processing, setProcessing] = useState(false); @@ -90,9 +93,9 @@ export default function Home() { }); try { const textinfo = await (await fetch(`/api/locale?${params}`)).json(); - localeRef.current = textinfo.locale; + setLocale(textinfo.locale); - const voice = voicesData.find(v => v.locale.startsWith(localeRef.current!)); + const voice = voicesData.find(v => v.locale.startsWith(textinfo.locale)); if (!voice) throw 'Voice not found.'; objurlRef.current = await getTTSAudioUrl( @@ -112,7 +115,7 @@ export default function Home() { console.error(e); setPause(true); - localeRef.current = null; + setLocale(null); setProcessing(false); } @@ -129,7 +132,7 @@ export default function Home() { const handleInputChange = (e: ChangeEvent) => { textRef.current = e.target.value.trim(); - localeRef.current = null; + setLocale(null); setIPA(''); if (objurlRef.current) URL.revokeObjectURL(objurlRef.current); objurlRef.current = null; @@ -147,6 +150,62 @@ export default function Home() { } } + const TextSpeakerItemSchema = z.object({ + text: z.string(), + ipa: z.string().optional(), + locale: z.string() + }); + const TextSpeakerArraySchema = z.array(TextSpeakerItemSchema); + + const save = () => { + if (!locale) return; + + const getTextSpeakerData = () => { + try { + const item = localStorage.getItem('text-speaker'); + + if (!item) return []; + + const rawData = JSON.parse(item); + const result = TextSpeakerArraySchema.safeParse(rawData); + + if (result.success) { + return result.data; + } else { + console.error('Invalid data structure in localStorage:', result.error); + return []; + } + } catch (e) { + console.error('Failed to parse text-speaker data:', e); + return []; + } + } + + const save = getTextSpeakerData(); + const oldIndex = save.findIndex(v => v.text === textRef.current); + if (oldIndex !== -1) { + const oldItem = save[oldIndex]; + if ((ipa && !oldItem.ipa) || (ipa && oldItem.ipa !== ipa)) { + oldItem.ipa = ipa; + localStorage.setItem('text-speaker', JSON.stringify(save)); + return; + } + } + if (ipa.length === 0) { + save.push({ + text: textRef.current, + locale: locale + }); + } else { + save.push({ + text: textRef.current, + locale: locale, + ipa: ipa + }); + } + localStorage.setItem('text-speaker', JSON.stringify(save)); + } + return (<>