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