├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── whisper-turbo.png └── workflows │ └── release_package.yml ├── .gitignore ├── LICENSE ├── README.md ├── justfile ├── package.json ├── playground ├── .eslintrc.js ├── README.md ├── next-env.d.ts ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public │ ├── chrome.png │ ├── favicon.ico │ ├── next.svg │ ├── vercel.svg │ └── whisper-turbo.png ├── src │ ├── components │ │ ├── configModal.tsx │ │ ├── controlPanel.tsx │ │ ├── gearIcon.tsx │ │ ├── languageDropdown.tsx │ │ ├── layout.tsx │ │ ├── micButton.tsx │ │ ├── modal.tsx │ │ ├── modelSelector.tsx │ │ ├── progressBar.tsx │ │ ├── suppressSelector.tsx │ │ └── taskSelector.tsx │ ├── pages │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── api │ │ │ └── hello.ts │ │ └── index.tsx │ ├── styles │ │ ├── Home.module.css │ │ └── globals.css │ └── util.ts ├── tailwind.config.js └── tsconfig.json ├── pnpm-lock.yaml ├── src ├── audio.ts ├── db │ ├── modelDB.ts │ └── types.ts ├── index.ts ├── inferenceSession.ts ├── models.ts ├── session.worker.ts └── sessionManager.ts └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "overrides": [ 11 | { 12 | "env": { 13 | "node": true 14 | }, 15 | "files": [ 16 | ".eslintrc.{js,cjs}" 17 | ], 18 | "parserOptions": { 19 | "sourceType": "script" 20 | } 21 | } 22 | ], 23 | "parser": "@typescript-eslint/parser", 24 | "parserOptions": { 25 | "ecmaVersion": "latest", 26 | "sourceType": "module" 27 | }, 28 | "plugins": [ 29 | "@typescript-eslint" 30 | ], 31 | "rules": { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: FL33TW00D 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Desktop (please complete the following information):** 21 | - OS: [Only MacOS + Windows are currently supported] 22 | - Browser [Only Chrome >=113 is supported] 23 | - Version [e.g. 22] 24 | 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/whisper-turbo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FL33TW00D/whisper-turbo/54916ad654a24b6424ca2052651dc384de7d66ed/.github/whisper-turbo.png -------------------------------------------------------------------------------- /.github/workflows/release_package.yml: -------------------------------------------------------------------------------- 1 | name: Release package 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | release-type: 6 | description: 'Release type (one of): patch, minor, major, prepatch, preminor, premajor, prerelease' 7 | required: true 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | # Checkout project repository 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | # Setup Node.js environment 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | registry-url: https://registry.npmjs.org/ 21 | node-version: '16' 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v2 25 | with: 26 | version: 8 27 | 28 | - name: Install dependencies 29 | run: pnpm install 30 | 31 | - name: Run build 32 | run: pnpm run build 33 | 34 | # Configure Git 35 | - name: Git configuration 36 | run: | 37 | git config --global user.email "fleetwoodpersonal@gmail.com" 38 | git config --global user.name "GitHub Actions" 39 | 40 | # Bump package version 41 | # Use tag latest 42 | - name: Bump release version 43 | if: startsWith(github.event.inputs.release-type, 'pre') != true 44 | run: | 45 | echo "NEW_VERSION=$(npm --no-git-tag-version version $RELEASE_TYPE)" >> $GITHUB_ENV 46 | echo "RELEASE_TAG=latest" >> $GITHUB_ENV 47 | env: 48 | RELEASE_TYPE: ${{ github.event.inputs.release-type }} 49 | 50 | # Bump package pre-release version 51 | # Use tag beta for pre-release versions 52 | - name: Bump pre-release version 53 | if: startsWith(github.event.inputs.release-type, 'pre') 54 | run: | 55 | echo "NEW_VERSION=$(npm --no-git-tag-version --preid=beta version $RELEASE_TYPE 56 | echo "RELEASE_TAG=beta" >> $GITHUB_ENV 57 | env: 58 | RELEASE_TYPE: ${{ github.event.inputs.release-type }} 59 | 60 | # Commit changes 61 | - name: Commit package.json changes and create tag 62 | run: | 63 | git add "package.json" "pnpm-lock.yaml" 64 | git commit -m "chore: release ${{ env.NEW_VERSION }}" 65 | git tag ${{ env.NEW_VERSION }} 66 | 67 | # Publish version to public repository 68 | - name: Publish 69 | run: npm publish --verbose --access public --tag ${{ env.RELEASE_TAG }} 70 | env: 71 | NODE_AUTH_TOKEN: ${{ secrets.NPMJS_RELEASE_TOKEN }} 72 | 73 | - name: Bump playground 74 | working-directory: "./playground" 75 | run: | 76 | pnpm install whisper-turbo@${{ env.NEW_VERSION }} 77 | git add "package.json" "pnpm-lock.yaml" 78 | git commit -m "chore: bump whisper-turbo" 79 | 80 | # Push repository changes 81 | - name: Push changes to repository 82 | env: 83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 84 | run: | 85 | git push origin && git push --tags 86 | 87 | # Update GitHub release with changelog 88 | - name: Update GitHub release documentation 89 | uses: softprops/action-gh-release@v1 90 | with: 91 | tag_name: ${{ env.NEW_VERSION }} 92 | body: "New release" 93 | prerelease: ${{ startsWith(github.event.inputs.release-type, 'pre') }} 94 | env: 95 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 96 | 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | *.swp 9 | 10 | pids 11 | logs 12 | results 13 | tmp 14 | 15 | # Build 16 | public/css/main.css 17 | 18 | # Coverage reports 19 | coverage 20 | 21 | # API keys and secrets 22 | .env 23 | 24 | # Dependency directory 25 | node_modules 26 | bower_components 27 | 28 | # Editors 29 | .idea 30 | *.iml 31 | 32 | # OS metadata 33 | .DS_Store 34 | Thumbs.db 35 | 36 | # Ignore built ts files 37 | dist/**/* 38 | 39 | # ignore yarn.lock 40 | yarn.lock 41 | 42 | .next 43 | **/tokenizer.json 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Demo Site | Documentation | Roadmap

4 |
5 | 6 | ## What is Whisper Turbo? 7 | 8 | Whisper Turbo is a fast, **cross-platform** Whisper implementation, designed to run entirely client-side in your browser/electron app. 9 | 10 | 11 | Check out the Rust library behind Whisper Turbo, [Ratchet](https://github.com/FL33TW00D/ratchet) 12 | 13 | ## Demo 14 | 15 | https://github.com/FL33TW00D/whisper-turbo/assets/45471420/1e19aa1f-bb56-4b5c-bc00-e79aabb4d1e0 16 | 17 | ## Supported Platforms 18 | 19 | WebGPU is only officially supported on Chromium based browsers running on Windows & MacOS. 20 | For more information, check out [Supported Platforms](https://ratchet.sh/whisper-turbo/platforms) 21 | 22 | ## Want to get involved? 23 | 24 | - Are you a GPU wizard? 25 | - Do you know what a HRTB is in Rust? 26 | - Do you know what is going on [here](https://github.com/RuyiLi/cursed-typescript/blob/master/random/game-of-life.ts)? 27 | - Reach out: chris@fleetwood.dev 28 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | push-hf: 2 | git push space `git subtree split --prefix playground/out main`:main --force 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whisper-turbo", 3 | "version": "0.11.0", 4 | "author": "Christopher Fleetwood ", 5 | "description": "GPU accelerated Whisper", 6 | "repository": "FL33TW00D/whisper-turbo", 7 | "keywords": [ 8 | "rust", 9 | "WebGPU", 10 | "ML", 11 | "Machine Learning", 12 | "AI" 13 | ], 14 | "main": "dist/index.js", 15 | "types": "dist/index.d.ts", 16 | "devDependencies": { 17 | "@types/node": "^14.18.63", 18 | "@types/uuid": "^9.0.6", 19 | "@typescript-eslint/eslint-plugin": "^6.9.1", 20 | "@typescript-eslint/parser": "^6.9.1", 21 | "eslint": "^8.52.0", 22 | "typescript": "~4.7.4" 23 | }, 24 | "scripts": { 25 | "build": "rm -rf ./dist && tsc", 26 | "format": "prettier --write \"src/**/*.ts\"", 27 | "lint": "eslint ./src" 28 | }, 29 | "dependencies": { 30 | "comlink": "4.3.1", 31 | "fix-webm-duration": "^1.0.5", 32 | "idb": "^7.1.1", 33 | "p-retry": "^5.1.2", 34 | "true-myth": "^6.2.0", 35 | "uuid": "^9.0.1", 36 | "whisper-webgpu": "0.10.0" 37 | }, 38 | "files": [ 39 | "dist/**/*" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /playground/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/eslint-recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "next/core-web-vitals", 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /playground/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 18 | 19 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 20 | 21 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 22 | 23 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 24 | 25 | ## Learn More 26 | 27 | To learn more about Next.js, take a look at the following resources: 28 | 29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 31 | 32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 33 | 34 | ## Deploy on Vercel 35 | 36 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 37 | 38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 39 | -------------------------------------------------------------------------------- /playground/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /playground/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | transpilePackages: ['whisper-turbo'], 5 | }; 6 | 7 | module.exports = nextConfig; 8 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "lint": "next lint" 9 | }, 10 | "dependencies": { 11 | "@next/font": "13.1.0", 12 | "@typescript-eslint/eslint-plugin": "^5.62.0", 13 | "next": "13.1.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-hot-toast": "^2.4.1", 17 | "react-responsive-modal": "^6.4.2", 18 | "true-myth": "^7.1.0", 19 | "whisper-turbo": "^0.11.0" 20 | }, 21 | "devDependencies": { 22 | "@tailwindcss/typography": "^0.5.10", 23 | "@types/node": "18.11.9", 24 | "@types/react": "18.0.25", 25 | "@types/react-dom": "18.0.9", 26 | "autoprefixer": "^10.4.16", 27 | "eslint": "8.28.0", 28 | "eslint-config-next": "13.0.5", 29 | "postcss": "^8.4.31", 30 | "tailwindcss": "^3.3.5", 31 | "typescript": "4.9.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /playground/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /playground/public/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FL33TW00D/whisper-turbo/54916ad654a24b6424ca2052651dc384de7d66ed/playground/public/chrome.png -------------------------------------------------------------------------------- /playground/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FL33TW00D/whisper-turbo/54916ad654a24b6424ca2052651dc384de7d66ed/playground/public/favicon.ico -------------------------------------------------------------------------------- /playground/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/public/whisper-turbo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FL33TW00D/whisper-turbo/54916ad654a24b6424ca2052651dc384de7d66ed/playground/public/whisper-turbo.png -------------------------------------------------------------------------------- /playground/src/components/configModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Modal from "react-responsive-modal"; 3 | import { Task } from "whisper-turbo"; 4 | import LanguageDropdown from "./languageDropdown"; 5 | import SuppressComponent from "./suppressSelector"; 6 | import TaskComponent from "./taskSelector"; 7 | 8 | interface ConfigModalProps { 9 | isModalOpen: boolean; 10 | setIsModalOpen: React.Dispatch>; 11 | configOptions: ConfigOptions; 12 | setConfigOptions: React.Dispatch>; 13 | } 14 | 15 | export interface ConfigOptions { 16 | language: string | null; 17 | task: Task; 18 | suppress_non_speech: boolean; 19 | } 20 | 21 | const ConfigModal = (props: ConfigModalProps) => { 22 | useEffect(() => { 23 | //@ts-ignore 24 | if (!navigator.gpu) { 25 | props.setIsModalOpen(true); 26 | return; 27 | } 28 | }, []); 29 | 30 | const handleModalClose = () => { 31 | props.setIsModalOpen(false); 32 | }; 33 | 34 | const closeIcon = ( 35 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ); 60 | 61 | return ( 62 | <> 63 | 72 |
78 |
79 | 80 | 81 | 82 |
83 |
84 |
85 | 86 | ); 87 | }; 88 | 89 | export default ConfigModal; 90 | -------------------------------------------------------------------------------- /playground/src/components/controlPanel.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useEffect } from "react"; 2 | import { 3 | AvailableModels, 4 | InferenceSession, 5 | SessionManager, 6 | Segment, 7 | DecodingOptionsBuilder, 8 | initialize, 9 | Task 10 | } from "whisper-turbo"; 11 | import toast from "react-hot-toast"; 12 | import { humanFileSize } from "../util"; 13 | import ProgressBar from "./progressBar"; 14 | import ModelSelector from "./modelSelector"; 15 | import MicButton, { AudioMetadata } from "./micButton"; 16 | import GearIcon from "./gearIcon"; 17 | import ConfigModal, { ConfigOptions } from "./configModal"; 18 | 19 | export interface Transcript { 20 | segments: Array; 21 | } 22 | 23 | interface ControlPanelProps { 24 | transcript: Transcript; 25 | setTranscript: React.Dispatch>; 26 | setDownloadAvailable: React.Dispatch>; 27 | } 28 | 29 | const ControlPanel = (props: ControlPanelProps) => { 30 | const session = useRef(null); 31 | const [selectedModel, setSelectedModel] = useState( 32 | null 33 | ); 34 | const [modelLoading, setModelLoading] = useState(false); 35 | const [loadedModel, setLoadedModel] = useState( 36 | null 37 | ); 38 | const [audioData, setAudioData] = useState(null); 39 | const [audioMetadata, setAudioMetadata] = useState( 40 | null 41 | ); 42 | const [blobUrl, setBlobUrl] = useState(null); 43 | const [loaded, setLoaded] = useState(false); 44 | const [progress, setProgress] = useState(0); 45 | const [transcribing, setTranscribing] = useState(false); 46 | const [isConfigOpen, setIsConfigOpen] = useState(false); 47 | const [configOptions, setConfigOptions] = useState({ 48 | language: null, 49 | task: Task.Transcribe, 50 | suppress_non_speech: true, 51 | }); 52 | 53 | useEffect(() => { 54 | if (loadedModel && selectedModel != loadedModel && !transcribing) { 55 | setLoaded(false); 56 | setProgress(0); 57 | } 58 | }, [selectedModel]); 59 | 60 | const handleAudioFile = () => async (event: any) => { 61 | const file = event.target.files[0]; 62 | if (!file) { 63 | return; 64 | } 65 | const reader = new FileReader(); 66 | reader.onload = () => { 67 | setAudioData(new Uint8Array(reader.result as ArrayBuffer)); 68 | setAudioMetadata({ 69 | file: file, 70 | fromMic: false, 71 | }); 72 | setBlobUrl(URL.createObjectURL(file)); 73 | }; 74 | reader.readAsArrayBuffer(file); 75 | }; 76 | 77 | const loadModel = async () => { 78 | if (session.current) { 79 | session.current.destroy(); 80 | } 81 | if (modelLoading) { 82 | return; 83 | } 84 | if (!selectedModel) { 85 | console.error("No model selected"); 86 | return; 87 | } 88 | setModelLoading(true); 89 | 90 | const manager = new SessionManager(); 91 | const loadResult = await manager.loadModel( 92 | selectedModel, 93 | () => { 94 | setLoaded(true); 95 | setLoadedModel(selectedModel); 96 | }, 97 | (p: number) => setProgress(p) 98 | ); 99 | if (loadResult.isErr) { 100 | toast.error(loadResult.error.message); 101 | } else { 102 | setModelLoading(false); 103 | session.current = loadResult.value; 104 | } 105 | }; 106 | 107 | const runSession = async () => { 108 | if (!session.current) { 109 | toast.error("No model loaded"); 110 | return; 111 | } 112 | if (!audioData) { 113 | toast.error("No audio file loaded"); 114 | return; 115 | } 116 | props.setTranscript((transcript: Transcript) => { 117 | return { 118 | ...transcript, 119 | segments: [], 120 | }; 121 | }); 122 | setTranscribing(true); 123 | await initialize(); 124 | let builder = new DecodingOptionsBuilder(); 125 | if (configOptions.language) 126 | builder = builder.setLanguage(configOptions.language); 127 | if (configOptions.suppress_non_speech) 128 | builder = builder.setSuppressTokens(Int32Array.from([-1])); 129 | else 130 | builder = builder.setSuppressTokens(Int32Array.from([])); 131 | 132 | builder = builder.setTask(configOptions.task); 133 | const options = builder.build(); 134 | console.log("Options: ", options); 135 | 136 | await session.current.transcribe( 137 | audioData!, 138 | audioMetadata!.fromMic, 139 | options, 140 | (s: Segment) => { 141 | console.log(s); 142 | if (s.last) { 143 | setTranscribing(false); 144 | props.setDownloadAvailable(true); 145 | return; 146 | } 147 | props.setTranscript((transcript: Transcript) => { 148 | return { 149 | ...transcript, 150 | segments: [...transcript.segments, s], 151 | }; 152 | }); 153 | } 154 | ); 155 | }; 156 | 157 | return ( 158 | <> 159 | 165 |
166 |
167 | 171 | window.open( 172 | "https://github.com/FL33TW00D/whisper-turbo", 173 | "_blank" 174 | ) 175 | } 176 | /> 177 |
178 |
179 | 185 | 186 | {selectedModel != loadedModel && progress == 0 && ( 187 |
188 | 194 |
195 | )} 196 |
197 |
198 |
199 | 202 | 221 | 229 |
230 | 235 |
236 | {blobUrl && ( 237 |
238 | 241 | 255 |
256 | )} 257 |
258 | 259 |
260 | 273 | 274 | 280 |
281 |
282 |
283 |

284 | Built by{" "} 285 | 289 | @fleetwood 290 | 291 |

292 |
293 |
294 | 295 | ); 296 | }; 297 | 298 | export default ControlPanel; 299 | -------------------------------------------------------------------------------- /playground/src/components/gearIcon.tsx: -------------------------------------------------------------------------------- 1 | const GearIcon = () => { 2 | return ( 3 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 49 | 56 | 57 | 58 | 65 | 72 | 79 | 86 | 93 | 100 | 107 | 114 | 121 | 128 | 135 | 136 | 137 | 138 | ); 139 | }; 140 | 141 | export default GearIcon; 142 | -------------------------------------------------------------------------------- /playground/src/components/languageDropdown.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { ConfigOptions } from "./configModal"; 3 | 4 | const AvailableLanguages = { 5 | en: "English", 6 | zh: "Chinese", 7 | de: "German", 8 | es: "Spanish", 9 | ru: "Russian", 10 | ko: "Korean", 11 | fr: "French", 12 | ja: "Japanese", 13 | pt: "Portuguese", 14 | tr: "Turkish", 15 | pl: "Polish", 16 | ca: "Catalan", 17 | nl: "Dutch", 18 | ar: "Arabic", 19 | sv: "Swedish", 20 | it: "Italian", 21 | id: "Indonesian", 22 | hi: "Hindi", 23 | fi: "Finnish", 24 | vi: "Vietnamese", 25 | he: "Hebrew", 26 | uk: "Ukrainian", 27 | el: "Greek", 28 | ms: "Malay", 29 | cs: "Czech", 30 | ro: "Romanian", 31 | da: "Danish", 32 | hu: "Hungarian", 33 | ta: "Tamil", 34 | no: "Norwegian", 35 | th: "Thai", 36 | ur: "Urdu", 37 | hr: "Croatian", 38 | bg: "Bulgarian", 39 | lt: "Lithuanian", 40 | la: "Latin", 41 | mi: "Maori", 42 | ml: "Malayalam", 43 | cy: "Welsh", 44 | sk: "Slovak", 45 | te: "Telugu", 46 | fa: "Persian", 47 | lv: "Latvian", 48 | bn: "Bengali", 49 | sr: "Serbian", 50 | az: "Azerbaijani", 51 | sl: "Slovenian", 52 | kn: "Kannada", 53 | et: "Estonian", 54 | mk: "Macedonian", 55 | br: "Breton", 56 | eu: "Basque", 57 | is: "Icelandic", 58 | hy: "Armenian", 59 | ne: "Nepali", 60 | mn: "Mongolian", 61 | bs: "Bosnian", 62 | kk: "Kazakh", 63 | sq: "Albanian", 64 | sw: "Swahili", 65 | gl: "Galician", 66 | mr: "Marathi", 67 | pa: "Punjabi", 68 | si: "Sinhala", 69 | km: "Khmer", 70 | sn: "Shona", 71 | yo: "Yoruba", 72 | so: "Somali", 73 | af: "Afrikaans", 74 | oc: "Occitan", 75 | ka: "Georgian", 76 | be: "Belarusian", 77 | tg: "Tajik", 78 | sd: "Sindhi", 79 | gu: "Gujarati", 80 | am: "Amharic", 81 | yi: "Yiddish", 82 | lo: "Lao", 83 | uz: "Uzbek", 84 | fo: "Faroese", 85 | ht: "Haitian creole", 86 | ps: "Pashto", 87 | tk: "Turkmen", 88 | nn: "Nynorsk", 89 | mt: "Maltese", 90 | sa: "Sanskrit", 91 | lb: "Luxembourgish", 92 | my: "Myanmar", 93 | bo: "Tibetan", 94 | tl: "Tagalog", 95 | mg: "Malagasy", 96 | as: "Assamese", 97 | tt: "Tatar", 98 | haw: "Hawaiian", 99 | ln: "Lingala", 100 | ha: "Hausa", 101 | ba: "Bashkir", 102 | jw: "Javanese", 103 | su: "Sundanese", 104 | yue: "Cantonese", 105 | }; 106 | 107 | interface LanguageDropdownProps { 108 | configOptions: ConfigOptions; 109 | setConfigOptions: React.Dispatch>; 110 | } 111 | 112 | const LanguageDropdown = (props: LanguageDropdownProps) => { 113 | const [open, setOpen] = useState(false); 114 | const [selectedLanguage, setSelectedLanguage] = useState( 115 | props.configOptions.language 116 | ); 117 | 118 | const toggleOpen = () => setOpen((prev) => !prev); 119 | 120 | const selectLanguage = (lang: string) => { 121 | props.setConfigOptions((prev: ConfigOptions) => ({ 122 | ...prev, 123 | language: lang, 124 | })); 125 | setSelectedLanguage(lang); 126 | setOpen(false); 127 | }; 128 | 129 | return ( 130 |
131 |
132 | 138 | 159 |
160 | 161 | {open && ( 162 |
163 |
169 | {Object.entries(AvailableLanguages).map( 170 | ([lang, name]) => ( 171 | selectLanguage(lang)} 177 | > 178 | {name} 179 | 180 | ) 181 | )} 182 |
183 |
184 | )} 185 |
186 | ); 187 | }; 188 | 189 | export default LanguageDropdown; 190 | -------------------------------------------------------------------------------- /playground/src/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { Toaster } from "react-hot-toast"; 3 | import React from "react"; 4 | 5 | export const siteTitle = "Whisper Turbo"; 6 | 7 | type LayoutProps = { 8 | children: React.ReactNode; 9 | title: string; 10 | }; 11 | 12 | export default function Layout(props: LayoutProps) { 13 | return ( 14 |
22 | 23 | {props.title} 24 | 25 | 26 | 30 | 31 |
32 | 33 |
{props.children}
34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /playground/src/components/micButton.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { MicRecorder } from "whisper-turbo"; 3 | 4 | const SAMPLE_RATE = 16000; 5 | 6 | interface MicButtonProps { 7 | setBlobUrl: (blobUrl: string) => void; 8 | setAudioData: (audioData: Uint8Array) => void; 9 | setAudioMetadata: (audioMetadata: AudioMetadata) => void; 10 | } 11 | 12 | export interface AudioMetadata { 13 | file: File; 14 | fromMic: boolean; 15 | } 16 | 17 | const MicButton = (props: MicButtonProps) => { 18 | const [mic, setMic] = useState(null); 19 | const [isRecording, setIsRecording] = useState(false); 20 | 21 | const handleRecord = async () => { 22 | setMic(await MicRecorder.start()); 23 | }; 24 | 25 | const handleStop = async () => { 26 | if (!mic) { 27 | return; 28 | } 29 | let recording = await mic.stop(); 30 | let ctx = new AudioContext({ sampleRate: SAMPLE_RATE }); 31 | let resampled = await ctx.decodeAudioData(recording.buffer); 32 | let ch0 = resampled.getChannelData(0); 33 | props.setAudioData(new Uint8Array(ch0.buffer)); 34 | 35 | let blob = recording.blob; 36 | props.setAudioMetadata({ 37 | file: new File([blob], "recording.wav"), 38 | fromMic: true, 39 | }); 40 | props.setBlobUrl(URL.createObjectURL(blob)); 41 | setMic(null); 42 | }; 43 | 44 | const handleClick = async () => { 45 | if (isRecording) { 46 | await handleStop(); 47 | } else { 48 | await handleRecord(); 49 | } 50 | setIsRecording(!isRecording); 51 | }; 52 | 53 | return ( 54 |
55 | 56 | 90 |
91 | ); 92 | }; 93 | 94 | export default MicButton; 95 | -------------------------------------------------------------------------------- /playground/src/components/modal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Modal from "react-responsive-modal"; 3 | 4 | const WebGPUModal = () => { 5 | const [hasWebGPU, setHasWebGPU] = useState(false); 6 | const [isModalOpen, setIsModalOpen] = useState(true); 7 | 8 | useEffect(() => { 9 | //@ts-ignore 10 | if (!navigator.gpu) { 11 | setIsModalOpen(true); 12 | return; 13 | } 14 | setHasWebGPU(true); 15 | }, []); 16 | 17 | const handleModalClose = () => { 18 | setIsModalOpen(false); 19 | }; 20 | 21 | const closeIcon = ( 22 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ); 47 | 48 | return ( 49 | <> 50 | {!hasWebGPU ? ( 51 | 60 |
66 |
67 |

68 | Uh oh! It looks like your browser doesn't 69 | support WebGPU. Please try again in a different 70 | browser. 71 |

72 |
73 |
74 |
75 | ) : ( 76 | <> 77 | )} 78 | 79 | ); 80 | }; 81 | 82 | export default WebGPUModal; 83 | -------------------------------------------------------------------------------- /playground/src/components/modelSelector.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { AvailableModels, ModelSizes } from "whisper-turbo"; 3 | import { humanFileSize } from "../util"; 4 | 5 | interface ModelSelectorProps { 6 | selectedModel: AvailableModels | null; 7 | setSelectedModel: (model: AvailableModels) => void; 8 | loaded: boolean; 9 | progress: number; 10 | } 11 | 12 | const ModelSelector = (props: ModelSelectorProps) => { 13 | const { selectedModel, setSelectedModel, loaded, progress } = props; 14 | 15 | const [dropdownOpen, setDropdownOpen] = useState(false); 16 | 17 | const displayModels = () => { 18 | const models = Object.values(AvailableModels).slice(0, -1); 19 | const sizes = Array.from(ModelSizes.values()).slice(0, -1); 20 | const zipped = models.map((model, i) => [model, sizes[i]]); 21 | return zipped.map((model, idx) => ( 22 |
  • 23 | { 28 | setSelectedModel(model[0] as AvailableModels); 29 | setDropdownOpen(false); 30 | }} 31 | > 32 | {fmtModel(model[0] as AvailableModels)}{" "} 33 | {humanFileSize(model[1] as number)} 34 | 35 |
  • 36 | )); 37 | }; 38 | 39 | const fmtModel = (model: AvailableModels) => { 40 | let name = model as string; 41 | name = name.charAt(0).toUpperCase() + name.slice(1); 42 | return name; 43 | }; 44 | 45 | return ( 46 | <> 47 |
    48 | 51 | {progress > 0 && !loaded && ( 52 | 55 | )} 56 |
    57 |
    58 | 75 |
      81 | {displayModels()} 82 |
    83 |
    84 | 85 | ); 86 | }; 87 | 88 | export default ModelSelector; 89 | -------------------------------------------------------------------------------- /playground/src/components/progressBar.tsx: -------------------------------------------------------------------------------- 1 | const ProgressBar = ({ progress, loaded }: any) => { 2 | return ( 3 | <> 4 | {progress > 0 && progress < 100 && !loaded && ( 5 |
    6 |
    7 |
    11 |
    12 |
    13 | )} 14 | 15 | ); 16 | }; 17 | 18 | export default ProgressBar; 19 | -------------------------------------------------------------------------------- /playground/src/components/suppressSelector.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { ConfigOptions } from "./configModal"; 3 | 4 | interface SuppressComponentProps { 5 | configOptions: ConfigOptions; 6 | setConfigOptions: React.Dispatch>; 7 | } 8 | 9 | const SuppressComponent = (props: SuppressComponentProps) => { 10 | const [checkedState, setCheckedState] = useState({ 11 | suppress_non_speech: props.configOptions.suppress_non_speech 12 | }); 13 | 14 | const handleOnChange = (event: React.ChangeEvent) => { 15 | setCheckedState({ 16 | ...checkedState, 17 | [event.target.name]: event.target.checked 18 | }); 19 | 20 | props.setConfigOptions({ 21 | ...props.configOptions, 22 | suppress_non_speech: event.target.checked 23 | }); 24 | }; 25 | 26 | return ( 27 |
    28 | 29 |
    30 |
    31 | 34 | 42 |
    43 |
    44 |
    45 | ); 46 | }; 47 | 48 | export default SuppressComponent; 49 | -------------------------------------------------------------------------------- /playground/src/components/taskSelector.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { ConfigOptions } from "./configModal"; 3 | import { Task } from "whisper-turbo"; 4 | 5 | interface TaskComponentProps { 6 | configOptions: ConfigOptions; 7 | setConfigOptions: React.Dispatch>; 8 | } 9 | 10 | const TaskComponent = (props: TaskComponentProps) => { 11 | let state = { 12 | translate: props.configOptions.task === Task.Translate, 13 | transcribe: props.configOptions.task === Task.Transcribe, 14 | }; 15 | 16 | const [checkedState, setCheckedState] = useState(state); 17 | 18 | const handleOnChange = (event: React.ChangeEvent) => { 19 | setCheckedState({ 20 | ...checkedState, 21 | [event.target.name]: event.target.checked, 22 | }); 23 | if (event.target.name === "translate") 24 | setCheckedState({ 25 | translate: event.target.checked, 26 | transcribe: !event.target.checked, 27 | }); 28 | if (event.target.name === "transcribe") 29 | setCheckedState({ 30 | translate: !event.target.checked, 31 | transcribe: event.target.checked, 32 | }); 33 | props.setConfigOptions((prev: ConfigOptions) => ({ 34 | ...prev, 35 | task: 36 | event.target.name === "translate" 37 | ? Task.Translate 38 | : Task.Transcribe, 39 | })); 40 | }; 41 | 42 | return ( 43 |
    44 | 45 |
    46 |
    47 | 50 | 58 |
    59 | 60 |
    61 | 64 | 72 |
    73 |
    74 |
    75 | ); 76 | }; 77 | 78 | export default TaskComponent; 79 | -------------------------------------------------------------------------------- /playground/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | import type { AppProps } from 'next/app' 3 | import "react-responsive-modal/styles.css"; 4 | 5 | export default function App({ Component, pageProps }: AppProps) { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /playground/src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
    9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /playground/src/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /playground/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import { VT323 } from "@next/font/google"; 3 | import { useState } from "react"; 4 | import Layout from "../components/layout"; 5 | import WebGPUModal from "../components/modal"; 6 | import { Segment } from "whisper-turbo"; 7 | import ControlPanel, { Transcript } from "../components/controlPanel"; 8 | 9 | const vt = VT323({ weight: "400", display: "swap" }); 10 | 11 | const Home: NextPage = () => { 12 | const [transcript, setTranscript] = useState({ 13 | segments: [], 14 | }); 15 | const [downloadAvailable, setDownloadAvailable] = useState(false); 16 | 17 | const handleDownload = () => { 18 | const jsonData = JSON.stringify(transcript); 19 | const blob = new Blob([jsonData], { type: "application/json" }); 20 | const url = URL.createObjectURL(blob); 21 | 22 | const link = document.createElement("a"); 23 | link.download = "transcript.json"; 24 | link.href = url; 25 | 26 | link.click(); 27 | link.remove(); 28 | }; 29 | 30 | return ( 31 | 32 |
    33 |
    34 | 39 |
    40 |
    41 |
    42 | {transcript && 43 | transcript.segments.map( 44 | (segment: Segment) => { 45 | return ( 46 |
    50 |
    53 |
    54 | {segment.start} 55 |
    56 |
    57 | {segment.text} 58 |
    59 |
    60 | {segment.stop} 61 |
    62 |
    63 |
    64 | ); 65 | } 66 | )} 67 | {downloadAvailable ? ( 68 |
    69 | 75 |
    76 | ) : ( 77 | <> 78 | )} 79 |
    80 |
    81 |
    82 |
    83 |
    84 | 85 |
    86 | ); 87 | }; 88 | 89 | export default Home; 90 | -------------------------------------------------------------------------------- /playground/src/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .title a { 16 | color: #0070f3; 17 | text-decoration: none; 18 | } 19 | 20 | .title a:hover, 21 | .title a:focus, 22 | .title a:active { 23 | text-decoration: underline; 24 | } 25 | 26 | .title { 27 | margin: 0; 28 | line-height: 1.15; 29 | font-size: 4rem; 30 | text-align: center; 31 | } 32 | 33 | .example { 34 | margin: 4rem 0; 35 | line-height: 1.5; 36 | font-size: 1.5rem; 37 | text-align: center; 38 | } 39 | 40 | @media (max-width: 600px) { 41 | .title { 42 | font-size: 3rem; 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /playground/src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | padding: 0; 8 | margin: 0; 9 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 10 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 11 | } 12 | 13 | a { 14 | color: inherit; 15 | text-decoration: none; 16 | } 17 | 18 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | :root { 23 | --foreground-rgb: 0, 0, 0; 24 | --background-start-rgb: 214, 219, 220; 25 | --background-end-rgb: 255, 255, 255; 26 | } 27 | 28 | @media (prefers-color-scheme: dark) { 29 | :root { 30 | --foreground-rgb: 255, 255, 255; 31 | --background-start-rgb: 0, 0, 0; 32 | --background-end-rgb: 0, 0, 0; 33 | } 34 | } 35 | 36 | body { 37 | color: rgb(var(--foreground-rgb)); 38 | background: linear-gradient( 39 | to bottom, 40 | transparent, 41 | rgb(var(--background-end-rgb)) 42 | ) 43 | rgb(var(--background-start-rgb)); 44 | } 45 | 46 | audio::-webkit-media-controls-current-time-display { 47 | font-family: "__VT323_2a9463"; 48 | font-size: 1.2rem; 49 | } 50 | 51 | audio::-webkit-media-controls-time-remaining-display { 52 | font-family: "__VT323_2a9463"; 53 | font-size: 1.2rem; 54 | } 55 | 56 | audio::-webkit-media-controls-enclosure { 57 | border-radius: 0; 58 | border: 2px solid black; 59 | } 60 | 61 | .loader { 62 | animation: spin 1s linear infinite; 63 | height: 10px; 64 | width: 10px; 65 | margin: -5px; 66 | scale: 0.5; 67 | } 68 | 69 | @keyframes spin { 70 | 0% { 71 | box-shadow: 72 | 0px -30px #fff, 73 | 10px -30px #fff, 74 | 20px -20px #fff, 75 | 30px -10px #fff, 76 | 30px 0px #fff, 77 | 30px 10px #fff, 78 | 20px 20px #fff, 79 | 10px 30px #fff, 80 | 0px 30px transparent, 81 | -10px 30px transparent, 82 | -20px 20px transparent, 83 | -30px 10px transparent, 84 | -30px 0px transparent, 85 | -30px -10px transparent, 86 | -20px -20px transparent, 87 | -10px -30px transparent; 88 | } 89 | 6.25% { 90 | box-shadow: 91 | 0px -30px transparent, 92 | 10px -30px #fff, 93 | 20px -20px #fff, 94 | 30px -10px #fff, 95 | 30px 0px #fff, 96 | 30px 10px #fff, 97 | 20px 20px #fff, 98 | 10px 30px #fff, 99 | 0px 30px #fff, 100 | -10px 30px transparent, 101 | -20px 20px transparent, 102 | -30px 10px transparent, 103 | -30px 0px transparent, 104 | -30px -10px transparent, 105 | -20px -20px transparent, 106 | -10px -30px transparent; 107 | } 108 | 12.5% { 109 | box-shadow: 110 | 0px -30px transparent, 111 | 10px -30px transparent, 112 | 20px -20px #fff, 113 | 30px -10px #fff, 114 | 30px 0px #fff, 115 | 30px 10px #fff, 116 | 20px 20px #fff, 117 | 10px 30px #fff, 118 | 0px 30px #fff, 119 | -10px 30px #fff, 120 | -20px 20px transparent, 121 | -30px 10px transparent, 122 | -30px 0px transparent, 123 | -30px -10px transparent, 124 | -20px -20px transparent, 125 | -10px -30px transparent; 126 | } 127 | 18.75% { 128 | box-shadow: 129 | 0px -30px transparent, 130 | 10px -30px transparent, 131 | 20px -20px transparent, 132 | 30px -10px #fff, 133 | 30px 0px #fff, 134 | 30px 10px #fff, 135 | 20px 20px #fff, 136 | 10px 30px #fff, 137 | 0px 30px #fff, 138 | -10px 30px #fff, 139 | -20px 20px #fff, 140 | -30px 10px transparent, 141 | -30px 0px transparent, 142 | -30px -10px transparent, 143 | -20px -20px transparent, 144 | -10px -30px transparent; 145 | } 146 | 25% { 147 | box-shadow: 148 | 0px -30px transparent, 149 | 10px -30px transparent, 150 | 20px -20px transparent, 151 | 30px -10px transparent, 152 | 30px 0px #fff, 153 | 30px 10px #fff, 154 | 20px 20px #fff, 155 | 10px 30px #fff, 156 | 0px 30px #fff, 157 | -10px 30px #fff, 158 | -20px 20px #fff, 159 | -30px 10px #fff, 160 | -30px 0px transparent, 161 | -30px -10px transparent, 162 | -20px -20px transparent, 163 | -10px -30px transparent; 164 | } 165 | 31.25% { 166 | box-shadow: 167 | 0px -30px transparent, 168 | 10px -30px transparent, 169 | 20px -20px transparent, 170 | 30px -10px transparent, 171 | 30px 0px transparent, 172 | 30px 10px #fff, 173 | 20px 20px #fff, 174 | 10px 30px #fff, 175 | 0px 30px #fff, 176 | -10px 30px #fff, 177 | -20px 20px #fff, 178 | -30px 10px #fff, 179 | -30px 0px #fff, 180 | -30px -10px transparent, 181 | -20px -20px transparent, 182 | -10px -30px transparent; 183 | } 184 | 37.5% { 185 | box-shadow: 186 | 0px -30px transparent, 187 | 10px -30px transparent, 188 | 20px -20px transparent, 189 | 30px -10px transparent, 190 | 30px 0px transparent, 191 | 30px 10px transparent, 192 | 20px 20px #fff, 193 | 10px 30px #fff, 194 | 0px 30px #fff, 195 | -10px 30px #fff, 196 | -20px 20px #fff, 197 | -30px 10px #fff, 198 | -30px 0px #fff, 199 | -30px -10px #fff, 200 | -20px -20px transparent, 201 | -10px -30px transparent; 202 | } 203 | 43.75% { 204 | box-shadow: 205 | 0px -30px transparent, 206 | 10px -30px transparent, 207 | 20px -20px transparent, 208 | 30px -10px transparent, 209 | 30px 0px transparent, 210 | 30px 10px transparent, 211 | 20px 20px transparent, 212 | 10px 30px #fff, 213 | 0px 30px #fff, 214 | -10px 30px #fff, 215 | -20px 20px #fff, 216 | -30px 10px #fff, 217 | -30px 0px #fff, 218 | -30px -10px #fff, 219 | -20px -20px #fff, 220 | -10px -30px transparent; 221 | } 222 | 50% { 223 | box-shadow: 224 | 0px -30px transparent, 225 | 10px -30px transparent, 226 | 20px -20px transparent, 227 | 30px -10px transparent, 228 | 30px 0px transparent, 229 | 30px 10px transparent, 230 | 20px 20px transparent, 231 | 10px 30px transparent, 232 | 0px 30px #fff, 233 | -10px 30px #fff, 234 | -20px 20px #fff, 235 | -30px 10px #fff, 236 | -30px 0px #fff, 237 | -30px -10px #fff, 238 | -20px -20px #fff, 239 | -10px -30px #fff; 240 | } 241 | 56.25% { 242 | box-shadow: 243 | 0px -30px #fff, 244 | 10px -30px transparent, 245 | 20px -20px transparent, 246 | 30px -10px transparent, 247 | 30px 0px transparent, 248 | 30px 10px transparent, 249 | 20px 20px transparent, 250 | 10px 30px transparent, 251 | 0px 30px transparent, 252 | -10px 30px #fff, 253 | -20px 20px #fff, 254 | -30px 10px #fff, 255 | -30px 0px #fff, 256 | -30px -10px #fff, 257 | -20px -20px #fff, 258 | -10px -30px #fff; 259 | } 260 | 62.5% { 261 | box-shadow: 262 | 0px -30px #fff, 263 | 10px -30px #fff, 264 | 20px -20px transparent, 265 | 30px -10px transparent, 266 | 30px 0px transparent, 267 | 30px 10px transparent, 268 | 20px 20px transparent, 269 | 10px 30px transparent, 270 | 0px 30px transparent, 271 | -10px 30px transparent, 272 | -20px 20px #fff, 273 | -30px 10px #fff, 274 | -30px 0px #fff, 275 | -30px -10px #fff, 276 | -20px -20px #fff, 277 | -10px -30px #fff; 278 | } 279 | 68.75% { 280 | box-shadow: 281 | 0px -30px #fff, 282 | 10px -30px #fff, 283 | 20px -20px #fff, 284 | 30px -10px transparent, 285 | 30px 0px transparent, 286 | 30px 10px transparent, 287 | 20px 20px transparent, 288 | 10px 30px transparent, 289 | 0px 30px transparent, 290 | -10px 30px transparent, 291 | -20px 20px transparent, 292 | -30px 10px #fff, 293 | -30px 0px #fff, 294 | -30px -10px #fff, 295 | -20px -20px #fff, 296 | -10px -30px #fff; 297 | } 298 | 75% { 299 | box-shadow: 300 | 0px -30px #fff, 301 | 10px -30px #fff, 302 | 20px -20px #fff, 303 | 30px -10px #fff, 304 | 30px 0px transparent, 305 | 30px 10px transparent, 306 | 20px 20px transparent, 307 | 10px 30px transparent, 308 | 0px 30px transparent, 309 | -10px 30px transparent, 310 | -20px 20px transparent, 311 | -30px 10px transparent, 312 | -30px 0px #fff, 313 | -30px -10px #fff, 314 | -20px -20px #fff, 315 | -10px -30px #fff; 316 | } 317 | 81.25% { 318 | box-shadow: 319 | 0px -30px #fff, 320 | 10px -30px #fff, 321 | 20px -20px #fff, 322 | 30px -10px #fff, 323 | 30px 0px #fff, 324 | 30px 10px transparent, 325 | 20px 20px transparent, 326 | 10px 30px transparent, 327 | 0px 30px transparent, 328 | -10px 30px transparent, 329 | -20px 20px transparent, 330 | -30px 10px transparent, 331 | -30px 0px transparent, 332 | -30px -10px #fff, 333 | -20px -20px #fff, 334 | -10px -30px #fff; 335 | } 336 | 87.5% { 337 | box-shadow: 338 | 0px -30px #fff, 339 | 10px -30px #fff, 340 | 20px -20px #fff, 341 | 30px -10px #fff, 342 | 30px 0px #fff, 343 | 30px 10px #fff, 344 | 20px 20px transparent, 345 | 10px 30px transparent, 346 | 0px 30px transparent, 347 | -10px 30px transparent, 348 | -20px 20px transparent, 349 | -30px 10px transparent, 350 | -30px 0px transparent, 351 | -30px -10px transparent, 352 | -20px -20px #fff, 353 | -10px -30px #fff; 354 | } 355 | 93.75% { 356 | box-shadow: 357 | 0px -30px #fff, 358 | 10px -30px #fff, 359 | 20px -20px #fff, 360 | 30px -10px #fff, 361 | 30px 0px #fff, 362 | 30px 10px #fff, 363 | 20px 20px #fff, 364 | 10px 30px transparent, 365 | 0px 30px transparent, 366 | -10px 30px transparent, 367 | -20px 20px transparent, 368 | -30px 10px transparent, 369 | -30px 0px transparent, 370 | -30px -10px transparent, 371 | -20px -20px transparent, 372 | -10px -30px #fff; 373 | } 374 | 100% { 375 | box-shadow: 376 | 0px -30px #fff, 377 | 10px -30px #fff, 378 | 20px -20px #fff, 379 | 30px -10px #fff, 380 | 30px 0px #fff, 381 | 30px 10px #fff, 382 | 20px 20px #fff, 383 | 10px 30px #fff, 384 | 0px 30px transparent, 385 | -10px 30px transparent, 386 | -20px 20px transparent, 387 | -30px 10px transparent, 388 | -30px 0px transparent, 389 | -30px -10px transparent, 390 | -20px -20px transparent, 391 | -10px -30px transparent; 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /playground/src/util.ts: -------------------------------------------------------------------------------- 1 | const UNITS = [ 2 | "byte", 3 | "kilobyte", 4 | "megabyte", 5 | "gigabyte", 6 | "terabyte", 7 | "petabyte", 8 | ]; 9 | const BYTES_PER_KB = 1000; 10 | 11 | /** 12 | * Format bytes as human-readable text. 13 | * 14 | * @param sizeBytes Number of bytes. 15 | * 16 | * @return Formatted string. 17 | */ 18 | export function humanFileSize(sizeBytes: number | bigint): string { 19 | let size = Math.abs(Number(sizeBytes)); 20 | 21 | let u = 0; 22 | while (size >= BYTES_PER_KB && u < UNITS.length - 1) { 23 | size /= BYTES_PER_KB; 24 | ++u; 25 | } 26 | 27 | return new Intl.NumberFormat([], { 28 | style: "unit", 29 | unit: UNITS[u], 30 | unitDisplay: "short", 31 | maximumFractionDigits: 1, 32 | }).format(size); 33 | } 34 | -------------------------------------------------------------------------------- /playground/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: { 6 | colors: { 7 | dark: "#131414", 8 | "pop-orange": "#f93c26", 9 | "pop-orange-dark": "#cc1905", 10 | }, 11 | }, 12 | }, 13 | plugins: [require("@tailwindcss/typography")], 14 | }; 15 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | comlink: 9 | specifier: 4.3.1 10 | version: 4.3.1 11 | fix-webm-duration: 12 | specifier: ^1.0.5 13 | version: 1.0.5 14 | idb: 15 | specifier: ^7.1.1 16 | version: 7.1.1 17 | p-retry: 18 | specifier: ^5.1.2 19 | version: 5.1.2 20 | true-myth: 21 | specifier: ^6.2.0 22 | version: 6.2.0 23 | uuid: 24 | specifier: ^9.0.1 25 | version: 9.0.1 26 | whisper-webgpu: 27 | specifier: 0.10.0 28 | version: 0.10.0 29 | 30 | devDependencies: 31 | '@types/node': 32 | specifier: ^14.18.63 33 | version: 14.18.63 34 | '@types/uuid': 35 | specifier: ^9.0.6 36 | version: 9.0.7 37 | '@typescript-eslint/eslint-plugin': 38 | specifier: ^6.9.1 39 | version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.54.0)(typescript@4.7.4) 40 | '@typescript-eslint/parser': 41 | specifier: ^6.9.1 42 | version: 6.11.0(eslint@8.54.0)(typescript@4.7.4) 43 | eslint: 44 | specifier: ^8.52.0 45 | version: 8.54.0 46 | typescript: 47 | specifier: ~4.7.4 48 | version: 4.7.4 49 | 50 | packages: 51 | 52 | /@aashutoshrathi/word-wrap@1.2.6: 53 | resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 54 | engines: {node: '>=0.10.0'} 55 | dev: true 56 | 57 | /@eslint-community/eslint-utils@4.4.0(eslint@8.54.0): 58 | resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} 59 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 60 | peerDependencies: 61 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 62 | dependencies: 63 | eslint: 8.54.0 64 | eslint-visitor-keys: 3.4.3 65 | dev: true 66 | 67 | /@eslint-community/regexpp@4.10.0: 68 | resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} 69 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 70 | dev: true 71 | 72 | /@eslint/eslintrc@2.1.3: 73 | resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==} 74 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 75 | dependencies: 76 | ajv: 6.12.6 77 | debug: 4.3.4 78 | espree: 9.6.1 79 | globals: 13.23.0 80 | ignore: 5.3.0 81 | import-fresh: 3.3.0 82 | js-yaml: 4.1.0 83 | minimatch: 3.1.2 84 | strip-json-comments: 3.1.1 85 | transitivePeerDependencies: 86 | - supports-color 87 | dev: true 88 | 89 | /@eslint/js@8.54.0: 90 | resolution: {integrity: sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==} 91 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 92 | dev: true 93 | 94 | /@humanwhocodes/config-array@0.11.13: 95 | resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} 96 | engines: {node: '>=10.10.0'} 97 | dependencies: 98 | '@humanwhocodes/object-schema': 2.0.1 99 | debug: 4.3.4 100 | minimatch: 3.1.2 101 | transitivePeerDependencies: 102 | - supports-color 103 | dev: true 104 | 105 | /@humanwhocodes/module-importer@1.0.1: 106 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 107 | engines: {node: '>=12.22'} 108 | dev: true 109 | 110 | /@humanwhocodes/object-schema@2.0.1: 111 | resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} 112 | dev: true 113 | 114 | /@nodelib/fs.scandir@2.1.5: 115 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 116 | engines: {node: '>= 8'} 117 | dependencies: 118 | '@nodelib/fs.stat': 2.0.5 119 | run-parallel: 1.2.0 120 | dev: true 121 | 122 | /@nodelib/fs.stat@2.0.5: 123 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 124 | engines: {node: '>= 8'} 125 | dev: true 126 | 127 | /@nodelib/fs.walk@1.2.8: 128 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 129 | engines: {node: '>= 8'} 130 | dependencies: 131 | '@nodelib/fs.scandir': 2.1.5 132 | fastq: 1.15.0 133 | dev: true 134 | 135 | /@types/json-schema@7.0.15: 136 | resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 137 | dev: true 138 | 139 | /@types/node@14.18.63: 140 | resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} 141 | dev: true 142 | 143 | /@types/retry@0.12.1: 144 | resolution: {integrity: sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==} 145 | dev: false 146 | 147 | /@types/semver@7.5.5: 148 | resolution: {integrity: sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==} 149 | dev: true 150 | 151 | /@types/uuid@9.0.7: 152 | resolution: {integrity: sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==} 153 | dev: true 154 | 155 | /@typescript-eslint/eslint-plugin@6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.54.0)(typescript@4.7.4): 156 | resolution: {integrity: sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==} 157 | engines: {node: ^16.0.0 || >=18.0.0} 158 | peerDependencies: 159 | '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha 160 | eslint: ^7.0.0 || ^8.0.0 161 | typescript: '*' 162 | peerDependenciesMeta: 163 | typescript: 164 | optional: true 165 | dependencies: 166 | '@eslint-community/regexpp': 4.10.0 167 | '@typescript-eslint/parser': 6.11.0(eslint@8.54.0)(typescript@4.7.4) 168 | '@typescript-eslint/scope-manager': 6.11.0 169 | '@typescript-eslint/type-utils': 6.11.0(eslint@8.54.0)(typescript@4.7.4) 170 | '@typescript-eslint/utils': 6.11.0(eslint@8.54.0)(typescript@4.7.4) 171 | '@typescript-eslint/visitor-keys': 6.11.0 172 | debug: 4.3.4 173 | eslint: 8.54.0 174 | graphemer: 1.4.0 175 | ignore: 5.3.0 176 | natural-compare: 1.4.0 177 | semver: 7.5.4 178 | ts-api-utils: 1.0.3(typescript@4.7.4) 179 | typescript: 4.7.4 180 | transitivePeerDependencies: 181 | - supports-color 182 | dev: true 183 | 184 | /@typescript-eslint/parser@6.11.0(eslint@8.54.0)(typescript@4.7.4): 185 | resolution: {integrity: sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==} 186 | engines: {node: ^16.0.0 || >=18.0.0} 187 | peerDependencies: 188 | eslint: ^7.0.0 || ^8.0.0 189 | typescript: '*' 190 | peerDependenciesMeta: 191 | typescript: 192 | optional: true 193 | dependencies: 194 | '@typescript-eslint/scope-manager': 6.11.0 195 | '@typescript-eslint/types': 6.11.0 196 | '@typescript-eslint/typescript-estree': 6.11.0(typescript@4.7.4) 197 | '@typescript-eslint/visitor-keys': 6.11.0 198 | debug: 4.3.4 199 | eslint: 8.54.0 200 | typescript: 4.7.4 201 | transitivePeerDependencies: 202 | - supports-color 203 | dev: true 204 | 205 | /@typescript-eslint/scope-manager@6.11.0: 206 | resolution: {integrity: sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==} 207 | engines: {node: ^16.0.0 || >=18.0.0} 208 | dependencies: 209 | '@typescript-eslint/types': 6.11.0 210 | '@typescript-eslint/visitor-keys': 6.11.0 211 | dev: true 212 | 213 | /@typescript-eslint/type-utils@6.11.0(eslint@8.54.0)(typescript@4.7.4): 214 | resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==} 215 | engines: {node: ^16.0.0 || >=18.0.0} 216 | peerDependencies: 217 | eslint: ^7.0.0 || ^8.0.0 218 | typescript: '*' 219 | peerDependenciesMeta: 220 | typescript: 221 | optional: true 222 | dependencies: 223 | '@typescript-eslint/typescript-estree': 6.11.0(typescript@4.7.4) 224 | '@typescript-eslint/utils': 6.11.0(eslint@8.54.0)(typescript@4.7.4) 225 | debug: 4.3.4 226 | eslint: 8.54.0 227 | ts-api-utils: 1.0.3(typescript@4.7.4) 228 | typescript: 4.7.4 229 | transitivePeerDependencies: 230 | - supports-color 231 | dev: true 232 | 233 | /@typescript-eslint/types@6.11.0: 234 | resolution: {integrity: sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==} 235 | engines: {node: ^16.0.0 || >=18.0.0} 236 | dev: true 237 | 238 | /@typescript-eslint/typescript-estree@6.11.0(typescript@4.7.4): 239 | resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==} 240 | engines: {node: ^16.0.0 || >=18.0.0} 241 | peerDependencies: 242 | typescript: '*' 243 | peerDependenciesMeta: 244 | typescript: 245 | optional: true 246 | dependencies: 247 | '@typescript-eslint/types': 6.11.0 248 | '@typescript-eslint/visitor-keys': 6.11.0 249 | debug: 4.3.4 250 | globby: 11.1.0 251 | is-glob: 4.0.3 252 | semver: 7.5.4 253 | ts-api-utils: 1.0.3(typescript@4.7.4) 254 | typescript: 4.7.4 255 | transitivePeerDependencies: 256 | - supports-color 257 | dev: true 258 | 259 | /@typescript-eslint/utils@6.11.0(eslint@8.54.0)(typescript@4.7.4): 260 | resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==} 261 | engines: {node: ^16.0.0 || >=18.0.0} 262 | peerDependencies: 263 | eslint: ^7.0.0 || ^8.0.0 264 | dependencies: 265 | '@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0) 266 | '@types/json-schema': 7.0.15 267 | '@types/semver': 7.5.5 268 | '@typescript-eslint/scope-manager': 6.11.0 269 | '@typescript-eslint/types': 6.11.0 270 | '@typescript-eslint/typescript-estree': 6.11.0(typescript@4.7.4) 271 | eslint: 8.54.0 272 | semver: 7.5.4 273 | transitivePeerDependencies: 274 | - supports-color 275 | - typescript 276 | dev: true 277 | 278 | /@typescript-eslint/visitor-keys@6.11.0: 279 | resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==} 280 | engines: {node: ^16.0.0 || >=18.0.0} 281 | dependencies: 282 | '@typescript-eslint/types': 6.11.0 283 | eslint-visitor-keys: 3.4.3 284 | dev: true 285 | 286 | /@ungap/structured-clone@1.2.0: 287 | resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} 288 | dev: true 289 | 290 | /acorn-jsx@5.3.2(acorn@8.11.2): 291 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 292 | peerDependencies: 293 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 294 | dependencies: 295 | acorn: 8.11.2 296 | dev: true 297 | 298 | /acorn@8.11.2: 299 | resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} 300 | engines: {node: '>=0.4.0'} 301 | hasBin: true 302 | dev: true 303 | 304 | /ajv@6.12.6: 305 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 306 | dependencies: 307 | fast-deep-equal: 3.1.3 308 | fast-json-stable-stringify: 2.1.0 309 | json-schema-traverse: 0.4.1 310 | uri-js: 4.4.1 311 | dev: true 312 | 313 | /ansi-regex@5.0.1: 314 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 315 | engines: {node: '>=8'} 316 | dev: true 317 | 318 | /ansi-styles@4.3.0: 319 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 320 | engines: {node: '>=8'} 321 | dependencies: 322 | color-convert: 2.0.1 323 | dev: true 324 | 325 | /argparse@2.0.1: 326 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 327 | dev: true 328 | 329 | /array-union@2.1.0: 330 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 331 | engines: {node: '>=8'} 332 | dev: true 333 | 334 | /balanced-match@1.0.2: 335 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 336 | dev: true 337 | 338 | /brace-expansion@1.1.11: 339 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 340 | dependencies: 341 | balanced-match: 1.0.2 342 | concat-map: 0.0.1 343 | dev: true 344 | 345 | /braces@3.0.2: 346 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 347 | engines: {node: '>=8'} 348 | dependencies: 349 | fill-range: 7.0.1 350 | dev: true 351 | 352 | /callsites@3.1.0: 353 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 354 | engines: {node: '>=6'} 355 | dev: true 356 | 357 | /chalk@4.1.2: 358 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 359 | engines: {node: '>=10'} 360 | dependencies: 361 | ansi-styles: 4.3.0 362 | supports-color: 7.2.0 363 | dev: true 364 | 365 | /color-convert@2.0.1: 366 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 367 | engines: {node: '>=7.0.0'} 368 | dependencies: 369 | color-name: 1.1.4 370 | dev: true 371 | 372 | /color-name@1.1.4: 373 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 374 | dev: true 375 | 376 | /comlink@4.3.1: 377 | resolution: {integrity: sha512-+YbhUdNrpBZggBAHWcgQMLPLH1KDF3wJpeqrCKieWQ8RL7atmgsgTQko1XEBK6PsecfopWNntopJ+ByYG1lRaA==} 378 | dev: false 379 | 380 | /concat-map@0.0.1: 381 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 382 | dev: true 383 | 384 | /cross-spawn@7.0.3: 385 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 386 | engines: {node: '>= 8'} 387 | dependencies: 388 | path-key: 3.1.1 389 | shebang-command: 2.0.0 390 | which: 2.0.2 391 | dev: true 392 | 393 | /debug@4.3.4: 394 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 395 | engines: {node: '>=6.0'} 396 | peerDependencies: 397 | supports-color: '*' 398 | peerDependenciesMeta: 399 | supports-color: 400 | optional: true 401 | dependencies: 402 | ms: 2.1.2 403 | dev: true 404 | 405 | /deep-is@0.1.4: 406 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 407 | dev: true 408 | 409 | /dir-glob@3.0.1: 410 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 411 | engines: {node: '>=8'} 412 | dependencies: 413 | path-type: 4.0.0 414 | dev: true 415 | 416 | /doctrine@3.0.0: 417 | resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 418 | engines: {node: '>=6.0.0'} 419 | dependencies: 420 | esutils: 2.0.3 421 | dev: true 422 | 423 | /escape-string-regexp@4.0.0: 424 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 425 | engines: {node: '>=10'} 426 | dev: true 427 | 428 | /eslint-scope@7.2.2: 429 | resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 430 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 431 | dependencies: 432 | esrecurse: 4.3.0 433 | estraverse: 5.3.0 434 | dev: true 435 | 436 | /eslint-visitor-keys@3.4.3: 437 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 438 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 439 | dev: true 440 | 441 | /eslint@8.54.0: 442 | resolution: {integrity: sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==} 443 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 444 | hasBin: true 445 | dependencies: 446 | '@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0) 447 | '@eslint-community/regexpp': 4.10.0 448 | '@eslint/eslintrc': 2.1.3 449 | '@eslint/js': 8.54.0 450 | '@humanwhocodes/config-array': 0.11.13 451 | '@humanwhocodes/module-importer': 1.0.1 452 | '@nodelib/fs.walk': 1.2.8 453 | '@ungap/structured-clone': 1.2.0 454 | ajv: 6.12.6 455 | chalk: 4.1.2 456 | cross-spawn: 7.0.3 457 | debug: 4.3.4 458 | doctrine: 3.0.0 459 | escape-string-regexp: 4.0.0 460 | eslint-scope: 7.2.2 461 | eslint-visitor-keys: 3.4.3 462 | espree: 9.6.1 463 | esquery: 1.5.0 464 | esutils: 2.0.3 465 | fast-deep-equal: 3.1.3 466 | file-entry-cache: 6.0.1 467 | find-up: 5.0.0 468 | glob-parent: 6.0.2 469 | globals: 13.23.0 470 | graphemer: 1.4.0 471 | ignore: 5.3.0 472 | imurmurhash: 0.1.4 473 | is-glob: 4.0.3 474 | is-path-inside: 3.0.3 475 | js-yaml: 4.1.0 476 | json-stable-stringify-without-jsonify: 1.0.1 477 | levn: 0.4.1 478 | lodash.merge: 4.6.2 479 | minimatch: 3.1.2 480 | natural-compare: 1.4.0 481 | optionator: 0.9.3 482 | strip-ansi: 6.0.1 483 | text-table: 0.2.0 484 | transitivePeerDependencies: 485 | - supports-color 486 | dev: true 487 | 488 | /espree@9.6.1: 489 | resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 490 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 491 | dependencies: 492 | acorn: 8.11.2 493 | acorn-jsx: 5.3.2(acorn@8.11.2) 494 | eslint-visitor-keys: 3.4.3 495 | dev: true 496 | 497 | /esquery@1.5.0: 498 | resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} 499 | engines: {node: '>=0.10'} 500 | dependencies: 501 | estraverse: 5.3.0 502 | dev: true 503 | 504 | /esrecurse@4.3.0: 505 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 506 | engines: {node: '>=4.0'} 507 | dependencies: 508 | estraverse: 5.3.0 509 | dev: true 510 | 511 | /estraverse@5.3.0: 512 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 513 | engines: {node: '>=4.0'} 514 | dev: true 515 | 516 | /esutils@2.0.3: 517 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 518 | engines: {node: '>=0.10.0'} 519 | dev: true 520 | 521 | /fast-deep-equal@3.1.3: 522 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 523 | dev: true 524 | 525 | /fast-glob@3.3.2: 526 | resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} 527 | engines: {node: '>=8.6.0'} 528 | dependencies: 529 | '@nodelib/fs.stat': 2.0.5 530 | '@nodelib/fs.walk': 1.2.8 531 | glob-parent: 5.1.2 532 | merge2: 1.4.1 533 | micromatch: 4.0.5 534 | dev: true 535 | 536 | /fast-json-stable-stringify@2.1.0: 537 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 538 | dev: true 539 | 540 | /fast-levenshtein@2.0.6: 541 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 542 | dev: true 543 | 544 | /fastq@1.15.0: 545 | resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 546 | dependencies: 547 | reusify: 1.0.4 548 | dev: true 549 | 550 | /file-entry-cache@6.0.1: 551 | resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 552 | engines: {node: ^10.12.0 || >=12.0.0} 553 | dependencies: 554 | flat-cache: 3.2.0 555 | dev: true 556 | 557 | /fill-range@7.0.1: 558 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 559 | engines: {node: '>=8'} 560 | dependencies: 561 | to-regex-range: 5.0.1 562 | dev: true 563 | 564 | /find-up@5.0.0: 565 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 566 | engines: {node: '>=10'} 567 | dependencies: 568 | locate-path: 6.0.0 569 | path-exists: 4.0.0 570 | dev: true 571 | 572 | /fix-webm-duration@1.0.5: 573 | resolution: {integrity: sha512-b6oula3OfSknx0aWoLsxvp4DVIYbwsf+UAkr6EDAK3iuMYk/OSNKzmeSI61GXK0MmFTEuzle19BPvTxMIKjkZg==} 574 | dev: false 575 | 576 | /flat-cache@3.2.0: 577 | resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} 578 | engines: {node: ^10.12.0 || >=12.0.0} 579 | dependencies: 580 | flatted: 3.2.9 581 | keyv: 4.5.4 582 | rimraf: 3.0.2 583 | dev: true 584 | 585 | /flatted@3.2.9: 586 | resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} 587 | dev: true 588 | 589 | /fs.realpath@1.0.0: 590 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 591 | dev: true 592 | 593 | /glob-parent@5.1.2: 594 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 595 | engines: {node: '>= 6'} 596 | dependencies: 597 | is-glob: 4.0.3 598 | dev: true 599 | 600 | /glob-parent@6.0.2: 601 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 602 | engines: {node: '>=10.13.0'} 603 | dependencies: 604 | is-glob: 4.0.3 605 | dev: true 606 | 607 | /glob@7.2.3: 608 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 609 | dependencies: 610 | fs.realpath: 1.0.0 611 | inflight: 1.0.6 612 | inherits: 2.0.4 613 | minimatch: 3.1.2 614 | once: 1.4.0 615 | path-is-absolute: 1.0.1 616 | dev: true 617 | 618 | /globals@13.23.0: 619 | resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} 620 | engines: {node: '>=8'} 621 | dependencies: 622 | type-fest: 0.20.2 623 | dev: true 624 | 625 | /globby@11.1.0: 626 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 627 | engines: {node: '>=10'} 628 | dependencies: 629 | array-union: 2.1.0 630 | dir-glob: 3.0.1 631 | fast-glob: 3.3.2 632 | ignore: 5.3.0 633 | merge2: 1.4.1 634 | slash: 3.0.0 635 | dev: true 636 | 637 | /graphemer@1.4.0: 638 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 639 | dev: true 640 | 641 | /has-flag@4.0.0: 642 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 643 | engines: {node: '>=8'} 644 | dev: true 645 | 646 | /idb@7.1.1: 647 | resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} 648 | dev: false 649 | 650 | /ignore@5.3.0: 651 | resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} 652 | engines: {node: '>= 4'} 653 | dev: true 654 | 655 | /import-fresh@3.3.0: 656 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 657 | engines: {node: '>=6'} 658 | dependencies: 659 | parent-module: 1.0.1 660 | resolve-from: 4.0.0 661 | dev: true 662 | 663 | /imurmurhash@0.1.4: 664 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 665 | engines: {node: '>=0.8.19'} 666 | dev: true 667 | 668 | /inflight@1.0.6: 669 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 670 | dependencies: 671 | once: 1.4.0 672 | wrappy: 1.0.2 673 | dev: true 674 | 675 | /inherits@2.0.4: 676 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 677 | dev: true 678 | 679 | /is-extglob@2.1.1: 680 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 681 | engines: {node: '>=0.10.0'} 682 | dev: true 683 | 684 | /is-glob@4.0.3: 685 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 686 | engines: {node: '>=0.10.0'} 687 | dependencies: 688 | is-extglob: 2.1.1 689 | dev: true 690 | 691 | /is-number@7.0.0: 692 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 693 | engines: {node: '>=0.12.0'} 694 | dev: true 695 | 696 | /is-path-inside@3.0.3: 697 | resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 698 | engines: {node: '>=8'} 699 | dev: true 700 | 701 | /isexe@2.0.0: 702 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 703 | dev: true 704 | 705 | /js-yaml@4.1.0: 706 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 707 | hasBin: true 708 | dependencies: 709 | argparse: 2.0.1 710 | dev: true 711 | 712 | /json-buffer@3.0.1: 713 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 714 | dev: true 715 | 716 | /json-schema-traverse@0.4.1: 717 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 718 | dev: true 719 | 720 | /json-stable-stringify-without-jsonify@1.0.1: 721 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 722 | dev: true 723 | 724 | /keyv@4.5.4: 725 | resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 726 | dependencies: 727 | json-buffer: 3.0.1 728 | dev: true 729 | 730 | /levn@0.4.1: 731 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 732 | engines: {node: '>= 0.8.0'} 733 | dependencies: 734 | prelude-ls: 1.2.1 735 | type-check: 0.4.0 736 | dev: true 737 | 738 | /locate-path@6.0.0: 739 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 740 | engines: {node: '>=10'} 741 | dependencies: 742 | p-locate: 5.0.0 743 | dev: true 744 | 745 | /lodash.merge@4.6.2: 746 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 747 | dev: true 748 | 749 | /lru-cache@6.0.0: 750 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 751 | engines: {node: '>=10'} 752 | dependencies: 753 | yallist: 4.0.0 754 | dev: true 755 | 756 | /merge2@1.4.1: 757 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 758 | engines: {node: '>= 8'} 759 | dev: true 760 | 761 | /micromatch@4.0.5: 762 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 763 | engines: {node: '>=8.6'} 764 | dependencies: 765 | braces: 3.0.2 766 | picomatch: 2.3.1 767 | dev: true 768 | 769 | /minimatch@3.1.2: 770 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 771 | dependencies: 772 | brace-expansion: 1.1.11 773 | dev: true 774 | 775 | /ms@2.1.2: 776 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 777 | dev: true 778 | 779 | /natural-compare@1.4.0: 780 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 781 | dev: true 782 | 783 | /once@1.4.0: 784 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 785 | dependencies: 786 | wrappy: 1.0.2 787 | dev: true 788 | 789 | /optionator@0.9.3: 790 | resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 791 | engines: {node: '>= 0.8.0'} 792 | dependencies: 793 | '@aashutoshrathi/word-wrap': 1.2.6 794 | deep-is: 0.1.4 795 | fast-levenshtein: 2.0.6 796 | levn: 0.4.1 797 | prelude-ls: 1.2.1 798 | type-check: 0.4.0 799 | dev: true 800 | 801 | /p-limit@3.1.0: 802 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 803 | engines: {node: '>=10'} 804 | dependencies: 805 | yocto-queue: 0.1.0 806 | dev: true 807 | 808 | /p-locate@5.0.0: 809 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 810 | engines: {node: '>=10'} 811 | dependencies: 812 | p-limit: 3.1.0 813 | dev: true 814 | 815 | /p-retry@5.1.2: 816 | resolution: {integrity: sha512-couX95waDu98NfNZV+i/iLt+fdVxmI7CbrrdC2uDWfPdUAApyxT4wmDlyOtR5KtTDmkDO0zDScDjDou9YHhd9g==} 817 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 818 | dependencies: 819 | '@types/retry': 0.12.1 820 | retry: 0.13.1 821 | dev: false 822 | 823 | /parent-module@1.0.1: 824 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 825 | engines: {node: '>=6'} 826 | dependencies: 827 | callsites: 3.1.0 828 | dev: true 829 | 830 | /path-exists@4.0.0: 831 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 832 | engines: {node: '>=8'} 833 | dev: true 834 | 835 | /path-is-absolute@1.0.1: 836 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 837 | engines: {node: '>=0.10.0'} 838 | dev: true 839 | 840 | /path-key@3.1.1: 841 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 842 | engines: {node: '>=8'} 843 | dev: true 844 | 845 | /path-type@4.0.0: 846 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 847 | engines: {node: '>=8'} 848 | dev: true 849 | 850 | /picomatch@2.3.1: 851 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 852 | engines: {node: '>=8.6'} 853 | dev: true 854 | 855 | /prelude-ls@1.2.1: 856 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 857 | engines: {node: '>= 0.8.0'} 858 | dev: true 859 | 860 | /punycode@2.3.1: 861 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 862 | engines: {node: '>=6'} 863 | dev: true 864 | 865 | /queue-microtask@1.2.3: 866 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 867 | dev: true 868 | 869 | /resolve-from@4.0.0: 870 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 871 | engines: {node: '>=4'} 872 | dev: true 873 | 874 | /retry@0.13.1: 875 | resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} 876 | engines: {node: '>= 4'} 877 | dev: false 878 | 879 | /reusify@1.0.4: 880 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 881 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 882 | dev: true 883 | 884 | /rimraf@3.0.2: 885 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 886 | hasBin: true 887 | dependencies: 888 | glob: 7.2.3 889 | dev: true 890 | 891 | /run-parallel@1.2.0: 892 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 893 | dependencies: 894 | queue-microtask: 1.2.3 895 | dev: true 896 | 897 | /semver@7.5.4: 898 | resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 899 | engines: {node: '>=10'} 900 | hasBin: true 901 | dependencies: 902 | lru-cache: 6.0.0 903 | dev: true 904 | 905 | /shebang-command@2.0.0: 906 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 907 | engines: {node: '>=8'} 908 | dependencies: 909 | shebang-regex: 3.0.0 910 | dev: true 911 | 912 | /shebang-regex@3.0.0: 913 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 914 | engines: {node: '>=8'} 915 | dev: true 916 | 917 | /slash@3.0.0: 918 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 919 | engines: {node: '>=8'} 920 | dev: true 921 | 922 | /strip-ansi@6.0.1: 923 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 924 | engines: {node: '>=8'} 925 | dependencies: 926 | ansi-regex: 5.0.1 927 | dev: true 928 | 929 | /strip-json-comments@3.1.1: 930 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 931 | engines: {node: '>=8'} 932 | dev: true 933 | 934 | /supports-color@7.2.0: 935 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 936 | engines: {node: '>=8'} 937 | dependencies: 938 | has-flag: 4.0.0 939 | dev: true 940 | 941 | /text-table@0.2.0: 942 | resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 943 | dev: true 944 | 945 | /to-regex-range@5.0.1: 946 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 947 | engines: {node: '>=8.0'} 948 | dependencies: 949 | is-number: 7.0.0 950 | dev: true 951 | 952 | /true-myth@6.2.0: 953 | resolution: {integrity: sha512-NYvzj/h2mGXmdIBmz825c/lQhpI4bzUQEEiBCAbNOVpr6aeYa1WTpJ+OmGmj1yPqbTLPKCCSi54yDnaEup504Q==} 954 | engines: {node: 14.* || 16.* || >= 18.*} 955 | dev: false 956 | 957 | /ts-api-utils@1.0.3(typescript@4.7.4): 958 | resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} 959 | engines: {node: '>=16.13.0'} 960 | peerDependencies: 961 | typescript: '>=4.2.0' 962 | dependencies: 963 | typescript: 4.7.4 964 | dev: true 965 | 966 | /type-check@0.4.0: 967 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 968 | engines: {node: '>= 0.8.0'} 969 | dependencies: 970 | prelude-ls: 1.2.1 971 | dev: true 972 | 973 | /type-fest@0.20.2: 974 | resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 975 | engines: {node: '>=10'} 976 | dev: true 977 | 978 | /typescript@4.7.4: 979 | resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} 980 | engines: {node: '>=4.2.0'} 981 | hasBin: true 982 | dev: true 983 | 984 | /uri-js@4.4.1: 985 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 986 | dependencies: 987 | punycode: 2.3.1 988 | dev: true 989 | 990 | /uuid@9.0.1: 991 | resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} 992 | hasBin: true 993 | dev: false 994 | 995 | /which@2.0.2: 996 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 997 | engines: {node: '>= 8'} 998 | hasBin: true 999 | dependencies: 1000 | isexe: 2.0.0 1001 | dev: true 1002 | 1003 | /whisper-webgpu@0.10.0: 1004 | resolution: {integrity: sha512-zrv72GKvL9Ui5VF4vDiUI1CnNffgBrTfDGEnCa3qKDF7TqJ5FdcLRoW8KWWMN5GaKDRPloqk2+xJFQfJaXRI8Q==} 1005 | dev: false 1006 | 1007 | /wrappy@1.0.2: 1008 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1009 | dev: true 1010 | 1011 | /yallist@4.0.0: 1012 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1013 | dev: true 1014 | 1015 | /yocto-queue@0.1.0: 1016 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1017 | engines: {node: '>=10'} 1018 | dev: true 1019 | -------------------------------------------------------------------------------- /src/audio.ts: -------------------------------------------------------------------------------- 1 | import fixWebmDuration from "fix-webm-duration"; 2 | 3 | export interface Recording { 4 | blob: Blob; 5 | buffer: ArrayBuffer; 6 | } 7 | 8 | export class MicRecorder { 9 | private currentStart: number | null = null; 10 | private currentStream: MediaStream | null = null; 11 | private inner: MediaRecorder | null = null; 12 | private audioChunks: Blob[] = []; 13 | private static readonly supportedMimes = [ 14 | "audio/webm", // Chrome 15 | "audio/ogg", // Firefox 16 | ]; 17 | 18 | private constructor(recorder: MediaRecorder) { 19 | this.inner = recorder; 20 | } 21 | 22 | public static async start(): Promise { 23 | if (!navigator.mediaDevices) { 24 | throw new Error("Media device not available"); 25 | } 26 | 27 | const stream = await navigator.mediaDevices.getUserMedia({ 28 | audio: true, 29 | }); 30 | const inner = new MediaRecorder(stream, { 31 | mimeType: MicRecorder.supportedMimes.find((mime: string) => 32 | MediaRecorder.isTypeSupported(mime) 33 | ), 34 | }); 35 | const recorder = new MicRecorder(inner); 36 | recorder.currentStream = stream; 37 | 38 | inner.addEventListener("dataavailable", (event) => { 39 | recorder.audioChunks.push(event.data); 40 | }); 41 | inner.start(); 42 | recorder.currentStart = Date.now(); 43 | return recorder; 44 | } 45 | 46 | public isRecording(): boolean { 47 | return this.inner !== null && this.inner.state === "recording"; 48 | } 49 | 50 | public async stop(): Promise { 51 | if (!this.inner) { 52 | throw new Error("Please start the recorder first"); 53 | } 54 | 55 | const promise: Promise = new Promise( 56 | (resolve) => { 57 | this.inner!.addEventListener("stop", async () => { 58 | const duration = Date.now() - this.currentStart!; 59 | let blob = new Blob(this.audioChunks, { 60 | type: this.inner!.mimeType, 61 | }); 62 | 63 | if (this.inner!.mimeType.includes("webm")) { 64 | blob = await fixWebmDuration(blob, duration, { 65 | logger: false, 66 | }); 67 | } 68 | 69 | const buffer = await blob.arrayBuffer(); 70 | 71 | resolve({ 72 | blob, 73 | buffer, 74 | }); 75 | }); 76 | this.inner!.stop(); 77 | this.currentStream!.getTracks().forEach((track) => 78 | track.stop() 79 | ); 80 | } 81 | ); 82 | return promise; 83 | } 84 | } 85 | 86 | export default MicRecorder; 87 | -------------------------------------------------------------------------------- /src/db/modelDB.ts: -------------------------------------------------------------------------------- 1 | import { DBSchema, IDBPDatabase, openDB } from "idb/with-async-ittr"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | import { DBModel, DBTokenizer } from "./types"; 4 | import { AvailableModels } from "../models"; 5 | import { Result } from "true-myth"; 6 | import pRetry from "p-retry"; 7 | 8 | interface ModelDBSchema extends DBSchema { 9 | models: { 10 | value: DBModel; 11 | key: string; 12 | indexes: { modelID: string }; 13 | }; 14 | availableModels: { 15 | value: string; 16 | key: AvailableModels; 17 | }; 18 | tokenizer: { 19 | value: DBTokenizer; 20 | key: string; 21 | indexes: { modelID: string }; 22 | }; 23 | } 24 | 25 | /** 26 | * A class that represents a database of models and related data. 27 | * 28 | * @remarks 29 | * The `ModelDB` class uses the IndexedDB API to store and retrieve data. The database schema is defined by the `ModelDBSchema` interface. 30 | * 31 | * To use the `ModelDB` class, first create an instance by calling the constructor. Then call the `init` method to open the database. 32 | * 33 | * Example usage: 34 | * 35 | * ```typescript 36 | * ``` 37 | */ 38 | export default class ModelDB { 39 | private readonly remoteUrl = "https://rmbl.us"; 40 | private db: IDBPDatabase | null; 41 | 42 | private constructor(db: IDBPDatabase) { 43 | this.db = db; 44 | } 45 | 46 | public static async create(): Promise { 47 | const db = await openDB("models", 1, { 48 | upgrade(db) { 49 | const modelStore = db.createObjectStore("models"); 50 | modelStore.createIndex("modelID", "modelID"); 51 | db.createObjectStore("availableModels"); 52 | const tokenizerStore = db.createObjectStore("tokenizer"); 53 | tokenizerStore.createIndex("modelID", "modelID"); 54 | }, 55 | }); 56 | 57 | return new ModelDB(db); 58 | } 59 | 60 | private async fetchBytes( 61 | url: string, 62 | onProgress?: (progress: number) => void 63 | ): Promise> { 64 | const run = async () => { 65 | const response = await fetch(url); 66 | if (!response.ok) { 67 | return Result.err( 68 | new Error(`Fetch failed: ${response.status}`) 69 | ); 70 | } 71 | const contentLength = +response.headers.get("Content-Length")!; 72 | 73 | const reader = response.body!.getReader(); 74 | let receivedLength = 0; 75 | const chunks: Uint8Array = new Uint8Array(contentLength); 76 | for (;;) { 77 | const { done, value } = await reader.read(); 78 | 79 | if (done) { 80 | break; 81 | } 82 | 83 | chunks.set(value, receivedLength); 84 | receivedLength += value.length; 85 | if (onProgress) { 86 | onProgress((receivedLength / contentLength) * 100); 87 | } 88 | } 89 | return Result.ok(chunks); 90 | }; 91 | return await pRetry(run, { retries: 3 }); 92 | } 93 | 94 | async _getModel(modelID: string): Promise> { 95 | if (!this.db) { 96 | return Result.err(new Error("ModelDB not initialized")); 97 | } 98 | 99 | const tx = this.db.transaction("models", "readonly"); 100 | const store = tx.objectStore("models"); 101 | const model = await store.get(modelID); 102 | 103 | if (!model) { 104 | return Result.err(new Error("Model not found")); 105 | } 106 | return Result.ok(model); 107 | } 108 | 109 | async getTokenizer(modelID: string): Promise> { 110 | if (!this.db) { 111 | return Result.err(new Error("ModelDB not initialized")); 112 | } 113 | 114 | let tokenizer = await this.db.getFromIndex( 115 | "tokenizer", 116 | "modelID", 117 | modelID 118 | ); 119 | 120 | if (!tokenizer) { 121 | const tokenizerBytes = await this.fetchBytes( 122 | "https://huggingface.co/openai/whisper-large-v2/raw/main/tokenizer.json" 123 | ); 124 | if (tokenizerBytes.isErr) { 125 | return Result.err(tokenizerBytes.error); 126 | } 127 | const tokenizerBytesValue = tokenizerBytes.value; 128 | tokenizer = { 129 | modelID, 130 | bytes: tokenizerBytesValue, 131 | }; 132 | this.db.put("tokenizer", tokenizer, modelID); 133 | tokenizer = await this.db.getFromIndex( 134 | "tokenizer", 135 | "modelID", 136 | modelID 137 | ); 138 | } 139 | 140 | return Result.ok(tokenizer!); 141 | } 142 | 143 | async getModel( 144 | model: AvailableModels, 145 | onProgress: (progress: number) => void 146 | ): Promise> { 147 | if (!this.db) { 148 | return Result.err(new Error("ModelDB not initialized")); 149 | } 150 | let modelID = await this.db.get("availableModels", model); 151 | if (!modelID) { 152 | await this.fetchRemote(model, onProgress); 153 | modelID = await this.db.get("availableModels", model); 154 | } 155 | return await this._getModel(modelID!); 156 | } 157 | 158 | async fetchRemote( 159 | model: AvailableModels, 160 | onProgress: (progress: number) => void 161 | ): Promise> { 162 | const remoteURL = `${this.remoteUrl}/whisper-turbo/${model}-q8g16.bin`; 163 | const fetchResult = await this.fetchBytes(remoteURL, onProgress); 164 | 165 | if (fetchResult.isErr) { 166 | return Result.err(fetchResult.error); 167 | } 168 | const data = fetchResult.value; 169 | 170 | const modelID = uuidv4(); 171 | this.db!.put("availableModels", modelID, model); 172 | const dbModel = { name: model, ID: modelID, bytes: data }; 173 | this.db!.put("models", dbModel, modelID); 174 | this.getTokenizer(modelID); 175 | 176 | return Result.ok(undefined); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/db/types.ts: -------------------------------------------------------------------------------- 1 | export interface DBModel { 2 | name: string; 3 | ID: string; 4 | bytes: Uint8Array; 5 | } 6 | 7 | export interface DBTokenizer { 8 | bytes: Uint8Array; 9 | modelID: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { InferenceSession } from "./inferenceSession"; 2 | export { AvailableModels, ModelSizes } from "./models"; 3 | export { SessionManager } from "./sessionManager"; 4 | export { MicRecorder } from "./audio"; 5 | export { DecodingOptionsBuilder, Segment, default as initialize, Task } from "whisper-webgpu"; 6 | -------------------------------------------------------------------------------- /src/inferenceSession.ts: -------------------------------------------------------------------------------- 1 | import { Session } from "./session.worker"; 2 | import * as Comlink from "comlink"; 3 | import { Result } from "true-myth"; 4 | import { AvailableModels } from "./models"; 5 | import { Segment } from "whisper-webgpu"; 6 | 7 | //User facing API 8 | export class InferenceSession { 9 | private session: Comlink.Remote | Session | null; 10 | private innerWorker: Worker | null; //Keep a reference to the worker so we can terminate it 11 | 12 | constructor(session: Comlink.Remote | Session, worker?: Worker) { 13 | this.session = session; 14 | this.innerWorker = worker || null; 15 | } 16 | 17 | async initSession( 18 | selectedModel: AvailableModels, 19 | onProgress: (progress: number) => void 20 | ): Promise> { 21 | return await this.session!.initSession(selectedModel, onProgress); 22 | } 23 | 24 | public async transcribe( 25 | audio: Uint8Array, 26 | raw_audio: boolean, 27 | options: any 28 | ): Promise>; 29 | 30 | public async transcribe( 31 | audio: Uint8Array, 32 | raw_audio: boolean, 33 | options: any, 34 | callback: (decoded: Segment) => void 35 | ): Promise>; 36 | 37 | async transcribe( 38 | audio: Uint8Array, 39 | raw_audio: boolean, 40 | options: any, 41 | callback?: (decoded: Segment) => void 42 | ): Promise> { 43 | if (this.session == null) { 44 | return Result.err(new Error("Session not initialized")); 45 | } 46 | 47 | if (callback) { 48 | if (this.session instanceof Session) { 49 | return await this.session.stream( 50 | audio, 51 | raw_audio, 52 | options, 53 | callback 54 | ); 55 | } else { 56 | return await this.session!.stream( 57 | audio, 58 | raw_audio, 59 | options, 60 | Comlink.proxy(callback) 61 | ); 62 | } 63 | } else { 64 | return await this.session!.run(audio, options); 65 | } 66 | } 67 | 68 | public destroy(): void { 69 | if (this.innerWorker !== null) { 70 | console.warn("Terminating worker"); 71 | this.innerWorker.terminate(); 72 | } 73 | this.session = null; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/models.ts: -------------------------------------------------------------------------------- 1 | import { Result } from "true-myth"; 2 | import ModelDB from "./db/modelDB"; 3 | import { DBModel } from "./db/types"; 4 | 5 | export enum AvailableModels { 6 | WHISPER_TINY = "tiny", 7 | WHISPER_BASE = "base", 8 | WHISPER_SMALL = "small", 9 | WHISPER_MEDIUM = "medium", 10 | WHISPER_LARGE = "large", 11 | } 12 | 13 | export const ModelSizes: Map = new Map([ 14 | [AvailableModels.WHISPER_TINY, 51444634], 15 | [AvailableModels.WHISPER_BASE, 96834130], 16 | [AvailableModels.WHISPER_SMALL, 313018088], 17 | [AvailableModels.WHISPER_MEDIUM, 972263884], 18 | [AvailableModels.WHISPER_LARGE, 1954315876], 19 | ]); 20 | 21 | export class Model { 22 | name: string; 23 | data: Uint8Array; 24 | tokenizer: Uint8Array; 25 | 26 | constructor(name: string, data: Uint8Array, tokenizer: Uint8Array) { 27 | this.name = name; 28 | this.data = data; 29 | this.tokenizer = tokenizer; 30 | } 31 | 32 | static async fromDBModel( 33 | dbModel: DBModel, 34 | db: ModelDB 35 | ): Promise> { 36 | const tokenizerResult = await db.getTokenizer(dbModel.ID); 37 | if (tokenizerResult.isErr) { 38 | return Result.err(tokenizerResult.error); 39 | } 40 | const tokenizerBytes = tokenizerResult.value.bytes; 41 | 42 | return Result.ok( 43 | new Model(dbModel.name, dbModel.bytes, tokenizerBytes) 44 | ); 45 | } 46 | } 47 | 48 | export interface EncoderDecoder { 49 | name: string; 50 | encoder: Model; 51 | decoder: Model; 52 | config: Uint8Array; 53 | tokenizer: Uint8Array; 54 | } 55 | -------------------------------------------------------------------------------- /src/session.worker.ts: -------------------------------------------------------------------------------- 1 | import * as whisper from "whisper-webgpu"; 2 | import * as Comlink from "comlink"; 3 | import { Result } from "true-myth"; 4 | import { AvailableModels, Model } from "./models"; 5 | import ModelDB from "./db/modelDB"; 6 | 7 | export class Session { 8 | whisperSession: whisper.Session | undefined; 9 | 10 | public async initSession( 11 | selectedModel: AvailableModels, 12 | onProgress: (progress: number) => void 13 | ): Promise> { 14 | if (this.whisperSession) { 15 | return Result.err( 16 | new Error( 17 | "Session already initialized. Call `destroy()` first." 18 | ) 19 | ); 20 | } 21 | const modelResult = await this.loadModel(selectedModel, onProgress); 22 | if (modelResult.isErr) { 23 | return Result.err(modelResult.error); 24 | } 25 | const model = modelResult.value; 26 | await whisper.default(); 27 | const builder = new whisper.SessionBuilder(); 28 | const session = await builder 29 | .setModel(model.data) 30 | .setTokenizer(model.tokenizer) 31 | .build(); 32 | this.whisperSession = session; 33 | return Result.ok(undefined); 34 | } 35 | 36 | private async loadModel( 37 | selectedModel: AvailableModels, 38 | onProgress: (progress: number) => void 39 | ): Promise> { 40 | const db = await ModelDB.create(); //TODO: don't create a new db every time 41 | const dbResult = await db.getModel(selectedModel, onProgress); 42 | if (dbResult.isErr) { 43 | return Result.err( 44 | new Error( 45 | `Failed to load model ${selectedModel} with error: ${dbResult.error}` 46 | ) 47 | ); 48 | } 49 | const dbModel = dbResult.value; 50 | 51 | const modelResult = await Model.fromDBModel(dbModel, db); 52 | 53 | if (modelResult.isErr) { 54 | return Result.err( 55 | new Error( 56 | `Failed to transmute model ${selectedModel} with error: ${modelResult.error}` 57 | ) 58 | ); 59 | } 60 | const model = modelResult.value; 61 | return Result.ok(model); 62 | } 63 | 64 | public async run( 65 | audio: Uint8Array, 66 | options: any 67 | ): Promise> { 68 | if (!this.whisperSession) { 69 | return Result.err( 70 | new Error( 71 | "The session is not initialized. Call `initSession()` method first." 72 | ) 73 | ); 74 | } 75 | 76 | return Result.ok(await this.whisperSession.run(audio, options)); 77 | } 78 | 79 | public async stream( 80 | audio: Uint8Array, 81 | raw_audio: boolean, 82 | options: any, 83 | callback: (decoded: whisper.Segment) => void 84 | ): Promise> { 85 | if (!this.whisperSession) { 86 | return Result.err( 87 | new Error( 88 | "The session is not initialized. Call `initSession()` method first." 89 | ) 90 | ); 91 | } 92 | 93 | return Result.ok( 94 | await this.whisperSession.stream( 95 | audio, 96 | raw_audio, 97 | options, 98 | callback 99 | ) 100 | ); 101 | } 102 | } 103 | 104 | if (typeof self !== "undefined") { 105 | Comlink.expose(Session); 106 | } 107 | -------------------------------------------------------------------------------- /src/sessionManager.ts: -------------------------------------------------------------------------------- 1 | import { InferenceSession } from "./inferenceSession"; 2 | import * as Comlink from "comlink"; 3 | import { Session } from "./session.worker"; 4 | import { AvailableModels } from "./models"; 5 | import { Result } from "true-myth"; 6 | 7 | export class SessionManager { 8 | /** 9 | * Loads a model and returns a Session instance. 10 | * @param selectedModel - The model to load. 11 | * @param onLoaded - A callback that is called when the model is loaded. 12 | * @returns A Promise that resolves with a Session instance. 13 | * 14 | */ 15 | public async loadModel( 16 | selectedModel: AvailableModels, 17 | onLoaded: (result: any) => void, 18 | onProgress: (progress: number) => void 19 | ): Promise> { 20 | const creationResult = await this.createSession( 21 | true, 22 | selectedModel, 23 | onProgress 24 | ); 25 | if (creationResult.isErr) { 26 | return Result.err(creationResult.error); 27 | } 28 | onLoaded(creationResult.value); 29 | return Result.ok(creationResult.value); 30 | } 31 | 32 | /** 33 | * Creates a new session with the specified models. 34 | * 35 | * @param spawnWorker - Determines whether a Web Worker should be used for the session. 36 | * @param selectedModel - The model to use for the session. 37 | * @returns A Promise that resolves with a Session instance, or a Remote instance if a Web Worker was used. 38 | * 39 | */ 40 | private async createSession( 41 | spawnWorker: boolean, 42 | selectedModel: AvailableModels, 43 | onProgress: (progress: number) => void 44 | ): Promise> { 45 | if (spawnWorker && typeof document !== "undefined") { 46 | const worker = new Worker( 47 | new URL("./session.worker.js", import.meta.url), 48 | { 49 | type: "module", 50 | } 51 | ); 52 | const SessionWorker = Comlink.wrap(worker); 53 | const session = await new SessionWorker(); 54 | const initResult = await session.initSession( 55 | selectedModel, 56 | Comlink.proxy(onProgress) 57 | ); 58 | //@ts-ignore 59 | const [state, data] = initResult.repr; 60 | if (state === "Err") { 61 | return Result.err( 62 | new Error( 63 | "Session initialization failed: " + data.toString() 64 | ) 65 | ); 66 | } 67 | return Result.ok(new InferenceSession(session, worker)); 68 | } else { 69 | const session = new Session(); 70 | const initResult = await session.initSession( 71 | selectedModel, 72 | onProgress 73 | ); 74 | if (initResult.isErr) { 75 | console.error("Error initializing session: ", initResult); 76 | return Result.err(initResult.error); 77 | } 78 | return Result.ok(new InferenceSession(session)); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "strict": true, 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": false, 11 | "checkJs": false, 12 | "esModuleInterop": true, 13 | "noImplicitAny": false, 14 | "outDir": "./dist", 15 | "sourceMap": true, 16 | "strictPropertyInitialization": true 17 | }, 18 | "include": ["src/**/*.ts"], 19 | "exclude": ["node_modules", "test"] 20 | } 21 | --------------------------------------------------------------------------------