diff --git a/.dumirc.ts b/.dumirc.ts index 41f4e30..61b4025 100644 --- a/.dumirc.ts +++ b/.dumirc.ts @@ -52,10 +52,7 @@ export default defineConfig({ 'process.env': process.env, }, favicons: ['https://lobehub.com/favicon.ico'], - locales: [ - { id: 'en-US', name: 'English' }, - { id: 'zh-CN', name: '简体中文' }, - ], + locales: [{ id: 'en-US', name: 'English' }], // mfsu: isWin ? undefined : {}, mfsu: false, npmClient: 'pnpm', diff --git a/api/edge-speech.ts b/api/edge-speech.ts index e8e6ab6..8751336 100644 --- a/api/edge-speech.ts +++ b/api/edge-speech.ts @@ -1,4 +1,5 @@ -import { EdgeSpeechPayload, createEdgeSpeech } from '../src/core/EdgeSpeechTTS/createEdgeSpeech'; +import cors from '../lib/cors'; +import { EdgeSpeechPayload, EdgeSpeechTTS } from '../src/core'; export const config = { runtime: 'edge', @@ -6,8 +7,9 @@ export const config = { export default async (req: Request) => { if (req.method !== 'POST') return new Response('Method Not Allowed', { status: 405 }); - const payload = (await req.json()) as EdgeSpeechPayload; - return createEdgeSpeech({ payload }); + const res = await EdgeSpeechTTS.createRequest({ payload }); + + return cors(req, res); }; diff --git a/api/microsoft-speech.ts b/api/microsoft-speech.ts index 5d95e62..4f3d2a1 100644 --- a/api/microsoft-speech.ts +++ b/api/microsoft-speech.ts @@ -1,7 +1,5 @@ -import { - MicrosoftSpeechPayload, - createMicrosoftSpeech, -} from '../src/core/MicrosoftSpeechTTS/createMicrosoftSpeech'; +import cors from '../lib/cors'; +import { MicrosoftSpeechPayload, MicrosoftSpeechTTS } from '../src/core'; export const config = { runtime: 'edge', @@ -11,5 +9,7 @@ export default async (req: Request) => { if (req.method !== 'POST') return new Response('Method Not Allowed', { status: 405 }); const payload = (await req.json()) as MicrosoftSpeechPayload; - return createMicrosoftSpeech({ payload }); + const res = await MicrosoftSpeechTTS.createRequest({ payload }); + + return cors(req, res); }; diff --git a/api/openai-stt.ts b/api/openai-stt.ts index 722f807..e457a02 100644 --- a/api/openai-stt.ts +++ b/api/openai-stt.ts @@ -2,6 +2,7 @@ import OpenAI from 'openai'; import { OpenAISTTPayload } from '@/core'; +import cors from '../lib/cors'; import { createOpenaiAudioTranscriptions } from '../src/server/createOpenaiAudioTranscriptions'; export const config = { @@ -21,9 +22,12 @@ export default async (req: Request) => { const openai = new OpenAI({ apiKey: OPENAI_API_KEY, baseURL: OPENAI_BASE_URL }); const res = await createOpenaiAudioTranscriptions({ openai, payload }); - return new Response(JSON.stringify(res), { - headers: { - 'content-type': 'application/json;charset=UTF-8', - }, - }); + return cors( + req, + new Response(JSON.stringify(res), { + headers: { + 'content-type': 'application/json;charset=UTF-8', + }, + }), + ); }; diff --git a/api/openai-tts.ts b/api/openai-tts.ts index 5d627ba..5e10734 100644 --- a/api/openai-tts.ts +++ b/api/openai-tts.ts @@ -2,6 +2,7 @@ import OpenAI from 'openai'; import { OpenAITTSPayload } from '@/core'; +import cors from '../lib/cors'; import { createOpenaiAudioSpeech } from '../src/server/createOpenaiAudioSpeech'; export const config = { @@ -10,6 +11,7 @@ export const config = { export default async (req: Request) => { if (req.method !== 'POST') return new Response('Method Not Allowed', { status: 405 }); + const OPENAI_API_KEY = process.env.OPENAI_API_KEY; const OPENAI_BASE_URL = process.env.OPENAI_BASE_URL; @@ -19,5 +21,7 @@ export default async (req: Request) => { const openai = new OpenAI({ apiKey: OPENAI_API_KEY, baseURL: OPENAI_BASE_URL }); - return createOpenaiAudioSpeech({ openai, payload }); + const res = await createOpenaiAudioSpeech({ openai, payload }); + + return cors(req, res); }; diff --git a/docs/STT.tsx b/docs/STT.tsx new file mode 100644 index 0000000..ea65f88 --- /dev/null +++ b/docs/STT.tsx @@ -0,0 +1,25 @@ +import { Icon } from '@lobehub/ui'; +import { Button, Input } from 'antd'; +import { Mic, StopCircle } from 'lucide-react'; +import { Flexbox } from 'react-layout-kit'; + +import { useSpeechRecognition } from '@/react'; + +export default () => { + const { text, start, stop, isLoading, formattedTime, url } = useSpeechRecognition('zh-CN'); + return ( + + {isLoading ? ( + + ) : ( + + )} + + {url && + ); +}; diff --git a/docs/TTS.tsx b/docs/TTS.tsx new file mode 100644 index 0000000..0a0b2a8 --- /dev/null +++ b/docs/TTS.tsx @@ -0,0 +1,34 @@ +import { Icon } from '@lobehub/ui'; +import { Button, Input } from 'antd'; +import { Volume2 } from 'lucide-react'; +import { Flexbox } from 'react-layout-kit'; + +import { AudioPlayer, useEdgeSpeech } from '@/react'; + +export default () => { + const { setText, isGlobalLoading, start, stop, audio } = useEdgeSpeech('Edge Speech Example', { + api: {}, + options: { + voice: 'zh-CN-YunxiaNeural', + }, + }); + + return ( + + {isGlobalLoading ? ( + + ) : ( + + )} + setText(e.target.value)} + /> + + + ); +}; diff --git a/docs/api-reference/edge-speech-tts.md b/docs/api-reference/edge-speech-tts.md index 4fb3a39..510c961 100644 --- a/docs/api-reference/edge-speech-tts.md +++ b/docs/api-reference/edge-speech-tts.md @@ -1,10 +1,12 @@ --- -group: TTS +group: API Reference title: EdgeSpeechTTS apiHeader: pkg: '@lobehub/tts' --- +## Introduction + `EdgeSpeechTTS` is a class for text-to-speech conversion based on Edge Speech Service. This class supports converting text to speech and provides a set of methods to retrieve voice options and create speech synthesis requests. diff --git a/docs/api-reference/edge-speech-tts.zh-CN.md b/docs/api-reference/edge-speech-tts.zh-CN.md index 322bf32..5af48e8 100644 --- a/docs/api-reference/edge-speech-tts.zh-CN.md +++ b/docs/api-reference/edge-speech-tts.zh-CN.md @@ -1,10 +1,12 @@ --- -group: TTS +group: API Reference title: EdgeSpeechTTS apiHeader: pkg: '@lobehub/tts' --- +## 介绍 + `EdgeSpeechTTS` 是一个基于 Edge 语音服务的文本转语音方法类。 该类支持将文本转换为语音,并提供了一系列方法来获取语音选项,创建语音合成请求。 diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md deleted file mode 100644 index 0d2c37b..0000000 --- a/docs/api-reference/index.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: API Reference -nav: - title: API - order: 10 ---- - -# API Reference Guide - -## TTS - -- [EdgeSpeechTTS](./edge-speech-tts.en-US.md) -- [MicrosoftSpeechTTS](microsoft-speech-tts.en-US.md) -- [OpenaiTTS](openai-tts.en-US.md) diff --git a/docs/api-reference/index.zh-CN.md b/docs/api-reference/index.zh-CN.md deleted file mode 100644 index 60baeb4..0000000 --- a/docs/api-reference/index.zh-CN.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: API Reference -nav: - title: API - order: 10 ---- - -# API 参考指南 - -## TTS - -- [EdgeSpeechTTS](./edge-speech-tts.zh-CN.md) -- [MicrosoftSpeechTTS](microsoft-speech-tts.zh-CN.md) -- [OpenaiTTS](openai-tts.zh-CN.md) diff --git a/docs/api-reference/microsoft-speech-tts.md b/docs/api-reference/microsoft-speech-tts.md index bcb251a..50f9636 100644 --- a/docs/api-reference/microsoft-speech-tts.md +++ b/docs/api-reference/microsoft-speech-tts.md @@ -1,10 +1,12 @@ --- -group: TTS +group: API Reference title: MicrosoftSpeechTTS apiHeader: pkg: '@lobehub/tts' --- +## Introduction + `MicrosoftSpeechTTS` is a class for text-to-speech using Microsoft Speech Services. This class supports converting text to speech and provides a series of methods to retrieve speech options and create speech synthesis requests. @@ -23,7 +25,6 @@ constructor(options: MicrosoftSpeechAPI): MicrosoftSpeechTTS ```js // index.js -// index.js import { MicrosoftSpeechTTS } from '@lobehub/tts'; // get MicrosoftSpeechTTS instance diff --git a/docs/api-reference/microsoft-speech-tts.zh-CN.md b/docs/api-reference/microsoft-speech-tts.zh-CN.md index 96f8912..a41e922 100644 --- a/docs/api-reference/microsoft-speech-tts.zh-CN.md +++ b/docs/api-reference/microsoft-speech-tts.zh-CN.md @@ -1,10 +1,12 @@ --- -group: TTS +group: API Reference title: MicrosoftSpeechTTS apiHeader: pkg: '@lobehub/tts' --- +## 介绍 + `MicrosoftSpeechTTS` 是一个基于 Microsoft 语音服务的文本转语音方法类。 该类支持将文本转换为语音,并提供了一系列方法来获取语音选项,创建语音合成请求。 diff --git a/docs/api-reference/openai-tts.md b/docs/api-reference/openai-tts.md index 95c24bc..db4f525 100644 --- a/docs/api-reference/openai-tts.md +++ b/docs/api-reference/openai-tts.md @@ -1,10 +1,12 @@ --- -group: TTS +group: API Reference title: OpenAITTS apiHeader: pkg: '@lobehub/tts' --- +## Introduction + `OpenAITTS` is a class for text-to-speech using the OpenAI voice service. This class supports converting text into speech and provides a set of methods for getting voice options and creating speech synthesis requests. diff --git a/docs/api-reference/openai-tts.zh-CN.md b/docs/api-reference/openai-tts.zh-CN.md index c9b31b7..d23b28e 100644 --- a/docs/api-reference/openai-tts.zh-CN.md +++ b/docs/api-reference/openai-tts.zh-CN.md @@ -1,10 +1,12 @@ --- -group: TTS +group: API Reference title: OpenAITTS apiHeader: pkg: '@lobehub/tts' --- +## 介绍 + `OpenAITTS` 是一个基于 OpenAI 语音服务的文本转语音方法类。 该类支持将文本转换为语音,并提供了一系列方法来获取语音选项,创建语音合成请求。 diff --git a/docs/index.tsx b/docs/index.tsx index 4504e2e..df9ea7e 100644 --- a/docs/index.tsx +++ b/docs/index.tsx @@ -1,11 +1,25 @@ -import { Snippet } from '@lobehub/ui'; -import { Center } from 'react-layout-kit'; +import { Grid, Snippet } from '@lobehub/ui'; +import { Card } from 'antd'; +import { Center, Flexbox } from 'react-layout-kit'; + +import STT from './STT'; +import TTS from './TTS'; export default () => { return ( -
-

To install Lobe TTS, run the following command:

- {'$ bun add @lobehub/tts'} -
+ +
+

To install Lobe TTS, run the following command:

+ {'$ bun add @lobehub/tts'} +
+ + + + + + + + +
); }; diff --git a/lib/cors.ts b/lib/cors.ts new file mode 100644 index 0000000..4b57f45 --- /dev/null +++ b/lib/cors.ts @@ -0,0 +1,140 @@ +/** + * Multi purpose CORS lib. + * Note: Based on the `cors` package in npm but using only + * web APIs. Feel free to use it in your own projects. + */ + +type StaticOrigin = boolean | string | RegExp | (boolean | string | RegExp)[]; + +type OriginFn = (origin: string | undefined, req: Request) => StaticOrigin | Promise; + +interface CorsOptions { + allowedHeaders?: string | string[]; + credentials?: boolean; + exposedHeaders?: string | string[]; + maxAge?: number; + methods?: string | string[]; + optionsSuccessStatus?: number; + origin?: StaticOrigin | OriginFn; + preflightContinue?: boolean; +} + +const defaultOptions: CorsOptions = { + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', + optionsSuccessStatus: 204, + origin: '*', + preflightContinue: false, +}; + +function isOriginAllowed(origin: string, allowed: StaticOrigin): boolean { + return Array.isArray(allowed) + ? allowed.some((o) => isOriginAllowed(origin, o)) + : typeof allowed === 'string' + ? origin === allowed + : allowed instanceof RegExp + ? allowed.test(origin) + : !!allowed; +} + +function getOriginHeaders(reqOrigin: string | undefined, origin: StaticOrigin) { + const headers = new Headers(); + + if (origin === '*') { + // Allow any origin + headers.set('Access-Control-Allow-Origin', '*'); + } else if (typeof origin === 'string') { + // Fixed origin + headers.set('Access-Control-Allow-Origin', origin); + headers.append('Vary', 'Origin'); + } else { + const allowed = isOriginAllowed(reqOrigin ?? '', origin); + + if (allowed && reqOrigin) { + headers.set('Access-Control-Allow-Origin', reqOrigin); + } + headers.append('Vary', 'Origin'); + } + + return headers; +} + +// originHeadersFromReq + +async function originHeadersFromReq(req: Request, origin: StaticOrigin | OriginFn) { + const reqOrigin = req.headers.get('Origin') || undefined; + const value = typeof origin === 'function' ? await origin(reqOrigin, req) : origin; + + if (!value) return; + return getOriginHeaders(reqOrigin, value); +} + +function getAllowedHeaders(req: Request, allowed?: string | string[]) { + const headers = new Headers(); + + if (!allowed) { + allowed = req.headers.get('Access-Control-Request-Headers')!; + headers.append('Vary', 'Access-Control-Request-Headers'); + } else if (Array.isArray(allowed)) { + // If the allowed headers is an array, turn it into a string + allowed = allowed.join(','); + } + if (allowed) { + headers.set('Access-Control-Allow-Headers', allowed); + } + + return headers; +} + +export default async function cors(req: Request, res: Response, options?: CorsOptions) { + const opts = { ...defaultOptions, ...options }; + const { headers } = res; + const originHeaders = await originHeadersFromReq(req, opts.origin ?? false); + const mergeHeaders = (v: string, k: string) => { + if (k === 'Vary') headers.append(k, v); + else headers.set(k, v); + }; + + // If there's no origin we won't touch the response + if (!originHeaders) return res; + + originHeaders.forEach(mergeHeaders); + + if (opts.credentials) { + headers.set('Access-Control-Allow-Credentials', 'true'); + } + + const exposed = Array.isArray(opts.exposedHeaders) + ? opts.exposedHeaders.join(',') + : opts.exposedHeaders; + + if (exposed) { + headers.set('Access-Control-Expose-Headers', exposed); + } + + // Handle the preflight request + if (req.method === 'OPTIONS') { + if (opts.methods) { + const methods = Array.isArray(opts.methods) ? opts.methods.join(',') : opts.methods; + + headers.set('Access-Control-Allow-Methods', methods); + } + + getAllowedHeaders(req, opts.allowedHeaders).forEach(mergeHeaders); + + if (typeof opts.maxAge === 'number') { + headers.set('Access-Control-Max-Age', String(opts.maxAge)); + } + + if (opts.preflightContinue) return res; + + headers.set('Content-Length', '0'); + return new Response(null, { headers, status: opts.optionsSuccessStatus }); + } + + // If we got here, it's a normal request + return res; +} + +export function initCors(options?: CorsOptions) { + return (req: Request, res: Response) => cors(req, res, options); +} diff --git a/package.json b/package.json index 28e414a..508aaf7 100644 --- a/package.json +++ b/package.json @@ -45,20 +45,19 @@ "build": "father build", "ci": "npm run lint && npm run type-check && npm run doctor", "dev": "father dev", - "docs:build": "npm run setup && npm run build && dumi build", + "docs:build": "npm run build && dumi build", "docs:build-analyze": "ANALYZE=1 dumi build", - "docs:dev": "npm run setup && dumi dev", + "docs:dev": "concurrently -n docs,api -c blue,yellow \"dumi dev\" \"vercel dev -y\"", "doctor": "father doctor", "i18n-md": "lobe-i18n md", "lint": "eslint \"{src,api,lib}/**/*.{js,jsx,ts,tsx}\" --fix", "lint:md": "remark . --quiet --frail --output", "lint:style": "stylelint \"{src,tests}/**/*.{js,jsx,ts,tsx}\" --fix", - "prepare": "husky install", + "prepare": "husky install && npm run setup", "prepublishOnly": "npm run build", "prettier": "prettier -c --write \"**/**\"", "release": "semantic-release", "setup": "dumi setup", - "start": "vercel dev", "type-check": "tsc --noEmit" }, "lint-staged": { @@ -90,10 +89,10 @@ "not ie <= 10" ], "dependencies": { - "@babel/runtime": "^7.24.7", + "@babel/runtime": "^7.26.0", "lodash-es": "^4.17.21", - "query-string": "^9.0.0", - "react-error-boundary": "^4.0.13", + "query-string": "^9.1.1", + "react-error-boundary": "^4.1.2", "remark-gfm": "^3.0.1", "remark-parse": "^10.0.2", "swr": "^2.2.5", @@ -103,30 +102,31 @@ "uuid": "^10.0.0" }, "devDependencies": { - "@commitlint/cli": "^19.3.0", - "@lobehub/i18n-cli": "^1.18.1", - "@lobehub/lint": "^1.24.3", - "@lobehub/ui": "^1.146.4", + "@commitlint/cli": "^19.6.0", + "@lobehub/i18n-cli": "^1.20.0", + "@lobehub/lint": "^1.24.4", + "@lobehub/ui": "^1.153.0", "@types/lodash-es": "^4.17.12", - "@types/node": "^20.14.9", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", + "@types/node": "^20.17.6", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", "@types/uuid": "^9.0.8", - "@vercel/node": "^3.2.0", - "commitlint": "^19.3.0", - "dumi": "2.2.17", - "dumi-theme-lobehub": "^1.8.1", - "eslint": "^8.57.0", - "father": "4.3.1", - "husky": "^9.0.11", - "lint-staged": "^15.2.7", - "prettier": "^3.3.2", + "@vercel/node": "^3.2.26", + "commitlint": "^19.6.0", + "concurrently": "^9.0.1", + "dumi": "^2.4.14", + "dumi-theme-lobehub": "^1.8.3", + "eslint": "^8.57.1", + "father": "^4.5.1", + "husky": "^9.1.7", + "lint-staged": "^15.2.10", + "prettier": "^3.3.3", "remark": "^14.0.3", "remark-cli": "^11.0.0", "semantic-release": "^21.1.2", "stylelint": "^15.11.0", - "tsx": "^4.16.0", - "typescript": "^5.5.2", + "tsx": "^4.19.2", + "typescript": "^5.6.3", "vercel": "^28.20.0" }, "peerDependencies": { diff --git a/src/core/MicrosoftSpeechTTS/createMicrosoftSpeech.ts b/src/core/MicrosoftSpeechTTS/createMicrosoftSpeech.ts index 36a9693..5847867 100644 --- a/src/core/MicrosoftSpeechTTS/createMicrosoftSpeech.ts +++ b/src/core/MicrosoftSpeechTTS/createMicrosoftSpeech.ts @@ -49,7 +49,7 @@ export const createMicrosoftSpeech = async ( SpeakTriggerSource: 'AccTuningPagePlayButton', }, ssml: genSSML(input, options), - ttsAudioFormat: 'audio-24khz-160kbitrate-mono-mp3', + ttsAudioFormat: 'audio-24khz-48kbitrate-mono-mp3', }); return fetch(proxyUrl ? proxyUrl : MICROSOFT_SPEECH_URL, { diff --git a/src/react/AudioPlayer/index.md b/src/react/AudioPlayer/index.md index 2e6aa53..a54eabf 100644 --- a/src/react/AudioPlayer/index.md +++ b/src/react/AudioPlayer/index.md @@ -2,8 +2,10 @@ nav: Components group: UI title: AudioPlayer +apiHeader: + pkg: '@lobehub/tts/react' --- -## default +## Default diff --git a/src/react/AudioPlayer/index.zh-CN.md b/src/react/AudioPlayer/index.zh-CN.md deleted file mode 100644 index 515b04a..0000000 --- a/src/react/AudioPlayer/index.zh-CN.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: AudioPlayer -group: UI -nav: 组件 -apiHeader: - pkg: '@lobehub/tts/react' ---- - -## default - - diff --git a/src/react/AudioVisualizer/index.md b/src/react/AudioVisualizer/index.md index 2f70b44..6ff54cc 100644 --- a/src/react/AudioVisualizer/index.md +++ b/src/react/AudioVisualizer/index.md @@ -2,8 +2,10 @@ nav: Components group: UI title: AudioVisualizer +apiHeader: + pkg: '@lobehub/tts/react' --- -## default +## Default diff --git a/src/react/AudioVisualizer/index.zh-CN.md b/src/react/AudioVisualizer/index.zh-CN.md deleted file mode 100644 index 04c808d..0000000 --- a/src/react/AudioVisualizer/index.zh-CN.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: AudioVisualizer -group: UI -nav: 组件 -apiHeader: - pkg: '@lobehub/tts/react' ---- - -## default - - diff --git a/src/react/useAudioRecorder/index.md b/src/react/useAudioRecorder/index.md index 6ced734..f74512b 100644 --- a/src/react/useAudioRecorder/index.md +++ b/src/react/useAudioRecorder/index.md @@ -2,8 +2,10 @@ nav: Components group: STT title: useAudioRecorder +apiHeader: + pkg: '@lobehub/tts/react' --- -## hooks +## React Hooks diff --git a/src/react/useEdgeSpeech/index.md b/src/react/useEdgeSpeech/index.md index 74698df..b63bc78 100644 --- a/src/react/useEdgeSpeech/index.md +++ b/src/react/useEdgeSpeech/index.md @@ -2,8 +2,10 @@ nav: Components group: TTS title: useEdgeSpeech +apiHeader: + pkg: '@lobehub/tts/react' --- -## hooks +## React Hooks diff --git a/src/react/useMicrosoftSpeech/index.md b/src/react/useMicrosoftSpeech/index.md index 6f77e91..e7505dc 100644 --- a/src/react/useMicrosoftSpeech/index.md +++ b/src/react/useMicrosoftSpeech/index.md @@ -2,9 +2,11 @@ nav: Components group: TTS title: useMicrosoftSpeech +apiHeader: + pkg: '@lobehub/tts/react' --- -## hooks +## React Hooks - ENV: `MICROSOFT_SPEECH_BACKEND_URL` diff --git a/src/react/useOpenAISTT/index.md b/src/react/useOpenAISTT/index.md index 00d46af..b77c96c 100644 --- a/src/react/useOpenAISTT/index.md +++ b/src/react/useOpenAISTT/index.md @@ -2,9 +2,11 @@ nav: Components group: STT title: useOpenAISTT +apiHeader: + pkg: '@lobehub/tts/react' --- -## hooks +## React Hooks - ENV: `OPENAI_API_KEY` `OPENAI_BASE_URL` diff --git a/src/react/useOpenAITTS/index.md b/src/react/useOpenAITTS/index.md index 4c5a571..bdbb7ec 100644 --- a/src/react/useOpenAITTS/index.md +++ b/src/react/useOpenAITTS/index.md @@ -2,9 +2,11 @@ nav: Components group: TTS title: useOpenAITTS +apiHeader: + pkg: '@lobehub/tts/react' --- -## hooks +## React Hooks - ENV: `OPENAI_API_KEY` `OPENAI_BASE_URL` diff --git a/src/react/useSpeechRecognition/index.md b/src/react/useSpeechRecognition/index.md index c8bc7c2..ad7a035 100644 --- a/src/react/useSpeechRecognition/index.md +++ b/src/react/useSpeechRecognition/index.md @@ -2,9 +2,11 @@ nav: Components group: STT title: useSpeechRecognition +apiHeader: + pkg: '@lobehub/tts/react' --- -## hooks +## React Hooks diff --git a/src/react/useSpeechSynthes/index.md b/src/react/useSpeechSynthes/index.md index 26427a0..65dc31e 100644 --- a/src/react/useSpeechSynthes/index.md +++ b/src/react/useSpeechSynthes/index.md @@ -2,8 +2,10 @@ nav: Components group: TTS title: useSpeechSynthes +apiHeader: + pkg: '@lobehub/tts/react' --- -## hooks +## React Hooks