@happyvertical/smrt-svelte 0.55.6 → 0.55.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -31
- package/README.md +195 -5
- package/agents/browser-ai.md +47 -0
- package/agents/command-palette.md +85 -0
- package/agents/import-export.md +62 -0
- package/agents/overview-surfaces.md +255 -0
- package/agents/screens.md +57 -0
- package/agents/workspace.md +202 -5
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/dictation-source.js +8 -2
- package/dist/browser-ai/adapters/stt/factory.d.ts +6 -3
- package/dist/browser-ai/adapters/stt/factory.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/factory.js +19 -9
- package/dist/browser-ai/adapters/stt/index.d.ts +3 -1
- package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/index.js +3 -1
- package/dist/browser-ai/adapters/stt/local-speech-model.d.ts +111 -0
- package/dist/browser-ai/adapters/stt/local-speech-model.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/local-speech-model.js +377 -0
- package/dist/browser-ai/adapters/stt/local-speech.d.ts +82 -0
- package/dist/browser-ai/adapters/stt/local-speech.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/local-speech.js +317 -0
- package/dist/browser-ai/adapters/stt/local-speech.worker.d.ts +2 -0
- package/dist/browser-ai/adapters/stt/local-speech.worker.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/local-speech.worker.js +32 -0
- package/dist/browser-ai/adapters/stt/transformers-env.d.ts +8 -0
- package/dist/browser-ai/adapters/stt/transformers-env.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/transformers-env.js +17 -0
- package/dist/browser-ai/adapters/stt/types.d.ts +54 -9
- package/dist/browser-ai/adapters/stt/types.d.ts.map +1 -1
- package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts +5 -0
- package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts.map +1 -1
- package/dist/browser-ai/adapters/tts/browser-synthesis.js +83 -55
- package/dist/browser-ai/capabilities/speech-probe.d.ts +29 -0
- package/dist/browser-ai/capabilities/speech-probe.d.ts.map +1 -0
- package/dist/browser-ai/capabilities/speech-probe.js +19 -0
- package/dist/browser-ai/core/errors.d.ts +9 -0
- package/dist/browser-ai/core/errors.d.ts.map +1 -1
- package/dist/browser-ai/core/errors.js +12 -0
- package/dist/browser-ai/index.d.ts +1 -0
- package/dist/browser-ai/index.d.ts.map +1 -1
- package/dist/browser-ai/index.js +1 -0
- package/dist/components/app/AppShell.svelte +587 -33
- package/dist/components/app/AppShell.svelte.d.ts +75 -4
- package/dist/components/app/AppShell.svelte.d.ts.map +1 -1
- package/dist/components/app/DockToggles.svelte +0 -1
- package/dist/components/app/dock-toggle.d.ts +7 -1
- package/dist/components/app/dock-toggle.d.ts.map +1 -1
- package/dist/components/app/index.d.ts +2 -1
- package/dist/components/app/index.d.ts.map +1 -1
- package/dist/components/app/slot-item.d.ts +21 -0
- package/dist/components/app/slot-item.d.ts.map +1 -0
- package/dist/components/app/slot-item.js +1 -0
- package/dist/components/command-palette/CommandPalette.svelte +75 -0
- package/dist/components/command-palette/CommandPalette.svelte.d.ts +22 -0
- package/dist/components/command-palette/CommandPalette.svelte.d.ts.map +1 -0
- package/dist/components/command-palette/CommandPaletteDialog.svelte +406 -0
- package/dist/components/command-palette/CommandPaletteDialog.svelte.d.ts +18 -0
- package/dist/components/command-palette/CommandPaletteDialog.svelte.d.ts.map +1 -0
- package/dist/components/command-palette/CommandPaletteTrigger.svelte +91 -0
- package/dist/components/command-palette/CommandPaletteTrigger.svelte.d.ts +17 -0
- package/dist/components/command-palette/CommandPaletteTrigger.svelte.d.ts.map +1 -0
- package/dist/components/command-palette/context.d.ts +18 -0
- package/dist/components/command-palette/context.d.ts.map +1 -0
- package/dist/components/command-palette/context.js +38 -0
- package/dist/components/command-palette/controller.svelte.d.ts +70 -0
- package/dist/components/command-palette/controller.svelte.d.ts.map +1 -0
- package/dist/components/command-palette/controller.svelte.js +379 -0
- package/dist/components/command-palette/hotkeys.d.ts +30 -0
- package/dist/components/command-palette/hotkeys.d.ts.map +1 -0
- package/dist/components/command-palette/hotkeys.js +161 -0
- package/dist/components/command-palette/index.d.ts +19 -0
- package/dist/components/command-palette/index.d.ts.map +1 -0
- package/dist/components/command-palette/index.js +17 -0
- package/dist/components/command-palette/match.d.ts +38 -0
- package/dist/components/command-palette/match.d.ts.map +1 -0
- package/dist/components/command-palette/match.js +172 -0
- package/dist/components/command-palette/providers.d.ts +159 -0
- package/dist/components/command-palette/providers.d.ts.map +1 -0
- package/dist/components/command-palette/providers.js +345 -0
- package/dist/components/command-palette/rank.d.ts +20 -0
- package/dist/components/command-palette/rank.d.ts.map +1 -0
- package/dist/components/command-palette/rank.js +74 -0
- package/dist/components/command-palette/types.d.ts +119 -0
- package/dist/components/command-palette/types.d.ts.map +1 -0
- package/dist/components/command-palette/types.js +10 -0
- package/dist/components/command-palette/use-provider.svelte.d.ts +14 -0
- package/dist/components/command-palette/use-provider.svelte.d.ts.map +1 -0
- package/dist/components/command-palette/use-provider.svelte.js +23 -0
- package/dist/components/forms/AddressInput.svelte +2 -15
- package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/DateRangeInput.svelte +2 -15
- package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/DateTimeInput.svelte +8 -15
- package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/MeasurementInput.svelte +2 -15
- package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/MoneyInput.svelte +8 -25
- package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/NumberInput.svelte +7 -15
- package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/PhoneInput.svelte +7 -15
- package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/SelectInput.svelte +34 -40
- package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/TextInput.svelte +20 -37
- package/dist/components/forms/TextInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/TextareaInput.svelte +19 -35
- package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -1
- package/dist/components/import-export/ImportExport.svelte +678 -0
- package/dist/components/import-export/ImportExport.svelte.d.ts +5 -0
- package/dist/components/import-export/ImportExport.svelte.d.ts.map +1 -0
- package/dist/components/import-export/collection-adapter.d.ts +35 -0
- package/dist/components/import-export/collection-adapter.d.ts.map +1 -0
- package/dist/components/import-export/collection-adapter.js +42 -0
- package/dist/components/import-export/csv.d.ts +60 -0
- package/dist/components/import-export/csv.d.ts.map +1 -0
- package/dist/components/import-export/csv.js +192 -0
- package/dist/components/import-export/download.d.ts +4 -0
- package/dist/components/import-export/download.d.ts.map +1 -0
- package/dist/components/import-export/download.js +17 -0
- package/dist/components/import-export/export.d.ts +25 -0
- package/dist/components/import-export/export.d.ts.map +1 -0
- package/dist/components/import-export/export.js +68 -0
- package/dist/components/import-export/fields.d.ts +48 -0
- package/dist/components/import-export/fields.d.ts.map +1 -0
- package/dist/components/import-export/fields.js +103 -0
- package/dist/components/import-export/index.d.ts +12 -0
- package/dist/components/import-export/index.d.ts.map +1 -0
- package/dist/components/import-export/index.js +9 -0
- package/dist/components/import-export/mapping.d.ts +12 -0
- package/dist/components/import-export/mapping.d.ts.map +1 -0
- package/dist/components/import-export/mapping.js +38 -0
- package/dist/components/import-export/props.d.ts +34 -0
- package/dist/components/import-export/props.d.ts.map +1 -0
- package/dist/components/import-export/props.js +1 -0
- package/dist/components/import-export/report.d.ts +13 -0
- package/dist/components/import-export/report.d.ts.map +1 -0
- package/dist/components/import-export/report.js +50 -0
- package/dist/components/import-export/runner.d.ts +17 -0
- package/dist/components/import-export/runner.d.ts.map +1 -0
- package/dist/components/import-export/runner.js +39 -0
- package/dist/components/import-export/types.d.ts +100 -0
- package/dist/components/import-export/types.d.ts.map +1 -0
- package/dist/components/import-export/types.js +6 -0
- package/dist/components/import-export/validate.d.ts +39 -0
- package/dist/components/import-export/validate.d.ts.map +1 -0
- package/dist/components/import-export/validate.js +268 -0
- package/dist/components/overview/OverviewGrid.svelte +579 -0
- package/dist/components/overview/OverviewGrid.svelte.d.ts +5 -0
- package/dist/components/overview/OverviewGrid.svelte.d.ts.map +1 -0
- package/dist/components/overview/WidgetOptionsForm.svelte +193 -0
- package/dist/components/overview/WidgetOptionsForm.svelte.d.ts +5 -0
- package/dist/components/overview/WidgetOptionsForm.svelte.d.ts.map +1 -0
- package/dist/components/overview/controller.svelte.d.ts +92 -0
- package/dist/components/overview/controller.svelte.d.ts.map +1 -0
- package/dist/components/overview/controller.svelte.js +336 -0
- package/dist/components/overview/format.d.ts +13 -0
- package/dist/components/overview/format.d.ts.map +1 -0
- package/dist/components/overview/format.js +45 -0
- package/dist/components/overview/grid-types.d.ts +52 -0
- package/dist/components/overview/grid-types.d.ts.map +1 -0
- package/dist/components/overview/grid-types.js +1 -0
- package/dist/components/overview/index.d.ts +24 -0
- package/dist/components/overview/index.d.ts.map +1 -0
- package/dist/components/overview/index.js +22 -0
- package/dist/components/overview/load.d.ts +30 -0
- package/dist/components/overview/load.d.ts.map +1 -0
- package/dist/components/overview/load.js +92 -0
- package/dist/components/overview/markdown.d.ts +53 -0
- package/dist/components/overview/markdown.d.ts.map +1 -0
- package/dist/components/overview/markdown.js +203 -0
- package/dist/components/overview/model.d.ts +100 -0
- package/dist/components/overview/model.d.ts.map +1 -0
- package/dist/components/overview/model.js +460 -0
- package/dist/components/overview/recipe-widgets.d.ts +77 -0
- package/dist/components/overview/recipe-widgets.d.ts.map +1 -0
- package/dist/components/overview/recipe-widgets.js +151 -0
- package/dist/components/overview/registry.d.ts +45 -0
- package/dist/components/overview/registry.d.ts.map +1 -0
- package/dist/components/overview/registry.js +112 -0
- package/dist/components/overview/resize.d.ts +25 -0
- package/dist/components/overview/resize.d.ts.map +1 -0
- package/dist/components/overview/resize.js +41 -0
- package/dist/components/overview/schema.d.ts +34 -0
- package/dist/components/overview/schema.d.ts.map +1 -0
- package/dist/components/overview/schema.js +180 -0
- package/dist/components/overview/server.d.ts +14 -0
- package/dist/components/overview/server.d.ts.map +1 -0
- package/dist/components/overview/server.js +13 -0
- package/dist/components/overview/types.d.ts +235 -0
- package/dist/components/overview/types.d.ts.map +1 -0
- package/dist/components/overview/types.js +17 -0
- package/dist/components/overview/widgets/ChartWidget.svelte +111 -0
- package/dist/components/overview/widgets/ChartWidget.svelte.d.ts +6 -0
- package/dist/components/overview/widgets/ChartWidget.svelte.d.ts.map +1 -0
- package/dist/components/overview/widgets/MetricWidget.svelte +86 -0
- package/dist/components/overview/widgets/MetricWidget.svelte.d.ts +6 -0
- package/dist/components/overview/widgets/MetricWidget.svelte.d.ts.map +1 -0
- package/dist/components/overview/widgets/NoteWidget.svelte +61 -0
- package/dist/components/overview/widgets/NoteWidget.svelte.d.ts +5 -0
- package/dist/components/overview/widgets/NoteWidget.svelte.d.ts.map +1 -0
- package/dist/components/overview/widgets/RecordListWidget.svelte +91 -0
- package/dist/components/overview/widgets/RecordListWidget.svelte.d.ts +6 -0
- package/dist/components/overview/widgets/RecordListWidget.svelte.d.ts.map +1 -0
- package/dist/components/overview/widgets/ShortcutsWidget.svelte +80 -0
- package/dist/components/overview/widgets/ShortcutsWidget.svelte.d.ts +6 -0
- package/dist/components/overview/widgets/ShortcutsWidget.svelte.d.ts.map +1 -0
- package/dist/components/overview/widgets/core.d.ts +44 -0
- package/dist/components/overview/widgets/core.d.ts.map +1 -0
- package/dist/components/overview/widgets/core.js +222 -0
- package/dist/components/overview/widgets/data.d.ts +76 -0
- package/dist/components/overview/widgets/data.d.ts.map +1 -0
- package/dist/components/overview/widgets/data.js +40 -0
- package/dist/components/screens/DetailScreen.svelte +220 -0
- package/dist/components/screens/DetailScreen.svelte.d.ts +5 -0
- package/dist/components/screens/DetailScreen.svelte.d.ts.map +1 -0
- package/dist/components/screens/EditForm.svelte +313 -0
- package/dist/components/screens/EditForm.svelte.d.ts +5 -0
- package/dist/components/screens/EditForm.svelte.d.ts.map +1 -0
- package/dist/components/screens/ListScreen.svelte +191 -0
- package/dist/components/screens/ListScreen.svelte.d.ts +5 -0
- package/dist/components/screens/ListScreen.svelte.d.ts.map +1 -0
- package/dist/components/screens/RecipeScreens.svelte +237 -0
- package/dist/components/screens/RecipeScreens.svelte.d.ts +5 -0
- package/dist/components/screens/RecipeScreens.svelte.d.ts.map +1 -0
- package/dist/components/screens/fields.d.ts +54 -0
- package/dist/components/screens/fields.d.ts.map +1 -0
- package/dist/components/screens/fields.js +229 -0
- package/dist/components/screens/index.d.ts +8 -0
- package/dist/components/screens/index.d.ts.map +1 -0
- package/dist/components/screens/index.js +6 -0
- package/dist/components/screens/types.d.ts +215 -0
- package/dist/components/screens/types.d.ts.map +1 -0
- package/dist/components/screens/types.js +1 -0
- package/dist/components/screens/values.d.ts +62 -0
- package/dist/components/screens/values.d.ts.map +1 -0
- package/dist/components/screens/values.js +289 -0
- package/dist/components/sortable/Sortable.svelte +6 -33
- package/dist/components/sortable/Sortable.svelte.d.ts.map +1 -1
- package/dist/components/sortable/announce.d.ts +5 -0
- package/dist/components/sortable/announce.d.ts.map +1 -0
- package/dist/components/sortable/announce.js +33 -0
- package/dist/components/sortable/controller.svelte.d.ts +10 -0
- package/dist/components/sortable/controller.svelte.d.ts.map +1 -1
- package/dist/components/sortable/controller.svelte.js +29 -7
- package/dist/components/workspace/admin-shell/AdminShell.svelte +424 -58
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +27 -2
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/ShellBrand.svelte +86 -0
- package/dist/components/workspace/admin-shell/ShellBrand.svelte.d.ts +18 -0
- package/dist/components/workspace/admin-shell/ShellBrand.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellIconButton.svelte +77 -0
- package/dist/components/workspace/admin-shell/ShellIconButton.svelte.d.ts +26 -0
- package/dist/components/workspace/admin-shell/ShellIconButton.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte +88 -16
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/ShellNavEditor.svelte +531 -0
- package/dist/components/workspace/admin-shell/ShellNavEditor.svelte.d.ts +26 -0
- package/dist/components/workspace/admin-shell/ShellNavEditor.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellSectionIcon.svelte +57 -0
- package/dist/components/workspace/admin-shell/ShellSectionIcon.svelte.d.ts +22 -0
- package/dist/components/workspace/admin-shell/ShellSectionIcon.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte +290 -0
- package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte.d.ts +32 -0
- package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte +67 -2
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +8 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts +67 -0
- package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/layout-controller.svelte.js +129 -1
- package/dist/components/workspace/admin-shell/layout.d.ts +122 -0
- package/dist/components/workspace/admin-shell/layout.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/layout.js +410 -10
- package/dist/components/workspace/admin-shell/settings.js +4 -4
- package/dist/components/workspace/admin-shell/shell-icons.d.ts +45 -0
- package/dist/components/workspace/admin-shell/shell-icons.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/shell-icons.js +66 -0
- package/dist/components/workspace/admin-shell/slot-labels.d.ts +33 -0
- package/dist/components/workspace/admin-shell/slot-labels.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/slot-labels.js +33 -0
- package/dist/components/workspace/admin-shell/slots.d.ts +50 -0
- package/dist/components/workspace/admin-shell/slots.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/slots.js +77 -0
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +35 -0
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +111 -0
- package/dist/components/workspace/admin-shell/types.d.ts +101 -1
- package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
- package/dist/components/workspace/index.d.ts +9 -2
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +6 -0
- package/dist/components/workspace/manifest-nav.d.ts +20 -0
- package/dist/components/workspace/manifest-nav.d.ts.map +1 -1
- package/dist/components/workspace/manifest-nav.js +42 -13
- package/dist/i18n/server.d.ts +6 -0
- package/dist/i18n/server.d.ts.map +1 -1
- package/dist/i18n/server.js +6 -0
- package/dist/i18n/strings.import-export.d.ts +88 -0
- package/dist/i18n/strings.import-export.d.ts.map +1 -0
- package/dist/i18n/strings.import-export.js +88 -0
- package/dist/i18n/strings.overview.d.ts +100 -0
- package/dist/i18n/strings.overview.d.ts.map +1 -0
- package/dist/i18n/strings.overview.js +107 -0
- package/dist/i18n/strings.palette.d.ts +18 -0
- package/dist/i18n/strings.palette.d.ts.map +1 -0
- package/dist/i18n/strings.palette.js +18 -0
- package/dist/i18n/strings.screens.d.ts +31 -0
- package/dist/i18n/strings.screens.d.ts.map +1 -0
- package/dist/i18n/strings.screens.js +31 -0
- package/dist/i18n/strings.workspace.d.ts +49 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +49 -0
- package/package.json +59 -19
- package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts +0 -51
- package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts.map +0 -1
- package/dist/browser-ai/adapters/stt/whisper-wasm.js +0 -380
|
@@ -12,10 +12,13 @@ function isBrowserSpeechOptions(options) {
|
|
|
12
12
|
return !options.type || options.type === 'browser-speech';
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Type guard for
|
|
15
|
+
* Type guard for on-device speech options (transformers.js): Whisper
|
|
16
|
+
* (`whisper-wasm` is the older name for `whisper-local`) and Moonshine.
|
|
16
17
|
*/
|
|
17
|
-
function
|
|
18
|
-
return options.type === 'whisper-
|
|
18
|
+
function isLocalSpeechOptions(options) {
|
|
19
|
+
return (options.type === 'whisper-local' ||
|
|
20
|
+
options.type === 'whisper-wasm' ||
|
|
21
|
+
options.type === 'moonshine');
|
|
19
22
|
}
|
|
20
23
|
/**
|
|
21
24
|
* Type guard for Whisper.cpp options (@remotion/whisper-web)
|
|
@@ -39,8 +42,11 @@ function isWhisperCppOptions(options) {
|
|
|
39
42
|
* // Whisper.cpp (recommended for accuracy)
|
|
40
43
|
* const stt = await getSTT({ type: 'whisper-cpp', model: 'tiny.en' });
|
|
41
44
|
*
|
|
42
|
-
* // Whisper
|
|
43
|
-
* const stt = await getSTT({ type: 'whisper-
|
|
45
|
+
* // Local Whisper (transformers.js; WebGPU or WASM, one-time ~45 MB download)
|
|
46
|
+
* const stt = await getSTT({ type: 'whisper-local' });
|
|
47
|
+
*
|
|
48
|
+
* // Moonshine (fastest for live dictation, one-time ~32 MB download)
|
|
49
|
+
* const stt = await getSTT({ type: 'moonshine' });
|
|
44
50
|
* ```
|
|
45
51
|
*/
|
|
46
52
|
export async function getSTT(options = {}) {
|
|
@@ -52,14 +58,16 @@ export async function getSTT(options = {}) {
|
|
|
52
58
|
const { WhisperCppSTTAdapter } = await import('./whisper-cpp.js');
|
|
53
59
|
return new WhisperCppSTTAdapter(options);
|
|
54
60
|
}
|
|
55
|
-
if (
|
|
56
|
-
const {
|
|
57
|
-
return new
|
|
61
|
+
if (isLocalSpeechOptions(options)) {
|
|
62
|
+
const { LocalSpeechSTTAdapter } = await import('./local-speech.js');
|
|
63
|
+
return new LocalSpeechSTTAdapter(options);
|
|
58
64
|
}
|
|
59
65
|
throw new UnsupportedAdapterError(String(options.type), [
|
|
60
66
|
'browser-speech',
|
|
61
67
|
'whisper-cpp',
|
|
68
|
+
'whisper-local',
|
|
62
69
|
'whisper-wasm',
|
|
70
|
+
'moonshine',
|
|
63
71
|
]);
|
|
64
72
|
}
|
|
65
73
|
/**
|
|
@@ -103,7 +111,9 @@ export function isSTTAvailable(type) {
|
|
|
103
111
|
if (type === 'browser-speech') {
|
|
104
112
|
return caps.stt.browserSpeechAPI;
|
|
105
113
|
}
|
|
106
|
-
if (type === 'whisper-wasm'
|
|
114
|
+
if (type === 'whisper-wasm' ||
|
|
115
|
+
type === 'whisper-local' ||
|
|
116
|
+
type === 'moonshine') {
|
|
107
117
|
return caps.stt.whisperWasm;
|
|
108
118
|
}
|
|
109
119
|
return false;
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
export { type BrowserSpeechError, BrowserSpeechSTTAdapter, } from './browser-speech.js';
|
|
5
5
|
export { createSttDictationSource } from './dictation-source.js';
|
|
6
6
|
export * from './factory.js';
|
|
7
|
+
export { LocalSpeechSTTAdapter, WhisperLocalSTTAdapter, WhisperWasmSTTAdapter, } from './local-speech.js';
|
|
8
|
+
export { createLocalSpeechModel, createWhisperLocalModel, isEnglishOnlyLocalSpeechModel, LOCAL_SPEECH_MODELS, type LocalSpeechDevice, type LocalSpeechLoadOptions, LocalSpeechModel, type LocalSpeechModelOptions, type LocalSpeechModelPreset, type LocalSpeechModelState, resolveLocalSpeechModelId, WHISPER_LOCAL_DEFAULT_MODEL, WHISPER_LOCAL_DEFAULT_SIZE_BYTES, type WhisperLocalDevice, type WhisperLocalLoadOptions, WhisperLocalModel, type WhisperLocalModelOptions, type WhisperLocalModelState, } from './local-speech-model.js';
|
|
9
|
+
export { configureTransformersEnv } from './transformers-env.js';
|
|
7
10
|
export * from './types.js';
|
|
8
11
|
export { WhisperCppSTTAdapter } from './whisper-cpp.js';
|
|
9
|
-
export { WhisperWasmSTTAdapter } from './whisper-wasm.js';
|
|
10
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/browser-ai/adapters/stt/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,kBAAkB,EACvB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,cAAc,cAAc,CAAC;AAC7B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/browser-ai/adapters/stt/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,kBAAkB,EACvB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,cAAc,cAAc,CAAC;AAC7B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,6BAA6B,EAC7B,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,yBAAyB,EACzB,2BAA2B,EAC3B,gCAAgC,EAChC,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,GAC5B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
export { BrowserSpeechSTTAdapter, } from './browser-speech.js';
|
|
5
5
|
export { createSttDictationSource } from './dictation-source.js';
|
|
6
6
|
export * from './factory.js';
|
|
7
|
+
export { LocalSpeechSTTAdapter, WhisperLocalSTTAdapter, WhisperWasmSTTAdapter, } from './local-speech.js';
|
|
8
|
+
export { createLocalSpeechModel, createWhisperLocalModel, isEnglishOnlyLocalSpeechModel, LOCAL_SPEECH_MODELS, LocalSpeechModel, resolveLocalSpeechModelId, WHISPER_LOCAL_DEFAULT_MODEL, WHISPER_LOCAL_DEFAULT_SIZE_BYTES, WhisperLocalModel, } from './local-speech-model.js';
|
|
9
|
+
export { configureTransformersEnv } from './transformers-env.js';
|
|
7
10
|
export * from './types.js';
|
|
8
11
|
export { WhisperCppSTTAdapter } from './whisper-cpp.js';
|
|
9
|
-
export { WhisperWasmSTTAdapter } from './whisper-wasm.js';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { DownloadProgressInfo } from '../../core/types.js';
|
|
2
|
+
/** Where the speech model comes from by default (kept for `whisper-local`). */
|
|
3
|
+
export declare const WHISPER_LOCAL_DEFAULT_MODEL = "onnx-community/whisper-tiny.en";
|
|
4
|
+
/**
|
|
5
|
+
* Bytes a first download of the default model takes: the 8-bit encoder
|
|
6
|
+
* (~10 MB), merged decoder (~31 MB) and tokenizer/config files (~4 MB).
|
|
7
|
+
*/
|
|
8
|
+
export declare const WHISPER_LOCAL_DEFAULT_SIZE_BYTES = 45100000;
|
|
9
|
+
/** A model this package knows by a short name. */
|
|
10
|
+
export interface LocalSpeechModelPreset {
|
|
11
|
+
/** Hugging Face model id. */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Approximate first-download size in bytes (8-bit weights + tokenizer). */
|
|
14
|
+
bytes: number;
|
|
15
|
+
/** Transcribes English only (its decoder takes no `language`). */
|
|
16
|
+
englishOnly: boolean;
|
|
17
|
+
label: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Short names for `model`. Moonshine is the fastest for live dictation;
|
|
21
|
+
* Whisper `.en` is the longer-established English model.
|
|
22
|
+
*/
|
|
23
|
+
export declare const LOCAL_SPEECH_MODELS: Readonly<Record<string, LocalSpeechModelPreset>>;
|
|
24
|
+
/** Resolve a short name (`moonshine-tiny`) or a Hugging Face id. */
|
|
25
|
+
export declare function resolveLocalSpeechModelId(model?: string): string;
|
|
26
|
+
/** Whether a model id only transcribes English. */
|
|
27
|
+
export declare function isEnglishOnlyLocalSpeechModel(modelId: string): boolean;
|
|
28
|
+
export type LocalSpeechDevice = 'webgpu' | 'wasm';
|
|
29
|
+
/** @deprecated Use {@link LocalSpeechDevice}. */
|
|
30
|
+
export type WhisperLocalDevice = LocalSpeechDevice;
|
|
31
|
+
export interface LocalSpeechModelOptions {
|
|
32
|
+
/**
|
|
33
|
+
* A short name (`'moonshine-tiny'`, `'moonshine-base'`, `'whisper-tiny.en'`,
|
|
34
|
+
* ...) or a Hugging Face model id. Default `onnx-community/whisper-tiny.en`.
|
|
35
|
+
*/
|
|
36
|
+
model?: string;
|
|
37
|
+
/** Alias of `model`, kept from `createWhisperLocalModel`. */
|
|
38
|
+
modelId?: string;
|
|
39
|
+
/**
|
|
40
|
+
* `'auto'` (default): WebGPU when available, else single-thread WASM.
|
|
41
|
+
* With a worker, the worker entry decides (see `local-speech.worker.ts`).
|
|
42
|
+
*/
|
|
43
|
+
device?: 'auto' | LocalSpeechDevice;
|
|
44
|
+
/** Weight quantisation. Default `'q8'`. Page-thread only, as `device`. */
|
|
45
|
+
dtype?: string;
|
|
46
|
+
/** Make the Web Worker (see `local-speech.worker.ts`). */
|
|
47
|
+
createWorker?: () => Worker;
|
|
48
|
+
/**
|
|
49
|
+
* Load `@happyvertical/speech/local`, e.g.
|
|
50
|
+
* `() => import('@happyvertical/speech/local')`. A bundled browser app
|
|
51
|
+
* needs it; plain `import()` of the bare name only works where Node (or
|
|
52
|
+
* an import map) resolves it.
|
|
53
|
+
*/
|
|
54
|
+
loadSpeech?: () => Promise<unknown>;
|
|
55
|
+
/**
|
|
56
|
+
* Load `@huggingface/transformers` when running on the page's thread,
|
|
57
|
+
* e.g. `() => import('@huggingface/transformers')`.
|
|
58
|
+
*/
|
|
59
|
+
loadModule?: () => Promise<unknown>;
|
|
60
|
+
allowLocalModels?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** @deprecated Use {@link LocalSpeechModelOptions}. */
|
|
63
|
+
export type WhisperLocalModelOptions = LocalSpeechModelOptions;
|
|
64
|
+
export interface LocalSpeechLoadOptions {
|
|
65
|
+
onProgress?: (progress: DownloadProgressInfo) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Cancel the download. With a worker the download stops (the worker is
|
|
68
|
+
* ended); on the page's own thread the promise rejects and the files
|
|
69
|
+
* already fetched stay cached.
|
|
70
|
+
*/
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
}
|
|
73
|
+
/** @deprecated Use {@link LocalSpeechLoadOptions}. */
|
|
74
|
+
export type WhisperLocalLoadOptions = LocalSpeechLoadOptions;
|
|
75
|
+
export type LocalSpeechModelState = 'idle' | 'loading' | 'ready' | 'error';
|
|
76
|
+
/** @deprecated Use {@link LocalSpeechModelState}. */
|
|
77
|
+
export type WhisperLocalModelState = LocalSpeechModelState;
|
|
78
|
+
export declare class LocalSpeechModel {
|
|
79
|
+
private readonly options;
|
|
80
|
+
readonly modelId: string;
|
|
81
|
+
private backend;
|
|
82
|
+
private loadPromise;
|
|
83
|
+
private listeners;
|
|
84
|
+
private _state;
|
|
85
|
+
constructor(options?: LocalSpeechModelOptions);
|
|
86
|
+
get state(): LocalSpeechModelState;
|
|
87
|
+
get ready(): boolean;
|
|
88
|
+
/** Transcribes English only (no `language` is sent to the engine). */
|
|
89
|
+
get englishOnly(): boolean;
|
|
90
|
+
/** Bytes a first download takes (an estimate; cached models take none). */
|
|
91
|
+
estimateSize(): number;
|
|
92
|
+
private emit;
|
|
93
|
+
private onEngineProgress;
|
|
94
|
+
private getBackend;
|
|
95
|
+
/** Whether the model files are already stored on this device. */
|
|
96
|
+
isCached(): Promise<boolean>;
|
|
97
|
+
/**
|
|
98
|
+
* Download (if needed) and prepare the model. Safe to call again: a ready
|
|
99
|
+
* model returns at once and a load in flight is shared.
|
|
100
|
+
*/
|
|
101
|
+
load(options?: LocalSpeechLoadOptions): Promise<void>;
|
|
102
|
+
/** Transcribe 16 kHz mono PCM. The model must be loaded. */
|
|
103
|
+
transcribe(audio: Float32Array, language?: string): Promise<string>;
|
|
104
|
+
dispose(): void;
|
|
105
|
+
}
|
|
106
|
+
/** @deprecated Use {@link LocalSpeechModel}. */
|
|
107
|
+
export { LocalSpeechModel as WhisperLocalModel };
|
|
108
|
+
export declare function createLocalSpeechModel(options?: LocalSpeechModelOptions): LocalSpeechModel;
|
|
109
|
+
/** @deprecated Use {@link createLocalSpeechModel}. */
|
|
110
|
+
export declare const createWhisperLocalModel: typeof createLocalSpeechModel;
|
|
111
|
+
//# sourceMappingURL=local-speech-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-speech-model.d.ts","sourceRoot":"","sources":["../../../../src/browser-ai/adapters/stt/local-speech-model.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,mCAAmC,CAAC;AAE5E;;;GAGG;AACH,eAAO,MAAM,gCAAgC,WAAa,CAAC;AAE3D,kDAAkD;AAClD,MAAM,WAAW,sBAAsB;IACrC,6BAA6B;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CACxC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAgCvC,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,yBAAyB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED,mDAAmD;AACnD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAMtE;AAED,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,MAAM,CAAC;AAElD,iDAAiD;AACjD,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAEnD,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC;IACpC,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,uDAAuD;AACvD,MAAM,MAAM,wBAAwB,GAAG,uBAAuB,CAAC;AAE/D,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,sDAAsD;AACtD,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAE3E,qDAAqD;AACrD,MAAM,MAAM,sBAAsB,GAAG,qBAAqB,CAAC;AAkN3D,qBAAa,gBAAgB;IAOf,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,SAAS,CAAgD;IACjE,OAAO,CAAC,MAAM,CAAiC;gBAElB,OAAO,GAAE,uBAA4B;IAIlE,IAAI,KAAK,IAAI,qBAAqB,CAEjC;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,sEAAsE;IACtE,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,2EAA2E;IAC3E,YAAY,IAAI,MAAM;IAOtB,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,gBAAgB,CAwBtB;IAEF,OAAO,CAAC,UAAU;IASlB,iEAAiE;IAC3D,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IASlC;;;OAGG;IACH,IAAI,CAAC,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IA4DzD,4DAA4D;IAC5D,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOnE,OAAO,IAAI,IAAI;CAOhB;AAED,gDAAgD;AAChD,OAAO,EAAE,gBAAgB,IAAI,iBAAiB,EAAE,CAAC;AAEjD,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,uBAA4B,GACpC,gBAAgB,CAElB;AAED,sDAAsD;AACtD,eAAO,MAAM,uBAAuB,+BAAyB,CAAC"}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The downloadable on-device speech model behind `whisper-local` and
|
|
3
|
+
* `moonshine`, as a handle a consent UI can drive before anything is spoken:
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* const model = createLocalSpeechModel({ model: 'moonshine-tiny', createWorker });
|
|
7
|
+
* const bytes = model.estimateSize(); // "Download 32 MB?"
|
|
8
|
+
* if (await model.isCached()) { ... } // already on this device
|
|
9
|
+
* await model.load({ onProgress, signal }); // downloads, then ready
|
|
10
|
+
* createSttDictationSource({ type: 'moonshine', modelHandle: model });
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* The engine is `@happyvertical/speech/local` (`LocalTranscriber`, or its
|
|
14
|
+
* worker host `serveLocalTranscriber` when `createWorker` is given). The
|
|
15
|
+
* files live in Cache Storage, so a later `load()` (even in a new tab) is
|
|
16
|
+
* quick. Passing the handle to an adapter shares the one loaded pipeline
|
|
17
|
+
* rather than loading a second copy.
|
|
18
|
+
*
|
|
19
|
+
* `@happyvertical/speech` and `@huggingface/transformers` are OPTIONAL
|
|
20
|
+
* peers. This module never imports them statically: a host supplies
|
|
21
|
+
* `loadSpeech` (and, without a worker, `loadModule`), or builds the worker
|
|
22
|
+
* from `@happyvertical/smrt-svelte/browser-ai/whisper-worker?worker`.
|
|
23
|
+
*/
|
|
24
|
+
import { importOptional } from '@happyvertical/smrt-ui/utils/import-optional.js';
|
|
25
|
+
import { InitializationError } from '../../core/errors.js';
|
|
26
|
+
import { configureTransformersEnv } from './transformers-env.js';
|
|
27
|
+
/** Where the speech model comes from by default (kept for `whisper-local`). */
|
|
28
|
+
export const WHISPER_LOCAL_DEFAULT_MODEL = 'onnx-community/whisper-tiny.en';
|
|
29
|
+
/**
|
|
30
|
+
* Bytes a first download of the default model takes: the 8-bit encoder
|
|
31
|
+
* (~10 MB), merged decoder (~31 MB) and tokenizer/config files (~4 MB).
|
|
32
|
+
*/
|
|
33
|
+
export const WHISPER_LOCAL_DEFAULT_SIZE_BYTES = 45_100_000;
|
|
34
|
+
/**
|
|
35
|
+
* Short names for `model`. Moonshine is the fastest for live dictation;
|
|
36
|
+
* Whisper `.en` is the longer-established English model.
|
|
37
|
+
*/
|
|
38
|
+
export const LOCAL_SPEECH_MODELS = {
|
|
39
|
+
'moonshine-tiny': {
|
|
40
|
+
id: 'onnx-community/moonshine-tiny-ONNX',
|
|
41
|
+
bytes: 32_200_000,
|
|
42
|
+
englishOnly: true,
|
|
43
|
+
label: 'Moonshine tiny',
|
|
44
|
+
},
|
|
45
|
+
'moonshine-base': {
|
|
46
|
+
id: 'onnx-community/moonshine-base-ONNX',
|
|
47
|
+
bytes: 67_000_000,
|
|
48
|
+
englishOnly: true,
|
|
49
|
+
label: 'Moonshine base',
|
|
50
|
+
},
|
|
51
|
+
'whisper-tiny.en': {
|
|
52
|
+
id: WHISPER_LOCAL_DEFAULT_MODEL,
|
|
53
|
+
bytes: WHISPER_LOCAL_DEFAULT_SIZE_BYTES,
|
|
54
|
+
englishOnly: true,
|
|
55
|
+
label: 'Whisper tiny (English)',
|
|
56
|
+
},
|
|
57
|
+
'whisper-base.en': {
|
|
58
|
+
id: 'onnx-community/whisper-base.en',
|
|
59
|
+
bytes: 85_000_000,
|
|
60
|
+
englishOnly: true,
|
|
61
|
+
label: 'Whisper base (English)',
|
|
62
|
+
},
|
|
63
|
+
'whisper-small.en': {
|
|
64
|
+
id: 'onnx-community/whisper-small.en',
|
|
65
|
+
bytes: 260_000_000,
|
|
66
|
+
englishOnly: true,
|
|
67
|
+
label: 'Whisper small (English)',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
/** Resolve a short name (`moonshine-tiny`) or a Hugging Face id. */
|
|
71
|
+
export function resolveLocalSpeechModelId(model) {
|
|
72
|
+
if (!model)
|
|
73
|
+
return WHISPER_LOCAL_DEFAULT_MODEL;
|
|
74
|
+
return LOCAL_SPEECH_MODELS[model]?.id ?? model;
|
|
75
|
+
}
|
|
76
|
+
/** Whether a model id only transcribes English. */
|
|
77
|
+
export function isEnglishOnlyLocalSpeechModel(modelId) {
|
|
78
|
+
return (Object.values(LOCAL_SPEECH_MODELS).some((preset) => preset.id === modelId && preset.englishOnly) || /\.en$|moonshine/i.test(modelId));
|
|
79
|
+
}
|
|
80
|
+
const PCM_MIME = 'audio/pcm;rate=16000;channels=1;encoding=f32le';
|
|
81
|
+
function abortError() {
|
|
82
|
+
const error = new Error('Download cancelled');
|
|
83
|
+
error.name = 'AbortError';
|
|
84
|
+
return error;
|
|
85
|
+
}
|
|
86
|
+
/** `en-CA` -> `en`; Whisper wants the two-letter code. */
|
|
87
|
+
function languageCode(language) {
|
|
88
|
+
return language?.split(/[-_]/)[0]?.toLowerCase() || undefined;
|
|
89
|
+
}
|
|
90
|
+
function pcmRequest(pcm) {
|
|
91
|
+
// The worker takes ownership of what it is sent: hand over a copy.
|
|
92
|
+
const bytes = new Uint8Array(pcm.slice().buffer);
|
|
93
|
+
return { data: bytes, mimeType: PCM_MIME };
|
|
94
|
+
}
|
|
95
|
+
async function loadSpeechModule(loadSpeech) {
|
|
96
|
+
try {
|
|
97
|
+
return (await (loadSpeech
|
|
98
|
+
? loadSpeech()
|
|
99
|
+
: importOptional('@happyvertical/speech/local')));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw new InitializationError('local-speech', '@happyvertical/speech could not be loaded. Install it (and ' +
|
|
103
|
+
'@huggingface/transformers) and pass ' +
|
|
104
|
+
'`loadSpeech: () => import("@happyvertical/speech/local")`.');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
class WorkerBackend {
|
|
108
|
+
createWorker;
|
|
109
|
+
loadSpeech;
|
|
110
|
+
progress;
|
|
111
|
+
worker = null;
|
|
112
|
+
client = null;
|
|
113
|
+
constructor(createWorker, loadSpeech, progress) {
|
|
114
|
+
this.createWorker = createWorker;
|
|
115
|
+
this.loadSpeech = loadSpeech;
|
|
116
|
+
this.progress = progress;
|
|
117
|
+
}
|
|
118
|
+
ensure() {
|
|
119
|
+
this.client ??= loadSpeechModule(this.loadSpeech).then((speech) => {
|
|
120
|
+
const worker = this.createWorker();
|
|
121
|
+
this.worker = worker;
|
|
122
|
+
return new speech.LocalTranscriberWorkerClient(worker, {
|
|
123
|
+
onProgress: this.progress,
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
return this.client;
|
|
127
|
+
}
|
|
128
|
+
async isCached(model) {
|
|
129
|
+
const client = await this.ensure();
|
|
130
|
+
return client.isCached(model);
|
|
131
|
+
}
|
|
132
|
+
async preload(model, signal) {
|
|
133
|
+
if (signal?.aborted)
|
|
134
|
+
throw abortError();
|
|
135
|
+
const client = await this.ensure();
|
|
136
|
+
if (!signal)
|
|
137
|
+
return client.preload(model);
|
|
138
|
+
// The only way to stop a download in a worker is to end the worker.
|
|
139
|
+
const onAbort = () => this.dispose();
|
|
140
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
141
|
+
try {
|
|
142
|
+
await Promise.race([
|
|
143
|
+
client.preload(model),
|
|
144
|
+
new Promise((_, reject) => signal.addEventListener('abort', () => reject(abortError()), {
|
|
145
|
+
once: true,
|
|
146
|
+
})),
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
signal.removeEventListener('abort', onAbort);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async transcribe(pcm, model, language) {
|
|
154
|
+
const client = await this.ensure();
|
|
155
|
+
const result = await client.transcribe({
|
|
156
|
+
audio: pcmRequest(pcm),
|
|
157
|
+
model,
|
|
158
|
+
language: languageCode(language),
|
|
159
|
+
});
|
|
160
|
+
return result.text.trim();
|
|
161
|
+
}
|
|
162
|
+
dispose() {
|
|
163
|
+
this.client?.then((client) => client.close()).catch(() => undefined);
|
|
164
|
+
this.worker?.terminate();
|
|
165
|
+
this.worker = null;
|
|
166
|
+
this.client = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
class InPageBackend {
|
|
170
|
+
options;
|
|
171
|
+
model;
|
|
172
|
+
progress;
|
|
173
|
+
transcriber = null;
|
|
174
|
+
constructor(options, model, progress) {
|
|
175
|
+
this.options = options;
|
|
176
|
+
this.model = model;
|
|
177
|
+
this.progress = progress;
|
|
178
|
+
}
|
|
179
|
+
ensure() {
|
|
180
|
+
this.transcriber ??= loadSpeechModule(this.options.loadSpeech).then((speech) => new speech.LocalTranscriber({
|
|
181
|
+
model: this.model,
|
|
182
|
+
device: this.options.device ?? 'auto',
|
|
183
|
+
dtype: this.options.dtype ?? 'q8',
|
|
184
|
+
...(this.options.loadModule
|
|
185
|
+
? { transformers: this.options.loadModule }
|
|
186
|
+
: {}),
|
|
187
|
+
onProgress: this.progress,
|
|
188
|
+
configureEnv: configureTransformersEnv(this.options.allowLocalModels ?? false),
|
|
189
|
+
}));
|
|
190
|
+
return this.transcriber;
|
|
191
|
+
}
|
|
192
|
+
async isCached(model) {
|
|
193
|
+
const transcriber = await this.ensure();
|
|
194
|
+
return transcriber.isCached(model);
|
|
195
|
+
}
|
|
196
|
+
async preload(model, signal) {
|
|
197
|
+
if (signal?.aborted)
|
|
198
|
+
throw abortError();
|
|
199
|
+
const transcriber = await this.ensure();
|
|
200
|
+
// `preload` stops waiting on abort; the files fetched stay cached.
|
|
201
|
+
await transcriber.preload(model, signal).catch((error) => {
|
|
202
|
+
if (signal?.aborted)
|
|
203
|
+
throw abortError();
|
|
204
|
+
throw error;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async transcribe(pcm, model, language) {
|
|
208
|
+
const transcriber = await this.ensure();
|
|
209
|
+
const result = await transcriber.transcribe({
|
|
210
|
+
audio: pcmRequest(pcm),
|
|
211
|
+
model,
|
|
212
|
+
language: languageCode(language),
|
|
213
|
+
});
|
|
214
|
+
return result.text.trim();
|
|
215
|
+
}
|
|
216
|
+
dispose() {
|
|
217
|
+
this.transcriber?.then((t) => t.dispose()).catch(() => undefined);
|
|
218
|
+
this.transcriber = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export class LocalSpeechModel {
|
|
222
|
+
options;
|
|
223
|
+
modelId;
|
|
224
|
+
backend = null;
|
|
225
|
+
loadPromise = null;
|
|
226
|
+
listeners = new Set();
|
|
227
|
+
_state = 'idle';
|
|
228
|
+
constructor(options = {}) {
|
|
229
|
+
this.options = options;
|
|
230
|
+
this.modelId = resolveLocalSpeechModelId(options.model ?? options.modelId);
|
|
231
|
+
}
|
|
232
|
+
get state() {
|
|
233
|
+
return this._state;
|
|
234
|
+
}
|
|
235
|
+
get ready() {
|
|
236
|
+
return this._state === 'ready';
|
|
237
|
+
}
|
|
238
|
+
/** Transcribes English only (no `language` is sent to the engine). */
|
|
239
|
+
get englishOnly() {
|
|
240
|
+
return isEnglishOnlyLocalSpeechModel(this.modelId);
|
|
241
|
+
}
|
|
242
|
+
/** Bytes a first download takes (an estimate; cached models take none). */
|
|
243
|
+
estimateSize() {
|
|
244
|
+
return (Object.values(LOCAL_SPEECH_MODELS).find((p) => p.id === this.modelId)
|
|
245
|
+
?.bytes ?? WHISPER_LOCAL_DEFAULT_SIZE_BYTES);
|
|
246
|
+
}
|
|
247
|
+
emit(progress) {
|
|
248
|
+
for (const listener of this.listeners)
|
|
249
|
+
listener(progress);
|
|
250
|
+
}
|
|
251
|
+
onEngineProgress = (event) => {
|
|
252
|
+
if (event.status !== 'progress_total' ||
|
|
253
|
+
typeof event.loaded !== 'number' ||
|
|
254
|
+
typeof event.total !== 'number') {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const total = this.estimateSize();
|
|
258
|
+
const bytesTotal = event.total || total;
|
|
259
|
+
// All bytes are in: what remains is compiling the model ("Getting ready").
|
|
260
|
+
const finished = event.total > 0 && event.loaded >= event.total;
|
|
261
|
+
this.emit({
|
|
262
|
+
state: finished ? 'extracting' : 'downloading',
|
|
263
|
+
bytesLoaded: event.loaded,
|
|
264
|
+
bytesTotal,
|
|
265
|
+
percent: event.total ? Math.round((event.loaded / event.total) * 100) : 0,
|
|
266
|
+
currentFile: event.file,
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
getBackend() {
|
|
270
|
+
if (this.backend)
|
|
271
|
+
return this.backend;
|
|
272
|
+
const { createWorker, loadSpeech } = this.options;
|
|
273
|
+
this.backend = createWorker
|
|
274
|
+
? new WorkerBackend(createWorker, loadSpeech, this.onEngineProgress)
|
|
275
|
+
: new InPageBackend(this.options, this.modelId, this.onEngineProgress);
|
|
276
|
+
return this.backend;
|
|
277
|
+
}
|
|
278
|
+
/** Whether the model files are already stored on this device. */
|
|
279
|
+
async isCached() {
|
|
280
|
+
if (this._state === 'ready')
|
|
281
|
+
return true;
|
|
282
|
+
try {
|
|
283
|
+
return await this.getBackend().isCached(this.modelId);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Download (if needed) and prepare the model. Safe to call again: a ready
|
|
291
|
+
* model returns at once and a load in flight is shared.
|
|
292
|
+
*/
|
|
293
|
+
load(options = {}) {
|
|
294
|
+
const { onProgress, signal } = options;
|
|
295
|
+
if (this._state === 'ready')
|
|
296
|
+
return Promise.resolve();
|
|
297
|
+
if (onProgress)
|
|
298
|
+
this.listeners.add(onProgress);
|
|
299
|
+
if (!this.loadPromise) {
|
|
300
|
+
this._state = 'loading';
|
|
301
|
+
const total = this.estimateSize();
|
|
302
|
+
this.emit({
|
|
303
|
+
state: 'downloading',
|
|
304
|
+
bytesLoaded: 0,
|
|
305
|
+
bytesTotal: total,
|
|
306
|
+
percent: 0,
|
|
307
|
+
});
|
|
308
|
+
this.loadPromise = this.getBackend()
|
|
309
|
+
.preload(this.modelId, signal)
|
|
310
|
+
.then(() => {
|
|
311
|
+
this._state = 'ready';
|
|
312
|
+
this.emit({
|
|
313
|
+
state: 'complete',
|
|
314
|
+
bytesLoaded: total,
|
|
315
|
+
bytesTotal: total,
|
|
316
|
+
percent: 100,
|
|
317
|
+
});
|
|
318
|
+
})
|
|
319
|
+
.catch((error) => {
|
|
320
|
+
this._state = 'idle';
|
|
321
|
+
// A cancelled worker is gone; start fresh next time.
|
|
322
|
+
this.backend?.dispose();
|
|
323
|
+
this.backend = null;
|
|
324
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
325
|
+
if (err.name !== 'AbortError') {
|
|
326
|
+
this._state = 'error';
|
|
327
|
+
this.emit({
|
|
328
|
+
state: 'error',
|
|
329
|
+
bytesLoaded: 0,
|
|
330
|
+
bytesTotal: total,
|
|
331
|
+
percent: 0,
|
|
332
|
+
error: err.message,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
throw err;
|
|
336
|
+
})
|
|
337
|
+
.finally(() => {
|
|
338
|
+
this.loadPromise = null;
|
|
339
|
+
this.listeners.clear();
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
const shared = this.loadPromise;
|
|
343
|
+
if (!signal)
|
|
344
|
+
return shared;
|
|
345
|
+
// Each caller's own signal rejects its own promise; the load stops only
|
|
346
|
+
// when the backend can (worker) and the call that owns it aborts.
|
|
347
|
+
return new Promise((resolve, reject) => {
|
|
348
|
+
if (signal.aborted)
|
|
349
|
+
return reject(abortError());
|
|
350
|
+
signal.addEventListener('abort', () => reject(abortError()), {
|
|
351
|
+
once: true,
|
|
352
|
+
});
|
|
353
|
+
shared.then(resolve, reject);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/** Transcribe 16 kHz mono PCM. The model must be loaded. */
|
|
357
|
+
transcribe(audio, language) {
|
|
358
|
+
if (this._state !== 'ready') {
|
|
359
|
+
return Promise.reject(new Error('The speech model is not loaded'));
|
|
360
|
+
}
|
|
361
|
+
return this.getBackend().transcribe(audio, this.modelId, language);
|
|
362
|
+
}
|
|
363
|
+
dispose() {
|
|
364
|
+
this.backend?.dispose();
|
|
365
|
+
this.backend = null;
|
|
366
|
+
this.loadPromise = null;
|
|
367
|
+
this.listeners.clear();
|
|
368
|
+
this._state = 'idle';
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/** @deprecated Use {@link LocalSpeechModel}. */
|
|
372
|
+
export { LocalSpeechModel as WhisperLocalModel };
|
|
373
|
+
export function createLocalSpeechModel(options = {}) {
|
|
374
|
+
return new LocalSpeechModel(options);
|
|
375
|
+
}
|
|
376
|
+
/** @deprecated Use {@link createLocalSpeechModel}. */
|
|
377
|
+
export const createWhisperLocalModel = createLocalSpeechModel;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-device speech-to-text (`whisper-local`, alias `whisper-wasm`, and
|
|
3
|
+
* `moonshine`).
|
|
4
|
+
*
|
|
5
|
+
* For browsers whose own speech recognition is missing or unreliable
|
|
6
|
+
* (Firefox, Brave). It is push-to-talk: `start()` records from the
|
|
7
|
+
* microphone, `stop()` ends the recording and transcribes it with a Whisper
|
|
8
|
+
* or Moonshine model running in the browser (WebGPU when available,
|
|
9
|
+
* single-thread WASM otherwise, in a Web Worker when the host supplies one),
|
|
10
|
+
* then emits one final result. `transcribePcm()` transcribes one finished
|
|
11
|
+
* utterance directly, which smrt-ui's hands-free dictation uses. The engine
|
|
12
|
+
* is `@happyvertical/speech/local`; the model is a one-time download kept in
|
|
13
|
+
* Cache Storage; see `LocalSpeechModel` for the consent/progress API.
|
|
14
|
+
*/
|
|
15
|
+
import { type DictationAudioCaptureFactory } from '@happyvertical/smrt-ui/forms';
|
|
16
|
+
import type { InitState, OnProgress } from '../../core/types.js';
|
|
17
|
+
import { type LocalSpeechLoadOptions, type LocalSpeechModel } from './local-speech-model.js';
|
|
18
|
+
import type { LocalSpeechSTTOptions, STTAdapter, STTCapabilities, STTOptions, STTResult } from './types.js';
|
|
19
|
+
export declare class LocalSpeechSTTAdapter implements STTAdapter {
|
|
20
|
+
readonly type: 'whisper-local' | 'whisper-wasm' | 'moonshine';
|
|
21
|
+
/**
|
|
22
|
+
* `Dictation` waits this long for the text after the person stops.
|
|
23
|
+
*/
|
|
24
|
+
readonly stopTimeoutMs = 90000;
|
|
25
|
+
private readonly options;
|
|
26
|
+
private readonly _model;
|
|
27
|
+
private readonly makeCapture;
|
|
28
|
+
private capture;
|
|
29
|
+
private captureOff;
|
|
30
|
+
private _isListening;
|
|
31
|
+
private language;
|
|
32
|
+
private session;
|
|
33
|
+
private starting;
|
|
34
|
+
private stopping;
|
|
35
|
+
private resultListeners;
|
|
36
|
+
private errorListeners;
|
|
37
|
+
private startListeners;
|
|
38
|
+
private endListeners;
|
|
39
|
+
constructor(options?: Partial<LocalSpeechSTTOptions> & {
|
|
40
|
+
/** Test seam: the microphone recorder. */
|
|
41
|
+
capture?: DictationAudioCaptureFactory;
|
|
42
|
+
});
|
|
43
|
+
/** The downloadable model, for consent UIs. */
|
|
44
|
+
get model(): LocalSpeechModel;
|
|
45
|
+
get initState(): InitState;
|
|
46
|
+
estimateSize(): number;
|
|
47
|
+
isCached(): Promise<boolean>;
|
|
48
|
+
load(options?: LocalSpeechLoadOptions): Promise<void>;
|
|
49
|
+
ensureInitialized(onProgress?: OnProgress): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Make the model ready for dictation without ever downloading it: a model
|
|
52
|
+
* already loaded is used, one stored on this device is loaded from there,
|
|
53
|
+
* and anything else rejects with `ModelNotDownloadedError`. The host asks
|
|
54
|
+
* for consent and calls `load()` itself.
|
|
55
|
+
*/
|
|
56
|
+
prepare(): Promise<void>;
|
|
57
|
+
getCapabilities(): STTCapabilities;
|
|
58
|
+
/**
|
|
59
|
+
* Transcribe one finished utterance (16 kHz mono PCM). Loads the model
|
|
60
|
+
* first if it is not ready. This is what hands-free dictation calls for
|
|
61
|
+
* each utterance the voice activity detector cuts out.
|
|
62
|
+
*/
|
|
63
|
+
transcribePcm(pcm: Float32Array, options?: {
|
|
64
|
+
language?: string;
|
|
65
|
+
}): Promise<string>;
|
|
66
|
+
start(options?: STTOptions): Promise<void>;
|
|
67
|
+
/** End the recording, transcribe it, emit the final result, then end. */
|
|
68
|
+
stop(): Promise<void>;
|
|
69
|
+
private finish;
|
|
70
|
+
private emitError;
|
|
71
|
+
abort(): void;
|
|
72
|
+
isListening(): boolean;
|
|
73
|
+
onResult(callback: (result: STTResult) => void): () => void;
|
|
74
|
+
onError(callback: (error: Error) => void): () => void;
|
|
75
|
+
onStart(callback: () => void): () => void;
|
|
76
|
+
onEnd(callback: () => void): () => void;
|
|
77
|
+
dispose(): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
/** @deprecated Use {@link LocalSpeechSTTAdapter}. */
|
|
80
|
+
/** @deprecated Use {@link LocalSpeechSTTAdapter}. */
|
|
81
|
+
export { LocalSpeechSTTAdapter as WhisperLocalSTTAdapter, LocalSpeechSTTAdapter as WhisperWasmSTTAdapter, };
|
|
82
|
+
//# sourceMappingURL=local-speech.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-speech.d.ts","sourceRoot":"","sources":["../../../../src/browser-ai/adapters/stt/local-speech.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAGL,KAAK,4BAA4B,EAClC,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EACV,qBAAqB,EACrB,UAAU,EACV,eAAe,EACf,UAAU,EACV,SAAS,EACV,MAAM,YAAY,CAAC;AAgCpB,qBAAa,qBAAsB,YAAW,UAAU;IACtD,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,cAAc,GAAG,WAAW,CAAC;IAC9D;;OAEG;IACH,QAAQ,CAAC,aAAa,SAAmB;IAEzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+B;IAC3D,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAA8B;IAE9C,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,YAAY,CAAyB;gBAG3C,OAAO,GAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG;QACxC,0CAA0C;QAC1C,OAAO,CAAC,EAAE,4BAA4B,CAAC;KACnC;IA+BR,+CAA+C;IAC/C,IAAI,KAAK,IAAI,gBAAgB,CAE5B;IAED,IAAI,SAAS,IAAI,SAAS,CAWzB;IAED,YAAY,IAAI,MAAM;IAItB,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAI5B,IAAI,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,iBAAiB,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/D;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ9B,eAAe,IAAI,eAAe;IA0BlC;;;;OAIG;IACG,aAAa,CACjB,GAAG,EAAE,YAAY,EACjB,OAAO,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAClC,OAAO,CAAC,MAAM,CAAC;IAQZ,KAAK,CAAC,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCpD,yEAAyE;IACzE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAsBP,MAAM;IAwBpB,OAAO,CAAC,SAAS;IAIjB,KAAK,IAAI,IAAI;IAab,WAAW,IAAI,OAAO;IAItB,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI;IAK3D,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI;IAKrD,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKzC,KAAK,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAS/B;AAED,qDAAqD;AACrD,qDAAqD;AACrD,OAAO,EACL,qBAAqB,IAAI,sBAAsB,EAC/C,qBAAqB,IAAI,qBAAqB,GAC/C,CAAC"}
|