├── .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 |
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 |
192 | {modelLoading ? "Loading..." : "Load"}
193 |
194 |
195 | )}
196 |
197 |
198 |
199 |
200 | Upload Audio
201 |
202 |
206 |
207 |
208 | {audioData && audioMetadata
209 | ? audioMetadata.file.name
210 | : `Select Audio File`}
211 |
212 |
213 | {audioData
214 | ? humanFileSize(
215 | audioData.length
216 | )
217 | : ""}
218 |
219 |
220 |
221 |
229 |
230 |
235 |
236 | {blobUrl && (
237 |
238 |
239 | Your Audio
240 |
241 |
249 |
254 |
255 |
256 | )}
257 |
258 |
259 |
260 |
265 | {transcribing ? (
266 |
267 |
268 |
269 | ) : (
270 | "Transcribe"
271 | )}
272 |
273 |
274 |
setIsConfigOpen(true)}
277 | >
278 |
279 |
280 |
281 |
282 |
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 |
142 | {selectedLanguage || "Select a language"}
143 |
144 |
150 |
156 |
157 |
158 |
159 |
160 |
161 | {open && (
162 |
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 |
Record
56 |
60 | {isRecording ? (
61 |
67 |
72 |
73 | ) : (
74 |
82 |
87 |
88 | )}
89 |
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 |
49 | Select Model
50 |
51 | {progress > 0 && !loaded && (
52 |
53 | {progress.toFixed(2)}%
54 |
55 | )}
56 |
57 |
58 |
setDropdownOpen(!dropdownOpen)}
61 | >
62 |
63 | {selectedModel
64 | ? fmtModel(selectedModel)
65 | : "Select Model"}
66 |
67 |
72 |
73 |
74 |
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 |
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 |
Suppress Tokens
29 |
30 |
31 |
32 | Non Speech
33 |
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 |
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 |
73 | Download
74 |
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 |
--------------------------------------------------------------------------------