@memberjunction/ng-conversations 6.2.0-edge.3 → 6.2.0-edge.5
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 +33 -7
- package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-view.component.js +1 -0
- package/dist/lib/components/collection/collection-view.component.js.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.d.ts +14 -2
- package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.js +35 -4
- package/dist/lib/components/composer/ai-composer.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-agent-picker.component.d.ts +1 -1
- package/dist/lib/components/conversation/conversation-agent-picker.component.js +3 -3
- package/dist/lib/components/conversation/conversation-agent-picker.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +651 -53
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +3002 -661
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/forks-popover.component.d.ts +37 -0
- package/dist/lib/components/conversation/forks-popover.component.d.ts.map +1 -0
- package/dist/lib/components/conversation/forks-popover.component.js +164 -0
- package/dist/lib/components/conversation/forks-popover.component.js.map +1 -0
- package/dist/lib/components/export/export-modal.component.d.ts +7 -2
- package/dist/lib/components/export/export-modal.component.d.ts.map +1 -1
- package/dist/lib/components/export/export-modal.component.js +16 -3
- package/dist/lib/components/export/export-modal.component.js.map +1 -1
- package/dist/lib/components/message/conversation-message-rating.component.d.ts.map +1 -1
- package/dist/lib/components/message/conversation-message-rating.component.js +11 -9
- package/dist/lib/components/message/conversation-message-rating.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +96 -11
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +253 -112
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +304 -16
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +2029 -363
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +101 -22
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +326 -55
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.d.ts +34 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.js +75 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.js.map +1 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.d.ts +80 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.js +202 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.d.ts +45 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.js +123 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.d.ts +90 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.js +223 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.d.ts +98 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.js +161 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.d.ts +48 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.js +205 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/channels/client-context-channel.d.ts +19 -2
- package/dist/lib/components/realtime/channels/client-context-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/channels/client-context-channel.js +83 -9
- package/dist/lib/components/realtime/channels/client-context-channel.js.map +1 -1
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts +30 -9
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js +78 -14
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js.map +1 -1
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.d.ts +71 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.js +259 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.js.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.d.ts +129 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.d.ts.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.js +373 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.js.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.d.ts +77 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.js +420 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.d.ts +83 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.js +292 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.d.ts +39 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.js +152 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.d.ts +109 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.js +146 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.d.ts +19 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.js +39 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.d.ts +183 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.js +761 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.d.ts +25 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.js +57 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.d.ts +40 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.js +97 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.d.ts +144 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.js +57 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.d.ts +36 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.js +55 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.d.ts +55 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.js +210 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.d.ts +32 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +90 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -0
- package/dist/lib/components/realtime/media/media-channel.d.ts +7 -0
- package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.js +28 -1
- package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +8 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.js +89 -71
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-orb.component.d.ts +21 -0
- package/dist/lib/components/realtime/realtime-agent-orb.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-agent-orb.component.js +49 -0
- package/dist/lib/components/realtime/realtime-agent-orb.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +44 -11
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-picker.component.js +162 -50
- package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +8 -2
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-audio-visuals.js +14 -18
- package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.d.ts +42 -2
- package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.js +137 -102
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-perception-chip.component.d.ts +71 -0
- package/dist/lib/components/realtime/realtime-perception-chip.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-perception-chip.component.js +221 -0
- package/dist/lib/components/realtime/realtime-perception-chip.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +264 -22
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +851 -135
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.d.ts +14 -0
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.js +14 -0
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.d.ts +74 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.js +171 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +71 -13
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js +232 -73
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +5 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js +8 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.d.ts +12 -2
- package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.js +5 -1
- package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +10 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +42 -2
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts +20 -3
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js +44 -10
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +28 -44
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +76 -162
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
- package/dist/lib/components/search/search-panel.component.d.ts +2 -0
- package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
- package/dist/lib/components/search/search-panel.component.js +25 -12
- package/dist/lib/components/search/search-panel.component.js.map +1 -1
- package/dist/lib/components/sidebar/routines-section.component.js +1 -1
- package/dist/lib/components/sidebar/routines-section.component.js.map +1 -1
- package/dist/lib/components/task/tasks-full-view.component.d.ts +152 -31
- package/dist/lib/components/task/tasks-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/task/tasks-full-view.component.js +2227 -214
- package/dist/lib/components/task/tasks-full-view.component.js.map +1 -1
- package/dist/lib/components/tasks/tasks-dropdown.component.js +4 -4
- package/dist/lib/components/tasks/tasks-dropdown.component.js.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts +33 -32
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.js +72 -88
- package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
- package/dist/lib/conversations.module.d.ts +30 -29
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +27 -5
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/events/chat-events.d.ts +25 -0
- package/dist/lib/events/chat-events.d.ts.map +1 -1
- package/dist/lib/events/chat-events.js +36 -0
- package/dist/lib/events/chat-events.js.map +1 -1
- package/dist/lib/models/agent-turn.model.d.ts +5 -0
- package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
- package/dist/lib/models/agent-turn.model.js.map +1 -1
- package/dist/lib/services/agent-state.service.d.ts +34 -8
- package/dist/lib/services/agent-state.service.d.ts.map +1 -1
- package/dist/lib/services/agent-state.service.js +90 -37
- package/dist/lib/services/agent-state.service.js.map +1 -1
- package/dist/lib/services/artifact-state.service.d.ts +0 -9
- package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-state.service.js +1 -32
- package/dist/lib/services/artifact-state.service.js.map +1 -1
- package/dist/lib/services/browser-realtime-media-host.d.ts +19 -7
- package/dist/lib/services/browser-realtime-media-host.d.ts.map +1 -1
- package/dist/lib/services/browser-realtime-media-host.js +21 -72
- package/dist/lib/services/browser-realtime-media-host.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +48 -16
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +110 -35
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.d.ts +22 -4
- package/dist/lib/services/conversation-detail-window.store.d.ts.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.js +50 -9
- package/dist/lib/services/conversation-detail-window.store.js.map +1 -1
- package/dist/lib/services/conversation-naming.d.ts +72 -22
- package/dist/lib/services/conversation-naming.d.ts.map +1 -1
- package/dist/lib/services/conversation-naming.js +93 -12
- package/dist/lib/services/conversation-naming.js.map +1 -1
- package/dist/lib/services/conversation-tasks-filter.d.ts +15 -0
- package/dist/lib/services/conversation-tasks-filter.d.ts.map +1 -0
- package/dist/lib/services/conversation-tasks-filter.js +69 -0
- package/dist/lib/services/conversation-tasks-filter.js.map +1 -0
- package/dist/lib/services/data-cache.service.d.ts +13 -5
- package/dist/lib/services/data-cache.service.d.ts.map +1 -1
- package/dist/lib/services/data-cache.service.js +39 -19
- package/dist/lib/services/data-cache.service.js.map +1 -1
- package/dist/lib/services/export.service.d.ts +26 -9
- package/dist/lib/services/export.service.d.ts.map +1 -1
- package/dist/lib/services/export.service.js +61 -18
- package/dist/lib/services/export.service.js.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.d.ts +115 -79
- package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.js +273 -186
- package/dist/lib/services/realtime-audio-recorder.js.map +1 -1
- package/dist/lib/services/realtime-pairing.d.ts +10 -5
- package/dist/lib/services/realtime-pairing.d.ts.map +1 -1
- package/dist/lib/services/realtime-pairing.js +15 -6
- package/dist/lib/services/realtime-pairing.js.map +1 -1
- package/dist/lib/services/realtime-session.service.d.ts +31 -2
- package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session.service.js +34 -2
- package/dist/lib/services/realtime-session.service.js.map +1 -1
- package/dist/lib/services/recording/libopus-wasm-binary.d.ts +3 -0
- package/dist/lib/services/recording/libopus-wasm-binary.d.ts.map +1 -0
- package/dist/lib/services/recording/libopus-wasm-binary.js +489 -0
- package/dist/lib/services/recording/libopus-wasm-binary.js.map +1 -0
- package/dist/lib/services/recording/libopus-worker-source.d.ts +48 -0
- package/dist/lib/services/recording/libopus-worker-source.d.ts.map +1 -0
- package/dist/lib/services/recording/libopus-worker-source.js +170 -0
- package/dist/lib/services/recording/libopus-worker-source.js.map +1 -0
- package/dist/lib/services/recording/linear-resampler.d.ts +45 -0
- package/dist/lib/services/recording/linear-resampler.d.ts.map +1 -0
- package/dist/lib/services/recording/linear-resampler.js +85 -0
- package/dist/lib/services/recording/linear-resampler.js.map +1 -0
- package/dist/lib/services/recording/opus-packet-encoder.d.ts +50 -0
- package/dist/lib/services/recording/opus-packet-encoder.d.ts.map +1 -0
- package/dist/lib/services/recording/opus-packet-encoder.js +41 -0
- package/dist/lib/services/recording/opus-packet-encoder.js.map +1 -0
- package/dist/lib/services/recording/wasm-opus-encoder.d.ts +86 -0
- package/dist/lib/services/recording/wasm-opus-encoder.d.ts.map +1 -0
- package/dist/lib/services/recording/wasm-opus-encoder.js +232 -0
- package/dist/lib/services/recording/wasm-opus-encoder.js.map +1 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.d.ts +39 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.d.ts.map +1 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.js +101 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.js.map +1 -0
- package/dist/lib/services/search.service.d.ts +7 -2
- package/dist/lib/services/search.service.d.ts.map +1 -1
- package/dist/lib/services/search.service.js +16 -20
- package/dist/lib/services/search.service.js.map +1 -1
- package/dist/lib/utils/conversation-branching.d.ts +4 -0
- package/dist/lib/utils/conversation-branching.d.ts.map +1 -0
- package/dist/lib/utils/conversation-branching.js +5 -0
- package/dist/lib/utils/conversation-branching.js.map +1 -0
- package/dist/lib/utils/conversation-forks.d.ts +315 -0
- package/dist/lib/utils/conversation-forks.d.ts.map +1 -0
- package/dist/lib/utils/conversation-forks.js +456 -0
- package/dist/lib/utils/conversation-forks.js.map +1 -0
- package/dist/lib/utils/distinct-artifacts.d.ts +9 -3
- package/dist/lib/utils/distinct-artifacts.d.ts.map +1 -1
- package/dist/lib/utils/distinct-artifacts.js +24 -5
- package/dist/lib/utils/distinct-artifacts.js.map +1 -1
- package/dist/public-api.d.ts +23 -1
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +30 -1
- package/dist/public-api.js.map +1 -1
- package/package.json +37 -33
- package/dist/lib/components/thread/thread-panel.component.d.ts +0 -126
- package/dist/lib/components/thread/thread-panel.component.d.ts.map +0 -1
- package/dist/lib/components/thread/thread-panel.component.js +0 -440
- package/dist/lib/components/thread/thread-panel.component.js.map +0 -1
- package/dist/lib/services/realtime-pcm-wav.d.ts +0 -79
- package/dist/lib/services/realtime-pcm-wav.d.ts.map +0 -1
- package/dist/lib/services/realtime-pcm-wav.js +0 -213
- package/dist/lib/services/realtime-pcm-wav.js.map +0 -1
|
@@ -1,36 +1,46 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RECORDING_SAMPLE_RATE } from '@memberjunction/ai-core-plus';
|
|
2
|
+
import { LinearResampler } from './recording/linear-resampler';
|
|
3
|
+
import { SelectOpusPacketEncoder } from './recording/opus-packet-encoder';
|
|
2
4
|
/**
|
|
3
|
-
* Browser-side audio recorder for a CLIENT-DIRECT realtime voice session
|
|
5
|
+
* Browser-side audio recorder for a CLIENT-DIRECT realtime voice session, implementing the
|
|
6
|
+
* runtime's {@link IRealtimeSessionRecorder} contract directly.
|
|
4
7
|
*
|
|
5
|
-
* Mixes the user's microphone
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* {@link
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* range seeking is exact and waveform peaks are computed cheaply during capture. The tradeoff is
|
|
13
|
-
* size (PCM WAV is larger than opus) — acceptable for these short session recordings.
|
|
8
|
+
* Mixes the user's microphone with the agent's remote-audio stream (every driver exposes one via
|
|
9
|
+
* `GetRemoteMediaStream()`: WebRTC drivers publish their remote track, PCM drivers — Gemini,
|
|
10
|
+
* ElevenLabs, AssemblyAI, xAI, HuggingFace — publish their playout engine's output) in a Web Audio
|
|
11
|
+
* graph, captures the mix as mono Float32 frames through an {@link AudioWorkletNode} (preferred) or a
|
|
12
|
+
* {@link ScriptProcessorNode} (fallback), and feeds them to ONE continuous Opus encoder for the whole
|
|
13
|
+
* call. The runtime pulls the encoded packets with {@link TakeEncodedPackets}, cuts them into shards
|
|
14
|
+
* and uploads them as the call goes, so nothing is held in memory until the end.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
+
* Timing is the 48 kHz sample clock: the graph asks for a 48 kHz context, and when the browser
|
|
17
|
+
* refuses (or reports another rate) every frame is resampled to 48 kHz before encoding. The
|
|
18
|
+
* captured-sample count drives {@link NowOffsetMs} and is what {@link Stop} resolves.
|
|
19
|
+
*
|
|
20
|
+
* Robust by design — every step degrades gracefully:
|
|
16
21
|
* - No `AudioContext` (server-side render / unsupported env) → the recorder disables itself
|
|
17
|
-
* ({@link IsRecording} stays `false`, {@link Stop} resolves
|
|
22
|
+
* ({@link IsRecording} stays `false`, {@link Stop} resolves 0); the voice call proceeds.
|
|
18
23
|
* - No `AudioWorkletNode` AND no `ScriptProcessorNode` → disables itself rather than throwing.
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
21
|
-
*
|
|
24
|
+
* - A forced-48 kHz context that cannot take the microphone (Firefox rejects a source whose rate
|
|
25
|
+
* differs from its context's) → rebuilt once at the device rate with resampling.
|
|
26
|
+
* - No remote stream yet (e.g. the WebRTC track hasn't landed) → records mic only until it does.
|
|
27
|
+
* - A remote stream that can't be connected (e.g. a cross-sample-rate `createMediaStreamSource`
|
|
28
|
+
* rejection) → warns and records mic only; the mic recording is never lost to the agent stream.
|
|
29
|
+
* - No usable encoder → logged, recording disabled for the call.
|
|
22
30
|
*
|
|
23
|
-
* Not an Angular service: it owns transient per-session media plumbing the
|
|
24
|
-
*
|
|
25
|
-
* shape (no DI, no singleton).
|
|
31
|
+
* Not an Angular service: it owns transient per-session media plumbing the runtime creates and
|
|
32
|
+
* discards per call, so a plain class is the right shape (no DI, no singleton).
|
|
26
33
|
*/
|
|
27
34
|
export class RealtimeAudioRecorder {
|
|
28
|
-
|
|
29
|
-
static wavMimeType = 'audio/wav';
|
|
30
|
-
/** Target waveform-peak resolution (buckets) across the whole recording. */
|
|
31
|
-
static peakBuckets = 600;
|
|
35
|
+
selectEncoder;
|
|
32
36
|
/** PCM frame size for the ScriptProcessor fallback (worklet uses its native 128-frame quantum). */
|
|
33
37
|
static scriptProcessorBufferSize = 4096;
|
|
38
|
+
/**
|
|
39
|
+
* Most 48 kHz audio held while the encoder is still being selected / loaded (10 s). Selection is
|
|
40
|
+
* normally well under a second; past this the encoder is stuck, and later frames are dropped
|
|
41
|
+
* (see {@link captureFrame} for why they are not counted either).
|
|
42
|
+
*/
|
|
43
|
+
static maxPendingSamples = 10 * RECORDING_SAMPLE_RATE;
|
|
34
44
|
/** Inline AudioWorklet processor: forwards each 128-frame mono input block to the main thread. */
|
|
35
45
|
static workletProcessorSource = `
|
|
36
46
|
class MJPcmCaptureProcessor extends AudioWorkletProcessor {
|
|
@@ -49,52 +59,48 @@ export class RealtimeAudioRecorder {
|
|
|
49
59
|
destination = null;
|
|
50
60
|
workletNode = null;
|
|
51
61
|
scriptNode = null;
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
/** Total captured sample count across {@link pcmFrames} (kept in step to avoid re-summing). */
|
|
55
|
-
totalSamples = 0;
|
|
56
|
-
/** Streaming waveform-peak accumulator — bounded regardless of recording length. */
|
|
57
|
-
peaks = new PeakAccumulator(RealtimeAudioRecorder.peakBuckets);
|
|
58
|
-
/** Final normalized peaks, snapshotted at {@link Stop} so {@link GetPeaks} survives cleanup. */
|
|
59
|
-
finalPeaks = [];
|
|
62
|
+
/** Converts the context's rate to 48 kHz; `null` when the context already runs at 48 kHz. */
|
|
63
|
+
resampler = null;
|
|
60
64
|
recording = false;
|
|
61
65
|
/** True once the agent's remote stream has been wired into the mix (prevents double-attach). */
|
|
62
66
|
remoteAttached = false;
|
|
63
67
|
/** A remote stream handed to AttachRemoteStream before the audio graph was ready; wired at setup. */
|
|
64
68
|
pendingRemoteStream = null;
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
/** The microphone to record: the latest stream given to Start or ReplaceMicrophone. Setup connects it. */
|
|
70
|
+
micStream = null;
|
|
71
|
+
/** The source node reading the microphone, swapped by ReplaceMicrophone. */
|
|
72
|
+
micSource = null;
|
|
73
|
+
/** 48 kHz samples captured (post-resample) — the recording's clock. */
|
|
74
|
+
capturedSamples = 0;
|
|
75
|
+
/** The call's encoder once selected; `null` while selecting, after a failure, or when not recording. */
|
|
76
|
+
encoder = null;
|
|
77
|
+
/** Resolves to the encoder (or `null` on failure) — Stop awaits it so buffered audio still gets encoded. */
|
|
78
|
+
encoderReady = null;
|
|
79
|
+
/** 48 kHz frames captured before the encoder was ready, fed to it in order once it is. */
|
|
80
|
+
pendingFrames = [];
|
|
81
|
+
pendingSamples = 0;
|
|
82
|
+
pendingOverflowWarned = false;
|
|
83
|
+
/** The first Stop's promise; later calls return it (idempotent). */
|
|
84
|
+
stopping = null;
|
|
79
85
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
86
|
+
* Bumped whenever the audio graph is released. The async graph setup snapshots it and, after each
|
|
87
|
+
* await, gives up if it moved: a {@link Stop} that lands mid-setup can only release what setup has
|
|
88
|
+
* already published, so setup must release the rest itself rather than build a live graph on a
|
|
89
|
+
* stopped recorder.
|
|
83
90
|
*/
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
setupGeneration = 0;
|
|
92
|
+
/** @param selectEncoder picks the call's Opus encoder; injectable for tests. */
|
|
93
|
+
constructor(selectEncoder = () => SelectOpusPacketEncoder()) {
|
|
94
|
+
this.selectEncoder = selectEncoder;
|
|
86
95
|
}
|
|
87
|
-
/**
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
*/
|
|
91
|
-
get SampleRate() {
|
|
92
|
-
return this.sampleRate;
|
|
96
|
+
/** True while a recorder is actively capturing. */
|
|
97
|
+
get IsRecording() {
|
|
98
|
+
return this.recording;
|
|
93
99
|
}
|
|
94
100
|
/**
|
|
95
101
|
* Begins recording a mix of `micStream` and (when present) `remoteStream`. Best-effort:
|
|
96
102
|
* a missing `AudioContext` / no worklet+scriptprocessor support / any setup failure disables
|
|
97
|
-
* the recorder
|
|
103
|
+
* the recorder (logged) so the live call is never blocked.
|
|
98
104
|
*
|
|
99
105
|
* @param micStream The user's microphone capture stream (required).
|
|
100
106
|
* @param remoteStream The agent's remote-audio stream, or `null` for mic-only capture.
|
|
@@ -107,18 +113,19 @@ export class RealtimeAudioRecorder {
|
|
|
107
113
|
console.warn('[RealtimeAudioRecorder] AudioContext unavailable — recording disabled for this session.');
|
|
108
114
|
return;
|
|
109
115
|
}
|
|
110
|
-
// Mark recording
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
// caller discards the recorder before it finishes starting and nothing is ever uploaded.
|
|
114
|
-
this.startedAtMs = Date.now();
|
|
115
|
-
this.finalPeaks = [];
|
|
116
|
+
// Mark recording SYNCHRONOUSLY so a caller that checks IsRecording immediately after Start()
|
|
117
|
+
// (the runtime does) sees `true`, even though the audio graph and the encoder are set up
|
|
118
|
+
// asynchronously below.
|
|
116
119
|
this.recording = true;
|
|
120
|
+
this.stopping = null;
|
|
121
|
+
this.capturedSamples = 0;
|
|
122
|
+
this.encoderReady = this.prepareEncoder();
|
|
123
|
+
this.micStream = micStream;
|
|
117
124
|
// Fire-and-forget the async setup (it must `await audioContext.resume()` + worklet load); any
|
|
118
125
|
// failure disables the recorder rather than throwing into the session-start path.
|
|
119
126
|
void this.startMixedRecording(micStream, remoteStream).catch((error) => {
|
|
120
127
|
console.warn('[RealtimeAudioRecorder] Failed to start recording — disabling for this session:', error);
|
|
121
|
-
this.
|
|
128
|
+
this.disable();
|
|
122
129
|
});
|
|
123
130
|
}
|
|
124
131
|
/**
|
|
@@ -132,8 +139,7 @@ export class RealtimeAudioRecorder {
|
|
|
132
139
|
return;
|
|
133
140
|
}
|
|
134
141
|
if (this.audioContext && this.captureNode()) {
|
|
135
|
-
this.
|
|
136
|
-
this.remoteAttached = true;
|
|
142
|
+
this.remoteAttached = this.connectRemoteStream(this.audioContext, stream);
|
|
137
143
|
}
|
|
138
144
|
else {
|
|
139
145
|
// Audio-graph setup still in flight (awaiting resume / worklet load) — connect at setup.
|
|
@@ -141,84 +147,177 @@ export class RealtimeAudioRecorder {
|
|
|
141
147
|
}
|
|
142
148
|
}
|
|
143
149
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* The canonical consolidated file is still authored as a single WAV from full memory at {@link Stop}.
|
|
150
|
+
* Records the microphone stream's current track from now on: call it after the track changes (a
|
|
151
|
+
* device switch). A source node reads the track it was created with, so the recording would otherwise
|
|
152
|
+
* carry silence from the old device. Safe before the audio graph is ready (setup connects the latest
|
|
153
|
+
* stream, and Start replaces it); a stream without an audio track keeps the current one.
|
|
149
154
|
*/
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
155
|
+
ReplaceMicrophone(micStream) {
|
|
156
|
+
this.micStream = micStream;
|
|
157
|
+
if (!this.audioContext || !this.captureNode()) {
|
|
158
|
+
return;
|
|
153
159
|
}
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
const replacement = this.connectStream(this.audioContext, micStream);
|
|
161
|
+
if (replacement) {
|
|
162
|
+
this.micSource?.disconnect();
|
|
163
|
+
this.micSource = replacement;
|
|
158
164
|
}
|
|
159
|
-
const pcm16 = this.floatToPcm16Bytes(fresh);
|
|
160
|
-
return new Blob([pcm16], { type: 'application/octet-stream' });
|
|
161
165
|
}
|
|
162
|
-
/**
|
|
166
|
+
/** Milliseconds of audio captured so far, on the 48 kHz sample clock (never wall clock). */
|
|
163
167
|
NowOffsetMs() {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
return (this.capturedSamples * 1000) / RECORDING_SAMPLE_RATE;
|
|
169
|
+
}
|
|
170
|
+
/** The Opus packets the encoder produced since the last call (also its final packets after {@link Stop}). */
|
|
171
|
+
TakeEncodedPackets() {
|
|
172
|
+
return this.encoder ? this.encoder.TakePackets() : [];
|
|
168
173
|
}
|
|
169
174
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
175
|
+
* Stops capture, waits for the encoder (encoding any audio buffered while it loaded), flushes and
|
|
176
|
+
* closes it, and resolves the 48 kHz samples captured. The final packets stay available to
|
|
177
|
+
* {@link TakeEncodedPackets}. Idempotent: every call resolves the same count; 0 when the recorder
|
|
178
|
+
* never started.
|
|
174
179
|
*/
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
Stop() {
|
|
181
|
+
if (!this.stopping) {
|
|
182
|
+
this.stopping = this.stopOnce();
|
|
183
|
+
}
|
|
184
|
+
return this.stopping;
|
|
185
|
+
}
|
|
186
|
+
async stopOnce() {
|
|
187
|
+
this.recording = false;
|
|
188
|
+
this.releaseGraph();
|
|
189
|
+
const encoderReady = this.encoderReady;
|
|
190
|
+
if (!encoderReady) {
|
|
191
|
+
return this.capturedSamples;
|
|
192
|
+
}
|
|
193
|
+
const encoder = await encoderReady;
|
|
194
|
+
if (encoder) {
|
|
195
|
+
await encoder.Flush();
|
|
196
|
+
encoder.Close();
|
|
197
|
+
}
|
|
198
|
+
return this.capturedSamples;
|
|
177
199
|
}
|
|
178
200
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* (recorder disabled, or no samples captured). Safe to call when not recording.
|
|
201
|
+
* Selects the encoder and feeds it what was captured meanwhile. A selection failure disables the
|
|
202
|
+
* recorder: with no encoder nothing captured could ever reach the server.
|
|
182
203
|
*/
|
|
183
|
-
async
|
|
184
|
-
|
|
185
|
-
|
|
204
|
+
async prepareEncoder() {
|
|
205
|
+
let encoder;
|
|
206
|
+
try {
|
|
207
|
+
encoder = await this.selectEncoder();
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
console.warn('[RealtimeAudioRecorder] No Opus encoder available — recording disabled for this session:', error);
|
|
211
|
+
this.disable();
|
|
212
|
+
// Nothing captured can be encoded now, so report nothing recorded rather than a count with no audio.
|
|
213
|
+
this.pendingFrames = [];
|
|
214
|
+
this.pendingSamples = 0;
|
|
215
|
+
this.capturedSamples = 0;
|
|
186
216
|
return null;
|
|
187
217
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
this.
|
|
194
|
-
return
|
|
218
|
+
this.encoder = encoder;
|
|
219
|
+
for (const frame of this.pendingFrames) {
|
|
220
|
+
encoder.Encode(frame);
|
|
221
|
+
}
|
|
222
|
+
this.pendingFrames = [];
|
|
223
|
+
this.pendingSamples = 0;
|
|
224
|
+
return encoder;
|
|
195
225
|
}
|
|
196
|
-
/**
|
|
226
|
+
/** Builds the mix graph + capture node, retrying once at the device rate if 48 kHz cannot take the mic. */
|
|
197
227
|
async startMixedRecording(micStream, remoteStream) {
|
|
198
|
-
const
|
|
228
|
+
const built = await this.buildGraph(micStream, remoteStream, true);
|
|
229
|
+
if (built === 'mic-rejected') {
|
|
230
|
+
await this.buildGraph(micStream, remoteStream, false);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Wires up the Web Audio mix graph + PCM capture node (worklet preferred) and starts capture.
|
|
235
|
+
* Resolves `'mic-rejected'` when a forced-48 kHz context refused the microphone source (the graph
|
|
236
|
+
* is released so the caller can rebuild at the device rate); otherwise `'done'`.
|
|
237
|
+
*/
|
|
238
|
+
async buildGraph(micStream, remoteStream, prefer48k) {
|
|
239
|
+
const generation = this.setupGeneration;
|
|
240
|
+
const audioContext = this.createAudioContext(prefer48k);
|
|
241
|
+
const forced48k = prefer48k && audioContext.sampleRate === RECORDING_SAMPLE_RATE;
|
|
199
242
|
// CRITICAL: browsers create an AudioContext in the `suspended` state under the autoplay policy.
|
|
200
243
|
// Without resuming it, the mix graph never processes audio and we capture only silence. Starting
|
|
201
244
|
// a voice call is a user gesture, so resume() succeeds here.
|
|
202
245
|
if (audioContext.state === 'suspended') {
|
|
203
246
|
await audioContext.resume();
|
|
204
247
|
}
|
|
248
|
+
if (generation !== this.setupGeneration) {
|
|
249
|
+
// Stopped while resuming: the context was never published, so releaseGraph() could not close it.
|
|
250
|
+
this.closeContext(audioContext);
|
|
251
|
+
return 'done';
|
|
252
|
+
}
|
|
205
253
|
const destination = audioContext.createMediaStreamDestination();
|
|
206
|
-
// Publish the context
|
|
207
|
-
//
|
|
254
|
+
// Publish the context BEFORE wiring the remote so a concurrent AttachRemoteStream() (the agent
|
|
255
|
+
// track can land during the awaits above) sees a ready graph to connect to.
|
|
208
256
|
this.audioContext = audioContext;
|
|
209
257
|
this.destination = destination;
|
|
210
|
-
this.
|
|
258
|
+
this.resampler = audioContext.sampleRate === RECORDING_SAMPLE_RATE ? null : new LinearResampler(audioContext.sampleRate, RECORDING_SAMPLE_RATE);
|
|
211
259
|
// Build the PCM capture node: AudioWorklet preferred, ScriptProcessor fallback. Either way the
|
|
212
260
|
// node both pulls audio from the mix AND drives the destination so the graph keeps processing.
|
|
213
261
|
await this.createCaptureNode(audioContext, destination);
|
|
214
|
-
this.
|
|
262
|
+
if (generation !== this.setupGeneration) {
|
|
263
|
+
return 'done'; // Stopped while the capture node loaded: releaseGraph() already closed the published context.
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
// The microphone may have been replaced while the graph was being built.
|
|
267
|
+
this.micSource = this.connectStream(audioContext, this.micStream ?? micStream);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
if (!forced48k) {
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
console.warn('[RealtimeAudioRecorder] The 48 kHz context rejected the microphone — rebuilding at the device rate with resampling:', error);
|
|
274
|
+
// releaseGraph() forgets a stashed agent stream and a replaced microphone; the rebuild still has to connect both.
|
|
275
|
+
const stashedRemote = this.pendingRemoteStream;
|
|
276
|
+
const stashedMic = this.micStream;
|
|
277
|
+
this.releaseGraph();
|
|
278
|
+
this.pendingRemoteStream = stashedRemote;
|
|
279
|
+
this.micStream = stashedMic;
|
|
280
|
+
return 'mic-rejected';
|
|
281
|
+
}
|
|
215
282
|
// Connect the agent's stream if it was passed now, or if AttachRemoteStream() stashed one while
|
|
216
283
|
// we were setting up. Either way, capture the agent voice — not just the mic.
|
|
217
284
|
const remote = remoteStream ?? this.pendingRemoteStream;
|
|
218
285
|
this.pendingRemoteStream = null;
|
|
219
286
|
if (remote && remote.getAudioTracks().length > 0) {
|
|
220
|
-
this.
|
|
221
|
-
|
|
287
|
+
this.remoteAttached = this.connectRemoteStream(audioContext, remote);
|
|
288
|
+
}
|
|
289
|
+
return 'done';
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* A 48 kHz context when asked for and the browser allows it, else the device-rate default. A
|
|
293
|
+
* browser that silently picks another rate is fine too: the resampler is chosen from the rate the
|
|
294
|
+
* context actually reports.
|
|
295
|
+
*/
|
|
296
|
+
createAudioContext(prefer48k) {
|
|
297
|
+
if (prefer48k) {
|
|
298
|
+
try {
|
|
299
|
+
return new AudioContext({ sampleRate: RECORDING_SAMPLE_RATE });
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
console.warn('[RealtimeAudioRecorder] 48 kHz AudioContext refused — capturing at the device rate with resampling:', error);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return new AudioContext();
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Connects the agent stream, degrading to mic-only on failure. Unlike the mic (whose failure
|
|
309
|
+
* rightly disables the recorder), the agent stream is best-effort: a browser may reject it
|
|
310
|
+
* (cross-sample-rate `NotSupportedError`) and that must not discard the user's recording.
|
|
311
|
+
* Returns whether it was connected, so the caller only marks it attached on success.
|
|
312
|
+
*/
|
|
313
|
+
connectRemoteStream(audioContext, stream) {
|
|
314
|
+
try {
|
|
315
|
+
this.connectStream(audioContext, stream);
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
console.warn('[RealtimeAudioRecorder] Could not mix the agent stream into the recording — continuing mic-only:', error);
|
|
320
|
+
return false;
|
|
222
321
|
}
|
|
223
322
|
}
|
|
224
323
|
/**
|
|
@@ -263,10 +362,7 @@ export class RealtimeAudioRecorder {
|
|
|
263
362
|
throw new Error('Neither AudioWorklet nor ScriptProcessor is supported');
|
|
264
363
|
}
|
|
265
364
|
const node = audioContext.createScriptProcessor(RealtimeAudioRecorder.scriptProcessorBufferSize, 1, 1);
|
|
266
|
-
node.onaudioprocess = (event) =>
|
|
267
|
-
// Copy out of the reused input buffer before retaining.
|
|
268
|
-
this.captureFrame(event.inputBuffer.getChannelData(0).slice(0));
|
|
269
|
-
};
|
|
365
|
+
node.onaudioprocess = (event) => this.captureFrame(event.inputBuffer.getChannelData(0));
|
|
270
366
|
// ScriptProcessor only fires onaudioprocess while connected to the destination.
|
|
271
367
|
node.connect(destination);
|
|
272
368
|
this.scriptNode = node;
|
|
@@ -275,105 +371,96 @@ export class RealtimeAudioRecorder {
|
|
|
275
371
|
captureNode() {
|
|
276
372
|
return this.workletNode ?? this.scriptNode;
|
|
277
373
|
}
|
|
278
|
-
/**
|
|
374
|
+
/**
|
|
375
|
+
* Absorbs one captured frame: resample to 48 kHz, count it, and hand it to the encoder (or buffer
|
|
376
|
+
* it while the encoder loads).
|
|
377
|
+
*
|
|
378
|
+
* Past the pending-buffer cap a frame is dropped AND not counted. Keeping the count equal to the
|
|
379
|
+
* audio actually handed to the encoder is what lets the runtime trust `packets × 960 ≥ captured`
|
|
380
|
+
* when it trims the final shard; a few lost seconds at the start of a call whose encoder never
|
|
381
|
+
* loaded is the smaller harm.
|
|
382
|
+
*/
|
|
279
383
|
captureFrame(frame) {
|
|
280
384
|
if (!this.recording || frame.length === 0) {
|
|
281
385
|
return;
|
|
282
386
|
}
|
|
283
|
-
//
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
387
|
+
// Copy out of the engine's reused buffer (a resampler output is already a fresh array).
|
|
388
|
+
const samples = this.resampler ? this.resampler.Process(frame) : frame.slice(0);
|
|
389
|
+
if (samples.length === 0) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (this.encoder) {
|
|
393
|
+
this.encoder.Encode(samples);
|
|
394
|
+
}
|
|
395
|
+
else if (this.pendingSamples + samples.length <= RealtimeAudioRecorder.maxPendingSamples) {
|
|
396
|
+
this.pendingFrames.push(samples);
|
|
397
|
+
this.pendingSamples += samples.length;
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
if (!this.pendingOverflowWarned) {
|
|
401
|
+
this.pendingOverflowWarned = true;
|
|
402
|
+
console.warn(`[RealtimeAudioRecorder] Opus encoder still not ready after ${RealtimeAudioRecorder.maxPendingSamples / RECORDING_SAMPLE_RATE} s of audio — dropping audio until it is.`);
|
|
403
|
+
}
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
this.capturedSamples += samples.length;
|
|
288
407
|
}
|
|
289
|
-
/** Routes one stream's audio tracks into the capture node
|
|
408
|
+
/** Routes one stream's audio tracks into the capture node; returns the source, or null when track-less / no node. */
|
|
290
409
|
connectStream(audioContext, stream) {
|
|
291
410
|
const node = this.captureNode();
|
|
292
411
|
if (!node || stream.getAudioTracks().length === 0) {
|
|
293
|
-
return;
|
|
412
|
+
return null;
|
|
294
413
|
}
|
|
295
414
|
const source = audioContext.createMediaStreamSource(stream);
|
|
296
415
|
source.connect(node);
|
|
297
|
-
|
|
298
|
-
/** Concatenates a half-open sample range [from, to) across the captured frames into one Float32Array. */
|
|
299
|
-
collectSamples(from, to) {
|
|
300
|
-
const out = new Float32Array(Math.max(0, to - from));
|
|
301
|
-
if (out.length === 0) {
|
|
302
|
-
return out;
|
|
303
|
-
}
|
|
304
|
-
let cursor = 0; // absolute sample index at the start of the current frame
|
|
305
|
-
let written = 0;
|
|
306
|
-
for (const frame of this.pcmFrames) {
|
|
307
|
-
const frameStart = cursor;
|
|
308
|
-
const frameEnd = cursor + frame.length;
|
|
309
|
-
cursor = frameEnd;
|
|
310
|
-
if (frameEnd <= from) {
|
|
311
|
-
continue; // entirely before the window
|
|
312
|
-
}
|
|
313
|
-
if (frameStart >= to) {
|
|
314
|
-
break; // entirely after the window
|
|
315
|
-
}
|
|
316
|
-
const sliceStart = Math.max(0, from - frameStart);
|
|
317
|
-
const sliceEnd = Math.min(frame.length, to - frameStart);
|
|
318
|
-
for (let i = sliceStart; i < sliceEnd; i++) {
|
|
319
|
-
out[written++] = frame[i];
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return written === out.length ? out : out.subarray(0, written);
|
|
416
|
+
return source;
|
|
323
417
|
}
|
|
324
418
|
/**
|
|
325
|
-
*
|
|
326
|
-
*
|
|
419
|
+
* Turns recording off after a setup failure and releases the graph. Audio already captured stays
|
|
420
|
+
* queued for the encoder, so a graph failure mid-call still keeps what was recorded before it.
|
|
327
421
|
*/
|
|
328
|
-
|
|
329
|
-
const buffer = new ArrayBuffer(samples.length * 2);
|
|
330
|
-
const view = new DataView(buffer);
|
|
331
|
-
for (let i = 0; i < samples.length; i++) {
|
|
332
|
-
const s = samples[i] < -1 ? -1 : samples[i] > 1 ? 1 : samples[i];
|
|
333
|
-
view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
|
334
|
-
}
|
|
335
|
-
return buffer;
|
|
336
|
-
}
|
|
337
|
-
/** Encodes all accumulated PCM into a single seekable WAV blob, or `null` when nothing captured. */
|
|
338
|
-
encodeWavBlob() {
|
|
339
|
-
if (this.totalSamples === 0 || this.sampleRate === 0) {
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
const all = this.collectSamples(0, this.totalSamples);
|
|
343
|
-
const wav = EncodePcm16Wav(all, this.sampleRate);
|
|
344
|
-
return new Blob([wav], { type: RealtimeAudioRecorder.wavMimeType });
|
|
345
|
-
}
|
|
346
|
-
/** Releases the audio graph + capture node and resets state. Idempotent. */
|
|
347
|
-
cleanup() {
|
|
422
|
+
disable() {
|
|
348
423
|
this.recording = false;
|
|
349
|
-
this.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
this.
|
|
424
|
+
this.releaseGraph();
|
|
425
|
+
}
|
|
426
|
+
/** Releases the audio graph + capture node. Idempotent; leaves the encoder and the sample count alone. */
|
|
427
|
+
releaseGraph() {
|
|
428
|
+
this.setupGeneration++;
|
|
354
429
|
this.remoteAttached = false;
|
|
355
430
|
this.pendingRemoteStream = null;
|
|
431
|
+
this.micStream = null;
|
|
432
|
+
this.micSource = null;
|
|
356
433
|
if (this.workletNode) {
|
|
357
434
|
this.workletNode.port.onmessage = null;
|
|
358
|
-
|
|
359
|
-
this.workletNode.disconnect();
|
|
360
|
-
}
|
|
361
|
-
catch { /* already disconnected */ }
|
|
435
|
+
this.disconnectNode(this.workletNode);
|
|
362
436
|
this.workletNode = null;
|
|
363
437
|
}
|
|
364
438
|
if (this.scriptNode) {
|
|
365
439
|
this.scriptNode.onaudioprocess = null;
|
|
366
|
-
|
|
367
|
-
this.scriptNode.disconnect();
|
|
368
|
-
}
|
|
369
|
-
catch { /* already disconnected */ }
|
|
440
|
+
this.disconnectNode(this.scriptNode);
|
|
370
441
|
this.scriptNode = null;
|
|
371
442
|
}
|
|
372
443
|
this.destination = null;
|
|
444
|
+
this.resampler = null;
|
|
373
445
|
if (this.audioContext) {
|
|
374
|
-
|
|
446
|
+
this.closeContext(this.audioContext);
|
|
375
447
|
this.audioContext = null;
|
|
376
448
|
}
|
|
377
449
|
}
|
|
450
|
+
disconnectNode(node) {
|
|
451
|
+
try {
|
|
452
|
+
node.disconnect();
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
// Expected when the node was never connected or is already disconnected; nothing to release.
|
|
456
|
+
console.debug('[RealtimeAudioRecorder] Capture node disconnect skipped:', error);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
closeContext(audioContext) {
|
|
460
|
+
void audioContext.close().catch((error) => {
|
|
461
|
+
// A context that is already closed rejects here; the graph is released either way.
|
|
462
|
+
console.debug('[RealtimeAudioRecorder] AudioContext close skipped:', error);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
378
465
|
}
|
|
379
466
|
//# sourceMappingURL=realtime-audio-recorder.js.map
|