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 ? ( + + ) : ( + + )} + 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;