From ff7385cf819efb938adf0b412b96a3dc46133a08 Mon Sep 17 00:00:00 2001 From: goddonebianu Date: Sun, 5 Oct 2025 19:43:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 6 +- src/app/ipa-reader/IPAForm.tsx | 91 +++++++++++++++++ src/app/ipa-reader/layout.tsx | 33 ------- src/app/ipa-reader/page.tsx | 141 ++------------------------- src/app/ipa-reader/useAudioPlayer.ts | 39 ++++++++ src/app/layout.tsx | 2 +- src/app/srt-player/layout.tsx | 33 ------- src/app/word-board/layout.tsx | 36 ------- src/app/word-board/page.tsx | 2 +- 9 files changed, 146 insertions(+), 237 deletions(-) create mode 100644 src/app/ipa-reader/IPAForm.tsx delete mode 100644 src/app/ipa-reader/layout.tsx create mode 100644 src/app/ipa-reader/useAudioPlayer.ts delete mode 100644 src/app/srt-player/layout.tsx delete mode 100644 src/app/word-board/layout.tsx diff --git a/src/app/globals.css b/src/app/globals.css index a2dc41e..b3e1619 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -22,5 +22,9 @@ body { background: var(--background); color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif; } + +.code-block { + font-family: var(--font-geist-mono), monospace; +} \ No newline at end of file diff --git a/src/app/ipa-reader/IPAForm.tsx b/src/app/ipa-reader/IPAForm.tsx new file mode 100644 index 0000000..bb860d5 --- /dev/null +++ b/src/app/ipa-reader/IPAForm.tsx @@ -0,0 +1,91 @@ +import Button from "@/components/Button"; +import { EdgeTTS } from "edge-tts-universal"; +import { useRef, useState } from "react"; +import useAudioPlayer from "./useAudioPlayer"; + +export default function IPAForm( + { voicesData }: { + voicesData: { + locale: string, + short_name: string + }[] + } +) { + const respref = useRef(null); + const inputref = useRef(null); + const [reqEnabled, setReqEnabled] = useState(true); + const [textInfo, setTextInfo] = useState<{ + lang: string, + ipa: string, + locale: string, + text: string + } | null>(null); + const { playAudio, pauseAudio, stopAudio } = useAudioPlayer(); + const readIPA = async () => { + if (!textInfo) { + respref.current!.innerText = '请先生成IPA。'; + return; + } + const voice = voicesData.find(v => v.locale.startsWith(textInfo.locale)); + if (!voice) { + respref.current!.innerText = '暂不支持朗读' + textInfo.lang; + return; + } + const tts = new EdgeTTS(textInfo.text, voice.short_name); + const result = await tts.synthesize(); + playAudio(URL.createObjectURL(result.audio)); + } + const generateIPA = () => { + if (!reqEnabled) return; + setReqEnabled(false); + + respref.current!.innerText = '生成国际音标中,请稍等~'; + let timer: NodeJS.Timeout; + (() => { + let count = 0; + timer = setInterval(() => { + respref.current!.innerText = '正在生成国际音标(IPA),请稍等~'; + respref.current!.innerText += `\n(waiting for ${++count}s)` + }, 1000); + })(); + + const text = inputref.current!.value.trim(); + if (text.length === 0) return; + + const params = new URLSearchParams({ text: text }); + fetch(`/api/ipa?${params}`) + .then(response => { + if (!response.ok) { + return response.json().then(resj => { + throw new Error(`HTTP ${response.status}: ${resj.error} ${resj.message}`); + }) + } + return response.json(); + }) + .then(data => { + setTextInfo({ ...data, text: text }); + respref.current!.innerText = `LANG: ${data.lang}\nIPA: ${data.ipa}`; + }) + .catch(error => { + respref.current!.innerText = `错误: ${error.message}`; + }) + .finally(() => { + setReqEnabled(true); + clearInterval(timer); + }); + } + + return (<> +
+ +
+
+ + +
+
+ ) +}; \ No newline at end of file diff --git a/src/app/ipa-reader/layout.tsx b/src/app/ipa-reader/layout.tsx deleted file mode 100644 index 65594bd..0000000 --- a/src/app/ipa-reader/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "IPA Reader", - description: "read ipa aloud", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - {children} - - - ); -} diff --git a/src/app/ipa-reader/page.tsx b/src/app/ipa-reader/page.tsx index c9f9ce2..abba037 100644 --- a/src/app/ipa-reader/page.tsx +++ b/src/app/ipa-reader/page.tsx @@ -1,155 +1,32 @@ "use client"; -import Button from "@/components/Button"; -import { useEffect, useRef, useState } from "react"; -import { EdgeTTS } from "edge-tts-universal/browser"; +import { useEffect, useState } from "react"; +import IPAForm from "./IPAForm"; -const useAudioPlayer = () => { - const audioRef = useRef(null); - useEffect(() => { - audioRef.current = new Audio(); - - return () => { - if (audioRef.current) { - audioRef.current.pause(); - audioRef.current = null; - } - }; - }, []); - - const playAudio = (audioUrl: string) => { - if (audioRef.current) { - audioRef.current.src = audioUrl; - audioRef.current.play().catch(error => { - console.error('播放失败:', error); - }); - } - }; - - const pauseAudio = () => { - if (audioRef.current) { - audioRef.current.pause(); - } - }; - - const stopAudio = () => { - if (audioRef.current) { - audioRef.current.pause(); - audioRef.current.currentTime = 0; - } - }; - - return { - playAudio, - pauseAudio, - stopAudio - }; -}; export default function Home() { - const respref = useRef(null); - const inputref = useRef(null); - const [reqEnabled, setReqEnabled] = useState(true); - const [textInfo, setTextInfo] = useState<{ - lang: string, - ipa: string, - locale: string, - text: string - } | null>(null); - const { playAudio, pauseAudio, stopAudio } = useAudioPlayer(); + const [voicesData, setVoicesData] = useState<{ locale: string, short_name: string }[] | null>(null); const [loading, setLoading] = useState(true); - useEffect(() => { - const fetchData = async () => { - try { - const response = await fetch('/list_of_voices.json'); - const jsonData = await response.json(); - setVoicesData(jsonData); - } catch (error) { - console.error('加载JSON失败:', error); - } finally { - setLoading(false); - } - }; - fetchData(); + fetch('/list_of_voices.json') + .then(res => res.json()) + .then(setVoicesData) + .catch(() => setVoicesData(null)) + .finally(() => setLoading(false)); }, []); if (loading) return
加载中...
; if (!voicesData) return
加载失败
; - - const readIPA = async () => { - if (!textInfo) { - respref.current!.innerText = '请先生成IPA。'; - return; - } - const voice = voicesData.find(v => v.locale.startsWith(textInfo.locale)); - if (!voice) { - respref.current!.innerText = '暂不支持朗读' + textInfo.lang; - return; - } - const tts = new EdgeTTS(textInfo.text, voice.short_name); - const result = await tts.synthesize(); - playAudio(URL.createObjectURL(result.audio)); - } - const generateIPA = () => { - if (!reqEnabled) return; - setReqEnabled(false); - - respref.current!.innerText = '生成国际音标中,请稍等~'; - let timer: NodeJS.Timeout; - (() => { - let count = 0; - timer = setInterval(() => { - respref.current!.innerText = '正在生成国际音标(IPA),请稍等~'; - respref.current!.innerText += `\n(waiting for ${++count}s)` - }, 1000); - })(); - - const text = inputref.current!.value.trim(); - if (text.length === 0) return; - - const params = new URLSearchParams({ text: text }); - fetch(`/api/ipa?${params}`) - .then(response => { - if (!response.ok) { - return response.json().then(resj => { - throw new Error(`HTTP ${response.status}: ${resj.error} ${resj.message}`); - }) - } - return response.json(); - }) - .then(data => { - setTextInfo({ ...data, text: text }); - respref.current!.innerText = `LANG: ${data.lang}\nIPA: ${data.ipa}`; - }) - .catch(error => { - respref.current!.innerText = `错误: ${error.message}`; - }) - .finally(() => { - setReqEnabled(true); - clearInterval(timer); - }); - } return (

IPA Reader

-
- -
-
- - -
-
+
); diff --git a/src/app/ipa-reader/useAudioPlayer.ts b/src/app/ipa-reader/useAudioPlayer.ts new file mode 100644 index 0000000..346ce94 --- /dev/null +++ b/src/app/ipa-reader/useAudioPlayer.ts @@ -0,0 +1,39 @@ +import { useRef, useEffect } from "react"; + +export default function useAudioPlayer() { + const audioRef = useRef(null); + useEffect(() => { + audioRef.current = new Audio(); + + return () => { + if (audioRef.current) { + audioRef.current.pause(); + audioRef.current = null; + } + }; + }, []); + const playAudio = (audioUrl: string) => { + if (audioRef.current) { + audioRef.current.src = audioUrl; + audioRef.current.play().catch(error => { + console.error('播放失败:', error); + }); + } + }; + const pauseAudio = () => { + if (audioRef.current) { + audioRef.current.pause(); + } + }; + const stopAudio = () => { + if (audioRef.current) { + audioRef.current.pause(); + audioRef.current.currentTime = 0; + } + }; + return { + playAudio, + pauseAudio, + stopAudio + }; +}; \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 864dbc5..5ce06e2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -14,7 +14,7 @@ const geistMono = Geist_Mono({ export const metadata: Metadata = { title: "Learn Languages", - description: "A Website to learn languages", + description: "A Website to Learn Languages", }; export default function RootLayout({ diff --git a/src/app/srt-player/layout.tsx b/src/app/srt-player/layout.tsx deleted file mode 100644 index 28fd902..0000000 --- a/src/app/srt-player/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "SRT Video Player", - description: "Practice spoken English", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - {children} - - - ); -} diff --git a/src/app/word-board/layout.tsx b/src/app/word-board/layout.tsx deleted file mode 100644 index dbfd6a2..0000000 --- a/src/app/word-board/layout.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "Word Board", - description: "Word board page", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - {children} - - - ); -} diff --git a/src/app/word-board/page.tsx b/src/app/word-board/page.tsx index 0a9ebf7..ac3110d 100644 --- a/src/app/word-board/page.tsx +++ b/src/app/word-board/page.tsx @@ -142,7 +142,7 @@ export default function Home() {
-
+