@memberjunction/ng-conversations 6.2.0-edge.2 → 6.2.0-edge.4
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 +4 -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-chat-area.component.d.ts +632 -55
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +2748 -624
- 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 +121 -11
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +307 -106
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +178 -5
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +919 -306
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +92 -21
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +337 -52
- 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/workspace/conversation-workspace.component.d.ts +22 -31
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.js +50 -63
- 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 +82 -29
- 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 +12 -0
- package/dist/lib/services/browser-realtime-media-host.d.ts.map +1 -1
- package/dist/lib/services/browser-realtime-media-host.js +19 -0
- package/dist/lib/services/browser-realtime-media-host.js.map +1 -1
- package/dist/lib/services/browser-recording-unload-guard.d.ts +30 -0
- package/dist/lib/services/browser-recording-unload-guard.d.ts.map +1 -0
- package/dist/lib/services/browser-recording-unload-guard.js +43 -0
- package/dist/lib/services/browser-recording-unload-guard.js.map +1 -0
- package/dist/lib/services/conversation-agent.service.d.ts +35 -11
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +65 -20
- 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 +84 -10
- package/dist/lib/services/conversation-naming.js.map +1 -1
- 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 +33 -5
- package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.js +71 -11
- 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 +3 -2
- package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session.service.js +7 -2
- package/dist/lib/services/realtime-session.service.js.map +1 -1
- 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 +13 -4
- package/dist/lib/services/search.service.js.map +1 -1
- package/dist/lib/util/frame-zone.d.ts +10 -0
- package/dist/lib/util/frame-zone.d.ts.map +1 -0
- package/dist/lib/util/frame-zone.js +10 -0
- package/dist/lib/util/frame-zone.js.map +1 -0
- 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/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 -32
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-audio-recorder.js","sourceRoot":"","sources":["../../../src/lib/services/realtime-audio-recorder.ts"],"names":[],"mappings":"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","sourcesContent":["import { EncodePcm16Wav, PeakAccumulator } from './realtime-pcm-wav';\n\n/**\n * Browser-side audio recorder for a CLIENT-DIRECT realtime voice session.\n *\n * Mixes the user's microphone stream with the agent's remote-audio stream (when the driver\n * exposes one — `OpenAIRealtimeClient.GetRemoteMediaStream()`) into a single mono **WAV** (16-bit\n * PCM) blob via the Web Audio API. Rather than a `MediaRecorder` (which only emits webm/opus —\n * header-less, no duration/cues, so HTTP-range seeking is unreliable), the mix is routed into an\n * {@link AudioWorkletNode} (preferred) or a {@link ScriptProcessorNode} (fallback) that captures\n * Float32 PCM frames. PCM is accumulated in memory and encoded as a seekable RIFF/WAVE file at\n * {@link Stop}. WAV's header carries the exact duration and maps byte-offset → time linearly, so\n * range seeking is exact and waveform peaks are computed cheaply during capture. The tradeoff is\n * size (PCM WAV is larger than opus) — acceptable for these short session recordings.\n *\n * Robust + MVP by design — every step degrades gracefully:\n * - No `AudioContext` (server-side render / unsupported env) → the recorder disables itself\n * ({@link IsRecording} stays `false`, {@link Stop} resolves `null`); the voice call proceeds.\n * - No `AudioWorkletNode` AND no `ScriptProcessorNode` → disables itself rather than throwing.\n * - No remote stream (non-WebRTC drivers, or the track hasn't landed yet) → records mic only.\n * - Any construction/start error is logged and disables the recorder rather than throwing into\n * the session-start path.\n *\n * Not an Angular service: it owns transient per-session media plumbing the\n * {@link RealtimeSessionService} creates and discards per call, so a plain class is the right\n * shape (no DI, no singleton).\n */\nexport class RealtimeAudioRecorder {\n /** The container/codec we now produce — a seekable mono 16-bit PCM WAV. */\n private static readonly wavMimeType = 'audio/wav';\n /** Target waveform-peak resolution (buckets) across the whole recording. */\n private static readonly peakBuckets = 600;\n /** PCM frame size for the ScriptProcessor fallback (worklet uses its native 128-frame quantum). */\n private static readonly scriptProcessorBufferSize = 4096;\n /** Inline AudioWorklet processor: forwards each 128-frame mono input block to the main thread. */\n private static readonly workletProcessorSource = `\n class MJPcmCaptureProcessor extends AudioWorkletProcessor {\n process(inputs) {\n const input = inputs[0];\n if (input && input[0] && input[0].length > 0) {\n // Copy out of the reused worklet buffer before posting.\n this.port.postMessage(input[0].slice(0));\n }\n return true;\n }\n }\n registerProcessor('mj-pcm-capture', MJPcmCaptureProcessor);\n `;\n\n private audioContext: AudioContext | null = null;\n private destination: MediaStreamAudioDestinationNode | null = null;\n private workletNode: AudioWorkletNode | null = null;\n private scriptNode: ScriptProcessorNode | null = null;\n /** Captured mono Float32 PCM frames (in capture order); concatenated + encoded to WAV at Stop. */\n private pcmFrames: Float32Array[] = [];\n /** Total captured sample count across {@link pcmFrames} (kept in step to avoid re-summing). */\n private totalSamples = 0;\n /** Streaming waveform-peak accumulator — bounded regardless of recording length. */\n private peaks = new PeakAccumulator(RealtimeAudioRecorder.peakBuckets);\n /** Final normalized peaks, snapshotted at {@link Stop} so {@link GetPeaks} survives cleanup. */\n private finalPeaks: number[] = [];\n private recording = false;\n\n /** True once the agent's remote stream has been wired into the mix (prevents double-attach). */\n private remoteAttached = false;\n /** A remote stream handed to AttachRemoteStream before the audio graph was ready; wired at setup. */\n private pendingRemoteStream: MediaStream | null = null;\n\n /** Sample index up to which crash-recovery shards have already been emitted (the segment cursor). */\n private flushedSampleCount = 0;\n\n /** Wall-clock ms at recording start; anchors {@link NowOffsetMs}. 0 before {@link Start}. */\n private startedAtMs = 0;\n\n /** Sample rate of the capturing {@link AudioContext}; 0 before {@link Start}. Needed to wrap shards. */\n private sampleRate = 0;\n\n /** True while a recorder is actively capturing. */\n public get IsRecording(): boolean {\n return this.recording;\n }\n\n /** Wall-clock ms when recording started (0 before {@link Start}). */\n public get StartedAtMs(): number {\n return this.startedAtMs;\n }\n\n /**\n * The container/codec the active recording uses. Always `'audio/wav'` once recording (empty\n * string before {@link Start} / when disabled) — the consolidated upload and the WAV-wrapped\n * crash-recovery shards are all 16-bit PCM WAV.\n */\n public get MimeType(): string {\n return this.recording ? RealtimeAudioRecorder.wavMimeType : '';\n }\n\n /**\n * The captured AudioContext sample rate (0 before {@link Start} / when disabled). Exposed so a\n * recovery path can WAV-wrap the raw-PCM shards with the correct rate.\n */\n public get SampleRate(): number {\n return this.sampleRate;\n }\n\n /**\n * Begins recording a mix of `micStream` and (when present) `remoteStream`. Best-effort:\n * a missing `AudioContext` / no worklet+scriptprocessor support / any setup failure disables\n * the recorder silently (logged) so the live call is never blocked.\n *\n * @param micStream The user's microphone capture stream (required).\n * @param remoteStream The agent's remote-audio stream, or `null` for mic-only capture.\n */\n public Start(micStream: MediaStream, remoteStream: MediaStream | null): void {\n if (this.recording) {\n return;\n }\n if (typeof AudioContext === 'undefined') {\n console.warn('[RealtimeAudioRecorder] AudioContext unavailable — recording disabled for this session.');\n return;\n }\n // Mark recording + stamp t0 SYNCHRONOUSLY so a caller that checks IsRecording immediately after\n // Start() (the session service does) sees `true`, even though the audio-graph setup\n // (AudioContext.resume + worklet module load) completes asynchronously below. Without this the\n // caller discards the recorder before it finishes starting and nothing is ever uploaded.\n this.startedAtMs = Date.now();\n this.finalPeaks = [];\n this.recording = true;\n // Fire-and-forget the async setup (it must `await audioContext.resume()` + worklet load); any\n // failure disables the recorder rather than throwing into the session-start path.\n void this.startMixedRecording(micStream, remoteStream).catch((error) => {\n console.warn('[RealtimeAudioRecorder] Failed to start recording — disabling for this session:', error);\n this.cleanup();\n });\n }\n\n /**\n * Wires the agent's remote-audio stream into the live mix once it arrives. The WebRTC\n * `ontrack` that delivers the agent voice usually lands AFTER recording has already begun\n * (mic-only), so the session attaches it here when it lands. Idempotent, and safe to call\n * before the audio graph finishes its async setup (the stream is connected once ready).\n */\n public AttachRemoteStream(stream: MediaStream): void {\n if (this.remoteAttached || stream.getAudioTracks().length === 0) {\n return;\n }\n if (this.audioContext && this.captureNode()) {\n this.connectStream(this.audioContext, stream);\n this.remoteAttached = true;\n } else {\n // Audio-graph setup still in flight (awaiting resume / worklet load) — connect at setup.\n this.pendingRemoteStream = stream;\n }\n }\n\n /**\n * Returns a Blob of the raw PCM captured since the last call (a window of header-less,\n * little-endian PCM16 bytes), advancing the segment cursor. `null` when nothing new. Used to\n * upload crash-recovery shards mid-call. NOTE: shards are header-less raw PCM (NOT individually\n * playable WAV) — recovery is concatenation in order, then WAV-wrapping with {@link SampleRate}.\n * The canonical consolidated file is still authored as a single WAV from full memory at {@link Stop}.\n */\n public SnapshotNewSegment(): Blob | null {\n if (!this.recording || this.flushedSampleCount >= this.totalSamples) {\n return null;\n }\n const fresh = this.collectSamples(this.flushedSampleCount, this.totalSamples);\n this.flushedSampleCount = this.totalSamples;\n if (fresh.length === 0) {\n return null;\n }\n const pcm16 = this.floatToPcm16Bytes(fresh);\n return new Blob([pcm16], { type: 'application/octet-stream' });\n }\n\n /** Ms elapsed since recording started (>= 0); 0 when not recording. */\n public NowOffsetMs(): number {\n if (this.startedAtMs === 0) {\n return 0;\n }\n return Math.max(0, Date.now() - this.startedAtMs);\n }\n\n /**\n * The downsampled waveform peaks (each 0..1, max-abs per bucket, ~{@link RealtimeAudioRecorder.PeakBuckets}\n * buckets across the whole recording). Computed incrementally during capture so it's cheap and\n * bounded regardless of length. Returns `[]` before any audio / for a silent recording. Safe to\n * call after {@link Stop} — the peaks are snapshotted there before {@link cleanup} clears state.\n */\n public GetPeaks(): number[] {\n return this.recording ? this.peaks.Normalize() : this.finalPeaks;\n }\n\n /**\n * Stops recording and resolves the captured audio as a single mono 16-bit PCM **WAV**\n * {@link Blob}, then releases the audio graph. Resolves `null` when nothing was recorded\n * (recorder disabled, or no samples captured). Safe to call when not recording.\n */\n public async Stop(): Promise<Blob | null> {\n if (!this.recording) {\n this.cleanup();\n return null;\n }\n // Detach the capture node so no further frames arrive while we assemble the file. Snapshot the\n // peaks BEFORE cleanup so GetPeaks() still returns them for the post-Stop upload.\n this.recording = false;\n this.finalPeaks = this.peaks.Normalize();\n const blob = this.encodeWavBlob();\n this.cleanup();\n return blob;\n }\n\n /** Wires up the Web Audio mix graph + PCM capture node (worklet preferred) and starts capture. */\n private async startMixedRecording(micStream: MediaStream, remoteStream: MediaStream | null): Promise<void> {\n const audioContext = new AudioContext();\n // CRITICAL: browsers create an AudioContext in the `suspended` state under the autoplay policy.\n // Without resuming it, the mix graph never processes audio and we capture only silence. Starting\n // a voice call is a user gesture, so resume() succeeds here.\n if (audioContext.state === 'suspended') {\n await audioContext.resume();\n }\n const destination = audioContext.createMediaStreamDestination();\n // Publish the context + sample rate BEFORE wiring the remote so a concurrent AttachRemoteStream()\n // (the agent track can land during the awaits above) sees a ready graph to connect to.\n this.audioContext = audioContext;\n this.destination = destination;\n this.sampleRate = audioContext.sampleRate;\n\n // Build the PCM capture node: AudioWorklet preferred, ScriptProcessor fallback. Either way the\n // node both pulls audio from the mix AND drives the destination so the graph keeps processing.\n await this.createCaptureNode(audioContext, destination);\n\n this.connectStream(audioContext, micStream);\n // Connect the agent's stream if it was passed now, or if AttachRemoteStream() stashed one while\n // we were setting up. Either way, capture the agent voice — not just the mic.\n const remote = remoteStream ?? this.pendingRemoteStream;\n this.pendingRemoteStream = null;\n if (remote && remote.getAudioTracks().length > 0) {\n this.connectStream(audioContext, remote);\n this.remoteAttached = true;\n }\n }\n\n /**\n * Creates the PCM capture node and inserts it into the graph: sources → captureNode → destination.\n * Prefers an {@link AudioWorkletNode} (off-main-thread, no deprecation); falls back to a\n * {@link ScriptProcessorNode} where worklets are unavailable. Throws when neither is supported\n * (caught by {@link Start}, which then disables the recorder).\n */\n private async createCaptureNode(audioContext: AudioContext, destination: MediaStreamAudioDestinationNode): Promise<void> {\n if (await this.tryCreateWorkletNode(audioContext, destination)) {\n return;\n }\n this.createScriptProcessorNode(audioContext, destination);\n }\n\n /** Attempts to build the AudioWorklet capture node; returns `false` (no throw) when unsupported. */\n private async tryCreateWorkletNode(audioContext: AudioContext, destination: MediaStreamAudioDestinationNode): Promise<boolean> {\n if (!audioContext.audioWorklet || typeof AudioWorkletNode === 'undefined' || typeof Blob === 'undefined') {\n return false;\n }\n try {\n const moduleUrl = URL.createObjectURL(\n new Blob([RealtimeAudioRecorder.workletProcessorSource], { type: 'application/javascript' }),\n );\n try {\n await audioContext.audioWorklet.addModule(moduleUrl);\n } finally {\n URL.revokeObjectURL(moduleUrl);\n }\n const node = new AudioWorkletNode(audioContext, 'mj-pcm-capture', { numberOfInputs: 1, numberOfOutputs: 1 });\n node.port.onmessage = (event: MessageEvent<Float32Array>) => this.captureFrame(event.data);\n node.connect(destination);\n this.workletNode = node;\n return true;\n } catch (error) {\n console.warn('[RealtimeAudioRecorder] AudioWorklet unavailable — falling back to ScriptProcessor:', error);\n return false;\n }\n }\n\n /** Builds the ScriptProcessor fallback capture node and inserts it into the graph. */\n private createScriptProcessorNode(audioContext: AudioContext, destination: MediaStreamAudioDestinationNode): void {\n if (typeof audioContext.createScriptProcessor !== 'function') {\n throw new Error('Neither AudioWorklet nor ScriptProcessor is supported');\n }\n const node = audioContext.createScriptProcessor(RealtimeAudioRecorder.scriptProcessorBufferSize, 1, 1);\n node.onaudioprocess = (event: AudioProcessingEvent) => {\n // Copy out of the reused input buffer before retaining.\n this.captureFrame(event.inputBuffer.getChannelData(0).slice(0));\n };\n // ScriptProcessor only fires onaudioprocess while connected to the destination.\n node.connect(destination);\n this.scriptNode = node;\n }\n\n /** The active capture node (worklet or script processor), or `null` before setup. */\n private captureNode(): AudioNode | null {\n return this.workletNode ?? this.scriptNode;\n }\n\n /** Absorbs one captured PCM frame: accumulate samples + update the bounded peak array. */\n private captureFrame(frame: Float32Array): void {\n if (!this.recording || frame.length === 0) {\n return;\n }\n // Defensive copy — worklet/script buffers are reused by the engine.\n const copy = frame.slice(0);\n this.pcmFrames.push(copy);\n this.totalSamples += copy.length;\n this.peaks.Push(copy);\n }\n\n /** Routes one stream's audio tracks into the capture node (no-op when track-less / no node). */\n private connectStream(audioContext: AudioContext, stream: MediaStream): void {\n const node = this.captureNode();\n if (!node || stream.getAudioTracks().length === 0) {\n return;\n }\n const source = audioContext.createMediaStreamSource(stream);\n source.connect(node);\n }\n\n /** Concatenates a half-open sample range [from, to) across the captured frames into one Float32Array. */\n private collectSamples(from: number, to: number): Float32Array {\n const out = new Float32Array(Math.max(0, to - from));\n if (out.length === 0) {\n return out;\n }\n let cursor = 0; // absolute sample index at the start of the current frame\n let written = 0;\n for (const frame of this.pcmFrames) {\n const frameStart = cursor;\n const frameEnd = cursor + frame.length;\n cursor = frameEnd;\n if (frameEnd <= from) {\n continue; // entirely before the window\n }\n if (frameStart >= to) {\n break; // entirely after the window\n }\n const sliceStart = Math.max(0, from - frameStart);\n const sliceEnd = Math.min(frame.length, to - frameStart);\n for (let i = sliceStart; i < sliceEnd; i++) {\n out[written++] = frame[i];\n }\n }\n return written === out.length ? out : out.subarray(0, written);\n }\n\n /**\n * Converts a mono Float32 buffer to a little-endian PCM16 `ArrayBuffer` (for raw crash-recovery\n * shards). Returns the backing `ArrayBuffer` directly so it's a valid `BlobPart`.\n */\n private floatToPcm16Bytes(samples: Float32Array): ArrayBuffer {\n const buffer = new ArrayBuffer(samples.length * 2);\n const view = new DataView(buffer);\n for (let i = 0; i < samples.length; i++) {\n const s = samples[i] < -1 ? -1 : samples[i] > 1 ? 1 : samples[i];\n view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);\n }\n return buffer;\n }\n\n /** Encodes all accumulated PCM into a single seekable WAV blob, or `null` when nothing captured. */\n private encodeWavBlob(): Blob | null {\n if (this.totalSamples === 0 || this.sampleRate === 0) {\n return null;\n }\n const all = this.collectSamples(0, this.totalSamples);\n const wav = EncodePcm16Wav(all, this.sampleRate);\n return new Blob([wav], { type: RealtimeAudioRecorder.wavMimeType });\n }\n\n /** Releases the audio graph + capture node and resets state. Idempotent. */\n private cleanup(): void {\n this.recording = false;\n this.pcmFrames = [];\n this.totalSamples = 0;\n this.flushedSampleCount = 0;\n this.sampleRate = 0;\n this.peaks = new PeakAccumulator(RealtimeAudioRecorder.peakBuckets);\n this.remoteAttached = false;\n this.pendingRemoteStream = null;\n if (this.workletNode) {\n this.workletNode.port.onmessage = null;\n try { this.workletNode.disconnect(); } catch { /* already disconnected */ }\n this.workletNode = null;\n }\n if (this.scriptNode) {\n this.scriptNode.onaudioprocess = null;\n try { this.scriptNode.disconnect(); } catch { /* already disconnected */ }\n this.scriptNode = null;\n }\n this.destination = null;\n if (this.audioContext) {\n void this.audioContext.close().catch(() => { /* already closed */ });\n this.audioContext = null;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"realtime-audio-recorder.js","sourceRoot":"","sources":["../../../src/lib/services/realtime-audio-recorder.ts"],"names":[],"mappings":"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","sourcesContent":["import { EncodePcm16Wav, PeakAccumulator } from './realtime-pcm-wav';\n\n/**\n * Browser-side audio recorder for a CLIENT-DIRECT realtime voice session.\n *\n * Mixes the user's microphone stream with the agent's remote-audio stream (every driver exposes\n * one via `GetRemoteMediaStream()`: WebRTC drivers publish their remote track, PCM drivers —\n * Gemini, ElevenLabs, AssemblyAI, xAI, HuggingFace — publish their playout engine's output) into a\n * single mono **WAV** (16-bit PCM) blob via the Web Audio API. Rather than a `MediaRecorder` (which only emits webm/opus —\n * header-less, no duration/cues, so HTTP-range seeking is unreliable), the mix is routed into an\n * {@link AudioWorkletNode} (preferred) or a {@link ScriptProcessorNode} (fallback) that captures\n * Float32 PCM frames. PCM is accumulated in memory and encoded as a seekable RIFF/WAVE file at\n * {@link Stop}. WAV's header carries the exact duration and maps byte-offset → time linearly, so\n * range seeking is exact and waveform peaks are computed cheaply during capture. The tradeoff is\n * size (PCM WAV is larger than opus) — acceptable for these short session recordings.\n *\n * Robust + MVP by design — every step degrades gracefully:\n * - No `AudioContext` (server-side render / unsupported env) → the recorder disables itself\n * ({@link IsRecording} stays `false`, {@link Stop} resolves `null`); the voice call proceeds.\n * - No `AudioWorkletNode` AND no `ScriptProcessorNode` → disables itself rather than throwing.\n * - No remote stream yet (e.g. the WebRTC track hasn't landed) → records mic only until it does.\n * - A remote stream that can't be connected (e.g. a browser that rejects cross-sample-rate\n * `createMediaStreamSource` — the PCM playout context runs at 24 kHz, this one at the device\n * rate) → warns and records mic only; the mic recording is never lost to the agent stream.\n * - Any construction/start error is logged and disables the recorder rather than throwing into\n * the session-start path.\n *\n * Not an Angular service: it owns transient per-session media plumbing the\n * {@link RealtimeSessionService} creates and discards per call, so a plain class is the right\n * shape (no DI, no singleton).\n */\nexport class RealtimeAudioRecorder {\n /** The container/codec we now produce — a seekable mono 16-bit PCM WAV. */\n private static readonly wavMimeType = 'audio/wav';\n /** Target waveform-peak resolution (buckets) across the whole recording. */\n private static readonly peakBuckets = 600;\n /** PCM frame size for the ScriptProcessor fallback (worklet uses its native 128-frame quantum). */\n private static readonly scriptProcessorBufferSize = 4096;\n /** Inline AudioWorklet processor: forwards each 128-frame mono input block to the main thread. */\n private static readonly workletProcessorSource = `\n class MJPcmCaptureProcessor extends AudioWorkletProcessor {\n process(inputs) {\n const input = inputs[0];\n if (input && input[0] && input[0].length > 0) {\n // Copy out of the reused worklet buffer before posting.\n this.port.postMessage(input[0].slice(0));\n }\n return true;\n }\n }\n registerProcessor('mj-pcm-capture', MJPcmCaptureProcessor);\n `;\n\n private audioContext: AudioContext | null = null;\n private destination: MediaStreamAudioDestinationNode | null = null;\n private workletNode: AudioWorkletNode | null = null;\n private scriptNode: ScriptProcessorNode | null = null;\n /** Captured mono Float32 PCM frames (in capture order); concatenated + encoded to WAV at Stop. */\n private pcmFrames: Float32Array[] = [];\n /** Total captured sample count across {@link pcmFrames} (kept in step to avoid re-summing). */\n private totalSamples = 0;\n /** Streaming waveform-peak accumulator — bounded regardless of recording length. */\n private peaks = new PeakAccumulator(RealtimeAudioRecorder.peakBuckets);\n /** Final normalized peaks, snapshotted at {@link Stop} so {@link GetPeaks} survives cleanup. */\n private finalPeaks: number[] = [];\n private recording = false;\n\n /** True once the agent's remote stream has been wired into the mix (prevents double-attach). */\n private remoteAttached = false;\n /** A remote stream handed to AttachRemoteStream before the audio graph was ready; wired at setup. */\n private pendingRemoteStream: MediaStream | null = null;\n /** The microphone to record: the latest stream given to Start or ReplaceMicrophone. Setup connects it. */\n private micStream: MediaStream | null = null;\n /** The source node reading the microphone, swapped by ReplaceMicrophone. */\n private micSource: MediaStreamAudioSourceNode | null = null;\n\n /** Sample index up to which crash-recovery shards have already been emitted (the segment cursor). */\n private flushedSampleCount = 0;\n\n /** Wall-clock ms at recording start; anchors {@link NowOffsetMs}. 0 before {@link Start}. */\n private startedAtMs = 0;\n\n /** Sample rate of the capturing {@link AudioContext}; 0 before {@link Start}. Needed to wrap shards. */\n private sampleRate = 0;\n\n /**\n * Bumped by {@link cleanup}. The async setup snapshots it and, after each await, gives up if it moved:\n * a {@link Stop} that lands mid-setup can only release what setup has already published, so setup\n * must release the rest itself rather than build a live graph on a stopped recorder.\n */\n private setupGeneration = 0;\n\n /** True while a recorder is actively capturing. */\n public get IsRecording(): boolean {\n return this.recording;\n }\n\n /** Wall-clock ms when recording started (0 before {@link Start}). */\n public get StartedAtMs(): number {\n return this.startedAtMs;\n }\n\n /**\n * The container/codec the active recording uses. Always `'audio/wav'` once recording (empty\n * string before {@link Start} / when disabled) — the consolidated upload and the WAV-wrapped\n * crash-recovery shards are all 16-bit PCM WAV.\n */\n public get MimeType(): string {\n return this.recording ? RealtimeAudioRecorder.wavMimeType : '';\n }\n\n /**\n * The captured AudioContext sample rate (0 before {@link Start} / when disabled). Exposed so a\n * recovery path can WAV-wrap the raw-PCM shards with the correct rate.\n */\n public get SampleRate(): number {\n return this.sampleRate;\n }\n\n /**\n * Begins recording a mix of `micStream` and (when present) `remoteStream`. Best-effort:\n * a missing `AudioContext` / no worklet+scriptprocessor support / any setup failure disables\n * the recorder silently (logged) so the live call is never blocked.\n *\n * @param micStream The user's microphone capture stream (required).\n * @param remoteStream The agent's remote-audio stream, or `null` for mic-only capture.\n */\n public Start(micStream: MediaStream, remoteStream: MediaStream | null): void {\n if (this.recording) {\n return;\n }\n if (typeof AudioContext === 'undefined') {\n console.warn('[RealtimeAudioRecorder] AudioContext unavailable — recording disabled for this session.');\n return;\n }\n // Mark recording + stamp t0 SYNCHRONOUSLY so a caller that checks IsRecording immediately after\n // Start() (the session service does) sees `true`, even though the audio-graph setup\n // (AudioContext.resume + worklet module load) completes asynchronously below. Without this the\n // caller discards the recorder before it finishes starting and nothing is ever uploaded.\n this.startedAtMs = Date.now();\n this.finalPeaks = [];\n this.recording = true;\n this.micStream = micStream;\n // Fire-and-forget the async setup (it must `await audioContext.resume()` + worklet load); any\n // failure disables the recorder rather than throwing into the session-start path.\n void this.startMixedRecording(micStream, remoteStream).catch((error) => {\n console.warn('[RealtimeAudioRecorder] Failed to start recording — disabling for this session:', error);\n this.cleanup();\n });\n }\n\n /**\n * Wires the agent's remote-audio stream into the live mix once it arrives. The WebRTC\n * `ontrack` that delivers the agent voice usually lands AFTER recording has already begun\n * (mic-only), so the session attaches it here when it lands. Idempotent, and safe to call\n * before the audio graph finishes its async setup (the stream is connected once ready).\n */\n public AttachRemoteStream(stream: MediaStream): void {\n if (this.remoteAttached || stream.getAudioTracks().length === 0) {\n return;\n }\n if (this.audioContext && this.captureNode()) {\n this.remoteAttached = this.connectRemoteStream(this.audioContext, stream);\n } else {\n // Audio-graph setup still in flight (awaiting resume / worklet load) — connect at setup.\n this.pendingRemoteStream = stream;\n }\n }\n\n /**\n * Records the microphone stream's current track from now on: call it after the track changes (a\n * device switch). A source node reads the track it was created with, so the recording would otherwise\n * carry silence from the old device. Safe before the audio graph is ready (setup connects the latest\n * stream, and Start replaces it); a stream without an audio track keeps the current one.\n */\n public ReplaceMicrophone(micStream: MediaStream): void {\n this.micStream = micStream;\n if (!this.audioContext || !this.captureNode()) {\n return;\n }\n const replacement = this.connectStream(this.audioContext, micStream);\n if (replacement) {\n this.micSource?.disconnect();\n this.micSource = replacement;\n }\n }\n\n /**\n * Returns a Blob of the raw PCM captured since the last call (a window of header-less,\n * little-endian PCM16 bytes), advancing the segment cursor. `null` when nothing new. Used to\n * upload crash-recovery shards mid-call. NOTE: shards are header-less raw PCM (NOT individually\n * playable WAV) — recovery is concatenation in order, then WAV-wrapping with {@link SampleRate}.\n * The canonical consolidated file is still authored as a single WAV from full memory at {@link Stop}.\n */\n public SnapshotNewSegment(): Blob | null {\n if (!this.recording || this.flushedSampleCount >= this.totalSamples) {\n return null;\n }\n const fresh = this.collectSamples(this.flushedSampleCount, this.totalSamples);\n this.flushedSampleCount = this.totalSamples;\n if (fresh.length === 0) {\n return null;\n }\n const pcm16 = this.floatToPcm16Bytes(fresh);\n return new Blob([pcm16], { type: 'application/octet-stream' });\n }\n\n /** Ms elapsed since recording started (>= 0); 0 when not recording. */\n public NowOffsetMs(): number {\n if (this.startedAtMs === 0) {\n return 0;\n }\n return Math.max(0, Date.now() - this.startedAtMs);\n }\n\n /**\n * The downsampled waveform peaks (each 0..1, max-abs per bucket, ~{@link RealtimeAudioRecorder.PeakBuckets}\n * buckets across the whole recording). Computed incrementally during capture so it's cheap and\n * bounded regardless of length. Returns `[]` before any audio / for a silent recording. Safe to\n * call after {@link Stop} — the peaks are snapshotted there before {@link cleanup} clears state.\n */\n public GetPeaks(): number[] {\n return this.recording ? this.peaks.Normalize() : this.finalPeaks;\n }\n\n /**\n * Stops recording and resolves the captured audio as a single mono 16-bit PCM **WAV**\n * {@link Blob}, then releases the audio graph. Resolves `null` when nothing was recorded\n * (recorder disabled, or no samples captured). Safe to call when not recording.\n */\n public async Stop(): Promise<Blob | null> {\n if (!this.recording) {\n this.cleanup();\n return null;\n }\n // Detach the capture node so no further frames arrive while we assemble the file. Snapshot the\n // peaks BEFORE cleanup so GetPeaks() still returns them for the post-Stop upload.\n this.recording = false;\n this.finalPeaks = this.peaks.Normalize();\n const blob = this.encodeWavBlob();\n this.cleanup();\n return blob;\n }\n\n /** Wires up the Web Audio mix graph + PCM capture node (worklet preferred) and starts capture. */\n private async startMixedRecording(micStream: MediaStream, remoteStream: MediaStream | null): Promise<void> {\n const generation = this.setupGeneration;\n const audioContext = new AudioContext();\n // CRITICAL: browsers create an AudioContext in the `suspended` state under the autoplay policy.\n // Without resuming it, the mix graph never processes audio and we capture only silence. Starting\n // a voice call is a user gesture, so resume() succeeds here.\n if (audioContext.state === 'suspended') {\n await audioContext.resume();\n }\n if (generation !== this.setupGeneration) {\n // Stopped while resuming: the context was never published, so cleanup() could not close it.\n void audioContext.close().catch(() => { /* already closed */ });\n return;\n }\n const destination = audioContext.createMediaStreamDestination();\n // Publish the context + sample rate BEFORE wiring the remote so a concurrent AttachRemoteStream()\n // (the agent track can land during the awaits above) sees a ready graph to connect to.\n this.audioContext = audioContext;\n this.destination = destination;\n this.sampleRate = audioContext.sampleRate;\n\n // Build the PCM capture node: AudioWorklet preferred, ScriptProcessor fallback. Either way the\n // node both pulls audio from the mix AND drives the destination so the graph keeps processing.\n await this.createCaptureNode(audioContext, destination);\n if (generation !== this.setupGeneration) {\n return; // Stopped while the capture node loaded: cleanup() already closed the published context.\n }\n\n // The microphone may have been replaced while the graph was being built.\n this.micSource = this.connectStream(audioContext, this.micStream ?? micStream);\n // Connect the agent's stream if it was passed now, or if AttachRemoteStream() stashed one while\n // we were setting up. Either way, capture the agent voice — not just the mic.\n const remote = remoteStream ?? this.pendingRemoteStream;\n this.pendingRemoteStream = null;\n if (remote && remote.getAudioTracks().length > 0) {\n this.remoteAttached = this.connectRemoteStream(audioContext, remote);\n }\n }\n\n /**\n * Connects the agent stream, degrading to mic-only on failure. Unlike the mic (whose failure\n * rightly disables the recorder), the agent stream is best-effort: a browser may reject it\n * (cross-sample-rate `NotSupportedError`) and that must not discard the user's recording.\n * Returns whether it was connected, so the caller only marks it attached on success.\n */\n private connectRemoteStream(audioContext: AudioContext, stream: MediaStream): boolean {\n try {\n this.connectStream(audioContext, stream);\n return true;\n } catch (error) {\n console.warn('[RealtimeAudioRecorder] Could not mix the agent stream into the recording — continuing mic-only:', error);\n return false;\n }\n }\n\n /**\n * Creates the PCM capture node and inserts it into the graph: sources → captureNode → destination.\n * Prefers an {@link AudioWorkletNode} (off-main-thread, no deprecation); falls back to a\n * {@link ScriptProcessorNode} where worklets are unavailable. Throws when neither is supported\n * (caught by {@link Start}, which then disables the recorder).\n */\n private async createCaptureNode(audioContext: AudioContext, destination: MediaStreamAudioDestinationNode): Promise<void> {\n if (await this.tryCreateWorkletNode(audioContext, destination)) {\n return;\n }\n this.createScriptProcessorNode(audioContext, destination);\n }\n\n /** Attempts to build the AudioWorklet capture node; returns `false` (no throw) when unsupported. */\n private async tryCreateWorkletNode(audioContext: AudioContext, destination: MediaStreamAudioDestinationNode): Promise<boolean> {\n if (!audioContext.audioWorklet || typeof AudioWorkletNode === 'undefined' || typeof Blob === 'undefined') {\n return false;\n }\n try {\n const moduleUrl = URL.createObjectURL(\n new Blob([RealtimeAudioRecorder.workletProcessorSource], { type: 'application/javascript' }),\n );\n try {\n await audioContext.audioWorklet.addModule(moduleUrl);\n } finally {\n URL.revokeObjectURL(moduleUrl);\n }\n const node = new AudioWorkletNode(audioContext, 'mj-pcm-capture', { numberOfInputs: 1, numberOfOutputs: 1 });\n node.port.onmessage = (event: MessageEvent<Float32Array>) => this.captureFrame(event.data);\n node.connect(destination);\n this.workletNode = node;\n return true;\n } catch (error) {\n console.warn('[RealtimeAudioRecorder] AudioWorklet unavailable — falling back to ScriptProcessor:', error);\n return false;\n }\n }\n\n /** Builds the ScriptProcessor fallback capture node and inserts it into the graph. */\n private createScriptProcessorNode(audioContext: AudioContext, destination: MediaStreamAudioDestinationNode): void {\n if (typeof audioContext.createScriptProcessor !== 'function') {\n throw new Error('Neither AudioWorklet nor ScriptProcessor is supported');\n }\n const node = audioContext.createScriptProcessor(RealtimeAudioRecorder.scriptProcessorBufferSize, 1, 1);\n node.onaudioprocess = (event: AudioProcessingEvent) => {\n // Copy out of the reused input buffer before retaining.\n this.captureFrame(event.inputBuffer.getChannelData(0).slice(0));\n };\n // ScriptProcessor only fires onaudioprocess while connected to the destination.\n node.connect(destination);\n this.scriptNode = node;\n }\n\n /** The active capture node (worklet or script processor), or `null` before setup. */\n private captureNode(): AudioNode | null {\n return this.workletNode ?? this.scriptNode;\n }\n\n /** Absorbs one captured PCM frame: accumulate samples + update the bounded peak array. */\n private captureFrame(frame: Float32Array): void {\n if (!this.recording || frame.length === 0) {\n return;\n }\n // Defensive copy — worklet/script buffers are reused by the engine.\n const copy = frame.slice(0);\n this.pcmFrames.push(copy);\n this.totalSamples += copy.length;\n this.peaks.Push(copy);\n }\n\n /** Routes one stream's audio tracks into the capture node; returns the source, or null when track-less / no node. */\n private connectStream(audioContext: AudioContext, stream: MediaStream): MediaStreamAudioSourceNode | null {\n const node = this.captureNode();\n if (!node || stream.getAudioTracks().length === 0) {\n return null;\n }\n const source = audioContext.createMediaStreamSource(stream);\n source.connect(node);\n return source;\n }\n\n /** Concatenates a half-open sample range [from, to) across the captured frames into one Float32Array. */\n private collectSamples(from: number, to: number): Float32Array {\n const out = new Float32Array(Math.max(0, to - from));\n if (out.length === 0) {\n return out;\n }\n let cursor = 0; // absolute sample index at the start of the current frame\n let written = 0;\n for (const frame of this.pcmFrames) {\n const frameStart = cursor;\n const frameEnd = cursor + frame.length;\n cursor = frameEnd;\n if (frameEnd <= from) {\n continue; // entirely before the window\n }\n if (frameStart >= to) {\n break; // entirely after the window\n }\n const sliceStart = Math.max(0, from - frameStart);\n const sliceEnd = Math.min(frame.length, to - frameStart);\n for (let i = sliceStart; i < sliceEnd; i++) {\n out[written++] = frame[i];\n }\n }\n return written === out.length ? out : out.subarray(0, written);\n }\n\n /**\n * Converts a mono Float32 buffer to a little-endian PCM16 `ArrayBuffer` (for raw crash-recovery\n * shards). Returns the backing `ArrayBuffer` directly so it's a valid `BlobPart`.\n */\n private floatToPcm16Bytes(samples: Float32Array): ArrayBuffer {\n const buffer = new ArrayBuffer(samples.length * 2);\n const view = new DataView(buffer);\n for (let i = 0; i < samples.length; i++) {\n const s = samples[i] < -1 ? -1 : samples[i] > 1 ? 1 : samples[i];\n view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);\n }\n return buffer;\n }\n\n /** Encodes all accumulated PCM into a single seekable WAV blob, or `null` when nothing captured. */\n private encodeWavBlob(): Blob | null {\n if (this.totalSamples === 0 || this.sampleRate === 0) {\n return null;\n }\n const all = this.collectSamples(0, this.totalSamples);\n const wav = EncodePcm16Wav(all, this.sampleRate);\n return new Blob([wav], { type: RealtimeAudioRecorder.wavMimeType });\n }\n\n /** Releases the audio graph + capture node and resets state. Idempotent. */\n private cleanup(): void {\n this.setupGeneration++;\n this.recording = false;\n this.pcmFrames = [];\n this.totalSamples = 0;\n this.flushedSampleCount = 0;\n this.sampleRate = 0;\n this.peaks = new PeakAccumulator(RealtimeAudioRecorder.peakBuckets);\n this.remoteAttached = false;\n this.pendingRemoteStream = null;\n this.micStream = null;\n this.micSource = null;\n if (this.workletNode) {\n this.workletNode.port.onmessage = null;\n try { this.workletNode.disconnect(); } catch { /* already disconnected */ }\n this.workletNode = null;\n }\n if (this.scriptNode) {\n this.scriptNode.onaudioprocess = null;\n try { this.scriptNode.disconnect(); } catch { /* already disconnected */ }\n this.scriptNode = null;\n }\n this.destination = null;\n if (this.audioContext) {\n void this.audioContext.close().catch(() => { /* already closed */ });\n this.audioContext = null;\n }\n }\n}\n"]}
|
|
@@ -108,19 +108,24 @@ export interface VoiceModelCandidate {
|
|
|
108
108
|
export declare function BuildRealtimeModelOptions(models: ReadonlyArray<VoiceModelCandidate>): RealtimeModelOption[];
|
|
109
109
|
/**
|
|
110
110
|
* Builds the `configOverridesJson` payload for the `StartRealtimeClientSession` mint from
|
|
111
|
-
* what the (authorization-gated) pickers chose — an explicit realtime model preference
|
|
112
|
-
* voice. The envelope
|
|
111
|
+
* what the (authorization-gated) pickers chose — an explicit realtime model preference, a
|
|
112
|
+
* voice, and the avatar that comes with that voice. The envelope
|
|
113
|
+
* (`{"realtime":{"modelPreference":"<id>","voice":{"default":{"voice":"<v>"}},"video":{"avatarId":"<a>"}}}`)
|
|
113
114
|
* is the pinned server contract, merged into the effective config server-side (highest precedence).
|
|
114
115
|
*
|
|
115
116
|
* The voice is filed PROVIDER-AGNOSTICALLY: the browser has a model id, never a vendor `DriverClass`
|
|
116
117
|
* (`GetRealtimeModelVoices` does not return one), so naming a provider here would be a guess that
|
|
117
118
|
* silently voided the setting for every non-OpenAI vendor. The server files it onto whichever driver
|
|
118
|
-
* it resolves.
|
|
119
|
-
*
|
|
119
|
+
* it resolves. A picked avatar leaves video to the agent: the server shows it only when the voiced
|
|
120
|
+
* agent's own video setting is on (else the call says why, reason `video-off`), and only when it names
|
|
121
|
+
* a Video binding on the vendor it resolves. Kept in lockstep with the server-side
|
|
122
|
+
* `BuildRealtimeOverridesJson` in `@memberjunction/ai-agents` by `realtime-convergence-drift.test.ts`.
|
|
123
|
+
* See issue #3530.
|
|
120
124
|
*
|
|
121
125
|
* @param preferredModelId The explicit `MJ: AI Models` id, or null/empty for none.
|
|
122
126
|
* @param preferredVoice The provider-native voice id (e.g. `echo`), or null/empty for none.
|
|
127
|
+
* @param preferredAvatarId The avatar that comes with the picked voice (`RealtimeVoiceOption.AvatarID`), or null/empty for none.
|
|
123
128
|
* @returns The JSON string, or `null` when nothing was overridden (keeps the mint identical to default).
|
|
124
129
|
*/
|
|
125
|
-
export declare function BuildRealtimeConfigOverridesJson(preferredModelId: string | null | undefined, preferredVoice?: string | null | undefined): string | null;
|
|
130
|
+
export declare function BuildRealtimeConfigOverridesJson(preferredModelId: string | null | undefined, preferredVoice?: string | null | undefined, preferredAvatarId?: string | null | undefined): string | null;
|
|
126
131
|
//# sourceMappingURL=realtime-pairing.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-pairing.d.ts","sourceRoot":"","sources":["../../../src/lib/services/realtime-pairing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAIzD;;;;;;;;;GASG;AACH,MAAM,WAAW,sBAAsB;IACrC,0BAA0B;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,mFAAmF;IACnF,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC,CA2B3H;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,GAAG,sBAAsB,EAAE,CASlG;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,iBAAiB,EACpE,MAAM,EAAE,SAAS,CAAC,EAAE,EACpB,QAAQ,EAAE,SAAS,sBAAsB,EAAE,GAC1C,CAAC,EAAE,CAmBL;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,GAAG,MAAM,GAAG,IAAI,CAOhG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAQlI;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAEnG;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,kCAAkC;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,kCAAkC;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kDAAkD;IAClD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,aAAa,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,EAAE,CAK3G;AAED
|
|
1
|
+
{"version":3,"file":"realtime-pairing.d.ts","sourceRoot":"","sources":["../../../src/lib/services/realtime-pairing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAIzD;;;;;;;;;GASG;AACH,MAAM,WAAW,sBAAsB;IACrC,0BAA0B;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,mFAAmF;IACnF,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC,CA2B3H;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,GAAG,sBAAsB,EAAE,CASlG;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,iBAAiB,EACpE,MAAM,EAAE,SAAS,CAAC,EAAE,EACpB,QAAQ,EAAE,SAAS,sBAAsB,EAAE,GAC1C,CAAC,EAAE,CAmBL;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,GAAG,MAAM,GAAG,IAAI,CAOhG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAQlI;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAEnG;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,kCAAkC;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,kCAAkC;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kDAAkD;IAClD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,aAAa,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,EAAE,CAK3G;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gCAAgC,CAC9C,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC3C,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC1C,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC5C,MAAM,GAAG,IAAI,CAkBf"}
|
|
@@ -132,24 +132,30 @@ export function BuildRealtimeModelOptions(models) {
|
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
134
|
* Builds the `configOverridesJson` payload for the `StartRealtimeClientSession` mint from
|
|
135
|
-
* what the (authorization-gated) pickers chose — an explicit realtime model preference
|
|
136
|
-
* voice. The envelope
|
|
135
|
+
* what the (authorization-gated) pickers chose — an explicit realtime model preference, a
|
|
136
|
+
* voice, and the avatar that comes with that voice. The envelope
|
|
137
|
+
* (`{"realtime":{"modelPreference":"<id>","voice":{"default":{"voice":"<v>"}},"video":{"avatarId":"<a>"}}}`)
|
|
137
138
|
* is the pinned server contract, merged into the effective config server-side (highest precedence).
|
|
138
139
|
*
|
|
139
140
|
* The voice is filed PROVIDER-AGNOSTICALLY: the browser has a model id, never a vendor `DriverClass`
|
|
140
141
|
* (`GetRealtimeModelVoices` does not return one), so naming a provider here would be a guess that
|
|
141
142
|
* silently voided the setting for every non-OpenAI vendor. The server files it onto whichever driver
|
|
142
|
-
* it resolves.
|
|
143
|
-
*
|
|
143
|
+
* it resolves. A picked avatar leaves video to the agent: the server shows it only when the voiced
|
|
144
|
+
* agent's own video setting is on (else the call says why, reason `video-off`), and only when it names
|
|
145
|
+
* a Video binding on the vendor it resolves. Kept in lockstep with the server-side
|
|
146
|
+
* `BuildRealtimeOverridesJson` in `@memberjunction/ai-agents` by `realtime-convergence-drift.test.ts`.
|
|
147
|
+
* See issue #3530.
|
|
144
148
|
*
|
|
145
149
|
* @param preferredModelId The explicit `MJ: AI Models` id, or null/empty for none.
|
|
146
150
|
* @param preferredVoice The provider-native voice id (e.g. `echo`), or null/empty for none.
|
|
151
|
+
* @param preferredAvatarId The avatar that comes with the picked voice (`RealtimeVoiceOption.AvatarID`), or null/empty for none.
|
|
147
152
|
* @returns The JSON string, or `null` when nothing was overridden (keeps the mint identical to default).
|
|
148
153
|
*/
|
|
149
|
-
export function BuildRealtimeConfigOverridesJson(preferredModelId, preferredVoice) {
|
|
154
|
+
export function BuildRealtimeConfigOverridesJson(preferredModelId, preferredVoice, preferredAvatarId) {
|
|
150
155
|
const modelId = preferredModelId?.trim() ?? '';
|
|
151
156
|
const voice = preferredVoice?.trim() ?? '';
|
|
152
|
-
|
|
157
|
+
const avatarId = preferredAvatarId?.trim() ?? '';
|
|
158
|
+
if (modelId.length === 0 && voice.length === 0 && avatarId.length === 0) {
|
|
153
159
|
return null;
|
|
154
160
|
}
|
|
155
161
|
const realtime = {};
|
|
@@ -159,6 +165,9 @@ export function BuildRealtimeConfigOverridesJson(preferredModelId, preferredVoic
|
|
|
159
165
|
if (voice.length > 0) {
|
|
160
166
|
realtime.voice = { default: { voice } };
|
|
161
167
|
}
|
|
168
|
+
if (avatarId.length > 0) {
|
|
169
|
+
realtime.video = { avatarId };
|
|
170
|
+
}
|
|
162
171
|
return JSON.stringify({ realtime });
|
|
163
172
|
}
|
|
164
173
|
//# sourceMappingURL=realtime-pairing.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-pairing.js","sourceRoot":"","sources":["../../../src/lib/services/realtime-pairing.ts"],"names":[],"mappings":"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","sourcesContent":["import { IMetadataProvider } from '@memberjunction/core';\nimport { NormalizeUUID, UUIDsEqual } from '@memberjunction/global';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\n\n/**\n * The narrow read-only projection of an `MJ: AI Agent Co Agents` row the voice UI consumes\n * (loaded via {@link LoadCoAgentPairings} from {@link AIEngineBase}'s cached `AgentCoAgents`;\n * only **Active** rows of relationship **Type `'CoAgent'`** with a specific agent target —\n * type-level rows express the resolution-chain default, not a target restriction, and\n * reserved relationship types are ignored until their features ship). Pairing semantics: a\n * co-agent with ZERO such rows is **universal** (it can front any target agent — the\n * zero-config default); a co-agent WITH rows may front exactly the listed targets, with the\n * `IsDefault` row as its preselected target.\n */\nexport interface RealtimePairedAgentRow {\n /** The pairing row id. */\n ID: string;\n /** The Realtime-type co-agent the pairing belongs to. */\n CoAgentID: string;\n /** The target agent this co-agent may front. */\n TargetAgentID: string;\n /** Denormalized target-agent display name (from the base view). */\n TargetAgent: string | null;\n /** When true, this target is the co-agent's default (at most one per co-agent). */\n IsDefault: boolean;\n /** Display/list ordering of the pairing rows. */\n Sequence: number | null;\n}\n\n/** Minimal agent shape the pure pairing/co-agent helpers operate on. */\nexport interface RealtimeAgentLike {\n ID: string;\n}\n\n/**\n * Loads the pairing rows for one co-agent from {@link AIEngineBase}'s cached\n * `MJ: AI Agent Co Agents` metadata (provider-scoped engine instance, lazy `Config`),\n * in `Sequence` order. The engine's BaseEntity-event reactivity keeps the cache fresh —\n * no per-call RunView round-trip.\n *\n * Tolerant by design: any failure degrades to an EMPTY list — i.e. \"no pairing\n * constraint\". The client gate is pure disclosure; the server independently validates\n * the co-agent/target pairing at session mint.\n */\nexport async function LoadCoAgentPairings(provider: IMetadataProvider, coAgentId: string): Promise<RealtimePairedAgentRow[]> {\n const id = coAgentId?.trim() ?? '';\n if (id.length === 0) {\n return [];\n }\n try {\n const engine = AIEngineBase.GetProviderInstance<AIEngineBase>(provider, AIEngineBase) as AIEngineBase;\n await engine.Config(false, undefined, provider);\n const rows = (engine.AgentCoAgents ?? [])\n .filter(p =>\n p.Type === 'CoAgent' &&\n p.Status === 'Active' &&\n p.TargetAgentID != null &&\n UUIDsEqual(p.CoAgentID, id))\n .map<RealtimePairedAgentRow>(p => ({\n ID: p.ID,\n CoAgentID: p.CoAgentID,\n TargetAgentID: p.TargetAgentID!,\n TargetAgent: p.TargetAgent ?? null,\n IsDefault: p.IsDefault,\n Sequence: p.Sequence ?? null\n }));\n return SortPairings(rows);\n } catch (error) {\n console.warn('[RealtimePairing] Co-agent pairing lookup unavailable — treating co-agent as universal:', error);\n return [];\n }\n}\n\n/**\n * Returns the pairing rows in canonical display order: `Sequence` ascending (null/missing\n * sequences last), ties broken by target-agent name. Pure; never mutates the input.\n */\nexport function SortPairings(pairings: readonly RealtimePairedAgentRow[]): RealtimePairedAgentRow[] {\n return [...pairings].sort((a, b) => {\n const aSeq = a.Sequence ?? Number.MAX_SAFE_INTEGER;\n const bSeq = b.Sequence ?? Number.MAX_SAFE_INTEGER;\n if (aSeq !== bSeq) {\n return aSeq - bSeq;\n }\n return (a.TargetAgent ?? '').localeCompare(b.TargetAgent ?? '');\n });\n}\n\n/**\n * Applies the pairing constraint to a candidate target-agent list:\n * - ZERO pairing rows → the co-agent is universal; the candidates are returned unchanged\n * (today's flow untouched).\n * - One or more rows → only candidates that appear among the pairing targets are\n * returned, ordered by the pairings' `Sequence`. Paired targets the user can't see /\n * run (absent from `agents`) are silently dropped.\n *\n * Pure and UUID-casing safe (`NormalizeUUID` keys).\n */\nexport function ConstrainTargetsToPairings<T extends RealtimeAgentLike>(\n agents: readonly T[],\n pairings: readonly RealtimePairedAgentRow[]\n): T[] {\n if (!pairings || pairings.length === 0) {\n return [...agents];\n }\n const agentsById = new Map<string, T>();\n for (const agent of agents) {\n agentsById.set(NormalizeUUID(agent.ID), agent);\n }\n const constrained: T[] = [];\n const seen = new Set<string>();\n for (const pairing of SortPairings(pairings)) {\n const key = NormalizeUUID(pairing.TargetAgentID);\n const agent = agentsById.get(key);\n if (agent && !seen.has(key)) {\n seen.add(key);\n constrained.push(agent);\n }\n }\n return constrained;\n}\n\n/**\n * The co-agent's default target from its pairing rows — the first `IsDefault` row in\n * `Sequence` order (server enforces at most one, but the helper stays deterministic\n * regardless). `null` when there are no rows or none is marked default.\n */\nexport function DefaultPairedTargetId(pairings: readonly RealtimePairedAgentRow[]): string | null {\n for (const pairing of SortPairings(pairings ?? [])) {\n if (pairing.IsDefault) {\n return pairing.TargetAgentID;\n }\n }\n return null;\n}\n\n/**\n * Whether the pairing rows permit `targetAgentId` as a target. ZERO rows means the\n * co-agent is universal (always allowed); otherwise the target must appear among the\n * rows. Missing/empty target id is never allowed against a constrained co-agent.\n */\nexport function PairingsAllowTarget(pairings: readonly RealtimePairedAgentRow[], targetAgentId: string | null | undefined): boolean {\n if (!pairings || pairings.length === 0) {\n return true;\n }\n if (!targetAgentId) {\n return false;\n }\n return pairings.some(p => UUIDsEqual(p.TargetAgentID, targetAgentId));\n}\n\n/**\n * Filters an agent list down to ACTIVE Realtime-type **co-agent** candidates. The input\n * is expected to already be the run-permission-filtered active-agent set the host uses\n * for routing/@mentions, so this only applies the agent-TYPE cut (trim +\n * case-insensitive match on the denormalized `Type` name).\n */\nexport function FilterRealtimeCoAgents<T extends { Type: string | null }>(agents: readonly T[]): T[] {\n return agents.filter(a => (a.Type ?? '').trim().toLowerCase() === 'realtime');\n}\n\n/**\n * A selectable realtime model option in the voice picker (narrow read-only projection of\n * `MJ: AI Models` — only the fields the dropdown renders).\n */\nexport interface RealtimeModelOption {\n /** The `MJ: AI Models` row id. */\n ID: string;\n /** The model's display name. */\n Name: string;\n}\n\n/**\n * The minimal `MJ: AI Models` row shape {@link BuildRealtimeModelOptions} consumes —\n * structurally satisfied by `AIEngineBase`'s cached `Models` entities.\n */\nexport interface VoiceModelCandidate {\n /** The `MJ: AI Models` row id. */\n ID: string;\n /** The model's display name. */\n Name: string;\n /** Denormalized model-type name (e.g. 'LLM', 'Realtime'). */\n AIModelType: string | null;\n /** Whether the model is active and selectable. */\n IsActive: boolean;\n}\n\n/**\n * Builds the \"Voice model\" dropdown options from a cached model list: ACTIVE models whose\n * type is `Realtime` (trim + case-insensitive — SQL-collation parity with the previous\n * `IsActive = 1 AND AIModelType = 'Realtime'` filter), projected to {@link RealtimeModelOption}\n * and sorted by Name. Pure; never mutates the input.\n */\nexport function BuildRealtimeModelOptions(models: ReadonlyArray<VoiceModelCandidate>): RealtimeModelOption[] {\n return models\n .filter(m => m.IsActive && (m.AIModelType ?? '').trim().toLowerCase() === 'realtime')\n .map<RealtimeModelOption>(m => ({ ID: m.ID, Name: m.Name }))\n .sort((a, b) => (a.Name ?? '').localeCompare(b.Name ?? ''));\n}\n\n/**\n * Builds the `configOverridesJson` payload for the `StartRealtimeClientSession` mint from\n * what the (authorization-gated) pickers chose — an explicit realtime model preference and/or a\n * voice. The envelope (`{\"realtime\":{\"modelPreference\":\"<id>\",\"voice\":{\"default\":{\"voice\":\"<v>\"}}}}`)\n * is the pinned server contract, merged into the effective config server-side (highest precedence).\n *\n * The voice is filed PROVIDER-AGNOSTICALLY: the browser has a model id, never a vendor `DriverClass`\n * (`GetRealtimeModelVoices` does not return one), so naming a provider here would be a guess that\n * silently voided the setting for every non-OpenAI vendor. The server files it onto whichever driver\n * it resolves. Kept in lockstep with the server-side `BuildRealtimeOverridesJson` in\n * `@memberjunction/ai-agents` by `realtime-convergence-drift.test.ts`. See issue #3530.\n *\n * @param preferredModelId The explicit `MJ: AI Models` id, or null/empty for none.\n * @param preferredVoice The provider-native voice id (e.g. `echo`), or null/empty for none.\n * @returns The JSON string, or `null` when nothing was overridden (keeps the mint identical to default).\n */\nexport function BuildRealtimeConfigOverridesJson(\n preferredModelId: string | null | undefined,\n preferredVoice?: string | null | undefined,\n): string | null {\n const modelId = preferredModelId?.trim() ?? '';\n const voice = preferredVoice?.trim() ?? '';\n if (modelId.length === 0 && voice.length === 0) {\n return null;\n }\n const realtime: { modelPreference?: string; voice?: { default: { voice: string } } } = {};\n if (modelId.length > 0) {\n realtime.modelPreference = modelId;\n }\n if (voice.length > 0) {\n realtime.voice = { default: { voice } };\n }\n return JSON.stringify({ realtime });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"realtime-pairing.js","sourceRoot":"","sources":["../../../src/lib/services/realtime-pairing.ts"],"names":[],"mappings":"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","sourcesContent":["import { IMetadataProvider } from '@memberjunction/core';\nimport { NormalizeUUID, UUIDsEqual } from '@memberjunction/global';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\n\n/**\n * The narrow read-only projection of an `MJ: AI Agent Co Agents` row the voice UI consumes\n * (loaded via {@link LoadCoAgentPairings} from {@link AIEngineBase}'s cached `AgentCoAgents`;\n * only **Active** rows of relationship **Type `'CoAgent'`** with a specific agent target —\n * type-level rows express the resolution-chain default, not a target restriction, and\n * reserved relationship types are ignored until their features ship). Pairing semantics: a\n * co-agent with ZERO such rows is **universal** (it can front any target agent — the\n * zero-config default); a co-agent WITH rows may front exactly the listed targets, with the\n * `IsDefault` row as its preselected target.\n */\nexport interface RealtimePairedAgentRow {\n /** The pairing row id. */\n ID: string;\n /** The Realtime-type co-agent the pairing belongs to. */\n CoAgentID: string;\n /** The target agent this co-agent may front. */\n TargetAgentID: string;\n /** Denormalized target-agent display name (from the base view). */\n TargetAgent: string | null;\n /** When true, this target is the co-agent's default (at most one per co-agent). */\n IsDefault: boolean;\n /** Display/list ordering of the pairing rows. */\n Sequence: number | null;\n}\n\n/** Minimal agent shape the pure pairing/co-agent helpers operate on. */\nexport interface RealtimeAgentLike {\n ID: string;\n}\n\n/**\n * Loads the pairing rows for one co-agent from {@link AIEngineBase}'s cached\n * `MJ: AI Agent Co Agents` metadata (provider-scoped engine instance, lazy `Config`),\n * in `Sequence` order. The engine's BaseEntity-event reactivity keeps the cache fresh —\n * no per-call RunView round-trip.\n *\n * Tolerant by design: any failure degrades to an EMPTY list — i.e. \"no pairing\n * constraint\". The client gate is pure disclosure; the server independently validates\n * the co-agent/target pairing at session mint.\n */\nexport async function LoadCoAgentPairings(provider: IMetadataProvider, coAgentId: string): Promise<RealtimePairedAgentRow[]> {\n const id = coAgentId?.trim() ?? '';\n if (id.length === 0) {\n return [];\n }\n try {\n const engine = AIEngineBase.GetProviderInstance<AIEngineBase>(provider, AIEngineBase) as AIEngineBase;\n await engine.Config(false, undefined, provider);\n const rows = (engine.AgentCoAgents ?? [])\n .filter(p =>\n p.Type === 'CoAgent' &&\n p.Status === 'Active' &&\n p.TargetAgentID != null &&\n UUIDsEqual(p.CoAgentID, id))\n .map<RealtimePairedAgentRow>(p => ({\n ID: p.ID,\n CoAgentID: p.CoAgentID,\n TargetAgentID: p.TargetAgentID!,\n TargetAgent: p.TargetAgent ?? null,\n IsDefault: p.IsDefault,\n Sequence: p.Sequence ?? null\n }));\n return SortPairings(rows);\n } catch (error) {\n console.warn('[RealtimePairing] Co-agent pairing lookup unavailable — treating co-agent as universal:', error);\n return [];\n }\n}\n\n/**\n * Returns the pairing rows in canonical display order: `Sequence` ascending (null/missing\n * sequences last), ties broken by target-agent name. Pure; never mutates the input.\n */\nexport function SortPairings(pairings: readonly RealtimePairedAgentRow[]): RealtimePairedAgentRow[] {\n return [...pairings].sort((a, b) => {\n const aSeq = a.Sequence ?? Number.MAX_SAFE_INTEGER;\n const bSeq = b.Sequence ?? Number.MAX_SAFE_INTEGER;\n if (aSeq !== bSeq) {\n return aSeq - bSeq;\n }\n return (a.TargetAgent ?? '').localeCompare(b.TargetAgent ?? '');\n });\n}\n\n/**\n * Applies the pairing constraint to a candidate target-agent list:\n * - ZERO pairing rows → the co-agent is universal; the candidates are returned unchanged\n * (today's flow untouched).\n * - One or more rows → only candidates that appear among the pairing targets are\n * returned, ordered by the pairings' `Sequence`. Paired targets the user can't see /\n * run (absent from `agents`) are silently dropped.\n *\n * Pure and UUID-casing safe (`NormalizeUUID` keys).\n */\nexport function ConstrainTargetsToPairings<T extends RealtimeAgentLike>(\n agents: readonly T[],\n pairings: readonly RealtimePairedAgentRow[]\n): T[] {\n if (!pairings || pairings.length === 0) {\n return [...agents];\n }\n const agentsById = new Map<string, T>();\n for (const agent of agents) {\n agentsById.set(NormalizeUUID(agent.ID), agent);\n }\n const constrained: T[] = [];\n const seen = new Set<string>();\n for (const pairing of SortPairings(pairings)) {\n const key = NormalizeUUID(pairing.TargetAgentID);\n const agent = agentsById.get(key);\n if (agent && !seen.has(key)) {\n seen.add(key);\n constrained.push(agent);\n }\n }\n return constrained;\n}\n\n/**\n * The co-agent's default target from its pairing rows — the first `IsDefault` row in\n * `Sequence` order (server enforces at most one, but the helper stays deterministic\n * regardless). `null` when there are no rows or none is marked default.\n */\nexport function DefaultPairedTargetId(pairings: readonly RealtimePairedAgentRow[]): string | null {\n for (const pairing of SortPairings(pairings ?? [])) {\n if (pairing.IsDefault) {\n return pairing.TargetAgentID;\n }\n }\n return null;\n}\n\n/**\n * Whether the pairing rows permit `targetAgentId` as a target. ZERO rows means the\n * co-agent is universal (always allowed); otherwise the target must appear among the\n * rows. Missing/empty target id is never allowed against a constrained co-agent.\n */\nexport function PairingsAllowTarget(pairings: readonly RealtimePairedAgentRow[], targetAgentId: string | null | undefined): boolean {\n if (!pairings || pairings.length === 0) {\n return true;\n }\n if (!targetAgentId) {\n return false;\n }\n return pairings.some(p => UUIDsEqual(p.TargetAgentID, targetAgentId));\n}\n\n/**\n * Filters an agent list down to ACTIVE Realtime-type **co-agent** candidates. The input\n * is expected to already be the run-permission-filtered active-agent set the host uses\n * for routing/@mentions, so this only applies the agent-TYPE cut (trim +\n * case-insensitive match on the denormalized `Type` name).\n */\nexport function FilterRealtimeCoAgents<T extends { Type: string | null }>(agents: readonly T[]): T[] {\n return agents.filter(a => (a.Type ?? '').trim().toLowerCase() === 'realtime');\n}\n\n/**\n * A selectable realtime model option in the voice picker (narrow read-only projection of\n * `MJ: AI Models` — only the fields the dropdown renders).\n */\nexport interface RealtimeModelOption {\n /** The `MJ: AI Models` row id. */\n ID: string;\n /** The model's display name. */\n Name: string;\n}\n\n/**\n * The minimal `MJ: AI Models` row shape {@link BuildRealtimeModelOptions} consumes —\n * structurally satisfied by `AIEngineBase`'s cached `Models` entities.\n */\nexport interface VoiceModelCandidate {\n /** The `MJ: AI Models` row id. */\n ID: string;\n /** The model's display name. */\n Name: string;\n /** Denormalized model-type name (e.g. 'LLM', 'Realtime'). */\n AIModelType: string | null;\n /** Whether the model is active and selectable. */\n IsActive: boolean;\n}\n\n/**\n * Builds the \"Voice model\" dropdown options from a cached model list: ACTIVE models whose\n * type is `Realtime` (trim + case-insensitive — SQL-collation parity with the previous\n * `IsActive = 1 AND AIModelType = 'Realtime'` filter), projected to {@link RealtimeModelOption}\n * and sorted by Name. Pure; never mutates the input.\n */\nexport function BuildRealtimeModelOptions(models: ReadonlyArray<VoiceModelCandidate>): RealtimeModelOption[] {\n return models\n .filter(m => m.IsActive && (m.AIModelType ?? '').trim().toLowerCase() === 'realtime')\n .map<RealtimeModelOption>(m => ({ ID: m.ID, Name: m.Name }))\n .sort((a, b) => (a.Name ?? '').localeCompare(b.Name ?? ''));\n}\n\n/**\n * Builds the `configOverridesJson` payload for the `StartRealtimeClientSession` mint from\n * what the (authorization-gated) pickers chose — an explicit realtime model preference, a\n * voice, and the avatar that comes with that voice. The envelope\n * (`{\"realtime\":{\"modelPreference\":\"<id>\",\"voice\":{\"default\":{\"voice\":\"<v>\"}},\"video\":{\"avatarId\":\"<a>\"}}}`)\n * is the pinned server contract, merged into the effective config server-side (highest precedence).\n *\n * The voice is filed PROVIDER-AGNOSTICALLY: the browser has a model id, never a vendor `DriverClass`\n * (`GetRealtimeModelVoices` does not return one), so naming a provider here would be a guess that\n * silently voided the setting for every non-OpenAI vendor. The server files it onto whichever driver\n * it resolves. A picked avatar leaves video to the agent: the server shows it only when the voiced\n * agent's own video setting is on (else the call says why, reason `video-off`), and only when it names\n * a Video binding on the vendor it resolves. Kept in lockstep with the server-side\n * `BuildRealtimeOverridesJson` in `@memberjunction/ai-agents` by `realtime-convergence-drift.test.ts`.\n * See issue #3530.\n *\n * @param preferredModelId The explicit `MJ: AI Models` id, or null/empty for none.\n * @param preferredVoice The provider-native voice id (e.g. `echo`), or null/empty for none.\n * @param preferredAvatarId The avatar that comes with the picked voice (`RealtimeVoiceOption.AvatarID`), or null/empty for none.\n * @returns The JSON string, or `null` when nothing was overridden (keeps the mint identical to default).\n */\nexport function BuildRealtimeConfigOverridesJson(\n preferredModelId: string | null | undefined,\n preferredVoice?: string | null | undefined,\n preferredAvatarId?: string | null | undefined,\n): string | null {\n const modelId = preferredModelId?.trim() ?? '';\n const voice = preferredVoice?.trim() ?? '';\n const avatarId = preferredAvatarId?.trim() ?? '';\n if (modelId.length === 0 && voice.length === 0 && avatarId.length === 0) {\n return null;\n }\n const realtime: { modelPreference?: string; voice?: { default: { voice: string } }; video?: { avatarId: string } } = {};\n if (modelId.length > 0) {\n realtime.modelPreference = modelId;\n }\n if (voice.length > 0) {\n realtime.voice = { default: { voice } };\n }\n if (avatarId.length > 0) {\n realtime.video = { avatarId };\n }\n return JSON.stringify({ realtime });\n}\n"]}
|
|
@@ -6,9 +6,10 @@ import * as i0 from "@angular/core";
|
|
|
6
6
|
* All orchestration — mint, driver resolution, transcripts, tool relay, delegation narration,
|
|
7
7
|
* channel lifecycle, usage relay, teardown — lives in
|
|
8
8
|
* {@link RealtimeSessionRuntime} (`@memberjunction/realtime-runtime`), which is framework-agnostic
|
|
9
|
-
* and shared with every other host, including the React Native app. This class supplies the
|
|
9
|
+
* and shared with every other host, including the React Native app. This class supplies the three
|
|
10
10
|
* things that are genuinely Angular/browser: the `@Injectable` shell that lets components inject
|
|
11
|
-
* it,
|
|
11
|
+
* it, the browser's media host, and the `beforeunload` guard that warns before the tab closes
|
|
12
|
+
* mid-recording-upload (#5195).
|
|
12
13
|
*
|
|
13
14
|
* Consumers are unaffected by that split — the injectable token, class name, and public surface
|
|
14
15
|
* are unchanged. Types that used to be declared in this file (`RealtimeCaption`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-session.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/realtime-session.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;;
|
|
1
|
+
{"version":3,"file":"realtime-session.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/realtime-session.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;;AA6B1E;;;;;;;;;;;;;;;;GAgBG;AACH,qBACa,sBAAuB,SAAQ,sBAAsB;;yCAArD,sBAAsB;6CAAtB,sBAAsB;CAOlC"}
|
|
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
|
|
2
2
|
import { RealtimeSessionRuntime } from '@memberjunction/realtime-runtime';
|
|
3
3
|
import { LoadAssemblyAIRealtimeClient, LoadElevenLabsRealtimeClient, LoadGeminiRealtimeClient, LoadHuggingFaceRealtimeClient, LoadOpenAILiveClient, LoadOpenAIRealtimeClient, LoadxAIRealtimeClient, } from '@memberjunction/ai-realtime-client';
|
|
4
4
|
import { BrowserRealtimeMediaHost } from './browser-realtime-media-host';
|
|
5
|
+
import { BrowserRecordingUnloadGuard } from './browser-recording-unload-guard';
|
|
5
6
|
import * as i0 from "@angular/core";
|
|
6
7
|
// Tree-shaking prevention: client drivers are resolved dynamically through the ClassFactory (by
|
|
7
8
|
// the server-reported Provider key), so these static calls are what keep their @RegisterClass side
|
|
@@ -24,9 +25,10 @@ LoadHuggingFaceRealtimeClient();
|
|
|
24
25
|
* All orchestration — mint, driver resolution, transcripts, tool relay, delegation narration,
|
|
25
26
|
* channel lifecycle, usage relay, teardown — lives in
|
|
26
27
|
* {@link RealtimeSessionRuntime} (`@memberjunction/realtime-runtime`), which is framework-agnostic
|
|
27
|
-
* and shared with every other host, including the React Native app. This class supplies the
|
|
28
|
+
* and shared with every other host, including the React Native app. This class supplies the three
|
|
28
29
|
* things that are genuinely Angular/browser: the `@Injectable` shell that lets components inject
|
|
29
|
-
* it,
|
|
30
|
+
* it, the browser's media host, and the `beforeunload` guard that warns before the tab closes
|
|
31
|
+
* mid-recording-upload (#5195).
|
|
30
32
|
*
|
|
31
33
|
* Consumers are unaffected by that split — the injectable token, class name, and public surface
|
|
32
34
|
* are unchanged. Types that used to be declared in this file (`RealtimeCaption`,
|
|
@@ -37,6 +39,9 @@ LoadHuggingFaceRealtimeClient();
|
|
|
37
39
|
export class RealtimeSessionService extends RealtimeSessionRuntime {
|
|
38
40
|
constructor() {
|
|
39
41
|
super(new BrowserRealtimeMediaHost());
|
|
42
|
+
const unloadGuard = new BrowserRecordingUnloadGuard(typeof window === 'undefined' ? null : window);
|
|
43
|
+
// Root singleton: the subscription lives exactly as long as the app, so it is never torn down.
|
|
44
|
+
this.SavingRecording$.subscribe((saving) => unloadGuard.SetArmed(saving));
|
|
40
45
|
}
|
|
41
46
|
static ɵfac = function RealtimeSessionService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RealtimeSessionService)(); };
|
|
42
47
|
static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: RealtimeSessionService, factory: RealtimeSessionService.ɵfac, providedIn: 'root' });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-session.service.js","sourceRoot":"","sources":["../../../src/lib/services/realtime-session.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EACH,4BAA4B,EAC5B,4BAA4B,EAC5B,wBAAwB,EACxB,6BAA6B,EAC7B,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,GACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;;
|
|
1
|
+
{"version":3,"file":"realtime-session.service.js","sourceRoot":"","sources":["../../../src/lib/services/realtime-session.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EACH,4BAA4B,EAC5B,4BAA4B,EAC5B,wBAAwB,EACxB,6BAA6B,EAC7B,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,GACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;;AAE/E,gGAAgG;AAChG,mGAAmG;AACnG,gDAAgD;AAChD,EAAE;AACF,+FAA+F;AAC/F,0FAA0F;AAC1F,+FAA+F;AAC/F,kBAAkB;AAClB,wBAAwB,EAAE,CAAC;AAC3B,oBAAoB,EAAE,CAAC;AACvB,wBAAwB,EAAE,CAAC;AAC3B,4BAA4B,EAAE,CAAC;AAC/B,4BAA4B,EAAE,CAAC;AAC/B,qBAAqB,EAAE,CAAC;AACxB,6BAA6B,EAAE,CAAC;AAEhC;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,OAAO,sBAAuB,SAAQ,sBAAsB;IAC9D;QACI,KAAK,CAAC,IAAI,wBAAwB,EAAE,CAAC,CAAC;QACtC,MAAM,WAAW,GAAG,IAAI,2BAA2B,CAAC,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACnG,+FAA+F;QAC/F,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9E,CAAC;gHANQ,sBAAsB;gEAAtB,sBAAsB,WAAtB,sBAAsB,mBADT,MAAM;;iFACnB,sBAAsB;cADlC,UAAU;eAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["import { Injectable } from '@angular/core';\nimport { RealtimeSessionRuntime } from '@memberjunction/realtime-runtime';\nimport {\n LoadAssemblyAIRealtimeClient,\n LoadElevenLabsRealtimeClient,\n LoadGeminiRealtimeClient,\n LoadHuggingFaceRealtimeClient,\n LoadOpenAILiveClient,\n LoadOpenAIRealtimeClient,\n LoadxAIRealtimeClient,\n} from '@memberjunction/ai-realtime-client';\nimport { BrowserRealtimeMediaHost } from './browser-realtime-media-host';\nimport { BrowserRecordingUnloadGuard } from './browser-recording-unload-guard';\n\n// Tree-shaking prevention: client drivers are resolved dynamically through the ClassFactory (by\n// the server-reported Provider key), so these static calls are what keep their @RegisterClass side\n// effects from being eliminated by the bundler.\n//\n// NOTE: the interactive-channel plugins (resolved dynamically from the `MJ: AI Agent Channels`\n// registry by ClientPluginClass key) get the same treatment, but their Load calls live in\n// `conversations.module.ts` — plugins carry Angular surface COMPONENTS, and this service stays\n// component-free.\nLoadOpenAIRealtimeClient();\nLoadOpenAILiveClient();\nLoadGeminiRealtimeClient();\nLoadElevenLabsRealtimeClient();\nLoadAssemblyAIRealtimeClient();\nLoadxAIRealtimeClient();\nLoadHuggingFaceRealtimeClient();\n\n/**\n * Angular host for MemberJunction's realtime co-agent sessions.\n *\n * All orchestration — mint, driver resolution, transcripts, tool relay, delegation narration,\n * channel lifecycle, usage relay, teardown — lives in\n * {@link RealtimeSessionRuntime} (`@memberjunction/realtime-runtime`), which is framework-agnostic\n * and shared with every other host, including the React Native app. This class supplies the three\n * things that are genuinely Angular/browser: the `@Injectable` shell that lets components inject\n * it, the browser's media host, and the `beforeunload` guard that warns before the tab closes\n * mid-recording-upload (#5195).\n *\n * Consumers are unaffected by that split — the injectable token, class name, and public surface\n * are unchanged. Types that used to be declared in this file (`RealtimeCaption`,\n * `RealtimeConnectionState`, `RealtimeSessionRunOptions`, …) now come from\n * `@memberjunction/realtime-runtime`; import them from there directly, since MJ does not\n * re-export across package boundaries.\n */\n@Injectable({ providedIn: 'root' })\nexport class RealtimeSessionService extends RealtimeSessionRuntime {\n constructor() {\n super(new BrowserRealtimeMediaHost());\n const unloadGuard = new BrowserRecordingUnloadGuard(typeof window === 'undefined' ? null : window);\n // Root singleton: the subscription lives exactly as long as the app, so it is never torn down.\n this.SavingRecording$.subscribe((saving) => unloadGuard.SetArmed(saving));\n }\n}\n"]}
|
|
@@ -26,6 +26,10 @@ export interface SearchResult {
|
|
|
26
26
|
collectionName?: string;
|
|
27
27
|
createdAt: Date;
|
|
28
28
|
relevanceScore: number;
|
|
29
|
+
/** A message hit's branch; null is the trunk. */
|
|
30
|
+
branchId?: string | null;
|
|
31
|
+
/** A message hit's position in its conversation. */
|
|
32
|
+
sequence?: number;
|
|
29
33
|
}
|
|
30
34
|
/**
|
|
31
35
|
* Search results grouped by type
|
|
@@ -107,7 +111,8 @@ export declare class SearchService {
|
|
|
107
111
|
*/
|
|
108
112
|
private searchConversations;
|
|
109
113
|
/**
|
|
110
|
-
* Search message content inside the conversations the conversation list shows the user
|
|
114
|
+
* Search message content inside the conversations the conversation list shows the user,
|
|
115
|
+
* on every branch of each conversation
|
|
111
116
|
*/
|
|
112
117
|
private searchMessages;
|
|
113
118
|
/**
|
|
@@ -135,7 +140,7 @@ export declare class SearchService {
|
|
|
135
140
|
*/
|
|
136
141
|
private mapConversationToSearchResult;
|
|
137
142
|
/**
|
|
138
|
-
* Map message
|
|
143
|
+
* Map a message row to a search result, with the branch and sequence that locate it
|
|
139
144
|
*/
|
|
140
145
|
private mapMessageToSearchResult;
|
|
141
146
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/search.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,UAAU,EAAe,MAAM,MAAM,CAAC;AAYhE,OAAO,EAAW,QAAQ,EAAY,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEtF,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;;AAE1E;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,CAAC;AAE/F;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,OAAO,CAAC;AAExG;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"search.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/search.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,10 @@ import { RunView, Metadata } from '@memberjunction/core';
|
|
|
6
6
|
import { EscapeSQLString } from '@memberjunction/global';
|
|
7
7
|
import * as i0 from "@angular/core";
|
|
8
8
|
import * as i1 from "./artifact-permission.service";
|
|
9
|
+
/** Columns of {@link MessageSearchRow}, as the message query's `Fields`. */
|
|
10
|
+
const MESSAGE_SEARCH_FIELDS = [
|
|
11
|
+
'ID', 'ConversationID', 'Conversation', 'Message', 'BranchID', 'Sequence', '__mj_CreatedAt'
|
|
12
|
+
];
|
|
9
13
|
/**
|
|
10
14
|
* Service for searching across conversations, messages, and artifacts
|
|
11
15
|
* Provides debounced search with result ranking and filtering
|
|
@@ -179,7 +183,8 @@ export class SearchService {
|
|
|
179
183
|
return result.Results.map(conv => this.mapConversationToSearchResult(conv, query));
|
|
180
184
|
}
|
|
181
185
|
/**
|
|
182
|
-
* Search message content inside the conversations the conversation list shows the user
|
|
186
|
+
* Search message content inside the conversations the conversation list shows the user,
|
|
187
|
+
* on every branch of each conversation
|
|
183
188
|
*/
|
|
184
189
|
async searchMessages(query, environmentId, currentUser, dateRange) {
|
|
185
190
|
const conversationIds = await this.getVisibleConversationIds(environmentId, currentUser);
|
|
@@ -199,12 +204,14 @@ export class SearchService {
|
|
|
199
204
|
if (dateRange.end) {
|
|
200
205
|
filter += ` AND __mj_CreatedAt <= '${dateRange.end.toISOString()}'`;
|
|
201
206
|
}
|
|
207
|
+
// conversation-scope: search spans every path on purpose; opening a hit switches to its branch
|
|
202
208
|
const result = await rv.RunView({
|
|
203
209
|
EntityName: 'MJ: Conversation Details',
|
|
204
210
|
ExtraFilter: filter,
|
|
205
211
|
OrderBy: '__mj_CreatedAt DESC',
|
|
206
212
|
MaxRows: 100,
|
|
207
|
-
|
|
213
|
+
Fields: [...MESSAGE_SEARCH_FIELDS],
|
|
214
|
+
ResultType: 'simple'
|
|
208
215
|
}, currentUser);
|
|
209
216
|
if (!result.Success || !result.Results) {
|
|
210
217
|
console.error('Failed to search messages:', result.ErrorMessage);
|
|
@@ -386,7 +393,7 @@ export class SearchService {
|
|
|
386
393
|
};
|
|
387
394
|
}
|
|
388
395
|
/**
|
|
389
|
-
* Map message
|
|
396
|
+
* Map a message row to a search result, with the branch and sequence that locate it
|
|
390
397
|
*/
|
|
391
398
|
mapMessageToSearchResult(message, query) {
|
|
392
399
|
const messageText = message.Message || '';
|
|
@@ -400,7 +407,9 @@ export class SearchService {
|
|
|
400
407
|
conversationId: message.ConversationID,
|
|
401
408
|
conversationName: message.Conversation || undefined,
|
|
402
409
|
createdAt: message.__mj_CreatedAt,
|
|
403
|
-
relevanceScore: 5
|
|
410
|
+
relevanceScore: 5,
|
|
411
|
+
branchId: message.BranchID ?? null,
|
|
412
|
+
sequence: message.Sequence
|
|
404
413
|
};
|
|
405
414
|
}
|
|
406
415
|
/**
|