From 4529c58aade3886804b35e5c9925b1e21cd570c0 Mon Sep 17 00:00:00 2001 From: goddonebianu Date: Mon, 27 Oct 2025 18:20:34 +0800 Subject: [PATCH] format everything in zed --- .drone.yml | 2 +- .gitignore | 2 +- css.d.ts | 2 +- next.config.ts | 2 +- src/app/alphabet/MemoryCard.tsx | 140 ++-- src/app/alphabet/page.tsx | 149 +++-- src/app/api/ipa/route.ts | 86 ++- src/app/api/locale/route.ts | 88 +-- src/app/api/route.ts | 11 +- src/app/api/textinfo/route.ts | 85 +-- src/app/api/translate/route.ts | 87 +-- src/app/globals.css | 22 +- src/app/layout.tsx | 10 +- src/app/memorize/page.tsx | 234 +++---- src/app/page.tsx | 48 +- src/app/srt-player/UploadArea.tsx | 30 +- .../VideoPlayer/SubtitleDisplay.tsx | 34 +- src/app/srt-player/VideoPlayer/VideoPanel.tsx | 283 ++++---- src/app/srt-player/page.tsx | 22 +- src/app/srt-player/subtitle.ts | 100 +-- src/app/text-speaker/SaveList.tsx | 166 ++--- src/app/text-speaker/page.tsx | 561 ++++++++-------- src/app/translator/page.tsx | 206 +++--- src/app/word-board/TheBoard.tsx | 102 +-- src/app/word-board/page.tsx | 133 ++-- src/components/Button.tsx | 14 +- src/components/IconClick.tsx | 40 +- src/components/Navbar.tsx | 25 +- src/config/images.ts | 36 +- src/config/locales.ts | 606 +++++++++--------- src/config/word-board-config.ts | 2 +- src/hooks/useAudioPlayer.ts | 57 +- src/interfaces.ts | 24 +- src/utils.ts | 127 ++-- 34 files changed, 1927 insertions(+), 1609 deletions(-) diff --git a/.drone.yml b/.drone.yml index 2193a14..329d87b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -70,4 +70,4 @@ steps: trigger: branch: - - main \ No newline at end of file + - main diff --git a/.gitignore b/.gitignore index f6d1fba..7da0df6 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,4 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -.env \ No newline at end of file +.env diff --git a/css.d.ts b/css.d.ts index bb82f98..cbe652d 100644 --- a/css.d.ts +++ b/css.d.ts @@ -1 +1 @@ - declare module '*.css'; \ No newline at end of file +declare module "*.css"; diff --git a/next.config.ts b/next.config.ts index a028faf..5dc6561 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,7 +3,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ output: "standalone", - allowedDevOrigins: ["192.168.3.65"] + allowedDevOrigins: ["192.168.3.65"], }; export default nextConfig; diff --git a/src/app/alphabet/MemoryCard.tsx b/src/app/alphabet/MemoryCard.tsx index 88bfa58..dfe0ad7 100644 --- a/src/app/alphabet/MemoryCard.tsx +++ b/src/app/alphabet/MemoryCard.tsx @@ -2,55 +2,99 @@ import Button from "@/components/Button"; import IconClick from "@/components/IconClick"; import IMAGES from "@/config/images"; import { Letter, SupportedAlphabets } from "@/interfaces"; -import { Dispatch, KeyboardEvent, SetStateAction, useEffect, useState } from "react"; +import { + Dispatch, + KeyboardEvent, + SetStateAction, + useEffect, + useState, +} from "react"; -export default function MemoryCard( - { - alphabet, - setChosenAlphabet - }: { - alphabet: Letter[], - setChosenAlphabet: Dispatch> - } -) { - const [index, setIndex] = useState(Math.floor(Math.random() * alphabet.length)); - const [more, setMore] = useState(false); - const [ipaDisplay, setIPADisplay] = useState(true); - const [letterDisplay, setLetterDisplay] = useState(true); +export default function MemoryCard({ + alphabet, + setChosenAlphabet, +}: { + alphabet: Letter[]; + setChosenAlphabet: Dispatch>; +}) { + const [index, setIndex] = useState( + Math.floor(Math.random() * alphabet.length), + ); + const [more, setMore] = useState(false); + const [ipaDisplay, setIPADisplay] = useState(true); + const [letterDisplay, setLetterDisplay] = useState(true); - useEffect(() => { - const handleKeydown = (e: globalThis.KeyboardEvent) => { - if (e.key === ' ') refresh(); - } - document.addEventListener('keydown', handleKeydown); - return () => document.removeEventListener('keydown', handleKeydown); - }); + useEffect(() => { + const handleKeydown = (e: globalThis.KeyboardEvent) => { + if (e.key === " ") refresh(); + }; + document.addEventListener("keydown", handleKeydown); + return () => document.removeEventListener("keydown", handleKeydown); + }); - const letter = alphabet[index]; - const refresh = () => { - setIndex(Math.floor(Math.random() * alphabet.length)); - } - return ( -
) => e.preventDefault()}> -
-
- setChosenAlphabet(null)}> -
-
- {letterDisplay ? letter.letter : ''} - {ipaDisplay ? letter.letter_sound_ipa : ''} -
-
- - setMore(!more)}> - { - more ? (<> - - - ) : (<>) - } -
-
+ const letter = alphabet[index]; + const refresh = () => { + setIndex(Math.floor(Math.random() * alphabet.length)); + }; + return ( +
) => e.preventDefault()} + > +
+
+ setChosenAlphabet(null)} + >
- ); -} \ No newline at end of file +
+ + {letterDisplay ? letter.letter : ""} + + + {ipaDisplay ? letter.letter_sound_ipa : ""} + +
+
+ + setMore(!more)} + > + {more ? ( + <> + + + + ) : ( + <> + )} +
+
+
+ ); +} diff --git a/src/app/alphabet/page.tsx b/src/app/alphabet/page.tsx index 0c40c59..52d46e8 100644 --- a/src/app/alphabet/page.tsx +++ b/src/app/alphabet/page.tsx @@ -1,4 +1,4 @@ -'use client'; +"use client"; import Button from "@/components/Button"; import { Letter, SupportedAlphabets } from "@/interfaces"; @@ -7,78 +7,91 @@ import MemoryCard from "./MemoryCard"; import { Navbar } from "@/components/Navbar"; export default function Alphabet() { - const [chosenAlphabet, setChosenAlphabet] = useState(null); - const [alphabetData, setAlphabetData] = useState>({ - japanese: null, - english: null, - esperanto: null, - uyghur: null - }); - const [loadingState, setLoadingState] = useState<'idle' | 'loading' | 'success' | 'error'>('idle'); + const [chosenAlphabet, setChosenAlphabet] = + useState(null); + const [alphabetData, setAlphabetData] = useState< + Record + >({ + japanese: null, + english: null, + esperanto: null, + uyghur: null, + }); + const [loadingState, setLoadingState] = useState< + "idle" | "loading" | "success" | "error" + >("idle"); + useEffect(() => { + if (chosenAlphabet && !alphabetData[chosenAlphabet]) { + setLoadingState("loading"); - useEffect(() => { - if (chosenAlphabet && !alphabetData[chosenAlphabet]) { - setLoadingState('loading'); + fetch("/alphabets/" + chosenAlphabet + ".json") + .then((res) => { + if (!res.ok) throw new Error("Network response was not ok"); + return res.json(); + }) + .then((obj) => { + setAlphabetData((prev) => ({ + ...prev, + [chosenAlphabet]: obj as Letter[], + })); + setLoadingState("success"); + }) + .catch(() => { + setLoadingState("error"); + }); + } + }, [chosenAlphabet, alphabetData]); - fetch('/alphabets/' + chosenAlphabet + '.json') - .then(res => { - if (!res.ok) throw new Error('Network response was not ok'); - return res.json(); - }).then((obj) => { - setAlphabetData(prev => ({ ...prev, [chosenAlphabet]: obj as Letter[] })); - setLoadingState('success'); - }).catch(() => { - setLoadingState('error'); - }); - } - }, [chosenAlphabet, alphabetData]); + useEffect(() => { + if (loadingState === "error") { + const timer = setTimeout(() => { + setLoadingState("idle"); + setChosenAlphabet(null); + }, 2000); + return () => clearTimeout(timer); + } + }, [loadingState]); - useEffect(() => { - if (loadingState === 'error') { - const timer = setTimeout(() => { - setLoadingState('idle'); - setChosenAlphabet(null); - }, 2000); - return () => clearTimeout(timer); - } - }, [loadingState]); - - if (!chosenAlphabet) return (<> + if (!chosenAlphabet) + return ( + <>
- 请选择您想学习的字符 -
- - - - -
+ 请选择您想学习的字符 +
+ + + + +
- + ); - if (loadingState === 'loading') { - return '加载中...'; - } - if (loadingState === 'error') { - return '加载失败,请重试'; - } - if (loadingState === 'success' && alphabetData[chosenAlphabet]) { - return (<> - - - - ); - } - return null; -} \ No newline at end of file + if (loadingState === "loading") { + return "加载中..."; + } + if (loadingState === "error") { + return "加载失败,请重试"; + } + if (loadingState === "success" && alphabetData[chosenAlphabet]) { + return ( + <> + + + + ); + } + return null; +} diff --git a/src/app/api/ipa/route.ts b/src/app/api/ipa/route.ts index 74bc7ed..2a05712 100644 --- a/src/app/api/ipa/route.ts +++ b/src/app/api/ipa/route.ts @@ -1,11 +1,12 @@ -import { callZhipuAPI } from "@/utils"; +import { callZhipuAPI, handleAPIError } from "@/utils"; import { NextRequest, NextResponse } from "next/server"; async function getIPA(text: string) { - console.log(`get ipa of ${text}`); - const messages = [ - { - role: 'user', content: ` + console.log(`get ipa of ${text}`); + const messages = [ + { + role: "user", + content: ` 请推断以下文本的语言,生成对应的宽式国际音标(IPA)以及locale,以JSON格式返回 [${text}] 结果如: @@ -18,47 +19,44 @@ async function getIPA(text: string) { ipa一定要加[], locale如果可能有多个,选取最可能的一个,其中使用符号"-", locale如果推断失败,就返回{"locale": "en-US"} -` - }]; - try { - const response = await callZhipuAPI(messages); - let to_parse = response.choices[0].message.content.trim() as string; - if (to_parse.startsWith('`')) to_parse = to_parse.slice(7, to_parse.length - 3); - if (to_parse.length === 0) throw Error('ai啥也每说'); - return JSON.parse(to_parse); - } catch (error) { - console.error(error); - return null; - } +`, + }, + ]; + try { + const response = await callZhipuAPI(messages); + let to_parse = response.choices[0].message.content.trim() as string; + if (to_parse.startsWith("`")) + to_parse = to_parse.slice(7, to_parse.length - 3); + if (to_parse.length === 0) throw Error("ai啥也每说"); + return JSON.parse(to_parse); + } catch (error) { + console.error(error); + return null; + } } export async function GET(request: NextRequest) { - try { - const searchParams = request.nextUrl.searchParams; - const text = searchParams.get('text'); + try { + const searchParams = request.nextUrl.searchParams; + const text = searchParams.get("text"); - if (!text) { - return NextResponse.json( - { error: "查询参数错误", message: "text参数是必需的" }, - { status: 400 } - ); - } - - const textInfo = await getIPA(text); - if (!textInfo) { - return NextResponse.json( - { error: "服务暂时不可用", message: "LLM API 请求失败" }, - { status: 503 } - ); - } - - return NextResponse.json(textInfo, { status: 200 }); - - } catch (error) { - console.error('API 错误:', error); - return NextResponse.json( - { error: "服务器内部错误", message: "请稍后重试" }, - { status: 500 } - ); + if (!text) { + return NextResponse.json( + { error: "查询参数错误", message: "text参数是必需的" }, + { status: 400 }, + ); } -} \ No newline at end of file + + const textInfo = await getIPA(text); + if (!textInfo) { + return NextResponse.json( + { error: "服务暂时不可用", message: "LLM API 请求失败" }, + { status: 503 }, + ); + } + + return NextResponse.json(textInfo, { status: 200 }); + } catch (error) { + handleAPIError(error, "请稍后再试"); + } +} diff --git a/src/app/api/locale/route.ts b/src/app/api/locale/route.ts index 756e684..810836c 100644 --- a/src/app/api/locale/route.ts +++ b/src/app/api/locale/route.ts @@ -2,10 +2,11 @@ import { callZhipuAPI } from "@/utils"; import { NextRequest, NextResponse } from "next/server"; async function getLocale(text: string) { - console.log(`get locale of ${text}`); - const messages = [ - { - role: 'user', content: ` + console.log(`get locale of ${text}`); + const messages = [ + { + role: "user", + content: ` 请推断以下文本的的locale,以JSON格式返回 [${text}] 结果如: @@ -16,47 +17,48 @@ async function getLocale(text: string) { 直接返回json文本, locale如果可能有多个,选取最可能的一个,其中使用符号"-", locale如果推断失败,就返回{"locale": "en-US"} -` - }]; - try { - const response = await callZhipuAPI(messages); - let to_parse = response.choices[0].message.content.trim() as string; - if (to_parse.startsWith('`')) to_parse = to_parse.slice(7, to_parse.length - 3); - if (to_parse.length === 0) throw Error('ai啥也每说'); - return JSON.parse(to_parse); - } catch (error) { - console.error(error); - return null; - } +`, + }, + ]; + try { + const response = await callZhipuAPI(messages); + let to_parse = response.choices[0].message.content.trim() as string; + if (to_parse.startsWith("`")) + to_parse = to_parse.slice(7, to_parse.length - 3); + if (to_parse.length === 0) throw Error("ai啥也每说"); + return JSON.parse(to_parse); + } catch (error) { + console.error(error); + return null; + } } export async function GET(request: NextRequest) { - try { - const searchParams = request.nextUrl.searchParams; - const text = searchParams.get('text'); + try { + const searchParams = request.nextUrl.searchParams; + const text = searchParams.get("text"); - if (!text) { - return NextResponse.json( - { error: "查询参数错误", message: "text参数是必需的" }, - { status: 400 } - ); - } - - const textInfo = await getLocale(text.slice(0, 30)); - if (!textInfo) { - return NextResponse.json( - { error: "服务暂时不可用", message: "LLM API 请求失败" }, - { status: 503 } - ); - } - - return NextResponse.json(textInfo, { status: 200 }); - - } catch (error) { - console.error('API 错误:', error); - return NextResponse.json( - { error: "服务器内部错误", message: "请稍后重试" }, - { status: 500 } - ); + if (!text) { + return NextResponse.json( + { error: "查询参数错误", message: "text参数是必需的" }, + { status: 400 }, + ); } -} \ No newline at end of file + + const textInfo = await getLocale(text.slice(0, 30)); + if (!textInfo) { + return NextResponse.json( + { error: "服务暂时不可用", message: "LLM API 请求失败" }, + { status: 503 }, + ); + } + + return NextResponse.json(textInfo, { status: 200 }); + } catch (error) { + console.error("API 错误:", error); + return NextResponse.json( + { error: "服务器内部错误", message: "请稍后重试" }, + { status: 500 }, + ); + } +} diff --git a/src/app/api/route.ts b/src/app/api/route.ts index 352f57f..8e79113 100644 --- a/src/app/api/route.ts +++ b/src/app/api/route.ts @@ -2,8 +2,11 @@ import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { const url = request.url; - return NextResponse.json({ - message: "Hello World", - url: url - }, { status: 200 }); + return NextResponse.json( + { + message: "Hello World", + url: url, + }, + { status: 200 }, + ); } diff --git a/src/app/api/textinfo/route.ts b/src/app/api/textinfo/route.ts index 60e57b3..fab0bb4 100644 --- a/src/app/api/textinfo/route.ts +++ b/src/app/api/textinfo/route.ts @@ -2,10 +2,11 @@ import { callZhipuAPI } from "@/utils"; import { NextRequest, NextResponse } from "next/server"; async function getTextinfo(text: string) { - console.log(`get textinfo of ${text}`); - const messages = [ - { - role: 'user', content: ` + console.log(`get textinfo of ${text}`); + const messages = [ + { + role: "user", + content: ` 请推断以下文本的语言、locale,生成宽式国际音标(IPA),以JSON格式返回 [${text}] 结果如: @@ -20,45 +21,47 @@ async function getTextinfo(text: string) { ipa一定要加[], lang的值是小写字母的英文的语言名称, locale如果可能有多个,选取最可能的一个,其中使用符号"-" -` - }]; - try { - const response = await callZhipuAPI(messages); - let to_parse = response.choices[0].message.content.trim() as string; - if (to_parse.startsWith('`')) to_parse = to_parse.slice(7, to_parse.length - 3); - if (to_parse.length === 0) throw Error('ai啥也每说'); - return JSON.parse(to_parse); - } catch (error) { - console.error(error); - return null; - } +`, + }, + ]; + try { + const response = await callZhipuAPI(messages); + let to_parse = response.choices[0].message.content.trim() as string; + if (to_parse.startsWith("`")) + to_parse = to_parse.slice(7, to_parse.length - 3); + if (to_parse.length === 0) throw Error("ai啥也每说"); + return JSON.parse(to_parse); + } catch (error) { + console.error(error); + return null; + } } export async function GET(request: NextRequest) { - try { - const searchParams = request.nextUrl.searchParams; - const text = searchParams.get('text'); + try { + const searchParams = request.nextUrl.searchParams; + const text = searchParams.get("text"); - if (!text) { - return NextResponse.json( - { error: "查询参数错误", message: "text参数是必需的" }, - { status: 400 } - ); - } - - const textInfo = await getTextinfo(text); - if (!textInfo) { - return NextResponse.json( - { error: "服务暂时不可用", message: "LLM API 请求失败" }, - { status: 503 } - ); - } - return NextResponse.json(textInfo, { status: 200 }); - } catch (error) { - console.error('API 错误:', error); - return NextResponse.json( - { error: "服务器内部错误", message: "请稍后重试" }, - { status: 500 } - ); + if (!text) { + return NextResponse.json( + { error: "查询参数错误", message: "text参数是必需的" }, + { status: 400 }, + ); } -} \ No newline at end of file + + const textInfo = await getTextinfo(text); + if (!textInfo) { + return NextResponse.json( + { error: "服务暂时不可用", message: "LLM API 请求失败" }, + { status: 503 }, + ); + } + return NextResponse.json(textInfo, { status: 200 }); + } catch (error) { + console.error("API 错误:", error); + return NextResponse.json( + { error: "服务器内部错误", message: "请稍后重试" }, + { status: 500 }, + ); + } +} diff --git a/src/app/api/translate/route.ts b/src/app/api/translate/route.ts index ef19cc3..6a463fc 100644 --- a/src/app/api/translate/route.ts +++ b/src/app/api/translate/route.ts @@ -2,10 +2,11 @@ import { callZhipuAPI } from "@/utils"; import { NextRequest, NextResponse } from "next/server"; async function translate(text: string, target_lang: string) { - console.log(`translate "${text}" into ${target_lang}`); - const messages = [ - { - role: 'user', content: ` + console.log(`translate "${text}" into ${target_lang}`); + const messages = [ + { + role: "user", + content: ` 请推断以下文本的语言、locale,并翻译到目标语言[${target_lang}],同样需要locale信息,以JSON格式返回 [${text}] 结果如: @@ -18,46 +19,48 @@ async function translate(text: string, target_lang: string) { 直接返回json文本, locale如果可能有多个,选取最可能的一个,其中使用符号"-", locale如果推断失败,就当作是en-US -` - }]; - try { - const response = await callZhipuAPI(messages); - let to_parse = response.choices[0].message.content.trim() as string; - if (to_parse.startsWith('`')) to_parse = to_parse.slice(7, to_parse.length - 3); - if (to_parse.length === 0) throw Error('ai啥也每说'); - return JSON.parse(to_parse); - } catch (error) { - console.error(error); - return null; - } +`, + }, + ]; + try { + const response = await callZhipuAPI(messages); + let to_parse = response.choices[0].message.content.trim() as string; + if (to_parse.startsWith("`")) + to_parse = to_parse.slice(7, to_parse.length - 3); + if (to_parse.length === 0) throw Error("ai啥也每说"); + return JSON.parse(to_parse); + } catch (error) { + console.error(error); + return null; + } } export async function GET(request: NextRequest) { - try { - const searchParams = request.nextUrl.searchParams; - const text = searchParams.get('text'); - const target_lang = searchParams.get('target'); + try { + const searchParams = request.nextUrl.searchParams; + const text = searchParams.get("text"); + const target_lang = searchParams.get("target"); - if (!text || !target_lang) { - return NextResponse.json( - { error: "查询参数错误", message: "text参数, target参数是必需的" }, - { status: 400 } - ); - } - - const textInfo = await translate(text, target_lang); - if (!textInfo) { - return NextResponse.json( - { error: "服务暂时不可用", message: "LLM API 请求失败" }, - { status: 503 } - ); - } - return NextResponse.json(textInfo, { status: 200 }); - } catch (error) { - console.error('API 错误:', error); - return NextResponse.json( - { error: "服务器内部错误", message: "请稍后重试" }, - { status: 500 } - ); + if (!text || !target_lang) { + return NextResponse.json( + { error: "查询参数错误", message: "text参数, target参数是必需的" }, + { status: 400 }, + ); } -} \ No newline at end of file + + const textInfo = await translate(text, target_lang); + if (!textInfo) { + return NextResponse.json( + { error: "服务暂时不可用", message: "LLM API 请求失败" }, + { status: 503 }, + ); + } + return NextResponse.json(textInfo, { status: 200 }); + } catch (error) { + console.error("API 错误:", error); + return NextResponse.json( + { error: "服务器内部错误", message: "请稍后重试" }, + { status: 500 }, + ); + } +} diff --git a/src/app/globals.css b/src/app/globals.css index 9af7a3c..416c53a 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,15 +1,15 @@ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; + --background: #ffffff; + --foreground: #171717; } @theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); } /* @media (prefers-color-scheme: dark) { @@ -20,13 +20,13 @@ } */ body { - background: var(--background); - color: var(--foreground); - font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif; + background: var(--background); + color: var(--foreground); + font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif; } .code-block { - font-family: var(--font-geist-mono), monospace; + font-family: var(--font-geist-mono), monospace; } -@source '../../node_modules/rc-modal-sheet/**/*.js' \ No newline at end of file +@source '../../node_modules/rc-modal-sheet/**/*.js'; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c78d04e..2af812a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,12 +1,12 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; -import type { Viewport } from 'next' +import type { Viewport } from "next"; export const viewport: Viewport = { - width: 'device-width', - initialScale: 1.0 -} + width: "device-width", + initialScale: 1.0, +}; const geistSans = Geist({ variable: "--font-geist-sans", @@ -23,8 +23,6 @@ export const metadata: Metadata = { description: "A Website to Learn Languages", }; - - export default function RootLayout({ children, }: Readonly<{ diff --git a/src/app/memorize/page.tsx b/src/app/memorize/page.tsx index 33cfe6d..e831e24 100644 --- a/src/app/memorize/page.tsx +++ b/src/app/memorize/page.tsx @@ -1,135 +1,146 @@ -'use client'; +"use client"; import Button from "@/components/Button"; import { Select, Option } from "@material-tailwind/react"; import { ChangeEvent, useState } from "react"; interface ACardProps { - children?: React.ReactNode, - className?: string + children?: React.ReactNode; + className?: string; } function ACard({ children, className }: ACardProps) { - return ( -
- {children} -
); + return ( +
+ {children} +
+ ); } interface BCard { - children?: React.ReactNode, - className?: string + children?: React.ReactNode; + className?: string; } function BCard({ children, className }: BCard) { - return ( -
- {children} -
); + return ( +
+ {children} +
+ ); } interface WordData { - locale1: string, - locale2: string, - data: Record + locale1: string; + locale2: string; + data: Record; } export default function Memorize() { - const [pageState, setPageState] = useState<'choose' | 'start' | 'main' | 'edit'>('edit'); - const [wordData, setWordData] = useState({ - locale1: 'en-US', - locale2: 'zh-CN', - data: { 'hello': '你好' } - }); - if (pageState === 'main') { - return (<> -
- -

- Memorize -

-
- -

Lang1: {wordData.locale1}

-

Lang2: {wordData.locale2}

-

Total Words: {Object.keys(wordData.data).length}

-
-
-
- - - - - - -
-
+ const [pageState, setPageState] = useState< + "choose" | "start" | "main" | "edit" + >("edit"); + const [wordData, setWordData] = useState({ + locale1: "en-US", + locale2: "zh-CN", + data: { hello: "你好" }, + }); + if (pageState === "main") { + return ( + <> +
+ +

+ Memorize +

+
+ +

Lang1: {wordData.locale1}

+

Lang2: {wordData.locale2}

+

Total Words: {Object.keys(wordData.data).length}

+
- ); - } - if (pageState === 'choose') { - return (<> - ); - } - if (pageState === 'start') { - return (<> - ); - } - if (pageState === 'edit') { - const convertIntoWordData = (text: string) => { - const t1 = text.split('\n').map(v => v.trim()).filter(v => v.includes(',')); - const t2 = t1.map(v => { - const [left, right] = v.split(',', 2).map(v => v.trim()); - if (left && right) - return { - [left]: right - }; - else return {}; - }); - const new_data = { - locale1: wordData.locale1, - locale2: wordData.locale2, - data: Object.assign({}, ...t2) - }; - setWordData(new_data); - } - const convertFromWordData = () => { - let result = ''; - for (const k in wordData.data) { - result += `${k}, ${wordData.data[k]}\n`; - } - return result; - } - let input = convertFromWordData(); - const handleSave = () => { - convertIntoWordData(input); - setPageState('main'); - } - const handleChange = (e: ChangeEvent) => { - input = e.target.value; - } - return (<> -
- - -
- - - - - - - -
-
-
-
+
+ + + + + +
+ +
+ + ); + } + if (pageState === "choose") { + return <>; + } + if (pageState === "start") { + return <>; + } + if (pageState === "edit") { + const convertIntoWordData = (text: string) => { + const t1 = text + .split("\n") + .map((v) => v.trim()) + .filter((v) => v.includes(",")); + const t2 = t1.map((v) => { + const [left, right] = v.split(",", 2).map((v) => v.trim()); + if (left && right) + return { + [left]: right, + }; + else return {}; + }); + const new_data = { + locale1: wordData.locale1, + locale2: wordData.locale2, + data: Object.assign({}, ...t2), + }; + setWordData(new_data); + }; + const convertFromWordData = () => { + let result = ""; + for (const k in wordData.data) { + result += `${k}, ${wordData.data[k]}\n`; + } + return result; + }; + let input = convertFromWordData(); + const handleSave = () => { + convertIntoWordData(input); + setPageState("main"); + }; + const handleChange = (e: ChangeEvent) => { + input = e.target.value; + }; + return ( + <> +
+ + +
+ + + + + + + +
+
+
+
- {/* */} - ); - } + + ); + } } diff --git a/src/app/page.tsx b/src/app/page.tsx index 95dac9c..c5904a8 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,25 +6,28 @@ function TopArea() {

Learn Languages

-

Here is a very useful website to help you learn almost every language in the world, including constructed ones.

+

+ Here is a very useful website to help you learn almost every language + in the world, including constructed ones. +

- ) + ); } interface LinkAreaProps { - href: string, - name: string, - description: string, - color: string + href: string; + name: string; + description: string; + color: string; } -function LinkArea( - { href, name, description, color }: LinkAreaProps -) { +function LinkArea({ href, name, description, color }: LinkAreaProps) { return ( - + className={`h-32 md:h-64 flex justify-center items-center`} + >

{name}

{description}

@@ -36,17 +39,18 @@ function LinkArea( function LinkGrid() { return (
- + color="#a56068" + > + color="#578aad" + > {/* - + + color="#dd7486" + > + color="#cab48a" + >
- ) + ); } function Fortune() { @@ -97,5 +104,6 @@ export default function Home() { - ); + + ); } diff --git a/src/app/srt-player/UploadArea.tsx b/src/app/srt-player/UploadArea.tsx index 070c46e..b1644b2 100644 --- a/src/app/srt-player/UploadArea.tsx +++ b/src/app/srt-player/UploadArea.tsx @@ -1,21 +1,19 @@ import Button from "@/components/Button"; import { useRef } from "react"; -export default function UploadArea( - { - setVideoUrl, - setSrtUrl - }: { - setVideoUrl: (url: string | null) => void; - setSrtUrl: (url: string | null) => void; - } -) { +export default function UploadArea({ + setVideoUrl, + setSrtUrl, +}: { + setVideoUrl: (url: string | null) => void; + setSrtUrl: (url: string | null) => void; +}) { const inputRef = useRef(null); const uploadVideo = () => { const input = inputRef.current; if (input) { - input.setAttribute('accept', 'video/*'); + input.setAttribute("accept", "video/*"); input.click(); input.onchange = () => { const file = input.files?.[0]; @@ -24,11 +22,11 @@ export default function UploadArea( } }; } - } + }; const uploadSRT = () => { const input = inputRef.current; if (input) { - input.setAttribute('accept', '.srt'); + input.setAttribute("accept", ".srt"); input.click(); input.onchange = () => { const file = input.files?.[0]; @@ -37,12 +35,12 @@ export default function UploadArea( } }; } - } + }; return (
-
- ) -} \ No newline at end of file +
+ ); +} diff --git a/src/app/srt-player/VideoPlayer/SubtitleDisplay.tsx b/src/app/srt-player/VideoPlayer/SubtitleDisplay.tsx index f157ae1..5138bc8 100644 --- a/src/app/srt-player/VideoPlayer/SubtitleDisplay.tsx +++ b/src/app/srt-player/VideoPlayer/SubtitleDisplay.tsx @@ -1,21 +1,19 @@ import { inspect } from "@/utils"; export default function SubtitleDisplay({ subtitle }: { subtitle: string }) { - const words = subtitle.match(/\b[\w']+(?:-[\w']+)*\b/g) || []; - let i = 0; - return ( -
- { - words.map((v) => ( - - {v + ' '} - - )) - } -
- ); -} \ No newline at end of file + const words = subtitle.match(/\b[\w']+(?:-[\w']+)*\b/g) || []; + let i = 0; + return ( +
+ {words.map((v) => ( + + {v + " "} + + ))} +
+ ); +} diff --git a/src/app/srt-player/VideoPlayer/VideoPanel.tsx b/src/app/srt-player/VideoPlayer/VideoPanel.tsx index a327bba..1248d89 100644 --- a/src/app/srt-player/VideoPlayer/VideoPanel.tsx +++ b/src/app/srt-player/VideoPlayer/VideoPanel.tsx @@ -4,176 +4,211 @@ import Button from "@/components/Button"; import { getIndex, parseSrt, getNearistIndex } from "../subtitle"; type VideoPanelProps = { - videoUrl: string | null; - srtUrl: string | null; + videoUrl: string | null; + srtUrl: string | null; }; -const VideoPanel = forwardRef(( - { videoUrl, srtUrl }, videoRef -) => { +const VideoPanel = forwardRef( + ({ videoUrl, srtUrl }, videoRef) => { videoRef = videoRef as React.RefObject; const [isPlaying, setIsPlaying] = useState(false); const [srtLength, setSrtLength] = useState(0); const [progress, setProgress] = useState(-1); const [autoPause, setAutoPause] = useState(true); - const [spanText, setSpanText] = useState(''); - const [subtitle, setSubtitle] = useState(''); - const parsedSrtRef = useRef<{ start: number; end: number; text: string; }[] | null>(null); + const [spanText, setSpanText] = useState(""); + const [subtitle, setSubtitle] = useState(""); + const parsedSrtRef = useRef< + { start: number; end: number; text: string }[] | null + >(null); const rafldRef = useRef(0); const ready = useRef({ - 'vid': false, - 'sub': false, - 'all': function () { return this.vid && this.sub } + vid: false, + sub: false, + all: function () { + return this.vid && this.sub; + }, }); const togglePlayPause = useCallback(() => { - if (!videoUrl) return; + if (!videoUrl) return; - const video = videoRef.current; - if (!video) return; - if (video.paused || video.currentTime === 0) { - video.play(); - } else { - video.pause(); - } - setIsPlaying(!video.paused); + const video = videoRef.current; + if (!video) return; + if (video.paused || video.currentTime === 0) { + video.play(); + } else { + video.pause(); + } + setIsPlaying(!video.paused); }, [videoRef, videoUrl]); useEffect(() => { - const handleKeyDownEvent = (e: globalThis.KeyboardEvent) => { - if (e.key === 'n') { - next(); - } else if (e.key === 'p') { - previous(); - } else if (e.key === ' ') { - togglePlayPause(); - } else if (e.key === 'r') { - restart(); - } else if (e.key === 'a') { - ; handleAutoPauseToggle(); - } + const handleKeyDownEvent = (e: globalThis.KeyboardEvent) => { + if (e.key === "n") { + next(); + } else if (e.key === "p") { + previous(); + } else if (e.key === " ") { + togglePlayPause(); + } else if (e.key === "r") { + restart(); + } else if (e.key === "a") { + handleAutoPauseToggle(); } - document.addEventListener('keydown', handleKeyDownEvent); - return () => document.removeEventListener('keydown', handleKeyDownEvent) + }; + document.addEventListener("keydown", handleKeyDownEvent); + return () => document.removeEventListener("keydown", handleKeyDownEvent); }); useEffect(() => { - const cb = () => { - if (ready.current.all()) { - if (!parsedSrtRef.current) { - ; - } else if (isPlaying) { - // 这里负责显示当前时间的字幕与自动暂停 - const srt = parsedSrtRef.current; - const ct = videoRef.current?.currentTime as number; - const index = getIndex(srt, ct); - if (index !== null) { - setSubtitle(srt[index].text) - if (autoPause && ct >= (srt[index].end - 0.05) && ct < srt[index].end) { - videoRef.current!.currentTime = srt[index].start; - togglePlayPause(); - } - } else { - setSubtitle(''); - } - } else { - ; - } + const cb = () => { + if (ready.current.all()) { + if (!parsedSrtRef.current) { + } else if (isPlaying) { + // 这里负责显示当前时间的字幕与自动暂停 + const srt = parsedSrtRef.current; + const ct = videoRef.current?.currentTime as number; + const index = getIndex(srt, ct); + if (index !== null) { + setSubtitle(srt[index].text); + if ( + autoPause && + ct >= srt[index].end - 0.05 && + ct < srt[index].end + ) { + videoRef.current!.currentTime = srt[index].start; + togglePlayPause(); + } + } else { + setSubtitle(""); } - rafldRef.current = requestAnimationFrame(cb); + } else { + } } rafldRef.current = requestAnimationFrame(cb); - return () => { - cancelAnimationFrame(rafldRef.current); - } + }; + rafldRef.current = requestAnimationFrame(cb); + return () => { + cancelAnimationFrame(rafldRef.current); + }; }, [autoPause, isPlaying, togglePlayPause, videoRef]); useEffect(() => { - if (videoUrl && videoRef.current) { - videoRef.current.src = videoUrl; - videoRef.current.load(); - setIsPlaying(false); - ready.current['vid'] = true; - } + if (videoUrl && videoRef.current) { + videoRef.current.src = videoUrl; + videoRef.current.load(); + setIsPlaying(false); + ready.current["vid"] = true; + } }, [videoRef, videoUrl]); useEffect(() => { - if (srtUrl) { - fetch(srtUrl) - .then(response => response.text()) - .then(data => { - parsedSrtRef.current = parseSrt(data); - setSrtLength(parsedSrtRef.current.length); - ready.current['sub'] = true; - }); - } + if (srtUrl) { + fetch(srtUrl) + .then((response) => response.text()) + .then((data) => { + parsedSrtRef.current = parseSrt(data); + setSrtLength(parsedSrtRef.current.length); + ready.current["sub"] = true; + }); + } }, [srtUrl]); const timeUpdate = () => { - if (!parsedSrtRef.current || !videoRef.current) return; - const index = getIndex(parsedSrtRef.current, videoRef.current.currentTime); - if (!index) return; - setSpanText(`${index + 1}/${parsedSrtRef.current.length}`) - } + if (!parsedSrtRef.current || !videoRef.current) return; + const index = getIndex( + parsedSrtRef.current, + videoRef.current.currentTime, + ); + if (!index) return; + setSpanText(`${index + 1}/${parsedSrtRef.current.length}`); + }; const handleSeek = (e: React.ChangeEvent) => { - if (videoRef.current && parsedSrtRef.current) { - const newProgress = parseInt(e.target.value); - videoRef.current.currentTime = parsedSrtRef.current[newProgress]?.start || 0; - setProgress(newProgress); - } + if (videoRef.current && parsedSrtRef.current) { + const newProgress = parseInt(e.target.value); + videoRef.current.currentTime = + parsedSrtRef.current[newProgress]?.start || 0; + setProgress(newProgress); + } }; const handleAutoPauseToggle = () => { - setAutoPause(!autoPause); + setAutoPause(!autoPause); }; const next = () => { - if (!parsedSrtRef.current || !videoRef.current) return; - const i = getNearistIndex(parsedSrtRef.current, videoRef.current.currentTime); - if (i != null && i + 1 < parsedSrtRef.current.length) { - videoRef.current.currentTime = parsedSrtRef.current[i + 1].start; - videoRef.current.play(); - setIsPlaying(true); - } - } + if (!parsedSrtRef.current || !videoRef.current) return; + const i = getNearistIndex( + parsedSrtRef.current, + videoRef.current.currentTime, + ); + if (i != null && i + 1 < parsedSrtRef.current.length) { + videoRef.current.currentTime = parsedSrtRef.current[i + 1].start; + videoRef.current.play(); + setIsPlaying(true); + } + }; const previous = () => { - if (!parsedSrtRef.current || !videoRef.current) return; - const i = getNearistIndex(parsedSrtRef.current, videoRef.current.currentTime); - if (i != null && i - 1 >= 0) { - videoRef.current.currentTime = parsedSrtRef.current[i - 1].start; - videoRef.current.play(); - setIsPlaying(true); - } - } + if (!parsedSrtRef.current || !videoRef.current) return; + const i = getNearistIndex( + parsedSrtRef.current, + videoRef.current.currentTime, + ); + if (i != null && i - 1 >= 0) { + videoRef.current.currentTime = parsedSrtRef.current[i - 1].start; + videoRef.current.play(); + setIsPlaying(true); + } + }; const restart = () => { - if (!parsedSrtRef.current || !videoRef.current) return; - const i = getNearistIndex(parsedSrtRef.current, videoRef.current.currentTime); - if (i != null && i >= 0) { - videoRef.current.currentTime = parsedSrtRef.current[i].start; - videoRef.current.play(); - setIsPlaying(true); - } - } + if (!parsedSrtRef.current || !videoRef.current) return; + const i = getNearistIndex( + parsedSrtRef.current, + videoRef.current.currentTime, + ); + if (i != null && i >= 0) { + videoRef.current.currentTime = parsedSrtRef.current[i].start; + videoRef.current.play(); + setIsPlaying(true); + } + }; return ( -
- - -
- - - - - -
- - {spanText} +
+ + +
+ + + + +
+ + {spanText} +
); -}); + }, +); -VideoPanel.displayName = 'VideoPanel'; +VideoPanel.displayName = "VideoPanel"; -export default VideoPanel; \ No newline at end of file +export default VideoPanel; diff --git a/src/app/srt-player/page.tsx b/src/app/srt-player/page.tsx index 613003e..f2f9109 100644 --- a/src/app/srt-player/page.tsx +++ b/src/app/srt-player/page.tsx @@ -1,4 +1,4 @@ -'use client'; +"use client"; import { KeyboardEvent, useRef, useState } from "react"; import UploadArea from "./UploadArea"; @@ -10,18 +10,18 @@ export default function SrtPlayer() { const [videoUrl, setVideoUrl] = useState(null); const [srtUrl, setSrtUrl] = useState(null); - return (<> + return ( + <> -
) => e.preventDefault()}> +
) => e.preventDefault()} + >
- - + +
- ); + + ); } diff --git a/src/app/srt-player/subtitle.ts b/src/app/srt-player/subtitle.ts index 00f3f25..06dd833 100644 --- a/src/app/srt-player/subtitle.ts +++ b/src/app/srt-player/subtitle.ts @@ -1,52 +1,74 @@ export function parseSrt(data: string) { - const lines = data.split(/\r?\n/); - const result = []; - const re = new RegExp('(\\d{2}:\\d{2}:\\d{2},\\d{3})\\s*-->\\s*(\\d{2}:\\d{2}:\\d{2},\\d{3})'); - let i = 0; - while (i < lines.length) { - if (!lines[i].trim()) { i++; continue; } - i++; - if (i >= lines.length) break; - const timeMatch = lines[i].match(re); - if (!timeMatch) { i++; continue; } - const start = toSeconds(timeMatch[1]); - const end = toSeconds(timeMatch[2]); - i++; - let text = ''; - while (i < lines.length && lines[i].trim()) { - text += lines[i] + '\n'; - i++; - } - result.push({ start, end, text: text.trim() }); - i++; + const lines = data.split(/\r?\n/); + const result = []; + const re = new RegExp( + "(\\d{2}:\\d{2}:\\d{2},\\d{3})\\s*-->\\s*(\\d{2}:\\d{2}:\\d{2},\\d{3})", + ); + let i = 0; + while (i < lines.length) { + if (!lines[i].trim()) { + i++; + continue; } - return result; + i++; + if (i >= lines.length) break; + const timeMatch = lines[i].match(re); + if (!timeMatch) { + i++; + continue; + } + const start = toSeconds(timeMatch[1]); + const end = toSeconds(timeMatch[2]); + i++; + let text = ""; + while (i < lines.length && lines[i].trim()) { + text += lines[i] + "\n"; + i++; + } + result.push({ start, end, text: text.trim() }); + i++; + } + return result; } -export function getNearistIndex(srt: { start: number; end: number; text: string; }[], ct: number) { - for (let i = 0; i < srt.length; i++) { - const s = srt[i]; - const l = ct - s.start >= 0; - const r = ct - s.end >= 0; - if (!(l || r)) return i - 1; - if (l && (!r)) return i; - } +export function getNearistIndex( + srt: { start: number; end: number; text: string }[], + ct: number, +) { + for (let i = 0; i < srt.length; i++) { + const s = srt[i]; + const l = ct - s.start >= 0; + const r = ct - s.end >= 0; + if (!(l || r)) return i - 1; + if (l && !r) return i; + } } -export function getIndex(srt: { start: number; end: number; text: string; }[], ct: number) { - for (let i = 0; i < srt.length; i++) { - if (ct >= srt[i].start && ct <= srt[i].end) { - return i; - } +export function getIndex( + srt: { start: number; end: number; text: string }[], + ct: number, +) { + for (let i = 0; i < srt.length; i++) { + if (ct >= srt[i].start && ct <= srt[i].end) { + return i; } - return null; + } + return null; } -export function getSubtitle(srt: { start: number; end: number; text: string; }[], currentTime: number) { - return srt.find(sub => currentTime >= sub.start && currentTime <= sub.end) || null; +export function getSubtitle( + srt: { start: number; end: number; text: string }[], + currentTime: number, +) { + return ( + srt.find((sub) => currentTime >= sub.start && currentTime <= sub.end) || + null + ); } function toSeconds(timeStr: string): number { - const [h, m, s] = timeStr.replace(',', '.').split(':'); - return parseFloat((parseInt(h) * 3600 + parseInt(m) * 60 + parseFloat(s)).toFixed(3)); + const [h, m, s] = timeStr.replace(",", ".").split(":"); + return parseFloat( + (parseInt(h) * 3600 + parseInt(m) * 60 + parseFloat(s)).toFixed(3), + ); } diff --git a/src/app/text-speaker/SaveList.tsx b/src/app/text-speaker/SaveList.tsx index c730562..0a81426 100644 --- a/src/app/text-speaker/SaveList.tsx +++ b/src/app/text-speaker/SaveList.tsx @@ -1,4 +1,4 @@ -'use client'; +"use client"; import { getTextSpeakerData, setTextSpeakerData } from "@/utils"; import { useState } from "react"; @@ -8,88 +8,98 @@ import IconClick from "@/components/IconClick"; import IMAGES from "@/config/images"; interface TextCardProps { - item: z.infer; - handleUse: (item: z.infer) => void; - handleDel: (item: z.infer) => void; + item: z.infer; + handleUse: (item: z.infer) => void; + handleDel: (item: z.infer) => void; } -function TextCard({ - item, - handleUse, - handleDel -}: TextCardProps) { - const onUseClick = () => { - handleUse(item); - } - const onDelClick = () => { - handleDel(item); - } - return ( -
-
-
{item.text}
-
{item.ipa}
-
-
- - -
+function TextCard({ item, handleUse, handleDel }: TextCardProps) { + const onUseClick = () => { + handleUse(item); + }; + const onDelClick = () => { + handleDel(item); + }; + return ( +
+
+
+ {item.text}
- ); +
+ {item.ipa} +
+
+
+ +
+
+ ); } interface SaveListProps { - show?: boolean; - handleUse: (item: z.infer) => void; + show?: boolean; + handleUse: (item: z.infer) => void; } -export default function SaveList({ - show = false, - handleUse -}: SaveListProps) { - const [data, setData] = useState(getTextSpeakerData()); - const handleDel = (item: z.infer) => { - const current_data = getTextSpeakerData(); - current_data.splice( - current_data.findIndex(v => v.text === item.text), 1 - ); - setTextSpeakerData(current_data); - refresh(); +export default function SaveList({ show = false, handleUse }: SaveListProps) { + const [data, setData] = useState(getTextSpeakerData()); + const handleDel = (item: z.infer) => { + const current_data = getTextSpeakerData(); + current_data.splice( + current_data.findIndex((v) => v.text === item.text), + 1, + ); + setTextSpeakerData(current_data); + refresh(); + }; + const refresh = () => { + setData(getTextSpeakerData()); + }; + const handleDeleteAll = () => { + const yesorno = prompt("确定删光吗?(Y/N)")?.trim(); + if (yesorno && (yesorno === "Y" || yesorno === "y")) { + setTextSpeakerData([]); + refresh(); } - const refresh = () => { - setData(getTextSpeakerData()); - } - const handleDeleteAll = () => { - const yesorno = prompt('确定删光吗?(Y/N)')?.trim(); - if (yesorno && (yesorno === 'Y' || yesorno === 'y')) { - setTextSpeakerData([]); - refresh(); - } - } - if (show) return ( -
-
- - -
-
    - {data.map(v => - - )} -
+ }; + if (show) + return ( +
+
+ +
- ); else return (<>); -} \ No newline at end of file +
    + {data.map((v) => ( + + ))} +
+
+ ); + else return <>; +} diff --git a/src/app/text-speaker/page.tsx b/src/app/text-speaker/page.tsx index 60a5a07..188189d 100644 --- a/src/app/text-speaker/page.tsx +++ b/src/app/text-speaker/page.tsx @@ -4,7 +4,11 @@ import Button from "@/components/Button"; import IconClick from "@/components/IconClick"; import IMAGES from "@/config/images"; import { useAudioPlayer } from "@/hooks/useAudioPlayer"; -import { getTextSpeakerData, getTTSAudioUrl, setTextSpeakerData } from "@/utils"; +import { + getTextSpeakerData, + getTTSAudioUrl, + setTextSpeakerData, +} from "@/utils"; import { ChangeEvent, useEffect, useRef, useState } from "react"; import SaveList from "./SaveList"; import { TextSpeakerItemSchema } from "@/interfaces"; @@ -13,277 +17,320 @@ import { Navbar } from "@/components/Navbar"; import { VOICES } from "@/config/locales"; export default function TextSpeaker() { - const textareaRef = useRef(null); - const [showSpeedAdjust, setShowSpeedAdjust] = useState(false); - const [showSaveList, setShowSaveList] = useState(false); - const [saving, setSaving] = 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 [locale, setLocale] = useState(null); - const [ipa, setIPA] = useState(''); - const objurlRef = useRef(null); - const [processing, setProcessing] = useState(false); - const { playAudio, stopAudio, audioRef } = useAudioPlayer(); - useEffect(() => { - const audio = audioRef.current; - if (!audio) return; + const textareaRef = useRef(null); + const [showSpeedAdjust, setShowSpeedAdjust] = useState(false); + const [showSaveList, setShowSaveList] = useState(false); + const [saving, setSaving] = 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 [locale, setLocale] = useState(null); + const [ipa, setIPA] = useState(""); + const objurlRef = useRef(null); + const [processing, setProcessing] = useState(false); + const { playAudio, stopAudio, audioRef } = useAudioPlayer(); + useEffect(() => { + const audio = audioRef.current; + if (!audio) return; - const handleEnded = () => { - if (autopause) { - setPause(true); - } else { - playAudio(objurlRef.current!); - } - } - audio.addEventListener('ended', handleEnded); - return () => { - audio.removeEventListener('ended', handleEnded); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [audioRef, autopause]); + const handleEnded = () => { + if (autopause) { + setPause(true); + } else { + playAudio(objurlRef.current!); + } + }; + audio.addEventListener("ended", handleEnded); + return () => { + audio.removeEventListener("ended", handleEnded); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [audioRef, autopause]); - const speak = async () => { - if (processing) return; - setProcessing(true); + const speak = async () => { + if (processing) return; + setProcessing(true); - if (ipa.length === 0 && ipaEnabled && textRef.current.length !== 0) { - const params = new URLSearchParams({ - text: textRef.current - }); - fetch(`/api/ipa?${params}`) - .then(res => res.json()) - .then(data => { - setIPA(data.ipa); - }).catch(e => { - console.error(e); - setIPA(''); - }) - } + if (ipa.length === 0 && ipaEnabled && textRef.current.length !== 0) { + const params = new URLSearchParams({ + text: textRef.current, + }); + fetch(`/api/ipa?${params}`) + .then((res) => res.json()) + .then((data) => { + setIPA(data.ipa); + }) + .catch((e) => { + console.error(e); + setIPA(""); + }); + } - if (pause) { - // 如果没在读 - if (textRef.current.length === 0) { - // 没文本咋读 - } else { - setPause(false); + if (pause) { + // 如果没在读 + if (textRef.current.length === 0) { + // 没文本咋读 + } else { + setPause(false); - if (objurlRef.current) { - // 之前有播放 - playAudio(objurlRef.current); - } else { - // 第一次播放 - try { - let theLocale = locale; - if (!theLocale) { - console.log('downloading text info'); - const params = new URLSearchParams({ - text: textRef.current.slice(0, 30) - }); - const textinfo = await (await fetch(`/api/locale?${params}`)).json(); - setLocale(textinfo.locale); - theLocale = textinfo.locale as string; - } - - const voice = VOICES.find(v => v.locale.startsWith(theLocale)); - if (!voice) throw 'Voice not found.'; - - objurlRef.current = await getTTSAudioUrl( - textRef.current, - voice.short_name, - (() => { - if (speed === 1) return {}; - else if (speed < 1) return { - rate: `-${100 - speed * 100}%` - }; else return { - rate: `+${speed * 100 - 100}%` - }; - })() - ); - playAudio(objurlRef.current); - } catch (e) { - console.error(e); - - setPause(true); - setLocale(null); - - setProcessing(false); - } - } - } + if (objurlRef.current) { + // 之前有播放 + playAudio(objurlRef.current); } else { - // 如果在读就暂停 - setPause(true); - stopAudio(); - } - - setProcessing(false); - } - - const handleInputChange = (e: ChangeEvent) => { - textRef.current = e.target.value.trim(); - setLocale(null); - setIPA(''); - if (objurlRef.current) URL.revokeObjectURL(objurlRef.current); - objurlRef.current = null; - stopAudio(); - setPause(true); - } - - const letMeSetSpeed = (new_speed: number) => { - return () => { - setSpeed(new_speed); - if (objurlRef.current) URL.revokeObjectURL(objurlRef.current); - objurlRef.current = null; - stopAudio(); - setPause(true); - } - } - - const handleUseItem = (item: z.infer) => { - if (textareaRef.current) textareaRef.current.value = item.text; - textRef.current = item.text; - setLocale(item.locale); - setIPA(item.ipa || ''); - if (objurlRef.current) URL.revokeObjectURL(objurlRef.current); - objurlRef.current = null; - stopAudio(); - setPause(true); - } - - const save = async () => { - if (saving) return; - if (textRef.current.length === 0) return; - - setSaving(true); - - try { + // 第一次播放 + try { let theLocale = locale; if (!theLocale) { - console.log('downloading text info'); - const params = new URLSearchParams({ - text: textRef.current.slice(0, 30) - }); - const textinfo = await (await fetch(`/api/locale?${params}`)).json(); - setLocale(textinfo.locale); - theLocale = textinfo.locale as string; + console.log("downloading text info"); + const params = new URLSearchParams({ + text: textRef.current.slice(0, 30), + }); + const textinfo = await ( + await fetch(`/api/locale?${params}`) + ).json(); + setLocale(textinfo.locale); + theLocale = textinfo.locale as string; } - let theIPA = ipa; - if (ipa.length === 0 && ipaEnabled) { - const params = new URLSearchParams({ - text: textRef.current - }); - const tmp = await (await fetch(`/api/ipa?${params}`)).json(); - setIPA(tmp.ipa); - theIPA = tmp.ipa; - } + const voice = VOICES.find((v) => v.locale.startsWith(theLocale)); + if (!voice) throw "Voice not found."; - const save = getTextSpeakerData(); - const oldIndex = save.findIndex(v => v.text === textRef.current); - if (oldIndex !== -1) { - const oldItem = save[oldIndex]; - if (theIPA) { - if ((!oldItem.ipa || (oldItem.ipa !== theIPA))) { - oldItem.ipa = theIPA; - setTextSpeakerData(save); - } - } - } else if (theIPA.length === 0) { - save.push({ - text: textRef.current, - locale: theLocale - }); - } else { - save.push({ - text: textRef.current, - locale: theLocale, - ipa: theIPA - }); - } - setTextSpeakerData(save); - } catch (e) { + objurlRef.current = await getTTSAudioUrl( + textRef.current, + voice.short_name, + (() => { + if (speed === 1) return {}; + else if (speed < 1) + return { + rate: `-${100 - speed * 100}%`, + }; + else + return { + rate: `+${speed * 100 - 100}%`, + }; + })(), + ); + playAudio(objurlRef.current); + } catch (e) { console.error(e); + + setPause(true); setLocale(null); - } finally { - setSaving(false); + + setProcessing(false); + } } + } + } else { + // 如果在读就暂停 + setPause(true); + stopAudio(); } - return (<> - -
- - { - ipa.length !== 0 && (
- {ipa} -
) || (
) - } -
- {showSpeedAdjust && ( -
- - - - - -
)} - - { - setAutopause(!autopause); if (objurlRef) { stopAudio(); } setPause(true); - }} src={ - autopause ? IMAGES.autoplay : IMAGES.autopause - } alt="autoplayorpause" - > - setShowSpeedAdjust(!showSpeedAdjust)} - src={IMAGES.speed} - alt="speed" - className={`${showSpeedAdjust ? 'bg-gray-200' : ''}`}> - -
- - -
+ setProcessing(false); + }; + + const handleInputChange = (e: ChangeEvent) => { + textRef.current = e.target.value.trim(); + setLocale(null); + setIPA(""); + if (objurlRef.current) URL.revokeObjectURL(objurlRef.current); + objurlRef.current = null; + stopAudio(); + setPause(true); + }; + + const letMeSetSpeed = (new_speed: number) => { + return () => { + setSpeed(new_speed); + if (objurlRef.current) URL.revokeObjectURL(objurlRef.current); + objurlRef.current = null; + stopAudio(); + setPause(true); + }; + }; + + const handleUseItem = (item: z.infer) => { + if (textareaRef.current) textareaRef.current.value = item.text; + textRef.current = item.text; + setLocale(item.locale); + setIPA(item.ipa || ""); + if (objurlRef.current) URL.revokeObjectURL(objurlRef.current); + objurlRef.current = null; + stopAudio(); + setPause(true); + }; + + const save = async () => { + if (saving) return; + if (textRef.current.length === 0) return; + + setSaving(true); + + try { + let theLocale = locale; + if (!theLocale) { + console.log("downloading text info"); + const params = new URLSearchParams({ + text: textRef.current.slice(0, 30), + }); + const textinfo = await (await fetch(`/api/locale?${params}`)).json(); + setLocale(textinfo.locale); + theLocale = textinfo.locale as string; + } + + let theIPA = ipa; + if (ipa.length === 0 && ipaEnabled) { + const params = new URLSearchParams({ + text: textRef.current, + }); + const tmp = await (await fetch(`/api/ipa?${params}`)).json(); + setIPA(tmp.ipa); + theIPA = tmp.ipa; + } + + const save = getTextSpeakerData(); + const oldIndex = save.findIndex((v) => v.text === textRef.current); + if (oldIndex !== -1) { + const oldItem = save[oldIndex]; + if (theIPA) { + if (!oldItem.ipa || oldItem.ipa !== theIPA) { + oldItem.ipa = theIPA; + setTextSpeakerData(save); + } + } + } else if (theIPA.length === 0) { + save.push({ + text: textRef.current, + locale: theLocale, + }); + } else { + save.push({ + text: textRef.current, + locale: theLocale, + ipa: theIPA, + }); + } + setTextSpeakerData(save); + } catch (e) { + console.error(e); + setLocale(null); + } finally { + setSaving(false); + } + }; + + return ( + <> + +
+ + {(ipa.length !== 0 && ( +
+ {ipa} +
+ )) ||
} +
+ {showSpeedAdjust && ( +
+ + + + +
+ )} + + { + setAutopause(!autopause); + if (objurlRef) { + stopAudio(); + } + setPause(true); + }} + src={autopause ? IMAGES.autoplay : IMAGES.autopause} + alt="autoplayorpause" + > + setShowSpeedAdjust(!showSpeedAdjust)} + src={IMAGES.speed} + alt="speed" + className={`${showSpeedAdjust ? "bg-gray-200" : ""}`} + > + +
+ + +
- - ); -} \ No newline at end of file +
+ + + ); +} diff --git a/src/app/translator/page.tsx b/src/app/translator/page.tsx index 0c3e079..c55f50f 100644 --- a/src/app/translator/page.tsx +++ b/src/app/translator/page.tsx @@ -11,25 +11,25 @@ import { VOICES } from "@/config/locales"; export default function Translator() { const [ipaEnabled, setIPAEnabled] = useState(true); - const [targetLang, setTargetLang] = useState('Chinese'); + const [targetLang, setTargetLang] = useState("Chinese"); - const [sourceText, setSourceText] = useState(''); - const [targetText, setTargetText] = useState(''); - const [sourceIPA, setSourceIPA] = useState(''); - const [targetIPA, setTargetIPA] = useState(''); + const [sourceText, setSourceText] = useState(""); + const [targetText, setTargetText] = useState(""); + const [sourceIPA, setSourceIPA] = useState(""); + const [targetIPA, setTargetIPA] = useState(""); const [sourceLocale, setSourceLocale] = useState(null); const [targetLocale, setTargetLocale] = useState(null); const [translating, setTranslating] = useState(false); const { playAudio } = useAudioPlayer(); - const tl = ['Chinese', 'English', 'Italian']; + const tl = ["Chinese", "English", "Italian"]; const inputLanguage = () => { - const lang = prompt('Input a language.')?.trim(); + const lang = prompt("Input a language.")?.trim(); if (lang) { setTargetLang(lang); } - } + }; const translate = () => { if (translating) return; @@ -37,91 +37,96 @@ export default function Translator() { setTranslating(true); - setTargetText(''); + setTargetText(""); setSourceLocale(null); setTargetLocale(null); - setSourceIPA(''); - setTargetIPA(''); + setSourceIPA(""); + setTargetIPA(""); const params = new URLSearchParams({ text: sourceText, - target: targetLang - }) + target: targetLang, + }); fetch(`/api/translate?${params}`) - .then(res => res.json()) - .then(obj => { + .then((res) => res.json()) + .then((obj) => { setSourceLocale(obj.source_locale); setTargetLocale(obj.target_locale); setTargetText(obj.target_text); if (ipaEnabled) { const params = new URLSearchParams({ - text: sourceText + text: sourceText, }); fetch(`/api/ipa?${params}`) - .then(res => res.json()) - .then(data => { + .then((res) => res.json()) + .then((data) => { setSourceIPA(data.ipa); - }).catch(e => { - console.error(e); - setSourceIPA(''); }) + .catch((e) => { + console.error(e); + setSourceIPA(""); + }); const params2 = new URLSearchParams({ - text: obj.target_text + text: obj.target_text, }); fetch(`/api/ipa?${params2}`) - .then(res => res.json()) - .then(data => { + .then((res) => res.json()) + .then((data) => { setTargetIPA(data.ipa); - }).catch(e => { - console.error(e); - setTargetIPA(''); }) + .catch((e) => { + console.error(e); + setTargetIPA(""); + }); } - }).catch(r => { + }) + .catch((r) => { console.error(r); - setSourceLocale(''); - setTargetLocale(''); - setTargetText(''); - }).finally(() => setTranslating(false)); - } + setSourceLocale(""); + setTargetLocale(""); + setTargetText(""); + }) + .finally(() => setTranslating(false)); + }; const handleInputChange = (e: ChangeEvent) => { setSourceText(e.target.value.trim()); - setTargetText(''); + setTargetText(""); setSourceLocale(null); setTargetLocale(null); - setSourceIPA(''); - setTargetIPA(''); - } + setSourceIPA(""); + setTargetIPA(""); + }; const readSource = async () => { if (sourceText.length === 0) return; if (sourceIPA.length === 0 && ipaEnabled) { const params = new URLSearchParams({ - text: sourceText + text: sourceText, }); fetch(`/api/ipa?${params}`) - .then(res => res.json()) - .then(data => { + .then((res) => res.json()) + .then((data) => { setSourceIPA(data.ipa); - }).catch(e => { - console.error(e); - setSourceIPA(''); }) + .catch((e) => { + console.error(e); + setSourceIPA(""); + }); } if (!sourceLocale) { try { const params = new URLSearchParams({ - text: sourceText.slice(0, 30) + text: sourceText.slice(0, 30), }); const res = await fetch(`/api/locale?${params}`); const info = await res.json(); setSourceLocale(info.locale); - const voice = VOICES.find(v => v.locale.startsWith(info.locale)); + const voice = VOICES.find((v) => v.locale.startsWith(info.locale)); if (!voice) { return; } @@ -135,7 +140,7 @@ export default function Translator() { return; } } else { - const voice = VOICES.find(v => v.locale.startsWith(sourceLocale!)); + const voice = VOICES.find((v) => v.locale.startsWith(sourceLocale!)); if (!voice) { return; } @@ -144,32 +149,33 @@ export default function Translator() { await playAudio(url); URL.revokeObjectURL(url); } - } + }; const readTarget = async () => { if (targetText.length === 0) return; if (targetIPA.length === 0 && ipaEnabled) { const params = new URLSearchParams({ - text: targetText + text: targetText, }); fetch(`/api/ipa?${params}`) - .then(res => res.json()) - .then(data => { + .then((res) => res.json()) + .then((data) => { setTargetIPA(data.ipa); - }).catch(e => { - console.error(e); - setTargetIPA(''); }) + .catch((e) => { + console.error(e); + setTargetIPA(""); + }); } - const voice = VOICES.find(v => v.locale.startsWith(targetLocale!)); + const voice = VOICES.find((v) => v.locale.startsWith(targetLocale!)); if (!voice) return; const url = await getTTSAudioUrl(targetText, voice.short_name); await playAudio(url); URL.revokeObjectURL(url); - } + }; return ( <> @@ -177,52 +183,100 @@ export default function Translator() {
- +
{sourceIPA}
- { - if (sourceText.length !== 0) - await navigator.clipboard.writeText(sourceText); - }} src={IMAGES.copy_all} alt="copy"> - + { + if (sourceText.length !== 0) + await navigator.clipboard.writeText(sourceText); + }} + src={IMAGES.copy_all} + alt="copy" + > +
detect language - +
-
{ - targetText - }
+
{targetText}
{targetIPA}
- { - if (targetText.length !== 0) - await navigator.clipboard.writeText(targetText); - }} src={IMAGES.copy_all} alt="copy"> - + { + if (targetText.length !== 0) + await navigator.clipboard.writeText(targetText); + }} + src={IMAGES.copy_all} + alt="copy" + > +
translate into - - - - + + + +
-
diff --git a/src/app/word-board/TheBoard.tsx b/src/app/word-board/TheBoard.tsx index f396ca1..22d39da 100644 --- a/src/app/word-board/TheBoard.tsx +++ b/src/app/word-board/TheBoard.tsx @@ -1,46 +1,66 @@ -'use client'; +"use client"; -import { BOARD_WIDTH, TEXT_WIDTH, BOARD_HEIGHT, TEXT_SIZE } from "@/config/word-board-config"; +import { + BOARD_WIDTH, + TEXT_WIDTH, + BOARD_HEIGHT, + TEXT_SIZE, +} from "@/config/word-board-config"; import { Word } from "@/interfaces"; import { Dispatch, SetStateAction } from "react"; -export default function TheBoard( - { words, selectWord }: { - words: [ - { - word: string, - x: number, - y: number - } - ], - setWords: Dispatch>, - selectWord: (word: string) => void - } -) { - function DraggableWord({ word }: { word: Word }) { - return ({word.word})) - onClick={() => { selectWord(word.word); }}>{word.word}); - } +export default function TheBoard({ + words, + selectWord, +}: { + words: [ + { + word: string; + x: number; + y: number; + }, + ]; + setWords: Dispatch>; + selectWord: (word: string) => void; +}) { + function DraggableWord({ word }: { word: Word }) { return ( -
- {words.map( - (v: { - word: string, - x: number, - y: number - }, i: number) => { - return () - })} -
- ) -} \ No newline at end of file + {word.word})) + onClick={() => { + selectWord(word.word); + }} + > + {word.word} + + ); + } + return ( +
+ {words.map( + ( + v: { + word: string; + x: number; + y: number; + }, + i: number, + ) => { + return ; + }, + )} +
+ ); +} diff --git a/src/app/word-board/page.tsx b/src/app/word-board/page.tsx index de40bcb..5942f61 100644 --- a/src/app/word-board/page.tsx +++ b/src/app/word-board/page.tsx @@ -1,135 +1,141 @@ -'use client'; +"use client"; import TheBoard from "@/app/word-board/TheBoard"; import Button from "../../components/Button"; import { KeyboardEvent, useRef, useState } from "react"; import { Word } from "@/interfaces"; -import { BOARD_WIDTH, TEXT_WIDTH, BOARD_HEIGHT, TEXT_SIZE } from "@/config/word-board-config"; +import { + BOARD_WIDTH, + TEXT_WIDTH, + BOARD_HEIGHT, + TEXT_SIZE, +} from "@/config/word-board-config"; import { inspect } from "@/utils"; import { Navbar } from "@/components/Navbar"; export default function WordBoard() { const inputRef = useRef(null); const inputFileRef = useRef(null); - const initialWords = - [ - // 'apple', - // 'banana', - // 'cannon', - // 'desktop', - // 'kernel', - // 'system', - // 'programming', - // 'owe' - ] as Array; + const initialWords = [ + // 'apple', + // 'banana', + // 'cannon', + // 'desktop', + // 'kernel', + // 'system', + // 'programming', + // 'owe' + ] as Array; const [words, setWords] = useState( initialWords.map((v: string) => ({ - 'word': v, - 'x': Math.random(), - 'y': Math.random() - })) + word: v, + x: Math.random(), + y: Math.random(), + })), ); const generateNewWord = (word: string) => { const isOK = (w: Word) => { if (words.length === 0) return true; - const tf = (ww: Word) => ({ - word: ww.word, - x: Math.floor(ww.x * (BOARD_WIDTH - TEXT_WIDTH * ww.word.length)), - y: Math.floor(ww.y * (BOARD_HEIGHT - TEXT_SIZE)) - } as Word); + const tf = (ww: Word) => + ({ + word: ww.word, + x: Math.floor(ww.x * (BOARD_WIDTH - TEXT_WIDTH * ww.word.length)), + y: Math.floor(ww.y * (BOARD_HEIGHT - TEXT_SIZE)), + }) as Word; const tfd_words = words.map(tf); const tfd_w = tf(w); for (const www of tfd_words) { const p1 = { x: (www.x + www.x + TEXT_WIDTH * www.word.length) / 2, - y: (www.y + www.y + TEXT_SIZE) / 2 - } + y: (www.y + www.y + TEXT_SIZE) / 2, + }; const p2 = { x: (tfd_w.x + tfd_w.x + TEXT_WIDTH * tfd_w.word.length) / 2, - y: (tfd_w.y + tfd_w.y + TEXT_SIZE) / 2 - } + y: (tfd_w.y + tfd_w.y + TEXT_SIZE) / 2, + }; if ( - Math.abs(p1.x - p2.x) < (TEXT_WIDTH * (www.word.length + tfd_w.word.length)) / 2 && + Math.abs(p1.x - p2.x) < + (TEXT_WIDTH * (www.word.length + tfd_w.word.length)) / 2 && Math.abs(p1.y - p2.y) < TEXT_SIZE ) { return false; } } return true; - } + }; let new_word; let count = 0; do { new_word = { word: word, x: Math.random(), - y: Math.random() + y: Math.random(), }; if (++count > 1000) return null; } while (!isOK(new_word)); return new_word as Word; - } + }; const insertWord = () => { if (!inputRef.current) return; const word = inputRef.current.value.trim(); - if (word === '') return; + if (word === "") return; const new_word = generateNewWord(word); if (!new_word) return; setWords([...words, new_word]); - inputRef.current.value = ''; - } + inputRef.current.value = ""; + }; const deleteWord = () => { if (!inputRef.current) return; const word = inputRef.current.value.trim(); - if (word === '') return; + if (word === "") return; setWords(words.filter((v) => v.word !== word)); - inputRef.current.value = ''; + inputRef.current.value = ""; }; const importWords = () => { inputFileRef.current?.click(); - } + }; const exportWords = () => { const blob = new Blob([JSON.stringify(words)], { - type: 'application/json' + type: "application/json", }); const url = URL.createObjectURL(blob); - const a = document.createElement('a'); + const a = document.createElement("a"); a.href = url; a.download = `${Date.now()}.json`; - a.style.display = 'none'; + a.style.display = "none"; a.click(); URL.revokeObjectURL(url); - } + }; const handleFileChange = () => { const files = inputFileRef.current?.files; if (files && files.length > 0) { const reader = new FileReader(); reader.onload = () => { - if (reader.result && typeof reader.result === 'string') + if (reader.result && typeof reader.result === "string") setWords(JSON.parse(reader.result) as [Word]); - } + }; reader.readAsText(files[0]); } - } + }; const deleteAll = () => { setWords([] as Array); - } + }; const handleKeyDown = (e: KeyboardEvent) => { // e.preventDefault(); - if (e.key === 'Enter') { + if (e.key === "Enter") { insertWord(); } - } + }; const selectWord = (word: string) => { if (!inputRef.current) return; inputRef.current.value = word; - } + }; const searchWord = () => { if (!inputRef.current) return; const word = inputRef.current.value.trim(); - if (word === '') return; + if (word === "") return; inspect(word)(); - inputRef.current.value = ''; - } + inputRef.current.value = ""; + }; // const readWordAloud = () => { // playFromUrl('https://fanyi.baidu.com/gettts?lan=uk&text=disclose&spd=3') // return; @@ -143,10 +149,22 @@ export default function WordBoard() { <>
-
- +
+
- + @@ -155,10 +173,15 @@ export default function WordBoard() { {/* */}
- +
- ); } diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 0c573d9..9569011 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -2,19 +2,19 @@ export default function Button({ onClick, className, selected, - children + children, }: { - onClick?: () => void, - className?: string, - selected?: boolean, - children?: React.ReactNode + onClick?: () => void; + className?: string; + selected?: boolean; + children?: React.ReactNode; }) { return ( ); -} \ No newline at end of file +} diff --git a/src/components/IconClick.tsx b/src/components/IconClick.tsx index ebad2b6..077e77c 100644 --- a/src/components/IconClick.tsx +++ b/src/components/IconClick.tsx @@ -1,23 +1,27 @@ import Image from "next/image"; - interface IconClickProps { - src: string; - alt: string; - onClick?: () => void; - className?: string; - size?: number + src: string; + alt: string; + onClick?: () => void; + className?: string; + size?: number; } -export default function IconClick( - { src, alt, onClick = () => { }, className = '', size = 32 }: IconClickProps) { - return (<> -
- {alt} -
- ); +export default function IconClick({ + src, + alt, + onClick = () => {}, + className = "", + size = 32, +}: IconClickProps) { + return ( + <> +
+ {alt} +
+ + ); } diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 919f504..91bcc94 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,30 +1,33 @@ import Link from "next/link"; import Image from "next/image"; -function MyLink( - { href, label }: { href: string, label: string } -) { +function MyLink({ href, label }: { href: string; label: string }) { return ( - {label} - ) + + {label} + + ); } export function Navbar() { return (
- + logo - + className="rounded-4xl" + > 学语言
- +
); -} \ No newline at end of file +} diff --git a/src/config/images.ts b/src/config/images.ts index af1c688..3869350 100644 --- a/src/config/images.ts +++ b/src/config/images.ts @@ -1,20 +1,20 @@ const IMAGES = { - speed_1_5x: '/images/speed_1_5x_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - speed_1_2_x: '/images/speed_1_2x_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - speed_0_7x: '/images/speed_0_7x_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - pause: '/images/pause_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - speed_0_5x: '/images/speed_0_5x_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - copy_all: '/images/copy_all_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - autoplay: '/images/autoplay_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - autopause: '/images/autopause_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - speed_1x: '/images/1x_mobiledata_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - play_arrow: '/images/play_arrow_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - close: '/images/close_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - refresh: '/images/refresh_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - more_horiz: '/images/more_horiz_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - save: '/images/save_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - delete: '/images/delete_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', - speed: '/images/speed_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg', -} + speed_1_5x: "/images/speed_1_5x_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + speed_1_2_x: "/images/speed_1_2x_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + speed_0_7x: "/images/speed_0_7x_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + pause: "/images/pause_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + speed_0_5x: "/images/speed_0_5x_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + copy_all: "/images/copy_all_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + autoplay: "/images/autoplay_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + autopause: "/images/autopause_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + speed_1x: "/images/1x_mobiledata_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + play_arrow: "/images/play_arrow_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + close: "/images/close_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + refresh: "/images/refresh_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + more_horiz: "/images/more_horiz_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + save: "/images/save_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + delete: "/images/delete_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", + speed: "/images/speed_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg", +}; -export default IMAGES; \ No newline at end of file +export default IMAGES; diff --git a/src/config/locales.ts b/src/config/locales.ts index 901459a..f6ea50d 100644 --- a/src/config/locales.ts +++ b/src/config/locales.ts @@ -1,1212 +1,1212 @@ const VOICES = [ { locale: "af-ZA", - short_name: "af-ZA-AdriNeural" + short_name: "af-ZA-AdriNeural", }, { locale: "af-ZA", - short_name: "af-ZA-WillemNeural" + short_name: "af-ZA-WillemNeural", }, { locale: "sq-AL", - short_name: "sq-AL-AnilaNeural" + short_name: "sq-AL-AnilaNeural", }, { locale: "sq-AL", - short_name: "sq-AL-IlirNeural" + short_name: "sq-AL-IlirNeural", }, { locale: "am-ET", - short_name: "am-ET-AmehaNeural" + short_name: "am-ET-AmehaNeural", }, { locale: "am-ET", - short_name: "am-ET-MekdesNeural" + short_name: "am-ET-MekdesNeural", }, { locale: "ar-DZ", - short_name: "ar-DZ-AminaNeural" + short_name: "ar-DZ-AminaNeural", }, { locale: "ar-DZ", - short_name: "ar-DZ-IsmaelNeural" + short_name: "ar-DZ-IsmaelNeural", }, { locale: "ar-BH", - short_name: "ar-BH-AliNeural" + short_name: "ar-BH-AliNeural", }, { locale: "ar-BH", - short_name: "ar-BH-LailaNeural" + short_name: "ar-BH-LailaNeural", }, { locale: "ar-EG", - short_name: "ar-EG-SalmaNeural" + short_name: "ar-EG-SalmaNeural", }, { locale: "ar-EG", - short_name: "ar-EG-ShakirNeural" + short_name: "ar-EG-ShakirNeural", }, { locale: "ar-IQ", - short_name: "ar-IQ-BasselNeural" + short_name: "ar-IQ-BasselNeural", }, { locale: "ar-IQ", - short_name: "ar-IQ-RanaNeural" + short_name: "ar-IQ-RanaNeural", }, { locale: "ar-JO", - short_name: "ar-JO-SanaNeural" + short_name: "ar-JO-SanaNeural", }, { locale: "ar-JO", - short_name: "ar-JO-TaimNeural" + short_name: "ar-JO-TaimNeural", }, { locale: "ar-KW", - short_name: "ar-KW-FahedNeural" + short_name: "ar-KW-FahedNeural", }, { locale: "ar-KW", - short_name: "ar-KW-NouraNeural" + short_name: "ar-KW-NouraNeural", }, { locale: "ar-LB", - short_name: "ar-LB-LaylaNeural" + short_name: "ar-LB-LaylaNeural", }, { locale: "ar-LB", - short_name: "ar-LB-RamiNeural" + short_name: "ar-LB-RamiNeural", }, { locale: "ar-LY", - short_name: "ar-LY-ImanNeural" + short_name: "ar-LY-ImanNeural", }, { locale: "ar-LY", - short_name: "ar-LY-OmarNeural" + short_name: "ar-LY-OmarNeural", }, { locale: "ar-MA", - short_name: "ar-MA-JamalNeural" + short_name: "ar-MA-JamalNeural", }, { locale: "ar-MA", - short_name: "ar-MA-MounaNeural" + short_name: "ar-MA-MounaNeural", }, { locale: "ar-OM", - short_name: "ar-OM-AbdullahNeural" + short_name: "ar-OM-AbdullahNeural", }, { locale: "ar-OM", - short_name: "ar-OM-AyshaNeural" + short_name: "ar-OM-AyshaNeural", }, { locale: "ar-QA", - short_name: "ar-QA-AmalNeural" + short_name: "ar-QA-AmalNeural", }, { locale: "ar-QA", - short_name: "ar-QA-MoazNeural" + short_name: "ar-QA-MoazNeural", }, { locale: "ar-SA", - short_name: "ar-SA-HamedNeural" + short_name: "ar-SA-HamedNeural", }, { locale: "ar-SA", - short_name: "ar-SA-ZariyahNeural" + short_name: "ar-SA-ZariyahNeural", }, { locale: "ar-SY", - short_name: "ar-SY-AmanyNeural" + short_name: "ar-SY-AmanyNeural", }, { locale: "ar-SY", - short_name: "ar-SY-LaithNeural" + short_name: "ar-SY-LaithNeural", }, { locale: "ar-TN", - short_name: "ar-TN-HediNeural" + short_name: "ar-TN-HediNeural", }, { locale: "ar-TN", - short_name: "ar-TN-ReemNeural" + short_name: "ar-TN-ReemNeural", }, { locale: "ar-AE", - short_name: "ar-AE-FatimaNeural" + short_name: "ar-AE-FatimaNeural", }, { locale: "ar-AE", - short_name: "ar-AE-HamdanNeural" + short_name: "ar-AE-HamdanNeural", }, { locale: "ar-YE", - short_name: "ar-YE-MaryamNeural" + short_name: "ar-YE-MaryamNeural", }, { locale: "ar-YE", - short_name: "ar-YE-SalehNeural" + short_name: "ar-YE-SalehNeural", }, { locale: "az-AZ", - short_name: "az-AZ-BabekNeural" + short_name: "az-AZ-BabekNeural", }, { locale: "az-AZ", - short_name: "az-AZ-BanuNeural" + short_name: "az-AZ-BanuNeural", }, { locale: "bn-BD", - short_name: "bn-BD-NabanitaNeural" + short_name: "bn-BD-NabanitaNeural", }, { locale: "bn-BD", - short_name: "bn-BD-PradeepNeural" + short_name: "bn-BD-PradeepNeural", }, { locale: "bn-IN", - short_name: "bn-IN-BashkarNeural" + short_name: "bn-IN-BashkarNeural", }, { locale: "bn-IN", - short_name: "bn-IN-TanishaaNeural" + short_name: "bn-IN-TanishaaNeural", }, { locale: "bs-BA", - short_name: "bs-BA-GoranNeural" + short_name: "bs-BA-GoranNeural", }, { locale: "bs-BA", - short_name: "bs-BA-VesnaNeural" + short_name: "bs-BA-VesnaNeural", }, { locale: "bg-BG", - short_name: "bg-BG-BorislavNeural" + short_name: "bg-BG-BorislavNeural", }, { locale: "bg-BG", - short_name: "bg-BG-KalinaNeural" + short_name: "bg-BG-KalinaNeural", }, { locale: "my-MM", - short_name: "my-MM-NilarNeural" + short_name: "my-MM-NilarNeural", }, { locale: "my-MM", - short_name: "my-MM-ThihaNeural" + short_name: "my-MM-ThihaNeural", }, { locale: "ca-ES", - short_name: "ca-ES-EnricNeural" + short_name: "ca-ES-EnricNeural", }, { locale: "ca-ES", - short_name: "ca-ES-JoanaNeural" + short_name: "ca-ES-JoanaNeural", }, { locale: "zh-HK", - short_name: "zh-HK-HiuGaaiNeural" + short_name: "zh-HK-HiuGaaiNeural", }, { locale: "zh-HK", - short_name: "zh-HK-HiuMaanNeural" + short_name: "zh-HK-HiuMaanNeural", }, { locale: "zh-HK", - short_name: "zh-HK-WanLungNeural" + short_name: "zh-HK-WanLungNeural", }, { locale: "zh-CN", - short_name: "zh-CN-XiaoxiaoNeural" + short_name: "zh-CN-XiaoxiaoNeural", }, { locale: "zh-CN", - short_name: "zh-CN-XiaoyiNeural" + short_name: "zh-CN-XiaoyiNeural", }, { locale: "zh-CN", - short_name: "zh-CN-YunjianNeural" + short_name: "zh-CN-YunjianNeural", }, { locale: "zh-CN", - short_name: "zh-CN-YunxiNeural" + short_name: "zh-CN-YunxiNeural", }, { locale: "zh-CN", - short_name: "zh-CN-YunxiaNeural" + short_name: "zh-CN-YunxiaNeural", }, { locale: "zh-CN", - short_name: "zh-CN-YunyangNeural" + short_name: "zh-CN-YunyangNeural", }, { locale: "zh-CN-liaoning", - short_name: "zh-CN-liaoning-XiaobeiNeural" + short_name: "zh-CN-liaoning-XiaobeiNeural", }, { locale: "zh-TW", - short_name: "zh-TW-HsiaoChenNeural" + short_name: "zh-TW-HsiaoChenNeural", }, { locale: "zh-TW", - short_name: "zh-TW-YunJheNeural" + short_name: "zh-TW-YunJheNeural", }, { locale: "zh-TW", - short_name: "zh-TW-HsiaoYuNeural" + short_name: "zh-TW-HsiaoYuNeural", }, { locale: "zh-CN-shaanxi", - short_name: "zh-CN-shaanxi-XiaoniNeural" + short_name: "zh-CN-shaanxi-XiaoniNeural", }, { locale: "hr-HR", - short_name: "hr-HR-GabrijelaNeural" + short_name: "hr-HR-GabrijelaNeural", }, { locale: "hr-HR", - short_name: "hr-HR-SreckoNeural" + short_name: "hr-HR-SreckoNeural", }, { locale: "cs-CZ", - short_name: "cs-CZ-AntoninNeural" + short_name: "cs-CZ-AntoninNeural", }, { locale: "cs-CZ", - short_name: "cs-CZ-VlastaNeural" + short_name: "cs-CZ-VlastaNeural", }, { locale: "da-DK", - short_name: "da-DK-ChristelNeural" + short_name: "da-DK-ChristelNeural", }, { locale: "da-DK", - short_name: "da-DK-JeppeNeural" + short_name: "da-DK-JeppeNeural", }, { locale: "nl-BE", - short_name: "nl-BE-ArnaudNeural" + short_name: "nl-BE-ArnaudNeural", }, { locale: "nl-BE", - short_name: "nl-BE-DenaNeural" + short_name: "nl-BE-DenaNeural", }, { locale: "nl-NL", - short_name: "nl-NL-ColetteNeural" + short_name: "nl-NL-ColetteNeural", }, { locale: "nl-NL", - short_name: "nl-NL-FennaNeural" + short_name: "nl-NL-FennaNeural", }, { locale: "nl-NL", - short_name: "nl-NL-MaartenNeural" + short_name: "nl-NL-MaartenNeural", }, { locale: "en-AU", - short_name: "en-AU-NatashaNeural" + short_name: "en-AU-NatashaNeural", }, { locale: "en-AU", - short_name: "en-AU-WilliamNeural" + short_name: "en-AU-WilliamNeural", }, { locale: "en-CA", - short_name: "en-CA-ClaraNeural" + short_name: "en-CA-ClaraNeural", }, { locale: "en-CA", - short_name: "en-CA-LiamNeural" + short_name: "en-CA-LiamNeural", }, { locale: "en-HK", - short_name: "en-HK-SamNeural" + short_name: "en-HK-SamNeural", }, { locale: "en-HK", - short_name: "en-HK-YanNeural" + short_name: "en-HK-YanNeural", }, { locale: "en-IN", - short_name: "en-IN-NeerjaNeural" + short_name: "en-IN-NeerjaNeural", }, { locale: "en-IN", - short_name: "en-IN-PrabhatNeural" + short_name: "en-IN-PrabhatNeural", }, { locale: "en-IE", - short_name: "en-IE-ConnorNeural" + short_name: "en-IE-ConnorNeural", }, { locale: "en-IE", - short_name: "en-IE-EmilyNeural" + short_name: "en-IE-EmilyNeural", }, { locale: "en-KE", - short_name: "en-KE-AsiliaNeural" + short_name: "en-KE-AsiliaNeural", }, { locale: "en-KE", - short_name: "en-KE-ChilembaNeural" + short_name: "en-KE-ChilembaNeural", }, { locale: "en-NZ", - short_name: "en-NZ-MitchellNeural" + short_name: "en-NZ-MitchellNeural", }, { locale: "en-NZ", - short_name: "en-NZ-MollyNeural" + short_name: "en-NZ-MollyNeural", }, { locale: "en-NG", - short_name: "en-NG-AbeoNeural" + short_name: "en-NG-AbeoNeural", }, { locale: "en-NG", - short_name: "en-NG-EzinneNeural" + short_name: "en-NG-EzinneNeural", }, { locale: "en-PH", - short_name: "en-PH-JamesNeural" + short_name: "en-PH-JamesNeural", }, { locale: "en-PH", - short_name: "en-PH-RosaNeural" + short_name: "en-PH-RosaNeural", }, { locale: "en-SG", - short_name: "en-SG-LunaNeural" + short_name: "en-SG-LunaNeural", }, { locale: "en-SG", - short_name: "en-SG-WayneNeural" + short_name: "en-SG-WayneNeural", }, { locale: "en-ZA", - short_name: "en-ZA-LeahNeural" + short_name: "en-ZA-LeahNeural", }, { locale: "en-ZA", - short_name: "en-ZA-LukeNeural" + short_name: "en-ZA-LukeNeural", }, { locale: "en-TZ", - short_name: "en-TZ-ElimuNeural" + short_name: "en-TZ-ElimuNeural", }, { locale: "en-TZ", - short_name: "en-TZ-ImaniNeural" + short_name: "en-TZ-ImaniNeural", }, { locale: "en-GB", - short_name: "en-GB-LibbyNeural" + short_name: "en-GB-LibbyNeural", }, { locale: "en-GB", - short_name: "en-GB-MaisieNeural" + short_name: "en-GB-MaisieNeural", }, { locale: "en-GB", - short_name: "en-GB-RyanNeural" + short_name: "en-GB-RyanNeural", }, { locale: "en-GB", - short_name: "en-GB-SoniaNeural" + short_name: "en-GB-SoniaNeural", }, { locale: "en-GB", - short_name: "en-GB-ThomasNeural" + short_name: "en-GB-ThomasNeural", }, { locale: "en-US", - short_name: "en-US-AriaNeural" + short_name: "en-US-AriaNeural", }, { locale: "en-US", - short_name: "en-US-AnaNeural" + short_name: "en-US-AnaNeural", }, { locale: "en-US", - short_name: "en-US-ChristopherNeural" + short_name: "en-US-ChristopherNeural", }, { locale: "en-US", - short_name: "en-US-EricNeural" + short_name: "en-US-EricNeural", }, { locale: "en-US", - short_name: "en-US-GuyNeural" + short_name: "en-US-GuyNeural", }, { locale: "en-US", - short_name: "en-US-JennyNeural" + short_name: "en-US-JennyNeural", }, { locale: "en-US", - short_name: "en-US-MichelleNeural" + short_name: "en-US-MichelleNeural", }, { locale: "en-US", - short_name: "en-US-RogerNeural" + short_name: "en-US-RogerNeural", }, { locale: "en-US", - short_name: "en-US-SteffanNeural" + short_name: "en-US-SteffanNeural", }, { locale: "et-EE", - short_name: "et-EE-AnuNeural" + short_name: "et-EE-AnuNeural", }, { locale: "et-EE", - short_name: "et-EE-KertNeural" + short_name: "et-EE-KertNeural", }, { locale: "fil-PH", - short_name: "fil-PH-AngeloNeural" + short_name: "fil-PH-AngeloNeural", }, { locale: "fil-PH", - short_name: "fil-PH-BlessicaNeural" + short_name: "fil-PH-BlessicaNeural", }, { locale: "fi-FI", - short_name: "fi-FI-HarriNeural" + short_name: "fi-FI-HarriNeural", }, { locale: "fi-FI", - short_name: "fi-FI-NooraNeural" + short_name: "fi-FI-NooraNeural", }, { locale: "fr-BE", - short_name: "fr-BE-CharlineNeural" + short_name: "fr-BE-CharlineNeural", }, { locale: "fr-BE", - short_name: "fr-BE-GerardNeural" + short_name: "fr-BE-GerardNeural", }, { locale: "fr-CA", - short_name: "fr-CA-AntoineNeural" + short_name: "fr-CA-AntoineNeural", }, { locale: "fr-CA", - short_name: "fr-CA-JeanNeural" + short_name: "fr-CA-JeanNeural", }, { locale: "fr-CA", - short_name: "fr-CA-SylvieNeural" + short_name: "fr-CA-SylvieNeural", }, { locale: "fr-FR", - short_name: "fr-FR-DeniseNeural" + short_name: "fr-FR-DeniseNeural", }, { locale: "fr-FR", - short_name: "fr-FR-EloiseNeural" + short_name: "fr-FR-EloiseNeural", }, { locale: "fr-FR", - short_name: "fr-FR-HenriNeural" + short_name: "fr-FR-HenriNeural", }, { locale: "fr-CH", - short_name: "fr-CH-ArianeNeural" + short_name: "fr-CH-ArianeNeural", }, { locale: "fr-CH", - short_name: "fr-CH-FabriceNeural" + short_name: "fr-CH-FabriceNeural", }, { locale: "gl-ES", - short_name: "gl-ES-RoiNeural" + short_name: "gl-ES-RoiNeural", }, { locale: "gl-ES", - short_name: "gl-ES-SabelaNeural" + short_name: "gl-ES-SabelaNeural", }, { locale: "ka-GE", - short_name: "ka-GE-EkaNeural" + short_name: "ka-GE-EkaNeural", }, { locale: "ka-GE", - short_name: "ka-GE-GiorgiNeural" + short_name: "ka-GE-GiorgiNeural", }, { locale: "de-AT", - short_name: "de-AT-IngridNeural" + short_name: "de-AT-IngridNeural", }, { locale: "de-AT", - short_name: "de-AT-JonasNeural" + short_name: "de-AT-JonasNeural", }, { locale: "de-DE", - short_name: "de-DE-AmalaNeural" + short_name: "de-DE-AmalaNeural", }, { locale: "de-DE", - short_name: "de-DE-ConradNeural" + short_name: "de-DE-ConradNeural", }, { locale: "de-DE", - short_name: "de-DE-KatjaNeural" + short_name: "de-DE-KatjaNeural", }, { locale: "de-DE", - short_name: "de-DE-KillianNeural" + short_name: "de-DE-KillianNeural", }, { locale: "de-CH", - short_name: "de-CH-JanNeural" + short_name: "de-CH-JanNeural", }, { locale: "de-CH", - short_name: "de-CH-LeniNeural" + short_name: "de-CH-LeniNeural", }, { locale: "el-GR", - short_name: "el-GR-AthinaNeural" + short_name: "el-GR-AthinaNeural", }, { locale: "el-GR", - short_name: "el-GR-NestorasNeural" + short_name: "el-GR-NestorasNeural", }, { locale: "gu-IN", - short_name: "gu-IN-DhwaniNeural" + short_name: "gu-IN-DhwaniNeural", }, { locale: "gu-IN", - short_name: "gu-IN-NiranjanNeural" + short_name: "gu-IN-NiranjanNeural", }, { locale: "he-IL", - short_name: "he-IL-AvriNeural" + short_name: "he-IL-AvriNeural", }, { locale: "he-IL", - short_name: "he-IL-HilaNeural" + short_name: "he-IL-HilaNeural", }, { locale: "hi-IN", - short_name: "hi-IN-MadhurNeural" + short_name: "hi-IN-MadhurNeural", }, { locale: "hi-IN", - short_name: "hi-IN-SwaraNeural" + short_name: "hi-IN-SwaraNeural", }, { locale: "hu-HU", - short_name: "hu-HU-NoemiNeural" + short_name: "hu-HU-NoemiNeural", }, { locale: "hu-HU", - short_name: "hu-HU-TamasNeural" + short_name: "hu-HU-TamasNeural", }, { locale: "is-IS", - short_name: "is-IS-GudrunNeural" + short_name: "is-IS-GudrunNeural", }, { locale: "is-IS", - short_name: "is-IS-GunnarNeural" + short_name: "is-IS-GunnarNeural", }, { locale: "id-ID", - short_name: "id-ID-ArdiNeural" + short_name: "id-ID-ArdiNeural", }, { locale: "id-ID", - short_name: "id-ID-GadisNeural" + short_name: "id-ID-GadisNeural", }, { locale: "ga-IE", - short_name: "ga-IE-ColmNeural" + short_name: "ga-IE-ColmNeural", }, { locale: "ga-IE", - short_name: "ga-IE-OrlaNeural" + short_name: "ga-IE-OrlaNeural", }, { locale: "it-IT", - short_name: "it-IT-DiegoNeural" + short_name: "it-IT-DiegoNeural", }, { locale: "it-IT", - short_name: "it-IT-ElsaNeural" + short_name: "it-IT-ElsaNeural", }, { locale: "it-IT", - short_name: "it-IT-IsabellaNeural" + short_name: "it-IT-IsabellaNeural", }, { locale: "ja-JP", - short_name: "ja-JP-KeitaNeural" + short_name: "ja-JP-KeitaNeural", }, { locale: "ja-JP", - short_name: "ja-JP-NanamiNeural" + short_name: "ja-JP-NanamiNeural", }, { locale: "jv-ID", - short_name: "jv-ID-DimasNeural" + short_name: "jv-ID-DimasNeural", }, { locale: "jv-ID", - short_name: "jv-ID-SitiNeural" + short_name: "jv-ID-SitiNeural", }, { locale: "kn-IN", - short_name: "kn-IN-GaganNeural" + short_name: "kn-IN-GaganNeural", }, { locale: "kn-IN", - short_name: "kn-IN-SapnaNeural" + short_name: "kn-IN-SapnaNeural", }, { locale: "kk-KZ", - short_name: "kk-KZ-AigulNeural" + short_name: "kk-KZ-AigulNeural", }, { locale: "kk-KZ", - short_name: "kk-KZ-DauletNeural" + short_name: "kk-KZ-DauletNeural", }, { locale: "km-KH", - short_name: "km-KH-PisethNeural" + short_name: "km-KH-PisethNeural", }, { locale: "km-KH", - short_name: "km-KH-SreymomNeural" + short_name: "km-KH-SreymomNeural", }, { locale: "ko-KR", - short_name: "ko-KR-InJoonNeural" + short_name: "ko-KR-InJoonNeural", }, { locale: "ko-KR", - short_name: "ko-KR-SunHiNeural" + short_name: "ko-KR-SunHiNeural", }, { locale: "lo-LA", - short_name: "lo-LA-ChanthavongNeural" + short_name: "lo-LA-ChanthavongNeural", }, { locale: "lo-LA", - short_name: "lo-LA-KeomanyNeural" + short_name: "lo-LA-KeomanyNeural", }, { locale: "lv-LV", - short_name: "lv-LV-EveritaNeural" + short_name: "lv-LV-EveritaNeural", }, { locale: "lv-LV", - short_name: "lv-LV-NilsNeural" + short_name: "lv-LV-NilsNeural", }, { locale: "lt-LT", - short_name: "lt-LT-LeonasNeural" + short_name: "lt-LT-LeonasNeural", }, { locale: "lt-LT", - short_name: "lt-LT-OnaNeural" + short_name: "lt-LT-OnaNeural", }, { locale: "mk-MK", - short_name: "mk-MK-AleksandarNeural" + short_name: "mk-MK-AleksandarNeural", }, { locale: "mk-MK", - short_name: "mk-MK-MarijaNeural" + short_name: "mk-MK-MarijaNeural", }, { locale: "ms-MY", - short_name: "ms-MY-OsmanNeural" + short_name: "ms-MY-OsmanNeural", }, { locale: "ms-MY", - short_name: "ms-MY-YasminNeural" + short_name: "ms-MY-YasminNeural", }, { locale: "ml-IN", - short_name: "ml-IN-MidhunNeural" + short_name: "ml-IN-MidhunNeural", }, { locale: "ml-IN", - short_name: "ml-IN-SobhanaNeural" + short_name: "ml-IN-SobhanaNeural", }, { locale: "mt-MT", - short_name: "mt-MT-GraceNeural" + short_name: "mt-MT-GraceNeural", }, { locale: "mt-MT", - short_name: "mt-MT-JosephNeural" + short_name: "mt-MT-JosephNeural", }, { locale: "mr-IN", - short_name: "mr-IN-AarohiNeural" + short_name: "mr-IN-AarohiNeural", }, { locale: "mr-IN", - short_name: "mr-IN-ManoharNeural" + short_name: "mr-IN-ManoharNeural", }, { locale: "mn-MN", - short_name: "mn-MN-BataaNeural" + short_name: "mn-MN-BataaNeural", }, { locale: "mn-MN", - short_name: "mn-MN-YesuiNeural" + short_name: "mn-MN-YesuiNeural", }, { locale: "ne-NP", - short_name: "ne-NP-HemkalaNeural" + short_name: "ne-NP-HemkalaNeural", }, { locale: "ne-NP", - short_name: "ne-NP-SagarNeural" + short_name: "ne-NP-SagarNeural", }, { locale: "nb-NO", - short_name: "nb-NO-FinnNeural" + short_name: "nb-NO-FinnNeural", }, { locale: "nb-NO", - short_name: "nb-NO-PernilleNeural" + short_name: "nb-NO-PernilleNeural", }, { locale: "ps-AF", - short_name: "ps-AF-GulNawazNeural" + short_name: "ps-AF-GulNawazNeural", }, { locale: "ps-AF", - short_name: "ps-AF-LatifaNeural" + short_name: "ps-AF-LatifaNeural", }, { locale: "fa-IR", - short_name: "fa-IR-DilaraNeural" + short_name: "fa-IR-DilaraNeural", }, { locale: "fa-IR", - short_name: "fa-IR-FaridNeural" + short_name: "fa-IR-FaridNeural", }, { locale: "pl-PL", - short_name: "pl-PL-MarekNeural" + short_name: "pl-PL-MarekNeural", }, { locale: "pl-PL", - short_name: "pl-PL-ZofiaNeural" + short_name: "pl-PL-ZofiaNeural", }, { locale: "pt-BR", - short_name: "pt-BR-AntonioNeural" + short_name: "pt-BR-AntonioNeural", }, { locale: "pt-BR", - short_name: "pt-BR-FranciscaNeural" + short_name: "pt-BR-FranciscaNeural", }, { locale: "pt-PT", - short_name: "pt-PT-DuarteNeural" + short_name: "pt-PT-DuarteNeural", }, { locale: "pt-PT", - short_name: "pt-PT-RaquelNeural" + short_name: "pt-PT-RaquelNeural", }, { locale: "ro-RO", - short_name: "ro-RO-AlinaNeural" + short_name: "ro-RO-AlinaNeural", }, { locale: "ro-RO", - short_name: "ro-RO-EmilNeural" + short_name: "ro-RO-EmilNeural", }, { locale: "ru-RU", - short_name: "ru-RU-DmitryNeural" + short_name: "ru-RU-DmitryNeural", }, { locale: "ru-RU", - short_name: "ru-RU-SvetlanaNeural" + short_name: "ru-RU-SvetlanaNeural", }, { locale: "sr-RS", - short_name: "sr-RS-NicholasNeural" + short_name: "sr-RS-NicholasNeural", }, { locale: "sr-RS", - short_name: "sr-RS-SophieNeural" + short_name: "sr-RS-SophieNeural", }, { locale: "si-LK", - short_name: "si-LK-SameeraNeural" + short_name: "si-LK-SameeraNeural", }, { locale: "si-LK", - short_name: "si-LK-ThiliniNeural" + short_name: "si-LK-ThiliniNeural", }, { locale: "sk-SK", - short_name: "sk-SK-LukasNeural" + short_name: "sk-SK-LukasNeural", }, { locale: "sk-SK", - short_name: "sk-SK-ViktoriaNeural" + short_name: "sk-SK-ViktoriaNeural", }, { locale: "sl-SI", - short_name: "sl-SI-PetraNeural" + short_name: "sl-SI-PetraNeural", }, { locale: "sl-SI", - short_name: "sl-SI-RokNeural" + short_name: "sl-SI-RokNeural", }, { locale: "so-SO", - short_name: "so-SO-MuuseNeural" + short_name: "so-SO-MuuseNeural", }, { locale: "so-SO", - short_name: "so-SO-UbaxNeural" + short_name: "so-SO-UbaxNeural", }, { locale: "es-AR", - short_name: "es-AR-ElenaNeural" + short_name: "es-AR-ElenaNeural", }, { locale: "es-AR", - short_name: "es-AR-TomasNeural" + short_name: "es-AR-TomasNeural", }, { locale: "es-BO", - short_name: "es-BO-MarceloNeural" + short_name: "es-BO-MarceloNeural", }, { locale: "es-BO", - short_name: "es-BO-SofiaNeural" + short_name: "es-BO-SofiaNeural", }, { locale: "es-CL", - short_name: "es-CL-CatalinaNeural" + short_name: "es-CL-CatalinaNeural", }, { locale: "es-CL", - short_name: "es-CL-LorenzoNeural" + short_name: "es-CL-LorenzoNeural", }, { locale: "es-CO", - short_name: "es-CO-GonzaloNeural" + short_name: "es-CO-GonzaloNeural", }, { locale: "es-CO", - short_name: "es-CO-SalomeNeural" + short_name: "es-CO-SalomeNeural", }, { locale: "es-CR", - short_name: "es-CR-JuanNeural" + short_name: "es-CR-JuanNeural", }, { locale: "es-CR", - short_name: "es-CR-MariaNeural" + short_name: "es-CR-MariaNeural", }, { locale: "es-CU", - short_name: "es-CU-BelkysNeural" + short_name: "es-CU-BelkysNeural", }, { locale: "es-CU", - short_name: "es-CU-ManuelNeural" + short_name: "es-CU-ManuelNeural", }, { locale: "es-DO", - short_name: "es-DO-EmilioNeural" + short_name: "es-DO-EmilioNeural", }, { locale: "es-DO", - short_name: "es-DO-RamonaNeural" + short_name: "es-DO-RamonaNeural", }, { locale: "es-EC", - short_name: "es-EC-AndreaNeural" + short_name: "es-EC-AndreaNeural", }, { locale: "es-EC", - short_name: "es-EC-LuisNeural" + short_name: "es-EC-LuisNeural", }, { locale: "es-SV", - short_name: "es-SV-LorenaNeural" + short_name: "es-SV-LorenaNeural", }, { locale: "es-SV", - short_name: "es-SV-RodrigoNeural" + short_name: "es-SV-RodrigoNeural", }, { locale: "es-GQ", - short_name: "es-GQ-JavierNeural" + short_name: "es-GQ-JavierNeural", }, { locale: "es-GQ", - short_name: "es-GQ-TeresaNeural" + short_name: "es-GQ-TeresaNeural", }, { locale: "es-GT", - short_name: "es-GT-AndresNeural" + short_name: "es-GT-AndresNeural", }, { locale: "es-GT", - short_name: "es-GT-MartaNeural" + short_name: "es-GT-MartaNeural", }, { locale: "es-HN", - short_name: "es-HN-CarlosNeural" + short_name: "es-HN-CarlosNeural", }, { locale: "es-HN", - short_name: "es-HN-KarlaNeural" + short_name: "es-HN-KarlaNeural", }, { locale: "es-MX", - short_name: "es-MX-DaliaNeural" + short_name: "es-MX-DaliaNeural", }, { locale: "es-MX", - short_name: "es-MX-JorgeNeural" + short_name: "es-MX-JorgeNeural", }, { locale: "es-MX", - short_name: "es-MX-LorenzoEsCLNeural" + short_name: "es-MX-LorenzoEsCLNeural", }, { locale: "es-NI", - short_name: "es-NI-FedericoNeural" + short_name: "es-NI-FedericoNeural", }, { locale: "es-NI", - short_name: "es-NI-YolandaNeural" + short_name: "es-NI-YolandaNeural", }, { locale: "es-PA", - short_name: "es-PA-MargaritaNeural" + short_name: "es-PA-MargaritaNeural", }, { locale: "es-PA", - short_name: "es-PA-RobertoNeural" + short_name: "es-PA-RobertoNeural", }, { locale: "es-PY", - short_name: "es-PY-MarioNeural" + short_name: "es-PY-MarioNeural", }, { locale: "es-PY", - short_name: "es-PY-TaniaNeural" + short_name: "es-PY-TaniaNeural", }, { locale: "es-PE", - short_name: "es-PE-AlexNeural" + short_name: "es-PE-AlexNeural", }, { locale: "es-PE", - short_name: "es-PE-CamilaNeural" + short_name: "es-PE-CamilaNeural", }, { locale: "es-PR", - short_name: "es-PR-KarinaNeural" + short_name: "es-PR-KarinaNeural", }, { locale: "es-PR", - short_name: "es-PR-VictorNeural" + short_name: "es-PR-VictorNeural", }, { locale: "es-ES", - short_name: "es-ES-AlvaroNeural" + short_name: "es-ES-AlvaroNeural", }, { locale: "es-ES", - short_name: "es-ES-ElviraNeural" + short_name: "es-ES-ElviraNeural", }, { locale: "es-ES", - short_name: "es-ES-ManuelEsCUNeural" + short_name: "es-ES-ManuelEsCUNeural", }, { locale: "es-US", - short_name: "es-US-AlonsoNeural" + short_name: "es-US-AlonsoNeural", }, { locale: "es-US", - short_name: "es-US-PalomaNeural" + short_name: "es-US-PalomaNeural", }, { locale: "es-UY", - short_name: "es-UY-MateoNeural" + short_name: "es-UY-MateoNeural", }, { locale: "es-UY", - short_name: "es-UY-ValentinaNeural" + short_name: "es-UY-ValentinaNeural", }, { locale: "es-VE", - short_name: "es-VE-PaolaNeural" + short_name: "es-VE-PaolaNeural", }, { locale: "es-VE", - short_name: "es-VE-SebastianNeural" + short_name: "es-VE-SebastianNeural", }, { locale: "su-ID", - short_name: "su-ID-JajangNeural" + short_name: "su-ID-JajangNeural", }, { locale: "su-ID", - short_name: "su-ID-TutiNeural" + short_name: "su-ID-TutiNeural", }, { locale: "sw-KE", - short_name: "sw-KE-RafikiNeural" + short_name: "sw-KE-RafikiNeural", }, { locale: "sw-KE", - short_name: "sw-KE-ZuriNeural" + short_name: "sw-KE-ZuriNeural", }, { locale: "sw-TZ", - short_name: "sw-TZ-DaudiNeural" + short_name: "sw-TZ-DaudiNeural", }, { locale: "sw-TZ", - short_name: "sw-TZ-RehemaNeural" + short_name: "sw-TZ-RehemaNeural", }, { locale: "sv-SE", - short_name: "sv-SE-MattiasNeural" + short_name: "sv-SE-MattiasNeural", }, { locale: "sv-SE", - short_name: "sv-SE-SofieNeural" + short_name: "sv-SE-SofieNeural", }, { locale: "ta-IN", - short_name: "ta-IN-PallaviNeural" + short_name: "ta-IN-PallaviNeural", }, { locale: "ta-IN", - short_name: "ta-IN-ValluvarNeural" + short_name: "ta-IN-ValluvarNeural", }, { locale: "ta-MY", - short_name: "ta-MY-KaniNeural" + short_name: "ta-MY-KaniNeural", }, { locale: "ta-MY", - short_name: "ta-MY-SuryaNeural" + short_name: "ta-MY-SuryaNeural", }, { locale: "ta-SG", - short_name: "ta-SG-AnbuNeural" + short_name: "ta-SG-AnbuNeural", }, { locale: "ta-SG", - short_name: "ta-SG-VenbaNeural" + short_name: "ta-SG-VenbaNeural", }, { locale: "ta-LK", - short_name: "ta-LK-KumarNeural" + short_name: "ta-LK-KumarNeural", }, { locale: "ta-LK", - short_name: "ta-LK-SaranyaNeural" + short_name: "ta-LK-SaranyaNeural", }, { locale: "te-IN", - short_name: "te-IN-MohanNeural" + short_name: "te-IN-MohanNeural", }, { locale: "te-IN", - short_name: "te-IN-ShrutiNeural" + short_name: "te-IN-ShrutiNeural", }, { locale: "th-TH", - short_name: "th-TH-NiwatNeural" + short_name: "th-TH-NiwatNeural", }, { locale: "th-TH", - short_name: "th-TH-PremwadeeNeural" + short_name: "th-TH-PremwadeeNeural", }, { locale: "tr-TR", - short_name: "tr-TR-AhmetNeural" + short_name: "tr-TR-AhmetNeural", }, { locale: "tr-TR", - short_name: "tr-TR-EmelNeural" + short_name: "tr-TR-EmelNeural", }, { locale: "uk-UA", - short_name: "uk-UA-OstapNeural" + short_name: "uk-UA-OstapNeural", }, { locale: "uk-UA", - short_name: "uk-UA-PolinaNeural" + short_name: "uk-UA-PolinaNeural", }, { locale: "ur-IN", - short_name: "ur-IN-GulNeural" + short_name: "ur-IN-GulNeural", }, { locale: "ur-IN", - short_name: "ur-IN-SalmanNeural" + short_name: "ur-IN-SalmanNeural", }, { locale: "ur-PK", - short_name: "ur-PK-AsadNeural" + short_name: "ur-PK-AsadNeural", }, { locale: "ur-PK", - short_name: "ur-PK-UzmaNeural" + short_name: "ur-PK-UzmaNeural", }, { locale: "uz-UZ", - short_name: "uz-UZ-MadinaNeural" + short_name: "uz-UZ-MadinaNeural", }, { locale: "uz-UZ", - short_name: "uz-UZ-SardorNeural" + short_name: "uz-UZ-SardorNeural", }, { locale: "vi-VN", - short_name: "vi-VN-HoaiMyNeural" + short_name: "vi-VN-HoaiMyNeural", }, { locale: "vi-VN", - short_name: "vi-VN-NamMinhNeural" + short_name: "vi-VN-NamMinhNeural", }, { locale: "cy-GB", - short_name: "cy-GB-AledNeural" + short_name: "cy-GB-AledNeural", }, { locale: "cy-GB", - short_name: "cy-GB-NiaNeural" + short_name: "cy-GB-NiaNeural", }, { locale: "zu-ZA", - short_name: "zu-ZA-ThandoNeural" + short_name: "zu-ZA-ThandoNeural", }, { locale: "zu-ZA", - short_name: "zu-ZA-ThembaNeural" - } + short_name: "zu-ZA-ThembaNeural", + }, ]; export { VOICES }; diff --git a/src/config/word-board-config.ts b/src/config/word-board-config.ts index 3fad102..d5b71ef 100644 --- a/src/config/word-board-config.ts +++ b/src/config/word-board-config.ts @@ -1,4 +1,4 @@ export const BOARD_WIDTH = globalThis.innerWidth * 0.68; export const BOARD_HEIGHT = globalThis.innerHeight * 0.68; export const TEXT_SIZE = 30; -export const TEXT_WIDTH = TEXT_SIZE * 0.6; \ No newline at end of file +export const TEXT_WIDTH = TEXT_SIZE * 0.6; diff --git a/src/hooks/useAudioPlayer.ts b/src/hooks/useAudioPlayer.ts index 0fa89b7..e6954df 100644 --- a/src/hooks/useAudioPlayer.ts +++ b/src/hooks/useAudioPlayer.ts @@ -1,34 +1,33 @@ import { useRef, useEffect } from "react"; - export function useAudioPlayer() { - const audioRef = useRef(null); - useEffect(() => { - audioRef.current = new Audio(); - return () => { - audioRef.current!.pause(); - audioRef.current = null; - }; - }, []); - const playAudio = async (audioUrl: string) => { - audioRef.current!.src = audioUrl; - try { - await audioRef.current!.play(); - } catch (e) { - return e; - } - }; - const pauseAudio = () => { - audioRef.current!.pause(); - }; - const stopAudio = () => { - audioRef.current!.pause(); - audioRef.current!.currentTime = 0; - }; - return { - playAudio, - pauseAudio, - stopAudio, - audioRef + const audioRef = useRef(null); + useEffect(() => { + audioRef.current = new Audio(); + return () => { + audioRef.current!.pause(); + audioRef.current = null; }; + }, []); + const playAudio = async (audioUrl: string) => { + audioRef.current!.src = audioUrl; + try { + await audioRef.current!.play(); + } catch (e) { + return e; + } + }; + const pauseAudio = () => { + audioRef.current!.pause(); + }; + const stopAudio = () => { + audioRef.current!.pause(); + audioRef.current!.currentTime = 0; + }; + return { + playAudio, + pauseAudio, + stopAudio, + audioRef, + }; } diff --git a/src/interfaces.ts b/src/interfaces.ts index fc320b7..1c0eda0 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -4,17 +4,21 @@ export interface Word { word: string; x: number; y: number; -}export interface Letter { - letter: string; - letter_name_ipa: string; - letter_sound_ipa: string; - roman_letter?: string; } -export type SupportedAlphabets = 'japanese' | 'english' | 'esperanto' | 'uyghur'; +export interface Letter { + letter: string; + letter_name_ipa: string; + letter_sound_ipa: string; + roman_letter?: string; +} +export type SupportedAlphabets = + | "japanese" + | "english" + | "esperanto" + | "uyghur"; export const TextSpeakerItemSchema = z.object({ - text: z.string(), - ipa: z.string().optional(), - locale: z.string() + text: z.string(), + ipa: z.string().optional(), + locale: z.string(), }); export const TextSpeakerArraySchema = z.array(TextSpeakerItemSchema); - diff --git a/src/utils.ts b/src/utils.ts index 2f8df04..60fae59 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,77 +2,94 @@ import { EdgeTTS, ProsodyOptions } from "edge-tts-universal/browser"; import { env } from "process"; import { TextSpeakerArraySchema } from "./interfaces"; import z from "zod"; +import { NextResponse } from "next/server"; export function inspect(word: string) { - const goto = (url: string) => { - window.open(url, '_blank'); - } - return () => { - word = word.toLowerCase(); - goto(`https://www.youdao.com/result?word=${word}&lang=en`); - } + const goto = (url: string) => { + window.open(url, "_blank"); + }; + return () => { + word = word.toLowerCase(); + goto(`https://www.youdao.com/result?word=${word}&lang=en`); + }; } export function urlGoto(url: string) { - window.open(url, '_blank'); + window.open(url, "_blank"); } const API_KEY = env.ZHIPU_API_KEY; -export async function callZhipuAPI(messages: { role: string; content: string; }[], model = 'glm-4.5-flash') { - const url = 'https://open.bigmodel.cn/api/paas/v4/chat/completions'; +export async function callZhipuAPI( + messages: { role: string; content: string }[], + model = "glm-4.5-flash", +) { + const url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"; - const response = await fetch(url, { - method: 'POST', - headers: { - 'Authorization': 'Bearer ' + API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: model, - messages: messages, - temperature: 0.2, - thinking: { - type: 'disabled' - } - }) - }); + const response = await fetch(url, { + method: "POST", + headers: { + Authorization: "Bearer " + API_KEY, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: model, + messages: messages, + temperature: 0.2, + thinking: { + type: "disabled", + }, + }), + }); - if (!response.ok) { - throw new Error(`API 调用失败: ${response.status}`); - } + if (!response.ok) { + throw new Error(`API 调用失败: ${response.status}`); + } - return await response.json(); + return await response.json(); } -export async function getTTSAudioUrl(text: string, short_name: string, options: ProsodyOptions | undefined = undefined) { - const tts = new EdgeTTS(text, short_name, options); - try { - const result = await tts.synthesize(); - return URL.createObjectURL(result.audio); - } catch (e) { - throw e; - } +export async function getTTSAudioUrl( + text: string, + short_name: string, + options: ProsodyOptions | undefined = undefined, +) { + const tts = new EdgeTTS(text, short_name, options); + try { + const result = await tts.synthesize(); + return URL.createObjectURL(result.audio); + } catch (e) { + throw e; + } } export const getTextSpeakerData = () => { - try { - const item = localStorage.getItem('text-speaker'); + try { + const item = localStorage.getItem("text-speaker"); - if (!item) return []; + if (!item) return []; - const rawData = JSON.parse(item); - const result = TextSpeakerArraySchema.safeParse(rawData); + 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 []; + 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 []; + } }; -export const setTextSpeakerData = (data: z.infer) => { - if (!localStorage) return; - localStorage.setItem('text-speaker', JSON.stringify(data)); -}; \ No newline at end of file +export const setTextSpeakerData = ( + data: z.infer, +) => { + if (!localStorage) return; + localStorage.setItem("text-speaker", JSON.stringify(data)); +}; +export function handleAPIError(error: unknown, message: string) { + console.error(message, error); + return NextResponse.json( + { error: "服务器内部错误", message }, + { status: 500 }, + ); +}