@absolutejs/voice 0.0.22-beta.51 → 0.0.22-beta.510
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 +4556 -603
- package/dist/agent.d.ts +79 -5
- package/dist/agentSquadContract.d.ts +98 -0
- package/dist/agentState.d.ts +12 -0
- package/dist/agentTools.d.ts +133 -0
- package/dist/aiVoiceModel.d.ts +15 -0
- package/dist/amdDetector.d.ts +25 -0
- package/dist/angular/index.d.ts +36 -5
- package/dist/angular/index.js +4004 -338
- package/dist/angular/voice-agent-squad-status.service.d.ts +12 -0
- package/dist/angular/voice-call-debugger.service.d.ts +12 -0
- package/dist/angular/voice-call-player.service.d.ts +19 -0
- package/dist/angular/voice-campaign-dialer-proof.service.d.ts +14 -0
- package/dist/angular/voice-controller.service.d.ts +3 -1
- package/dist/angular/voice-cost-dashboard.service.d.ts +15 -0
- package/dist/angular/voice-delivery-runtime.component.d.ts +17 -0
- package/dist/angular/voice-delivery-runtime.service.d.ts +16 -0
- package/dist/angular/voice-live-agent-console.service.d.ts +16 -0
- package/dist/angular/voice-live-call-viewer.service.d.ts +16 -0
- package/dist/angular/voice-live-ops.service.d.ts +11 -0
- package/dist/angular/voice-ops-action-center.service.d.ts +13 -0
- package/dist/angular/voice-ops-status.component.d.ts +15 -0
- package/dist/angular/voice-ops-status.service.d.ts +12 -0
- package/dist/angular/voice-platform-coverage.service.d.ts +12 -0
- package/dist/angular/voice-profile-comparison.service.d.ts +12 -0
- package/dist/angular/voice-proof-trends.service.d.ts +12 -0
- package/dist/angular/voice-provider-capabilities.service.d.ts +12 -0
- package/dist/angular/voice-provider-contracts.service.d.ts +12 -0
- package/dist/angular/voice-provider-status.service.d.ts +2 -2
- package/dist/angular/voice-readiness-failures.service.d.ts +13 -0
- package/dist/angular/voice-reconnect-profile-evidence.service.d.ts +12 -0
- package/dist/angular/voice-replay-timeline.service.d.ts +13 -0
- package/dist/angular/voice-routing-status.service.d.ts +11 -0
- package/dist/angular/voice-session-observability.service.d.ts +12 -0
- package/dist/angular/voice-session-snapshot.service.d.ts +13 -0
- package/dist/angular/voice-stream.service.d.ts +4 -1
- package/dist/angular/voice-trace-timeline.service.d.ts +12 -0
- package/dist/angular/voice-turn-latency.service.d.ts +13 -0
- package/dist/angular/voice-turn-quality.service.d.ts +12 -0
- package/dist/angular/voice-widget.service.d.ts +18 -0
- package/dist/angular/voice-workflow-status.service.d.ts +2 -2
- package/dist/assistant.d.ts +12 -13
- package/dist/assistantExperiment.d.ts +41 -0
- package/dist/assistantHealth.d.ts +6 -6
- package/dist/assistantMemory.d.ts +3 -3
- package/dist/assistantMode.d.ts +22 -0
- package/dist/audioConditioning.d.ts +1 -1
- package/dist/audit.d.ts +131 -0
- package/dist/auditDeliveryRoutes.d.ts +85 -0
- package/dist/auditExport.d.ts +34 -0
- package/dist/auditRoutes.d.ts +66 -0
- package/dist/auditSinks.d.ts +151 -0
- package/dist/backchannel.d.ts +18 -0
- package/dist/bargeInRoutes.d.ts +56 -0
- package/dist/browserCallProfiles.d.ts +120 -0
- package/dist/browserMediaRoutes.d.ts +62 -0
- package/dist/callDebugger.d.ts +66 -0
- package/dist/callDisposition.d.ts +38 -0
- package/dist/callQuota.d.ts +54 -0
- package/dist/callerMemory.d.ts +37 -0
- package/dist/callingWindow.d.ts +26 -0
- package/dist/campaign.d.ts +794 -0
- package/dist/campaignControls.d.ts +37 -0
- package/dist/campaignDialers.d.ts +111 -0
- package/dist/campaignTemplate.d.ts +16 -0
- package/dist/client/actions.d.ts +95 -1
- package/dist/client/agentSquadStatus.d.ts +37 -0
- package/dist/client/agentSquadStatusWidget.d.ts +24 -0
- package/dist/client/audioPlayer.d.ts +2 -2
- package/dist/client/bargeInMonitor.d.ts +7 -0
- package/dist/client/browserMedia.d.ts +8 -0
- package/dist/client/browserNoiseSuppression.d.ts +42 -0
- package/dist/client/browserVoiceSupport.d.ts +22 -0
- package/dist/client/callDebugger.d.ts +19 -0
- package/dist/client/callDebuggerWidget.d.ts +30 -0
- package/dist/client/callPlayer.d.ts +41 -0
- package/dist/client/campaignDialerProof.d.ts +23 -0
- package/dist/client/connection.d.ts +4 -3
- package/dist/client/controller.d.ts +1 -1
- package/dist/client/conversationAnalytics.d.ts +29 -0
- package/dist/client/costDashboard.d.ts +27 -0
- package/dist/client/createVoiceStream.d.ts +1 -1
- package/dist/client/deliveryRuntime.d.ts +34 -0
- package/dist/client/deliveryRuntimeWidget.d.ts +37 -0
- package/dist/client/duplex.d.ts +2 -2
- package/dist/client/htmx.d.ts +1 -1
- package/dist/client/htmxAttributes.d.ts +28 -0
- package/dist/client/htmxBootstrap.js +967 -14
- package/dist/client/htmxDashboardRenderers.d.ts +72 -0
- package/dist/client/index.d.ts +104 -13
- package/dist/client/index.js +10142 -26
- package/dist/client/liveAgentConsole.d.ts +28 -0
- package/dist/client/liveCallViewer.d.ts +42 -0
- package/dist/client/liveOps.d.ts +22 -0
- package/dist/client/liveOpsWidget.d.ts +23 -0
- package/dist/client/liveTurnLatency.d.ts +41 -0
- package/dist/client/microphone.d.ts +4 -4
- package/dist/client/opsActionCenter.d.ts +54 -0
- package/dist/client/opsActionCenterWidget.d.ts +29 -0
- package/dist/client/opsActionHistory.d.ts +19 -0
- package/dist/client/opsActionHistoryWidget.d.ts +11 -0
- package/dist/client/opsStatus.d.ts +19 -0
- package/dist/client/opsStatusWidget.d.ts +40 -0
- package/dist/client/platformCoverage.d.ts +19 -0
- package/dist/client/platformCoverageWidget.d.ts +37 -0
- package/dist/client/profileComparison.d.ts +19 -0
- package/dist/client/profileComparisonWidget.d.ts +41 -0
- package/dist/client/profileSwitchRecommendation.d.ts +19 -0
- package/dist/client/profileSwitchRecommendationWidget.d.ts +12 -0
- package/dist/client/proofTrends.d.ts +19 -0
- package/dist/client/proofTrendsWidget.d.ts +37 -0
- package/dist/client/providerCapabilities.d.ts +19 -0
- package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
- package/dist/client/providerContracts.d.ts +19 -0
- package/dist/client/providerContractsWidget.d.ts +37 -0
- package/dist/client/providerSimulationControls.d.ts +33 -0
- package/dist/client/providerSimulationControlsWidget.d.ts +20 -0
- package/dist/client/providerStatus.d.ts +1 -1
- package/dist/client/providerStatusWidget.d.ts +32 -0
- package/dist/client/readinessFailures.d.ts +19 -0
- package/dist/client/readinessFailuresWidget.d.ts +42 -0
- package/dist/client/reconnectProfileEvidence.d.ts +19 -0
- package/dist/client/reconnectProfileEvidenceWidget.d.ts +39 -0
- package/dist/client/replayTimeline.d.ts +26 -0
- package/dist/client/routingStatus.d.ts +19 -0
- package/dist/client/routingStatusWidget.d.ts +32 -0
- package/dist/client/sessionObservability.d.ts +19 -0
- package/dist/client/sessionObservabilityWidget.d.ts +31 -0
- package/dist/client/sessionSnapshot.d.ts +21 -0
- package/dist/client/sessionSnapshotWidget.d.ts +33 -0
- package/dist/client/store.d.ts +1 -1
- package/dist/client/traceTimeline.d.ts +19 -0
- package/dist/client/traceTimelineWidget.d.ts +36 -0
- package/dist/client/turnLatency.d.ts +22 -0
- package/dist/client/turnLatencyWidget.d.ts +33 -0
- package/dist/client/turnQuality.d.ts +19 -0
- package/dist/client/turnQualityWidget.d.ts +32 -0
- package/dist/client/voiceWidgetView.d.ts +50 -0
- package/dist/client/workflowStatus.d.ts +1 -1
- package/dist/competitiveCoverage.d.ts +141 -0
- package/dist/correction.d.ts +2 -2
- package/dist/costAccounting.d.ts +76 -0
- package/dist/costPredictor.d.ts +74 -0
- package/dist/dataControl.d.ts +180 -0
- package/dist/defineVoiceAssistant.d.ts +68 -0
- package/dist/deliveryRuntime.d.ts +158 -0
- package/dist/deliverySinkRoutes.d.ts +117 -0
- package/dist/demoReadyRoutes.d.ts +98 -0
- package/dist/diagnosticsRoutes.d.ts +2 -2
- package/dist/dncRegistry.d.ts +38 -0
- package/dist/dtmfCollector.d.ts +37 -0
- package/dist/evalRoutes.d.ts +10 -4
- package/dist/fileStore.d.ts +21 -7
- package/dist/generated/htmxBootstrapBundle.d.ts +1 -0
- package/dist/guardrails.d.ts +128 -0
- package/dist/handoff.d.ts +6 -6
- package/dist/handoffHealth.d.ts +5 -5
- package/dist/holdAudio.d.ts +23 -0
- package/dist/htmx.d.ts +1 -1
- package/dist/htmxDashboardRoutes.d.ts +249 -0
- package/dist/iceServers.d.ts +34 -0
- package/dist/incidentBundle.d.ts +119 -0
- package/dist/incidentTimeline.d.ts +260 -0
- package/dist/index.d.ts +314 -74
- package/dist/index.js +43236 -7352
- package/dist/ivrPlan.d.ts +40 -0
- package/dist/latencySlo.d.ts +56 -0
- package/dist/liveLatency.d.ts +78 -0
- package/dist/liveOps.d.ts +190 -0
- package/dist/llmJudge.d.ts +45 -0
- package/dist/logger.d.ts +1 -1
- package/dist/mediaPipelineRoutes.d.ts +171 -0
- package/dist/mediaPipelineSurfaces.d.ts +48 -0
- package/dist/memoryStore.d.ts +1 -1
- package/dist/midCallSummary.d.ts +27 -0
- package/dist/modelAdapters.d.ts +59 -7
- package/dist/monitor.d.ts +148 -0
- package/dist/multilingualProof.d.ts +77 -0
- package/dist/oauth2TokenSource.d.ts +21 -0
- package/dist/observabilityExport.d.ts +501 -0
- package/dist/openaiTTS.d.ts +18 -0
- package/dist/operationalStatus.d.ts +87 -0
- package/dist/operationsRecord.d.ts +370 -0
- package/dist/ops.d.ts +12 -12
- package/dist/opsActionAuditRoutes.d.ts +99 -0
- package/dist/opsConsoleRoutes.d.ts +8 -5
- package/dist/opsPresets.d.ts +2 -2
- package/dist/opsRecovery.d.ts +137 -0
- package/dist/opsRuntime.d.ts +6 -6
- package/dist/opsSinks.d.ts +13 -13
- package/dist/opsStatus.d.ts +76 -0
- package/dist/opsStatusRoutes.d.ts +33 -0
- package/dist/opsWebhook.d.ts +6 -6
- package/dist/otelExporter.d.ts +83 -0
- package/dist/outcomeContract.d.ts +146 -0
- package/dist/outcomeRecipes.d.ts +4 -4
- package/dist/phoneAgent.d.ts +139 -0
- package/dist/phoneAgentProductionSmoke.d.ts +115 -0
- package/dist/phoneProvisioning.d.ts +29 -0
- package/dist/platformCoverage.d.ts +91 -0
- package/dist/plugin.d.ts +2 -2
- package/dist/postCallAnalysis.d.ts +98 -0
- package/dist/postCallSurvey.d.ts +41 -0
- package/dist/postgresStore.d.ts +17 -6
- package/dist/presets.d.ts +3 -3
- package/dist/productionReadiness.d.ts +756 -0
- package/dist/profileSwitchRecommendation.d.ts +350 -0
- package/dist/promptInjectionGuard.d.ts +30 -0
- package/dist/proofAssertions.d.ts +32 -0
- package/dist/proofPack.d.ts +211 -0
- package/dist/proofRunner.d.ts +79 -0
- package/dist/proofTrends.d.ts +966 -0
- package/dist/providerAdapters.d.ts +16 -5
- package/dist/providerCapabilities.d.ts +92 -0
- package/dist/providerDecisionTraces.d.ts +130 -0
- package/dist/providerHealth.d.ts +3 -3
- package/dist/providerOrchestration.d.ts +109 -0
- package/dist/providerRouterTraces.d.ts +35 -0
- package/dist/providerRoutingContract.d.ts +71 -0
- package/dist/providerSlo.d.ts +142 -0
- package/dist/providerStackRecommendations.d.ts +187 -0
- package/dist/qualityRoutes.d.ts +4 -4
- package/dist/queue.d.ts +23 -14
- package/dist/ragTool.d.ts +57 -0
- package/dist/react/VoiceAgentSquadStatus.d.ts +5 -0
- package/dist/react/VoiceCallDebuggerLaunch.d.ts +6 -0
- package/dist/react/VoiceCallPlayer.d.ts +11 -0
- package/dist/react/VoiceCostDashboard.d.ts +10 -0
- package/dist/react/VoiceDeliveryRuntime.d.ts +7 -0
- package/dist/react/VoiceLiveAgentConsole.d.ts +11 -0
- package/dist/react/VoiceLiveCallViewer.d.ts +9 -0
- package/dist/react/VoiceOpsActionCenter.d.ts +5 -0
- package/dist/react/VoiceOpsStatus.d.ts +6 -0
- package/dist/react/VoicePlatformCoverage.d.ts +6 -0
- package/dist/react/VoiceProfileComparison.d.ts +6 -0
- package/dist/react/VoiceProfileSwitchRecommendation.d.ts +6 -0
- package/dist/react/VoiceProofTrends.d.ts +6 -0
- package/dist/react/VoiceProviderCapabilities.d.ts +6 -0
- package/dist/react/VoiceProviderContracts.d.ts +6 -0
- package/dist/react/VoiceProviderSimulationControls.d.ts +5 -0
- package/dist/react/VoiceProviderStatus.d.ts +6 -0
- package/dist/react/VoiceReadinessFailures.d.ts +6 -0
- package/dist/react/VoiceReconnectProfileEvidence.d.ts +6 -0
- package/dist/react/VoiceReplayTimeline.d.ts +6 -0
- package/dist/react/VoiceRoutingStatus.d.ts +6 -0
- package/dist/react/VoiceSessionObservability.d.ts +6 -0
- package/dist/react/VoiceSessionSnapshot.d.ts +6 -0
- package/dist/react/VoiceTraceTimeline.d.ts +6 -0
- package/dist/react/VoiceTurnLatency.d.ts +6 -0
- package/dist/react/VoiceTurnQuality.d.ts +6 -0
- package/dist/react/VoiceWidget.d.ts +13 -0
- package/dist/react/index.d.ts +59 -5
- package/dist/react/index.js +12733 -168
- package/dist/react/useVoiceAgentSquadStatus.d.ts +8 -0
- package/dist/react/useVoiceCallDebugger.d.ts +8 -0
- package/dist/react/useVoiceCampaignDialerProof.d.ts +10 -0
- package/dist/react/useVoiceController.d.ts +4 -1
- package/dist/react/useVoiceDeliveryRuntime.d.ts +13 -0
- package/dist/react/useVoiceLiveOps.d.ts +9 -0
- package/dist/react/useVoiceOpsActionCenter.d.ts +11 -0
- package/dist/react/useVoiceOpsStatus.d.ts +8 -0
- package/dist/react/useVoicePlatformCoverage.d.ts +8 -0
- package/dist/react/useVoiceProfileComparison.d.ts +8 -0
- package/dist/react/useVoiceProfileSwitchRecommendation.d.ts +8 -0
- package/dist/react/useVoiceProofTrends.d.ts +8 -0
- package/dist/react/useVoiceProviderCapabilities.d.ts +8 -0
- package/dist/react/useVoiceProviderContracts.d.ts +8 -0
- package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -0
- package/dist/react/useVoiceProviderStatus.d.ts +1 -1
- package/dist/react/useVoiceReadinessFailures.d.ts +8 -0
- package/dist/react/useVoiceReconnectProfileEvidence.d.ts +8 -0
- package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
- package/dist/react/useVoiceSessionObservability.d.ts +8 -0
- package/dist/react/useVoiceSessionSnapshot.d.ts +9 -0
- package/dist/react/useVoiceStream.d.ts +4 -1
- package/dist/react/useVoiceTraceTimeline.d.ts +8 -0
- package/dist/react/useVoiceTurnLatency.d.ts +9 -0
- package/dist/react/useVoiceTurnQuality.d.ts +8 -0
- package/dist/react/useVoiceWorkflowStatus.d.ts +1 -1
- package/dist/readinessProfiles.d.ts +45 -0
- package/dist/realtimeChannel.d.ts +136 -0
- package/dist/realtimeProviderContracts.d.ts +133 -0
- package/dist/reconnectContract.d.ts +176 -0
- package/dist/recordingRedaction.d.ts +18 -0
- package/dist/recordingStore.d.ts +60 -0
- package/dist/redaction.d.ts +13 -0
- package/dist/resilienceRoutes.d.ts +45 -5
- package/dist/retention.d.ts +37 -0
- package/dist/retryPolicy.d.ts +38 -0
- package/dist/routeAuth.d.ts +58 -0
- package/dist/routing.d.ts +1 -1
- package/dist/runtimeOps.d.ts +3 -3
- package/dist/s3Store.d.ts +18 -3
- package/dist/semanticTurn.d.ts +27 -0
- package/dist/session.d.ts +1 -1
- package/dist/sessionObservability.d.ts +145 -0
- package/dist/sessionReplay.d.ts +19 -7
- package/dist/sessionSnapshot.d.ts +109 -0
- package/dist/simulationSuite.d.ts +143 -0
- package/dist/sloCalibration.d.ts +185 -0
- package/dist/sqliteStore.d.ts +17 -6
- package/dist/store.d.ts +1 -1
- package/dist/svelte/createVoiceAgentSquadStatus.d.ts +9 -0
- package/dist/svelte/createVoiceCallDebugger.d.ts +12 -0
- package/dist/svelte/createVoiceCallPlayer.d.ts +33 -0
- package/dist/svelte/createVoiceCampaignDialerProof.d.ts +9 -0
- package/dist/svelte/createVoiceCostDashboard.d.ts +13 -0
- package/dist/svelte/createVoiceDeliveryRuntime.d.ts +11 -0
- package/dist/svelte/createVoiceLiveAgentConsole.d.ts +23 -0
- package/dist/svelte/createVoiceLiveCallViewer.d.ts +26 -0
- package/dist/svelte/createVoiceLiveOps.d.ts +13 -0
- package/dist/svelte/createVoiceOpsActionCenter.d.ts +10 -0
- package/dist/svelte/createVoiceOpsStatus.d.ts +9 -0
- package/dist/svelte/createVoicePlatformCoverage.d.ts +7 -0
- package/dist/svelte/createVoiceProfileComparison.d.ts +7 -0
- package/dist/svelte/createVoiceProofTrends.d.ts +7 -0
- package/dist/svelte/createVoiceProviderCapabilities.d.ts +10 -0
- package/dist/svelte/createVoiceProviderContracts.d.ts +10 -0
- package/dist/svelte/createVoiceProviderSimulationControls.d.ts +11 -0
- package/dist/svelte/createVoiceProviderStatus.d.ts +4 -2
- package/dist/svelte/createVoiceReadinessFailures.d.ts +7 -0
- package/dist/svelte/createVoiceReconnectProfileEvidence.d.ts +7 -0
- package/dist/svelte/createVoiceReplayTimeline.d.ts +13 -0
- package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
- package/dist/svelte/createVoiceSessionObservability.d.ts +10 -0
- package/dist/svelte/createVoiceSessionSnapshot.d.ts +13 -0
- package/dist/svelte/createVoiceStream.d.ts +1 -1
- package/dist/svelte/createVoiceTraceTimeline.d.ts +10 -0
- package/dist/svelte/createVoiceTurnLatency.d.ts +11 -0
- package/dist/svelte/createVoiceTurnQuality.d.ts +10 -0
- package/dist/svelte/createVoiceWidget.d.ts +19 -0
- package/dist/svelte/createVoiceWorkflowStatus.d.ts +1 -1
- package/dist/svelte/index.d.ts +37 -5
- package/dist/svelte/index.js +6931 -684
- package/dist/telephony/contract.d.ts +61 -0
- package/dist/telephony/matrix.d.ts +97 -0
- package/dist/telephony/plivo.d.ts +303 -0
- package/dist/telephony/response.d.ts +1 -1
- package/dist/telephony/security.d.ts +182 -0
- package/dist/telephony/telnyx.d.ts +291 -0
- package/dist/telephony/twilio.d.ts +147 -13
- package/dist/telephonyMediaRoutes.d.ts +72 -0
- package/dist/telephonyOutcome.d.ts +273 -0
- package/dist/testing/accuracy.d.ts +1 -1
- package/dist/testing/benchmark.d.ts +9 -9
- package/dist/testing/corrected.d.ts +5 -5
- package/dist/testing/duplex.d.ts +3 -3
- package/dist/testing/fixtures.d.ts +3 -3
- package/dist/testing/index.d.ts +13 -13
- package/dist/testing/index.js +5223 -159
- package/dist/testing/ioProviderSimulator.d.ts +5 -5
- package/dist/testing/providerSimulator.d.ts +5 -5
- package/dist/testing/review.d.ts +4 -4
- package/dist/testing/sessionBenchmark.d.ts +3 -3
- package/dist/testing/stt.d.ts +3 -3
- package/dist/testing/telephony.d.ts +25 -0
- package/dist/testing/tts.d.ts +1 -1
- package/dist/toolContract.d.ts +161 -0
- package/dist/toolRuntime.d.ts +50 -0
- package/dist/trace.d.ts +46 -7
- package/dist/traceDeliveryRoutes.d.ts +86 -0
- package/dist/traceTimeline.d.ts +97 -0
- package/dist/turnDetection.d.ts +1 -1
- package/dist/turnLatency.d.ts +95 -0
- package/dist/turnProfiles.d.ts +2 -2
- package/dist/turnQuality.d.ts +94 -0
- package/dist/types.d.ts +293 -80
- package/dist/vapiAdapter.d.ts +160 -0
- package/dist/voiceMonitoring.d.ts +444 -0
- package/dist/vue/VoiceCallDebuggerLaunch.d.ts +68 -0
- package/dist/vue/VoiceCallPlayer.d.ts +40 -0
- package/dist/vue/VoiceCostDashboard.d.ts +57 -0
- package/dist/vue/VoiceDeliveryRuntime.d.ts +30 -0
- package/dist/vue/VoiceLiveAgentConsole.d.ts +50 -0
- package/dist/vue/VoiceLiveCallViewer.d.ts +35 -0
- package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
- package/dist/vue/VoiceOpsStatus.d.ts +30 -0
- package/dist/vue/VoicePlatformCoverage.d.ts +23 -0
- package/dist/vue/VoiceProofTrends.d.ts +21 -0
- package/dist/vue/VoiceProviderCapabilities.d.ts +51 -0
- package/dist/vue/VoiceProviderContracts.d.ts +21 -0
- package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
- package/dist/vue/VoiceProviderStatus.d.ts +51 -0
- package/dist/vue/VoiceReadinessFailures.d.ts +21 -0
- package/dist/vue/VoiceReconnectProfileEvidence.d.ts +21 -0
- package/dist/vue/VoiceReplayTimeline.d.ts +17 -0
- package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
- package/dist/vue/VoiceSessionObservability.d.ts +23 -0
- package/dist/vue/VoiceSessionSnapshot.d.ts +68 -0
- package/dist/vue/VoiceTurnLatency.d.ts +69 -0
- package/dist/vue/VoiceTurnQuality.d.ts +51 -0
- package/dist/vue/VoiceWidget.d.ts +77 -0
- package/dist/vue/index.d.ts +49 -5
- package/dist/vue/index.js +11954 -226
- package/dist/vue/useVoiceAgentSquadStatus.d.ts +9 -0
- package/dist/vue/useVoiceCallDebugger.d.ts +10 -0
- package/dist/vue/useVoiceCampaignDialerProof.d.ts +11 -0
- package/dist/vue/useVoiceController.d.ts +4 -2
- package/dist/vue/useVoiceDeliveryRuntime.d.ts +13 -0
- package/dist/vue/useVoiceLiveOps.d.ts +9 -0
- package/dist/vue/useVoiceOpsActionCenter.d.ts +11 -0
- package/dist/vue/useVoiceOpsStatus.d.ts +9 -0
- package/dist/vue/useVoicePlatformCoverage.d.ts +9 -0
- package/dist/vue/useVoiceProfileComparison.d.ts +9 -0
- package/dist/vue/useVoiceProofTrends.d.ts +9 -0
- package/dist/vue/useVoiceProviderCapabilities.d.ts +9 -0
- package/dist/vue/useVoiceProviderContracts.d.ts +9 -0
- package/dist/vue/useVoiceProviderSimulationControls.d.ts +24 -0
- package/dist/vue/useVoiceProviderStatus.d.ts +3 -3
- package/dist/vue/useVoiceReadinessFailures.d.ts +959 -0
- package/dist/vue/useVoiceReconnectProfileEvidence.d.ts +9 -0
- package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
- package/dist/vue/useVoiceSessionObservability.d.ts +9 -0
- package/dist/vue/useVoiceSessionSnapshot.d.ts +10 -0
- package/dist/vue/useVoiceStream.d.ts +5 -2
- package/dist/vue/useVoiceTraceTimeline.d.ts +9 -0
- package/dist/vue/useVoiceTurnLatency.d.ts +10 -0
- package/dist/vue/useVoiceTurnQuality.d.ts +9 -0
- package/dist/vue/useVoiceWorkflowStatus.d.ts +3 -3
- package/dist/webhookFanout.d.ts +48 -0
- package/dist/webhookVerification.d.ts +27 -0
- package/dist/workflowContract.d.ts +8 -8
- package/fixtures/manifest.json +356 -197
- package/package.json +265 -256
- package/dist/angular/voice-app-kit-status.service.d.ts +0 -12
- package/dist/appKit.d.ts +0 -92
- package/dist/client/appKitStatus.d.ts +0 -19
- package/dist/react/useVoiceAppKitStatus.d.ts +0 -8
- package/dist/svelte/createVoiceAppKitStatus.d.ts +0 -8
- package/dist/vue/useVoiceAppKitStatus.d.ts +0 -9
|
@@ -188,6 +188,11 @@ var serverMessageToAction = (message) => {
|
|
|
188
188
|
sessionId: message.sessionId,
|
|
189
189
|
type: "complete"
|
|
190
190
|
};
|
|
191
|
+
case "connection":
|
|
192
|
+
return {
|
|
193
|
+
reconnect: message.reconnect,
|
|
194
|
+
type: "connection"
|
|
195
|
+
};
|
|
191
196
|
case "call_lifecycle":
|
|
192
197
|
return {
|
|
193
198
|
event: message.event,
|
|
@@ -209,9 +214,22 @@ var serverMessageToAction = (message) => {
|
|
|
209
214
|
transcript: message.transcript,
|
|
210
215
|
type: "partial"
|
|
211
216
|
};
|
|
217
|
+
case "replay":
|
|
218
|
+
return {
|
|
219
|
+
assistantTexts: message.assistantTexts,
|
|
220
|
+
call: message.call,
|
|
221
|
+
partial: message.partial,
|
|
222
|
+
scenarioId: message.scenarioId,
|
|
223
|
+
sessionId: message.sessionId,
|
|
224
|
+
sessionMetadata: message.sessionMetadata,
|
|
225
|
+
status: message.status,
|
|
226
|
+
turns: message.turns,
|
|
227
|
+
type: "replay"
|
|
228
|
+
};
|
|
212
229
|
case "session":
|
|
213
230
|
return {
|
|
214
231
|
sessionId: message.sessionId,
|
|
232
|
+
sessionMetadata: message.sessionMetadata,
|
|
215
233
|
scenarioId: message.scenarioId,
|
|
216
234
|
status: message.status,
|
|
217
235
|
type: "session"
|
|
@@ -226,6 +244,232 @@ var serverMessageToAction = (message) => {
|
|
|
226
244
|
}
|
|
227
245
|
};
|
|
228
246
|
|
|
247
|
+
// node_modules/@absolutejs/media/dist/index.js
|
|
248
|
+
var pushIssue = (issues, severity, code, message) => {
|
|
249
|
+
issues.push({ code, message, severity });
|
|
250
|
+
};
|
|
251
|
+
var average = (values) => values.length === 0 ? undefined : values.reduce((total, value) => total + value, 0) / values.length;
|
|
252
|
+
var max = (values) => values.length === 0 ? undefined : Math.max(...values);
|
|
253
|
+
var numericStat = (stat, key) => {
|
|
254
|
+
const value = stat[key];
|
|
255
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
256
|
+
};
|
|
257
|
+
var booleanStat = (stat, key) => {
|
|
258
|
+
const value = stat[key];
|
|
259
|
+
return typeof value === "boolean" ? value : undefined;
|
|
260
|
+
};
|
|
261
|
+
var stringStat = (stat, key) => {
|
|
262
|
+
const value = stat[key];
|
|
263
|
+
return typeof value === "string" ? value : undefined;
|
|
264
|
+
};
|
|
265
|
+
var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
|
|
266
|
+
var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
|
|
267
|
+
var normalizeWebRTCStat = (stat) => {
|
|
268
|
+
const sample = {};
|
|
269
|
+
for (const [key, value] of Object.entries(stat)) {
|
|
270
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
271
|
+
sample[key] = value;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return sample;
|
|
275
|
+
};
|
|
276
|
+
var buildMediaWebRTCStatsReport = (input = {}) => {
|
|
277
|
+
const stats = input.stats ?? [];
|
|
278
|
+
const issues = [];
|
|
279
|
+
const inbound = stats.filter((stat) => stat.type === "inbound-rtp" && stringStat(stat, "kind") !== "video");
|
|
280
|
+
const outbound = stats.filter((stat) => stat.type === "outbound-rtp" && stringStat(stat, "kind") !== "video");
|
|
281
|
+
const candidatePairs = stats.filter((stat) => stat.type === "candidate-pair");
|
|
282
|
+
const audioTracks = stats.filter((stat) => (stat.type === "track" || stat.type === "media-source") && stringStat(stat, "kind") === "audio");
|
|
283
|
+
const activeCandidatePairs = candidatePairs.filter((stat) => booleanStat(stat, "selected") === true || booleanStat(stat, "nominated") === true || stringStat(stat, "state") === "succeeded").length;
|
|
284
|
+
const liveAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") !== "ended" && stringStat(stat, "trackState") !== "ended" && booleanStat(stat, "ended") !== true).length;
|
|
285
|
+
const endedAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") === "ended" || stringStat(stat, "trackState") === "ended" || booleanStat(stat, "ended") === true).length;
|
|
286
|
+
const inboundPackets = inbound.reduce((total, stat) => total + (numericStat(stat, "packetsReceived") ?? 0), 0);
|
|
287
|
+
const outboundPackets = outbound.reduce((total, stat) => total + (numericStat(stat, "packetsSent") ?? 0), 0);
|
|
288
|
+
const packetsLost = [...inbound, ...outbound].reduce((total, stat) => total + Math.max(0, numericStat(stat, "packetsLost") ?? 0), 0);
|
|
289
|
+
const packetLossDenominator = inboundPackets + packetsLost;
|
|
290
|
+
const packetLossRatio = packetLossDenominator === 0 ? 0 : packetsLost / packetLossDenominator;
|
|
291
|
+
const bytesReceived = inbound.reduce((total, stat) => total + (numericStat(stat, "bytesReceived") ?? 0), 0);
|
|
292
|
+
const bytesSent = outbound.reduce((total, stat) => total + (numericStat(stat, "bytesSent") ?? 0), 0);
|
|
293
|
+
const roundTripTimeMs = max(candidatePairs.map((stat) => secondsToMs(numericStat(stat, "currentRoundTripTime") ?? numericStat(stat, "roundTripTime"))).filter((value) => value !== undefined));
|
|
294
|
+
const jitterMs = max([...inbound, ...outbound].map((stat) => secondsToMs(numericStat(stat, "jitter"))).filter((value) => value !== undefined));
|
|
295
|
+
const jitterBufferDelayMs = max(inbound.map((stat) => {
|
|
296
|
+
const delay = numericStat(stat, "jitterBufferDelay");
|
|
297
|
+
const emitted = numericStat(stat, "jitterBufferEmittedCount");
|
|
298
|
+
return delay !== undefined && emitted !== undefined && emitted > 0 ? delay / emitted * 1000 : undefined;
|
|
299
|
+
}).filter((value) => value !== undefined));
|
|
300
|
+
const audioLevels = audioTracks.map((stat) => numericStat(stat, "audioLevel")).filter((value) => value !== undefined);
|
|
301
|
+
if (input.requireConnectedCandidatePair && candidatePairs.length > 0 && activeCandidatePairs === 0) {
|
|
302
|
+
pushIssue(issues, "error", "media.webrtc_candidate_pair_missing", "No active WebRTC candidate pair was observed.");
|
|
303
|
+
}
|
|
304
|
+
if (input.requireLiveAudioTrack && liveAudioTracks === 0) {
|
|
305
|
+
pushIssue(issues, "error", "media.webrtc_audio_track_missing", "No live WebRTC audio track was observed.");
|
|
306
|
+
}
|
|
307
|
+
if (input.maxPacketLossRatio !== undefined && packetLossRatio > input.maxPacketLossRatio) {
|
|
308
|
+
pushIssue(issues, "warning", "media.webrtc_packet_loss", `Observed WebRTC packet loss ratio ${String(packetLossRatio)} above ${String(input.maxPacketLossRatio)}.`);
|
|
309
|
+
}
|
|
310
|
+
if (input.maxRoundTripTimeMs !== undefined && roundTripTimeMs !== undefined && roundTripTimeMs > input.maxRoundTripTimeMs) {
|
|
311
|
+
pushIssue(issues, "warning", "media.webrtc_round_trip_time", `Observed WebRTC RTT ${String(roundTripTimeMs)}ms above ${String(input.maxRoundTripTimeMs)}ms.`);
|
|
312
|
+
}
|
|
313
|
+
if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
|
|
314
|
+
pushIssue(issues, "warning", "media.webrtc_jitter", `Observed WebRTC jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
activeCandidatePairs,
|
|
318
|
+
audioLevelAverage: average(audioLevels),
|
|
319
|
+
bytesReceived,
|
|
320
|
+
bytesSent,
|
|
321
|
+
checkedAt: Date.now(),
|
|
322
|
+
endedAudioTracks,
|
|
323
|
+
inboundPackets,
|
|
324
|
+
issues,
|
|
325
|
+
jitterBufferDelayMs,
|
|
326
|
+
jitterMs,
|
|
327
|
+
liveAudioTracks,
|
|
328
|
+
outboundPackets,
|
|
329
|
+
packetLossRatio,
|
|
330
|
+
packetsLost,
|
|
331
|
+
roundTripTimeMs,
|
|
332
|
+
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
333
|
+
totalStats: stats.length
|
|
334
|
+
};
|
|
335
|
+
};
|
|
336
|
+
var collectMediaWebRTCStats = async (input) => {
|
|
337
|
+
const report = await input.peerConnection.getStats(input.selector ?? null);
|
|
338
|
+
return [...report.values()].map(normalizeWebRTCStat);
|
|
339
|
+
};
|
|
340
|
+
var buildMediaWebRTCStreamContinuityReport = (input = {}) => {
|
|
341
|
+
const stats = input.stats ?? [];
|
|
342
|
+
const previousStats = input.previousStats ?? [];
|
|
343
|
+
const issues = [];
|
|
344
|
+
const previousByKey = new Map(previousStats.map((stat) => [statKey(stat), stat]));
|
|
345
|
+
const audioRtp = stats.filter((stat) => (stat.type === "inbound-rtp" || stat.type === "outbound-rtp") && stringStat(stat, "kind") !== "video" && stringStat(stat, "mediaType") !== "video");
|
|
346
|
+
const streams = audioRtp.map((stat) => {
|
|
347
|
+
const direction = stat.type === "outbound-rtp" ? "outbound" : "inbound";
|
|
348
|
+
const packetsKey = direction === "outbound" ? "packetsSent" : "packetsReceived";
|
|
349
|
+
const bytesKey = direction === "outbound" ? "bytesSent" : "bytesReceived";
|
|
350
|
+
const previous = previousByKey.get(statKey(stat));
|
|
351
|
+
const currentPackets = numericStat(stat, packetsKey);
|
|
352
|
+
const previousPackets = previous ? numericStat(previous, packetsKey) : undefined;
|
|
353
|
+
const currentBytes = numericStat(stat, bytesKey);
|
|
354
|
+
const previousBytes = previous ? numericStat(previous, bytesKey) : undefined;
|
|
355
|
+
const timeDeltaMs = stat.timestamp !== undefined && previous?.timestamp !== undefined ? stat.timestamp - previous.timestamp : undefined;
|
|
356
|
+
return {
|
|
357
|
+
bytesDelta: currentBytes !== undefined && previousBytes !== undefined ? currentBytes - previousBytes : undefined,
|
|
358
|
+
currentPackets,
|
|
359
|
+
direction,
|
|
360
|
+
id: statKey(stat),
|
|
361
|
+
packetDelta: currentPackets !== undefined && previousPackets !== undefined ? currentPackets - previousPackets : undefined,
|
|
362
|
+
previousPackets,
|
|
363
|
+
timeDeltaMs
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
const inbound = streams.filter((stream) => stream.direction === "inbound");
|
|
367
|
+
const outbound = streams.filter((stream) => stream.direction === "outbound");
|
|
368
|
+
const maxObservedGapMs = max(streams.map((stream) => stream.timeDeltaMs).filter((value) => value !== undefined));
|
|
369
|
+
const stalledInboundStreams = inbound.filter((stream) => input.maxInboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxInboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
|
|
370
|
+
const stalledOutboundStreams = outbound.filter((stream) => input.maxOutboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxOutboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
|
|
371
|
+
if (input.requireInboundAudio && inbound.length === 0) {
|
|
372
|
+
pushIssue(issues, "error", "media.webrtc_inbound_audio_missing", "No inbound WebRTC audio RTP stream was observed.");
|
|
373
|
+
}
|
|
374
|
+
if (input.requireOutboundAudio && outbound.length === 0) {
|
|
375
|
+
pushIssue(issues, "error", "media.webrtc_outbound_audio_missing", "No outbound WebRTC audio RTP stream was observed.");
|
|
376
|
+
}
|
|
377
|
+
if (input.maxGapMs !== undefined && maxObservedGapMs !== undefined && maxObservedGapMs > input.maxGapMs) {
|
|
378
|
+
pushIssue(issues, "warning", "media.webrtc_stream_gap", `Observed WebRTC stream sample gap ${String(maxObservedGapMs)}ms above ${String(input.maxGapMs)}ms.`);
|
|
379
|
+
}
|
|
380
|
+
if (stalledInboundStreams > 0) {
|
|
381
|
+
pushIssue(issues, "error", "media.webrtc_inbound_stalled", `${String(stalledInboundStreams)} inbound WebRTC audio stream(s) stopped receiving packets.`);
|
|
382
|
+
}
|
|
383
|
+
if (stalledOutboundStreams > 0) {
|
|
384
|
+
pushIssue(issues, "error", "media.webrtc_outbound_stalled", `${String(stalledOutboundStreams)} outbound WebRTC audio stream(s) stopped sending packets.`);
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
checkedAt: Date.now(),
|
|
388
|
+
inboundAudioStreams: inbound.length,
|
|
389
|
+
issues,
|
|
390
|
+
maxObservedGapMs,
|
|
391
|
+
outboundAudioStreams: outbound.length,
|
|
392
|
+
stalledInboundStreams,
|
|
393
|
+
stalledOutboundStreams,
|
|
394
|
+
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
395
|
+
streams,
|
|
396
|
+
totalStats: stats.length
|
|
397
|
+
};
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/client/browserMedia.ts
|
|
401
|
+
var DEFAULT_BROWSER_MEDIA_PATH = "/api/voice/browser-media";
|
|
402
|
+
var DEFAULT_BROWSER_MEDIA_INTERVAL_MS = 5000;
|
|
403
|
+
var resolvePeerConnection = async (options) => options.peerConnection ?? await options.getPeerConnection?.() ?? null;
|
|
404
|
+
var postBrowserMediaReport = async (payload, options) => {
|
|
405
|
+
const requestFetch = options.fetch ?? globalThis.fetch;
|
|
406
|
+
if (!requestFetch) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
await requestFetch(options.path ?? DEFAULT_BROWSER_MEDIA_PATH, {
|
|
410
|
+
body: JSON.stringify(payload),
|
|
411
|
+
headers: {
|
|
412
|
+
"Content-Type": "application/json"
|
|
413
|
+
},
|
|
414
|
+
keepalive: true,
|
|
415
|
+
method: "POST"
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
var createVoiceBrowserMediaReporter = (options) => {
|
|
419
|
+
let interval = null;
|
|
420
|
+
let previousStats = [];
|
|
421
|
+
const reportOnce = async () => {
|
|
422
|
+
const peerConnection = await resolvePeerConnection(options);
|
|
423
|
+
if (!peerConnection) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const stats = await collectMediaWebRTCStats({ peerConnection });
|
|
427
|
+
const report = buildMediaWebRTCStatsReport({
|
|
428
|
+
...options,
|
|
429
|
+
stats
|
|
430
|
+
});
|
|
431
|
+
const continuity = options.continuity === false ? undefined : buildMediaWebRTCStreamContinuityReport({
|
|
432
|
+
...options.continuity,
|
|
433
|
+
previousStats,
|
|
434
|
+
stats
|
|
435
|
+
});
|
|
436
|
+
const payload = {
|
|
437
|
+
at: Date.now(),
|
|
438
|
+
continuity,
|
|
439
|
+
report,
|
|
440
|
+
scenarioId: options.getScenarioId?.() ?? null,
|
|
441
|
+
sessionId: options.getSessionId?.() ?? null
|
|
442
|
+
};
|
|
443
|
+
previousStats = stats;
|
|
444
|
+
options.onReport?.(payload);
|
|
445
|
+
await postBrowserMediaReport(payload, options);
|
|
446
|
+
return payload;
|
|
447
|
+
};
|
|
448
|
+
const run = () => {
|
|
449
|
+
reportOnce().catch((error) => {
|
|
450
|
+
options.onError?.(error);
|
|
451
|
+
});
|
|
452
|
+
};
|
|
453
|
+
const stop = () => {
|
|
454
|
+
if (interval) {
|
|
455
|
+
clearInterval(interval);
|
|
456
|
+
interval = null;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
return {
|
|
460
|
+
close: stop,
|
|
461
|
+
reportOnce,
|
|
462
|
+
start: () => {
|
|
463
|
+
if (interval) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
run();
|
|
467
|
+
interval = setInterval(run, options.intervalMs ?? DEFAULT_BROWSER_MEDIA_INTERVAL_MS);
|
|
468
|
+
},
|
|
469
|
+
stop
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
|
|
229
473
|
// src/client/connection.ts
|
|
230
474
|
var WS_OPEN = 1;
|
|
231
475
|
var WS_CLOSED = 3;
|
|
@@ -245,6 +489,7 @@ var NOOP_CONNECTION = {
|
|
|
245
489
|
getSessionId: () => "",
|
|
246
490
|
send: noop,
|
|
247
491
|
sendAudio: noop,
|
|
492
|
+
simulateDisconnect: noop,
|
|
248
493
|
start: () => {},
|
|
249
494
|
subscribe: noopUnsubscribe
|
|
250
495
|
};
|
|
@@ -269,10 +514,12 @@ var isVoiceServerMessage = (value) => {
|
|
|
269
514
|
case "assistant":
|
|
270
515
|
case "call_lifecycle":
|
|
271
516
|
case "complete":
|
|
517
|
+
case "connection":
|
|
272
518
|
case "error":
|
|
273
519
|
case "final":
|
|
274
520
|
case "partial":
|
|
275
521
|
case "pong":
|
|
522
|
+
case "replay":
|
|
276
523
|
case "session":
|
|
277
524
|
case "turn":
|
|
278
525
|
return true;
|
|
@@ -309,6 +556,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
309
556
|
sessionId: options.sessionId ?? createSessionId(),
|
|
310
557
|
ws: null
|
|
311
558
|
};
|
|
559
|
+
const emitConnection = (reconnect) => {
|
|
560
|
+
listeners.forEach((listener) => listener(reconnect));
|
|
561
|
+
};
|
|
312
562
|
const clearTimers = () => {
|
|
313
563
|
if (state.pingInterval) {
|
|
314
564
|
clearInterval(state.pingInterval);
|
|
@@ -331,9 +581,28 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
331
581
|
}
|
|
332
582
|
};
|
|
333
583
|
const scheduleReconnect = () => {
|
|
584
|
+
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
334
585
|
state.reconnectAttempts += 1;
|
|
586
|
+
emitConnection({
|
|
587
|
+
reconnect: {
|
|
588
|
+
attempts: state.reconnectAttempts,
|
|
589
|
+
lastDisconnectAt: Date.now(),
|
|
590
|
+
maxAttempts: maxReconnectAttempts,
|
|
591
|
+
nextAttemptAt,
|
|
592
|
+
status: "reconnecting"
|
|
593
|
+
},
|
|
594
|
+
type: "connection"
|
|
595
|
+
});
|
|
335
596
|
state.reconnectTimeout = setTimeout(() => {
|
|
336
597
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
598
|
+
emitConnection({
|
|
599
|
+
reconnect: {
|
|
600
|
+
attempts: state.reconnectAttempts,
|
|
601
|
+
maxAttempts: maxReconnectAttempts,
|
|
602
|
+
status: "exhausted"
|
|
603
|
+
},
|
|
604
|
+
type: "connection"
|
|
605
|
+
});
|
|
337
606
|
return;
|
|
338
607
|
}
|
|
339
608
|
connect();
|
|
@@ -343,9 +612,21 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
343
612
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
|
344
613
|
ws.binaryType = "arraybuffer";
|
|
345
614
|
ws.onopen = () => {
|
|
615
|
+
const wasReconnecting = state.reconnectAttempts > 0;
|
|
346
616
|
state.isConnected = true;
|
|
347
|
-
state.reconnectAttempts = 0;
|
|
348
617
|
flushPendingMessages();
|
|
618
|
+
if (wasReconnecting) {
|
|
619
|
+
emitConnection({
|
|
620
|
+
reconnect: {
|
|
621
|
+
attempts: state.reconnectAttempts,
|
|
622
|
+
lastResumedAt: Date.now(),
|
|
623
|
+
maxAttempts: maxReconnectAttempts,
|
|
624
|
+
status: "resumed"
|
|
625
|
+
},
|
|
626
|
+
type: "connection"
|
|
627
|
+
});
|
|
628
|
+
state.reconnectAttempts = 0;
|
|
629
|
+
}
|
|
349
630
|
listeners.forEach((listener) => listener({
|
|
350
631
|
scenarioId: state.scenarioId ?? undefined,
|
|
351
632
|
sessionId: state.sessionId,
|
|
@@ -375,6 +656,16 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
375
656
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
376
657
|
if (reconnectable) {
|
|
377
658
|
scheduleReconnect();
|
|
659
|
+
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
660
|
+
emitConnection({
|
|
661
|
+
reconnect: {
|
|
662
|
+
attempts: state.reconnectAttempts,
|
|
663
|
+
lastDisconnectAt: Date.now(),
|
|
664
|
+
maxAttempts: maxReconnectAttempts,
|
|
665
|
+
status: "exhausted"
|
|
666
|
+
},
|
|
667
|
+
type: "connection"
|
|
668
|
+
});
|
|
378
669
|
}
|
|
379
670
|
};
|
|
380
671
|
state.ws = ws;
|
|
@@ -423,6 +714,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
423
714
|
state.isConnected = false;
|
|
424
715
|
listeners.clear();
|
|
425
716
|
};
|
|
717
|
+
const simulateDisconnect = () => {
|
|
718
|
+
if (state.ws?.readyState === WS_OPEN) {
|
|
719
|
+
state.ws.close(4000, "absolutejs-voice-reconnect-proof");
|
|
720
|
+
}
|
|
721
|
+
};
|
|
426
722
|
const subscribe = (callback) => {
|
|
427
723
|
listeners.add(callback);
|
|
428
724
|
return () => {
|
|
@@ -439,20 +735,28 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
439
735
|
getSessionId: () => state.sessionId,
|
|
440
736
|
send,
|
|
441
737
|
sendAudio,
|
|
738
|
+
simulateDisconnect,
|
|
442
739
|
start,
|
|
443
740
|
subscribe
|
|
444
741
|
};
|
|
445
742
|
};
|
|
446
743
|
|
|
447
744
|
// src/client/store.ts
|
|
745
|
+
var createInitialReconnectState = () => ({
|
|
746
|
+
attempts: 0,
|
|
747
|
+
maxAttempts: 0,
|
|
748
|
+
status: "idle"
|
|
749
|
+
});
|
|
448
750
|
var createInitialState = () => ({
|
|
449
751
|
assistantAudio: [],
|
|
450
752
|
assistantTexts: [],
|
|
451
753
|
call: null,
|
|
452
754
|
error: null,
|
|
453
755
|
isConnected: false,
|
|
756
|
+
sessionMetadata: null,
|
|
454
757
|
scenarioId: null,
|
|
455
758
|
partial: "",
|
|
759
|
+
reconnect: createInitialReconnectState(),
|
|
456
760
|
sessionId: null,
|
|
457
761
|
status: "idle",
|
|
458
762
|
turns: []
|
|
@@ -509,7 +813,19 @@ var createVoiceStreamStore = () => {
|
|
|
509
813
|
case "connected":
|
|
510
814
|
state = {
|
|
511
815
|
...state,
|
|
512
|
-
isConnected: true
|
|
816
|
+
isConnected: true,
|
|
817
|
+
reconnect: state.reconnect.status === "reconnecting" ? {
|
|
818
|
+
...state.reconnect,
|
|
819
|
+
lastResumedAt: Date.now(),
|
|
820
|
+
nextAttemptAt: undefined,
|
|
821
|
+
status: "resumed"
|
|
822
|
+
} : state.reconnect
|
|
823
|
+
};
|
|
824
|
+
break;
|
|
825
|
+
case "connection":
|
|
826
|
+
state = {
|
|
827
|
+
...state,
|
|
828
|
+
reconnect: action.reconnect
|
|
513
829
|
};
|
|
514
830
|
break;
|
|
515
831
|
case "disconnected":
|
|
@@ -537,6 +853,27 @@ var createVoiceStreamStore = () => {
|
|
|
537
853
|
partial: action.transcript.text
|
|
538
854
|
};
|
|
539
855
|
break;
|
|
856
|
+
case "replay":
|
|
857
|
+
state = {
|
|
858
|
+
...state,
|
|
859
|
+
assistantTexts: [...action.assistantTexts],
|
|
860
|
+
call: action.call ?? null,
|
|
861
|
+
error: null,
|
|
862
|
+
isConnected: action.status === "active",
|
|
863
|
+
partial: action.partial,
|
|
864
|
+
reconnect: state.reconnect.status === "reconnecting" ? {
|
|
865
|
+
...state.reconnect,
|
|
866
|
+
lastResumedAt: Date.now(),
|
|
867
|
+
nextAttemptAt: undefined,
|
|
868
|
+
status: "resumed"
|
|
869
|
+
} : state.reconnect,
|
|
870
|
+
scenarioId: action.scenarioId ?? state.scenarioId,
|
|
871
|
+
sessionId: action.sessionId,
|
|
872
|
+
sessionMetadata: action.sessionMetadata ?? state.sessionMetadata,
|
|
873
|
+
status: action.status,
|
|
874
|
+
turns: [...action.turns]
|
|
875
|
+
};
|
|
876
|
+
break;
|
|
540
877
|
case "session":
|
|
541
878
|
state = {
|
|
542
879
|
...state,
|
|
@@ -544,6 +881,7 @@ var createVoiceStreamStore = () => {
|
|
|
544
881
|
scenarioId: action.scenarioId ?? state.scenarioId,
|
|
545
882
|
isConnected: action.status === "active",
|
|
546
883
|
sessionId: action.sessionId,
|
|
884
|
+
sessionMetadata: action.sessionMetadata ?? state.sessionMetadata,
|
|
547
885
|
status: action.status
|
|
548
886
|
};
|
|
549
887
|
break;
|
|
@@ -574,20 +912,50 @@ var createVoiceStreamStore = () => {
|
|
|
574
912
|
var createVoiceStream = (path, options = {}) => {
|
|
575
913
|
const connection = createVoiceConnection(path, options);
|
|
576
914
|
const store = createVoiceStreamStore();
|
|
915
|
+
const browserMediaReporter = options.browserMedia && typeof window !== "undefined" ? createVoiceBrowserMediaReporter({
|
|
916
|
+
...options.browserMedia,
|
|
917
|
+
getScenarioId: () => options.browserMedia ? options.browserMedia.getScenarioId?.() ?? connection.getScenarioId() : connection.getScenarioId(),
|
|
918
|
+
getSessionId: () => options.browserMedia ? options.browserMedia.getSessionId?.() ?? connection.getSessionId() : connection.getSessionId()
|
|
919
|
+
}) : null;
|
|
577
920
|
const subscribers = new Set;
|
|
578
921
|
const start = (input) => Promise.resolve().then(() => {
|
|
579
922
|
if (!input?.sessionId && !input?.scenarioId) {
|
|
580
923
|
return;
|
|
581
924
|
}
|
|
582
925
|
connection.start(input);
|
|
926
|
+
browserMediaReporter?.start();
|
|
583
927
|
});
|
|
584
928
|
const notify = () => {
|
|
585
929
|
subscribers.forEach((subscriber) => subscriber());
|
|
586
930
|
};
|
|
931
|
+
const reportReconnect = () => {
|
|
932
|
+
if (!options.reconnectReportPath || typeof fetch === "undefined") {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const snapshot = store.getSnapshot();
|
|
936
|
+
const body = JSON.stringify({
|
|
937
|
+
at: Date.now(),
|
|
938
|
+
reconnect: snapshot.reconnect,
|
|
939
|
+
scenarioId: snapshot.scenarioId,
|
|
940
|
+
sessionId: connection.getSessionId(),
|
|
941
|
+
turnIds: snapshot.turns.map((turn) => turn.id)
|
|
942
|
+
});
|
|
943
|
+
fetch(options.reconnectReportPath, {
|
|
944
|
+
body,
|
|
945
|
+
headers: {
|
|
946
|
+
"Content-Type": "application/json"
|
|
947
|
+
},
|
|
948
|
+
keepalive: true,
|
|
949
|
+
method: "POST"
|
|
950
|
+
}).catch(() => {});
|
|
951
|
+
};
|
|
587
952
|
const unsubscribeConnection = connection.subscribe((message) => {
|
|
588
953
|
const action = serverMessageToAction(message);
|
|
589
954
|
if (action) {
|
|
590
955
|
store.dispatch(action);
|
|
956
|
+
if (message.type === "connection") {
|
|
957
|
+
reportReconnect();
|
|
958
|
+
}
|
|
591
959
|
notify();
|
|
592
960
|
}
|
|
593
961
|
});
|
|
@@ -597,6 +965,7 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
597
965
|
},
|
|
598
966
|
close() {
|
|
599
967
|
unsubscribeConnection();
|
|
968
|
+
browserMediaReporter?.close();
|
|
600
969
|
connection.close();
|
|
601
970
|
store.dispatch({ type: "disconnected" });
|
|
602
971
|
notify();
|
|
@@ -619,10 +988,16 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
619
988
|
get scenarioId() {
|
|
620
989
|
return store.getSnapshot().scenarioId;
|
|
621
990
|
},
|
|
991
|
+
get sessionMetadata() {
|
|
992
|
+
return store.getSnapshot().sessionMetadata;
|
|
993
|
+
},
|
|
622
994
|
start,
|
|
623
995
|
get partial() {
|
|
624
996
|
return store.getSnapshot().partial;
|
|
625
997
|
},
|
|
998
|
+
get reconnect() {
|
|
999
|
+
return store.getSnapshot().reconnect;
|
|
1000
|
+
},
|
|
626
1001
|
get sessionId() {
|
|
627
1002
|
return connection.getSessionId();
|
|
628
1003
|
},
|
|
@@ -644,6 +1019,9 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
644
1019
|
sendAudio(audio) {
|
|
645
1020
|
connection.sendAudio(audio);
|
|
646
1021
|
},
|
|
1022
|
+
simulateDisconnect() {
|
|
1023
|
+
connection.simulateDisconnect();
|
|
1024
|
+
},
|
|
647
1025
|
subscribe(subscriber) {
|
|
648
1026
|
subscribers.add(subscriber);
|
|
649
1027
|
return () => {
|
|
@@ -941,8 +1319,10 @@ var createInitialState2 = (stream) => ({
|
|
|
941
1319
|
isConnected: stream.isConnected,
|
|
942
1320
|
isRecording: false,
|
|
943
1321
|
partial: stream.partial,
|
|
1322
|
+
reconnect: stream.reconnect,
|
|
944
1323
|
recordingError: null,
|
|
945
1324
|
sessionId: stream.sessionId,
|
|
1325
|
+
sessionMetadata: stream.sessionMetadata,
|
|
946
1326
|
scenarioId: stream.scenarioId,
|
|
947
1327
|
status: stream.status,
|
|
948
1328
|
turns: [...stream.turns]
|
|
@@ -970,7 +1350,9 @@ var createVoiceController = (path, options = {}) => {
|
|
|
970
1350
|
error: stream.error,
|
|
971
1351
|
isConnected: stream.isConnected,
|
|
972
1352
|
partial: stream.partial,
|
|
1353
|
+
reconnect: stream.reconnect,
|
|
973
1354
|
sessionId: stream.sessionId,
|
|
1355
|
+
sessionMetadata: stream.sessionMetadata,
|
|
974
1356
|
scenarioId: stream.scenarioId,
|
|
975
1357
|
status: stream.status,
|
|
976
1358
|
turns: [...stream.turns]
|
|
@@ -994,7 +1376,13 @@ var createVoiceController = (path, options = {}) => {
|
|
|
994
1376
|
capture = createMicrophoneCapture({
|
|
995
1377
|
channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
|
|
996
1378
|
onLevel: options.capture?.onLevel,
|
|
997
|
-
onAudio: (audio) =>
|
|
1379
|
+
onAudio: (audio) => {
|
|
1380
|
+
if (options.capture?.onAudio) {
|
|
1381
|
+
options.capture.onAudio(audio, stream.sendAudio);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
stream.sendAudio(audio);
|
|
1385
|
+
},
|
|
998
1386
|
sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
|
|
999
1387
|
});
|
|
1000
1388
|
return capture;
|
|
@@ -1064,10 +1452,17 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1064
1452
|
get recordingError() {
|
|
1065
1453
|
return state.recordingError;
|
|
1066
1454
|
},
|
|
1455
|
+
get reconnect() {
|
|
1456
|
+
return state.reconnect;
|
|
1457
|
+
},
|
|
1067
1458
|
sendAudio: (audio) => stream.sendAudio(audio),
|
|
1459
|
+
simulateDisconnect: () => stream.simulateDisconnect(),
|
|
1068
1460
|
get sessionId() {
|
|
1069
1461
|
return state.sessionId;
|
|
1070
1462
|
},
|
|
1463
|
+
get sessionMetadata() {
|
|
1464
|
+
return state.sessionMetadata;
|
|
1465
|
+
},
|
|
1071
1466
|
get scenarioId() {
|
|
1072
1467
|
return state.scenarioId;
|
|
1073
1468
|
},
|
|
@@ -1104,6 +1499,475 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1104
1499
|
};
|
|
1105
1500
|
};
|
|
1106
1501
|
|
|
1502
|
+
// src/client/audioPlayer.ts
|
|
1503
|
+
var DEFAULT_LOOKAHEAD_MS = 15;
|
|
1504
|
+
var createInitialState3 = () => ({
|
|
1505
|
+
activeSourceCount: 0,
|
|
1506
|
+
error: null,
|
|
1507
|
+
isActive: false,
|
|
1508
|
+
isPlaying: false,
|
|
1509
|
+
lastInterruptLatencyMs: undefined,
|
|
1510
|
+
lastPlaybackStopLatencyMs: undefined,
|
|
1511
|
+
processedChunkCount: 0,
|
|
1512
|
+
queuedChunkCount: 0
|
|
1513
|
+
});
|
|
1514
|
+
var getAudioContextCtor = () => {
|
|
1515
|
+
if (typeof window === "undefined") {
|
|
1516
|
+
return typeof AudioContext === "undefined" ? undefined : AudioContext;
|
|
1517
|
+
}
|
|
1518
|
+
return window.AudioContext ?? window.webkitAudioContext;
|
|
1519
|
+
};
|
|
1520
|
+
var decodePCM16LEChunk = (audioContext, chunk) => {
|
|
1521
|
+
const format = chunk.format;
|
|
1522
|
+
if (format.container !== "raw" || format.encoding !== "pcm_s16le") {
|
|
1523
|
+
throw new Error(`Unsupported assistant audio format: ${format.container}/${format.encoding}`);
|
|
1524
|
+
}
|
|
1525
|
+
const bytes = chunk.chunk;
|
|
1526
|
+
const channels = Math.max(1, format.channels);
|
|
1527
|
+
const sampleCount = Math.floor(bytes.byteLength / 2);
|
|
1528
|
+
const frameCount = Math.max(1, Math.floor(sampleCount / channels));
|
|
1529
|
+
const audioBuffer = audioContext.createBuffer(channels, frameCount, format.sampleRateHz);
|
|
1530
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1531
|
+
for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
|
|
1532
|
+
const channelData = audioBuffer.getChannelData(channelIndex);
|
|
1533
|
+
for (let frameIndex = 0;frameIndex < frameCount; frameIndex += 1) {
|
|
1534
|
+
const sampleIndex = frameIndex * channels + channelIndex;
|
|
1535
|
+
const sampleOffset = sampleIndex * 2;
|
|
1536
|
+
if (sampleOffset + 1 >= bytes.byteLength) {
|
|
1537
|
+
channelData[frameIndex] = 0;
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
channelData[frameIndex] = view.getInt16(sampleOffset, true) / 32768;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
return audioBuffer;
|
|
1544
|
+
};
|
|
1545
|
+
var createVoiceAudioPlayer = (source, options = {}) => {
|
|
1546
|
+
const subscribers = new Set;
|
|
1547
|
+
const sourceNodes = new Set;
|
|
1548
|
+
const lookaheadSeconds = (options.lookaheadMs ?? DEFAULT_LOOKAHEAD_MS) / 1000;
|
|
1549
|
+
let state = createInitialState3();
|
|
1550
|
+
let audioContext = null;
|
|
1551
|
+
let outputNode = null;
|
|
1552
|
+
let queueEndTime = 0;
|
|
1553
|
+
let syncPromise = Promise.resolve();
|
|
1554
|
+
let interruptStartedAt = null;
|
|
1555
|
+
let interruptPromise = null;
|
|
1556
|
+
let resolveInterruptPromise = null;
|
|
1557
|
+
let interruptFallbackTimer = null;
|
|
1558
|
+
const notify = () => {
|
|
1559
|
+
for (const subscriber of subscribers) {
|
|
1560
|
+
subscriber();
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
const setState = (next) => {
|
|
1564
|
+
state = {
|
|
1565
|
+
...state,
|
|
1566
|
+
...next
|
|
1567
|
+
};
|
|
1568
|
+
notify();
|
|
1569
|
+
};
|
|
1570
|
+
const clearError = () => {
|
|
1571
|
+
if (state.error !== null) {
|
|
1572
|
+
setState({ error: null });
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
const clearInterruptTimer = () => {
|
|
1576
|
+
if (interruptFallbackTimer !== null) {
|
|
1577
|
+
clearTimeout(interruptFallbackTimer);
|
|
1578
|
+
interruptFallbackTimer = null;
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
const resolveInterrupt = (latencyMs) => {
|
|
1582
|
+
clearInterruptTimer();
|
|
1583
|
+
interruptStartedAt = null;
|
|
1584
|
+
setState({
|
|
1585
|
+
activeSourceCount: sourceNodes.size,
|
|
1586
|
+
isPlaying: false,
|
|
1587
|
+
lastInterruptLatencyMs: latencyMs,
|
|
1588
|
+
lastPlaybackStopLatencyMs: state.lastPlaybackStopLatencyMs ?? latencyMs
|
|
1589
|
+
});
|
|
1590
|
+
resolveInterruptPromise?.();
|
|
1591
|
+
resolveInterruptPromise = null;
|
|
1592
|
+
interruptPromise = null;
|
|
1593
|
+
};
|
|
1594
|
+
const estimateOutputStopLatencyMs = (context) => {
|
|
1595
|
+
if (!context) {
|
|
1596
|
+
return 0;
|
|
1597
|
+
}
|
|
1598
|
+
return Math.max(0, ((context.baseLatency ?? 0) + (context.outputLatency ?? 0)) * 1000);
|
|
1599
|
+
};
|
|
1600
|
+
const restoreOutputGain = (context) => {
|
|
1601
|
+
if (!outputNode) {
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
const gainValue = 1;
|
|
1605
|
+
if (outputNode.gain.setValueAtTime) {
|
|
1606
|
+
outputNode.gain.setValueAtTime(gainValue, context?.currentTime ?? 0);
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
outputNode.gain.value = gainValue;
|
|
1610
|
+
};
|
|
1611
|
+
const muteOutputGain = (context) => {
|
|
1612
|
+
if (!outputNode) {
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
const gainValue = 0;
|
|
1616
|
+
if (outputNode.gain.setValueAtTime) {
|
|
1617
|
+
outputNode.gain.setValueAtTime(gainValue, context?.currentTime ?? 0);
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
outputNode.gain.value = gainValue;
|
|
1621
|
+
};
|
|
1622
|
+
const maybeResolveInterrupt = () => {
|
|
1623
|
+
if (interruptStartedAt === null || sourceNodes.size > 0) {
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
resolveInterrupt(Date.now() - interruptStartedAt);
|
|
1627
|
+
};
|
|
1628
|
+
const ensureAudioContext = async () => {
|
|
1629
|
+
if (audioContext) {
|
|
1630
|
+
return audioContext;
|
|
1631
|
+
}
|
|
1632
|
+
if (options.createAudioContext) {
|
|
1633
|
+
audioContext = options.createAudioContext();
|
|
1634
|
+
} else {
|
|
1635
|
+
const AudioContextCtor = getAudioContextCtor();
|
|
1636
|
+
if (!AudioContextCtor) {
|
|
1637
|
+
throw new Error("Assistant audio playback requires AudioContext support.");
|
|
1638
|
+
}
|
|
1639
|
+
audioContext = new AudioContextCtor;
|
|
1640
|
+
}
|
|
1641
|
+
if (audioContext.createGain) {
|
|
1642
|
+
outputNode = audioContext.createGain();
|
|
1643
|
+
outputNode.connect?.(audioContext.destination);
|
|
1644
|
+
}
|
|
1645
|
+
queueEndTime = audioContext.currentTime;
|
|
1646
|
+
return audioContext;
|
|
1647
|
+
};
|
|
1648
|
+
const scheduleChunk = async (chunk) => {
|
|
1649
|
+
const context = await ensureAudioContext();
|
|
1650
|
+
const buffer = decodePCM16LEChunk(context, chunk);
|
|
1651
|
+
const node = context.createBufferSource();
|
|
1652
|
+
node.buffer = buffer;
|
|
1653
|
+
node.connect(outputNode ?? context.destination);
|
|
1654
|
+
node.onended = () => {
|
|
1655
|
+
sourceNodes.delete(node);
|
|
1656
|
+
node.disconnect?.();
|
|
1657
|
+
setState({
|
|
1658
|
+
activeSourceCount: sourceNodes.size,
|
|
1659
|
+
isPlaying: sourceNodes.size > 0 && state.isActive
|
|
1660
|
+
});
|
|
1661
|
+
maybeResolveInterrupt();
|
|
1662
|
+
};
|
|
1663
|
+
const startAt = Math.max(context.currentTime + lookaheadSeconds, queueEndTime);
|
|
1664
|
+
queueEndTime = startAt + buffer.duration;
|
|
1665
|
+
sourceNodes.add(node);
|
|
1666
|
+
setState({
|
|
1667
|
+
activeSourceCount: sourceNodes.size,
|
|
1668
|
+
isPlaying: true
|
|
1669
|
+
});
|
|
1670
|
+
node.start(startAt);
|
|
1671
|
+
};
|
|
1672
|
+
const stopQueuedPlayback = (options2) => {
|
|
1673
|
+
for (const node of [...sourceNodes]) {
|
|
1674
|
+
node.stop?.();
|
|
1675
|
+
}
|
|
1676
|
+
queueEndTime = audioContext ? audioContext.currentTime : 0;
|
|
1677
|
+
if (options2?.forceClear) {
|
|
1678
|
+
for (const node of sourceNodes) {
|
|
1679
|
+
node.disconnect?.();
|
|
1680
|
+
}
|
|
1681
|
+
sourceNodes.clear();
|
|
1682
|
+
maybeResolveInterrupt();
|
|
1683
|
+
}
|
|
1684
|
+
};
|
|
1685
|
+
const sync = async () => {
|
|
1686
|
+
if (!state.isActive) {
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
const nextChunks = source.assistantAudio.slice(state.processedChunkCount);
|
|
1690
|
+
if (nextChunks.length === 0) {
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
try {
|
|
1694
|
+
clearError();
|
|
1695
|
+
for (const chunk of nextChunks) {
|
|
1696
|
+
await scheduleChunk(chunk);
|
|
1697
|
+
}
|
|
1698
|
+
setState({
|
|
1699
|
+
processedChunkCount: source.assistantAudio.length,
|
|
1700
|
+
queuedChunkCount: state.queuedChunkCount + nextChunks.length
|
|
1701
|
+
});
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
setState({
|
|
1704
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
const queueSync = () => {
|
|
1709
|
+
syncPromise = syncPromise.then(() => sync(), () => sync());
|
|
1710
|
+
return syncPromise;
|
|
1711
|
+
};
|
|
1712
|
+
const unsubscribeSource = source.subscribe(() => {
|
|
1713
|
+
if (options.autoStart && !state.isActive && source.assistantAudio.length > 0) {
|
|
1714
|
+
player.start();
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
if (state.isActive) {
|
|
1718
|
+
queueSync();
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
const player = {
|
|
1722
|
+
close: async () => {
|
|
1723
|
+
unsubscribeSource();
|
|
1724
|
+
stopQueuedPlayback({ forceClear: true });
|
|
1725
|
+
clearInterruptTimer();
|
|
1726
|
+
resolveInterruptPromise?.();
|
|
1727
|
+
resolveInterruptPromise = null;
|
|
1728
|
+
interruptPromise = null;
|
|
1729
|
+
interruptStartedAt = null;
|
|
1730
|
+
if (audioContext && audioContext.state !== "closed") {
|
|
1731
|
+
await audioContext.close();
|
|
1732
|
+
}
|
|
1733
|
+
audioContext = null;
|
|
1734
|
+
outputNode?.disconnect?.();
|
|
1735
|
+
outputNode = null;
|
|
1736
|
+
queueEndTime = 0;
|
|
1737
|
+
setState({
|
|
1738
|
+
activeSourceCount: 0,
|
|
1739
|
+
isActive: false,
|
|
1740
|
+
isPlaying: false
|
|
1741
|
+
});
|
|
1742
|
+
},
|
|
1743
|
+
get activeSourceCount() {
|
|
1744
|
+
return state.activeSourceCount;
|
|
1745
|
+
},
|
|
1746
|
+
get error() {
|
|
1747
|
+
return state.error;
|
|
1748
|
+
},
|
|
1749
|
+
getSnapshot: () => state,
|
|
1750
|
+
get isActive() {
|
|
1751
|
+
return state.isActive;
|
|
1752
|
+
},
|
|
1753
|
+
get isPlaying() {
|
|
1754
|
+
return state.isPlaying;
|
|
1755
|
+
},
|
|
1756
|
+
interrupt: async () => {
|
|
1757
|
+
const startedAt = Date.now();
|
|
1758
|
+
const context = await ensureAudioContext();
|
|
1759
|
+
interruptStartedAt = startedAt;
|
|
1760
|
+
muteOutputGain(context);
|
|
1761
|
+
const playbackStopLatencyMs = Date.now() - startedAt + estimateOutputStopLatencyMs(context);
|
|
1762
|
+
setState({
|
|
1763
|
+
isActive: false,
|
|
1764
|
+
isPlaying: sourceNodes.size > 0,
|
|
1765
|
+
lastPlaybackStopLatencyMs: playbackStopLatencyMs
|
|
1766
|
+
});
|
|
1767
|
+
if (sourceNodes.size === 0) {
|
|
1768
|
+
resolveInterrupt(playbackStopLatencyMs);
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
if (!interruptPromise) {
|
|
1772
|
+
interruptPromise = new Promise((resolve) => {
|
|
1773
|
+
resolveInterruptPromise = resolve;
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
clearInterruptTimer();
|
|
1777
|
+
interruptFallbackTimer = setTimeout(() => {
|
|
1778
|
+
for (const node of sourceNodes) {
|
|
1779
|
+
node.disconnect?.();
|
|
1780
|
+
}
|
|
1781
|
+
sourceNodes.clear();
|
|
1782
|
+
resolveInterrupt(Date.now() - startedAt);
|
|
1783
|
+
}, 250);
|
|
1784
|
+
stopQueuedPlayback();
|
|
1785
|
+
await interruptPromise;
|
|
1786
|
+
},
|
|
1787
|
+
get lastInterruptLatencyMs() {
|
|
1788
|
+
return state.lastInterruptLatencyMs;
|
|
1789
|
+
},
|
|
1790
|
+
get lastPlaybackStopLatencyMs() {
|
|
1791
|
+
return state.lastPlaybackStopLatencyMs;
|
|
1792
|
+
},
|
|
1793
|
+
pause: async () => {
|
|
1794
|
+
if (!audioContext) {
|
|
1795
|
+
setState({
|
|
1796
|
+
activeSourceCount: 0,
|
|
1797
|
+
isActive: false,
|
|
1798
|
+
isPlaying: false
|
|
1799
|
+
});
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
await audioContext.suspend();
|
|
1803
|
+
setState({
|
|
1804
|
+
activeSourceCount: sourceNodes.size,
|
|
1805
|
+
isActive: false,
|
|
1806
|
+
isPlaying: false
|
|
1807
|
+
});
|
|
1808
|
+
},
|
|
1809
|
+
get processedChunkCount() {
|
|
1810
|
+
return state.processedChunkCount;
|
|
1811
|
+
},
|
|
1812
|
+
get queuedChunkCount() {
|
|
1813
|
+
return state.queuedChunkCount;
|
|
1814
|
+
},
|
|
1815
|
+
start: async () => {
|
|
1816
|
+
try {
|
|
1817
|
+
clearError();
|
|
1818
|
+
const context = await ensureAudioContext();
|
|
1819
|
+
restoreOutputGain(context);
|
|
1820
|
+
if (context.state === "suspended") {
|
|
1821
|
+
await context.resume();
|
|
1822
|
+
}
|
|
1823
|
+
setState({
|
|
1824
|
+
activeSourceCount: sourceNodes.size,
|
|
1825
|
+
isActive: true,
|
|
1826
|
+
isPlaying: context.state === "running"
|
|
1827
|
+
});
|
|
1828
|
+
await queueSync();
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
setState({
|
|
1831
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1832
|
+
isActive: false,
|
|
1833
|
+
isPlaying: false
|
|
1834
|
+
});
|
|
1835
|
+
throw error;
|
|
1836
|
+
}
|
|
1837
|
+
},
|
|
1838
|
+
subscribe: (subscriber) => {
|
|
1839
|
+
subscribers.add(subscriber);
|
|
1840
|
+
return () => {
|
|
1841
|
+
subscribers.delete(subscriber);
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
return player;
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
// src/client/bargeInMonitor.ts
|
|
1849
|
+
var DEFAULT_THRESHOLD_MS = 250;
|
|
1850
|
+
var createEventId = () => `barge-in:${Date.now()}:${crypto.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
1851
|
+
var summarize = (events, thresholdMs) => {
|
|
1852
|
+
const stopped = events.filter((event) => event.status === "stopped");
|
|
1853
|
+
const latencies = stopped.map((event) => event.latencyMs).filter((value) => typeof value === "number");
|
|
1854
|
+
const failed = stopped.filter((event) => typeof event.latencyMs === "number" && event.latencyMs > thresholdMs).length;
|
|
1855
|
+
const passed = stopped.length - failed;
|
|
1856
|
+
return {
|
|
1857
|
+
averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
|
|
1858
|
+
events: [...events],
|
|
1859
|
+
failed,
|
|
1860
|
+
lastEvent: events.at(-1),
|
|
1861
|
+
passed,
|
|
1862
|
+
status: events.length === 0 ? "empty" : failed > 0 ? "fail" : stopped.length === 0 ? "warn" : "pass",
|
|
1863
|
+
thresholdMs,
|
|
1864
|
+
total: stopped.length
|
|
1865
|
+
};
|
|
1866
|
+
};
|
|
1867
|
+
var createVoiceBargeInMonitor = (options = {}) => {
|
|
1868
|
+
const listeners = new Set;
|
|
1869
|
+
const thresholdMs = options.thresholdMs ?? DEFAULT_THRESHOLD_MS;
|
|
1870
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1871
|
+
const events = [];
|
|
1872
|
+
const emit = () => {
|
|
1873
|
+
for (const listener of listeners) {
|
|
1874
|
+
listener();
|
|
1875
|
+
}
|
|
1876
|
+
};
|
|
1877
|
+
const postEvent = (event) => {
|
|
1878
|
+
if (!options.path || typeof fetchImpl !== "function") {
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
fetchImpl(options.path, {
|
|
1882
|
+
body: JSON.stringify(event),
|
|
1883
|
+
headers: {
|
|
1884
|
+
"Content-Type": "application/json"
|
|
1885
|
+
},
|
|
1886
|
+
method: "POST"
|
|
1887
|
+
}).catch(() => {});
|
|
1888
|
+
};
|
|
1889
|
+
const record = (status, input) => {
|
|
1890
|
+
const event = {
|
|
1891
|
+
at: Date.now(),
|
|
1892
|
+
id: createEventId(),
|
|
1893
|
+
latencyMs: input.latencyMs,
|
|
1894
|
+
playbackStopLatencyMs: input.playbackStopLatencyMs,
|
|
1895
|
+
reason: input.reason,
|
|
1896
|
+
sessionId: input.sessionId,
|
|
1897
|
+
status,
|
|
1898
|
+
thresholdMs
|
|
1899
|
+
};
|
|
1900
|
+
events.push(event);
|
|
1901
|
+
postEvent(event);
|
|
1902
|
+
emit();
|
|
1903
|
+
return event;
|
|
1904
|
+
};
|
|
1905
|
+
return {
|
|
1906
|
+
getSnapshot: () => summarize(events, thresholdMs),
|
|
1907
|
+
recordRequested: (input) => record("requested", input),
|
|
1908
|
+
recordSkipped: (input) => record("skipped", input),
|
|
1909
|
+
recordStopped: (input) => record("stopped", input),
|
|
1910
|
+
subscribe: (subscriber) => {
|
|
1911
|
+
listeners.add(subscriber);
|
|
1912
|
+
return () => {
|
|
1913
|
+
listeners.delete(subscriber);
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
};
|
|
1918
|
+
|
|
1919
|
+
// src/client/duplex.ts
|
|
1920
|
+
var DEFAULT_INTERRUPT_THRESHOLD = 0.08;
|
|
1921
|
+
var shouldInterruptForLevel = (level, options = {}) => (options.enabled ?? true) && level >= (options.interruptThreshold ?? DEFAULT_INTERRUPT_THRESHOLD);
|
|
1922
|
+
var bindVoiceBargeIn = (controller, player, options = {}) => {
|
|
1923
|
+
let lastPartial = controller.partial;
|
|
1924
|
+
const interruptIfPlaying = (reason) => {
|
|
1925
|
+
if (!player.isPlaying || options.enabled === false) {
|
|
1926
|
+
options.monitor?.recordSkipped({
|
|
1927
|
+
reason,
|
|
1928
|
+
sessionId: controller.sessionId
|
|
1929
|
+
});
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
options.monitor?.recordRequested({
|
|
1933
|
+
reason,
|
|
1934
|
+
sessionId: controller.sessionId
|
|
1935
|
+
});
|
|
1936
|
+
player.interrupt().then(() => {
|
|
1937
|
+
options.monitor?.recordStopped({
|
|
1938
|
+
latencyMs: player.lastInterruptLatencyMs,
|
|
1939
|
+
playbackStopLatencyMs: player.lastPlaybackStopLatencyMs,
|
|
1940
|
+
reason,
|
|
1941
|
+
sessionId: controller.sessionId
|
|
1942
|
+
});
|
|
1943
|
+
});
|
|
1944
|
+
};
|
|
1945
|
+
const unsubscribe = controller.subscribe(() => {
|
|
1946
|
+
if (options.interruptOnPartial === false) {
|
|
1947
|
+
lastPartial = controller.partial;
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
if (!lastPartial && controller.partial) {
|
|
1951
|
+
interruptIfPlaying("partial-transcript");
|
|
1952
|
+
}
|
|
1953
|
+
lastPartial = controller.partial;
|
|
1954
|
+
});
|
|
1955
|
+
return {
|
|
1956
|
+
close: () => {
|
|
1957
|
+
unsubscribe();
|
|
1958
|
+
},
|
|
1959
|
+
handleLevel: (level) => {
|
|
1960
|
+
if (shouldInterruptForLevel(level, options)) {
|
|
1961
|
+
interruptIfPlaying("input-level");
|
|
1962
|
+
}
|
|
1963
|
+
},
|
|
1964
|
+
sendAudio: (audio) => {
|
|
1965
|
+
interruptIfPlaying("manual-audio");
|
|
1966
|
+
controller.sendAudio(audio);
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1969
|
+
};
|
|
1970
|
+
|
|
1107
1971
|
// src/client/htmxBootstrap.ts
|
|
1108
1972
|
var VOICE_WAVE_POINTS = 48;
|
|
1109
1973
|
var VOICE_WAVE_WIDTH = 320;
|
|
@@ -1126,7 +1990,7 @@ var DEFAULT_GUIDED_PROMPTS = [
|
|
|
1126
1990
|
"Now describe what you are trying to do or test.",
|
|
1127
1991
|
"Finish with any detail that feels blocked, risky, or unclear."
|
|
1128
1992
|
];
|
|
1129
|
-
var clamp = (value, min,
|
|
1993
|
+
var clamp = (value, min, max2) => Math.min(max2, Math.max(min, value));
|
|
1130
1994
|
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1131
1995
|
var readErrorField = (value, key) => {
|
|
1132
1996
|
const candidate = value[key];
|
|
@@ -1160,6 +2024,17 @@ var formatErrorMessage = (error) => {
|
|
|
1160
2024
|
}
|
|
1161
2025
|
return "Unexpected error";
|
|
1162
2026
|
};
|
|
2027
|
+
var formatReconnectState = (reconnect) => {
|
|
2028
|
+
const pieces = [reconnect.status];
|
|
2029
|
+
if (reconnect.attempts > 0 || reconnect.maxAttempts > 0) {
|
|
2030
|
+
pieces.push(`${reconnect.attempts}/${reconnect.maxAttempts} attempts`);
|
|
2031
|
+
}
|
|
2032
|
+
if (reconnect.nextAttemptAt) {
|
|
2033
|
+
const waitMs = Math.max(0, reconnect.nextAttemptAt - Date.now());
|
|
2034
|
+
pieces.push(`retry in ${Math.ceil(waitMs / 100) / 10}s`);
|
|
2035
|
+
}
|
|
2036
|
+
return pieces.join(" · ");
|
|
2037
|
+
};
|
|
1163
2038
|
var createInitialVoiceWaveLevels = (count = VOICE_WAVE_POINTS) => Array.from({ length: count }, () => 0);
|
|
1164
2039
|
var pushVoiceWaveLevel = (levels, nextLevel, count = VOICE_WAVE_POINTS) => {
|
|
1165
2040
|
const next = levels.slice(-(count - 1));
|
|
@@ -1216,6 +2091,20 @@ var parsePromptList = (value) => {
|
|
|
1216
2091
|
} catch {}
|
|
1217
2092
|
return DEFAULT_GUIDED_PROMPTS;
|
|
1218
2093
|
};
|
|
2094
|
+
var parseOptionalNumber = (value) => {
|
|
2095
|
+
if (!value) {
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
const parsed = Number(value);
|
|
2099
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
2100
|
+
};
|
|
2101
|
+
var resolveElement2 = (root, selector, ctor) => {
|
|
2102
|
+
if (!selector) {
|
|
2103
|
+
return null;
|
|
2104
|
+
}
|
|
2105
|
+
const value = document.querySelector(selector);
|
|
2106
|
+
return value instanceof ctor ? value : null;
|
|
2107
|
+
};
|
|
1219
2108
|
var requireElement = (root, selector, ctor, name) => {
|
|
1220
2109
|
const value = selector ? document.querySelector(selector) : null;
|
|
1221
2110
|
if (value instanceof ctor) {
|
|
@@ -1266,11 +2155,20 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1266
2155
|
const guidedPrompts = parsePromptList(root.dataset.voiceGuidedPrompts);
|
|
1267
2156
|
const guidedLabel = root.dataset.voiceGuidedLabel ?? DEFAULT_GUIDED_LABEL;
|
|
1268
2157
|
const generalLabel = root.dataset.voiceGeneralLabel ?? DEFAULT_GENERAL_LABEL;
|
|
2158
|
+
const reconnectReportPath = root.dataset.voiceReconnectReportPath;
|
|
2159
|
+
const bargeInPath = root.dataset.voiceBargeInPath;
|
|
2160
|
+
const bargeInMonitor = bargeInPath ? createVoiceBargeInMonitor({
|
|
2161
|
+
path: bargeInPath,
|
|
2162
|
+
thresholdMs: parseOptionalNumber(root.dataset.voiceBargeInThresholdMs)
|
|
2163
|
+
}) : null;
|
|
2164
|
+
const bargeInRecentWindowMs = parseOptionalNumber(root.dataset.voiceBargeInRecentWindowMs) ?? 4000;
|
|
2165
|
+
const bargeInSpeechThreshold = parseOptionalNumber(root.dataset.voiceBargeInSpeechThreshold) ?? 0.04;
|
|
1269
2166
|
const syncElement = requireElement(document, root.dataset.voiceSync, HTMLElement, "voice-htmx-sync");
|
|
1270
2167
|
const connectionMetric = requireElement(root, root.dataset.voiceConnection, HTMLElement, "metric-connection");
|
|
1271
2168
|
const errorStatus = requireElement(root, root.dataset.voiceError, HTMLElement, "status-error");
|
|
1272
2169
|
const microphoneStatus = requireElement(root, root.dataset.voiceMicrophone, HTMLElement, "status-mic");
|
|
1273
2170
|
const promptStatus = requireElement(root, root.dataset.voicePrompt, HTMLElement, "status-prompt");
|
|
2171
|
+
const reconnectStatus = resolveElement2(root, root.dataset.voiceReconnect, HTMLElement);
|
|
1274
2172
|
const chatList = requireElement(root, root.dataset.voiceChat, HTMLElement, "chat-list");
|
|
1275
2173
|
const startGuidedButton = requireElement(root, root.dataset.voiceStartGuided, HTMLButtonElement, "start-guided");
|
|
1276
2174
|
const startGeneralButton = requireElement(root, root.dataset.voiceStartGeneral, HTMLButtonElement, "start-general");
|
|
@@ -1279,35 +2177,70 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1279
2177
|
const voiceMonitorCopy = requireElement(root, root.dataset.voiceMonitorCopy, HTMLElement, "voice-monitor-copy");
|
|
1280
2178
|
const voiceWaveGlow = requireElement(root, root.dataset.voiceWaveGlow, SVGPathElement, "voice-wave-glow");
|
|
1281
2179
|
const voiceWavePath = requireElement(root, root.dataset.voiceWavePath, SVGPathElement, "voice-wave-path");
|
|
2180
|
+
let activeMode = null;
|
|
2181
|
+
let hasStartedModes = {
|
|
2182
|
+
general: false,
|
|
2183
|
+
guided: false
|
|
2184
|
+
};
|
|
2185
|
+
let isCapturing = false;
|
|
2186
|
+
let micError = null;
|
|
2187
|
+
let waveLevels = createInitialVoiceWaveLevels();
|
|
2188
|
+
let guidedBargeInBinding = null;
|
|
2189
|
+
let generalBargeInBinding = null;
|
|
1282
2190
|
const guidedVoice = createVoiceController(guidedPath, {
|
|
1283
2191
|
capture: {
|
|
2192
|
+
onAudio: (audio, sendAudio) => {
|
|
2193
|
+
if (guidedBargeInBinding) {
|
|
2194
|
+
guidedBargeInBinding.sendAudio(audio);
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
sendAudio(audio);
|
|
2198
|
+
},
|
|
1284
2199
|
onLevel: (level) => {
|
|
2200
|
+
guidedBargeInBinding?.handleLevel(level);
|
|
1285
2201
|
waveLevels = pushVoiceWaveLevel(waveLevels, level);
|
|
1286
2202
|
renderWave();
|
|
1287
2203
|
}
|
|
1288
2204
|
},
|
|
2205
|
+
connection: {
|
|
2206
|
+
reconnectReportPath
|
|
2207
|
+
},
|
|
1289
2208
|
preset: "guided-intake"
|
|
1290
2209
|
});
|
|
1291
2210
|
const generalVoice = createVoiceController(generalPath, {
|
|
1292
2211
|
capture: {
|
|
2212
|
+
onAudio: (audio, sendAudio) => {
|
|
2213
|
+
if (generalBargeInBinding) {
|
|
2214
|
+
generalBargeInBinding.sendAudio(audio);
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
sendAudio(audio);
|
|
2218
|
+
},
|
|
1293
2219
|
onLevel: (level) => {
|
|
2220
|
+
generalBargeInBinding?.handleLevel(level);
|
|
1294
2221
|
waveLevels = pushVoiceWaveLevel(waveLevels, level);
|
|
1295
2222
|
renderWave();
|
|
1296
2223
|
}
|
|
1297
2224
|
},
|
|
2225
|
+
connection: {
|
|
2226
|
+
reconnectReportPath
|
|
2227
|
+
},
|
|
1298
2228
|
preset: "dictation"
|
|
1299
2229
|
});
|
|
1300
2230
|
const stopGuidedBinding = guidedVoice.bindHTMX({ element: syncElement });
|
|
1301
2231
|
const stopGeneralBinding = generalVoice.bindHTMX({ element: syncElement });
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
2232
|
+
const guidedAudioPlayer = createVoiceAudioPlayer(guidedVoice);
|
|
2233
|
+
const generalAudioPlayer = createVoiceAudioPlayer(generalVoice);
|
|
2234
|
+
guidedBargeInBinding = bindVoiceBargeIn(guidedVoice, guidedAudioPlayer, {
|
|
2235
|
+
interruptThreshold: bargeInSpeechThreshold,
|
|
2236
|
+
monitor: bargeInMonitor ?? undefined
|
|
2237
|
+
});
|
|
2238
|
+
generalBargeInBinding = bindVoiceBargeIn(generalVoice, generalAudioPlayer, {
|
|
2239
|
+
interruptThreshold: bargeInSpeechThreshold,
|
|
2240
|
+
monitor: bargeInMonitor ?? undefined
|
|
2241
|
+
});
|
|
1310
2242
|
const currentVoice = () => activeMode === "general" ? generalVoice : guidedVoice;
|
|
2243
|
+
const currentAudioPlayer = () => activeMode === "general" ? generalAudioPlayer : guidedAudioPlayer;
|
|
1311
2244
|
const renderWave = () => {
|
|
1312
2245
|
const path = createVoiceWavePath(waveLevels);
|
|
1313
2246
|
voiceWaveGlow.setAttribute("d", path);
|
|
@@ -1322,6 +2255,9 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1322
2255
|
const status = voice.status;
|
|
1323
2256
|
connectionMetric.textContent = voice.isConnected ? "Connected" : "Waiting";
|
|
1324
2257
|
errorStatus.textContent = micError || voice.error || "None";
|
|
2258
|
+
if (reconnectStatus) {
|
|
2259
|
+
reconnectStatus.textContent = formatReconnectState(voice.reconnect);
|
|
2260
|
+
}
|
|
1325
2261
|
microphoneStatus.textContent = isCapturing ? DEFAULT_MIC_LIVE : DEFAULT_MIC_IDLE;
|
|
1326
2262
|
promptStatus.textContent = resolvePromptMessage({
|
|
1327
2263
|
guidedPrompts,
|
|
@@ -1385,8 +2321,18 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1385
2321
|
render();
|
|
1386
2322
|
}
|
|
1387
2323
|
};
|
|
1388
|
-
guidedVoice.subscribe(
|
|
1389
|
-
|
|
2324
|
+
guidedVoice.subscribe(() => {
|
|
2325
|
+
if (guidedVoice.assistantAudio.length > 0) {
|
|
2326
|
+
guidedAudioPlayer.start().catch(() => {});
|
|
2327
|
+
}
|
|
2328
|
+
render();
|
|
2329
|
+
});
|
|
2330
|
+
generalVoice.subscribe(() => {
|
|
2331
|
+
if (generalVoice.assistantAudio.length > 0) {
|
|
2332
|
+
generalAudioPlayer.start().catch(() => {});
|
|
2333
|
+
}
|
|
2334
|
+
render();
|
|
2335
|
+
});
|
|
1390
2336
|
startGuidedButton.addEventListener("click", () => {
|
|
1391
2337
|
startMode("guided");
|
|
1392
2338
|
});
|
|
@@ -1396,9 +2342,16 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1396
2342
|
stopButton.addEventListener("click", () => {
|
|
1397
2343
|
stopMic();
|
|
1398
2344
|
});
|
|
2345
|
+
root.addEventListener("absolute-voice-simulate-disconnect", () => {
|
|
2346
|
+
currentVoice().simulateDisconnect();
|
|
2347
|
+
});
|
|
1399
2348
|
window.addEventListener("beforeunload", () => {
|
|
1400
2349
|
guidedVoice.stopRecording();
|
|
1401
2350
|
generalVoice.stopRecording();
|
|
2351
|
+
guidedBargeInBinding?.close();
|
|
2352
|
+
generalBargeInBinding?.close();
|
|
2353
|
+
guidedAudioPlayer.close();
|
|
2354
|
+
generalAudioPlayer.close();
|
|
1402
2355
|
stopGuidedBinding();
|
|
1403
2356
|
stopGeneralBinding();
|
|
1404
2357
|
guidedVoice.close();
|