@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,66 @@
|
|
|
1
|
+
import { type DictationEngine } from './dictation-engine';
|
|
2
|
+
export interface DictationTranscribeContext {
|
|
3
|
+
/** BCP-47 tag the session was started with (host `lang` or the document's). */
|
|
4
|
+
lang: string;
|
|
5
|
+
/** Fires when the user cancels or the composer gives up waiting. */
|
|
6
|
+
signal: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Turn a finished recording into text. Receives the whole clip once the user
|
|
10
|
+
* stops; resolves with the transcript (empty when nothing was said). Should
|
|
11
|
+
* honour `signal` so a cancelled dictation does not keep uploading.
|
|
12
|
+
*/
|
|
13
|
+
export type DictationTranscriber = (clip: Blob, context: DictationTranscribeContext) => Promise<string>;
|
|
14
|
+
export interface RecordedDictationEngineOptions {
|
|
15
|
+
transcribe: DictationTranscriber;
|
|
16
|
+
/**
|
|
17
|
+
* Preferred `MediaRecorder` container, tried in order until the browser
|
|
18
|
+
* accepts one. Defaults to Opus in WebM (Chrome, Firefox, Edge) then MP4
|
|
19
|
+
* (Safari); the browser's own default is the last resort.
|
|
20
|
+
*/
|
|
21
|
+
mimeTypes?: readonly string[];
|
|
22
|
+
/**
|
|
23
|
+
* Upper bound on the transcription round trip; the in-flight request is
|
|
24
|
+
* aborted when it elapses. Defaults to 90s, above agora's 60s Deepgram
|
|
25
|
+
* budget plus upload time.
|
|
26
|
+
*/
|
|
27
|
+
flushTimeoutMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Longest recording the engine accepts before it stops on its own and
|
|
30
|
+
* transcribes what it has, exactly as if Stop had been pressed. Bounds a
|
|
31
|
+
* mic left open by mistake: every recorded minute is a minute billed by the
|
|
32
|
+
* recognizer, and agora refuses clips over its size cap. Defaults to
|
|
33
|
+
* 5 minutes; pass `Infinity` to disable.
|
|
34
|
+
*/
|
|
35
|
+
maxDurationMs?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare const DEFAULT_MAX_RECORDING_MS: number;
|
|
38
|
+
interface MediaRecorderLike extends EventTarget {
|
|
39
|
+
readonly state: 'inactive' | 'recording' | 'paused';
|
|
40
|
+
readonly mimeType: string;
|
|
41
|
+
start: (timeslice?: number) => void;
|
|
42
|
+
stop: () => void;
|
|
43
|
+
ondataavailable: ((event: {
|
|
44
|
+
data: Blob;
|
|
45
|
+
}) => void) | null;
|
|
46
|
+
onstop: (() => void) | null;
|
|
47
|
+
onerror: ((event: Event) => void) | null;
|
|
48
|
+
}
|
|
49
|
+
interface MediaRecorderCtor {
|
|
50
|
+
new (stream: MediaStream, options?: {
|
|
51
|
+
mimeType?: string;
|
|
52
|
+
}): MediaRecorderLike;
|
|
53
|
+
isTypeSupported?: (type: string) => boolean;
|
|
54
|
+
}
|
|
55
|
+
/** First container the recorder accepts, or undefined for the browser default. */
|
|
56
|
+
export declare function pickRecorderMimeType(Ctor: Pick<MediaRecorderCtor, 'isTypeSupported'>, candidates: readonly string[]): string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Record-then-transcribe: capture the microphone with `MediaRecorder` while
|
|
59
|
+
* the user talks, then hand the finished clip to `transcribe` on stop. No
|
|
60
|
+
* interim words are shown; the composer shows a "transcribing" state until
|
|
61
|
+
* the text lands. Works in every browser with `MediaRecorder`, so the mic is
|
|
62
|
+
* available where Web Speech is not (Firefox) and the quality is whatever the
|
|
63
|
+
* server-side recognizer provides.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createRecordedDictationEngine(options: RecordedDictationEngineOptions): DictationEngine;
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { DICTATION_ENGINE_ERROR_KINDS } from "./dictation-engine.js";
|
|
2
|
+
const DEFAULT_MIME_TYPES = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4", "audio/ogg;codecs=opus"];
|
|
3
|
+
const DEFAULT_FLUSH_TIMEOUT_MS = 9e4;
|
|
4
|
+
const DEFAULT_MAX_RECORDING_MS = 5 * 6e4;
|
|
5
|
+
function resolveMediaRecorderCtor() {
|
|
6
|
+
if (typeof window === "undefined") return null;
|
|
7
|
+
return window.MediaRecorder ?? null;
|
|
8
|
+
}
|
|
9
|
+
function pickRecorderMimeType(Ctor, candidates) {
|
|
10
|
+
if (!Ctor.isTypeSupported) return void 0;
|
|
11
|
+
return candidates.find((type) => {
|
|
12
|
+
var _a;
|
|
13
|
+
return (_a = Ctor.isTypeSupported) == null ? void 0 : _a.call(Ctor, type);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function transcriptionError(error) {
|
|
17
|
+
return {
|
|
18
|
+
kind: DICTATION_ENGINE_ERROR_KINDS.connection,
|
|
19
|
+
message: error instanceof Error && error.message ? error.message : "Transcription failed. Try again."
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function createRecordedDictationEngine(options) {
|
|
23
|
+
const {
|
|
24
|
+
transcribe,
|
|
25
|
+
mimeTypes = DEFAULT_MIME_TYPES,
|
|
26
|
+
flushTimeoutMs = DEFAULT_FLUSH_TIMEOUT_MS,
|
|
27
|
+
maxDurationMs = DEFAULT_MAX_RECORDING_MS
|
|
28
|
+
} = options;
|
|
29
|
+
return {
|
|
30
|
+
id: "recorded-upload",
|
|
31
|
+
flushTimeoutMs,
|
|
32
|
+
stoppingLabel: "Transcribing…",
|
|
33
|
+
isSupported: () => {
|
|
34
|
+
var _a;
|
|
35
|
+
return resolveMediaRecorderCtor() !== null && typeof navigator !== "undefined" && typeof ((_a = navigator.mediaDevices) == null ? void 0 : _a.getUserMedia) === "function";
|
|
36
|
+
},
|
|
37
|
+
start: ({ stream, lang, callbacks }) => {
|
|
38
|
+
const Ctor = resolveMediaRecorderCtor();
|
|
39
|
+
const noop = { stop: () => void 0, abort: () => void 0 };
|
|
40
|
+
if (!Ctor || !stream) {
|
|
41
|
+
callbacks.onError({
|
|
42
|
+
kind: !Ctor ? DICTATION_ENGINE_ERROR_KINDS.unsupported : DICTATION_ENGINE_ERROR_KINDS.permission,
|
|
43
|
+
message: !Ctor ? "This browser cannot record audio." : "No microphone stream was available."
|
|
44
|
+
});
|
|
45
|
+
callbacks.onEnd();
|
|
46
|
+
return noop;
|
|
47
|
+
}
|
|
48
|
+
const mimeType = pickRecorderMimeType(Ctor, mimeTypes);
|
|
49
|
+
let recorder;
|
|
50
|
+
try {
|
|
51
|
+
recorder = new Ctor(stream, mimeType ? { mimeType } : void 0);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
callbacks.onError({
|
|
54
|
+
kind: DICTATION_ENGINE_ERROR_KINDS.unknown,
|
|
55
|
+
message: error instanceof Error ? error.message : "Recording could not start."
|
|
56
|
+
});
|
|
57
|
+
callbacks.onEnd();
|
|
58
|
+
return noop;
|
|
59
|
+
}
|
|
60
|
+
const chunks = [];
|
|
61
|
+
const upload = new AbortController();
|
|
62
|
+
let ended = false;
|
|
63
|
+
let aborted = false;
|
|
64
|
+
let limitTimer;
|
|
65
|
+
const clearLimit = () => {
|
|
66
|
+
if (limitTimer === void 0) return;
|
|
67
|
+
clearTimeout(limitTimer);
|
|
68
|
+
limitTimer = void 0;
|
|
69
|
+
};
|
|
70
|
+
const end = () => {
|
|
71
|
+
if (ended) return;
|
|
72
|
+
ended = true;
|
|
73
|
+
clearLimit();
|
|
74
|
+
recorder.ondataavailable = null;
|
|
75
|
+
recorder.onstop = null;
|
|
76
|
+
recorder.onerror = null;
|
|
77
|
+
callbacks.onEnd();
|
|
78
|
+
};
|
|
79
|
+
const stopRecorder = () => {
|
|
80
|
+
if (recorder.state === "inactive") return false;
|
|
81
|
+
try {
|
|
82
|
+
recorder.stop();
|
|
83
|
+
return true;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
recorder.ondataavailable = (event) => {
|
|
89
|
+
if (event.data && event.data.size > 0) chunks.push(event.data);
|
|
90
|
+
};
|
|
91
|
+
recorder.onerror = () => {
|
|
92
|
+
callbacks.onError({
|
|
93
|
+
kind: DICTATION_ENGINE_ERROR_KINDS.unknown,
|
|
94
|
+
message: "Recording stopped unexpectedly."
|
|
95
|
+
});
|
|
96
|
+
end();
|
|
97
|
+
};
|
|
98
|
+
recorder.onstop = () => {
|
|
99
|
+
if (aborted || ended) {
|
|
100
|
+
end();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const clip = new Blob(chunks, { type: recorder.mimeType || mimeType || "audio/webm" });
|
|
104
|
+
if (clip.size === 0) {
|
|
105
|
+
end();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
void transcribe(clip, { lang, signal: upload.signal }).then((text) => {
|
|
109
|
+
if (ended) return;
|
|
110
|
+
const trimmed = text.trim();
|
|
111
|
+
if (trimmed) callbacks.onTranscript({ final: trimmed, interim: "" });
|
|
112
|
+
}).catch((error) => {
|
|
113
|
+
if (ended) return;
|
|
114
|
+
callbacks.onError(transcriptionError(error));
|
|
115
|
+
}).finally(end);
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
recorder.start();
|
|
119
|
+
} catch (error) {
|
|
120
|
+
callbacks.onError({
|
|
121
|
+
kind: DICTATION_ENGINE_ERROR_KINDS.unknown,
|
|
122
|
+
message: error instanceof Error ? error.message : "Recording could not start."
|
|
123
|
+
});
|
|
124
|
+
end();
|
|
125
|
+
return noop;
|
|
126
|
+
}
|
|
127
|
+
if (Number.isFinite(maxDurationMs) && maxDurationMs > 0) {
|
|
128
|
+
limitTimer = setTimeout(() => {
|
|
129
|
+
limitTimer = void 0;
|
|
130
|
+
if (!ended) stopRecorder();
|
|
131
|
+
}, maxDurationMs);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
stop: () => {
|
|
135
|
+
clearLimit();
|
|
136
|
+
if (!stopRecorder()) end();
|
|
137
|
+
},
|
|
138
|
+
abort: () => {
|
|
139
|
+
aborted = true;
|
|
140
|
+
upload.abort();
|
|
141
|
+
stopRecorder();
|
|
142
|
+
end();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
DEFAULT_MAX_RECORDING_MS,
|
|
150
|
+
createRecordedDictationEngine,
|
|
151
|
+
pickRecorderMimeType
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=recorded-dictation-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorded-dictation-engine.js","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":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const react = require("react");
|
|
4
|
+
const AthenaContext = require("../provider/AthenaContext.cjs");
|
|
5
|
+
const statewireAuthHeaders = require("../runtime/statewire-auth-headers.cjs");
|
|
6
|
+
const dictationEngine = require("./dictation-engine.cjs");
|
|
7
|
+
const recordedDictationEngine = require("./recorded-dictation-engine.cjs");
|
|
8
|
+
function transcriptionUploadUrl(backendUrl) {
|
|
9
|
+
return `${backendUrl.replace(/\/api\/assistant-ui\/?$/, "").replace(/\/$/, "")}/api/transcription/upload`;
|
|
10
|
+
}
|
|
11
|
+
function transcriptionUploadErrorMessage(status) {
|
|
12
|
+
switch (status) {
|
|
13
|
+
case 401:
|
|
14
|
+
case 403:
|
|
15
|
+
return "Dictation is not available for this sign-in. Reload and try again.";
|
|
16
|
+
case 413:
|
|
17
|
+
return "That recording is too long to transcribe.";
|
|
18
|
+
case 422:
|
|
19
|
+
return "The dictation language was rejected. Use a BCP-47 tag such as en-US.";
|
|
20
|
+
case 429:
|
|
21
|
+
return "Too many dictations in a row. Wait a moment and try again.";
|
|
22
|
+
case 503:
|
|
23
|
+
return "Dictation is not set up on this server.";
|
|
24
|
+
default:
|
|
25
|
+
return `Transcription failed (${status}).`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function createAthenaUploadTranscriber(getConfig) {
|
|
29
|
+
return async (clip, { lang, signal }) => {
|
|
30
|
+
const config = getConfig();
|
|
31
|
+
const headers = await statewireAuthHeaders.resolveStatewireAuthHeaders(config, { staleAuth: false });
|
|
32
|
+
const form = new FormData();
|
|
33
|
+
const extension = clip.type.includes("mp4") ? "mp4" : clip.type.includes("ogg") ? "ogg" : "webm";
|
|
34
|
+
form.append("file", clip, `dictation.${extension}`);
|
|
35
|
+
if (lang) form.append("language", lang);
|
|
36
|
+
const response = await fetch(transcriptionUploadUrl(config.backendUrl), {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers,
|
|
39
|
+
body: form,
|
|
40
|
+
credentials: "include",
|
|
41
|
+
signal
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));
|
|
44
|
+
const payload = await response.json();
|
|
45
|
+
return typeof payload.transcript === "string" ? payload.transcript : "";
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function useAthenaDictationEngine(override) {
|
|
49
|
+
const config = AthenaContext.useAthenaConfig();
|
|
50
|
+
const configRef = react.useRef(config);
|
|
51
|
+
configRef.current = config;
|
|
52
|
+
return react.useMemo(() => {
|
|
53
|
+
if (override) return override;
|
|
54
|
+
const recorded = recordedDictationEngine.createRecordedDictationEngine({
|
|
55
|
+
transcribe: createAthenaUploadTranscriber(() => configRef.current)
|
|
56
|
+
});
|
|
57
|
+
return recorded.isSupported() ? recorded : dictationEngine.createWebSpeechDictationEngine();
|
|
58
|
+
}, [override]);
|
|
59
|
+
}
|
|
60
|
+
exports.createAthenaUploadTranscriber = createAthenaUploadTranscriber;
|
|
61
|
+
exports.transcriptionUploadErrorMessage = transcriptionUploadErrorMessage;
|
|
62
|
+
exports.transcriptionUploadUrl = transcriptionUploadUrl;
|
|
63
|
+
exports.useAthenaDictationEngine = useAthenaDictationEngine;
|
|
64
|
+
//# sourceMappingURL=use-athena-dictation-engine.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-athena-dictation-engine.cjs","sources":["../../src/dictation/use-athena-dictation-engine.ts"],"sourcesContent":["import { useMemo, useRef } from 'react';\nimport { type AthenaConfig, useAthenaConfig } from '../provider/AthenaContext';\nimport { resolveStatewireAuthHeaders } from '../runtime/statewire-auth-headers';\nimport { createWebSpeechDictationEngine, type DictationEngine } from './dictation-engine';\nimport { createRecordedDictationEngine, type DictationTranscriber } from './recorded-dictation-engine';\n\ninterface UploadTranscriptionResponse {\n transcript: string;\n}\n\n/** Agora's REST base from the configured backend URL (which may point at a nested endpoint). */\nexport function transcriptionUploadUrl(backendUrl: string): string {\n return `${backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '').replace(/\\/$/, '')}/api/transcription/upload`;\n}\n\n/**\n * One line for the composer per way `/transcription/upload` refuses a clip.\n * The copy lives here, apart from the fetch, so it can be pinned by a test.\n */\nexport function transcriptionUploadErrorMessage(status: number): string {\n switch (status) {\n case 401:\n case 403:\n return 'Dictation is not available for this sign-in. Reload and try again.';\n case 413:\n return 'That recording is too long to transcribe.';\n case 422:\n return 'The dictation language was rejected. Use a BCP-47 tag such as en-US.';\n case 429:\n return 'Too many dictations in a row. Wait a moment and try again.';\n case 503:\n return 'Dictation is not set up on this server.';\n default:\n return `Transcription failed (${status}).`;\n }\n}\n\n/**\n * Post a finished clip to agora's `/transcription/upload` route, which runs\n * it through Deepgram and returns the text. Credentials are read at call\n * time so a rotated token is picked up. The session language (host `lang`\n * or the document's) is forwarded so non-English speech is recognised as\n * such; the request is dropped when the engine aborts.\n */\nexport function createAthenaUploadTranscriber(\n getConfig: () => Pick<AthenaConfig, 'backendUrl' | 'apiKey' | 'token' | 'getToken'>,\n): DictationTranscriber {\n return async (clip, { lang, signal }) => {\n const config = getConfig();\n const headers = await resolveStatewireAuthHeaders(config, { staleAuth: false });\n const form = new FormData();\n const extension = clip.type.includes('mp4') ? 'mp4' : clip.type.includes('ogg') ? 'ogg' : 'webm';\n form.append('file', clip, `dictation.${extension}`);\n if (lang) form.append('language', lang);\n const response = await fetch(transcriptionUploadUrl(config.backendUrl), {\n method: 'POST',\n headers,\n body: form,\n credentials: 'include',\n signal,\n });\n if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));\n const payload = (await response.json()) as UploadTranscriptionResponse;\n return typeof payload.transcript === 'string' ? payload.transcript : '';\n };\n}\n\n/**\n * The composer's default speech engine: record with `MediaRecorder`, then\n * transcribe the clip through agora (Deepgram). Falls back to the browser's\n * Web Speech API only where `MediaRecorder` is missing. A host-provided\n * `engine` always wins.\n */\nexport function useAthenaDictationEngine(override?: DictationEngine): DictationEngine {\n const config = useAthenaConfig();\n const configRef = useRef(config);\n configRef.current = config;\n return useMemo(() => {\n if (override) return override;\n const recorded = createRecordedDictationEngine({\n transcribe: createAthenaUploadTranscriber(() => configRef.current),\n });\n return recorded.isSupported() ? recorded : createWebSpeechDictationEngine();\n }, [override]);\n}\n"],"names":["resolveStatewireAuthHeaders","useAthenaConfig","useRef","useMemo","createRecordedDictationEngine","createWebSpeechDictationEngine"],"mappings":";;;;;;;AAWO,SAAS,uBAAuB,YAA4B;AACjE,SAAO,GAAG,WAAW,QAAQ,2BAA2B,EAAE,EAAE,QAAQ,OAAO,EAAE,CAAC;AAChF;AAMO,SAAS,gCAAgC,QAAwB;AACtE,UAAQ,QAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,yBAAyB,MAAM;AAAA,EAAA;AAE5C;AASO,SAAS,8BACd,WACsB;AACtB,SAAO,OAAO,MAAM,EAAE,MAAM,aAAa;AACvC,UAAM,SAAS,UAAA;AACf,UAAM,UAAU,MAAMA,qBAAAA,4BAA4B,QAAQ,EAAE,WAAW,OAAO;AAC9E,UAAM,OAAO,IAAI,SAAA;AACjB,UAAM,YAAY,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ;AAC1F,SAAK,OAAO,QAAQ,MAAM,aAAa,SAAS,EAAE;AAClD,QAAI,KAAM,MAAK,OAAO,YAAY,IAAI;AACtC,UAAM,WAAW,MAAM,MAAM,uBAAuB,OAAO,UAAU,GAAG;AAAA,MACtE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM;AAAA,MACN,aAAa;AAAA,MACb;AAAA,IAAA,CACD;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,gCAAgC,SAAS,MAAM,CAAC;AAClF,UAAM,UAAW,MAAM,SAAS,KAAA;AAChC,WAAO,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;AAAA,EACvE;AACF;AAQO,SAAS,yBAAyB,UAA6C;AACpF,QAAM,SAASC,cAAAA,gBAAA;AACf,QAAM,YAAYC,MAAAA,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,SAAOC,MAAAA,QAAQ,MAAM;AACnB,QAAI,SAAU,QAAO;AACrB,UAAM,WAAWC,wBAAAA,8BAA8B;AAAA,MAC7C,YAAY,8BAA8B,MAAM,UAAU,OAAO;AAAA,IAAA,CAClE;AACD,WAAO,SAAS,gBAAgB,WAAWC,gBAAAA,+BAAA;AAAA,EAC7C,GAAG,CAAC,QAAQ,CAAC;AACf;;;;;"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type AthenaConfig } from '../provider/AthenaContext';
|
|
2
|
+
import { type DictationEngine } from './dictation-engine';
|
|
3
|
+
import { type DictationTranscriber } from './recorded-dictation-engine';
|
|
4
|
+
/** Agora's REST base from the configured backend URL (which may point at a nested endpoint). */
|
|
5
|
+
export declare function transcriptionUploadUrl(backendUrl: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* One line for the composer per way `/transcription/upload` refuses a clip.
|
|
8
|
+
* The copy lives here, apart from the fetch, so it can be pinned by a test.
|
|
9
|
+
*/
|
|
10
|
+
export declare function transcriptionUploadErrorMessage(status: number): string;
|
|
11
|
+
/**
|
|
12
|
+
* Post a finished clip to agora's `/transcription/upload` route, which runs
|
|
13
|
+
* it through Deepgram and returns the text. Credentials are read at call
|
|
14
|
+
* time so a rotated token is picked up. The session language (host `lang`
|
|
15
|
+
* or the document's) is forwarded so non-English speech is recognised as
|
|
16
|
+
* such; the request is dropped when the engine aborts.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createAthenaUploadTranscriber(getConfig: () => Pick<AthenaConfig, 'backendUrl' | 'apiKey' | 'token' | 'getToken'>): DictationTranscriber;
|
|
19
|
+
/**
|
|
20
|
+
* The composer's default speech engine: record with `MediaRecorder`, then
|
|
21
|
+
* transcribe the clip through agora (Deepgram). Falls back to the browser's
|
|
22
|
+
* Web Speech API only where `MediaRecorder` is missing. A host-provided
|
|
23
|
+
* `engine` always wins.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useAthenaDictationEngine(override?: DictationEngine): DictationEngine;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useRef, useMemo } from "react";
|
|
2
|
+
import { useAthenaConfig } from "../provider/AthenaContext.js";
|
|
3
|
+
import { resolveStatewireAuthHeaders } from "../runtime/statewire-auth-headers.js";
|
|
4
|
+
import { createWebSpeechDictationEngine } from "./dictation-engine.js";
|
|
5
|
+
import { createRecordedDictationEngine } from "./recorded-dictation-engine.js";
|
|
6
|
+
function transcriptionUploadUrl(backendUrl) {
|
|
7
|
+
return `${backendUrl.replace(/\/api\/assistant-ui\/?$/, "").replace(/\/$/, "")}/api/transcription/upload`;
|
|
8
|
+
}
|
|
9
|
+
function transcriptionUploadErrorMessage(status) {
|
|
10
|
+
switch (status) {
|
|
11
|
+
case 401:
|
|
12
|
+
case 403:
|
|
13
|
+
return "Dictation is not available for this sign-in. Reload and try again.";
|
|
14
|
+
case 413:
|
|
15
|
+
return "That recording is too long to transcribe.";
|
|
16
|
+
case 422:
|
|
17
|
+
return "The dictation language was rejected. Use a BCP-47 tag such as en-US.";
|
|
18
|
+
case 429:
|
|
19
|
+
return "Too many dictations in a row. Wait a moment and try again.";
|
|
20
|
+
case 503:
|
|
21
|
+
return "Dictation is not set up on this server.";
|
|
22
|
+
default:
|
|
23
|
+
return `Transcription failed (${status}).`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function createAthenaUploadTranscriber(getConfig) {
|
|
27
|
+
return async (clip, { lang, signal }) => {
|
|
28
|
+
const config = getConfig();
|
|
29
|
+
const headers = await resolveStatewireAuthHeaders(config, { staleAuth: false });
|
|
30
|
+
const form = new FormData();
|
|
31
|
+
const extension = clip.type.includes("mp4") ? "mp4" : clip.type.includes("ogg") ? "ogg" : "webm";
|
|
32
|
+
form.append("file", clip, `dictation.${extension}`);
|
|
33
|
+
if (lang) form.append("language", lang);
|
|
34
|
+
const response = await fetch(transcriptionUploadUrl(config.backendUrl), {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers,
|
|
37
|
+
body: form,
|
|
38
|
+
credentials: "include",
|
|
39
|
+
signal
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));
|
|
42
|
+
const payload = await response.json();
|
|
43
|
+
return typeof payload.transcript === "string" ? payload.transcript : "";
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function useAthenaDictationEngine(override) {
|
|
47
|
+
const config = useAthenaConfig();
|
|
48
|
+
const configRef = useRef(config);
|
|
49
|
+
configRef.current = config;
|
|
50
|
+
return useMemo(() => {
|
|
51
|
+
if (override) return override;
|
|
52
|
+
const recorded = createRecordedDictationEngine({
|
|
53
|
+
transcribe: createAthenaUploadTranscriber(() => configRef.current)
|
|
54
|
+
});
|
|
55
|
+
return recorded.isSupported() ? recorded : createWebSpeechDictationEngine();
|
|
56
|
+
}, [override]);
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
createAthenaUploadTranscriber,
|
|
60
|
+
transcriptionUploadErrorMessage,
|
|
61
|
+
transcriptionUploadUrl,
|
|
62
|
+
useAthenaDictationEngine
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=use-athena-dictation-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-athena-dictation-engine.js","sources":["../../src/dictation/use-athena-dictation-engine.ts"],"sourcesContent":["import { useMemo, useRef } from 'react';\nimport { type AthenaConfig, useAthenaConfig } from '../provider/AthenaContext';\nimport { resolveStatewireAuthHeaders } from '../runtime/statewire-auth-headers';\nimport { createWebSpeechDictationEngine, type DictationEngine } from './dictation-engine';\nimport { createRecordedDictationEngine, type DictationTranscriber } from './recorded-dictation-engine';\n\ninterface UploadTranscriptionResponse {\n transcript: string;\n}\n\n/** Agora's REST base from the configured backend URL (which may point at a nested endpoint). */\nexport function transcriptionUploadUrl(backendUrl: string): string {\n return `${backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '').replace(/\\/$/, '')}/api/transcription/upload`;\n}\n\n/**\n * One line for the composer per way `/transcription/upload` refuses a clip.\n * The copy lives here, apart from the fetch, so it can be pinned by a test.\n */\nexport function transcriptionUploadErrorMessage(status: number): string {\n switch (status) {\n case 401:\n case 403:\n return 'Dictation is not available for this sign-in. Reload and try again.';\n case 413:\n return 'That recording is too long to transcribe.';\n case 422:\n return 'The dictation language was rejected. Use a BCP-47 tag such as en-US.';\n case 429:\n return 'Too many dictations in a row. Wait a moment and try again.';\n case 503:\n return 'Dictation is not set up on this server.';\n default:\n return `Transcription failed (${status}).`;\n }\n}\n\n/**\n * Post a finished clip to agora's `/transcription/upload` route, which runs\n * it through Deepgram and returns the text. Credentials are read at call\n * time so a rotated token is picked up. The session language (host `lang`\n * or the document's) is forwarded so non-English speech is recognised as\n * such; the request is dropped when the engine aborts.\n */\nexport function createAthenaUploadTranscriber(\n getConfig: () => Pick<AthenaConfig, 'backendUrl' | 'apiKey' | 'token' | 'getToken'>,\n): DictationTranscriber {\n return async (clip, { lang, signal }) => {\n const config = getConfig();\n const headers = await resolveStatewireAuthHeaders(config, { staleAuth: false });\n const form = new FormData();\n const extension = clip.type.includes('mp4') ? 'mp4' : clip.type.includes('ogg') ? 'ogg' : 'webm';\n form.append('file', clip, `dictation.${extension}`);\n if (lang) form.append('language', lang);\n const response = await fetch(transcriptionUploadUrl(config.backendUrl), {\n method: 'POST',\n headers,\n body: form,\n credentials: 'include',\n signal,\n });\n if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));\n const payload = (await response.json()) as UploadTranscriptionResponse;\n return typeof payload.transcript === 'string' ? payload.transcript : '';\n };\n}\n\n/**\n * The composer's default speech engine: record with `MediaRecorder`, then\n * transcribe the clip through agora (Deepgram). Falls back to the browser's\n * Web Speech API only where `MediaRecorder` is missing. A host-provided\n * `engine` always wins.\n */\nexport function useAthenaDictationEngine(override?: DictationEngine): DictationEngine {\n const config = useAthenaConfig();\n const configRef = useRef(config);\n configRef.current = config;\n return useMemo(() => {\n if (override) return override;\n const recorded = createRecordedDictationEngine({\n transcribe: createAthenaUploadTranscriber(() => configRef.current),\n });\n return recorded.isSupported() ? recorded : createWebSpeechDictationEngine();\n }, [override]);\n}\n"],"names":[],"mappings":";;;;;AAWO,SAAS,uBAAuB,YAA4B;AACjE,SAAO,GAAG,WAAW,QAAQ,2BAA2B,EAAE,EAAE,QAAQ,OAAO,EAAE,CAAC;AAChF;AAMO,SAAS,gCAAgC,QAAwB;AACtE,UAAQ,QAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,yBAAyB,MAAM;AAAA,EAAA;AAE5C;AASO,SAAS,8BACd,WACsB;AACtB,SAAO,OAAO,MAAM,EAAE,MAAM,aAAa;AACvC,UAAM,SAAS,UAAA;AACf,UAAM,UAAU,MAAM,4BAA4B,QAAQ,EAAE,WAAW,OAAO;AAC9E,UAAM,OAAO,IAAI,SAAA;AACjB,UAAM,YAAY,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ;AAC1F,SAAK,OAAO,QAAQ,MAAM,aAAa,SAAS,EAAE;AAClD,QAAI,KAAM,MAAK,OAAO,YAAY,IAAI;AACtC,UAAM,WAAW,MAAM,MAAM,uBAAuB,OAAO,UAAU,GAAG;AAAA,MACtE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM;AAAA,MACN,aAAa;AAAA,MACb;AAAA,IAAA,CACD;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,gCAAgC,SAAS,MAAM,CAAC;AAClF,UAAM,UAAW,MAAM,SAAS,KAAA;AAChC,WAAO,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;AAAA,EACvE;AACF;AAQO,SAAS,yBAAyB,UAA6C;AACpF,QAAM,SAAS,gBAAA;AACf,QAAM,YAAY,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,SAAO,QAAQ,MAAM;AACnB,QAAI,SAAU,QAAO;AACrB,UAAM,WAAW,8BAA8B;AAAA,MAC7C,YAAY,8BAA8B,MAAM,UAAU,OAAO;AAAA,IAAA,CAClE;AACD,WAAO,SAAS,gBAAgB,WAAW,+BAAA;AAAA,EAC7C,GAAG,CAAC,QAAQ,CAAC;AACf;"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const react = require("react");
|
|
4
|
+
const athenaDiagnostics = require("../diagnostics/athena-diagnostics.cjs");
|
|
5
|
+
const dictationEngine = require("./dictation-engine.cjs");
|
|
6
|
+
const dictationState = require("./dictation-state.cjs");
|
|
7
|
+
const DEFAULT_STOP_FLUSH_TIMEOUT_MS = 1500;
|
|
8
|
+
const EMPTY_TRANSCRIPT = { final: "", interim: "" };
|
|
9
|
+
function documentLanguage() {
|
|
10
|
+
if (typeof document !== "undefined" && document.documentElement.lang) {
|
|
11
|
+
return document.documentElement.lang;
|
|
12
|
+
}
|
|
13
|
+
if (typeof navigator !== "undefined" && navigator.language) return navigator.language;
|
|
14
|
+
return "en-US";
|
|
15
|
+
}
|
|
16
|
+
async function openMicrophone() {
|
|
17
|
+
var _a;
|
|
18
|
+
if (typeof navigator === "undefined" || !((_a = navigator.mediaDevices) == null ? void 0 : _a.getUserMedia)) return null;
|
|
19
|
+
return navigator.mediaDevices.getUserMedia({ audio: true });
|
|
20
|
+
}
|
|
21
|
+
function releaseStream(stream) {
|
|
22
|
+
if (!stream) return;
|
|
23
|
+
for (const track of stream.getTracks()) track.stop();
|
|
24
|
+
}
|
|
25
|
+
function useDictation(options = {}) {
|
|
26
|
+
const engine = react.useMemo(() => options.engine ?? dictationEngine.createWebSpeechDictationEngine(), [options.engine]);
|
|
27
|
+
const { lang, captureStream = true, onAutoEnd } = options;
|
|
28
|
+
const [state, dispatch] = react.useReducer(dictationState.dictationReducer, dictationState.INITIAL_DICTATION_STATE);
|
|
29
|
+
const [stream, setStream] = react.useState(null);
|
|
30
|
+
const [isSupported, setIsSupported] = react.useState(false);
|
|
31
|
+
const sessionRef = react.useRef(null);
|
|
32
|
+
const streamRef = react.useRef(null);
|
|
33
|
+
const stateRef = react.useRef(state);
|
|
34
|
+
stateRef.current = state;
|
|
35
|
+
const transcriptRef = react.useRef(EMPTY_TRANSCRIPT);
|
|
36
|
+
const onAutoEndRef = react.useRef(onAutoEnd);
|
|
37
|
+
onAutoEndRef.current = onAutoEnd;
|
|
38
|
+
const stopWaitersRef = react.useRef([]);
|
|
39
|
+
const sessionErrorRef = react.useRef(null);
|
|
40
|
+
const generationRef = react.useRef(0);
|
|
41
|
+
react.useEffect(() => {
|
|
42
|
+
setIsSupported(engine.isSupported());
|
|
43
|
+
}, [engine]);
|
|
44
|
+
const settleWaiters = react.useCallback((spoken) => {
|
|
45
|
+
const waiters = stopWaitersRef.current;
|
|
46
|
+
stopWaitersRef.current = [];
|
|
47
|
+
const result = { text: spoken, error: sessionErrorRef.current };
|
|
48
|
+
for (const resolve of waiters) resolve(result);
|
|
49
|
+
}, []);
|
|
50
|
+
const releaseSession = react.useCallback(() => {
|
|
51
|
+
sessionRef.current = null;
|
|
52
|
+
releaseStream(streamRef.current);
|
|
53
|
+
streamRef.current = null;
|
|
54
|
+
setStream(null);
|
|
55
|
+
}, []);
|
|
56
|
+
const teardown = react.useCallback(() => {
|
|
57
|
+
var _a;
|
|
58
|
+
generationRef.current += 1;
|
|
59
|
+
(_a = sessionRef.current) == null ? void 0 : _a.abort();
|
|
60
|
+
releaseSession();
|
|
61
|
+
transcriptRef.current = EMPTY_TRANSCRIPT;
|
|
62
|
+
settleWaiters("");
|
|
63
|
+
}, [releaseSession, settleWaiters]);
|
|
64
|
+
const reportEngineError = react.useCallback(
|
|
65
|
+
(engineError) => {
|
|
66
|
+
athenaDiagnostics.athenaDiagnostics.emit("composer.dictation", {
|
|
67
|
+
level: "warn",
|
|
68
|
+
data: { phase: "error", engine: engine.id, kind: engineError.kind }
|
|
69
|
+
});
|
|
70
|
+
sessionErrorRef.current = engineError;
|
|
71
|
+
dispatch({ type: "error", error: engineError });
|
|
72
|
+
},
|
|
73
|
+
[engine.id]
|
|
74
|
+
);
|
|
75
|
+
const start = react.useCallback(() => {
|
|
76
|
+
if (sessionRef.current) return;
|
|
77
|
+
const generation = generationRef.current + 1;
|
|
78
|
+
generationRef.current = generation;
|
|
79
|
+
transcriptRef.current = EMPTY_TRANSCRIPT;
|
|
80
|
+
sessionErrorRef.current = null;
|
|
81
|
+
dispatch({ type: "start" });
|
|
82
|
+
athenaDiagnostics.athenaDiagnostics.emit("composer.dictation", {
|
|
83
|
+
level: "debug",
|
|
84
|
+
data: { phase: "start", engine: engine.id }
|
|
85
|
+
});
|
|
86
|
+
const run = async () => {
|
|
87
|
+
let microphone = null;
|
|
88
|
+
if (captureStream) {
|
|
89
|
+
try {
|
|
90
|
+
microphone = await openMicrophone();
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (generationRef.current !== generation) {
|
|
93
|
+
releaseStream(microphone);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
dispatch({
|
|
97
|
+
type: "error",
|
|
98
|
+
error: {
|
|
99
|
+
kind: dictationEngine.DICTATION_ENGINE_ERROR_KINDS.permission,
|
|
100
|
+
message: error instanceof DOMException && error.name === "NotFoundError" ? "No microphone was found." : "Microphone access was denied. Allow the microphone for this site and try again."
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (generationRef.current !== generation) {
|
|
107
|
+
releaseStream(microphone);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
streamRef.current = microphone;
|
|
111
|
+
setStream(microphone);
|
|
112
|
+
let terminated = false;
|
|
113
|
+
const endSession = () => {
|
|
114
|
+
var _a;
|
|
115
|
+
terminated = true;
|
|
116
|
+
const current = stateRef.current;
|
|
117
|
+
const spoken = dictationState.spokenText(transcriptRef.current);
|
|
118
|
+
const endedOnItsOwn = stopWaitersRef.current.length === 0 && current.status === dictationState.DICTATION_STATUSES.listening;
|
|
119
|
+
dispatch({ type: "ended" });
|
|
120
|
+
releaseSession();
|
|
121
|
+
settleWaiters(spoken);
|
|
122
|
+
if (endedOnItsOwn) (_a = onAutoEndRef.current) == null ? void 0 : _a.call(onAutoEndRef, spoken);
|
|
123
|
+
};
|
|
124
|
+
let session;
|
|
125
|
+
try {
|
|
126
|
+
session = engine.start({
|
|
127
|
+
stream: microphone,
|
|
128
|
+
lang: lang ?? documentLanguage(),
|
|
129
|
+
callbacks: {
|
|
130
|
+
onTranscript: (update) => {
|
|
131
|
+
if (generationRef.current !== generation) return;
|
|
132
|
+
transcriptRef.current = {
|
|
133
|
+
final: dictationState.joinSpeech(transcriptRef.current.final, update.final),
|
|
134
|
+
interim: update.interim
|
|
135
|
+
};
|
|
136
|
+
dispatch({ type: "transcript", update });
|
|
137
|
+
},
|
|
138
|
+
onError: (engineError) => {
|
|
139
|
+
if (generationRef.current !== generation) return;
|
|
140
|
+
if (engineError.kind === dictationEngine.DICTATION_ENGINE_ERROR_KINDS.noSpeech) return;
|
|
141
|
+
reportEngineError(engineError);
|
|
142
|
+
},
|
|
143
|
+
onEnd: () => {
|
|
144
|
+
if (generationRef.current !== generation) return;
|
|
145
|
+
endSession();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
releaseSession();
|
|
151
|
+
reportEngineError({
|
|
152
|
+
kind: dictationEngine.DICTATION_ENGINE_ERROR_KINDS.connection,
|
|
153
|
+
message: error instanceof Error ? error.message : "Dictation could not start."
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (terminated) return;
|
|
158
|
+
sessionRef.current = session;
|
|
159
|
+
dispatch({ type: "listening" });
|
|
160
|
+
};
|
|
161
|
+
void run();
|
|
162
|
+
}, [captureStream, engine, lang, releaseSession, reportEngineError, settleWaiters]);
|
|
163
|
+
const stop = react.useCallback(() => {
|
|
164
|
+
const session = sessionRef.current;
|
|
165
|
+
if (!session) {
|
|
166
|
+
if (stateRef.current.status === dictationState.DICTATION_STATUSES.starting) {
|
|
167
|
+
teardown();
|
|
168
|
+
dispatch({ type: "cancel" });
|
|
169
|
+
return Promise.resolve({ text: "", error: null });
|
|
170
|
+
}
|
|
171
|
+
return Promise.resolve({ text: dictationState.spokenText(transcriptRef.current), error: sessionErrorRef.current });
|
|
172
|
+
}
|
|
173
|
+
dispatch({ type: "stop" });
|
|
174
|
+
const flushed = new Promise((resolve) => {
|
|
175
|
+
stopWaitersRef.current.push(resolve);
|
|
176
|
+
window.setTimeout(() => {
|
|
177
|
+
if (sessionRef.current === session) {
|
|
178
|
+
if (!dictationState.spokenText(transcriptRef.current)) {
|
|
179
|
+
reportEngineError({
|
|
180
|
+
kind: dictationEngine.DICTATION_ENGINE_ERROR_KINDS.connection,
|
|
181
|
+
message: "Transcription timed out. Try again."
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
session.abort();
|
|
185
|
+
dispatch({ type: "ended" });
|
|
186
|
+
releaseSession();
|
|
187
|
+
settleWaiters(dictationState.spokenText(transcriptRef.current));
|
|
188
|
+
}
|
|
189
|
+
}, engine.flushTimeoutMs ?? DEFAULT_STOP_FLUSH_TIMEOUT_MS);
|
|
190
|
+
});
|
|
191
|
+
session.stop();
|
|
192
|
+
return flushed;
|
|
193
|
+
}, [engine.flushTimeoutMs, releaseSession, reportEngineError, settleWaiters, teardown]);
|
|
194
|
+
const cancel = react.useCallback(() => {
|
|
195
|
+
teardown();
|
|
196
|
+
dispatch({ type: "cancel" });
|
|
197
|
+
}, [teardown]);
|
|
198
|
+
react.useEffect(() => () => teardown(), [teardown]);
|
|
199
|
+
const isActive = state.status === dictationState.DICTATION_STATUSES.starting || state.status === dictationState.DICTATION_STATUSES.listening || state.status === dictationState.DICTATION_STATUSES.stopping;
|
|
200
|
+
return {
|
|
201
|
+
status: state.status,
|
|
202
|
+
isActive,
|
|
203
|
+
transcript: state.transcript,
|
|
204
|
+
text: dictationState.spokenText(state.transcript),
|
|
205
|
+
error: state.error,
|
|
206
|
+
stream,
|
|
207
|
+
isSupported,
|
|
208
|
+
engine,
|
|
209
|
+
start,
|
|
210
|
+
stop,
|
|
211
|
+
cancel
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
exports.useDictation = useDictation;
|
|
215
|
+
//# sourceMappingURL=use-dictation.cjs.map
|