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