@athenaintel/react 0.23.2 → 0.24.0
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/README.md +46 -1
- package/dist/assets/AssetPanel.cjs +1 -1
- package/dist/assets/AssetPanel.cjs.map +1 -1
- package/dist/assets/AssetPanel.js +1 -1
- package/dist/assets/AssetPanel.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.js +1 -1
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/chat/AthenaChat.cjs +138 -46
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +37 -1
- package/dist/chat/AthenaChat.js +139 -47
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +3 -18
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.js +3 -18
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/StatewireClientToolBridge.cjs +2 -16
- package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
- package/dist/chat/StatewireClientToolBridge.js +2 -16
- package/dist/chat/StatewireClientToolBridge.js.map +1 -1
- package/dist/chat/StatewireQueuedMessages.cjs +5 -16
- package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
- package/dist/chat/StatewireQueuedMessages.js +5 -16
- package/dist/chat/StatewireQueuedMessages.js.map +1 -1
- package/dist/chat/WaitingOnYouBanner.cjs +3 -18
- package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
- package/dist/chat/WaitingOnYouBanner.js +3 -18
- package/dist/chat/WaitingOnYouBanner.js.map +1 -1
- package/dist/chat/athena-composer-controls.js +1 -1
- package/dist/chat/run-activity.cjs +0 -16
- package/dist/chat/run-activity.cjs.map +1 -1
- package/dist/chat/run-activity.js +0 -16
- package/dist/chat/run-activity.js.map +1 -1
- package/dist/chat/statewire-client-tools.cjs +21 -17
- package/dist/chat/statewire-client-tools.cjs.map +1 -1
- package/dist/chat/statewire-client-tools.d.ts +27 -0
- package/dist/chat/statewire-client-tools.js +21 -17
- package/dist/chat/statewire-client-tools.js.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.cjs +0 -16
- package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.js +0 -16
- package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
- package/dist/chat/tiptap-text.cjs +1 -17
- package/dist/chat/tiptap-text.cjs.map +1 -1
- package/dist/chat/tiptap-text.js +1 -17
- package/dist/chat/tiptap-text.js.map +1 -1
- package/dist/chat/use-athena-run-live.cjs +0 -15
- package/dist/chat/use-athena-run-live.cjs.map +1 -1
- package/dist/chat/use-athena-run-live.d.ts +7 -0
- package/dist/chat/use-athena-run-live.js +0 -15
- package/dist/chat/use-athena-run-live.js.map +1 -1
- package/dist/chat/use-statewire-approval-request.cjs +4 -16
- package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
- package/dist/chat/use-statewire-approval-request.d.ts +2 -0
- package/dist/chat/use-statewire-approval-request.js +4 -16
- package/dist/chat/use-statewire-approval-request.js.map +1 -1
- package/dist/chat-ui/composer/tiptap-composer.cjs +17 -17
- package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
- package/dist/chat-ui/composer/tiptap-composer.js +17 -17
- package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
- package/dist/chat-ui/index.cjs +0 -16
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.js +0 -16
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/mentions/mention-chip.cjs +1 -16
- package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
- package/dist/chat-ui/mentions/mention-chip.js +1 -16
- package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs +4 -6
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js +4 -6
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs +2 -9
- package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/index.js +2 -9
- package/dist/chat-ui/mentions/suggestions/data/sources/index.js.map +1 -1
- package/dist/chat-ui/messages/message-components.cjs +5 -19
- package/dist/chat-ui/messages/message-components.cjs.map +1 -1
- package/dist/chat-ui/messages/message-components.js +5 -19
- package/dist/chat-ui/messages/message-components.js.map +1 -1
- package/dist/chat-ui/messages/runtime-message.cjs +1 -16
- package/dist/chat-ui/messages/runtime-message.cjs.map +1 -1
- package/dist/chat-ui/messages/runtime-message.js +1 -16
- package/dist/chat-ui/messages/runtime-message.js.map +1 -1
- package/dist/chat-ui/messages/statewire-continue-run.cjs +3 -15
- package/dist/chat-ui/messages/statewire-continue-run.cjs.map +1 -1
- package/dist/chat-ui/messages/statewire-continue-run.js +3 -15
- package/dist/chat-ui/messages/statewire-continue-run.js.map +1 -1
- package/dist/chat-ui/messages/user-edit-composer.cjs +0 -16
- package/dist/chat-ui/messages/user-edit-composer.cjs.map +1 -1
- package/dist/chat-ui/messages/user-edit-composer.js +0 -16
- package/dist/chat-ui/messages/user-edit-composer.js.map +1 -1
- package/dist/chat-ui/messages/wake-marker.cjs +1 -16
- package/dist/chat-ui/messages/wake-marker.cjs.map +1 -1
- package/dist/chat-ui/messages/wake-marker.js +1 -16
- package/dist/chat-ui/messages/wake-marker.js.map +1 -1
- package/dist/chat-ui/statewire.cjs +0 -16
- package/dist/chat-ui/statewire.cjs.map +1 -1
- package/dist/chat-ui/statewire.js +0 -16
- package/dist/chat-ui/statewire.js.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -17
- package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.js +2 -17
- package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
- package/dist/composer/composer-handoff.cjs +121 -0
- package/dist/composer/composer-handoff.cjs.map +1 -0
- package/dist/composer/composer-handoff.d.ts +89 -0
- package/dist/composer/composer-handoff.js +121 -0
- package/dist/composer/composer-handoff.js.map +1 -0
- package/dist/composer/index.d.ts +2 -0
- package/dist/composer/useAppendToComposer.cjs +19 -7
- package/dist/composer/useAppendToComposer.cjs.map +1 -1
- package/dist/composer/useAppendToComposer.d.ts +22 -4
- package/dist/composer/useAppendToComposer.js +20 -8
- package/dist/composer/useAppendToComposer.js.map +1 -1
- package/dist/composer/useSendMessage.cjs +26 -8
- package/dist/composer/useSendMessage.cjs.map +1 -1
- package/dist/composer/useSendMessage.d.ts +8 -0
- package/dist/composer/useSendMessage.js +27 -9
- package/dist/composer/useSendMessage.js.map +1 -1
- package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
- package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
- package/dist/diagnostics/athena-diagnostics.js.map +1 -1
- package/dist/diagnostics/errors.cjs +2 -0
- package/dist/diagnostics/errors.cjs.map +1 -1
- package/dist/diagnostics/errors.d.ts +2 -0
- package/dist/diagnostics/errors.js +2 -0
- package/dist/diagnostics/errors.js.map +1 -1
- package/dist/dictation/DictationStrip.cjs +61 -0
- package/dist/dictation/DictationStrip.cjs.map +1 -0
- package/dist/dictation/DictationStrip.d.ts +27 -0
- package/dist/dictation/DictationStrip.js +61 -0
- package/dist/dictation/DictationStrip.js.map +1 -0
- package/dist/dictation/audio-level-history.cjs +68 -0
- package/dist/dictation/audio-level-history.cjs.map +1 -0
- package/dist/dictation/audio-level-history.d.ts +27 -0
- package/dist/dictation/audio-level-history.js +68 -0
- package/dist/dictation/audio-level-history.js.map +1 -0
- package/dist/dictation/dictation-controls.cjs +60 -0
- package/dist/dictation/dictation-controls.cjs.map +1 -0
- package/dist/dictation/dictation-controls.d.ts +15 -0
- package/dist/dictation/dictation-controls.js +60 -0
- package/dist/dictation/dictation-controls.js.map +1 -0
- package/dist/dictation/dictation-engine.cjs +128 -0
- package/dist/dictation/dictation-engine.cjs.map +1 -0
- package/dist/dictation/dictation-engine.d.ts +95 -0
- package/dist/dictation/dictation-engine.js +128 -0
- package/dist/dictation/dictation-engine.js.map +1 -0
- package/dist/dictation/dictation-state.cjs +68 -0
- package/dist/dictation/dictation-state.cjs.map +1 -0
- package/dist/dictation/dictation-state.d.ts +58 -0
- package/dist/dictation/dictation-state.js +68 -0
- package/dist/dictation/dictation-state.js.map +1 -0
- package/dist/dictation/recorded-dictation-engine.cjs +153 -0
- package/dist/dictation/recorded-dictation-engine.cjs.map +1 -0
- package/dist/dictation/recorded-dictation-engine.d.ts +66 -0
- package/dist/dictation/recorded-dictation-engine.js +153 -0
- package/dist/dictation/recorded-dictation-engine.js.map +1 -0
- package/dist/dictation/use-athena-dictation-engine.cjs +64 -0
- package/dist/dictation/use-athena-dictation-engine.cjs.map +1 -0
- package/dist/dictation/use-athena-dictation-engine.d.ts +25 -0
- package/dist/dictation/use-athena-dictation-engine.js +64 -0
- package/dist/dictation/use-athena-dictation-engine.js.map +1 -0
- package/dist/dictation/use-dictation.cjs +215 -0
- package/dist/dictation/use-dictation.cjs.map +1 -0
- package/dist/dictation/use-dictation.d.ts +63 -0
- package/dist/dictation/use-dictation.js +215 -0
- package/dist/dictation/use-dictation.js.map +1 -0
- package/dist/index.cjs +41 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +14 -0
- package/dist/index.js +23 -21
- package/dist/index.js.map +1 -1
- package/dist/packages/agent-runtime/src/edit-command.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/edit-command.js.map +1 -1
- package/dist/packages/agent-runtime/src/errors.cjs +18 -0
- package/dist/packages/agent-runtime/src/errors.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/errors.js +19 -1
- package/dist/packages/agent-runtime/src/errors.js.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +1 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +1 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.cjs +61 -12
- package/dist/packages/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.js +61 -12
- package/dist/packages/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs +49 -0
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js +49 -0
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
- package/dist/packages/agent-runtime/src/thread-status.js +1 -1
- package/dist/packages/agent-runtime/src/tool-metadata.cjs +1 -37
- package/dist/packages/agent-runtime/src/tool-metadata.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/tool-metadata.js +1 -37
- package/dist/packages/agent-runtime/src/tool-metadata.js.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-config.cjs +0 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-config.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-config.js +0 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-config.js.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
- package/dist/packages/react/package.json.cjs +1 -1
- package/dist/packages/react/package.json.js +1 -1
- package/dist/provider/AthenaProvider.cjs +45 -20
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +1 -1
- package/dist/provider/AthenaProvider.js +46 -21
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/runtime/statewire-lifecycle.cjs +0 -16
- package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
- package/dist/runtime/statewire-lifecycle.js +0 -16
- package/dist/runtime/statewire-lifecycle.js.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.cjs +3 -17
- package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.js +3 -17
- package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/threads/statewire-thread-list.cjs +4 -17
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.js +4 -17
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.cjs +2 -16
- package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
- package/dist/threads/use-rest-conversation-history.js +2 -16
- package/dist/threads/use-rest-conversation-history.js.map +1 -1
- package/dist/threads/use-statewire-conversation-history.cjs +2 -16
- package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
- package/dist/threads/use-statewire-conversation-history.js +2 -16
- package/dist/threads/use-statewire-conversation-history.js.map +1 -1
- package/dist/toolkits/index.cjs +0 -7
- package/dist/toolkits/index.cjs.map +1 -1
- package/dist/toolkits/index.d.ts +0 -2
- package/dist/toolkits/index.js +0 -7
- package/dist/toolkits/index.js.map +1 -1
- package/dist/tools/index.cjs +4 -20
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.js +1 -17
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/misc-tool-uis.cjs +2 -17
- package/dist/tools/misc-tool-uis.cjs.map +1 -1
- package/dist/tools/misc-tool-uis.js +2 -17
- package/dist/tools/misc-tool-uis.js.map +1 -1
- package/dist/tools/primitives.cjs +1 -16
- package/dist/tools/primitives.cjs.map +1 -1
- package/dist/tools/primitives.js +1 -16
- package/dist/tools/primitives.js.map +1 -1
- package/dist/tools/tool-fallback.cjs +2 -17
- package/dist/tools/tool-fallback.cjs.map +1 -1
- package/dist/tools/tool-fallback.js +2 -17
- package/dist/tools/tool-fallback.js.map +1 -1
- package/dist/tools/voice-update-tool-ui.cjs +1 -17
- package/dist/tools/voice-update-tool-ui.cjs.map +1 -1
- package/dist/tools/voice-update-tool-ui.js +1 -17
- package/dist/tools/voice-update-tool-ui.js.map +1 -1
- package/package.json +1 -1
- package/dist/node_modules/qrcode-generator/dist/qrcode.cjs +0 -32
- package/dist/node_modules/qrcode-generator/dist/qrcode.cjs.map +0 -1
- package/dist/node_modules/qrcode-generator/dist/qrcode.js +0 -31
- package/dist/node_modules/qrcode-generator/dist/qrcode.js.map +0 -1
- package/dist/node_modules/zod/v4/classic/errors.cjs +0 -44
- package/dist/node_modules/zod/v4/classic/errors.cjs.map +0 -1
- package/dist/node_modules/zod/v4/classic/errors.js +0 -44
- package/dist/node_modules/zod/v4/classic/errors.js.map +0 -1
- package/dist/node_modules/zod/v4/classic/iso.cjs +0 -43
- package/dist/node_modules/zod/v4/classic/iso.cjs.map +0 -1
- package/dist/node_modules/zod/v4/classic/iso.js +0 -43
- package/dist/node_modules/zod/v4/classic/iso.js.map +0 -1
- package/dist/node_modules/zod/v4/classic/parse.cjs +0 -29
- package/dist/node_modules/zod/v4/classic/parse.cjs.map +0 -1
- package/dist/node_modules/zod/v4/classic/parse.js +0 -29
- package/dist/node_modules/zod/v4/classic/parse.js.map +0 -1
- package/dist/node_modules/zod/v4/classic/schemas.cjs +0 -871
- package/dist/node_modules/zod/v4/classic/schemas.cjs.map +0 -1
- package/dist/node_modules/zod/v4/classic/schemas.js +0 -871
- package/dist/node_modules/zod/v4/classic/schemas.js.map +0 -1
- package/dist/node_modules/zod/v4/core/api.cjs +0 -570
- package/dist/node_modules/zod/v4/core/api.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/api.js +0 -570
- package/dist/node_modules/zod/v4/core/api.js.map +0 -1
- package/dist/node_modules/zod/v4/core/checks.cjs +0 -411
- package/dist/node_modules/zod/v4/core/checks.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/checks.js +0 -411
- package/dist/node_modules/zod/v4/core/checks.js.map +0 -1
- package/dist/node_modules/zod/v4/core/core.cjs +0 -77
- package/dist/node_modules/zod/v4/core/core.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/core.js +0 -77
- package/dist/node_modules/zod/v4/core/core.js.map +0 -1
- package/dist/node_modules/zod/v4/core/doc.cjs +0 -38
- package/dist/node_modules/zod/v4/core/doc.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/doc.js +0 -38
- package/dist/node_modules/zod/v4/core/doc.js.map +0 -1
- package/dist/node_modules/zod/v4/core/errors.cjs +0 -76
- package/dist/node_modules/zod/v4/core/errors.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/errors.js +0 -76
- package/dist/node_modules/zod/v4/core/errors.js.map +0 -1
- package/dist/node_modules/zod/v4/core/json-schema-processors.cjs +0 -312
- package/dist/node_modules/zod/v4/core/json-schema-processors.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/json-schema-processors.js +0 -312
- package/dist/node_modules/zod/v4/core/json-schema-processors.js.map +0 -1
- package/dist/node_modules/zod/v4/core/parse.cjs +0 -96
- package/dist/node_modules/zod/v4/core/parse.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/parse.js +0 -96
- package/dist/node_modules/zod/v4/core/parse.js.map +0 -1
- package/dist/node_modules/zod/v4/core/regexes.cjs +0 -86
- package/dist/node_modules/zod/v4/core/regexes.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/regexes.js +0 -86
- package/dist/node_modules/zod/v4/core/regexes.js.map +0 -1
- package/dist/node_modules/zod/v4/core/registries.cjs +0 -52
- package/dist/node_modules/zod/v4/core/registries.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/registries.js +0 -52
- package/dist/node_modules/zod/v4/core/registries.js.map +0 -1
- package/dist/node_modules/zod/v4/core/schemas.cjs +0 -1412
- package/dist/node_modules/zod/v4/core/schemas.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/schemas.js +0 -1413
- package/dist/node_modules/zod/v4/core/schemas.js.map +0 -1
- package/dist/node_modules/zod/v4/core/to-json-schema.cjs +0 -370
- package/dist/node_modules/zod/v4/core/to-json-schema.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/to-json-schema.js +0 -370
- package/dist/node_modules/zod/v4/core/to-json-schema.js.map +0 -1
- package/dist/node_modules/zod/v4/core/util.cjs +0 -444
- package/dist/node_modules/zod/v4/core/util.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/util.js +0 -444
- package/dist/node_modules/zod/v4/core/util.js.map +0 -1
- package/dist/node_modules/zod/v4/core/versions.cjs +0 -9
- package/dist/node_modules/zod/v4/core/versions.cjs.map +0 -1
- package/dist/node_modules/zod/v4/core/versions.js +0 -9
- package/dist/node_modules/zod/v4/core/versions.js.map +0 -1
- package/dist/packages/agent-runtime/src/thread-handoff.cjs +0 -85
- package/dist/packages/agent-runtime/src/thread-handoff.cjs.map +0 -1
- package/dist/packages/agent-runtime/src/thread-handoff.js +0 -85
- package/dist/packages/agent-runtime/src/thread-handoff.js.map +0 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speech-to-text engines behind the composer's dictation mode.
|
|
3
|
+
*
|
|
4
|
+
* An engine turns microphone audio into a stream of transcript updates. The
|
|
5
|
+
* SDK ships the browser's own `SpeechRecognition` engine (Chrome, Edge,
|
|
6
|
+
* Safari), which needs no backend. A host can plug in another engine (for
|
|
7
|
+
* example a relay to a server-side recognizer) through the `engine` option
|
|
8
|
+
* of `useDictation` / `<AthenaChat dictation>`; the composer UI is engine
|
|
9
|
+
* agnostic.
|
|
10
|
+
*/
|
|
11
|
+
export interface DictationTranscriptUpdate {
|
|
12
|
+
/** Text the engine has committed. Appended to earlier committed text. */
|
|
13
|
+
final: string;
|
|
14
|
+
/** Text still being recognized; replaced on the next update. */
|
|
15
|
+
interim: string;
|
|
16
|
+
}
|
|
17
|
+
export interface DictationEngineCallbacks {
|
|
18
|
+
onTranscript: (update: DictationTranscriptUpdate) => void;
|
|
19
|
+
/** The engine stopped on its own (silence timeout, network, permission). */
|
|
20
|
+
onEnd: () => void;
|
|
21
|
+
onError: (error: DictationEngineError) => void;
|
|
22
|
+
}
|
|
23
|
+
export interface DictationEngineSession {
|
|
24
|
+
/** Ask the engine to finish. `onEnd` fires once it has flushed. */
|
|
25
|
+
stop: () => void;
|
|
26
|
+
/** Tear the engine down without waiting for a flush. */
|
|
27
|
+
abort: () => void;
|
|
28
|
+
}
|
|
29
|
+
export interface DictationEngine {
|
|
30
|
+
/** Stable identifier surfaced in diagnostics (e.g. `web-speech`). */
|
|
31
|
+
readonly id: string;
|
|
32
|
+
/**
|
|
33
|
+
* Whether this engine can run in the current browser. When false the
|
|
34
|
+
* composer hides the mic instead of rendering a control that cannot work.
|
|
35
|
+
*/
|
|
36
|
+
isSupported: () => boolean;
|
|
37
|
+
/**
|
|
38
|
+
* How long `stop()` may wait for the final transcript before the session
|
|
39
|
+
* is forced closed. Engines that transcribe after recording need more than
|
|
40
|
+
* the live default (1.5s).
|
|
41
|
+
*/
|
|
42
|
+
readonly flushTimeoutMs?: number;
|
|
43
|
+
/** Status shown while flushing (e.g. `Transcribing…`); defaults to `Finishing…`. */
|
|
44
|
+
readonly stoppingLabel?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Start recognizing. `stream` is the microphone stream the composer already
|
|
47
|
+
* opened for the waveform; engines that read audio themselves (Web Speech)
|
|
48
|
+
* ignore it, relay engines encode it.
|
|
49
|
+
*/
|
|
50
|
+
start: (options: {
|
|
51
|
+
stream: MediaStream | null;
|
|
52
|
+
lang: string;
|
|
53
|
+
callbacks: DictationEngineCallbacks;
|
|
54
|
+
}) => DictationEngineSession;
|
|
55
|
+
}
|
|
56
|
+
export declare const DICTATION_ENGINE_ERROR_KINDS: {
|
|
57
|
+
/** The user denied the microphone, or the browser blocked it. */
|
|
58
|
+
readonly permission: 'permission';
|
|
59
|
+
/** No speech engine is available in this browser. */
|
|
60
|
+
readonly unsupported: 'unsupported';
|
|
61
|
+
/** The engine could not reach its recognizer. */
|
|
62
|
+
readonly connection: 'connection';
|
|
63
|
+
/** The engine heard nothing for its silence window. */
|
|
64
|
+
readonly noSpeech: 'no-speech';
|
|
65
|
+
readonly unknown: 'unknown';
|
|
66
|
+
};
|
|
67
|
+
export type DictationEngineErrorKind = (typeof DICTATION_ENGINE_ERROR_KINDS)[keyof typeof DICTATION_ENGINE_ERROR_KINDS];
|
|
68
|
+
export interface DictationEngineError {
|
|
69
|
+
kind: DictationEngineErrorKind;
|
|
70
|
+
/** One line the composer can show the user. */
|
|
71
|
+
message: string;
|
|
72
|
+
}
|
|
73
|
+
interface SpeechRecognitionAlternativeLike {
|
|
74
|
+
transcript: string;
|
|
75
|
+
}
|
|
76
|
+
interface SpeechRecognitionResultLike {
|
|
77
|
+
isFinal: boolean;
|
|
78
|
+
readonly length: number;
|
|
79
|
+
[index: number]: SpeechRecognitionAlternativeLike;
|
|
80
|
+
}
|
|
81
|
+
/** Map a Web Speech `error` code to the engine's error vocabulary. */
|
|
82
|
+
export declare function classifyWebSpeechError(code: string): DictationEngineError;
|
|
83
|
+
/**
|
|
84
|
+
* Fold one Web Speech result event into `{ final, interim }`. Finalized
|
|
85
|
+
* alternatives from `resultIndex` onward are joined with a space; the rest is
|
|
86
|
+
* the interim tail. Pure so it can be tested without a browser.
|
|
87
|
+
*/
|
|
88
|
+
export declare function foldWebSpeechResults(results: ArrayLike<SpeechRecognitionResultLike>, resultIndex: number): DictationTranscriptUpdate;
|
|
89
|
+
/**
|
|
90
|
+
* The browser's own recognizer. Zero dependencies and zero backend; Chrome
|
|
91
|
+
* and Edge stream partial results, Safari returns results in chunks, Firefox
|
|
92
|
+
* has no implementation (`isSupported()` is false there).
|
|
93
|
+
*/
|
|
94
|
+
export declare function createWebSpeechDictationEngine(): DictationEngine;
|
|
95
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const DICTATION_ENGINE_ERROR_KINDS = {
|
|
2
|
+
/** The user denied the microphone, or the browser blocked it. */
|
|
3
|
+
permission: "permission",
|
|
4
|
+
/** No speech engine is available in this browser. */
|
|
5
|
+
unsupported: "unsupported",
|
|
6
|
+
/** The engine could not reach its recognizer. */
|
|
7
|
+
connection: "connection",
|
|
8
|
+
/** The engine heard nothing for its silence window. */
|
|
9
|
+
noSpeech: "no-speech",
|
|
10
|
+
unknown: "unknown"
|
|
11
|
+
};
|
|
12
|
+
function resolveSpeechRecognitionCtor() {
|
|
13
|
+
if (typeof window === "undefined") return null;
|
|
14
|
+
const scope = window;
|
|
15
|
+
return scope.SpeechRecognition ?? scope.webkitSpeechRecognition ?? null;
|
|
16
|
+
}
|
|
17
|
+
function classifyWebSpeechError(code) {
|
|
18
|
+
switch (code) {
|
|
19
|
+
case "not-allowed":
|
|
20
|
+
case "service-not-allowed":
|
|
21
|
+
return {
|
|
22
|
+
kind: DICTATION_ENGINE_ERROR_KINDS.permission,
|
|
23
|
+
message: "Microphone access was denied. Allow the microphone for this site and try again."
|
|
24
|
+
};
|
|
25
|
+
case "network":
|
|
26
|
+
return {
|
|
27
|
+
kind: DICTATION_ENGINE_ERROR_KINDS.connection,
|
|
28
|
+
message: "Speech recognition could not connect. Check your connection and try again."
|
|
29
|
+
};
|
|
30
|
+
case "no-speech":
|
|
31
|
+
return {
|
|
32
|
+
kind: DICTATION_ENGINE_ERROR_KINDS.noSpeech,
|
|
33
|
+
message: "No speech was heard."
|
|
34
|
+
};
|
|
35
|
+
case "audio-capture":
|
|
36
|
+
return {
|
|
37
|
+
kind: DICTATION_ENGINE_ERROR_KINDS.permission,
|
|
38
|
+
message: "No microphone was found."
|
|
39
|
+
};
|
|
40
|
+
case "aborted":
|
|
41
|
+
return { kind: DICTATION_ENGINE_ERROR_KINDS.unknown, message: "Dictation was interrupted." };
|
|
42
|
+
default:
|
|
43
|
+
return { kind: DICTATION_ENGINE_ERROR_KINDS.unknown, message: "Dictation stopped unexpectedly." };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function foldWebSpeechResults(results, resultIndex) {
|
|
47
|
+
var _a;
|
|
48
|
+
let final = "";
|
|
49
|
+
let interim = "";
|
|
50
|
+
for (let i = resultIndex; i < results.length; i += 1) {
|
|
51
|
+
const result = results[i];
|
|
52
|
+
const transcript = ((_a = result == null ? void 0 : result[0]) == null ? void 0 : _a.transcript) ?? "";
|
|
53
|
+
if (!transcript) continue;
|
|
54
|
+
if (result.isFinal) final = final ? `${final} ${transcript.trim()}` : transcript.trim();
|
|
55
|
+
else interim = interim ? `${interim} ${transcript.trim()}` : transcript.trim();
|
|
56
|
+
}
|
|
57
|
+
return { final, interim };
|
|
58
|
+
}
|
|
59
|
+
function createWebSpeechDictationEngine() {
|
|
60
|
+
return {
|
|
61
|
+
id: "web-speech",
|
|
62
|
+
isSupported: () => resolveSpeechRecognitionCtor() !== null,
|
|
63
|
+
start: ({ lang, callbacks }) => {
|
|
64
|
+
const Ctor = resolveSpeechRecognitionCtor();
|
|
65
|
+
if (!Ctor) {
|
|
66
|
+
callbacks.onError({
|
|
67
|
+
kind: DICTATION_ENGINE_ERROR_KINDS.unsupported,
|
|
68
|
+
message: "This browser has no speech recognition."
|
|
69
|
+
});
|
|
70
|
+
callbacks.onEnd();
|
|
71
|
+
return { stop: () => void 0, abort: () => void 0 };
|
|
72
|
+
}
|
|
73
|
+
const recognition = new Ctor();
|
|
74
|
+
recognition.continuous = true;
|
|
75
|
+
recognition.interimResults = true;
|
|
76
|
+
recognition.lang = lang;
|
|
77
|
+
let ended = false;
|
|
78
|
+
const end = () => {
|
|
79
|
+
if (ended) return;
|
|
80
|
+
ended = true;
|
|
81
|
+
recognition.onresult = null;
|
|
82
|
+
recognition.onerror = null;
|
|
83
|
+
recognition.onend = null;
|
|
84
|
+
callbacks.onEnd();
|
|
85
|
+
};
|
|
86
|
+
recognition.onresult = (event) => {
|
|
87
|
+
callbacks.onTranscript(foldWebSpeechResults(event.results, event.resultIndex));
|
|
88
|
+
};
|
|
89
|
+
recognition.onerror = (event) => {
|
|
90
|
+
if (event.error === "aborted") return;
|
|
91
|
+
callbacks.onError(classifyWebSpeechError(event.error));
|
|
92
|
+
};
|
|
93
|
+
recognition.onend = end;
|
|
94
|
+
try {
|
|
95
|
+
recognition.start();
|
|
96
|
+
} catch (error) {
|
|
97
|
+
callbacks.onError({
|
|
98
|
+
kind: DICTATION_ENGINE_ERROR_KINDS.unknown,
|
|
99
|
+
message: error instanceof Error ? error.message : "Dictation could not start."
|
|
100
|
+
});
|
|
101
|
+
end();
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
stop: () => {
|
|
105
|
+
try {
|
|
106
|
+
recognition.stop();
|
|
107
|
+
} catch {
|
|
108
|
+
end();
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
abort: () => {
|
|
112
|
+
try {
|
|
113
|
+
recognition.abort();
|
|
114
|
+
} finally {
|
|
115
|
+
end();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
DICTATION_ENGINE_ERROR_KINDS,
|
|
124
|
+
classifyWebSpeechError,
|
|
125
|
+
createWebSpeechDictationEngine,
|
|
126
|
+
foldWebSpeechResults
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=dictation-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dictation-engine.js","sources":["../../src/dictation/dictation-engine.ts"],"sourcesContent":["/**\n * Speech-to-text engines behind the composer's dictation mode.\n *\n * An engine turns microphone audio into a stream of transcript updates. The\n * SDK ships the browser's own `SpeechRecognition` engine (Chrome, Edge,\n * Safari), which needs no backend. A host can plug in another engine (for\n * example a relay to a server-side recognizer) through the `engine` option\n * of `useDictation` / `<AthenaChat dictation>`; the composer UI is engine\n * agnostic.\n */\n\nexport interface DictationTranscriptUpdate {\n /** Text the engine has committed. Appended to earlier committed text. */\n final: string;\n /** Text still being recognized; replaced on the next update. */\n interim: string;\n}\n\nexport interface DictationEngineCallbacks {\n onTranscript: (update: DictationTranscriptUpdate) => void;\n /** The engine stopped on its own (silence timeout, network, permission). */\n onEnd: () => void;\n onError: (error: DictationEngineError) => void;\n}\n\nexport interface DictationEngineSession {\n /** Ask the engine to finish. `onEnd` fires once it has flushed. */\n stop: () => void;\n /** Tear the engine down without waiting for a flush. */\n abort: () => void;\n}\n\nexport interface DictationEngine {\n /** Stable identifier surfaced in diagnostics (e.g. `web-speech`). */\n readonly id: string;\n /**\n * Whether this engine can run in the current browser. When false the\n * composer hides the mic instead of rendering a control that cannot work.\n */\n isSupported: () => boolean;\n /**\n * How long `stop()` may wait for the final transcript before the session\n * is forced closed. Engines that transcribe after recording need more than\n * the live default (1.5s).\n */\n readonly flushTimeoutMs?: number;\n /** Status shown while flushing (e.g. `Transcribing…`); defaults to `Finishing…`. */\n readonly stoppingLabel?: string;\n /**\n * Start recognizing. `stream` is the microphone stream the composer already\n * opened for the waveform; engines that read audio themselves (Web Speech)\n * ignore it, relay engines encode it.\n */\n start: (options: {\n stream: MediaStream | null;\n lang: string;\n callbacks: DictationEngineCallbacks;\n }) => DictationEngineSession;\n}\n\nexport const DICTATION_ENGINE_ERROR_KINDS = {\n /** The user denied the microphone, or the browser blocked it. */\n permission: 'permission',\n /** No speech engine is available in this browser. */\n unsupported: 'unsupported',\n /** The engine could not reach its recognizer. */\n connection: 'connection',\n /** The engine heard nothing for its silence window. */\n noSpeech: 'no-speech',\n unknown: 'unknown',\n} as const;\n\nexport type DictationEngineErrorKind =\n (typeof DICTATION_ENGINE_ERROR_KINDS)[keyof typeof DICTATION_ENGINE_ERROR_KINDS];\n\nexport interface DictationEngineError {\n kind: DictationEngineErrorKind;\n /** One line the composer can show the user. */\n message: string;\n}\n\n// ─── Web Speech API engine ──────────────────────────────────────────\n\n// The Web Speech API has no lib.dom entry in every TypeScript version the SDK\n// supports, and browsers still vendor-prefix the constructor.\ninterface SpeechRecognitionAlternativeLike {\n transcript: string;\n}\ninterface SpeechRecognitionResultLike {\n isFinal: boolean;\n readonly length: number;\n [index: number]: SpeechRecognitionAlternativeLike;\n}\ninterface SpeechRecognitionEventLike extends Event {\n resultIndex: number;\n results: ArrayLike<SpeechRecognitionResultLike>;\n}\ninterface SpeechRecognitionErrorEventLike extends Event {\n error: string;\n message?: string;\n}\ninterface SpeechRecognitionLike extends EventTarget {\n continuous: boolean;\n interimResults: boolean;\n lang: string;\n start: () => void;\n stop: () => void;\n abort: () => void;\n onresult: ((event: SpeechRecognitionEventLike) => void) | null;\n onerror: ((event: SpeechRecognitionErrorEventLike) => void) | null;\n onend: (() => void) | null;\n}\ntype SpeechRecognitionCtor = new () => SpeechRecognitionLike;\n\nfunction resolveSpeechRecognitionCtor(): SpeechRecognitionCtor | null {\n if (typeof window === 'undefined') return null;\n const scope = window as unknown as {\n SpeechRecognition?: SpeechRecognitionCtor;\n webkitSpeechRecognition?: SpeechRecognitionCtor;\n };\n return scope.SpeechRecognition ?? scope.webkitSpeechRecognition ?? null;\n}\n\n/** Map a Web Speech `error` code to the engine's error vocabulary. */\nexport function classifyWebSpeechError(code: string): DictationEngineError {\n switch (code) {\n case 'not-allowed':\n case 'service-not-allowed':\n return {\n kind: DICTATION_ENGINE_ERROR_KINDS.permission,\n message: 'Microphone access was denied. Allow the microphone for this site and try again.',\n };\n case 'network':\n return {\n kind: DICTATION_ENGINE_ERROR_KINDS.connection,\n message: 'Speech recognition could not connect. Check your connection and try again.',\n };\n case 'no-speech':\n return {\n kind: DICTATION_ENGINE_ERROR_KINDS.noSpeech,\n message: 'No speech was heard.',\n };\n case 'audio-capture':\n return {\n kind: DICTATION_ENGINE_ERROR_KINDS.permission,\n message: 'No microphone was found.',\n };\n case 'aborted':\n return { kind: DICTATION_ENGINE_ERROR_KINDS.unknown, message: 'Dictation was interrupted.' };\n default:\n return { kind: DICTATION_ENGINE_ERROR_KINDS.unknown, message: 'Dictation stopped unexpectedly.' };\n }\n}\n\n/**\n * Fold one Web Speech result event into `{ final, interim }`. Finalized\n * alternatives from `resultIndex` onward are joined with a space; the rest is\n * the interim tail. Pure so it can be tested without a browser.\n */\nexport function foldWebSpeechResults(\n results: ArrayLike<SpeechRecognitionResultLike>,\n resultIndex: number,\n): DictationTranscriptUpdate {\n let final = '';\n let interim = '';\n for (let i = resultIndex; i < results.length; i += 1) {\n const result = results[i];\n const transcript = result?.[0]?.transcript ?? '';\n if (!transcript) continue;\n if (result.isFinal) final = final ? `${final} ${transcript.trim()}` : transcript.trim();\n else interim = interim ? `${interim} ${transcript.trim()}` : transcript.trim();\n }\n return { final, interim };\n}\n\n/**\n * The browser's own recognizer. Zero dependencies and zero backend; Chrome\n * and Edge stream partial results, Safari returns results in chunks, Firefox\n * has no implementation (`isSupported()` is false there).\n */\nexport function createWebSpeechDictationEngine(): DictationEngine {\n return {\n id: 'web-speech',\n isSupported: () => resolveSpeechRecognitionCtor() !== null,\n start: ({ lang, callbacks }) => {\n const Ctor = resolveSpeechRecognitionCtor();\n if (!Ctor) {\n callbacks.onError({\n kind: DICTATION_ENGINE_ERROR_KINDS.unsupported,\n message: 'This browser has no speech recognition.',\n });\n callbacks.onEnd();\n return { stop: () => undefined, abort: () => undefined };\n }\n const recognition = new Ctor();\n recognition.continuous = true;\n recognition.interimResults = true;\n recognition.lang = lang;\n let ended = false;\n const end = () => {\n if (ended) return;\n ended = true;\n recognition.onresult = null;\n recognition.onerror = null;\n recognition.onend = null;\n callbacks.onEnd();\n };\n recognition.onresult = (event) => {\n callbacks.onTranscript(foldWebSpeechResults(event.results, event.resultIndex));\n };\n recognition.onerror = (event) => {\n // `aborted` follows our own abort(); the session already ended.\n if (event.error === 'aborted') return;\n callbacks.onError(classifyWebSpeechError(event.error));\n };\n recognition.onend = end;\n try {\n recognition.start();\n } catch (error) {\n callbacks.onError({\n kind: DICTATION_ENGINE_ERROR_KINDS.unknown,\n message: error instanceof Error ? error.message : 'Dictation could not start.',\n });\n end();\n }\n return {\n stop: () => {\n try {\n recognition.stop();\n } catch {\n end();\n }\n },\n abort: () => {\n try {\n recognition.abort();\n } finally {\n end();\n }\n },\n };\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const DICTATION_STATUSES = {
|
|
4
|
+
idle: "idle",
|
|
5
|
+
starting: "starting",
|
|
6
|
+
listening: "listening",
|
|
7
|
+
stopping: "stopping",
|
|
8
|
+
error: "error"
|
|
9
|
+
};
|
|
10
|
+
const INITIAL_DICTATION_STATE = {
|
|
11
|
+
status: DICTATION_STATUSES.idle,
|
|
12
|
+
transcript: { final: "", interim: "" },
|
|
13
|
+
error: null
|
|
14
|
+
};
|
|
15
|
+
function joinSpeech(head, tail) {
|
|
16
|
+
const left = head.trim();
|
|
17
|
+
const right = tail.trim();
|
|
18
|
+
if (!left) return right;
|
|
19
|
+
if (!right) return left;
|
|
20
|
+
return `${left} ${right}`;
|
|
21
|
+
}
|
|
22
|
+
function dictationReducer(state, action) {
|
|
23
|
+
switch (action.type) {
|
|
24
|
+
case "start":
|
|
25
|
+
return { ...INITIAL_DICTATION_STATE, status: DICTATION_STATUSES.starting };
|
|
26
|
+
case "listening":
|
|
27
|
+
return state.status === DICTATION_STATUSES.starting ? { ...state, status: DICTATION_STATUSES.listening } : state;
|
|
28
|
+
case "transcript": {
|
|
29
|
+
if (state.status !== DICTATION_STATUSES.listening && state.status !== DICTATION_STATUSES.stopping) {
|
|
30
|
+
return state;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...state,
|
|
34
|
+
transcript: {
|
|
35
|
+
final: joinSpeech(state.transcript.final, action.update.final),
|
|
36
|
+
interim: action.update.interim
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
case "stop":
|
|
41
|
+
return state.status === DICTATION_STATUSES.listening || state.status === DICTATION_STATUSES.starting ? { ...state, status: DICTATION_STATUSES.stopping } : state;
|
|
42
|
+
case "ended":
|
|
43
|
+
return {
|
|
44
|
+
...state,
|
|
45
|
+
status: state.status === DICTATION_STATUSES.error ? DICTATION_STATUSES.error : DICTATION_STATUSES.idle,
|
|
46
|
+
transcript: { final: joinSpeech(state.transcript.final, state.transcript.interim), interim: "" }
|
|
47
|
+
};
|
|
48
|
+
case "cancel":
|
|
49
|
+
return INITIAL_DICTATION_STATE;
|
|
50
|
+
case "error":
|
|
51
|
+
return { ...state, status: DICTATION_STATUSES.error, error: action.error };
|
|
52
|
+
default:
|
|
53
|
+
return state;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function spokenText(transcript) {
|
|
57
|
+
return joinSpeech(transcript.final, transcript.interim);
|
|
58
|
+
}
|
|
59
|
+
function composeDictationDraft(existingDraft, spoken) {
|
|
60
|
+
return joinSpeech(existingDraft, spoken);
|
|
61
|
+
}
|
|
62
|
+
exports.DICTATION_STATUSES = DICTATION_STATUSES;
|
|
63
|
+
exports.INITIAL_DICTATION_STATE = INITIAL_DICTATION_STATE;
|
|
64
|
+
exports.composeDictationDraft = composeDictationDraft;
|
|
65
|
+
exports.dictationReducer = dictationReducer;
|
|
66
|
+
exports.joinSpeech = joinSpeech;
|
|
67
|
+
exports.spokenText = spokenText;
|
|
68
|
+
//# sourceMappingURL=dictation-state.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dictation-state.cjs","sources":["../../src/dictation/dictation-state.ts"],"sourcesContent":["import type { DictationEngineError, DictationTranscriptUpdate } from './dictation-engine';\n\n/**\n * The dictation session's state machine, kept pure so the transitions are\n * testable without a microphone.\n *\n * idle ──start──▶ starting ──engine ready──▶ listening ──stop──▶ stopping ──flushed──▶ idle\n * ▲ │ │ │\n * └────cancel──────┴──────────────────────────┴─────────────────────┘\n *\n * `error` is a terminal state shown inline until the next start or cancel.\n */\nexport const DICTATION_STATUSES = {\n idle: 'idle',\n starting: 'starting',\n listening: 'listening',\n stopping: 'stopping',\n error: 'error',\n} as const;\n\nexport type DictationStatus = (typeof DICTATION_STATUSES)[keyof typeof DICTATION_STATUSES];\n\nexport interface DictationTranscript {\n /** Everything the engine committed this session, space-joined. */\n final: string;\n /** The in-progress tail; replaced on every update. */\n interim: string;\n}\n\nexport interface DictationState {\n status: DictationStatus;\n transcript: DictationTranscript;\n error: DictationEngineError | null;\n}\n\nexport type DictationAction =\n | { type: 'start' }\n | { type: 'listening' }\n | { type: 'transcript'; update: DictationTranscriptUpdate }\n | { type: 'stop' }\n | { type: 'ended' }\n | { type: 'cancel' }\n | { type: 'error'; error: DictationEngineError };\n\nexport const INITIAL_DICTATION_STATE: DictationState = {\n status: DICTATION_STATUSES.idle,\n transcript: { final: '', interim: '' },\n error: null,\n};\n\n/** Join two transcript fragments with one space, ignoring empty sides. */\nexport function joinSpeech(head: string, tail: string): string {\n const left = head.trim();\n const right = tail.trim();\n if (!left) return right;\n if (!right) return left;\n return `${left} ${right}`;\n}\n\nexport function dictationReducer(state: DictationState, action: DictationAction): DictationState {\n switch (action.type) {\n case 'start':\n return { ...INITIAL_DICTATION_STATE, status: DICTATION_STATUSES.starting };\n case 'listening':\n return state.status === DICTATION_STATUSES.starting\n ? { ...state, status: DICTATION_STATUSES.listening }\n : state;\n case 'transcript': {\n if (state.status !== DICTATION_STATUSES.listening && state.status !== DICTATION_STATUSES.stopping) {\n return state;\n }\n return {\n ...state,\n transcript: {\n final: joinSpeech(state.transcript.final, action.update.final),\n interim: action.update.interim,\n },\n };\n }\n case 'stop':\n return state.status === DICTATION_STATUSES.listening || state.status === DICTATION_STATUSES.starting\n ? { ...state, status: DICTATION_STATUSES.stopping }\n : state;\n case 'ended':\n // A flush may still have an interim tail; commit it, the engine will\n // not refine it any further. An error reported before the engine closed\n // stays visible: only a start or cancel clears it.\n return {\n ...state,\n status: state.status === DICTATION_STATUSES.error ? DICTATION_STATUSES.error : DICTATION_STATUSES.idle,\n transcript: { final: joinSpeech(state.transcript.final, state.transcript.interim), interim: '' },\n };\n case 'cancel':\n return INITIAL_DICTATION_STATE;\n case 'error':\n return { ...state, status: DICTATION_STATUSES.error, error: action.error };\n default:\n return state;\n }\n}\n\n/** The text a dictation session hands to the composer once it ends. */\nexport function spokenText(transcript: DictationTranscript): string {\n return joinSpeech(transcript.final, transcript.interim);\n}\n\n/**\n * Merge spoken text into the draft that existed before recording started.\n * Manual edits are kept; the speech is appended after one space.\n */\nexport function composeDictationDraft(existingDraft: string, spoken: string): string {\n return joinSpeech(existingDraft, spoken);\n}\n"],"names":[],"mappings":";;AAYO,MAAM,qBAAqB;AAAA,EAChC,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AAAA,EACV,OAAO;AACT;AA0BO,MAAM,0BAA0C;AAAA,EACrD,QAAQ,mBAAmB;AAAA,EAC3B,YAAY,EAAE,OAAO,IAAI,SAAS,GAAA;AAAA,EAClC,OAAO;AACT;AAGO,SAAS,WAAW,MAAc,MAAsB;AAC7D,QAAM,OAAO,KAAK,KAAA;AAClB,QAAM,QAAQ,KAAK,KAAA;AACnB,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,GAAG,IAAI,IAAI,KAAK;AACzB;AAEO,SAAS,iBAAiB,OAAuB,QAAyC;AAC/F,UAAQ,OAAO,MAAA;AAAA,IACb,KAAK;AACH,aAAO,EAAE,GAAG,yBAAyB,QAAQ,mBAAmB,SAAA;AAAA,IAClE,KAAK;AACH,aAAO,MAAM,WAAW,mBAAmB,WACvC,EAAE,GAAG,OAAO,QAAQ,mBAAmB,UAAA,IACvC;AAAA,IACN,KAAK,cAAc;AACjB,UAAI,MAAM,WAAW,mBAAmB,aAAa,MAAM,WAAW,mBAAmB,UAAU;AACjG,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AAAA,UACV,OAAO,WAAW,MAAM,WAAW,OAAO,OAAO,OAAO,KAAK;AAAA,UAC7D,SAAS,OAAO,OAAO;AAAA,QAAA;AAAA,MACzB;AAAA,IAEJ;AAAA,IACA,KAAK;AACH,aAAO,MAAM,WAAW,mBAAmB,aAAa,MAAM,WAAW,mBAAmB,WACxF,EAAE,GAAG,OAAO,QAAQ,mBAAmB,aACvC;AAAA,IACN,KAAK;AAIH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,QAAQ,MAAM,WAAW,mBAAmB,QAAQ,mBAAmB,QAAQ,mBAAmB;AAAA,QAClG,YAAY,EAAE,OAAO,WAAW,MAAM,WAAW,OAAO,MAAM,WAAW,OAAO,GAAG,SAAS,GAAA;AAAA,MAAG;AAAA,IAEnG,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,mBAAmB,OAAO,OAAO,OAAO,MAAA;AAAA,IACrE;AACE,aAAO;AAAA,EAAA;AAEb;AAGO,SAAS,WAAW,YAAyC;AAClE,SAAO,WAAW,WAAW,OAAO,WAAW,OAAO;AACxD;AAMO,SAAS,sBAAsB,eAAuB,QAAwB;AACnF,SAAO,WAAW,eAAe,MAAM;AACzC;;;;;;;"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { DictationEngineError, DictationTranscriptUpdate } from './dictation-engine';
|
|
2
|
+
/**
|
|
3
|
+
* The dictation session's state machine, kept pure so the transitions are
|
|
4
|
+
* testable without a microphone.
|
|
5
|
+
*
|
|
6
|
+
* idle ──start──▶ starting ──engine ready──▶ listening ──stop──▶ stopping ──flushed──▶ idle
|
|
7
|
+
* ▲ │ │ │
|
|
8
|
+
* └────cancel──────┴──────────────────────────┴─────────────────────┘
|
|
9
|
+
*
|
|
10
|
+
* `error` is a terminal state shown inline until the next start or cancel.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DICTATION_STATUSES: {
|
|
13
|
+
readonly idle: 'idle';
|
|
14
|
+
readonly starting: 'starting';
|
|
15
|
+
readonly listening: 'listening';
|
|
16
|
+
readonly stopping: 'stopping';
|
|
17
|
+
readonly error: 'error';
|
|
18
|
+
};
|
|
19
|
+
export type DictationStatus = (typeof DICTATION_STATUSES)[keyof typeof DICTATION_STATUSES];
|
|
20
|
+
export interface DictationTranscript {
|
|
21
|
+
/** Everything the engine committed this session, space-joined. */
|
|
22
|
+
final: string;
|
|
23
|
+
/** The in-progress tail; replaced on every update. */
|
|
24
|
+
interim: string;
|
|
25
|
+
}
|
|
26
|
+
export interface DictationState {
|
|
27
|
+
status: DictationStatus;
|
|
28
|
+
transcript: DictationTranscript;
|
|
29
|
+
error: DictationEngineError | null;
|
|
30
|
+
}
|
|
31
|
+
export type DictationAction = {
|
|
32
|
+
type: 'start';
|
|
33
|
+
} | {
|
|
34
|
+
type: 'listening';
|
|
35
|
+
} | {
|
|
36
|
+
type: 'transcript';
|
|
37
|
+
update: DictationTranscriptUpdate;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'stop';
|
|
40
|
+
} | {
|
|
41
|
+
type: 'ended';
|
|
42
|
+
} | {
|
|
43
|
+
type: 'cancel';
|
|
44
|
+
} | {
|
|
45
|
+
type: 'error';
|
|
46
|
+
error: DictationEngineError;
|
|
47
|
+
};
|
|
48
|
+
export declare const INITIAL_DICTATION_STATE: DictationState;
|
|
49
|
+
/** Join two transcript fragments with one space, ignoring empty sides. */
|
|
50
|
+
export declare function joinSpeech(head: string, tail: string): string;
|
|
51
|
+
export declare function dictationReducer(state: DictationState, action: DictationAction): DictationState;
|
|
52
|
+
/** The text a dictation session hands to the composer once it ends. */
|
|
53
|
+
export declare function spokenText(transcript: DictationTranscript): string;
|
|
54
|
+
/**
|
|
55
|
+
* Merge spoken text into the draft that existed before recording started.
|
|
56
|
+
* Manual edits are kept; the speech is appended after one space.
|
|
57
|
+
*/
|
|
58
|
+
export declare function composeDictationDraft(existingDraft: string, spoken: string): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const DICTATION_STATUSES = {
|
|
2
|
+
idle: "idle",
|
|
3
|
+
starting: "starting",
|
|
4
|
+
listening: "listening",
|
|
5
|
+
stopping: "stopping",
|
|
6
|
+
error: "error"
|
|
7
|
+
};
|
|
8
|
+
const INITIAL_DICTATION_STATE = {
|
|
9
|
+
status: DICTATION_STATUSES.idle,
|
|
10
|
+
transcript: { final: "", interim: "" },
|
|
11
|
+
error: null
|
|
12
|
+
};
|
|
13
|
+
function joinSpeech(head, tail) {
|
|
14
|
+
const left = head.trim();
|
|
15
|
+
const right = tail.trim();
|
|
16
|
+
if (!left) return right;
|
|
17
|
+
if (!right) return left;
|
|
18
|
+
return `${left} ${right}`;
|
|
19
|
+
}
|
|
20
|
+
function dictationReducer(state, action) {
|
|
21
|
+
switch (action.type) {
|
|
22
|
+
case "start":
|
|
23
|
+
return { ...INITIAL_DICTATION_STATE, status: DICTATION_STATUSES.starting };
|
|
24
|
+
case "listening":
|
|
25
|
+
return state.status === DICTATION_STATUSES.starting ? { ...state, status: DICTATION_STATUSES.listening } : state;
|
|
26
|
+
case "transcript": {
|
|
27
|
+
if (state.status !== DICTATION_STATUSES.listening && state.status !== DICTATION_STATUSES.stopping) {
|
|
28
|
+
return state;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...state,
|
|
32
|
+
transcript: {
|
|
33
|
+
final: joinSpeech(state.transcript.final, action.update.final),
|
|
34
|
+
interim: action.update.interim
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
case "stop":
|
|
39
|
+
return state.status === DICTATION_STATUSES.listening || state.status === DICTATION_STATUSES.starting ? { ...state, status: DICTATION_STATUSES.stopping } : state;
|
|
40
|
+
case "ended":
|
|
41
|
+
return {
|
|
42
|
+
...state,
|
|
43
|
+
status: state.status === DICTATION_STATUSES.error ? DICTATION_STATUSES.error : DICTATION_STATUSES.idle,
|
|
44
|
+
transcript: { final: joinSpeech(state.transcript.final, state.transcript.interim), interim: "" }
|
|
45
|
+
};
|
|
46
|
+
case "cancel":
|
|
47
|
+
return INITIAL_DICTATION_STATE;
|
|
48
|
+
case "error":
|
|
49
|
+
return { ...state, status: DICTATION_STATUSES.error, error: action.error };
|
|
50
|
+
default:
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function spokenText(transcript) {
|
|
55
|
+
return joinSpeech(transcript.final, transcript.interim);
|
|
56
|
+
}
|
|
57
|
+
function composeDictationDraft(existingDraft, spoken) {
|
|
58
|
+
return joinSpeech(existingDraft, spoken);
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
DICTATION_STATUSES,
|
|
62
|
+
INITIAL_DICTATION_STATE,
|
|
63
|
+
composeDictationDraft,
|
|
64
|
+
dictationReducer,
|
|
65
|
+
joinSpeech,
|
|
66
|
+
spokenText
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=dictation-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dictation-state.js","sources":["../../src/dictation/dictation-state.ts"],"sourcesContent":["import type { DictationEngineError, DictationTranscriptUpdate } from './dictation-engine';\n\n/**\n * The dictation session's state machine, kept pure so the transitions are\n * testable without a microphone.\n *\n * idle ──start──▶ starting ──engine ready──▶ listening ──stop──▶ stopping ──flushed──▶ idle\n * ▲ │ │ │\n * └────cancel──────┴──────────────────────────┴─────────────────────┘\n *\n * `error` is a terminal state shown inline until the next start or cancel.\n */\nexport const DICTATION_STATUSES = {\n idle: 'idle',\n starting: 'starting',\n listening: 'listening',\n stopping: 'stopping',\n error: 'error',\n} as const;\n\nexport type DictationStatus = (typeof DICTATION_STATUSES)[keyof typeof DICTATION_STATUSES];\n\nexport interface DictationTranscript {\n /** Everything the engine committed this session, space-joined. */\n final: string;\n /** The in-progress tail; replaced on every update. */\n interim: string;\n}\n\nexport interface DictationState {\n status: DictationStatus;\n transcript: DictationTranscript;\n error: DictationEngineError | null;\n}\n\nexport type DictationAction =\n | { type: 'start' }\n | { type: 'listening' }\n | { type: 'transcript'; update: DictationTranscriptUpdate }\n | { type: 'stop' }\n | { type: 'ended' }\n | { type: 'cancel' }\n | { type: 'error'; error: DictationEngineError };\n\nexport const INITIAL_DICTATION_STATE: DictationState = {\n status: DICTATION_STATUSES.idle,\n transcript: { final: '', interim: '' },\n error: null,\n};\n\n/** Join two transcript fragments with one space, ignoring empty sides. */\nexport function joinSpeech(head: string, tail: string): string {\n const left = head.trim();\n const right = tail.trim();\n if (!left) return right;\n if (!right) return left;\n return `${left} ${right}`;\n}\n\nexport function dictationReducer(state: DictationState, action: DictationAction): DictationState {\n switch (action.type) {\n case 'start':\n return { ...INITIAL_DICTATION_STATE, status: DICTATION_STATUSES.starting };\n case 'listening':\n return state.status === DICTATION_STATUSES.starting\n ? { ...state, status: DICTATION_STATUSES.listening }\n : state;\n case 'transcript': {\n if (state.status !== DICTATION_STATUSES.listening && state.status !== DICTATION_STATUSES.stopping) {\n return state;\n }\n return {\n ...state,\n transcript: {\n final: joinSpeech(state.transcript.final, action.update.final),\n interim: action.update.interim,\n },\n };\n }\n case 'stop':\n return state.status === DICTATION_STATUSES.listening || state.status === DICTATION_STATUSES.starting\n ? { ...state, status: DICTATION_STATUSES.stopping }\n : state;\n case 'ended':\n // A flush may still have an interim tail; commit it, the engine will\n // not refine it any further. An error reported before the engine closed\n // stays visible: only a start or cancel clears it.\n return {\n ...state,\n status: state.status === DICTATION_STATUSES.error ? DICTATION_STATUSES.error : DICTATION_STATUSES.idle,\n transcript: { final: joinSpeech(state.transcript.final, state.transcript.interim), interim: '' },\n };\n case 'cancel':\n return INITIAL_DICTATION_STATE;\n case 'error':\n return { ...state, status: DICTATION_STATUSES.error, error: action.error };\n default:\n return state;\n }\n}\n\n/** The text a dictation session hands to the composer once it ends. */\nexport function spokenText(transcript: DictationTranscript): string {\n return joinSpeech(transcript.final, transcript.interim);\n}\n\n/**\n * Merge spoken text into the draft that existed before recording started.\n * Manual edits are kept; the speech is appended after one space.\n */\nexport function composeDictationDraft(existingDraft: string, spoken: string): string {\n return joinSpeech(existingDraft, spoken);\n}\n"],"names":[],"mappings":"AAYO,MAAM,qBAAqB;AAAA,EAChC,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AAAA,EACV,OAAO;AACT;AA0BO,MAAM,0BAA0C;AAAA,EACrD,QAAQ,mBAAmB;AAAA,EAC3B,YAAY,EAAE,OAAO,IAAI,SAAS,GAAA;AAAA,EAClC,OAAO;AACT;AAGO,SAAS,WAAW,MAAc,MAAsB;AAC7D,QAAM,OAAO,KAAK,KAAA;AAClB,QAAM,QAAQ,KAAK,KAAA;AACnB,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,GAAG,IAAI,IAAI,KAAK;AACzB;AAEO,SAAS,iBAAiB,OAAuB,QAAyC;AAC/F,UAAQ,OAAO,MAAA;AAAA,IACb,KAAK;AACH,aAAO,EAAE,GAAG,yBAAyB,QAAQ,mBAAmB,SAAA;AAAA,IAClE,KAAK;AACH,aAAO,MAAM,WAAW,mBAAmB,WACvC,EAAE,GAAG,OAAO,QAAQ,mBAAmB,UAAA,IACvC;AAAA,IACN,KAAK,cAAc;AACjB,UAAI,MAAM,WAAW,mBAAmB,aAAa,MAAM,WAAW,mBAAmB,UAAU;AACjG,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AAAA,UACV,OAAO,WAAW,MAAM,WAAW,OAAO,OAAO,OAAO,KAAK;AAAA,UAC7D,SAAS,OAAO,OAAO;AAAA,QAAA;AAAA,MACzB;AAAA,IAEJ;AAAA,IACA,KAAK;AACH,aAAO,MAAM,WAAW,mBAAmB,aAAa,MAAM,WAAW,mBAAmB,WACxF,EAAE,GAAG,OAAO,QAAQ,mBAAmB,aACvC;AAAA,IACN,KAAK;AAIH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,QAAQ,MAAM,WAAW,mBAAmB,QAAQ,mBAAmB,QAAQ,mBAAmB;AAAA,QAClG,YAAY,EAAE,OAAO,WAAW,MAAM,WAAW,OAAO,MAAM,WAAW,OAAO,GAAG,SAAS,GAAA;AAAA,MAAG;AAAA,IAEnG,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,mBAAmB,OAAO,OAAO,OAAO,MAAA;AAAA,IACrE;AACE,aAAO;AAAA,EAAA;AAEb;AAGO,SAAS,WAAW,YAAyC;AAClE,SAAO,WAAW,WAAW,OAAO,WAAW,OAAO;AACxD;AAMO,SAAS,sBAAsB,eAAuB,QAAwB;AACnF,SAAO,WAAW,eAAe,MAAM;AACzC;"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const dictationEngine = require("./dictation-engine.cjs");
|
|
4
|
+
const DEFAULT_MIME_TYPES = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4", "audio/ogg;codecs=opus"];
|
|
5
|
+
const DEFAULT_FLUSH_TIMEOUT_MS = 9e4;
|
|
6
|
+
const DEFAULT_MAX_RECORDING_MS = 5 * 6e4;
|
|
7
|
+
function resolveMediaRecorderCtor() {
|
|
8
|
+
if (typeof window === "undefined") return null;
|
|
9
|
+
return window.MediaRecorder ?? null;
|
|
10
|
+
}
|
|
11
|
+
function pickRecorderMimeType(Ctor, candidates) {
|
|
12
|
+
if (!Ctor.isTypeSupported) return void 0;
|
|
13
|
+
return candidates.find((type) => {
|
|
14
|
+
var _a;
|
|
15
|
+
return (_a = Ctor.isTypeSupported) == null ? void 0 : _a.call(Ctor, type);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function transcriptionError(error) {
|
|
19
|
+
return {
|
|
20
|
+
kind: dictationEngine.DICTATION_ENGINE_ERROR_KINDS.connection,
|
|
21
|
+
message: error instanceof Error && error.message ? error.message : "Transcription failed. Try again."
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function createRecordedDictationEngine(options) {
|
|
25
|
+
const {
|
|
26
|
+
transcribe,
|
|
27
|
+
mimeTypes = DEFAULT_MIME_TYPES,
|
|
28
|
+
flushTimeoutMs = DEFAULT_FLUSH_TIMEOUT_MS,
|
|
29
|
+
maxDurationMs = DEFAULT_MAX_RECORDING_MS
|
|
30
|
+
} = options;
|
|
31
|
+
return {
|
|
32
|
+
id: "recorded-upload",
|
|
33
|
+
flushTimeoutMs,
|
|
34
|
+
stoppingLabel: "Transcribing…",
|
|
35
|
+
isSupported: () => {
|
|
36
|
+
var _a;
|
|
37
|
+
return resolveMediaRecorderCtor() !== null && typeof navigator !== "undefined" && typeof ((_a = navigator.mediaDevices) == null ? void 0 : _a.getUserMedia) === "function";
|
|
38
|
+
},
|
|
39
|
+
start: ({ stream, lang, callbacks }) => {
|
|
40
|
+
const Ctor = resolveMediaRecorderCtor();
|
|
41
|
+
const noop = { stop: () => void 0, abort: () => void 0 };
|
|
42
|
+
if (!Ctor || !stream) {
|
|
43
|
+
callbacks.onError({
|
|
44
|
+
kind: !Ctor ? dictationEngine.DICTATION_ENGINE_ERROR_KINDS.unsupported : dictationEngine.DICTATION_ENGINE_ERROR_KINDS.permission,
|
|
45
|
+
message: !Ctor ? "This browser cannot record audio." : "No microphone stream was available."
|
|
46
|
+
});
|
|
47
|
+
callbacks.onEnd();
|
|
48
|
+
return noop;
|
|
49
|
+
}
|
|
50
|
+
const mimeType = pickRecorderMimeType(Ctor, mimeTypes);
|
|
51
|
+
let recorder;
|
|
52
|
+
try {
|
|
53
|
+
recorder = new Ctor(stream, mimeType ? { mimeType } : void 0);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
callbacks.onError({
|
|
56
|
+
kind: dictationEngine.DICTATION_ENGINE_ERROR_KINDS.unknown,
|
|
57
|
+
message: error instanceof Error ? error.message : "Recording could not start."
|
|
58
|
+
});
|
|
59
|
+
callbacks.onEnd();
|
|
60
|
+
return noop;
|
|
61
|
+
}
|
|
62
|
+
const chunks = [];
|
|
63
|
+
const upload = new AbortController();
|
|
64
|
+
let ended = false;
|
|
65
|
+
let aborted = false;
|
|
66
|
+
let limitTimer;
|
|
67
|
+
const clearLimit = () => {
|
|
68
|
+
if (limitTimer === void 0) return;
|
|
69
|
+
clearTimeout(limitTimer);
|
|
70
|
+
limitTimer = void 0;
|
|
71
|
+
};
|
|
72
|
+
const end = () => {
|
|
73
|
+
if (ended) return;
|
|
74
|
+
ended = true;
|
|
75
|
+
clearLimit();
|
|
76
|
+
recorder.ondataavailable = null;
|
|
77
|
+
recorder.onstop = null;
|
|
78
|
+
recorder.onerror = null;
|
|
79
|
+
callbacks.onEnd();
|
|
80
|
+
};
|
|
81
|
+
const stopRecorder = () => {
|
|
82
|
+
if (recorder.state === "inactive") return false;
|
|
83
|
+
try {
|
|
84
|
+
recorder.stop();
|
|
85
|
+
return true;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
recorder.ondataavailable = (event) => {
|
|
91
|
+
if (event.data && event.data.size > 0) chunks.push(event.data);
|
|
92
|
+
};
|
|
93
|
+
recorder.onerror = () => {
|
|
94
|
+
callbacks.onError({
|
|
95
|
+
kind: dictationEngine.DICTATION_ENGINE_ERROR_KINDS.unknown,
|
|
96
|
+
message: "Recording stopped unexpectedly."
|
|
97
|
+
});
|
|
98
|
+
end();
|
|
99
|
+
};
|
|
100
|
+
recorder.onstop = () => {
|
|
101
|
+
if (aborted || ended) {
|
|
102
|
+
end();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const clip = new Blob(chunks, { type: recorder.mimeType || mimeType || "audio/webm" });
|
|
106
|
+
if (clip.size === 0) {
|
|
107
|
+
end();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
void transcribe(clip, { lang, signal: upload.signal }).then((text) => {
|
|
111
|
+
if (ended) return;
|
|
112
|
+
const trimmed = text.trim();
|
|
113
|
+
if (trimmed) callbacks.onTranscript({ final: trimmed, interim: "" });
|
|
114
|
+
}).catch((error) => {
|
|
115
|
+
if (ended) return;
|
|
116
|
+
callbacks.onError(transcriptionError(error));
|
|
117
|
+
}).finally(end);
|
|
118
|
+
};
|
|
119
|
+
try {
|
|
120
|
+
recorder.start();
|
|
121
|
+
} catch (error) {
|
|
122
|
+
callbacks.onError({
|
|
123
|
+
kind: dictationEngine.DICTATION_ENGINE_ERROR_KINDS.unknown,
|
|
124
|
+
message: error instanceof Error ? error.message : "Recording could not start."
|
|
125
|
+
});
|
|
126
|
+
end();
|
|
127
|
+
return noop;
|
|
128
|
+
}
|
|
129
|
+
if (Number.isFinite(maxDurationMs) && maxDurationMs > 0) {
|
|
130
|
+
limitTimer = setTimeout(() => {
|
|
131
|
+
limitTimer = void 0;
|
|
132
|
+
if (!ended) stopRecorder();
|
|
133
|
+
}, maxDurationMs);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
stop: () => {
|
|
137
|
+
clearLimit();
|
|
138
|
+
if (!stopRecorder()) end();
|
|
139
|
+
},
|
|
140
|
+
abort: () => {
|
|
141
|
+
aborted = true;
|
|
142
|
+
upload.abort();
|
|
143
|
+
stopRecorder();
|
|
144
|
+
end();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
exports.DEFAULT_MAX_RECORDING_MS = DEFAULT_MAX_RECORDING_MS;
|
|
151
|
+
exports.createRecordedDictationEngine = createRecordedDictationEngine;
|
|
152
|
+
exports.pickRecorderMimeType = pickRecorderMimeType;
|
|
153
|
+
//# sourceMappingURL=recorded-dictation-engine.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorded-dictation-engine.cjs","sources":["../../src/dictation/recorded-dictation-engine.ts"],"sourcesContent":["import {\n DICTATION_ENGINE_ERROR_KINDS,\n type DictationEngine,\n type DictationEngineError,\n} from './dictation-engine';\n\nexport interface DictationTranscribeContext {\n /** BCP-47 tag the session was started with (host `lang` or the document's). */\n lang: string;\n /** Fires when the user cancels or the composer gives up waiting. */\n signal: AbortSignal;\n}\n\n/**\n * Turn a finished recording into text. Receives the whole clip once the user\n * stops; resolves with the transcript (empty when nothing was said). Should\n * honour `signal` so a cancelled dictation does not keep uploading.\n */\nexport type DictationTranscriber = (clip: Blob, context: DictationTranscribeContext) => Promise<string>;\n\nexport interface RecordedDictationEngineOptions {\n transcribe: DictationTranscriber;\n /**\n * Preferred `MediaRecorder` container, tried in order until the browser\n * accepts one. Defaults to Opus in WebM (Chrome, Firefox, Edge) then MP4\n * (Safari); the browser's own default is the last resort.\n */\n mimeTypes?: readonly string[];\n /**\n * Upper bound on the transcription round trip; the in-flight request is\n * aborted when it elapses. Defaults to 90s, above agora's 60s Deepgram\n * budget plus upload time.\n */\n flushTimeoutMs?: number;\n /**\n * Longest recording the engine accepts before it stops on its own and\n * transcribes what it has, exactly as if Stop had been pressed. Bounds a\n * mic left open by mistake: every recorded minute is a minute billed by the\n * recognizer, and agora refuses clips over its size cap. Defaults to\n * 5 minutes; pass `Infinity` to disable.\n */\n maxDurationMs?: number;\n}\n\nconst DEFAULT_MIME_TYPES = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/ogg;codecs=opus'];\nconst DEFAULT_FLUSH_TIMEOUT_MS = 90_000;\nexport const DEFAULT_MAX_RECORDING_MS = 5 * 60_000;\n\n// lib.dom typings for MediaRecorder vary across the TypeScript versions the\n// SDK supports; describe only what the engine touches.\ninterface MediaRecorderLike extends EventTarget {\n readonly state: 'inactive' | 'recording' | 'paused';\n readonly mimeType: string;\n start: (timeslice?: number) => void;\n stop: () => void;\n ondataavailable: ((event: { data: Blob }) => void) | null;\n onstop: (() => void) | null;\n onerror: ((event: Event) => void) | null;\n}\ninterface MediaRecorderCtor {\n new (stream: MediaStream, options?: { mimeType?: string }): MediaRecorderLike;\n isTypeSupported?: (type: string) => boolean;\n}\n\nfunction resolveMediaRecorderCtor(): MediaRecorderCtor | null {\n if (typeof window === 'undefined') return null;\n return (window as unknown as { MediaRecorder?: MediaRecorderCtor }).MediaRecorder ?? null;\n}\n\n/** First container the recorder accepts, or undefined for the browser default. */\nexport function pickRecorderMimeType(\n Ctor: Pick<MediaRecorderCtor, 'isTypeSupported'>,\n candidates: readonly string[],\n): string | undefined {\n if (!Ctor.isTypeSupported) return undefined;\n return candidates.find((type) => Ctor.isTypeSupported?.(type));\n}\n\nfunction transcriptionError(error: unknown): DictationEngineError {\n return {\n kind: DICTATION_ENGINE_ERROR_KINDS.connection,\n message: error instanceof Error && error.message ? error.message : 'Transcription failed. Try again.',\n };\n}\n\n/**\n * Record-then-transcribe: capture the microphone with `MediaRecorder` while\n * the user talks, then hand the finished clip to `transcribe` on stop. No\n * interim words are shown; the composer shows a \"transcribing\" state until\n * the text lands. Works in every browser with `MediaRecorder`, so the mic is\n * available where Web Speech is not (Firefox) and the quality is whatever the\n * server-side recognizer provides.\n */\nexport function createRecordedDictationEngine(options: RecordedDictationEngineOptions): DictationEngine {\n const {\n transcribe,\n mimeTypes = DEFAULT_MIME_TYPES,\n flushTimeoutMs = DEFAULT_FLUSH_TIMEOUT_MS,\n maxDurationMs = DEFAULT_MAX_RECORDING_MS,\n } = options;\n return {\n id: 'recorded-upload',\n flushTimeoutMs,\n stoppingLabel: 'Transcribing…',\n isSupported: () =>\n resolveMediaRecorderCtor() !== null &&\n typeof navigator !== 'undefined' &&\n typeof navigator.mediaDevices?.getUserMedia === 'function',\n start: ({ stream, lang, callbacks }) => {\n const Ctor = resolveMediaRecorderCtor();\n const noop = { stop: () => undefined, abort: () => undefined };\n if (!Ctor || !stream) {\n callbacks.onError({\n kind: !Ctor ? DICTATION_ENGINE_ERROR_KINDS.unsupported : DICTATION_ENGINE_ERROR_KINDS.permission,\n message: !Ctor ? 'This browser cannot record audio.' : 'No microphone stream was available.',\n });\n callbacks.onEnd();\n return noop;\n }\n\n const mimeType = pickRecorderMimeType(Ctor, mimeTypes);\n let recorder: MediaRecorderLike;\n try {\n recorder = new Ctor(stream, mimeType ? { mimeType } : undefined);\n } catch (error) {\n callbacks.onError({\n kind: DICTATION_ENGINE_ERROR_KINDS.unknown,\n message: error instanceof Error ? error.message : 'Recording could not start.',\n });\n callbacks.onEnd();\n return noop;\n }\n\n const chunks: Blob[] = [];\n const upload = new AbortController();\n let ended = false;\n let aborted = false;\n let limitTimer: ReturnType<typeof setTimeout> | undefined;\n const clearLimit = () => {\n if (limitTimer === undefined) return;\n clearTimeout(limitTimer);\n limitTimer = undefined;\n };\n const end = () => {\n if (ended) return;\n ended = true;\n clearLimit();\n recorder.ondataavailable = null;\n recorder.onstop = null;\n recorder.onerror = null;\n callbacks.onEnd();\n };\n const stopRecorder = () => {\n if (recorder.state === 'inactive') return false;\n try {\n recorder.stop();\n return true;\n } catch {\n return false;\n }\n };\n\n recorder.ondataavailable = (event) => {\n if (event.data && event.data.size > 0) chunks.push(event.data);\n };\n recorder.onerror = () => {\n callbacks.onError({\n kind: DICTATION_ENGINE_ERROR_KINDS.unknown,\n message: 'Recording stopped unexpectedly.',\n });\n end();\n };\n recorder.onstop = () => {\n if (aborted || ended) {\n end();\n return;\n }\n const clip = new Blob(chunks, { type: recorder.mimeType || mimeType || 'audio/webm' });\n if (clip.size === 0) {\n end();\n return;\n }\n void transcribe(clip, { lang, signal: upload.signal })\n .then((text) => {\n if (ended) return;\n const trimmed = text.trim();\n if (trimmed) callbacks.onTranscript({ final: trimmed, interim: '' });\n })\n .catch((error: unknown) => {\n if (ended) return;\n callbacks.onError(transcriptionError(error));\n })\n .finally(end);\n };\n\n try {\n recorder.start();\n } catch (error) {\n callbacks.onError({\n kind: DICTATION_ENGINE_ERROR_KINDS.unknown,\n message: error instanceof Error ? error.message : 'Recording could not start.',\n });\n end();\n return noop;\n }\n\n if (Number.isFinite(maxDurationMs) && maxDurationMs > 0) {\n limitTimer = setTimeout(() => {\n limitTimer = undefined;\n // Same path as the user's Stop: onstop transcribes the clip and the\n // words land in the draft. A recorder that is already inactive is a\n // stop in flight, which must run to completion untouched.\n if (!ended) stopRecorder();\n }, maxDurationMs);\n }\n\n return {\n stop: () => {\n clearLimit();\n if (!stopRecorder()) end();\n },\n abort: () => {\n aborted = true;\n upload.abort();\n stopRecorder();\n end();\n },\n };\n },\n };\n}\n"],"names":["DICTATION_ENGINE_ERROR_KINDS"],"mappings":"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"}
|