diff --git a/package.json b/package.json
index d7aac6f..ac941ee 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@yuntijs/ui",
- "version": "1.0.0-beta.67",
+ "version": "1.0.0-beta.68",
"description": "☁️ Yunti UI - an open-source UI component library for building Cloud Native web apps",
"keywords": [
"yuntijs",
@@ -86,6 +86,7 @@
"@lexical/selection": "^0.16.1",
"@lexical/text": "^0.16.1",
"@lexical/utils": "^0.16.1",
+ "@lobehub/tts": "^1.25.1",
"@lobehub/ui": "^1.147.0",
"@melloware/react-logviewer": "^5.2.0",
"@monaco-editor/loader": "^1.4.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index eec1523..03ba529 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -29,6 +29,9 @@ importers:
'@lexical/utils':
specifier: ^0.16.1
version: 0.16.1
+ '@lobehub/tts':
+ specifier: ^1.25.1
+ version: 1.25.1(@lobehub/ui@1.147.0(@types/react-dom@18.3.0)(@types/react@18.2.40)(ahooks@3.8.0(react@18.3.1))(antd-style@3.6.2(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lucide-react@0.417.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd-style@3.6.2(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lucide-react@0.417.0(react@18.3.1))(openai@4.67.3(encoding@0.1.13)(zod@3.23.8))(react-dom@18.3.1(react@18.3.1))(react-layout-kit@1.9.0(react@18.3.1))(react@18.3.1)
'@lobehub/ui':
specifier: ^1.147.0
version: 1.147.0(@types/react-dom@18.3.0)(@types/react@18.2.40)(ahooks@3.8.0(react@18.3.1))(antd-style@3.6.2(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lucide-react@0.417.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -122,7 +125,7 @@ importers:
version: 2.4.9(@babel/core@7.25.2)(@swc/helpers@0.5.1)(@types/node@22.0.0)(@types/react@18.2.40)(eslint@8.57.0)(jest@27.5.1)(lightningcss@1.22.1)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(stylelint@15.11.0(typescript@5.5.4))(terser@5.31.3)(type-fest@4.23.0)(typescript@5.5.4)(webpack@5.93.0)
dumi-theme-yunti:
specifier: ^1.7.1
- version: 1.7.1(kc6prymi3ksoiipe53dmaojid4)
+ version: 1.7.2(@emotion/css@11.13.0)(@giscus/react@3.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@lobehub/ui@1.147.0(@types/react-dom@18.3.0)(@types/react@18.2.40)(ahooks@3.8.0(react@18.3.1))(antd-style@3.6.2(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lucide-react@0.417.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dumi@2.4.9(@babel/core@7.25.2)(@swc/helpers@0.5.1)(@types/node@22.0.0)(@types/react@18.2.40)(eslint@8.57.0)(jest@27.5.1)(lightningcss@1.22.1)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(stylelint@15.11.0(typescript@5.5.4))(terser@5.31.3)(type-fest@4.23.0)(typescript@5.5.4)(webpack@5.93.0))(immer@10.1.1)(lucide-react@0.417.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-layout-kit@1.9.0(react@18.3.1))(react@18.3.1)
eslint:
specifier: ^8.56.0
version: 8.57.0
@@ -1586,6 +1589,18 @@ packages:
'@lobehub/emojilib@1.0.0':
resolution: {integrity: sha512-s9KnjaPjsEefaNv150G3aifvB+J3P4eEKG+epY9zDPS2BeB6+V2jELWqAZll+nkogMaVovjEE813z3V751QwGw==}
+ '@lobehub/tts@1.25.1':
+ resolution: {integrity: sha512-bcBbGPZJIyB5LFqkjVKXeDL3qbZA5az4zHM+oZTtZ8GK88xCu/juANUmssCXErtfy4YX+vtAHj3JtDqaZAftFw==}
+ peerDependencies:
+ '@lobehub/ui': '>=1'
+ antd: '>=5'
+ antd-style: '>=3'
+ lucide-react: '>=0.396.0'
+ openai: '>=4'
+ react: '>=18'
+ react-dom: '>=18'
+ react-layout-kit: '>=1'
+
'@lobehub/ui@1.147.0':
resolution: {integrity: sha512-bjZbvtUAAX4e91tJLrCbnx5JVMWxSucPr9VUWQBFMfrmNSPZgESx4tmkY2rMWPf4z3jbjH5Q1SONDNmEtKpeyQ==}
peerDependencies:
@@ -2588,6 +2603,9 @@ packages:
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
+ '@types/node-fetch@2.6.11':
+ resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
+
'@types/node@17.0.45':
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
@@ -3102,6 +3120,10 @@ packages:
resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ abort-controller@3.0.0:
+ resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
+ engines: {node: '>=6.5'}
+
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
@@ -3153,6 +3175,10 @@ packages:
resolution: {integrity: sha512-yqXL+k5rr8+ZRpOAntkaaRgWgE5o8ESAj5DyRmVTCSoZxXmqemb9Dd7T4i5UzwuERdLAJUy6XzR9zFVuf0kzkw==}
engines: {node: '>= 4.0.0'}
+ agentkeepalive@4.5.0:
+ resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
+ engines: {node: '>= 8.0.0'}
+
aggregate-error@3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
@@ -4487,8 +4513,8 @@ packages:
dumi-assets-types@2.3.0:
resolution: {integrity: sha512-mM6UoGTgTNoo8lA4dwaIwoeSGT+4PeQeiFylr2+kCB5z3/7NEf7lIM4tqrAsEyzecE/HX0+w7Z78hnFZQ9k5vQ==}
- dumi-theme-yunti@1.7.1:
- resolution: {integrity: sha512-tm0GYRIl97WvbuISV7vi9j0P+9dmMGi1Bv+ygOije+L7eYs2QaBkmPU5RXXgnuZfTWrHL3jok2N1VL/vp7KpKA==}
+ dumi-theme-yunti@1.7.2:
+ resolution: {integrity: sha512-tGSN9hO3qUNQs+2GABlNq7qtjSwEUt4tE/Z6cnrXS6tJYrBBQqXdhDn5M9kN9EsxeNzqmfGbHQI8ulUC78WtuA==}
peerDependencies:
'@giscus/react': '>=3'
'@lobehub/ui': '>=1'
@@ -4904,6 +4930,10 @@ packages:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
+ event-target-shim@5.0.1:
+ resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
+ engines: {node: '>=6'}
+
eventemitter3@2.0.3:
resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==}
@@ -5134,6 +5164,9 @@ packages:
typescript: '>3.6.0'
webpack: ^5.11.0
+ form-data-encoder@1.7.2:
+ resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
+
form-data@3.0.1:
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
engines: {node: '>= 6'}
@@ -5146,6 +5179,10 @@ packages:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
engines: {node: '>=0.4.x'}
+ formdata-node@4.4.1:
+ resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
+ engines: {node: '>= 12.20'}
+
formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
@@ -7614,6 +7651,15 @@ packages:
resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==}
engines: {node: '>=14.16'}
+ openai@4.67.3:
+ resolution: {integrity: sha512-HT2tZgjLgRqbLQNKmYtjdF/4TQuiBvg1oGvTDhwpSEQzxo6/oM1us8VQ53vBK2BiKvCxFuq6gKGG70qfwrNhKg==}
+ hasBin: true
+ peerDependencies:
+ zod: ^3.23.8
+ peerDependenciesMeta:
+ zod:
+ optional: true
+
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -10782,6 +10828,10 @@ packages:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
+ web-streams-polyfill@4.0.0-beta.3:
+ resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
+ engines: {node: '>= 14'}
+
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
@@ -12656,6 +12706,30 @@ snapshots:
'@lobehub/emojilib@1.0.0': {}
+ '@lobehub/tts@1.25.1(@lobehub/ui@1.147.0(@types/react-dom@18.3.0)(@types/react@18.2.40)(ahooks@3.8.0(react@18.3.1))(antd-style@3.6.2(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lucide-react@0.417.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd-style@3.6.2(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lucide-react@0.417.0(react@18.3.1))(openai@4.67.3(encoding@0.1.13)(zod@3.23.8))(react-dom@18.3.1(react@18.3.1))(react-layout-kit@1.9.0(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.25.0
+ '@lobehub/ui': 1.147.0(@types/react-dom@18.3.0)(@types/react@18.2.40)(ahooks@3.8.0(react@18.3.1))(antd-style@3.6.2(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lucide-react@0.417.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ antd: 5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ antd-style: 3.6.2(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ lodash-es: 4.17.21
+ lucide-react: 0.417.0(react@18.3.1)
+ openai: 4.67.3(encoding@0.1.13)(zod@3.23.8)
+ query-string: 9.1.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-error-boundary: 4.0.13(react@18.3.1)
+ react-layout-kit: 1.9.0(react@18.3.1)
+ remark-gfm: 3.0.1
+ remark-parse: 10.0.2
+ swr: 2.2.5(react@18.3.1)
+ unified: 11.0.5
+ unist-util-visit: 5.0.0
+ url-join: 5.0.0
+ uuid: 10.0.0
+ transitivePeerDependencies:
+ - supports-color
+
'@lobehub/ui@1.147.0(@types/react-dom@18.3.0)(@types/react@18.2.40)(ahooks@3.8.0(react@18.3.1))(antd-style@3.6.2(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lucide-react@0.417.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
@@ -13690,7 +13764,7 @@ snapshots:
'@types/concat-stream@2.0.3':
dependencies:
- '@types/node': 18.19.42
+ '@types/node': 22.0.0
'@types/debug@4.1.12':
dependencies:
@@ -13793,6 +13867,11 @@ snapshots:
'@types/ms@0.7.34': {}
+ '@types/node-fetch@2.6.11':
+ dependencies:
+ '@types/node': 22.0.0
+ form-data: 4.0.0
+
'@types/node@17.0.45': {}
'@types/node@18.19.42':
@@ -14701,6 +14780,10 @@ snapshots:
abbrev@2.0.0: {}
+ abort-controller@3.0.0:
+ dependencies:
+ event-target-shim: 5.0.1
+
accepts@1.3.8:
dependencies:
mime-types: 2.1.35
@@ -14747,6 +14830,10 @@ snapshots:
dependencies:
humanize-ms: 1.2.1
+ agentkeepalive@4.5.0:
+ dependencies:
+ humanize-ms: 1.2.1
+
aggregate-error@3.1.0:
dependencies:
clean-stack: 2.2.0
@@ -16343,8 +16430,8 @@ snapshots:
dumi-assets-types@2.3.0: {}
- dumi-theme-yunti@1.7.1(kc6prymi3ksoiipe53dmaojid4):
- dependencies:
+ ? dumi-theme-yunti@1.7.2(@emotion/css@11.13.0)(@giscus/react@3.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@lobehub/ui@1.147.0(@types/react-dom@18.3.0)(@types/react@18.2.40)(ahooks@3.8.0(react@18.3.1))(antd-style@3.6.2(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lucide-react@0.417.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.2.40)(antd@5.19.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dumi@2.4.9(@babel/core@7.25.2)(@swc/helpers@0.5.1)(@types/node@22.0.0)(@types/react@18.2.40)(eslint@8.57.0)(jest@27.5.1)(lightningcss@1.22.1)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.29.4)(stylelint@15.11.0(typescript@5.5.4))(terser@5.31.3)(type-fest@4.23.0)(typescript@5.5.4)(webpack@5.93.0))(immer@10.1.1)(lucide-react@0.417.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-layout-kit@1.9.0(react@18.3.1))(react@18.3.1)
+ : dependencies:
'@ant-design/cssinjs': 1.21.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@emotion/server': 11.11.0(@emotion/css@11.13.0)
'@floating-ui/react': 0.26.20(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -17092,6 +17179,8 @@ snapshots:
etag@1.8.1: {}
+ event-target-shim@5.0.1: {}
+
eventemitter3@2.0.3: {}
eventemitter3@5.0.1: {}
@@ -17463,6 +17552,8 @@ snapshots:
typescript: 5.5.4
webpack: 5.93.0
+ form-data-encoder@1.7.2: {}
+
form-data@3.0.1:
dependencies:
asynckit: 0.4.0
@@ -17477,6 +17568,11 @@ snapshots:
format@0.2.2: {}
+ formdata-node@4.4.1:
+ dependencies:
+ node-domexception: 1.0.0
+ web-streams-polyfill: 4.0.0-beta.3
+
formdata-polyfill@4.0.10:
dependencies:
fetch-blob: 3.2.0
@@ -20735,6 +20831,20 @@ snapshots:
is-inside-container: 1.0.0
is-wsl: 2.2.0
+ openai@4.67.3(encoding@0.1.13)(zod@3.23.8):
+ dependencies:
+ '@types/node': 18.19.42
+ '@types/node-fetch': 2.6.11
+ abort-controller: 3.0.0
+ agentkeepalive: 4.5.0
+ form-data-encoder: 1.7.2
+ formdata-node: 4.4.1
+ node-fetch: 2.7.0(encoding@0.1.13)
+ optionalDependencies:
+ zod: 3.23.8
+ transitivePeerDependencies:
+ - encoding
+
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -24869,6 +24979,8 @@ snapshots:
web-streams-polyfill@3.3.3: {}
+ web-streams-polyfill@4.0.0-beta.3: {}
+
webidl-conversions@3.0.1: {}
webidl-conversions@5.0.0: {}
diff --git a/src/index.ts b/src/index.ts
index 8faaf42..2f53413 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -36,6 +36,9 @@ export * from './EditableMessage';
export * from './Highlighter';
export * from './styles';
+// ~ custom @lobehub/tts
+export * from './useSpeechSynthes';
+
// ~ antd
export {
Affix,
diff --git a/src/useSpeechSynthes/const/polyfill.ts b/src/useSpeechSynthes/const/polyfill.ts
new file mode 100644
index 0000000..23db065
--- /dev/null
+++ b/src/useSpeechSynthes/const/polyfill.ts
@@ -0,0 +1,32 @@
+const getSpeechRecognition = () => {
+ try {
+ return (
+ (globalThis as any)?.SpeechRecognition ||
+ (window as any)?.SpeechRecognition ||
+ (window as any)?.webkitSpeechRecognition
+ );
+ } catch {}
+};
+
+const getSpeechSynthesis = () => {
+ try {
+ return (
+ (globalThis as any)?.speechSynthesis ||
+ (window as any)?.speechSynthesis ||
+ (window as any)?.webkitSpeechSynthesis
+ );
+ } catch {}
+};
+
+const getSpeechSynthesisUtterance = () => {
+ try {
+ return (
+ (globalThis as any)?.SpeechSynthesisUtterance ||
+ (window as any)?.SpeechSynthesisUtterance ||
+ (window as any)?.webkitSpeechSynthesisUtterance
+ );
+ } catch {}
+};
+export const SpeechRecognition = getSpeechRecognition();
+export const SpeechSynthesis = getSpeechSynthesis();
+export const SpeechSynthesisUtterance = getSpeechSynthesisUtterance();
diff --git a/src/useSpeechSynthes/demos/index.tsx b/src/useSpeechSynthes/demos/index.tsx
new file mode 100644
index 0000000..e0ed90a
--- /dev/null
+++ b/src/useSpeechSynthes/demos/index.tsx
@@ -0,0 +1,57 @@
+import { SpeechSynthesisTTS } from '@lobehub/tts';
+import { Icon, StoryBook, useControls, useCreateStore } from '@lobehub/ui';
+import { useSpeechSynthes } from '@yuntijs/ui';
+import { Button, Input, SelectProps } from 'antd';
+import { StopCircle, Volume2 } from 'lucide-react';
+import { Flexbox } from 'react-layout-kit';
+
+const defaultText = '这是一段使用 Speech Synthes 的语音演示';
+
+const genLevaOptions = (options: SelectProps['options']) => {
+ const data: any = {};
+ // eslint-disable-next-line unicorn/no-array-for-each
+ options?.forEach((item: any) => (data[item?.label || item?.value] = item?.value));
+ return data;
+};
+
+export default () => {
+ const store = useCreateStore();
+ const options: any = useControls(
+ {
+ pitch: {
+ max: 1,
+ min: -1,
+ step: 0.1,
+ value: 0,
+ },
+ rate: {
+ max: 1,
+ min: -1,
+ step: 0.1,
+ value: 0,
+ },
+ voice: {
+ options: genLevaOptions(new SpeechSynthesisTTS().voiceOptions),
+ value: '婷婷',
+ },
+ },
+ { store }
+ );
+ const { setText, isLoading, start, stop } = useSpeechSynthes(defaultText, options);
+ return (
+
+
+ {isLoading ? (
+ } onClick={stop}>
+ Stop
+
+ ) : (
+ } onClick={start} type={'primary'}>
+ Speak
+
+ )}
+ setText(e.target.value)} />
+
+
+ );
+};
diff --git a/src/useSpeechSynthes/index.md b/src/useSpeechSynthes/index.md
new file mode 100644
index 0000000..26427a0
--- /dev/null
+++ b/src/useSpeechSynthes/index.md
@@ -0,0 +1,9 @@
+---
+nav: Components
+group: TTS
+title: useSpeechSynthes
+---
+
+## hooks
+
+
diff --git a/src/useSpeechSynthes/index.ts b/src/useSpeechSynthes/index.ts
new file mode 100644
index 0000000..9f13966
--- /dev/null
+++ b/src/useSpeechSynthes/index.ts
@@ -0,0 +1,58 @@
+import { type SsmlOptions } from '@lobehub/tts/core/utils/genSSML';
+import { useCallback, useEffect, useMemo, useState } from 'react';
+
+import { SpeechSynthesis, SpeechSynthesisUtterance } from './const/polyfill';
+
+export interface SpeechSynthesOptions extends Pick {
+ onStart?: () => void;
+ onStop?: () => void;
+}
+export const useSpeechSynthes = (
+ defaultText: string,
+ { voice, rate, pitch, ...options }: SpeechSynthesOptions
+) => {
+ const [voiceList, setVoiceList] = useState(SpeechSynthesis?.getVoices());
+ const [text, setText] = useState(defaultText);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const speechSynthesisUtterance = useMemo(() => {
+ if (!SpeechSynthesisUtterance) return;
+ const utterance = new SpeechSynthesisUtterance(text);
+ utterance.voice = voiceList.find((item: any) => item.name === voice) as any;
+ utterance.onstart = () => setIsLoading(true);
+ utterance.onend = () => setIsLoading(false);
+ if (pitch) utterance.pitch = pitch * 10;
+ if (rate) utterance.rate = rate * 10;
+ return utterance;
+ }, [text, voiceList, rate, pitch, voice]);
+
+ useEffect(() => {
+ if (!SpeechSynthesis) return;
+
+ SpeechSynthesis.onvoiceschanged = () => {
+ setVoiceList(SpeechSynthesis?.getVoices());
+ };
+ SpeechSynthesis.onstart = () => setIsLoading(true);
+ SpeechSynthesis.onend = () => setIsLoading(false);
+ }, []);
+
+ const handleStart = useCallback(() => {
+ options?.onStart?.();
+ SpeechSynthesis?.speak(speechSynthesisUtterance);
+ }, [options, speechSynthesisUtterance]);
+
+ const handleStop = useCallback(() => {
+ options?.onStop?.();
+ speechSynthesis?.cancel();
+ setIsLoading(false);
+ }, [options]);
+
+ return {
+ isLoading,
+ setText,
+ start: handleStart,
+ stop: handleStop,
+ };
+};
+
+export default useSpeechSynthes;